@@ -52,6 +52,14 @@ export const ClipboardTabContent = GObject.registerClass(
5252 this . _imagePreviewSize = this . _settings . get_int ( 'clipboard-image-preview-size' ) ;
5353 this . _layoutMode = this . _settings . get_string ( 'clipboard-layout-mode' ) || 'list' ;
5454 this . _hasRenderedOnce = false ;
55+ this . _isDestroyed = false ;
56+ this . _deferredRedrawPending = false ;
57+ this . _redrawIdleId = 0 ;
58+ this . _redrawScheduled = false ;
59+ this . _retryId = 0 ;
60+ this . _settingSignalIds = [ ] ;
61+ this . _managerSignalIds = [ ] ;
62+ this . _actorSignalIds = [ ] ;
5563
5664 this . _copyService = new ClipboardCopyService ( ) ;
5765 this . _selectionService = new ClipboardSelectionService ( ) ;
@@ -73,6 +81,12 @@ export const ClipboardTabContent = GObject.registerClass(
7381
7482 this . _setupSettingsSignals ( ) ;
7583 this . _connectManagerSignals ( ) ;
84+ this . _actorSignalIds . push ( this . connect ( 'notify::mapped' , ( ) => this . _flushDeferredRedraw ( ) ) ) ;
85+ this . _actorSignalIds . push ( this . connect ( 'notify::visible' , ( ) => this . _flushDeferredRedraw ( ) ) ) ;
86+ this . connect ( 'destroy' , ( ) => {
87+ this . _isDestroyed = true ;
88+ this . _clearRedrawSources ( ) ;
89+ } ) ;
7690 }
7791
7892 // ========================================================================
@@ -293,16 +307,22 @@ export const ClipboardTabContent = GObject.registerClass(
293307 * @private
294308 */
295309 _scheduleRedraw ( immediate = false ) {
310+ if ( this . _isDestroyed ) return ;
311+
296312 if ( ! this . _canRenderNow ( ) ) {
297313 this . _deferredRedrawPending = true ;
298- this . _ensureRetry ( ) ;
314+ if ( this . _shouldRetryRender ( ) ) this . _ensureRetry ( ) ;
299315 return ;
300316 }
301317
302318 this . _deferredRedrawPending = false ;
303319
304320 if ( immediate ) {
305- if ( this . _redrawIdleId ) GLib . source_remove ( this . _redrawIdleId ) ;
321+ if ( this . _redrawIdleId ) {
322+ GLib . source_remove ( this . _redrawIdleId ) ;
323+ this . _redrawIdleId = 0 ;
324+ this . _redrawScheduled = false ;
325+ }
306326 this . _redraw ( ) ;
307327 return ;
308328 }
@@ -325,21 +345,36 @@ export const ClipboardTabContent = GObject.registerClass(
325345 * @private
326346 */
327347 _canRenderNow ( ) {
328- if ( ! this . _currentView || ! this . _scrollView || ! this . mapped || ! this . visible ) return false ;
348+ if ( ! this . _shouldRetryRender ( ) ) return false ;
329349 const box = this . _scrollView . get_allocation_box ( ) ;
330350 return box && box . get_width ( ) > 1 && box . get_height ( ) > 1 ;
331351 }
332352
353+ /**
354+ * Determine if a pending render should wait for allocation.
355+ *
356+ * @returns {boolean } True if the actor is alive and visible.
357+ * @private
358+ */
359+ _shouldRetryRender ( ) {
360+ return ! this . _isDestroyed && ! ! this . _currentView && ! ! this . _scrollView && this . mapped && this . visible ;
361+ }
362+
333363 /**
334364 * Ensure redraw is retried when rendering becomes possible again.
335365 *
336366 * @private
337367 */
338368 _ensureRetry ( ) {
339- if ( this . _retryId ) return ;
369+ if ( this . _isDestroyed || this . _retryId ) return ;
340370
341371 this . _retryId = GLib . timeout_add ( GLib . PRIORITY_DEFAULT , RETRY_INTERVAL_MS , ( ) => {
342- if ( ! this . _deferredRedrawPending || ! this . _currentView ) {
372+ if ( this . _isDestroyed || ! this . _deferredRedrawPending || ! this . _currentView ) {
373+ this . _retryId = 0 ;
374+ return GLib . SOURCE_REMOVE ;
375+ }
376+
377+ if ( ! this . _shouldRetryRender ( ) ) {
343378 this . _retryId = 0 ;
344379 return GLib . SOURCE_REMOVE ;
345380 }
@@ -354,15 +389,27 @@ export const ClipboardTabContent = GObject.registerClass(
354389 } ) ;
355390 }
356391
392+ /**
393+ * Render pending content when a cached tab becomes visible.
394+ *
395+ * @private
396+ */
397+ _flushDeferredRedraw ( ) {
398+ if ( this . _isDestroyed || ! this . _deferredRedrawPending || ! this . _shouldRetryRender ( ) ) return ;
399+ this . _scheduleRedraw ( true ) ;
400+ }
401+
357402 /**
358403 * Perform the actual redraw of the current view.
359404 *
360405 * @private
361406 */
362407 _redraw ( ) {
408+ if ( this . _isDestroyed ) return ;
409+
363410 if ( ! this . _canRenderNow ( ) ) {
364411 this . _deferredRedrawPending = true ;
365- this . _ensureRetry ( ) ;
412+ if ( this . _shouldRetryRender ( ) ) this . _ensureRetry ( ) ;
366413 return ;
367414 }
368415
@@ -391,6 +438,8 @@ export const ClipboardTabContent = GObject.registerClass(
391438 * Handle the event when the clipboard tab is selected.
392439 */
393440 onTabSelected ( ) {
441+ if ( this . _isDestroyed ) return ;
442+
394443 const needs = this . _deferredRedrawPending || this . _searchService . pendingReset || ! this . _hasRenderedOnce ;
395444
396445 this . _searchService . onTabSelected ( this . _searchComponent ) ;
@@ -427,19 +476,33 @@ export const ClipboardTabContent = GObject.registerClass(
427476 * Handle the event when the extension menu is closed.
428477 */
429478 onMenuClosed ( ) {
430- this . _deferredRedrawPending = false ;
479+ if ( this . _isDestroyed ) return ;
480+
431481 this . _searchService . onMenuClosed ( ) ;
482+ this . _clearRedrawSources ( ) ;
483+
484+ const currentFocus = global . stage . get_key_focus ( ) ;
485+ if ( currentFocus && ( currentFocus === this || this . contains ( currentFocus ) ) ) {
486+ global . stage . set_key_focus ( null ) ;
487+ }
488+ }
432489
490+ /**
491+ * Remove pending redraw sources.
492+ *
493+ * @private
494+ */
495+ _clearRedrawSources ( ) {
433496 if ( this . _redrawIdleId ) {
434497 GLib . source_remove ( this . _redrawIdleId ) ;
435498 this . _redrawIdleId = 0 ;
436- this . _redrawScheduled = false ;
437499 }
438-
439- const currentFocus = global . stage . get_key_focus ( ) ;
440- if ( currentFocus && ( currentFocus === this || this . contains ( currentFocus ) ) ) {
441- global . stage . set_key_focus ( null ) ;
500+ if ( this . _retryId ) {
501+ GLib . source_remove ( this . _retryId ) ;
502+ this . _retryId = 0 ;
442503 }
504+ this . _redrawScheduled = false ;
505+ this . _deferredRedrawPending = false ;
443506 }
444507
445508 // ========================================================================
@@ -450,8 +513,8 @@ export const ClipboardTabContent = GObject.registerClass(
450513 * Clean up resources and disconnect signals before destruction.
451514 */
452515 destroy ( ) {
453- if ( this . _redrawIdleId ) GLib . source_remove ( this . _redrawIdleId ) ;
454- if ( this . _retryId ) GLib . source_remove ( this . _retryId ) ;
516+ this . _isDestroyed = true ;
517+ this . _clearRedrawSources ( ) ;
455518
456519 this . _copyService ?. destroy ( ) ;
457520
@@ -460,6 +523,10 @@ export const ClipboardTabContent = GObject.registerClass(
460523
461524 this . _settingSignalIds . forEach ( ( id ) => this . _settings . disconnect ( id ) ) ;
462525 this . _managerSignalIds ?. forEach ( ( id ) => this . _manager . disconnect ( id ) ) ;
526+ this . _actorSignalIds . forEach ( ( id ) => this . disconnect ( id ) ) ;
527+ this . _settingSignalIds = [ ] ;
528+ this . _managerSignalIds = [ ] ;
529+ this . _actorSignalIds = [ ] ;
463530
464531 this . _searchComponent ?. destroy ( ) ;
465532 this . _actionBar ?. destroy ( ) ;
0 commit comments