@@ -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