@qooxdoo/framework 8.0.0-beta.2 → 8.0.0-beta.3

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/Manifest.json +1 -1
  3. package/lib/compiler/compile-info.json +42 -43
  4. package/lib/compiler/index.js +555 -645
  5. package/package.json +1 -1
  6. package/source/class/qx/Class.js +23 -4
  7. package/source/class/qx/Mixin.js +15 -6
  8. package/source/class/qx/core/check/AbstractCheck.js +5 -1
  9. package/source/class/qx/core/check/CheckFactory.js +6 -0
  10. package/source/class/qx/core/check/DynamicTypeCheck.js +5 -0
  11. package/source/class/qx/core/property/ExplicitPropertyStorage.js +7 -19
  12. package/source/class/qx/core/property/IPropertyStorage.js +2 -21
  13. package/source/class/qx/core/property/Property.js +96 -72
  14. package/source/class/qx/core/property/SimplePropertyStorage.js +2 -18
  15. package/source/class/qx/data/MBinding.js +1 -1
  16. package/source/class/qx/data/SingleValueBinding.js +58 -100
  17. package/source/class/qx/data/binding/AbstractSegment.js +15 -10
  18. package/source/class/qx/data/binding/PropNameSegment.js +34 -11
  19. package/source/class/qx/test/Mixin.js +219 -0
  20. package/source/class/qx/test/Promise.js +10 -11
  21. package/source/class/qx/test/core/Property.js +22 -16
  22. package/source/class/qx/test/data/singlevalue/Async.js +17 -4
  23. package/source/class/qx/test/performance/Property.js +0 -1
  24. package/source/class/qx/test/ui/core/SingleSelectionManager.js +150 -0
  25. package/source/class/qx/tool/compiler/cli/api/CompilerApi.js +1 -2
  26. package/source/class/qx/ui/core/SingleSelectionManager.js +4 -4
  27. package/source/class/qx/ui/form/validation/Manager.js +1 -1
  28. package/source/resource/qx/decoration/Modern/table/boolean-false.png +0 -0
  29. package/source/resource/qx/decoration/Modern/table/boolean-true.png +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qooxdoo/framework",
3
- "version": "8.0.0-beta.2",
3
+ "version": "8.0.0-beta.3",
4
4
  "description": "The JS Framework for Coders",
5
5
  "author": "The qooxdoo project",
