react-x11 2.18.0 → 2.18.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-x11",
3
- "version": "2.18.0",
3
+ "version": "2.18.2",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
package/src/cocoa/app.js CHANGED
@@ -85,6 +85,17 @@ const SCREEN_POLL_MS = 500;
85
85
  // have finished moving the window (`_recheckScreens`).
86
86
  const SCREEN_RECHECK_MS = 1000;
87
87
 
88
+ /** One frame callback; a throw is reported, not allowed to end the tick. */
89
+ function runFrame(entry, now) {
90
+ try {
91
+ entry.cb(now);
92
+ } catch (err) {
93
+ queueMicrotask(() => {
94
+ throw err;
95
+ });
96
+ }
97
+ }
98
+
88
99
  export class CocoaApp {
89
100
  constructor(native, options = {}) {
90
101
  this._native = native;
@@ -92,7 +103,7 @@ export class CocoaApp {
92
103
  // CocoaWindow._key -> CocoaWindow: the number, or on a worker the handle
93
104
  this._windows = new Map();
94
105
  this._grabWindow = null;
95
- this._rafQueue = []; // [{ cb, wnd }]
106
+ this._rafQueue = []; // [{ cb, wnd, surface }]
96
107
  // the app's own frame clock, for a frame no window owns (a pane's)
97
108
  this._rafLast = 0;
98
109
  // an explicit interval applies to every window; null means each
@@ -843,8 +854,8 @@ export class CocoaApp {
843
854
  * paced claim (src/pacing.js) land at its wait rather than at its wait
844
855
  * rounded up to the pump.
845
856
  */
846
- _requestFrame(cb, wnd = null) {
847
- this._rafQueue.push({ cb, wnd });
857
+ _requestFrame(cb, wnd = null, surface = false) {
858
+ this._rafQueue.push({ cb, wnd, surface });
848
859
  if ((this._pump || this._threaded) && this._rafQueue.length === 1) {
849
860
  const now = performance.now();
850
861
  this._armFrameTimer(Math.max(1, this._frameWait(wnd ?? this, now)), now);
@@ -947,6 +958,14 @@ export class CocoaApp {
947
958
  const queue = this._rafQueue;
948
959
  this._rafQueue = [];
949
960
  let soonest = Infinity;
961
+ // A `<glarea>`'s frames, due in this tick, run after every window's
962
+ // own: its children are laid out, painted and presented in the window's
963
+ // flush, so a GL frame drawn ahead of that flush reached the screen
964
+ // under the overlay of the frame before — for as long as the flush took,
965
+ // which is more than a display frame when the change costs layout
966
+ // (issue #641). The Wayland backend orders them the same way
967
+ // (`requestSurfaceFrame`).
968
+ const surfaces = [];
950
969
  for (const entry of queue) {
951
970
  // A window whose last flip has not reached its layer yet (threaded
952
971
  // mode's fence, `CocoaWindow.frameInFlight`) waits before
@@ -974,14 +993,10 @@ export class CocoaApp {
974
993
  this._rafQueue.push(entry);
975
994
  continue;
976
995
  }
977
- try {
978
- entry.cb(now);
979
- } catch (err) {
980
- queueMicrotask(() => {
981
- throw err;
982
- });
983
- }
996
+ if (entry.surface) surfaces.push(entry);
997
+ else runFrame(entry, now);
984
998
  }
999
+ for (const entry of surfaces) runFrame(entry, now);
985
1000
  this._armFrameTimer(soonest, now);
986
1001
  }
987
1002
 
@@ -28,6 +28,8 @@
28
28
  // so the ladder is visible before it is built. Policy decides, the machine
29
29
  // answers — the same rule the GL policy follows everywhere else.
30
30
 
31
+ import { withoutActions } from './quiet.js';
32
+
31
33
  const RUNGS = ['auto', 'gl'];
32
34
  const KNOWN_RUNGS = ['auto', 'gl', 'gles', 'webgpu'];
33
35
 
@@ -145,8 +147,11 @@ export class CocoaGLArea {
145
147
  this.scale = this.parent.scale ?? app.scale ?? 1;
146
148
  this.destroyed = false;
147
149
  this._reactX11Node = null;
148
- this.layer = this._native.createLayer();
149
- this._native.addSublayer(this.parent._layer, this.layer);
150
+ this.layer = withoutActions(this._native, () => {
151
+ const layer = this._native.createLayer();
152
+ this._native.addSublayer(this.parent._layer, layer);
153
+ return layer;
154
+ });
150
155
  this.rect = null;
151
156
  this.setState({
152
157
  x: options.x ?? 0,
@@ -227,8 +232,9 @@ export class CocoaGLArea {
227
232
  for (const fn of this._listeners.get(name) ?? []) fn(ev);
228
233
  }
229
234
 
235
+ // on the owning window's clock, after its flush in the same tick (#641)
230
236
  requestAnimationFrame(cb) {
231
- return this.parent.requestAnimationFrame(cb);
237
+ return this.parent.requestSurfaceFrame(cb);
232
238
  }
233
239
 
234
240
  /**
@@ -254,7 +260,9 @@ export class CocoaGLArea {
254
260
  this.destroyed = true;
255
261
  this._context?._destroy();
256
262
  this._context = null;
257
- this._native.removeFromSuperlayer(this.layer);
263
+ withoutActions(this._native, () =>
264
+ this._native.removeFromSuperlayer(this.layer),
265
+ );
258
266
  }
259
267
  }
260
268
 
@@ -15,6 +15,7 @@
15
15
  // was painted on the layer: ntk blits an X window's backing store on its
16
16
  // own, where a layer's contents are a copy the bitmap has to be pushed to.
17
17
  import { BackendContext2D } from '../backend/context2d.js';
18
+ import { withoutActions } from './quiet.js';
18
19
 
19
20
  export const OVERLAY_Z = 1e7 + 1;
20
21
 
@@ -25,8 +26,11 @@ export class CocoaOverlayPane {
25
26
  this._native = app._native;
26
27
  this.scale = this.parent.scale ?? app.scale ?? 1;
27
28
  this.destroyed = false;
28
- this.layer = this._native.createLayer();
29
- this._native.addSublayer(this.parent._layer, this.layer);
29
+ this.layer = withoutActions(this._native, () => {
30
+ const layer = this._native.createLayer();
31
+ this._native.addSublayer(this.parent._layer, layer);
32
+ return layer;
33
+ });
30
34
  this.rect = null;
31
35
  this._surface = null;
32
36
  this._surfaceSize = null;
@@ -154,6 +158,11 @@ export class CocoaOverlayPane {
154
158
  this.destroyed = true;
155
159
  this._release();
156
160
  this._ctx = null;
157
- this._native.removeFromSuperlayer(this.layer);
161
+ // Actions off, or the layer fades out over a quarter of a second, what
162
+ // the children last painted going translucent over a surface still
163
+ // drawing (#638).
164
+ withoutActions(this._native, () =>
165
+ this._native.removeFromSuperlayer(this.layer),
166
+ );
158
167
  }
159
168
  }
@@ -3,13 +3,18 @@
3
3
  // contents are whatever IOSurface the pane process last presented. The pane
4
4
  // owns its buffers and its drawing; this side owns the layer, the layout
5
5
  // and the input — CPU offloading, not isolation (docs/frame.md).
6
+ import { withoutActions } from './quiet.js';
7
+
6
8
  export class CocoaPaneHost {
7
9
  constructor(app, wnd) {
8
10
  this.app = app;
9
11
  this.wnd = wnd;
10
12
  this._native = app._native;
11
- this.layer = this._native.createLayer();
12
- this._native.addSublayer(wnd._layer, this.layer);
13
+ this.layer = withoutActions(this._native, () => {
14
+ const layer = this._native.createLayer();
15
+ this._native.addSublayer(wnd._layer, layer);
16
+ return layer;
17
+ });
13
18
  this.destroyed = false;
14
19
  this._rect = null;
15
20
  }
@@ -73,6 +78,8 @@ export class CocoaPaneHost {
73
78
  destroy() {
74
79
  if (this.destroyed) return;
75
80
  this.destroyed = true;
76
- this._native.removeFromSuperlayer(this.layer);
81
+ withoutActions(this._native, () =>
82
+ this._native.removeFromSuperlayer(this.layer),
83
+ );
77
84
  }
78
85
  }
@@ -99,6 +99,11 @@ export class CocoaPaneWindow {
99
99
  return this.app._requestFrame(cb);
100
100
  }
101
101
 
102
+ /** A `<glarea>`'s frame, after this pane's own (`CocoaWindow`'s). */
103
+ requestSurfaceFrame(cb) {
104
+ return this.app._requestFrame(cb, null, true);
105
+ }
106
+
102
107
  /**
103
108
  * Whether the host may still be showing the frame before the last one —
104
109
  * the gate `flushPendingFrames` (src/frames.js) is written around: a
@@ -0,0 +1,13 @@
1
+ // Layer changes with Core Animation's implicit actions off, for a layer
2
+ // that is ours rather than a presenter's (a <glarea>'s surface and overlay,
3
+ // a frame pane's host): no frame's transaction covers it, so a bare
4
+ // `addSublayer` or `removeFromSuperlayer` takes the default quarter-second
5
+ // order-in or order-out fade, and a bare property set tweens.
6
+ export function withoutActions(native, fn) {
7
+ native.txBegin({ disableActions: true });
8
+ try {
9
+ return fn();
10
+ } finally {
11
+ native.txCommit();
12
+ }
13
+ }
@@ -915,6 +915,15 @@ export class CocoaWindow {
915
915
  return this.app._requestFrame(cb, this);
916
916
  }
917
917
 
918
+ /**
919
+ * A `<glarea>`'s frame: on this window's clock, and after this window's
920
+ * own frame in the tick that runs both, so the GL frame goes out after
921
+ * the overlay its children were painted on (src/cocoa/glarea.js).
922
+ */
923
+ requestSurfaceFrame(cb) {
924
+ return this.app._requestFrame(cb, this, true);
925
+ }
926
+
918
927
  /**
919
928
  * The earliest moment this window's clock will hand out another frame,
920
929
  * for a gate of `interval` ms — the display's period by default, which