react-x11 2.8.3 → 2.9.1
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 +4 -2
- package/src/Reconciler.js +7 -0
- package/src/cocoa/app.js +35 -0
- package/src/cocoa/calendar.js +186 -0
- package/src/cocoa/context2d.js +220 -4
- package/src/cocoa/native.js +20 -3
- package/src/cocoa/permissions.js +14 -5
- package/src/cocoa/window.js +21 -5
- package/src/debug.js +8 -2
- package/src/desktopcalendar.js +1415 -0
- package/src/desktopcalendarhooks.js +259 -0
- package/src/glnodes.js +63 -4
- package/src/index.d.ts +11 -1
- package/src/index.js +9 -0
- package/src/node.d.ts +214 -2
- package/src/nodes.js +208 -13
- package/src/pacing.js +476 -0
- package/src/permissionhooks.js +9 -6
- package/src/permissions.js +12 -2
- package/src/trace-registry.js +4 -3
- package/src/types/desktopcalendar.d.ts +207 -0
- package/src/types/elements.d.ts +54 -0
- package/src/types/permissions.d.ts +12 -4
package/src/nodes.js
CHANGED
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
} from './dnd.js';
|
|
89
89
|
import { TYPE_GROUPS } from './transfer.js';
|
|
90
90
|
import { addPendingFrame, clearPendingFrame } from './frames.js';
|
|
91
|
+
import { FramePacer, resolveFramePolicy } from './pacing.js';
|
|
91
92
|
import { createClientMessages } from './clientmessage.js';
|
|
92
93
|
import {
|
|
93
94
|
argbVisual,
|
|
@@ -413,6 +414,17 @@ function insetRect(rect, by) {
|
|
|
413
414
|
};
|
|
414
415
|
}
|
|
415
416
|
|
|
417
|
+
/** The whole pixels inside `rect` — a fractional edge left out — or null
|
|
418
|
+
* when none are. */
|
|
419
|
+
function innerPixels(rect) {
|
|
420
|
+
const x = Math.ceil(rect.x);
|
|
421
|
+
const y = Math.ceil(rect.y);
|
|
422
|
+
const right = Math.floor(rect.x + rect.width);
|
|
423
|
+
const bottom = Math.floor(rect.y + rect.height);
|
|
424
|
+
if (right <= x || bottom <= y) return null;
|
|
425
|
+
return { x, y, width: right - x, height: bottom - y };
|
|
426
|
+
}
|
|
427
|
+
|
|
416
428
|
/** The overlap of two rects, or null when they have none. */
|
|
417
429
|
export function intersectRects(a, b) {
|
|
418
430
|
const x = Math.max(a.x, b.x);
|
|
@@ -3501,6 +3513,7 @@ export class Node {
|
|
|
3501
3513
|
// next tick, so a spinner that unmounts leaves the clock idle even if
|
|
3502
3514
|
// nothing else ever asks for a frame
|
|
3503
3515
|
this.root?._animating.delete(this);
|
|
3516
|
+
this.root?._opaqueNodes?.delete(this);
|
|
3504
3517
|
// a surface that goes away takes its selection with it, and the app-wide
|
|
3505
3518
|
// claim on being the one showing one goes with it too
|
|
3506
3519
|
this._textSelection?.destroy();
|
|
@@ -3510,7 +3523,11 @@ export class Node {
|
|
|
3510
3523
|
|
|
3511
3524
|
_setRoot(root) {
|
|
3512
3525
|
if (this.root === root) return;
|
|
3526
|
+
// an element answering `opaqueRect()` is one the window asks per pass
|
|
3527
|
+
const opaque = this.opaqueRect !== Node.prototype.opaqueRect;
|
|
3528
|
+
if (opaque) this.root?._opaqueNodes?.delete(this);
|
|
3513
3529
|
this.root = root;
|
|
3530
|
+
if (opaque) root?._opaqueNodes?.add(this);
|
|
3514
3531
|
// A node is styled in its constructor, before it has a window — so this
|
|
3515
3532
|
// is where a loop declared by the very first style finds a frame clock
|
|
3516
3533
|
// to run on.
|
|
@@ -4258,6 +4275,32 @@ export class Node {
|
|
|
4258
4275
|
return this.root?._paintDamage ?? null;
|
|
4259
4276
|
}
|
|
4260
4277
|
|
|
4278
|
+
/**
|
|
4279
|
+
* The rect this element writes opaque pixels over on every paint — in
|
|
4280
|
+
* window coordinates like `abs`, in whole pixels — or null, the default,
|
|
4281
|
+
* which promises nothing.
|
|
4282
|
+
*
|
|
4283
|
+
* What it buys: a pass that lies inside it is painted without the fills
|
|
4284
|
+
* that would be under it — the window's background, this node's own and
|
|
4285
|
+
* every ancestor's — because not one of those pixels survives. On the
|
|
4286
|
+
* Cocoa backend those fills are full-area CoreGraphics passes, and for a
|
|
4287
|
+
* streaming terminal they were a fifth of the frame; on X11 they are
|
|
4288
|
+
* composites the server ran for nothing. An element with a retained
|
|
4289
|
+
* surface it draws whole — a terminal, a media frame, a chart — answers
|
|
4290
|
+
* with the rect it covers, and claims its damage as a **rect inside it**
|
|
4291
|
+
* rather than as the node: a node claim is inflated by a pixel of slop,
|
|
4292
|
+
* which is outside the rect and so never covered.
|
|
4293
|
+
*
|
|
4294
|
+
* The promise is the element's to keep: every pixel of the rect, alpha
|
|
4295
|
+
* one, on every paint of this node, whatever the props. A translucent
|
|
4296
|
+
* element, or one that draws a background only sometimes, answers null.
|
|
4297
|
+
* The answer is read at paint time, so it may follow `contentBox()`, and
|
|
4298
|
+
* a fractional edge is not opaque — core takes the whole pixels inside.
|
|
4299
|
+
*/
|
|
4300
|
+
opaqueRect() {
|
|
4301
|
+
return null;
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4261
4304
|
/**
|
|
4262
4305
|
* "The pixels in `rect` moved by (dx, dy); the rest of it is new" — the
|
|
4263
4306
|
* public form of the dance `<box overflow="scroll">` has been doing since
|
|
@@ -4915,6 +4958,9 @@ export class Node {
|
|
|
4915
4958
|
}
|
|
4916
4959
|
|
|
4917
4960
|
_paintBackground(ctx) {
|
|
4961
|
+
// under an element that covers this pass with opaque pixels, this fill
|
|
4962
|
+
// is never seen (`WindowNode._coverFor`)
|
|
4963
|
+
if (this.root?._coverChain?.has(this)) return;
|
|
4918
4964
|
const { backgroundColor, borderRadius = 0 } = this.style;
|
|
4919
4965
|
const fill = (style) => {
|
|
4920
4966
|
ctx.fillStyle = style;
|
|
@@ -9343,6 +9389,22 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
9343
9389
|
this.needsLayout = true;
|
|
9344
9390
|
this.needsPaint = true;
|
|
9345
9391
|
this._scheduled = false;
|
|
9392
|
+
// The frame pacer (src/pacing.js): whether a claim waits before its
|
|
9393
|
+
// frame is scheduled, priced by what the last frames cost. Off unless
|
|
9394
|
+
// the `frameRate` prop, the root's default or the environment says
|
|
9395
|
+
// otherwise — resolved again whenever the prop changes.
|
|
9396
|
+
this._pacer = new FramePacer();
|
|
9397
|
+
this._framePolicy = null;
|
|
9398
|
+
this._syncFramePolicy();
|
|
9399
|
+
// a claim raised by the frame on itself is scheduled once the frame is
|
|
9400
|
+
// over and its cost is known (`flush`)
|
|
9401
|
+
this._inFlush = false;
|
|
9402
|
+
this._claimAfterFlush = false;
|
|
9403
|
+
// the nodes answering `opaqueRect()`, and — during a paint pass one of
|
|
9404
|
+
// them covers — that node with its ancestors, whose fills are skipped
|
|
9405
|
+
// (`_coverFor`, `Node._paintBackground`)
|
|
9406
|
+
this._opaqueNodes = new Set();
|
|
9407
|
+
this._coverChain = null;
|
|
9346
9408
|
// Nodes that want the `attention` event (ntk#37) — an
|
|
9347
9409
|
// `unstable_onAttention` prop,
|
|
9348
9410
|
// an `:attention` block, or both. Built before the EventManager so the
|
|
@@ -11212,6 +11274,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
11212
11274
|
if (this.destroyed) return;
|
|
11213
11275
|
this.destroyed = true;
|
|
11214
11276
|
clearPendingFrame(this);
|
|
11277
|
+
this._pacer.cancel();
|
|
11215
11278
|
this._unwatchCompositing?.();
|
|
11216
11279
|
this._unwatchCompositing = null;
|
|
11217
11280
|
// before endWindowState below, which is the session this is subscribed to
|
|
@@ -11252,6 +11315,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
11252
11315
|
if (Boolean(newProps.trapFocus) !== Boolean(before.trapFocus)) {
|
|
11253
11316
|
this._syncFocusScope();
|
|
11254
11317
|
}
|
|
11318
|
+
if (newProps.frameRate !== before.frameRate) this._syncFramePolicy();
|
|
11255
11319
|
// a <window onDrop> is a whole-window dropzone; same edge as Node
|
|
11256
11320
|
if (hasDropProps(newProps) !== hasDropProps(before)) {
|
|
11257
11321
|
if (hasDropProps(newProps)) this._registerDropTarget(this);
|
|
@@ -11871,16 +11935,40 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
11871
11935
|
this._scheduleFrame();
|
|
11872
11936
|
}
|
|
11873
11937
|
|
|
11874
|
-
/**
|
|
11938
|
+
/**
|
|
11939
|
+
* A frame, on the window's clock — after whatever wait the pacer asks
|
|
11940
|
+
* for (src/pacing.js). Off by default, the pacer answers "now" for one
|
|
11941
|
+
* property read; under an adaptive policy a claim that finds the window
|
|
11942
|
+
* in debt for its last frames is held on a one-shot, and every claim
|
|
11943
|
+
* until then folds into it. Nothing here changes what the frame paints:
|
|
11944
|
+
* the damage accumulates on the node exactly as it does between two
|
|
11945
|
+
* ticks of the clock.
|
|
11946
|
+
*/
|
|
11875
11947
|
_scheduleFrame() {
|
|
11876
|
-
// Recorded before
|
|
11877
|
-
//
|
|
11878
|
-
//
|
|
11879
|
-
//
|
|
11880
|
-
//
|
|
11881
|
-
//
|
|
11948
|
+
// Recorded before either gate, not inside them: the debt is "this
|
|
11949
|
+
// window has damage", which a discrete event may pay off early (see
|
|
11950
|
+
// frames.js). Tying it to whether a callback is outstanding would hide
|
|
11951
|
+
// the second of two clicks a few milliseconds apart — the first one's
|
|
11952
|
+
// frame is still scheduled, so this returns here, and the early flush
|
|
11953
|
+
// would find nothing to paint. A held claim is a debt too: the early
|
|
11954
|
+
// flush paints it, and `flush` stands the wait down.
|
|
11882
11955
|
addPendingFrame(this);
|
|
11956
|
+
// A claim the frame raises on itself — an animation stepping, a
|
|
11957
|
+
// container query settling, a promotion moving a node — is answered
|
|
11958
|
+
// once the frame is over, so the pacer prices the frame that raised it
|
|
11959
|
+
// and not the one before.
|
|
11960
|
+
if (this._inFlush) {
|
|
11961
|
+
this._claimAfterFlush = true;
|
|
11962
|
+
return;
|
|
11963
|
+
}
|
|
11883
11964
|
if (this._scheduled) return;
|
|
11965
|
+
if (this._pacer.defer(() => this._requestFrame())) return;
|
|
11966
|
+
this._requestFrame();
|
|
11967
|
+
}
|
|
11968
|
+
|
|
11969
|
+
/** The callback on the window's clock. */
|
|
11970
|
+
_requestFrame() {
|
|
11971
|
+
if (this._scheduled || this.destroyed || !this.window) return;
|
|
11884
11972
|
this._scheduled = true;
|
|
11885
11973
|
const schedule =
|
|
11886
11974
|
typeof this.window.requestAnimationFrame === 'function'
|
|
@@ -11892,12 +11980,68 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
11892
11980
|
});
|
|
11893
11981
|
}
|
|
11894
11982
|
|
|
11983
|
+
/**
|
|
11984
|
+
* The policy this window paces its frames by: the environment, then the
|
|
11985
|
+
* `frameRate` prop, then the root's `createRoot({ frameRate })`, then
|
|
11986
|
+
* `'display'` (src/pacing.js). A bad value throws here — at mount or at
|
|
11987
|
+
* the prop change — naming the value and the choices.
|
|
11988
|
+
*/
|
|
11989
|
+
_syncFramePolicy() {
|
|
11990
|
+
const policy = resolveFramePolicy(
|
|
11991
|
+
this.props.frameRate,
|
|
11992
|
+
this.app,
|
|
11993
|
+
`<${this.kind} frameRate>`,
|
|
11994
|
+
);
|
|
11995
|
+
this._framePolicy = policy;
|
|
11996
|
+
this._pacer.configure(policy);
|
|
11997
|
+
}
|
|
11998
|
+
|
|
11999
|
+
/**
|
|
12000
|
+
* The backend's half of a frame's cost, where it has one: the Cocoa
|
|
12001
|
+
* present — the swapchain flip and its catch-up copy — runs after the
|
|
12002
|
+
* flush returns, on the same thread, and is part of what the frame cost
|
|
12003
|
+
* (src/cocoa/window.js reports it). An ntk window's present is one
|
|
12004
|
+
* request, and reports nothing.
|
|
12005
|
+
*/
|
|
12006
|
+
_notePresentCost(ms) {
|
|
12007
|
+
this._pacer.charge(ms);
|
|
12008
|
+
}
|
|
12009
|
+
|
|
12010
|
+
/**
|
|
12011
|
+
* A frame: layout if owed, then the paint passes — or the presenter's
|
|
12012
|
+
* frame — then the backend's word. Runs on the window's clock through
|
|
12013
|
+
* `_scheduleFrame`, and early, synchronously, for a discrete input
|
|
12014
|
+
* (frames.js). Every route lands here, so this is where a frame is
|
|
12015
|
+
* priced: the pacer brackets the work, and what it cost is what the
|
|
12016
|
+
* next claim is judged against (src/pacing.js).
|
|
12017
|
+
*/
|
|
11895
12018
|
flush() {
|
|
11896
12019
|
// Whatever this frame turns out to owe, it is this call's to pay — and
|
|
11897
12020
|
// a window that returns below because it is destroyed or unrealized
|
|
11898
12021
|
// owes nothing at all.
|
|
11899
12022
|
clearPendingFrame(this);
|
|
12023
|
+
// …and a wait the pacer had armed for it has nothing left to wait for:
|
|
12024
|
+
// whichever route got here first pays the same debt.
|
|
12025
|
+
this._pacer.cancel();
|
|
11900
12026
|
if (this.destroyed || !this.yoga || !this.window) return;
|
|
12027
|
+
const pacer = this._pacer;
|
|
12028
|
+
pacer.began();
|
|
12029
|
+
this._inFlush = true;
|
|
12030
|
+
let painted = false;
|
|
12031
|
+
try {
|
|
12032
|
+
painted = this._flushFrame();
|
|
12033
|
+
} finally {
|
|
12034
|
+
this._inFlush = false;
|
|
12035
|
+
pacer.ended(undefined, painted);
|
|
12036
|
+
if (this._claimAfterFlush) {
|
|
12037
|
+
this._claimAfterFlush = false;
|
|
12038
|
+
if (!this.destroyed) this._scheduleFrame();
|
|
12039
|
+
}
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
|
|
12043
|
+
/** The frame itself. True when it painted or presented something. */
|
|
12044
|
+
_flushFrame() {
|
|
11901
12045
|
// A frame is scheduled a tick before it is painted, and the connection
|
|
11902
12046
|
// can go in between: an app closing its own client, a server exit, a
|
|
11903
12047
|
// test closing the app it lent the root. Nothing unmounts the tree on
|
|
@@ -11905,7 +12049,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
11905
12049
|
// socket, and the first request it makes throws out of the frame clock
|
|
11906
12050
|
// where there is nothing waiting to catch it. There is no screen left to
|
|
11907
12051
|
// paint to, so this owes nothing either.
|
|
11908
|
-
if (this.app?.X?._closing) return;
|
|
12052
|
+
if (this.app?.X?._closing) return false;
|
|
11909
12053
|
// a transientFor whose owner was not realized yet at commit time. The
|
|
11910
12054
|
// frame after the mount is the first moment refs have attached, so the
|
|
11911
12055
|
// common "two <window>s in one tree" case resolves here rather than
|
|
@@ -12016,7 +12160,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12016
12160
|
// …and the next commit's claims name the arrangement this frame leaves
|
|
12017
12161
|
// behind again, from before whatever scroll comes with them
|
|
12018
12162
|
this._laidOut = false;
|
|
12019
|
-
if (!this.needsPaint) return;
|
|
12163
|
+
if (!this.needsPaint) return false;
|
|
12020
12164
|
this.needsPaint = false;
|
|
12021
12165
|
const damage = this._takeDamage(width, height);
|
|
12022
12166
|
if (debugPaint === 'full' && !damage && width > 0 && height > 0) {
|
|
@@ -12040,9 +12184,9 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12040
12184
|
if (typeof this.window.presentFrame === 'function') {
|
|
12041
12185
|
this.window.presentFrame(this, damage);
|
|
12042
12186
|
this.app._reactX11Startup?.painted();
|
|
12043
|
-
return;
|
|
12187
|
+
return true;
|
|
12044
12188
|
}
|
|
12045
|
-
if (typeof this.window.getContext !== 'function') return; // headless mock
|
|
12189
|
+
if (typeof this.window.getContext !== 'function') return false; // headless mock
|
|
12046
12190
|
// ntk getContext creates a fresh context (with window-event
|
|
12047
12191
|
// subscriptions) on every call — cache one per window
|
|
12048
12192
|
const ctx = (this._ctx ??= this.window.getContext('2d'));
|
|
@@ -12082,6 +12226,8 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12082
12226
|
// server work separate cleanly in a trace only when both are in it —
|
|
12083
12227
|
// a slow virtualized GPU shows up here, not in `end`.
|
|
12084
12228
|
landed: this.window.frameLatency,
|
|
12229
|
+
// how long the pacer held this frame's claim, ms; 0 when it did not
|
|
12230
|
+
waited: this._pacer.pendingWait,
|
|
12085
12231
|
});
|
|
12086
12232
|
}
|
|
12087
12233
|
// A frame that actually painted, which is the moment the app is up
|
|
@@ -12089,6 +12235,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12089
12235
|
// session clears itself off the app — which is the same bargain the
|
|
12090
12236
|
// trace hook above makes with the frame loop.
|
|
12091
12237
|
this.app._reactX11Startup?.painted();
|
|
12238
|
+
return true;
|
|
12092
12239
|
}
|
|
12093
12240
|
|
|
12094
12241
|
/**
|
|
@@ -12564,8 +12711,50 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12564
12711
|
return check(this);
|
|
12565
12712
|
}
|
|
12566
12713
|
|
|
12714
|
+
/**
|
|
12715
|
+
* The node whose `opaqueRect()` holds the whole of `rect`, or null: the
|
|
12716
|
+
* pass needs no fill under that node. Whole pixels only — a fractional
|
|
12717
|
+
* edge is antialiased, and an antialiased pixel is not opaque. A clipping
|
|
12718
|
+
* ancestor shrinks the answer to what reaches the surface, a rounded one
|
|
12719
|
+
* by its radius all round, since the corner squares are exactly the
|
|
12720
|
+
* pixels a rounded clip gives up. A handful of nodes answer at all, so
|
|
12721
|
+
* this is a few rect tests per pass.
|
|
12722
|
+
*/
|
|
12723
|
+
_coverFor(rect) {
|
|
12724
|
+
const nodes = this._opaqueNodes;
|
|
12725
|
+
if (nodes.size === 0) return null;
|
|
12726
|
+
for (const node of nodes) {
|
|
12727
|
+
if (node.destroyed || node.hidden || node._promoted) continue;
|
|
12728
|
+
if (node.style?.display === 'none' || !(node.abs?.width > 0)) continue;
|
|
12729
|
+
let cover = node.opaqueRect();
|
|
12730
|
+
if (!cover) continue;
|
|
12731
|
+
cover = innerPixels(cover);
|
|
12732
|
+
for (let n = node.parent; cover && n && n !== this; n = n.parent) {
|
|
12733
|
+
if (n.hidden || n.style?.display === 'none') {
|
|
12734
|
+
cover = null;
|
|
12735
|
+
break;
|
|
12736
|
+
}
|
|
12737
|
+
if (n.clipsChildren()) {
|
|
12738
|
+
const radius = n.style?.borderRadius ?? 0;
|
|
12739
|
+
cover = intersectRects(
|
|
12740
|
+
cover,
|
|
12741
|
+
radius > 0 ? insetRect(n.abs, radius) : n.abs,
|
|
12742
|
+
);
|
|
12743
|
+
}
|
|
12744
|
+
}
|
|
12745
|
+
if (cover && rectContains(cover, rect)) return node;
|
|
12746
|
+
}
|
|
12747
|
+
return null;
|
|
12748
|
+
}
|
|
12749
|
+
|
|
12567
12750
|
/** Repaint one damage rect, or the whole window when `damage` is null. */
|
|
12568
12751
|
_paintRegion(ctx, damage, width, height) {
|
|
12752
|
+
// An element that covers the pass with opaque pixels (`Node.opaqueRect`)
|
|
12753
|
+
// makes every fill under it wasted work — the clear, the window's
|
|
12754
|
+
// background, the node's own and its ancestors'. The first two are
|
|
12755
|
+
// skipped here; `_paintBackground` skips the chain's while
|
|
12756
|
+
// `_coverChain` names it.
|
|
12757
|
+
const cover = this._coverFor(damage ?? { x: 0, y: 0, width, height });
|
|
12569
12758
|
// A transparent window erases where an opaque one paints over. Its
|
|
12570
12759
|
// backing store holds premultiplied ARGB, and compositing a translucent
|
|
12571
12760
|
// background onto the previous frame would compound towards opaque
|
|
@@ -12578,7 +12767,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12578
12767
|
// `transparencyEffective`, not `_transparent`: an ARGB window with
|
|
12579
12768
|
// nothing compositing it must not clear, because the server would show
|
|
12580
12769
|
// those zeroed pixels as black rather than as the desktop.
|
|
12581
|
-
if (this.transparencyEffective) {
|
|
12770
|
+
if (this.transparencyEffective && !cover) {
|
|
12582
12771
|
if (damage) {
|
|
12583
12772
|
ctx.clearRect(damage.x, damage.y, damage.width, damage.height);
|
|
12584
12773
|
} else {
|
|
@@ -12600,7 +12789,12 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12600
12789
|
ctx.rect(damage.x, damage.y, damage.width, damage.height);
|
|
12601
12790
|
ctx.clip();
|
|
12602
12791
|
}
|
|
12603
|
-
this._paintWindowBackground(ctx, damage, width, height);
|
|
12792
|
+
if (!cover) this._paintWindowBackground(ctx, damage, width, height);
|
|
12793
|
+
if (cover) {
|
|
12794
|
+
const chain = new Set();
|
|
12795
|
+
for (let n = cover; n; n = n.parent) chain.add(n);
|
|
12796
|
+
this._coverChain = chain;
|
|
12797
|
+
}
|
|
12604
12798
|
this._paintDamage = damage;
|
|
12605
12799
|
try {
|
|
12606
12800
|
this._paintChildren(ctx);
|
|
@@ -12644,6 +12838,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
12644
12838
|
}
|
|
12645
12839
|
} finally {
|
|
12646
12840
|
this._paintDamage = null;
|
|
12841
|
+
this._coverChain = null;
|
|
12647
12842
|
if (damage) ctx.restore();
|
|
12648
12843
|
}
|
|
12649
12844
|
}
|