@@ -459,7 +459,9 @@ export class Node extends GObject.Object {
459459style_class : "window-tabbed-tab" ,
460460x_expand : true ,
461461} ) ;
462+ // Window tracker may not resolve an app for a dying window
462463let app = this . app ;
464+ if ( ! app ) return ;
463465let labelText = this . _getTitle ( ) ;
464466let metaWin = this . nodeValue ;
465467let titleButton = new St . Button ( {
@@ -537,6 +539,21 @@ export class Node extends GObject.Object {
537539return 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+
540557render ( ) {
541558// Always update the title for the tab
542559if ( this . tab !== null && this . tab !== undefined ) {
@@ -1274,8 +1291,12 @@ export class Tree extends Node {
12741291tiledChildren . forEach ( ( w ) => {
12751292if ( w . renderRect ) {
12761293if ( w . renderRect . width > 0 && w . renderRect . height > 0 ) {
1294+ // Window may have been destroyed since processNode computed renderRect
12771295let 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 {
12801301Logger . 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
13901412if ( node . layout === LAYOUT_TYPES . HSPLIT || node . layout === LAYOUT_TYPES . VSPLIT ) {
13911413this . processSplit ( node , child , params , index ) ;
@@ -1527,10 +1549,17 @@ export class Tree extends Node {
15271549if ( node . childNodes . length > 1 || alwaysShowDecorationTab ) {
15281550nodeY = nodeRect . y + params . stackedHeight ;
15291551nodeHeight = nodeRect . height - params . stackedHeight ;
1530- if ( node . decoration && child . isWindow ( ) ) {
1552+ if ( node . decoration && child . isWindow ( ) && child . isNodeValid ( ) ) {
15311553let gap = this . extWm . calculateGaps ( node ) ;
15321554let 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
15361565let adjust = 4 * Utils . dpi ( ) ;
@@ -1552,7 +1581,12 @@ export class Tree extends Node {
15521581} else {
15531582decoration . 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
15581592child . render ( ) ;
0 commit comments