-
Notifications
You must be signed in to change notification settings - Fork 84
fix: guard tree rendering against destroyed window actors #507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -459,7 +459,9 @@ export class Node extends GObject.Object { | |||||||
| style_class: "window-tabbed-tab", | ||||||||
| x_expand: true, | ||||||||
| }); | ||||||||
| // Window tracker may not resolve an app for a dying window | ||||||||
| let app = this.app; | ||||||||
| if (!app) return; | ||||||||
| let labelText = this._getTitle(); | ||||||||
| let metaWin = this.nodeValue; | ||||||||
| let titleButton = new St.Button({ | ||||||||
|
|
@@ -537,6 +539,21 @@ export class Node extends GObject.Object { | |||||||
| return null; | ||||||||
| } | ||||||||
|
|
||||||||
| // Check if the underlying window actor is still alive. GJS throws | ||||||||
| // on property access of finalized GObjects rather than segfaulting, | ||||||||
| // so a cheap get_name() call is enough to detect dead actors. | ||||||||
| isNodeValid() { | ||||||||
| if (!this.isWindow()) return true; | ||||||||
| try { | ||||||||
| let actor = this._actor; | ||||||||
| if (!actor) return false; | ||||||||
| actor.get_name(); | ||||||||
| return true; | ||||||||
| } catch (e) { | ||||||||
| return false; | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| render() { | ||||||||
| // Always update the title for the tab | ||||||||
| if (this.tab !== null && this.tab !== undefined) { | ||||||||
|
|
@@ -1274,8 +1291,12 @@ export class Tree extends Node { | |||||||
| tiledChildren.forEach((w) => { | ||||||||
| if (w.renderRect) { | ||||||||
| if (w.renderRect.width > 0 && w.renderRect.height > 0) { | ||||||||
| // Window may have been destroyed since processNode computed renderRect | ||||||||
| let metaWin = w.nodeValue; | ||||||||
| this.extWm.move(metaWin, w.renderRect); | ||||||||
| try { | ||||||||
| this.extWm.move(metaWin, w.renderRect); | ||||||||
| } catch (e) { | ||||||||
| } | ||||||||
| } else { | ||||||||
| Logger.debug(`ignoring apply for ${w.renderRect.width}x${w.renderRect.height}`); | ||||||||
| } | ||||||||
|
|
@@ -1385,7 +1406,8 @@ export class Tree extends Node { | |||||||
| }); | ||||||||
| } | ||||||||
|
|
||||||||
| tiledChildren.forEach((child, index) => { | ||||||||
| // Skip windows whose actors were destroyed mid-render | ||||||||
| tiledChildren.filter((c) => c.isNodeValid()).forEach((child, index) => { | ||||||||
| // A monitor can contain a window or container child | ||||||||
| if (node.layout === LAYOUT_TYPES.HSPLIT || node.layout === LAYOUT_TYPES.VSPLIT) { | ||||||||
|
Comment on lines
+1409
to
1412
|
||||||||
| this.processSplit(node, child, params, index); | ||||||||
|
|
@@ -1527,10 +1549,17 @@ export class Tree extends Node { | |||||||
| if (node.childNodes.length > 1 || alwaysShowDecorationTab) { | ||||||||
| nodeY = nodeRect.y + params.stackedHeight; | ||||||||
| nodeHeight = nodeRect.height - params.stackedHeight; | ||||||||
| if (node.decoration && child.isWindow()) { | ||||||||
| if (node.decoration && child.isWindow() && child.isNodeValid()) { | ||||||||
| let gap = this.extWm.calculateGaps(node); | ||||||||
| let renderRect = this.processGap(node); | ||||||||
| let borderWidth = child.actor.border.get_theme_node().get_border_width(St.Side.TOP); | ||||||||
| // Border actor may be gone if the window was destroyed mid-render | ||||||||
| let borderWidth = 0; | ||||||||
| try { | ||||||||
| if (child.actor?.border) { | ||||||||
| borderWidth = child.actor.border.get_theme_node().get_border_width(St.Side.TOP); | ||||||||
| } | ||||||||
| } catch (e) { | ||||||||
| } | ||||||||
|
Comment on lines
+1555
to
+1562
|
||||||||
|
|
||||||||
| // Make adjustments to the gaps | ||||||||
| let adjust = 4 * Utils.dpi(); | ||||||||
|
|
@@ -1552,7 +1581,12 @@ export class Tree extends Node { | |||||||
| } else { | ||||||||
| decoration.hide(); | ||||||||
| } | ||||||||
| if (!decoration.contains(child.tab)) decoration.add_child(child.tab); | ||||||||
| if (child.tab && !decoration.contains(child.tab)) { | ||||||||
| try { | ||||||||
| decoration.add_child(child.tab); | ||||||||
| } catch (e) { | ||||||||
|
||||||||
| } catch (e) { | |
| } catch (e) { | |
| Logger.debug(`Failed to add tab actor to decoration: ${e}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apply()now swallows all exceptions fromthis.extWm.move()with an emptycatch. This makes unrelated failures (bad rects, API changes, etc.) silent and hard to diagnose. If the intention is to ignore “window destroyed mid-render” errors, consider (a) filtering invalid windows before callingmoveand/or (b) logging at least a debug/warn message (possibly rate-limited) inside the catch so unexpected errors are visible.