@qooxdoo/framework 7.7.2 → 7.9.0

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 (93) hide show
  1. package/Manifest.json +2 -2
  2. package/lib/compiler/compile-info.json +91 -89
  3. package/lib/compiler/index.js +2517 -1488
  4. package/lib/resource/qx/tool/schema/compile-1-0-0.json +13 -0
  5. package/lib/resource/qx/tool/website/build/404.html +3 -25
  6. package/lib/resource/qx/tool/website/build/about.html +3 -25
  7. package/lib/resource/qx/tool/website/build/assets/common.js +20 -0
  8. package/lib/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
  9. package/lib/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
  10. package/lib/resource/qx/tool/website/build/index.html +3 -25
  11. package/lib/resource/qx/tool/website/partials/footer.html +3 -21
  12. package/lib/resource/qx/tool/website/partials/head.html +0 -1
  13. package/package.json +2 -2
  14. package/source/class/qx/Bootstrap.js +6 -3
  15. package/source/class/qx/Promise.js +93 -6964
  16. package/source/class/qx/bom/Label.js +82 -2
  17. package/source/class/qx/bom/webfonts/WebFont.js +1 -0
  18. package/source/class/qx/core/Environment.js +83 -1
  19. package/source/class/qx/data/controller/List.js +50 -21
  20. package/source/class/qx/data/controller/MSelection.js +45 -12
  21. package/source/class/qx/data/marshal/Json.js +64 -11
  22. package/source/class/qx/dev/unit/AsyncWrapper.js +8 -0
  23. package/source/class/qx/event/Manager.js +163 -124
  24. package/source/class/qx/io/ImageLoader.js +6 -3
  25. package/source/class/qx/io/exception/Transport.js +1 -0
  26. package/source/class/qx/io/jsonrpc/Client.js +64 -8
  27. package/source/class/qx/io/jsonrpc/protocol/Request.js +10 -6
  28. package/source/class/qx/lang/Type.js +36 -3
  29. package/source/class/qx/promise/BluebirdImpl.js +6918 -0
  30. package/source/class/qx/promise/NativeWrapper.js +738 -0
  31. package/source/class/qx/test/Promise.js +1145 -22
  32. package/source/class/qx/test/bom/client/Pdfjs.js +4 -0
  33. package/source/class/qx/test/bom/element/AnimationJs.js +3 -0
  34. package/source/class/qx/test/bom/element/Style.js +1 -0
  35. package/source/class/qx/test/bom/media/MediaTestCase.js +6 -0
  36. package/source/class/qx/test/core/Environment.js +44 -0
  37. package/source/class/qx/test/data/controller/List.js +6 -0
  38. package/source/class/qx/test/data/marshal/Json.js +29 -0
  39. package/source/class/qx/test/io/MAssert.js +94 -0
  40. package/source/class/qx/test/io/TestMAssert.js +47 -0
  41. package/source/class/qx/test/io/jsonrpc/Client.js +79 -19
  42. package/source/class/qx/test/io/jsonrpc/PostMessageClient.js +152 -0
  43. package/source/class/qx/test/io/jsonrpc/Protocol.js +1 -5
  44. package/source/class/qx/test/io/request/Xhr.js +16 -0
  45. package/source/class/qx/test/lang/Type.js +151 -0
  46. package/source/class/qx/test/ui/embed/Iframe.js +1 -1
  47. package/source/class/qx/test/ui/form/TextArea.js +4 -0
  48. package/source/class/qx/test/util/DeferredCall.js +6 -0
  49. package/source/class/qx/test/util/Function.js +2 -2
  50. package/source/class/qx/theme/indigo/ColorDark.js +1 -1
  51. package/source/class/qx/tool/cli/api/Test.js +22 -0
  52. package/source/class/qx/tool/cli/commands/Compile.js +17 -4
  53. package/source/class/qx/tool/cli/commands/Test.js +7 -1
  54. package/source/class/qx/tool/compiler/Analyser.js +7 -0
  55. package/source/class/qx/tool/compiler/ClassFile.js +3 -2
  56. package/source/class/qx/tool/compiler/MetaExtraction.js +0 -5
  57. package/source/class/qx/tool/compiler/targets/meta/Browserify.js +8 -2
  58. package/source/class/qx/ui/basic/Image.js +72 -8
  59. package/source/class/qx/ui/basic/Label.js +4 -6
  60. package/source/class/qx/ui/control/DateChooser.js +4 -6
  61. package/source/class/qx/ui/core/Blocker.js +4 -6
  62. package/source/class/qx/ui/core/LayoutItem.js +4 -6
  63. package/source/class/qx/ui/core/MPlacement.js +23 -1
  64. package/source/class/qx/ui/core/Widget.js +7 -5
  65. package/source/class/qx/ui/form/AbstractField.js +4 -6
  66. package/source/class/qx/ui/form/MForm.js +4 -6
  67. package/source/class/qx/ui/form/Spinner.js +4 -6
  68. package/source/class/qx/ui/form/renderer/AbstractRenderer.js +4 -6
  69. package/source/class/qx/ui/menu/AbstractButton.js +7 -11
  70. package/source/class/qx/ui/mobile/basic/Label.js +4 -6
  71. package/source/class/qx/ui/mobile/form/Label.js +4 -6
  72. package/source/class/qx/ui/mobile/list/List.js +4 -6
  73. package/source/class/qx/ui/progressive/renderer/table/Row.js +35 -7
  74. package/source/class/qx/ui/progressive/renderer/table/cell/Boolean.js +4 -6
  75. package/source/class/qx/ui/table/Table.js +4 -6
  76. package/source/class/qx/ui/table/cellrenderer/Abstract.js +4 -6
  77. package/source/class/qx/ui/table/cellrenderer/Boolean.js +4 -6
  78. package/source/class/qx/ui/table/pane/Scroller.js +1 -1
  79. package/source/class/qx/ui/table/rowrenderer/Default.js +4 -6
  80. package/source/class/qx/util/ConcurrencyLimiter.js +78 -0
  81. package/source/resource/qx/tool/schema/compile-1-0-0.json +13 -0
  82. package/source/resource/qx/tool/website/build/404.html +3 -25
  83. package/source/resource/qx/tool/website/build/about.html +3 -25
  84. package/source/resource/qx/tool/website/build/assets/common.js +20 -0
  85. package/source/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
  86. package/source/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
  87. package/source/resource/qx/tool/website/build/index.html +3 -25
  88. package/source/resource/qx/tool/website/partials/footer.html +3 -21
  89. package/source/resource/qx/tool/website/partials/head.html +0 -1
  90. package/lib/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
  91. package/lib/resource/qx/tool/website/src/assets/bluebird.min.js +0 -4615
  92. package/source/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
  93. package/source/resource/qx/tool/website/src/assets/bluebird.min.js +0 -4615
