Skip to content

Commit 8716a05

Browse files
braceyourselfjmmaranan
authored andcommitted
fix: guard tree rendering against destroyed window actors
If a window gets destroyed mid-render (app crash, rapid close, async cleanup), nodes in the tree still reference the finalized GObject. Accessing anything on it segfaults gnome-shell (signal 11). Adds isNodeValid() that probes the actor with get_name() in a try/catch (GJS throws on finalized GObjects rather than segfaulting). Filters dead nodes out before layout, and guards the remaining property accesses that can race with window destruction.
1 parent 4f05319 commit 8716a05

1 file changed

Lines changed: 39 additions & 5 deletions

File tree

lib/extension/tree.js

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,9 @@ export class Node extends GObject.Object {
459459
style_class: "window-tabbed-tab",
460460
x_expand: true,
461461
});
462+
// Window tracker may not resolve an app for a dying window
462463
let app = this.app;
464+
if (!app) return;
463465
let labelText = this._getTitle();
464466
let metaWin = this.nodeValue;
465467
let titleButton = new St.Button({
@@ -537,6 +539,21 @@ export class Node extends GObject.Object {
537539
return null;
538540
}
539541

542+
// Check if the underlying window actor is still alive. GJS throws
543+
// on property access of finalized GObjects rather than segfaulting,
544+
// so a cheap get_name() call is enough to detect dead actors.
545+
isNodeValid() {
546+
if (!this.isWindow()) return true;
547+
try {
548+
let actor = this._actor;
549+
if (!actor) return false;
550+
actor.get_name();
551+
return true;
552+
} catch (e) {
553+
return false;
554+
}
555+
}
556+
540557
render() {
541558
// Always update the title for the tab
542559
if (this.tab !== null && this.tab !== undefined) {
@@ -1274,8 +1291,12 @@ export class Tree extends Node {
12741291
tiledChildren.forEach((w) => {
12751292
if (w.renderRect) {
12761293
if (w.renderRect.width > 0 && w.renderRect.height > 0) {
1294+
// Window may have been destroyed since processNode computed renderRect
12771295
let metaWin = w.nodeValue;
1278-
this.extWm.move(metaWin, w.renderRect);
1296+
try {
1297+
this.extWm.move(metaWin, w.renderRect);
1298+
} catch (e) {
1299+
}
12791300
} else {
12801301
Logger.debug(`ignoring apply for ${w.renderRect.width}x${w.renderRect.height}`);
12811302
}
@@ -1385,7 +1406,8 @@ export class Tree extends Node {
13851406
});
13861407
}
13871408

1388-
tiledChildren.forEach((child, index) => {
1409+
// Skip windows whose actors were destroyed mid-render
1410+
tiledChildren.filter((c) => c.isNodeValid()).forEach((child, index) => {
13891411
// A monitor can contain a window or container child
13901412
if (node.layout === LAYOUT_TYPES.HSPLIT || node.layout === LAYOUT_TYPES.VSPLIT) {
13911413
this.processSplit(node, child, params, index);
@@ -1527,10 +1549,17 @@ export class Tree extends Node {
15271549
if (node.childNodes.length > 1 || alwaysShowDecorationTab) {
15281550
nodeY = nodeRect.y + params.stackedHeight;
15291551
nodeHeight = nodeRect.height - params.stackedHeight;
1530-
if (node.decoration && child.isWindow()) {
1552+
if (node.decoration && child.isWindow() && child.isNodeValid()) {
15311553
let gap = this.extWm.calculateGaps(node);
15321554
let renderRect = this.processGap(node);
1533-
let borderWidth = child.actor.border.get_theme_node().get_border_width(St.Side.TOP);
1555+
// Border actor may be gone if the window was destroyed mid-render
1556+
let borderWidth = 0;
1557+
try {
1558+
if (child.actor?.border) {
1559+
borderWidth = child.actor.border.get_theme_node().get_border_width(St.Side.TOP);
1560+
}
1561+
} catch (e) {
1562+
}
15341563

15351564
// Make adjustments to the gaps
15361565
let adjust = 4 * Utils.dpi();
@@ -1552,7 +1581,12 @@ export class Tree extends Node {
15521581
} else {
15531582
decoration.hide();
15541583
}
1555-
if (!decoration.contains(child.tab)) decoration.add_child(child.tab);
1584+
if (child.tab && !decoration.contains(child.tab)) {
1585+
try {
1586+
decoration.add_child(child.tab);
1587+
} catch (e) {
1588+
}
1589+
}
15561590
}
15571591

15581592
child.render();

0 commit comments

Comments
 (0)