@gtkx/runtime 2.0.0-beta.1 → 2.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -40
- package/dist/descriptors.d.ts +2 -1
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +5 -1
- package/dist/descriptors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +17 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +13 -0
- package/dist/internal.js.map +1 -1
- package/dist/lifecycle.js +1 -1
- package/dist/lifecycle.js.map +1 -1
- package/dist/listeners.d.ts +3 -13
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +12 -12
- package/dist/listeners.js.map +1 -1
- package/dist/mixin.d.ts +5 -3
- package/dist/mixin.d.ts.map +1 -1
- package/dist/mixin.js +36 -9
- package/dist/mixin.js.map +1 -1
- package/dist/object.d.ts +14 -3
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +20 -27
- package/dist/object.js.map +1 -1
- package/dist/properties.d.ts +4 -2
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +69 -16
- package/dist/properties.js.map +1 -1
- package/dist/property-brand.d.ts +5 -0
- package/dist/property-brand.d.ts.map +1 -0
- package/dist/property-brand.js +5 -0
- package/dist/property-brand.js.map +1 -0
- package/dist/register-class.d.ts +102 -20
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +19 -37
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +4 -4
- package/dist/registry.js.map +1 -1
- package/dist/signal-brand.d.ts +12 -0
- package/dist/signal-brand.d.ts.map +1 -0
- package/dist/signal-brand.js +6 -0
- package/dist/signal-brand.js.map +1 -0
- package/dist/signal.d.ts +39 -2
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +75 -5
- package/dist/signal.js.map +1 -1
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +1 -1
- package/dist/type.js.map +1 -1
- package/dist/value.d.ts +2 -1
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +28 -7
- package/dist/value.js.map +1 -1
- package/package.json +7 -5
- package/src/descriptors.ts +7 -0
- package/src/index.ts +2 -2
- package/src/internal.ts +42 -0
- package/src/lifecycle.ts +1 -1
- package/src/listeners.ts +21 -26
- package/src/mixin.ts +55 -9
- package/src/object.ts +57 -7
- package/src/properties.ts +94 -17
- package/src/property-brand.ts +5 -0
- package/src/register-class.ts +296 -95
- package/src/registry.ts +4 -4
- package/src/signal-brand.ts +29 -0
- package/src/signal.ts +177 -6
- package/src/type.ts +1 -1
- package/src/value.ts +40 -5
package/src/listeners.ts
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Connects a handler to a signal and returns its handler id. */
|
|
9
|
-
connect(signal: string, handler: SignalHandler, isAfter?: boolean): number;
|
|
10
|
-
/** Disconnects the handler previously connected under the given id. */
|
|
11
|
-
disconnect(handlerId: number): void;
|
|
12
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
canonicalDetailedSignalName,
|
|
3
|
+
connectSignalByName,
|
|
4
|
+
disconnectSignal,
|
|
5
|
+
isSignalHandlerConnected,
|
|
6
|
+
type SignalHandler,
|
|
7
|
+
} from "./signal.js";
|
|
13
8
|
|
|
14
9
|
const listenerTable: WeakMap<object, Map<string, Map<SignalHandler, number[]>>> = new WeakMap();
|
|
15
10
|
|
|
16
11
|
const findListenerHandlerId = (instance: object, signal: string, handler: SignalHandler): number | undefined =>
|
|
17
|
-
listenerTable.get(instance)?.get(signal)?.get(handler)?.at(-1);
|
|
12
|
+
listenerTable.get(instance)?.get(canonicalDetailedSignalName(signal))?.get(handler)?.at(-1);
|
|
18
13
|
|
|
19
14
|
const trackListener = (instance: object, signal: string, handler: SignalHandler, handlerId: number): void => {
|
|
15
|
+
const key = canonicalDetailedSignalName(signal);
|
|
20
16
|
let bySignal = listenerTable.get(instance);
|
|
21
17
|
|
|
22
18
|
if (!bySignal) {
|
|
@@ -24,11 +20,11 @@ const trackListener = (instance: object, signal: string, handler: SignalHandler,
|
|
|
24
20
|
listenerTable.set(instance, bySignal);
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
let byHandler = bySignal.get(
|
|
23
|
+
let byHandler = bySignal.get(key);
|
|
28
24
|
|
|
29
25
|
if (!byHandler) {
|
|
30
26
|
byHandler = new Map();
|
|
31
|
-
bySignal.set(
|
|
27
|
+
bySignal.set(key, byHandler);
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
let handlerIds = byHandler.get(handler);
|
|
@@ -52,8 +48,9 @@ const removeTrackedHandlerId = (handlerIds: number[], handlerId: number): number
|
|
|
52
48
|
};
|
|
53
49
|
|
|
54
50
|
const untrackHandlerId = (instance: object, signal: string, handlerId: number): void => {
|
|
51
|
+
const key = canonicalDetailedSignalName(signal);
|
|
55
52
|
const bySignal = listenerTable.get(instance);
|
|
56
|
-
const byHandler = bySignal?.get(
|
|
53
|
+
const byHandler = bySignal?.get(key);
|
|
57
54
|
|
|
58
55
|
if (byHandler === undefined) {
|
|
59
56
|
return;
|
|
@@ -66,7 +63,7 @@ const untrackHandlerId = (instance: object, signal: string, handlerId: number):
|
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
if (byHandler.size === 0) {
|
|
69
|
-
bySignal?.delete(
|
|
66
|
+
bySignal?.delete(key);
|
|
70
67
|
}
|
|
71
68
|
};
|
|
72
69
|
|
|
@@ -79,8 +76,8 @@ const untrackHandlerId = (instance: object, signal: string, handlerId: number):
|
|
|
79
76
|
* @param handler The callback invoked on each emission.
|
|
80
77
|
* @param isAfter When true, run the handler after the default handler.
|
|
81
78
|
*/
|
|
82
|
-
function onSignal(instance:
|
|
83
|
-
const handlerId = instance
|
|
79
|
+
function onSignal(instance: object, signal: string, handler: SignalHandler, isAfter?: boolean): void {
|
|
80
|
+
const handlerId = connectSignalByName(instance, signal, handler, isAfter);
|
|
84
81
|
trackListener(instance, signal, handler, handlerId);
|
|
85
82
|
}
|
|
86
83
|
|
|
@@ -93,18 +90,17 @@ function onSignal(instance: SignalConnectable, signal: string, handler: SignalHa
|
|
|
93
90
|
* @param handler The callback invoked on the first emission.
|
|
94
91
|
* @param isAfter When true, run the handler after the default handler.
|
|
95
92
|
*/
|
|
96
|
-
function onceSignal(instance:
|
|
93
|
+
function onceSignal(instance: object, signal: string, handler: SignalHandler, isAfter?: boolean): void {
|
|
97
94
|
let handlerId = 0;
|
|
98
95
|
|
|
99
96
|
const wrapped: SignalHandler = (...args) => {
|
|
100
97
|
untrackHandlerId(instance, signal, handlerId);
|
|
101
|
-
|
|
102
|
-
instance.disconnect(handlerId);
|
|
98
|
+
disconnectSignal(instance, handlerId);
|
|
103
99
|
|
|
104
100
|
return handler(...args);
|
|
105
101
|
};
|
|
106
102
|
|
|
107
|
-
handlerId = instance
|
|
103
|
+
handlerId = connectSignalByName(instance, signal, wrapped, isAfter);
|
|
108
104
|
trackListener(instance, signal, wrapped, handlerId);
|
|
109
105
|
trackListener(instance, signal, handler, handlerId);
|
|
110
106
|
}
|
|
@@ -117,7 +113,7 @@ function onceSignal(instance: SignalConnectable, signal: string, handler: Signal
|
|
|
117
113
|
* @param signal The signal name the handler was connected to.
|
|
118
114
|
* @param handler The handler to disconnect.
|
|
119
115
|
*/
|
|
120
|
-
function offSignal(instance:
|
|
116
|
+
function offSignal(instance: object, signal: string, handler: SignalHandler): void {
|
|
121
117
|
const handlerId = findListenerHandlerId(instance, signal, handler);
|
|
122
118
|
|
|
123
119
|
if (handlerId === undefined) {
|
|
@@ -125,13 +121,12 @@ function offSignal(instance: SignalConnectable, signal: string, handler: SignalH
|
|
|
125
121
|
}
|
|
126
122
|
|
|
127
123
|
untrackHandlerId(instance, signal, handlerId);
|
|
128
|
-
untrackConnection(instance, handlerId);
|
|
129
124
|
|
|
130
125
|
if (!isSignalHandlerConnected(instance, handlerId)) {
|
|
131
126
|
return;
|
|
132
127
|
}
|
|
133
128
|
|
|
134
|
-
instance
|
|
129
|
+
disconnectSignal(instance, handlerId);
|
|
135
130
|
}
|
|
136
131
|
|
|
137
132
|
export { onSignal, onceSignal, offSignal };
|
package/src/mixin.ts
CHANGED
|
@@ -17,6 +17,12 @@ type MixinReceiver = {
|
|
|
17
17
|
*/
|
|
18
18
|
type Mixin = (base: AnyClass<MixinReceiver>) => AnyClass;
|
|
19
19
|
|
|
20
|
+
const syntheticSignalMembers: WeakMap<object, ReadonlySet<string>> = new WeakMap();
|
|
21
|
+
|
|
22
|
+
function markSyntheticSignalMembers(klass: AnyClass, names: readonly string[]): void {
|
|
23
|
+
syntheticSignalMembers.set(klass.prototype, new Set(names));
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
function isDefinedInClassChain(prototype: object, key: string): boolean {
|
|
21
27
|
let current: object | null = prototype;
|
|
22
28
|
|
|
@@ -31,12 +37,28 @@ function isDefinedInClassChain(prototype: object, key: string): boolean {
|
|
|
31
37
|
return false;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
function
|
|
40
|
+
function isNaturalInClassChain(prototype: object, key: string): boolean {
|
|
41
|
+
let current: object | null = prototype;
|
|
42
|
+
|
|
43
|
+
while (current !== null && current !== Object.prototype) {
|
|
44
|
+
if (Object.hasOwn(current, key)) {
|
|
45
|
+
return syntheticSignalMembers.get(current)?.has(key) !== true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
current = Reflect.getPrototypeOf(current);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function copyLayerMember(target: AnyClass, layer: object, key: string, inheritedOverrides: Set<string>): void {
|
|
35
55
|
if (key === "constructor") {
|
|
36
56
|
return;
|
|
37
57
|
}
|
|
38
58
|
|
|
39
|
-
|
|
59
|
+
const isProtectedInherited = !inheritedOverrides.has(key) && isDefinedInClassChain(target.prototype, key);
|
|
60
|
+
|
|
61
|
+
if (isProtectedInherited || Object.hasOwn(target.prototype, key)) {
|
|
40
62
|
return;
|
|
41
63
|
}
|
|
42
64
|
|
|
@@ -47,9 +69,9 @@ function copyLayerMember(target: AnyClass, layer: object, key: string): void {
|
|
|
47
69
|
}
|
|
48
70
|
}
|
|
49
71
|
|
|
50
|
-
function copyLayerMembers(target: AnyClass, layer: object): void {
|
|
72
|
+
function copyLayerMembers(target: AnyClass, layer: object, inheritedOverrides: Set<string> = new Set()): void {
|
|
51
73
|
for (const key of Object.getOwnPropertyNames(layer)) {
|
|
52
|
-
copyLayerMember(target, layer, key);
|
|
74
|
+
copyLayerMember(target, layer, key, inheritedOverrides);
|
|
53
75
|
}
|
|
54
76
|
}
|
|
55
77
|
|
|
@@ -60,7 +82,8 @@ function copyLayerMembers(target: AnyClass, layer: object): void {
|
|
|
60
82
|
* @param target The class whose prototype receives the mixin members.
|
|
61
83
|
* @param mixins The mixins to apply, in order.
|
|
62
84
|
*/
|
|
63
|
-
function installMixins(target: AnyClass, mixins: Mixin[]): void {
|
|
85
|
+
function installMixins(target: AnyClass, mixins: Mixin[], inheritedOverrideNames: Iterable<string> = []): void {
|
|
86
|
+
const inheritedOverrides = new Set(inheritedOverrideNames);
|
|
64
87
|
const empty: AnyClass<MixinReceiver> = class {
|
|
65
88
|
connect(): number {
|
|
66
89
|
return 0;
|
|
@@ -72,7 +95,7 @@ function installMixins(target: AnyClass, mixins: Mixin[]): void {
|
|
|
72
95
|
};
|
|
73
96
|
|
|
74
97
|
for (const mixin of mixins) {
|
|
75
|
-
copyLayerMembers(target, mixin(empty).prototype);
|
|
98
|
+
copyLayerMembers(target, mixin(empty).prototype, inheritedOverrides);
|
|
76
99
|
}
|
|
77
100
|
}
|
|
78
101
|
|
|
@@ -82,11 +105,34 @@ function dropLayerMembers(layer: AnyClass, names: Set<string>): void {
|
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
107
|
|
|
108
|
+
function createMixinLayer(parent: AnyClass<MixinReceiver>, mixin: Mixin, inheritedNames: Set<string>): AnyClass {
|
|
109
|
+
const layer = mixin(parent);
|
|
110
|
+
const protectedNames = new Set(inheritedNames);
|
|
111
|
+
|
|
112
|
+
for (const key of Object.getOwnPropertyNames(layer.prototype)) {
|
|
113
|
+
if (key !== "constructor" && isNaturalInClassChain(parent.prototype, key)) {
|
|
114
|
+
protectedNames.add(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
dropLayerMembers(layer, protectedNames);
|
|
119
|
+
|
|
120
|
+
return layer;
|
|
121
|
+
}
|
|
122
|
+
|
|
85
123
|
function insertMixinLayer(target: AnyClass, mixin: Mixin, inheritedNames: Set<string>): void {
|
|
86
|
-
const
|
|
87
|
-
|
|
124
|
+
const parent = getParentClass(target) as AnyClass<MixinReceiver>;
|
|
125
|
+
const layer = createMixinLayer(parent, mixin, inheritedNames);
|
|
88
126
|
Object.setPrototypeOf(target.prototype, layer.prototype);
|
|
89
127
|
Object.setPrototypeOf(target, layer);
|
|
90
128
|
}
|
|
91
129
|
|
|
92
|
-
export {
|
|
130
|
+
export {
|
|
131
|
+
copyLayerMembers,
|
|
132
|
+
createMixinLayer,
|
|
133
|
+
insertMixinLayer,
|
|
134
|
+
installMixins,
|
|
135
|
+
markSyntheticSignalMembers,
|
|
136
|
+
type MixinReceiver,
|
|
137
|
+
type Mixin,
|
|
138
|
+
};
|
package/src/object.ts
CHANGED
|
@@ -3,9 +3,16 @@ import { type AnyClass, getParentClass } from "@gtkx/utils";
|
|
|
3
3
|
import { bind } from "./bind.js";
|
|
4
4
|
import { objectT, stringT, voidT } from "./descriptors.js";
|
|
5
5
|
import { LIB, VALUE_T } from "./library.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
coercePropertyValue,
|
|
8
|
+
type ConstructProperty,
|
|
9
|
+
constructPropertyFor,
|
|
10
|
+
readableObjectPropertyFor,
|
|
11
|
+
writableObjectPropertyFor,
|
|
12
|
+
} from "./properties.js";
|
|
13
|
+
import { propertyMapOverride, writablePropertyMapOverride } from "./property-brand.js";
|
|
7
14
|
import { getHandle, registerWrapper } from "./registry.js";
|
|
8
|
-
import { fromValueForDescriptor, newValueForDescriptor, toValue } from "./value.js";
|
|
15
|
+
import { fromObjectPropertyValue, fromValueForDescriptor, newValueForDescriptor, toValue } from "./value.js";
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* One construct property a wrapper class accepts: the canonical `GObject` name it is set under,
|
|
@@ -172,7 +179,28 @@ function newObjectWithProperties<T extends object>(gtype: bigint, props: object,
|
|
|
172
179
|
* @param propertyName The property name.
|
|
173
180
|
* @param descriptor Describes the property's type.
|
|
174
181
|
*/
|
|
175
|
-
function
|
|
182
|
+
function getProperty<
|
|
183
|
+
TObject extends { __properties__: object },
|
|
184
|
+
TPropertyMap extends object = TObject extends { [propertyMapOverride]?: infer TResolver }
|
|
185
|
+
? TResolver extends () => infer TMap
|
|
186
|
+
? Extract<NonNullable<TMap>, object>
|
|
187
|
+
: TObject["__properties__"]
|
|
188
|
+
: TObject["__properties__"],
|
|
189
|
+
TName extends Extract<keyof NoInfer<TPropertyMap>, string> = Extract<keyof NoInfer<TPropertyMap>, string>,
|
|
190
|
+
>(obj: TObject, propertyName: TName): NoInfer<TPropertyMap>[TName];
|
|
191
|
+
function getProperty(obj: object, propertyName: string, descriptor: Descriptor): unknown;
|
|
192
|
+
function getProperty(obj: object, propertyName: string, descriptor?: Descriptor): unknown {
|
|
193
|
+
if (arguments.length === 2) {
|
|
194
|
+
const property = readableObjectPropertyFor(obj, propertyName);
|
|
195
|
+
gObjectGetProperty(getHandle(obj), property.name, property.value);
|
|
196
|
+
|
|
197
|
+
return fromObjectPropertyValue(property.value);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (descriptor === undefined) {
|
|
201
|
+
throw new TypeError("getProperty requires a property descriptor when called with three arguments");
|
|
202
|
+
}
|
|
203
|
+
|
|
176
204
|
const value = newValueForDescriptor(descriptor);
|
|
177
205
|
gObjectGetProperty(getHandle(obj), propertyName, value);
|
|
178
206
|
|
|
@@ -192,15 +220,37 @@ function getObjectProperty(obj: object, propertyName: string, descriptor: Descri
|
|
|
192
220
|
* @param descriptor Describes the property's type.
|
|
193
221
|
* @param jsValue The value to set.
|
|
194
222
|
*/
|
|
195
|
-
function
|
|
196
|
-
|
|
223
|
+
function setProperty<
|
|
224
|
+
TObject extends { __writableProperties__: object },
|
|
225
|
+
TPropertyMap extends object = TObject extends { [writablePropertyMapOverride]?: infer TResolver }
|
|
226
|
+
? TResolver extends () => infer TMap
|
|
227
|
+
? Extract<NonNullable<TMap>, object>
|
|
228
|
+
: TObject["__writableProperties__"]
|
|
229
|
+
: TObject["__writableProperties__"],
|
|
230
|
+
TName extends Extract<keyof NoInfer<TPropertyMap>, string> = Extract<keyof NoInfer<TPropertyMap>, string>,
|
|
231
|
+
>(obj: TObject, propertyName: TName, jsValue: NoInfer<TPropertyMap>[TName]): void;
|
|
232
|
+
function setProperty(obj: object, propertyName: string, descriptor: Descriptor, jsValue: unknown): void;
|
|
233
|
+
function setProperty(
|
|
234
|
+
obj: object,
|
|
235
|
+
propertyName: string,
|
|
236
|
+
descriptorOrValue: unknown,
|
|
237
|
+
jsValue?: unknown,
|
|
238
|
+
): void {
|
|
239
|
+
if (arguments.length === 3) {
|
|
240
|
+
const property = writableObjectPropertyFor(obj, propertyName, descriptorOrValue);
|
|
241
|
+
gObjectSetProperty(getHandle(obj), property.name, property.value);
|
|
242
|
+
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
gObjectSetProperty(getHandle(obj), propertyName, toValue(descriptorOrValue as Descriptor, jsValue));
|
|
197
247
|
}
|
|
198
248
|
|
|
199
249
|
export {
|
|
200
250
|
newObjectWithProperties,
|
|
201
|
-
|
|
251
|
+
getProperty,
|
|
202
252
|
registerConstructProperties,
|
|
203
|
-
|
|
253
|
+
setProperty,
|
|
204
254
|
type ConstructBinding,
|
|
205
255
|
type ConstructBindings,
|
|
206
256
|
};
|
package/src/properties.ts
CHANGED
|
@@ -55,7 +55,7 @@ type AccessorBase = PropertyCheck & {
|
|
|
55
55
|
type DeclaredAccessor = AccessorBase & {
|
|
56
56
|
isInterfaceProperty?: false;
|
|
57
57
|
memberName: string;
|
|
58
|
-
|
|
58
|
+
hasMemberAccessor: boolean;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
type InterfaceAccessor = AccessorBase & { isInterfaceProperty: true };
|
|
@@ -100,7 +100,7 @@ const paramSpecDefaultValue = bind(LIB, "g_param_spec_get_default_value", [PARAM
|
|
|
100
100
|
const paramSpecName = bind(LIB, "g_param_spec_get_name", [PARAM_T], stringT("borrowed"));
|
|
101
101
|
const typeClassRef = bind(LIB, "g_type_class_ref", [biguint64T], CLASS_T);
|
|
102
102
|
const typeClassUnref = bind(LIB, "g_type_class_unref", [CLASS_T], voidT);
|
|
103
|
-
const
|
|
103
|
+
const propertyChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
|
|
104
104
|
const classFindProperty = bind(LIB, "g_object_class_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
105
105
|
|
|
106
106
|
const classListProperties = bind(
|
|
@@ -214,8 +214,11 @@ const findSourceSpec = (source: bigint | AnyClass, name: string): ExternalObject
|
|
|
214
214
|
* @param source The GType, wrapper class or interface declaring the property.
|
|
215
215
|
* @returns Handle of the override spec.
|
|
216
216
|
*/
|
|
217
|
-
const newParamSpecOverride = (name: string, source: bigint | AnyClass): ExternalObject<Handle> =>
|
|
218
|
-
|
|
217
|
+
const newParamSpecOverride = (name: string, source: bigint | AnyClass): ExternalObject<Handle> => {
|
|
218
|
+
const canonicalName = canonicalCase(name);
|
|
219
|
+
|
|
220
|
+
return paramSpecOverrideNew(canonicalName, findSourceSpec(source, canonicalName)) as ExternalObject<Handle>;
|
|
221
|
+
};
|
|
219
222
|
|
|
220
223
|
function describeValue(value: unknown): string {
|
|
221
224
|
if (typeof value === "string") {
|
|
@@ -283,6 +286,24 @@ function assertWritable(instance: object, check: PropertyCheck, value: unknown):
|
|
|
283
286
|
throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, READ_ONLY_REASON)));
|
|
284
287
|
}
|
|
285
288
|
|
|
289
|
+
function assertReadable(instance: object, check: PropertyCheck): void {
|
|
290
|
+
if (isParamReadable(check.flags)) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
throw new TypeError(
|
|
295
|
+
propertyMessage(instance, check, `cannot read property '${check.propertyName}'; the property is write-only`),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function assertMutable(instance: object, check: PropertyCheck, value: unknown): void {
|
|
300
|
+
assertWritable(instance, check, value);
|
|
301
|
+
|
|
302
|
+
if (isParamConstructOnly(check.flags)) {
|
|
303
|
+
throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, CONSTRUCT_ONLY_REASON)));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
286
307
|
function writeHeld(check: PropertyCheck, gValue: ExternalObject<Handle>, value: unknown): void {
|
|
287
308
|
check.write ??= valueWriterFor(check.valueType);
|
|
288
309
|
check.write(gValue, check.narrowValue(value));
|
|
@@ -333,7 +354,7 @@ function isReadableProperty(gtype: bigint, propertyName: string): boolean {
|
|
|
333
354
|
}
|
|
334
355
|
}
|
|
335
356
|
|
|
336
|
-
function
|
|
357
|
+
function lookupPropertyCheck(gtype: bigint, name: string): PropertyCheck | null {
|
|
337
358
|
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
338
359
|
|
|
339
360
|
try {
|
|
@@ -345,10 +366,35 @@ function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null
|
|
|
345
366
|
}
|
|
346
367
|
}
|
|
347
368
|
|
|
348
|
-
function
|
|
349
|
-
const checks =
|
|
369
|
+
function propertyCheckFor(gtype: bigint, name: string): PropertyCheck | null {
|
|
370
|
+
const checks = propertyChecks.getOrInsertComputed(gtype, () => new Map<string, PropertyCheck | null>());
|
|
371
|
+
|
|
372
|
+
return checks.getOrInsertComputed(name, () => lookupPropertyCheck(gtype, name));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function objectPropertyCheckFor(instance: object, name: string): PropertyCheck {
|
|
376
|
+
const gtype = getInstanceType(instance);
|
|
377
|
+
const check = typeIsA(gtype, TYPE_OBJECT) ? propertyCheckFor(gtype, name) : null;
|
|
378
|
+
|
|
379
|
+
if (check === null) {
|
|
380
|
+
throw new TypeError(`${instanceClassName(instance)} has no GObject property named '${name}'`);
|
|
381
|
+
}
|
|
350
382
|
|
|
351
|
-
return
|
|
383
|
+
return check;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function readableObjectPropertyFor(instance: object, name: string): ConstructProperty {
|
|
387
|
+
const check = objectPropertyCheckFor(instance, name);
|
|
388
|
+
assertReadable(instance, check);
|
|
389
|
+
|
|
390
|
+
return { name: check.propertyName, value: newValueForType(check.valueType) };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function writableObjectPropertyFor(instance: object, name: string, value: unknown): ConstructProperty {
|
|
394
|
+
const check = objectPropertyCheckFor(instance, name);
|
|
395
|
+
assertMutable(instance, check, value);
|
|
396
|
+
|
|
397
|
+
return { name: check.propertyName, value: checkedValueFor(instance, check, value) };
|
|
352
398
|
}
|
|
353
399
|
|
|
354
400
|
function truncateToWhole(check: PropertyCheck, value: number): number {
|
|
@@ -384,7 +430,7 @@ function coercePropertyValue(gtype: bigint, propertyName: string, value: unknown
|
|
|
384
430
|
return value;
|
|
385
431
|
}
|
|
386
432
|
|
|
387
|
-
const check =
|
|
433
|
+
const check = propertyCheckFor(gtype, propertyName);
|
|
388
434
|
|
|
389
435
|
return check === null ? value : coerceNumber(check, value);
|
|
390
436
|
}
|
|
@@ -499,12 +545,35 @@ function checkedSetter(accessor: DeclaredAccessor): (this: object, value: unknow
|
|
|
499
545
|
};
|
|
500
546
|
}
|
|
501
547
|
|
|
502
|
-
function
|
|
503
|
-
|
|
548
|
+
function memberDescriptorFor(prototype: object, name: string): PropertyDescriptor | undefined {
|
|
549
|
+
let current: object | null = prototype;
|
|
550
|
+
|
|
551
|
+
while (current !== null) {
|
|
552
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, name);
|
|
553
|
+
|
|
554
|
+
if (descriptor !== undefined) {
|
|
555
|
+
return descriptor;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
current = Reflect.getPrototypeOf(current);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return undefined;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function isMemberAccessor(descriptor: PropertyDescriptor | undefined): boolean {
|
|
565
|
+
return descriptor !== undefined && ("get" in descriptor || "set" in descriptor);
|
|
504
566
|
}
|
|
505
567
|
|
|
506
568
|
function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias: string): void {
|
|
507
|
-
if (
|
|
569
|
+
if (Object.getOwnPropertyDescriptor(prototype, alias) !== undefined) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const parent = Reflect.getPrototypeOf(prototype);
|
|
574
|
+
const inherited = parent === null ? undefined : memberDescriptorFor(parent, alias);
|
|
575
|
+
|
|
576
|
+
if (inherited !== undefined && !isMemberAccessor(inherited)) {
|
|
508
577
|
return;
|
|
509
578
|
}
|
|
510
579
|
|
|
@@ -513,13 +582,13 @@ function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias
|
|
|
513
582
|
|
|
514
583
|
function installAccessors(klass: AnyClass, accessor: DeclaredAccessor): void {
|
|
515
584
|
const prototype = (klass as { prototype: object }).prototype;
|
|
516
|
-
accessor.
|
|
585
|
+
accessor.hasMemberAccessor = isMemberAccessor(Object.getOwnPropertyDescriptor(prototype, accessor.memberName));
|
|
517
586
|
|
|
518
587
|
const descriptor: PropertyDescriptor = {
|
|
519
588
|
configurable: true,
|
|
520
589
|
enumerable: true,
|
|
521
|
-
get: accessor.
|
|
522
|
-
set: accessor.
|
|
590
|
+
get: accessor.hasMemberAccessor ? memberGetter(accessor) : storedGetter(accessor),
|
|
591
|
+
set: accessor.hasMemberAccessor ? memberSetter(accessor) : checkedSetter(accessor),
|
|
523
592
|
};
|
|
524
593
|
|
|
525
594
|
for (const alias of accessorNames(accessor.name)) {
|
|
@@ -586,7 +655,13 @@ function callMember(instance: object, member: string, args: unknown[]): unknown
|
|
|
586
655
|
}
|
|
587
656
|
|
|
588
657
|
function backingMemberFor(instance: object, accessor: DeclaredAccessor): string | undefined {
|
|
589
|
-
if (accessor.
|
|
658
|
+
if (accessor.hasMemberAccessor) {
|
|
659
|
+
return accessor.memberName;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const descriptor = Object.getOwnPropertyDescriptor(instance, accessor.memberName);
|
|
663
|
+
|
|
664
|
+
if (descriptor === undefined || ("value" in descriptor && typeof descriptor.value === "function")) {
|
|
590
665
|
return undefined;
|
|
591
666
|
}
|
|
592
667
|
|
|
@@ -686,7 +761,7 @@ function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): Decl
|
|
|
686
761
|
...checkFor(getHandle(pspec), name),
|
|
687
762
|
memberName: camelCase(name),
|
|
688
763
|
storage: Symbol(`gtkx:property:${name}`),
|
|
689
|
-
|
|
764
|
+
hasMemberAccessor: false,
|
|
690
765
|
};
|
|
691
766
|
|
|
692
767
|
installAccessors(klass, accessor);
|
|
@@ -770,8 +845,10 @@ export {
|
|
|
770
845
|
makeGetProperty,
|
|
771
846
|
makeSetProperty,
|
|
772
847
|
newParamSpecOverride,
|
|
848
|
+
readableObjectPropertyFor,
|
|
773
849
|
SET_PROPERTY_VFUNC,
|
|
774
850
|
toNativeProperties,
|
|
851
|
+
writableObjectPropertyFor,
|
|
775
852
|
type ConstructProperty,
|
|
776
853
|
type PropertyDispatch,
|
|
777
854
|
type PropertySpec,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const propertyMapOverride: unique symbol = Symbol("gtkx.propertyMapOverride");
|
|
2
|
+
const writablePropertyMapOverride: unique symbol = Symbol("gtkx.writablePropertyMapOverride");
|
|
3
|
+
const descriptorFreePropertySpec: unique symbol = Symbol("gtkx.descriptorFreePropertySpec");
|
|
4
|
+
|
|
5
|
+
export { descriptorFreePropertySpec, propertyMapOverride, writablePropertyMapOverride };
|