6
6
  "keywords": [
@@ -75,7 +75,6 @@ qx.Bootstrap.define("qx.Class", {
75
75
  event: ["string", "object"], // String or null
76
76
  check: null, // Array, String, Function
77
77
  transform: null, // String, Function
78
- async: "boolean", // Boolean
79
78
  deferredInit: "boolean", // Boolean
80
79
  validate: ["string", "function"], // String, Function
81
80
  isEqual: ["string", "function"], // String, Function
@@ -803,10 +802,18 @@ qx.Bootstrap.define("qx.Class", {
803
802
  * @param members {Map}
804
803
  * The map of members to attach
805
804
  *
805
+ * @param events {Map?}
806
+ * The map of events declared by the mixin (used for member validation)
807
+ *
806
808
  * @param patch {Boolean ? false}
807
809
  * Enable patching
810
+ *
811
+ * @param mixin {qx.Mixin?}
812
+ * The mixin being applied, if any. Used to store per-mixin-per-class base
813
+ * method references so that super calls resolve correctly when the same
814
+ * mixin is included in multiple classes.
808
815
  */
809
- addMembers(clazz, members, events, patch) {
816
+ addMembers(clazz, members, events, patch, mixin) {
810
817
  let proto = clazz.prototype;
811
818
  let classOwnMembers = {}; // Track class members to restore after mixin addition
812
819
 
@@ -906,6 +913,18 @@ qx.Bootstrap.define("qx.Class", {
906
913
  member.self = clazz;
907
914
  }
908
915
  member.base = clazz.prototype[key];
916
+ // Fix: per-mixin-per-class storage to prevent clobbering when the same mixin
917
+ // is included/patched into multiple classes, or when multiple mixins override
918
+ // the same method in the same class.
919
+ if (mixin) {
920
+ if (!clazz.$mixinBases) {
921
+ clazz.$mixinBases = new Map();
922
+ }
923
+ if (!clazz.$mixinBases.has(mixin)) {
924
+ clazz.$mixinBases.set(mixin, {});
925
+ }
926
+ clazz.$mixinBases.get(mixin)[key] = clazz.prototype[key];
927
+ }
909
928
  }
910
929
 
911
930
  // Create the storage for this member
@@ -1127,7 +1146,7 @@ qx.Bootstrap.define("qx.Class", {
1127
1146
 
1128
1147
  // Attach members
1129
1148
  if (entry.$$members) {
1130
- qx.Class.addMembers(clazz, entry.$$members, entry.$$events, patch);
1149
+ qx.Class.addMembers(clazz, entry.$$members, entry.$$events, patch, entry);
1131
1150
  }
1132
1151
 
1133
1152
  // Attach properties
@@ -1875,7 +1894,7 @@ qx.Bootstrap.define("qx.Class", {
1875
1894
  */
1876
1895
  isPropertyInitialized(object, name) {
1877
1896
  let property = object.constructor.prototype.$$allProperties[name];
1878
- return !!(property && property.isInitialized(object));
1897
+ return !!(property && property.hasLocalValue(object));
1879
1898
  },
1880
1899
 
1881
1900
  /**
@@ -348,7 +348,7 @@ qx.Bootstrap.define("qx.Mixin", {
348
348
  // implementation is the first mixin's method
349
349
  for (var i = mixedInIndex - 1; i > -1; i--) {
350
350
  var peerMixin = mixedInAt.$$flatIncludes[i];
351
- if (peerMixin.$$members[methodName]) {
351
+ if (peerMixin.$$members && peerMixin.$$members[methodName]) {
352
352
  fn = peerMixin.$$members[methodName];
353
353
  break;
354
354
  }
@@ -356,12 +356,21 @@ qx.Bootstrap.define("qx.Mixin", {
356
356
 
357
357
  // Try looking in the class itself
358
358
  if (!fn && mixedInAt.prototype[methodName]) {
359
- fn = mixedInAt.prototype[methodName];
360
- for (let i = 0; i < mixedInAt.$$flatIncludes.length; i++) {
361
- if (!mixedInAt.$$flatIncludes[i].$$members[methodName]) {
362
- continue;
359
+ // Use per-mixin-per-class storage to avoid clobbering when the same mixin
360
+ // is included in multiple classes or when multiple mixins override the same method
361
+ if (mixedInAt.$mixinBases && mixedInAt.$mixinBases.has(mixin)) {
362
+ fn = mixedInAt.$mixinBases.get(mixin)[methodName] ?? null;
363
+ }
364
+ if (!fn) {
365
+ // Fallback: traverse .base chain (works for patch=true since each patch
366
+ // creates a unique wrapper function with its own .base property)
367
+ fn = mixedInAt.prototype[methodName];
368
+ for (let i = 0; i < mixedInAt.$$flatIncludes.length; i++) {
369
+ if (!mixedInAt.$$flatIncludes[i].$$members || !mixedInAt.$$flatIncludes[i].$$members[methodName]) {
370
+ continue;
371
+ }
372
+ fn = fn.base;
363
373
  }
364
- fn = fn.base;
365
374
  }
366
375
  }
367
376
 
@@ -23,13 +23,17 @@ qx.Bootstrap.define("qx.core.check.AbstractCheck", {
23
23
  extend: Object,
24
24
  implement: qx.core.check.ICheck,
25
25
 
26
+ /**
27
+ *
28
+ * @param {boolean} nullable
29
+ */
26
30
  construct(nullable) {
27
31
  super();
28
32
  this.__nullable = !!nullable;
29
33
  },
30
34
 
31
35
  members: {
32
- /** @type{Boolean} whether null is allowed */
36
+ /** @type {Boolean} whether null is allowed */
33
37
  __nullable: null,
34
38
 
35
39
  /**
@@ -68,6 +68,12 @@ qx.Bootstrap.define("qx.core.check.CheckFactory", {
68
68
  expr = expr.replace(/<.*>/g, ""); // remove generics
69
69
 
70
70
  let checkname = expr;
71
+ if (checkname.includes("||")) {
72
+ //For now, if the typename contains ||, we just accept it.
73
+ //That's what it was like in version 7
74
+ return new qx.core.check.Any();
75
+ }
76
+
71
77
  if (checkname.endsWith("?")) {
72
78
  checkname = checkname.substring(0, checkname.length - 1);
73
79
  nullable = true;
@@ -22,6 +22,11 @@
22
22
  qx.Bootstrap.define("qx.core.check.DynamicTypeCheck", {
23
23
  extend: qx.core.check.AbstractCheck,
24
24
 
25
+ /**
26
+ *
27
+ * @param {string} typename
28
+ * @param {boolean} nullable
29
+ */
25
30
  construct(typename, nullable) {
26
31
  super(nullable);
27
32
  this.__typename = typename;
@@ -25,16 +25,10 @@ qx.Bootstrap.define("qx.core.property.ExplicitPropertyStorage", {
25
25
 
26
26
  /**
27
27
  * @param {qx.core.property.Property} property
28
- * @param {Function} clazz Qooxdoo class which the property relates to
29
28
  */
30
- construct(property, clazz) {
29
+ construct(property) {
31
30
  super();
32
- let def = property.getDefinition();
33
31
  this.__property = property;
34
- this.__get = def.get;
35
- this.__getAsync = def.getAsync || def.get;
36
- this.__set = def.set;
37
- this.__setAsync = def.setAsync || def.set;
38
32
  },
39
33
 
40
34
  members: {
@@ -42,32 +36,26 @@ qx.Bootstrap.define("qx.core.property.ExplicitPropertyStorage", {
42
36
  * @type {qx.core.property.Property}
43
37
  */
44
38
  __property: null,
39
+
45
40
  /**
46
41
  * @Override
47
42
  */
48
43
  get(thisObj, property) {
49
- return this.__get.call(thisObj, property, thisObj);
44
+ return this.__property.getDefinition().get.call(thisObj, property, thisObj);
50
45
  },
51
46
 
52
47
  /**
53
48
  * @Override
54
49
  */
55
50
  getAsync(thisObj, property) {
56
- return this.__getAsync.call(thisObj, property, thisObj);
51
+ return this.__property.getDefinition().getAsync.call(thisObj, property, thisObj);
57
52
  },
58
53
 
59
54
  /**
60
55
  * @Override
61
56
  */
62
57
  set(thisObj, property, value) {
63
- this.__set.call(thisObj, value, property, thisObj);
64
- },
65
-
66
- /**
67
- * @Override
68
- */
69
- setAsync(thisObj, property, value) {
70
- return this.__setAsync.call(thisObj, property, value);
58
+ this.__property.getDefinition().set.call(thisObj, value, property, thisObj);
71
59
  },
72
60
 
73
61
  /**
@@ -80,8 +68,8 @@ qx.Bootstrap.define("qx.core.property.ExplicitPropertyStorage", {
80
68
  /**
81
69
  * @Override
82
70
  */
83
- isAsyncStorage() {
84
- return this.__property.isAsync();
71
+ supportsGetAsync() {
72
+ return !!this.__property.getDefinition().getAsync;
85
73
  }
86
74
  }
87
75
  });
@@ -48,25 +48,6 @@ qx.Interface.define("qx.core.property.IPropertyStorage", {
48
48
  */
49
49
  set(thisObj, property, value) {},
50
50
 
51
- /**
52
- * Sets a value asynchronously
53
- *
54
- * @param {qx.core.Object} thisObj
55
- * @param {qx.core.propety.IProperty} property the property to set the value of
56
- * @param {*} value
57
- */
58
- async setAsync(thisObj, property, value) {},
59
-
60
- /**
61
- * Called when the property is reset; for standard properties, this just passes through to
62
- * `set`, but pseudo properties may need to call the implemented `reset` method
63
- *
64
- * @param {qx.core.Object} thisObj
65
- * @param {qx.core.propety.IProperty} property the property to set the value of
66
- * @param {*} value
67
- */
68
- reset(thisObj, property, value) {},
69
-
70
51
  /**
71
52
  * Deletes the value
72
53
  *
@@ -76,8 +57,8 @@ qx.Interface.define("qx.core.property.IPropertyStorage", {
76
57
  dereference(thisObj, property) {},
77
58
 
78
59
  /**
79
- * Returns whether the storage supports asynchronous backing, ie the getter could be async
60
+ * Returns whether the storage supports asynchronous getting.
80
61
  */
81
- isAsyncStorage() {}
62
+ supportsGetAsync() {}
82
63
  }
83
64
  });
@@ -41,19 +41,20 @@ qx.Bootstrap.define("qx.core.property.Property", {
41
41
  * will return null, instead of throwing an error.
42
42
  */
43
43
  "qx.core.property.Property.inheritableDefaultIsNull": false,
44
+
44
45
  /**
45
46
  * If set to true, then properties with init values will have their apply method called during construction.
46
- * Default is false to maintain backward compatibility with v7 behavior.
47
- */
48
- "qx.core.property.Property.applyDuringConstruct": false,//TODO should this be true?
47
+ * You should only set this to false if you have a lot of legacy code which would take too much time/effort to modify to work with the new behavior.
48
+ */
49
+ "qx.core.property.Property.applyDuringConstruct": true,
49
50
 
50
51
  /**
51
52
  * Only relevant when applyDuringConstruct is true.
52
53
  * This contains regexes matching classnames which are excluded from the auto apply behaviour.
53
54
  * They refer to concrete classes only, not the superclasses.
54
55
  *
55
- * Currently (2025-12-03), only "qx." classes are excluded because enabling applyDuringConstruct
56
- * would create problems which are difficult to fix.
56
+ * Currently (2025-DEC-03), only "qx." classes are excluded because enabling applyDuringConstruct
57
+ * would create problems which are too difficult and time-consuming to fix.
57
58
  *
58
59
  * @type {Array<RegExp | string>}
59
60
  */
@@ -189,10 +190,11 @@ qx.Bootstrap.define("qx.core.property.Property", {
189
190
 
190
191
  // Figure out the storage implementation
191
192
  if (def.storage) {
193
+ //if it's an object, use it directly as storage
192
194
  if (qx.Class.hasInterface(def.storage.constructor, qx.core.property.IPropertyStorage)) {
193
195
  this.__storage = def.storage;
194
196
  } else {
195
- this.__storage = new def.storage();
197
+ this.__storage = qx.core.property.PropertyStorageFactory.getStorage(def.storage);
196
198
  }
197
199
  } else {
198
200
  if (def.immutable == "replace") {
@@ -201,7 +203,7 @@ qx.Bootstrap.define("qx.core.property.Property", {
201
203
  } else if (def.check == "Object") {
202
204
  this.__storage = qx.core.property.PropertyStorageFactory.getStorage(qx.core.property.ImmutableObjectStorage);
203
205
  } else if (def.check == "qx.data.Array") {
204
- this.__storage = qx.core.property.PropertyStorageFactory.getStorage(qx.core.property.ImmutableDataArrayStorage);
206
+ this.__storage = qx.core.property.PropertyStorageFactory.getStorage(qx.core.property.ImmutableDataArrayStorage);//cbh
205
207
  } else {
206
208
  throw new Error(
207
209
  `${this}: ` + "only `check : 'Array'` and `check : 'Object'` " + "properties may have `immutable : 'replace'`."
@@ -209,7 +211,7 @@ qx.Bootstrap.define("qx.core.property.Property", {
209
211
  }
210
212
  } else {
211
213
  if (typeof def.get == "function" || typeof def.getAsync == "function") {
212
- this.__storage = new qx.core.property.ExplicitPropertyStorage(this, this.__clazz);
214
+ this.__storage = new qx.core.property.ExplicitPropertyStorage(this);
213
215
  } else {
214
216
  this.__storage = qx.core.property.PropertyStorageFactory.getStorage(qx.core.property.SimplePropertyStorage);
215
217
  }
@@ -537,6 +539,12 @@ qx.Bootstrap.define("qx.core.property.Property", {
537
539
  return self.resetAsync(this);
538
540
  });
539
541
  }
542
+
543
+ if (this.__pseudoProperty) {
544
+ this.__supportsGetAsync = this.__clazz.prototype["get" + qx.Bootstrap.firstUp(this.__propertyName) + "Async"] !== undefined;
545
+ } else {
546
+ this.__supportsGetAsync = this.__storage.supportsGetAsync();
547
+ }
540
548
  },
541
549
 
542
550
  /**
@@ -841,35 +849,20 @@ qx.Bootstrap.define("qx.core.property.Property", {
841
849
  }
842
850
  }
843
851
 
852
+ //store the old value
853
+ let oldValue = this.__getImpl(thisObj, true);
844
854
  let state = this.getPropertyState(thisObj);
845
855
 
856
+ //validate and transform the value
846
857
  if (this.__validate) {
847
858
  this.__callFunction(thisObj, this.__validate, value, this);
848
859
  }
849
860
 
850
- let oldValue = this.__getImpl(thisObj, true);
851
-
852
861
  if (this.__transform) {
853
862
  value = this.__callFunction(thisObj, this.__transform, value, oldValue, this.__propertyName);
854
863
  }
855
864
 
856
- if (method == "reset") {
857
- if (scope == "user") {
858
- this.__storage.reset(thisObj, this, value);
859
- } else if (scope == "themed") {
860
- if (value === undefined) {
861
- delete state.themeValue;
862
- }
863
- }
864
- value = this.__getImpl(thisObj, true);
865
- }
866
-
867
- if (this.isInheritable()) {
868
- if (value === "inherit") {
869
- value = state.inheritedValue;
870
- }
871
- }
872
-
865
+ //perform additional checks if promise
873
866
  let check = this.getCheck();
874
867
  if (qx.core.Environment.get("qx.debug")) {
875
868
  if (!check || check instanceof qx.core.check.Any) {
@@ -877,13 +870,15 @@ qx.Bootstrap.define("qx.core.property.Property", {
877
870
  this.warn(
878
871
  "Property " +
879
872
  this +
880
- " is being set to a promise, bt its check is not a Promise. The property will be set to the promise itself."
873
+ " is being set to a promise, but its check is not a Promise. The property will be set to the promise itself."
881
874
  );
882
875
  }
883
876
  }
884
877
  }
885
878
 
886
- if (method !== "reset" && check) {
879
+ //use check for validation and coercion if necessary,
880
+ //but only if not resetting
881
+ if (!(value === "inherit" && this.isInheritable()) && method !== "reset" && check) {
887
882
  if (!(value === null && oldValue === null) && !check.matches(value, thisObj)) {
888
883
  let coerced = check.coerce(value, thisObj);
889
884
  if (qx.lang.Type.isPromise(value) || coerced === null || !check.matches(coerced, thisObj)) {
@@ -893,6 +888,39 @@ qx.Bootstrap.define("qx.core.property.Property", {
893
888
  }
894
889
  }
895
890
 
891
+ //Modify the value in the underlying storage
892
+ if (method == "reset") {
893
+ if (scope == "user") {
894
+ this.__storage.set(thisObj, this, value);
895
+ } else if (scope == "themed") {
896
+ if (value === undefined) {
897
+ delete state.themeValue;
898
+ }
899
+ }
900
+ value = this.__getImpl(thisObj, true);
901
+ } else {
902
+ if (scope == "user") {
903
+ // Always set the value to the storage if it is a user value; this is because themable properties
904
+ // might be equal now, but if the theme value changes, the user's override needs to remain.
905
+ if (method == "set" || method == "init") {
906
+ this.__storage.set(thisObj, this, value);
907
+ }
908
+ } else if (scope == "themed") {
909
+ if (value !== undefined) {
910
+ state.themeValue = value;
911
+ value = this.__getImpl(thisObj);
912
+ }
913
+ }
914
+ }
915
+
916
+ // If the value is "inherit" and the property is inheritable, we need to calculate the inherited value
917
+ if (this.isInheritable() && value === "inherit") {
918
+ value = state.inheritedValue;
919
+ }
920
+
921
+ //the final value has now been deternined.
922
+ //If it's not equal to the old value or state.applyMethodCalled was not set,
923
+ //we call apply and fire event
896
924
  let isEqual = this.isEqual(thisObj, value, oldValue);
897
925
  if (!isEqual && this.isMutating(thisObj)) {
898
926
  this.warn("Property " + this + " is currently mutating");
@@ -914,23 +942,6 @@ qx.Bootstrap.define("qx.core.property.Property", {
914
942
 
915
943
  this.__setMutating(thisObj, true);
916
944
 
917
- if (scope == "user") {
918
- // Always set the value to the storage if it is a user value; this is because themable properties
919
- // might be equal now, but if the theme value changes, the user's override needs to remain.
920
- if (method == "set" || method == "init") {
921
- this.__storage.set(thisObj, this, value);
922
- }
923
- } else if (scope == "themed") {
924
- if (method != "reset") {
925
- if (value !== undefined) {
926
- state.themeValue = value;
927
- value = this.get(thisObj);
928
- }
929
- }
930
- } else if (qx.core.Environment.get("qx.debug")) {
931
- throw new Error(`Invalid scope=${scope} in ${this.classname}.__setImpl`);
932
- }
933
-
934
945
  if (value === undefined) {
935
946
  value = null;
936
947
  }
@@ -976,21 +987,22 @@ qx.Bootstrap.define("qx.core.property.Property", {
976
987
 
977
988
  let state = this.getPropertyState(thisObj);
978
989
 
990
+ //store the old value
991
+ //we get it synchronously here because if this property supports async get,
992
+ //we may not always want to fetch the value asynchronously
993
+ //this means the apply method will be called with undefined as oldValue
994
+ let oldValue = this.__getImpl(thisObj, true);
995
+
996
+ //validate and transform the value
979
997
  if (this.__validate) {
980
998
  this.__callFunction(thisObj, this.__validate, value, this);
981
999
  }
982
1000
 
983
- let oldValue = await this.__getAsyncImpl(thisObj, true);
984
1001
 
985
1002
  if (this.__transform) {
986
1003
  value = this.__callFunction(thisObj, this.__transform, value, oldValue, this.__propertyName);
987
1004
  }
988
1005
 
989
- if (method == "reset") {
990
- this.__storage.reset(thisObj, this, value);
991
- value = await this.__getAsyncImpl(thisObj, true);
992
- }
993
-
994
1006
  let check = this.getCheck();
995
1007
  if (qx.core.Environment.get("qx.debug")) {
996
1008
  if (!check || check instanceof qx.core.check.Any) {
@@ -1014,6 +1026,22 @@ qx.Bootstrap.define("qx.core.property.Property", {
1014
1026
  }
1015
1027
  }
1016
1028
 
1029
+ //Actually modify the underlying storage
1030
+ if (method == "reset") {
1031
+ this.__storage.set(thisObj, this, value);
1032
+ value = this.__getImpl(thisObj, true);
1033
+ } else {
1034
+ if (scope == "user") {
1035
+ // Always set the value to the storage if it is a user value; this is because themable properties
1036
+ // might be equal now, but if the theme value changes, the user's override needs to remain.
1037
+ if (method == "set" || method == "init") {
1038
+ this.__storage.set(thisObj, this, value);
1039
+ }
1040
+ } else if (qx.core.Environment.get("qx.debug")) {
1041
+ throw new Error(`Invalid scope=${scope} in ${this.classname}.__setAsyncImpl`);
1042
+ }
1043
+ }
1044
+
1017
1045
  let isEqual = this.isEqual(thisObj, value, oldValue);
1018
1046
  if (!isEqual && this.isMutating(thisObj)) {
1019
1047
  this.warn("Property " + this + " is currently mutating");
@@ -1037,16 +1065,6 @@ qx.Bootstrap.define("qx.core.property.Property", {
1037
1065
  let promise = new Promise(r => (resolve = r));
1038
1066
  this.__setMutating(thisObj, promise);
1039
1067
 
1040
- if (scope == "user") {
1041
- // Always set the value to the storage if it is a user value; this is because themable properties
1042
- // might be equal now, but if the theme value changes, the user's override needs to remain.
1043
- if (method == "set" || method == "init") {
1044
- this.__storage.set(thisObj, this, value);
1045
- }
1046
- } else if (qx.core.Environment.get("qx.debug")) {
1047
- throw new Error(`Invalid scope=${scope} in ${this.classname}.__setAsyncImpl`);
1048
- }
1049
-
1050
1068
  if (value === undefined) {
1051
1069
  value = null;
1052
1070
  }
@@ -1075,8 +1093,6 @@ qx.Bootstrap.define("qx.core.property.Property", {
1075
1093
 
1076
1094
  if (value !== undefined) {
1077
1095
  return value;
1078
- } else if (this.isAsync()) {
1079
- return undefined;
1080
1096
  }
1081
1097
 
1082
1098
  let state = this.getPropertyState(thisObj);
@@ -1105,7 +1121,7 @@ qx.Bootstrap.define("qx.core.property.Property", {
1105
1121
 
1106
1122
  if (value !== undefined) {
1107
1123
  return value;
1108
- } else if (this.isAsync()) {
1124
+ } else if (this.__supportsGetAsync) {
1109
1125
  if (!safe) {
1110
1126
  throw new Error("Property " + this + " has not been initialized - try using getAsync() instead");
1111
1127
  } else {
@@ -1133,6 +1149,11 @@ qx.Bootstrap.define("qx.core.property.Property", {
1133
1149
  * @returns {Promise<*>}
1134
1150
  */
1135
1151
  async __getAsyncImpl(thisObj, safe) {
1152
+ if (qx.core.Environment.get("qx.debug")) {
1153
+ if (!this.__supportsGetAsync) {
1154
+ this.warn(`Called getAsync/resetAsync in property ${this} which does not support async getter.`);
1155
+ }
1156
+ }
1136
1157
  const getRaw = async () => {
1137
1158
  let value = this.__storage.get(thisObj, this);
1138
1159
 
@@ -1140,12 +1161,14 @@ qx.Bootstrap.define("qx.core.property.Property", {
1140
1161
  return value;
1141
1162
  }
1142
1163
 
1143
- value = await this.__storage.getAsync(thisObj, this);
1164
+ value = this.getInitValue(thisObj);
1144
1165
  if (value !== undefined) {
1145
1166
  return value;
1146
1167
  }
1147
1168
 
1148
- return this.getInitValue(thisObj);
1169
+ if (this.__supportsGetAsync) {
1170
+ return await this.__storage.getAsync(thisObj, this);
1171
+ }
1149
1172
  };
1150
1173
 
1151
1174
  let value = await getRaw();
@@ -1383,23 +1406,24 @@ qx.Bootstrap.define("qx.core.property.Property", {
1383
1406
  promiseReady(thisObj) {},
1384
1407
 
1385
1408
  /**
1386
- * Whether the property is initialized
1409
+ * Whether the property has a defined value stored locally,
1410
+ * meaning we can safely get it synchronously
1387
1411
  *
1388
1412
  * @param {qx.core.Object} thisObj the object on which the property is defined
1389
1413
  * @return {Boolean}
1390
1414
  */
1391
- isInitialized(thisObj) {
1415
+ hasLocalValue(thisObj) {
1392
1416
  let value = this.__getImpl(thisObj, true);
1393
1417
  return value !== undefined;
1394
1418
  },
1395
1419
 
1396
1420
  /**
1397
- * Whether the property supports async
1421
+ * Whether the property supports getting asynchronously from the underlying storage
1398
1422
  *
1399
1423
  * @return {Boolean}
1400
1424
  */
1401
- isAsync() {
1402
- return !!this.__definition?.async;
1425
+ supportsGetAsync() {
1426
+ return this.__supportsGetAsync;
1403
1427
  },
1404
1428
 
1405
1429
  /**
@@ -37,7 +37,7 @@ qx.Bootstrap.define("qx.core.property.SimplePropertyStorage", {
37
37
  * @Override
38
38
  */
39
39
  async getAsync(thisObj, property) {
40
- return this.get(thisObj, property);
40
+ // nothing
41
41
  },
42
42
 
43
43
  /**
@@ -52,22 +52,6 @@ qx.Bootstrap.define("qx.core.property.SimplePropertyStorage", {
52
52
  data.value = value;
53
53
  },
54
54
 
55
- /**
56
- * @Override
57
- */
58
- async setAsync(thisObj, property, value) {
59
- return qx.Promise.resolve(value).then(value => {
60
- this.set(thisObj, property, value);
61
- });
62
- },
63
-
64
- /**
65
- * @Override
66
- */
67
- reset(thisObj, property, value) {
68
- this.set(thisObj, property, value);
69
- },
70
-
71
55
  /**
72
56
  * @Override
73
57
  */
@@ -78,7 +62,7 @@ qx.Bootstrap.define("qx.core.property.SimplePropertyStorage", {
78
62
  /**
79
63
  * @Override
80
64
  */
81
- isAsyncStorage() {
65
+ supportsGetAsync() {
82
66
  return false;
83
67
  },
84
68
 
@@ -77,7 +77,7 @@ qx.Mixin.define("qx.data.MBinding", {
77
77
  */
78
78
  bindAsync: qx.core.Environment.select("qx.promise", {
79
79
  true(sourcePropertyChain, targetObject, targetProperty, options) {
80
- var binding = qx.data.SingleValueBinding.bind(this, sourcePropertyChain, targetObject, targetProperty || "value", options);
80
+ var binding = qx.data.SingleValueBinding.bind(this, sourcePropertyChain, targetObject, targetProperty || "value", {...options, async: true});
81
81
  return binding.getInitPromise().then(() => binding);
82
82
  },
83
83
  false(sourcePropertyChain, targetObject, targetProperty, options) {