@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
@@ -66,7 +66,7 @@ qx.Class.define("qx.event.Manager", {
66
66
  }
67
67
 
68
68
  // Registry for event listeners
69
- this.__listeners = {};
69
+ this.__listeners = new Map();
70
70
 
71
71
  // The handler and dispatcher instances
72
72
  this.__handlers = {};
@@ -264,18 +264,41 @@ qx.Class.define("qx.event.Manager", {
264
264
  * null when no listener were found.
265
265
  */
266
266
  getListeners(target, type, capture) {
267
- var targetKey =
268
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
269
- var targetMap = this.__listeners[targetKey];
267
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
268
+ var targetMap = this.__listeners.get(targetKey);
270
269
 
271
270
  if (!targetMap) {
272
271
  return null;
273
272
  }
274
273
 
275
274
  var entryKey = type + (capture ? "|capture" : "|bubble");
276
- var entryList = targetMap[entryKey];
277
-
278
- return entryList ? entryList.concat() : null;
275
+ var entryMap = targetMap.get(entryKey);
276
+
277
+ if (entryMap && entryMap.size > 0) {
278
+ var listeners = [...entryMap.values()];
279
+
280
+ return new Proxy(listeners, {
281
+ deleteProperty(target, property) {
282
+ if (property !== "length") {
283
+ var listener = target[property];
284
+ entryMap.delete(listener.unique);
285
+ }
286
+ delete target[property];
287
+ return true;
288
+ },
289
+ set(target, property, value, receiver) {
290
+ if (property !== "length") {
291
+ if (!value.unique) {
292
+ throw new Error("Cannot store a listener without a unique id. Use addListener()");
293
+ }
294
+ entryMap[value.unique] = value;
295
+ }
296
+ target[property] = value;
297
+ return true;
298
+ }
299
+ });
300
+ }
301
+ return null;
279
302
  },
280
303
 
281
304
  /**
@@ -283,10 +306,41 @@ qx.Class.define("qx.event.Manager", {
283
306
  *
284
307
  * @internal
285
308
  *
286
- * @return {Map} All registered listeners. The key is the hash code form an object.
309
+ * @return {Object} All registered listeners. The key is the hash code for an object.
287
310
  */
288
311
  getAllListeners() {
289
- return this.__listeners;
312
+ return Object.fromEntries(
313
+ this.__listeners.entries().map(
314
+ ([targetKey, targetMap]) => [targetKey, Object.fromEntries(
315
+ targetMap.entries().map(
316
+ ([entryKey, entryMap]) => {
317
+ var listeners = [...entryMap.values()];
318
+ var proxy = new Proxy(listeners, {
319
+ deleteProperty(target, property) {
320
+ if (property !== "length") {
321
+ var listener = target[property];
322
+ entryMap.delete(listener.unique);
323
+ }
324
+ delete target[property];
325
+ return true;
326
+ },
327
+ set(target, property, value, receiver) {
328
+ if (property !== "length") {
329
+ if (!value.unique) {
330
+ throw new Error("Cannot store a listener without a unique id. Use addListener()");
331
+ }
332
+ entryMap[value.unique] = value;
333
+ }
334
+ target[property] = value;
335
+ return true;
336
+ }
337
+ })
338
+ return [entryKey, proxy];
339
+ }
340
+ )
341
+ )]
342
+ )
343
+ );
290
344
  },
291
345
 
292
346
  /**
@@ -297,28 +351,25 @@ qx.Class.define("qx.event.Manager", {
297
351
  * <code>handler</code>, <code>self</code>, <code>type</code> and <code>capture</code>.
298
352
  */
299
353
  serializeListeners(target) {
300
- var targetKey =
301
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
302
- var targetMap = this.__listeners[targetKey];
354
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
355
+ var targetMap = this.__listeners.get(targetKey);
303
356
  var result = [];
304
357
 
305
358
  if (targetMap) {
306
- var indexOf, type, capture, entryList, entry;
307
- for (var entryKey in targetMap) {
359
+ var indexOf, type, capture;
360
+ for (const [entryKey, entryMap] of targetMap) {
308
361
  indexOf = entryKey.indexOf("|");
309
362
  type = entryKey.substring(0, indexOf);
310
- capture = entryKey.charAt(indexOf + 1) == "c";
311
- entryList = targetMap[entryKey];
312
-
313
- for (var i = 0, l = entryList.length; i < l; i++) {
314
- entry = entryList[i];
315
- result.push({
316
- self: entry.context,
317
- handler: entry.handler,
318
- type: type,
319
- capture: capture
320
- });
321
- }
363
+ capture = entryKey.charAt(indexOf + 1) === "c";
364
+ result = result.concat(
365
+ [...entryMap.values().map(entry => ({
366
+ self: entry.context,
367
+ handler: entry.handler,
368
+ type: type,
369
+ capture: capture
370
+ })
371
+ )]
372
+ );
322
373
  }
323
374
  }
324
375
 
@@ -339,17 +390,15 @@ qx.Class.define("qx.event.Manager", {
339
390
  * @param enable {Boolean} Whether to enable or disable the events
340
391
  */
341
392
  toggleAttachedEvents(target, enable) {
342
- var targetKey =
343
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
344
- var targetMap = this.__listeners[targetKey];
393
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
394
+ var targetMap = this.__listeners.get(targetKey);
345
395
 
346
396
  if (targetMap) {
347
- var indexOf, type, capture, entryList;
348
- for (var entryKey in targetMap) {
397
+ var indexOf, type, capture;
398
+ for (const entryKey of targetMap.keys()) {
349
399
  indexOf = entryKey.indexOf("|");
350
400
  type = entryKey.substring(0, indexOf);
351
401
  capture = entryKey.charCodeAt(indexOf + 1) === 99; // checking for character "c".
352
- entryList = targetMap[entryKey];
353
402
 
354
403
  if (enable) {
355
404
  this.__registerAtHandler(target, type, capture);
@@ -378,18 +427,17 @@ qx.Class.define("qx.event.Manager", {
378
427
  }
379
428
  }
380
429
 
381
- var targetKey =
382
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
383
- var targetMap = this.__listeners[targetKey];
430
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
431
+ var targetMap = this.__listeners.get(targetKey);
384
432
 
385
433
  if (!targetMap) {
386
434
  return false;
387
435
  }
388
436
 
389
437
  var entryKey = type + (capture ? "|capture" : "|bubble");
390
- var entryList = targetMap[entryKey];
438
+ var entryMap = targetMap.get(entryKey);
391
439
 
392
- return !!(entryList && entryList.length > 0);
440
+ return Boolean(entryMap && entryMap.size > 0);
393
441
  },
394
442
 
395
443
  /**
@@ -415,32 +463,40 @@ qx.Class.define("qx.event.Manager", {
415
463
  }
416
464
  }
417
465
 
418
- var targetKey =
419
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
420
- var targetMap = (this.__listeners[targetKey] = {});
421
- var clazz = qx.event.Manager;
466
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
467
+ var targetMap = this.__listeners.get(targetKey);
468
+
469
+ if (!targetMap) {
470
+ targetMap = new Map();
471
+ this.__listeners.set(targetKey, targetMap);
472
+ }
422
473
 
423
474
  for (var listKey in list) {
424
475
  var item = list[listKey];
425
476
 
426
477
  var entryKey = item.type + (item.capture ? "|capture" : "|bubble");
427
- var entryList = targetMap[entryKey];
478
+ var entryMap = targetMap.get(entryKey);
428
479
 
429
- if (!entryList) {
430
- entryList = targetMap[entryKey] = [];
480
+ if (!entryMap) {
481
+ entryMap = new Map();
482
+ targetMap.set(entryKey, entryMap)
483
+ }
431
484
 
485
+ if (entryMap.size === 0) {
432
486
  // This is the first event listener for this type and target
433
487
  // Inform the event handler about the new event
434
488
  // they perform the event registration at DOM level if needed
435
489
  this.__registerAtHandler(target, item.type, item.capture);
436
490
  }
437
491
 
438
- // Append listener to list
439
- entryList.push({
440
- handler: item.listener,
441
- context: item.self,
442
- unique: item.unique || clazz.__lastUnique++ + ""
443
- });
492
+ // Add listener to map
493
+ var unique = item.unique || qx.event.Manager.getNextUniqueId();
494
+ entryMap.set(unique, {
495
+ handler: item.listener,
496
+ context: item.self,
497
+ unique: unique
498
+ }
499
+ );
444
500
  }
445
501
  },
446
502
 
@@ -486,37 +542,37 @@ qx.Class.define("qx.event.Manager", {
486
542
  }
487
543
  }
488
544
 
489
- var targetKey =
490
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
491
- var targetMap = this.__listeners[targetKey];
545
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
546
+ var targetMap = this.__listeners.get(targetKey);
492
547
 
493
548
  if (!targetMap) {
494
- targetMap = this.__listeners[targetKey] = {};
549
+ targetMap = new Map();
550
+ this.__listeners.set(targetKey, targetMap);
495
551
  }
496
552
 
497
553
  var entryKey = type + (capture ? "|capture" : "|bubble");
498
- var entryList = targetMap[entryKey];
554
+ var entryMap = targetMap.get(entryKey);
499
555
 
500
- if (!entryList) {
501
- entryList = targetMap[entryKey] = [];
556
+ if (!entryMap) {
557
+ entryMap = new Map();
558
+ targetMap.set(entryKey, entryMap)
502
559
  }
503
560
 
504
- // This is the first event listener for this type and target
505
- // Inform the event handler about the new event
506
- // they perform the event registration at DOM level if needed
507
- if (entryList.length === 0) {
561
+ if (entryMap.size === 0) {
562
+ // This is the first event listener for this type and target
563
+ // Inform the event handler about the new event
564
+ // they perform the event registration at DOM level if needed
508
565
  this.__registerAtHandler(target, type, capture);
509
566
  }
510
567
 
511
- // Append listener to list
512
- var unique = qx.event.Manager.__lastUnique++ + "";
513
- var entry = {
514
- handler: listener,
515
- context: self,
516
- unique: unique
517
- };
518
-
519
- entryList.push(entry);
568
+ // Add listener to map
569
+ var unique = qx.event.Manager.getNextUniqueId();
570
+ entryMap.set(unique, {
571
+ handler: listener,
572
+ context: self,
573
+ unique: unique
574
+ }
575
+ );
520
576
 
521
577
  return entryKey + "|" + unique;
522
578
  },
@@ -524,7 +580,7 @@ qx.Class.define("qx.event.Manager", {
524
580
  /**
525
581
  * Get the event handler class matching the given event target and type
526
582
  *
527
- * @param target {var} The event target
583
+ * @param target {Object} The event target
528
584
  * @param type {String} The event type
529
585
  * @return {qx.event.IEventHandler|null} The best matching event handler or
530
586
  * <code>null</code>.
@@ -683,38 +739,29 @@ qx.Class.define("qx.event.Manager", {
683
739
  }
684
740
  }
685
741
 
686
- var targetKey =
687
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
688
- var targetMap = this.__listeners[targetKey];
742
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
743
+ var targetMap = this.__listeners.get(targetKey);
689
744
 
690
745
  if (!targetMap) {
691
746
  return false;
692
747
  }
693
-
694
748
  var entryKey = type + (capture ? "|capture" : "|bubble");
695
- var entryList = targetMap[entryKey];
749
+ var entryMap = targetMap.get(entryKey);
696
750
 
697
- if (!entryList) {
751
+ if (!entryMap) {
698
752
  return false;
699
753
  }
754
+ var deleted = false;
700
755
 
701
- var entry;
702
- for (var i = 0, l = entryList.length; i < l; i++) {
703
- entry = entryList[i];
704
-
705
- if (entry.handler === listener && entry.context === self) {
706
- qx.lang.Array.removeAt(entryList, i);
707
- this.__addToBlacklist(entry.unique);
708
-
709
- if (entryList.length == 0) {
710
- this.__unregisterAtHandler(target, type, capture);
711
- }
712
-
713
- return true;
756
+ for (const [entryKey, entry] of entryMap.entries().filter(([eK, e]) => e.handler === listener && e.context === self)) {
757
+ deleted = true;
758
+ entryMap.delete(entryKey);
759
+ this.__addToBlacklist(entryKey);
760
+ if (entryMap.size === 0) {
761
+ this.__unregisterAtHandler(target, type, capture);
714
762
  }
715
763
  }
716
-
717
- return false;
764
+ return deleted;
718
765
  },
719
766
 
720
767
  /**
@@ -741,40 +788,32 @@ qx.Class.define("qx.event.Manager", {
741
788
 
742
789
  var split = id.split("|");
743
790
  var type = split[0];
744
- var capture = split[1].charCodeAt(0) == 99; // detect leading "c"
791
+ var capture = split[1].charCodeAt(0) === 99; // detect leading "c"
745
792
  var unique = split[2];
746
793
 
747
- var targetKey =
748
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
749
- var targetMap = this.__listeners[targetKey];
794
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
795
+ var targetMap = this.__listeners.get(targetKey);
750
796
 
751
797
  if (!targetMap) {
752
798
  return false;
753
799
  }
754
800
 
755
801
  var entryKey = type + (capture ? "|capture" : "|bubble");
756
- var entryList = targetMap[entryKey];
802
+ var entryMap = targetMap.get(entryKey);
757
803
 
758
- if (!entryList) {
804
+ if (!entryMap) {
759
805
  return false;
760
806
  }
761
807
 
762
- var entry;
763
- for (var i = 0, l = entryList.length; i < l; i++) {
764
- entry = entryList[i];
765
-
766
- if (entry.unique === unique) {
767
- qx.lang.Array.removeAt(entryList, i);
768
- this.__addToBlacklist(entry.unique);
769
-
770
- if (entryList.length == 0) {
771
- this.__unregisterAtHandler(target, type, capture);
772
- }
773
-
774
- return true;
808
+ var entry = entryMap.get(unique);
809
+ if (entry) {
810
+ entryMap.delete(unique);
811
+ this.__addToBlacklist(entry.unique);
812
+ if (entryMap.size === 0) {
813
+ this.__unregisterAtHandler(target, type, capture);
775
814
  }
815
+ return true;
776
816
  }
777
-
778
817
  return false;
779
818
  },
780
819
 
@@ -785,23 +824,23 @@ qx.Class.define("qx.event.Manager", {
785
824
  * @return {Boolean} Whether the events were existant and were removed successfully.
786
825
  */
787
826
  removeAllListeners(target) {
788
- var targetKey =
789
- target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
790
- var targetMap = this.__listeners[targetKey];
827
+ var targetKey = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
828
+ var targetMap = this.__listeners.get(targetKey);
791
829
  if (!targetMap) {
792
830
  return false;
793
831
  }
794
832
 
795
833
  // Deregister from event handlers
796
834
  var split, type, capture;
797
- for (var entryKey in targetMap) {
798
- if (targetMap[entryKey].length > 0) {
835
+ for (const [entryKey, entryMap] of targetMap) {
836
+ if (entryMap && entryMap.size > 0) {
799
837
  // This is quite expensive, see bug #1283
800
838
  split = entryKey.split("|");
801
839
 
802
- targetMap[entryKey].forEach(function (entry) {
803
- this.__addToBlacklist(entry.unique);
804
- }, this);
840
+ for (const uniqueKey of entryMap.keys()) {
841
+ this.__addToBlacklist(uniqueKey);
842
+ }
843
+ entryMap.clear();
805
844
 
806
845
  type = split[0];
807
846
  capture = split[1] === "capture";
@@ -810,7 +849,7 @@ qx.Class.define("qx.event.Manager", {
810
849
  }
811
850
  }
812
851
 
813
- delete this.__listeners[targetKey];
852
+ this.__listeners.delete(targetKey);
814
853
  return true;
815
854
  },
816
855
 
@@ -824,7 +863,7 @@ qx.Class.define("qx.event.Manager", {
824
863
  * @internal
825
864
  */
826
865
  deleteAllListeners(targetKey) {
827
- delete this.__listeners[targetKey];
866
+ this.__listeners.delete(targetKey);
828
867
  },
829
868
 
830
869
  /**
@@ -1002,7 +1041,7 @@ qx.Class.define("qx.event.Manager", {
1002
1041
  /**
1003
1042
  * Add event to blacklist.
1004
1043
  *
1005
- * @param uid {number} unique event id
1044
+ * @param uid {String} unique event id
1006
1045
  */
1007
1046
  __addToBlacklist(uid) {
1008
1047
  if (this.__blacklist === null) {
@@ -1015,7 +1054,7 @@ qx.Class.define("qx.event.Manager", {
1015
1054
  /**
1016
1055
  * Check if the event with the given id has been removed and is therefore blacklisted for event handling
1017
1056
  *
1018
- * @param uid {number} unique event id
1057
+ * @param uid {String} unique event id
1019
1058
  * @return {boolean}
1020
1059
  */
1021
1060
  isBlacklisted(uid) {
@@ -41,7 +41,7 @@ qx.Bootstrap.define("qx.io.ImageLoader", {
41
41
  __knownImageTypesRegExp: /\.(png|gif|jpg|jpeg|bmp)\b/i,
42
42
 
43
43
  /** @type {RegExp} Image types of a data URL */
44
- __dataUrlRegExp: /^data:image\/(png|gif|jpg|jpeg|bmp)\b/i,
44
+ __dataUrlRegExp: /^data:image\/(png|gif|jpg|jpeg|bmp|svg)\b/i,
45
45
 
46
46
  /**
47
47
  * Whether the given image has previously been loaded using the
@@ -161,12 +161,15 @@ qx.Bootstrap.define("qx.io.ImageLoader", {
161
161
  * second parameter is the data entry which contains additional
162
162
  * information about the image.
163
163
  * @param context {Object?} Context in which the given callback should be executed
164
+ * @param options {Map?} Optional configuration options:
165
+ * - retryFailed {boolean} Whether to retry loading of a failed image source
164
166
  */
165
- load(source, callback, context) {
167
+ load(source, callback, context, options) {
168
+ options ??= {};
166
169
  // Shorthand
167
170
  var entry = this.__data[source];
168
171
 
169
- if (!entry) {
172
+ if (!entry || (options.retryFailed && entry.failed)) {
170
173
  entry = this.__data[source] = {};
171
174
  }
172
175
 
@@ -23,6 +23,7 @@
23
23
  qx.Class.define("qx.io.exception.Transport", {
24
24
  extend: qx.io.exception.Exception,
25
25
  statics: {
26
+ FORWARDED: 0,
26
27
  TIMEOUT: 1,
27
28
  ABORTED: 2,
28
29
  NO_DATA: 3,
@@ -125,8 +125,8 @@ qx.Class.define("qx.io.jsonrpc.Client", {
125
125
  },
126
126
 
127
127
  /**
128
- * Fires "error" event and throws the error after informing pending requests
129
- * about the error.
128
+ * Fires "error" event and rejects the pending requests' promises.
129
+ * The method will be renamed and made private in v8.
130
130
  * @param exception
131
131
  * @private
132
132
  */
@@ -134,19 +134,37 @@ qx.Class.define("qx.io.jsonrpc.Client", {
134
134
  this.fireDataEvent("error", exception);
135
135
  this.__requests.forEach(request => {
136
136
  if (request instanceof qx.io.jsonrpc.protocol.Request) {
137
+ // this rejects the request's promise
137
138
  request.handleTransportException(exception);
138
139
  }
139
140
  });
140
- throw exception;
141
+ if (!qx.core.Environment.get("qx.io.jsonrpc.forwardTransportPromiseRejectionToRequest")){
142
+ throw exception; // will be removed in v8 since it is not caught anywhere
143
+ }
141
144
  },
142
145
 
143
146
  /**
144
147
  * Send the given JSON-RPC message object using the configured transport
145
148
  *
146
149
  * @param {qx.io.jsonrpc.protocol.Message|qx.io.jsonrpc.protocol.Batch} message
147
- * @return {qx.Promise} Promise that resolves (with no data)
148
- * when the message has been successfully sent out, and rejects
149
- * when there is an error or a cancellation up to that point.
150
+ * @return {qx.Promise} Promise that resolves (with no data) when the message has been successfully
151
+ * sent out. As this means different things depending on the transport implementation, it is best
152
+ * not to base any kind of business logic on the fulfillment of that promise.
153
+ *
154
+ * The current behavior is to return the promise from the transport `send()` implementation, which
155
+ * might be rejected with a {@link qx.io.exception.Transport} in case of a transport error.
156
+ * This has caused problems with "unhandled promise rejection" errors, so a new behaviour will be
157
+ * the default in v8: The returned promise is already resolved, and any rejection of the transport
158
+ * promise will only be forwarded to the promise(s) of the request(s) contained in the the `message`
159
+ * argument. The returned promise will never be rejected. This behavior can be enabled by setting
160
+ * the environment variable `qx.io.jsonrpc.forwardTransportPromiseRejectionToRequest` to `true` in v7.
161
+ * In v8, the default of `qx.io.jsonrpc.forwardTransportPromiseRejectionToRequest` will become `true`,
162
+ * and that environment variable will become deprecated.
163
+ *
164
+ * In any case, the result of the jsonrpc request is retrieved by awaiting {@link qx.io.jsonrpc.protocol.Request}'s
165
+ * promise, which is resolved with the jsonrpc server's response or is rejected either with a
166
+ * {@link qx.io.exception.Transport} in case of a transport error or with {@link qx.io.protocol.Error}
167
+ * in case of a jsonrpc error.
150
168
  */
151
169
  async send(message) {
152
170
  if (
@@ -191,7 +209,27 @@ qx.Class.define("qx.io.jsonrpc.Client", {
191
209
  }
192
210
 
193
211
  // send it async, using transport-specific implementation
194
- return this.getTransport().send(message.toString());
212
+ const transportPromise = this.getTransport().send(message.toString())
213
+
214
+ if (qx.core.Environment.get("qx.io.jsonrpc.forwardTransportPromiseRejectionToRequest")) {
215
+ // forward rejections to the requests' promises, which will be standard behavior in v8
216
+ transportPromise.catch(error => {
217
+ // wrap error in transport exception
218
+ if (!(error instanceof qx.io.exception.Transport)) {
219
+ error = new qx.io.exception.Transport(
220
+ error.toString(),
221
+ qx.io.exception.Transport.FORWARDED,
222
+ error
223
+ );
224
+ }
225
+ this._throwTransportException(error)
226
+ })
227
+ // return a resolved promise so that the actual completion of the transport is not awaited
228
+ return qx.Promise.resolve()
229
+ } else {
230
+ // default behavior in v7: return promise from transport
231
+ return transportPromise;
232
+ }
195
233
  },
196
234
 
197
235
  /**
@@ -208,7 +246,9 @@ qx.Class.define("qx.io.jsonrpc.Client", {
208
246
  params
209
247
  );
210
248
 
249
+ // await completion of transport
211
250
  await this.send(request);
251
+ // await fulfillment of requests
212
252
  return await request.getPromise();
213
253
  },
214
254
 
@@ -243,7 +283,9 @@ qx.Class.define("qx.io.jsonrpc.Client", {
243
283
  message.setMethod(this._prependMethodPrefix(message.getMethod()))
244
284
  );
245
285
  }
286
+ // await completion of transport
246
287
  await this.send(batch);
288
+ // await fulfilment of requests
247
289
  return await qx.Promise.all(batch.getPromises());
248
290
  },
249
291
 
@@ -284,6 +326,7 @@ qx.Class.define("qx.io.jsonrpc.Client", {
284
326
  /**
285
327
  * Handle an incoming message or batch of messages
286
328
  * @param {qx.io.jsonrpc.protocol.Message|qx.io.jsonrpc.protocol.Batch} message Message or Batch
329
+ * @throws {qx.io.exception.Transport} For transport-related errors
287
330
  */
288
331
  handleMessage(message) {
289
332
  // handle batches
@@ -324,6 +367,7 @@ qx.Class.define("qx.io.jsonrpc.Client", {
324
367
  // resolve the individual promise
325
368
  request.getPromise().resolve(message.getResult());
326
369
  } else if (message instanceof qx.io.jsonrpc.protocol.Error) {
370
+ // handle jsonrpc (not transport-related) errors
327
371
  let error = message.getError();
328
372
  let ex = new qx.io.exception.Protocol(
329
373
  error.message,
@@ -350,6 +394,18 @@ qx.Class.define("qx.io.jsonrpc.Client", {
350
394
  },
351
395
 
352
396
  environment: {
353
- "qx.io.jsonrpc.debug": false
397
+ /**
398
+ * If true, log detailed information on the jsonrpc traffic in the console
399
+ */
400
+ "qx.io.jsonrpc.debug": false,
401
+
402
+ /**
403
+ * If true, forward transport errors to the running jsonrpc requests' promise instead of rejecting
404
+ * the promise returned by {@link #send}. Default is `false`.
405
+
406
+ * @deprecated
407
+ * Behavior in v8 will be as if the environment variable value is `true`, but the environment variable will no longer be available.
408
+ */
409
+ "qx.io.jsonrpc.forwardTransportPromiseRejectionToRequest": false
354
410
  }
355
411
  });