@@ -42,6 +42,156 @@ qx.Class.define("qx.test.lang.Type", {
42
42
  this.assertFalse(Type.isString(document.getElementById("ReturenedNull")));
43
43
  },
44
44
 
45
+ testIsPojo() {
46
+ var isPojo = qx.lang.Type.isPojo;
47
+
48
+ //Class Person extends Object
49
+ function Person() {
50
+ this.name = "Mary";
51
+ this.surname = "Berry";
52
+ }
53
+
54
+ Person.prototype.getFullName = function () {
55
+ return this.name + " " + this.surname;
56
+ };
57
+
58
+ Person.prototype.properties = function () {
59
+ return "Properties";
60
+ };
61
+
62
+ //Class Child extends Person
63
+ function Child() {
64
+ Person.call(this);
65
+ }
66
+
67
+ Child.prototype = Object.create(Person.prototype);
68
+ Child.prototype.constructor = Child;
69
+
70
+ Child.prototype.childMethod = function () {
71
+ return "Child method";
72
+ };
73
+
74
+ // Simple POJO, should return true
75
+ this.assertTrue(isPojo({ foo: 1 }));
76
+
77
+ //Arrays, strings, numbers, and booleans are not POJOs
78
+ this.assertFalse(isPojo([]));
79
+ this.assertFalse(isPojo(null));
80
+ this.assertFalse(isPojo(new qx.data.Array()));
81
+ this.assertFalse(isPojo("hello"));
82
+ this.assertFalse(isPojo(12));
83
+ this.assertFalse(isPojo(true));
84
+
85
+ //Class instance which does not inherit
86
+ var nonInheritance = new Person();
87
+ this.assertFalse(isPojo(nonInheritance));
88
+
89
+ //Class instance with class that inherits
90
+ var withInheritance = new Child();
91
+ this.assertFalse(isPojo(withInheritance));
92
+
93
+ //POJO with class prototype
94
+ var pojoWithClassPrototype = { age: 22 };
95
+ Object.setPrototypeOf(pojoWithClassPrototype, Person.prototype);
96
+ this.assertFalse(isPojo(pojoWithClassPrototype));
97
+
98
+ //POJO with an object prototype
99
+ //Should return true
100
+ var pojoWithObjectPrototype = { extraproperty: "Nuts" };
101
+ var proto = {
102
+ protoMethod() {
103
+ return "protomethod";
104
+ }
105
+ };
106
+ Object.setPrototypeOf(pojoWithObjectPrototype, proto);
107
+ this.assertTrue(isPojo(pojoWithObjectPrototype));
108
+
109
+ //Object which has a POJO prototype, which in turn has a class prototype
110
+ //Should return false
111
+ var prototypeIsObjectThenConstructor = { extraproperty: "Nuts" };
112
+ var proto = {
113
+ protoMethod() {
114
+ return "protomethod";
115
+ }
116
+ };
117
+
118
+ Object.setPrototypeOf(prototypeIsObjectThenConstructor, proto);
119
+ Object.setPrototypeOf(proto, Person.prototype);
120
+ this.assertFalse(isPojo(prototypeIsObjectThenConstructor));
121
+
122
+ //Object which has a POJO prototype, which in turn has a POJO prototype
123
+ //Should return true
124
+ var obj = {
125
+ nuts: 345
126
+ };
127
+
128
+ var proto1 = {
129
+ myMethod() {
130
+ return "proto1.myMethod";
131
+ }
132
+ };
133
+
134
+ Object.setPrototypeOf(obj, proto1);
135
+ var proto2 = {
136
+ myMethod() {
137
+ return "proto2.myMethod";
138
+ },
139
+
140
+ method2() {
141
+ return "proto2.method2";
142
+ }
143
+ };
144
+
145
+ Object.setPrototypeOf(proto1, proto2);
146
+
147
+ this.assertEquals("proto1.myMethod", obj.myMethod());
148
+ this.assertEquals("proto2.method2", obj.method2());
149
+ this.assertTrue(isPojo(obj));
150
+
151
+ //Object which is instance of a class, where the class has a prototype which is not Object.prototype
152
+ //Must return false
153
+ function NonObjectPrototype() {
154
+ this.foo = "foo";
155
+ }
156
+
157
+ var proto = {
158
+ myMethod() {
159
+ return "proto.myMethod";
160
+ }
161
+ };
162
+
163
+ NonObjectPrototype.prototype = proto;
164
+ NonObjectPrototype.prototype.constructor = NonObjectPrototype;
165
+ NonObjectPrototype.prototype.extraMethod = function () {
166
+ return "extraMethod";
167
+ };
168
+
169
+ var obj = new NonObjectPrototype();
170
+
171
+ this.assertEquals("proto.myMethod", obj.myMethod());
172
+ this.assertEquals("extraMethod", obj.extraMethod());
173
+ this.assertEquals("foo", obj.foo);
174
+ this.assertFalse(isPojo(obj));
175
+
176
+ //Qooxdoo objects must not be POJOs
177
+ var obj = new qx.core.Object();
178
+ this.assertFalse(isPojo(obj));
179
+
180
+ //ES6 class must not be a POJO
181
+ class ES6Class {
182
+ constructor() {
183
+ this.foo = "bar";
184
+ }
185
+
186
+ method() {
187
+ return "method: " + this.foo;
188
+ }
189
+ }
190
+
191
+ var obj = new ES6Class();
192
+ this.assertFalse(isPojo(obj));
193
+ },
194
+
45
195
  testIsArray() {
46
196
  var Type = qx.lang.Type;
47
197
 
@@ -64,6 +214,7 @@ qx.Class.define("qx.test.lang.Type", {
64
214
  },
65
215
 
66
216
  testIsObject() {
217
+ //note: old testIsObject
67
218
  var Type = qx.lang.Type;
68
219
 
69
220
  this.assertTrue(Type.isObject({}));
@@ -145,7 +145,7 @@ qx.Class.define("qx.test.ui.embed.Iframe", {
145
145
  this.assertEquals("Hello World!", innerText);
146
146
  });
147
147
  }.bind(this),
148
- 4000
148
+ 5000
149
149
  );
150
150
 
151
151
  this.wait(10000);
@@ -294,6 +294,10 @@ qx.Class.define("qx.test.ui.form.TextArea", {
294
294
  this.skip();
295
295
  }
296
296
 
297
+ if ((qx.core.Environment.get("engine.name") === "gecko") && (navigator.plugins.length == 0)) {
298
+ this.skip("test disabled on headless firefox browser");
299
+ }
300
+
297
301
  var textArea = this.__textArea;
298
302
  textArea.set({
299
303
  autoSize: true,
@@ -24,6 +24,12 @@ qx.Class.define("qx.test.util.DeferredCall", {
24
24
  if (navigator.plugins.length == 0) {
25
25
  this.skip("test disabled on headless browsers");
26
26
  }
27
+
28
+ if (qx.core.Environment.get("browser.name") == "safari") {
29
+ this.skip(
30
+ "we can not detect headless mode in safari"
31
+ );
32
+ }
27
33
 
28
34
  var fail = function () {
29
35
  throw new Error("fail");
@@ -30,7 +30,7 @@ qx.Class.define("qx.test.util.Function", {
30
30
  this.assertNotCalled(test);
31
31
  debouncedTest(false);
32
32
  this.wait(
33
- 100,
33
+ 250,
34
34
  function () {
35
35
  this.assertCalledOnce(test);
36
36
  this.assertCalledWith(test, false);
@@ -52,7 +52,7 @@ qx.Class.define("qx.test.util.Function", {
52
52
  debouncedTest(true);
53
53
  debouncedTest(false);
54
54
  this.wait(
55
- 100,
55
+ 250,
56
56
  function () {
57
57
  this.assertCalledTwice(test);
58
58
  this.assertCalledWith(test, false);
@@ -95,7 +95,7 @@ qx.Theme.define("qx.theme.indigo.ColorDark", {
95
95
  // used in table code
96
96
  "table-header-cell": "#ebeadb",
97
97
  "table-row-background-focused-selected": "#666666",
98
- "table-row-background-focused": "#666666",
98
+ "table-row-background-focused": "#444444",
99
99
  "table-row-background-selected": "#666666",
100
100
  "table-row-background-even": "#333333",
101
101
  "table-row-background-odd": "#333333",
@@ -1,3 +1,16 @@
1
+ /* ************************************************************************
2
+
3
+ qooxdoo - the new era of web development
4
+
5
+ http://qooxdoo.org
6
+
7
+ Copyright:
8
+ 2020 Henner Kollmann
9
+
10
+ License:
11
+ MIT: https://opensource.org/licenses/MIT
12
+ See the LICENSE file in the project"s top-level directory for details.
13
+ ************************************************************************ */
1
14
  /**
2
15
  * This is used to add an test case for qx test
3
16
  */
@@ -47,6 +60,15 @@ qx.Class.define("qx.tool.cli.api.Test", {
47
60
  init: true
48
61
  },
49
62
 
63
+ /**
64
+ * If this special test fails exit process
65
+ */
66
+ failFast: {
67
+ check: "Boolean",
68
+ nullable: false,
69
+ init: false
70
+ },
71
+
50
72
  /**
51
73
  * The test function called by qx test
52
74
  *
@@ -1345,6 +1345,15 @@ Framework: v${await this.getQxVersion()} in ${await this.getQxPath()}`);
1345
1345
  if (data.environment) {
1346
1346
  maker.setEnvironment(data.environment);
1347
1347
  }
1348
+
1349
+ /*
1350
+ Libraries have to be added first because there is qx library
1351
+ which includes a framework version
1352
+ */
1353
+ for (let library of librariesArray) {
1354
+ maker.getAnalyser().addLibrary(library);
1355
+ }
1356
+
1348
1357
  let targetEnvironment = {
1349
1358
  "qx.version": maker.getAnalyser().getQooxdooVersion(),
1350
1359
  "qx.compiler.targetType": target.getType(),
@@ -1395,6 +1404,14 @@ Framework: v${await this.getQxVersion()} in ${await this.getQxPath()}`);
1395
1404
 
1396
1405
  maker.getAnalyser().setBabelConfig(babelConfig);
1397
1406
 
1407
+ let browserifyConfig = qx.lang.Object.clone(data.browserify || {}, true);
1408
+ browserifyConfig.options = browserifyConfig.options || {};
1409
+ qx.lang.Object.mergeWith(
1410
+ browserifyConfig.options,
1411
+ targetConfig.browserifyOptions || {}
1412
+ );
1413
+ maker.getAnalyser().setBrowserifyConfig(browserifyConfig);
1414
+
1398
1415
  var addCreatedAt =
1399
1416
  targetConfig["addCreatedAt"] || t.argv["addCreatedAt"];
1400
1417
  if (addCreatedAt) {
@@ -1406,10 +1423,6 @@ Framework: v${await this.getQxVersion()} in ${await this.getQxPath()}`);
1406
1423
  maker.getAnalyser().setVerboseCreatedAt(true);
1407
1424
  }
1408
1425
 
1409
- for (let library of librariesArray) {
1410
- maker.getAnalyser().addLibrary(library);
1411
- }
1412
-
1413
1426
  let allApplicationTypes = {};
1414
1427
  appConfigs.forEach(appConfig => {
1415
1428
  var app = (appConfig.app = new qx.tool.compiler.app.Application(
@@ -99,7 +99,7 @@ qx.Class.define("qx.tool.cli.commands.Test", {
99
99
  let exitCode = evt.getData();
100
100
  // overwrite error code only in case of errors
101
101
  if (exitCode !== 0 && argv.failFast) {
102
- process.exit(exitCode);
102
+ process.exit(Math.min(255, exitCode));
103
103
  }
104
104
  });
105
105
  },
@@ -152,6 +152,9 @@ qx.Class.define("qx.tool.cli.commands.Test", {
152
152
  }
153
153
  // overwrite error code only in case of errors
154
154
  if (exitCode !== 0) {
155
+ if (test.getFailFast()) {
156
+ this.argv.failFast = true;
157
+ }
155
158
  this.setExitCode(exitCode);
156
159
  }
157
160
  });
@@ -194,6 +197,9 @@ qx.Class.define("qx.tool.cli.commands.Test", {
194
197
 
195
198
  this.addListener("afterStart", async () => {
196
199
  qx.tool.compiler.Console.info(`Running unit tests`);
200
+ if (this.argv.verbose) {
201
+ console.log(this.argv);
202
+ }
197
203
  await this.fireDataEventAsync("runTests", this);
198
204
  if (
199
205
  this.getCompilerApi() &&
@@ -126,6 +126,13 @@ qx.Class.define("qx.tool.compiler.Analyser", {
126
126
  check: "Object"
127
127
  },
128
128
 
129
+ /** configuration of browserify */
130
+ browserifyConfig: {
131
+ init: null,
132
+ nullable: true,
133
+ check: "Object"
134
+ },
135
+
129
136
  /** list of global ignores */
130
137
  ignores: {
131
138
  init: [],
@@ -962,14 +962,15 @@ qx.Class.define("qx.tool.compiler.ClassFile", {
962
962
 
963
963
  CallExpression(path) {
964
964
  const name = collapseMemberExpression(path.node.callee);
965
+ const env = t.__analyser.getEnvironment();
965
966
 
966
967
  if (
968
+ env["qx.environment.allowRuntimeMutations"] !== true &&
967
969
  (name === "qx.core.Environment.select" ||
968
970
  name === "qx.core.Environment.get") &&
969
- types.isLiteral(path.node.arguments[0])
971
+ types.isLiteral(path.node.arguments[0])
970
972
  ) {
971
973
  const arg = path.node.arguments[0];
972
- const env = t.__analyser.getEnvironment();
973
974
  const envValue = env[arg.value];
974
975
 
975
976
  if (envValue !== undefined) {
@@ -109,11 +109,6 @@ qx.Class.define("qx.tool.compiler.MetaExtraction", {
109
109
 
110
110
  const babelCore = require("@babel/core");
111
111
  let src = await fs.promises.readFile(classFilename, "utf8");
112
- let babelConfig = {
113
- options: {
114
- modules: false
115
- }
116
- };
117
112
 
118
113
  let plugins = [require("@babel/plugin-syntax-jsx"), this.__plugin()];
119
114
 
@@ -158,12 +158,18 @@ qx.Class.define("qx.tool.compiler.targets.meta.Browserify", {
158
158
  builtins.process = builtins._process;
159
159
 
160
160
  return new Promise((resolve, reject) => {
161
- let b = browserify([], {
161
+ const options = {
162
162
  builtins: builtins,
163
163
  ignoreMissing: true,
164
164
  insertGlobals: true,
165
165
  detectGlobals: true
166
- });
166
+ };
167
+ qx.lang.Object.mergeWith(
168
+ options,
169
+ this.getAppMeta().getAnalyser().getBrowserifyConfig()?.options || {},
170
+ false
171
+ );
172
+ let b = browserify([], options);
167
173
 
168
174
  b._mdeps.on("missing", (id, parent) => {
169
175
  let message = [];
@@ -144,6 +144,16 @@ qx.Class.define("qx.ui.basic.Image", {
144
144
  allowGrowY: {
145
145
  refine: true,
146
146
  init: false
147
+ },
148
+
149
+ /**
150
+ * Source of image to display if the image in the `source` property fails to load.
151
+ */
152
+ fallbackSource: {
153
+ check: "String",
154
+ init: null,
155
+ nullable: true,
156
+ event: "changeFallbackSource"
147
157
  }
148
158
  },
149
159
 
@@ -189,6 +199,19 @@ qx.Class.define("qx.ui.basic.Image", {
189
199
  __currentContentElement: null,
190
200
  __wrapper: null,
191
201
  __requestId: 0,
202
+ /**
203
+ * If the image in the `source` property failed to load.
204
+ */
205
+ __failedToLoad: false,
206
+
207
+ /**
208
+ * @type {string} The actual source of the image that we display.
209
+ * While the `source` property is what we want to display,
210
+ * this property is what we are actually showing.
211
+ * This can be the `source` property but it can also be the `fallbackSource` property
212
+ * if the image in the `source` property failed to load.
213
+ */
214
+ __sourceToDisplay: null,
192
215
 
193
216
  // overridden
194
217
  _onChangeTheme() {
@@ -225,7 +248,7 @@ qx.Class.define("qx.ui.basic.Image", {
225
248
  _applyDecorator(value, old) {
226
249
  super._applyDecorator(value, old);
227
250
 
228
- var source = this.getSource();
251
+ var source = this.__sourceToDisplay;
229
252
  source = qx.util.AliasManager.getInstance().resolve(source);
230
253
  var el = this.getContentElement();
231
254
  if (this.__wrapper) {
@@ -307,7 +330,7 @@ qx.Class.define("qx.ui.basic.Image", {
307
330
  _applyEnabled(value, old) {
308
331
  super._applyEnabled(value, old);
309
332
 
310
- if (this.getSource()) {
333
+ if (this.__sourceToDisplay) {
311
334
  this._styleSource();
312
335
  }
313
336
  },
@@ -321,6 +344,8 @@ qx.Class.define("qx.ui.basic.Image", {
321
344
  }
322
345
  }
323
346
 
347
+ this.__failedToLoad = false;
348
+ this.__sourceToDisplay = value;
324
349
  this._styleSource();
325
350
  },
326
351
 
@@ -345,7 +370,7 @@ qx.Class.define("qx.ui.basic.Image", {
345
370
  */
346
371
  __getMode() {
347
372
  if (this.__mode == null) {
348
- var source = this.getSource();
373
+ var source = this.__sourceToDisplay;
349
374
 
350
375
  if (source && qx.lang.String.startsWith(source, "@")) {
351
376
  this.__mode = "font";
@@ -458,13 +483,12 @@ qx.Class.define("qx.ui.basic.Image", {
458
483
  /**
459
484
  * Applies the source to the clipped image instance or preload
460
485
  * an image to detect sizes and apply it afterwards.
461
- *
462
486
  */
463
487
  _styleSource() {
464
488
  var AliasManager = qx.util.AliasManager.getInstance();
465
489
  var ResourceManager = qx.util.ResourceManager.getInstance();
466
490
 
467
- var source = AliasManager.resolve(this.getSource());
491
+ var source = AliasManager.resolve(this.__sourceToDisplay);
468
492
 
469
493
  var element = this.getContentElement();
470
494
  if (this.__wrapper) {
@@ -791,7 +815,7 @@ qx.Class.define("qx.ui.basic.Image", {
791
815
  el.setStyle("fontSize", (width > height ? height : width) + "px");
792
816
  } else {
793
817
  var source = qx.util.AliasManager.getInstance().resolve(
794
- this.getSource()
818
+ this.__sourceToDisplay
795
819
  );
796
820
 
797
821
  var sparts = source.split("/");
@@ -805,7 +829,8 @@ qx.Class.define("qx.ui.basic.Image", {
805
829
  super._applyDimension();
806
830
 
807
831
  var isFont =
808
- this.getSource() && qx.lang.String.startsWith(this.getSource(), "@");
832
+ this.__sourceToDisplay &&
833
+ qx.lang.String.startsWith(this.__sourceToDisplay, "@");
809
834
  if (isFont) {
810
835
  var el = this.getContentElement();
811
836
  if (el) {
@@ -872,6 +897,11 @@ qx.Class.define("qx.ui.basic.Image", {
872
897
  ImageLoader.load(source, this.__loaderCallback, this);
873
898
  } else {
874
899
  this.__resetSource(el);
900
+ if (!this.__failedToLoad && this.getFallbackSource()) {
901
+ this.__failedToLoad = true;
902
+ this.__sourceToDisplay = this.getFallbackSource();
903
+ this._styleSource();
904
+ }
875
905
  }
876
906
  },
877
907
 
@@ -904,6 +934,7 @@ qx.Class.define("qx.ui.basic.Image", {
904
934
  },
905
935
 
906
936
  /**
937
+ * Sets image source on the DOM element
907
938
  * Combines the decorator's image styles with our own image to make sure
908
939
  * gradient and backgroundImage decorators work on Images.
909
940
  *
@@ -991,6 +1022,32 @@ qx.Class.define("qx.ui.basic.Image", {
991
1022
  }
992
1023
  },
993
1024
 
1025
+ /**
1026
+ * Tries to display the image in the `source` property again if it has failed to load.
1027
+ */
1028
+ tryReload() {
1029
+ if (!this.__failedToLoad) {
1030
+ return;
1031
+ }
1032
+ this.__failedToLoad = false;
1033
+ this.__sourceToDisplay = this.getSource();
1034
+ qx.io.ImageLoader.load(
1035
+ this.getSource(),
1036
+ () => this._styleSource(),
1037
+ undefined,
1038
+ {
1039
+ retryFailed: true
1040
+ }
1041
+ );
1042
+ },
1043
+
1044
+ /**
1045
+ * @returns {boolean} If the image in the `source` property failed to load.
1046
+ */
1047
+ hasFailedToLoad() {
1048
+ return this.__failedToLoad;
1049
+ },
1050
+
994
1051
  /**
995
1052
  * Event handler fired after the preloader has finished loading the icon
996
1053
  *
@@ -1005,7 +1062,8 @@ qx.Class.define("qx.ui.basic.Image", {
1005
1062
 
1006
1063
  // Ignore when the source has already been modified
1007
1064
  if (
1008
- source !== qx.util.AliasManager.getInstance().resolve(this.getSource())
1065
+ source !==
1066
+ qx.util.AliasManager.getInstance().resolve(this.__sourceToDisplay)
1009
1067
  ) {
1010
1068
  this.fireEvent("aborted");
1011
1069
  return;
@@ -1015,6 +1073,12 @@ qx.Class.define("qx.ui.basic.Image", {
1015
1073
  if (imageInfo.failed) {
1016
1074
  this.warn("Image could not be loaded: " + source);
1017
1075
  this.fireEvent("loadingFailed");
1076
+ if (!this.__failedToLoad && this.getFallbackSource()) {
1077
+ this.__failedToLoad = true;
1078
+ this.__sourceToDisplay = this.getFallbackSource();
1079
+ this._styleSource();
1080
+ return;
1081
+ }
1018
1082
  } else if (imageInfo.aborted) {
1019
1083
  this.fireEvent("aborted");
1020
1084
  return;
@@ -79,7 +79,7 @@ qx.Class.define("qx.ui.basic.Label", {
79
79
  }
80
80
 
81
81
  if (qx.core.Environment.get("qx.dynlocale")) {
82
- qx.locale.Manager.getInstance().addListener(
82
+ this.__changeLocaleLabelListenerId = qx.locale.Manager.getInstance().addListener(
83
83
  "changeLocale",
84
84
  this._onChangeLocale,
85
85
  this
@@ -529,11 +529,9 @@ qx.Class.define("qx.ui.basic.Label", {
529
529
  */
530
530
 
531
531
  destruct() {
532
- if (qx.core.Environment.get("qx.dynlocale")) {
533
- qx.locale.Manager.getInstance().removeListener(
534
- "changeLocale",
535
- this._onChangeLocale,
536
- this
532
+ if (qx.core.Environment.get("qx.dynlocale") && this.__changeLocaleLabelListenerId) {
533
+ qx.locale.Manager.getInstance().removeListenerById(
534
+ this.__changeLocaleLabelListenerId
537
535
  );
538
536
  }
539
537
 
@@ -102,7 +102,7 @@ qx.Class.define("qx.ui.control.DateChooser", {
102
102
 
103
103
  // listen for locale changes
104
104
  if (qx.core.Environment.get("qx.dynlocale")) {
105
- qx.locale.Manager.getInstance().addListener(
105
+ this.__changeLocaleDatePaneListenerId = qx.locale.Manager.getInstance().addListener(
106
106
  "changeLocale",
107
107
  this._updateDatePane,
108
108
  this
@@ -782,11 +782,9 @@ qx.Class.define("qx.ui.control.DateChooser", {
782
782
  */
783
783
 
784
784
  destruct() {
785
- if (qx.core.Environment.get("qx.dynlocale")) {
786
- qx.locale.Manager.getInstance().removeListener(
787
- "changeLocale",
788
- this._updateDatePane,
789
- this
785
+ if (qx.core.Environment.get("qx.dynlocale") && this.__changeLocaleDatePaneListenerId) {
786
+ qx.locale.Manager.getInstance().removeListenerById(
787
+ this.__changeLocaleDatePaneListenerId
790
788
  );
791
789
  }
792
790
 
@@ -63,7 +63,7 @@ qx.Class.define("qx.ui.core.Blocker", {
63
63
 
64
64
  // dynamic theme switch
65
65
  if (qx.core.Environment.get("qx.dyntheme")) {
66
- qx.theme.manager.Meta.getInstance().addListener(
66
+ this.__changeThemeBlockerListenerId = qx.theme.manager.Meta.getInstance().addListener(
67
67
  "changeTheme",
68
68
  this._onChangeTheme,
69
69
  this
@@ -499,11 +499,9 @@ qx.Class.define("qx.ui.core.Blocker", {
499
499
 
500
500
  destruct() {
501
501
  // remove dynamic theme listener
502
- if (qx.core.Environment.get("qx.dyntheme")) {
503
- qx.theme.manager.Meta.getInstance().removeListener(
504
- "changeTheme",
505
- this._onChangeTheme,
506
- this
502
+ if (qx.core.Environment.get("qx.dyntheme") && this.__changeThemeBlockerListenerId) {
503
+ qx.theme.manager.Meta.getInstance().removeListenerById(
504
+ this.__changeThemeBlockerListenerId
507
505
  );
508
506
  }
509
507
 
@@ -30,7 +30,7 @@ qx.Class.define("qx.ui.core.LayoutItem", {
30
30
 
31
31
  // dynamic theme switch
32
32
  if (qx.core.Environment.get("qx.dyntheme")) {
33
- qx.theme.manager.Meta.getInstance().addListener(
33
+ this.__changeThemeLayoutItemListenerId = qx.theme.manager.Meta.getInstance().addListener(
34
34
  "changeTheme",
35
35
  this._onChangeTheme,
36
36
  this
@@ -933,11 +933,9 @@ qx.Class.define("qx.ui.core.LayoutItem", {
933
933
 
934
934
  destruct() {
935
935
  // remove dynamic theme listener
936
- if (qx.core.Environment.get("qx.dyntheme")) {
937
- qx.theme.manager.Meta.getInstance().removeListener(
938
- "changeTheme",
939
- this._onChangeTheme,
940
- this
936
+ if (qx.core.Environment.get("qx.dyntheme") && this.__changeThemeLayoutItemListenerId) {
937
+ qx.theme.manager.Meta.getInstance().removeListenerById(
938
+ this.__changeThemeLayoutItemListenerId
941
939
  );
942
940
  }
943
941
  this.$$parent =