fix: preserve session restore across CEF relaunch
- commit
- f7b7a96bc163e2b0cf8b1076711b7c8ab89790b0
- author
- brice · · 2026-04-15 06:10 UTC
- parent
- 1fa5469
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +64 -45
M internal/infrastructure/cef/app.go +6 -8
| @@ -177,13 +177,10 @@ func parseBrowseRelaunchCommandLine(commandLine purecef.CommandLine) (string, bo | |||
| 177 | 177 | } | |
| 178 | 178 | ||
| 179 | 179 | args := parseRelaunchCommandLineArgs(commandLine.GetCommandLineString()) | |
| 180 | - | if len(args) < 2 || args[1] != "browse" { | |
| 180 | + | if len(args) < 3 || args[1] != "browse" || args[2] == "" { | |
| 181 | 181 | return "", false | |
| 182 | 182 | } | |
| 183 | - | if len(args) > 2 { | |
| 184 | - | return args[2], true | |
| 185 | - | } | |
| 186 | - | return "", true | |
| 183 | + | return args[2], true | |
| 187 | 184 | } | |
| 188 | 185 | ||
| 189 | 186 | func parseBrowseURLFromRelaunchCommandLine(commandLine purecef.CommandLine) string { | |
| @@ -192,13 +189,14 @@ func parseBrowseURLFromRelaunchCommandLine(commandLine purecef.CommandLine) stri | |||
| 192 | 189 | } | |
| 193 | 190 | ||
| 194 | 191 | func (h *dumberBPH) OnAlreadyRunningAppRelaunch(commandLine purecef.CommandLine, _ string) int32 { | |
| 195 | - | if h != nil && h.engine != nil { | |
| 196 | - | if browseURL, ok := parseBrowseRelaunchCommandLine(commandLine); ok && h.engine.alreadyRunningAppRelaunchHandler != nil { | |
| 192 | + | if browseURL, ok := parseBrowseRelaunchCommandLine(commandLine); ok { | |
| 193 | + | if h != nil && h.engine != nil && h.engine.alreadyRunningAppRelaunchHandler != nil { | |
| 197 | 194 | h.engine.alreadyRunningAppRelaunchHandler(browseURL) | |
| 198 | 195 | } | |
| 196 | + | return 1 | |
| 199 | 197 | } | |
| 200 | 198 | ||
| 201 | - | return 1 | |
| 199 | + | return 0 | |
| 202 | 200 | } | |
| 203 | 201 | func (h *dumberBPH) GetDefaultClient() purecef.Client { return nil } | |
| 204 | 202 | func (h *dumberBPH) GetDefaultRequestContextHandler() purecef.RequestContextHandler { return nil } | |
| @@ -177,13 +177,10 @@ func parseBrowseRelaunchCommandLine(commandLine purecef.CommandLine) (string, bo | |||||
| 177 | } | 177 | } | ||
| 178 | 178 | ||||
| 179 | args := parseRelaunchCommandLineArgs(commandLine.GetCommandLineString()) | 179 | args := parseRelaunchCommandLineArgs(commandLine.GetCommandLineString()) | ||
| 180 | - | if len(args) < 2 || args[1] != "browse" { | 180 | + | if len(args) < 3 || args[1] != "browse" || args[2] == "" { |
| 181 | return "", false | 181 | return "", false | ||
| 182 | } | 182 | } | ||
| 183 | - | if len(args) > 2 { | 183 | + | return args[2], true |
| 184 | - | return args[2], true | |||
| 185 | - | } | |||
| 186 | - | return "", true | |||
| 187 | } | 184 | } | ||
| 188 | 185 | ||||
| 189 | func parseBrowseURLFromRelaunchCommandLine(commandLine purecef.CommandLine) string { | 186 | func parseBrowseURLFromRelaunchCommandLine(commandLine purecef.CommandLine) string { | ||
| @@ -192,13 +189,14 @@ func parseBrowseURLFromRelaunchCommandLine(commandLine purecef.CommandLine) stri | |||||
| 192 | } | 189 | } | ||
| 193 | 190 | ||||
| 194 | func (h *dumberBPH) OnAlreadyRunningAppRelaunch(commandLine purecef.CommandLine, _ string) int32 { | 191 | func (h *dumberBPH) OnAlreadyRunningAppRelaunch(commandLine purecef.CommandLine, _ string) int32 { | ||
| 195 | - | if h != nil && h.engine != nil { | 192 | + | if browseURL, ok := parseBrowseRelaunchCommandLine(commandLine); ok { |
| 196 | - | if browseURL, ok := parseBrowseRelaunchCommandLine(commandLine); ok && h.engine.alreadyRunningAppRelaunchHandler != nil { | 193 | + | if h != nil && h.engine != nil && h.engine.alreadyRunningAppRelaunchHandler != nil { |
| 197 | h.engine.alreadyRunningAppRelaunchHandler(browseURL) | 194 | h.engine.alreadyRunningAppRelaunchHandler(browseURL) | ||
| 198 | } | 195 | } | ||
| 196 | + | return 1 | |||
| 199 | } | 197 | } | ||
| 200 | 198 | ||||
| 201 | - | return 1 | 199 | + | return 0 |
| 202 | } | 200 | } | ||
| 203 | func (h *dumberBPH) GetDefaultClient() purecef.Client { return nil } | 201 | func (h *dumberBPH) GetDefaultClient() purecef.Client { return nil } | ||
| 204 | func (h *dumberBPH) GetDefaultRequestContextHandler() purecef.RequestContextHandler { return nil } | 202 | func (h *dumberBPH) GetDefaultRequestContextHandler() purecef.RequestContextHandler { return nil } | ||
M internal/infrastructure/cef/app_test.go +7 -7
| @@ -102,7 +102,7 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_ForwardsBrowseURLAndReturns1(t *t | |||
| 102 | 102 | } | |
| 103 | 103 | } | |
| 104 | 104 | ||
| 105 | - | func TestDumberBPH_OnAlreadyRunningAppRelaunch_ForwardsEmptyBrowseURLAndReturns1(t *testing.T) { | |
| 105 | + | func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardEmptyBrowseURLAndReturns0(t *testing.T) { | |
| 106 | 106 | t.Parallel() | |
| 107 | 107 | ||
| 108 | 108 | gotURL := "sentinel" | |
| @@ -115,11 +115,11 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_ForwardsEmptyBrowseURLAndReturns1 | |||
| 115 | 115 | commandLineString: "dumber browse", | |
| 116 | 116 | }, "") | |
| 117 | 117 | ||
| 118 | - | if ret != 1 { | |
| 119 | - | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 1", ret) | |
| 118 | + | if ret != 0 { | |
| 119 | + | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 0", ret) | |
| 120 | 120 | } | |
| 121 | - | if gotURL != "" { | |
| 122 | - | t.Fatalf("forwarded browse url = %q, want empty string", gotURL) | |
| 121 | + | if gotURL != "sentinel" { | |
| 122 | + | t.Fatalf("forwarded browse url = %q, want %q", gotURL, "sentinel") | |
| 123 | 123 | } | |
| 124 | 124 | } | |
| 125 | 125 | ||
| @@ -136,8 +136,8 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardNonBrowse(t *testin | |||
| 136 | 136 | commandLineString: "dumber", | |
| 137 | 137 | }, "") | |
| 138 | 138 | ||
| 139 | - | if ret != 1 { | |
| 140 | - | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 1", ret) | |
| 139 | + | if ret != 0 { | |
| 140 | + | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 0", ret) | |
| 141 | 141 | } | |
| 142 | 142 | if called { | |
| 143 | 143 | t.Fatal("handler should not be called for non-browse relaunch") | |
| @@ -102,7 +102,7 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_ForwardsBrowseURLAndReturns1(t *t | |||||
| 102 | } | 102 | } | ||
| 103 | } | 103 | } | ||
| 104 | 104 | ||||
| 105 | - | func TestDumberBPH_OnAlreadyRunningAppRelaunch_ForwardsEmptyBrowseURLAndReturns1(t *testing.T) { | 105 | + | func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardEmptyBrowseURLAndReturns0(t *testing.T) { |
| 106 | t.Parallel() | 106 | t.Parallel() | ||
| 107 | 107 | ||||
| 108 | gotURL := "sentinel" | 108 | gotURL := "sentinel" | ||
| @@ -115,11 +115,11 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_ForwardsEmptyBrowseURLAndReturns1 | |||||
| 115 | commandLineString: "dumber browse", | 115 | commandLineString: "dumber browse", | ||
| 116 | }, "") | 116 | }, "") | ||
| 117 | 117 | ||||
| 118 | - | if ret != 1 { | 118 | + | if ret != 0 { |
| 119 | - | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 1", ret) | 119 | + | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 0", ret) |
| 120 | } | 120 | } | ||
| 121 | - | if gotURL != "" { | 121 | + | if gotURL != "sentinel" { |
| 122 | - | t.Fatalf("forwarded browse url = %q, want empty string", gotURL) | 122 | + | t.Fatalf("forwarded browse url = %q, want %q", gotURL, "sentinel") |
| 123 | } | 123 | } | ||
| 124 | } | 124 | } | ||
| 125 | 125 | ||||
| @@ -136,8 +136,8 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardNonBrowse(t *testin | |||||
| 136 | commandLineString: "dumber", | 136 | commandLineString: "dumber", | ||
| 137 | }, "") | 137 | }, "") | ||
| 138 | 138 | ||||
| 139 | - | if ret != 1 { | 139 | + | if ret != 0 { |
| 140 | - | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 1", ret) | 140 | + | t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 0", ret) |
| 141 | } | 141 | } | ||
| 142 | if called { | 142 | if called { | ||
| 143 | t.Fatal("handler should not be called for non-browse relaunch") | 143 | t.Fatal("handler should not be called for non-browse relaunch") | ||
M internal/ui/app_browser_launch_test.go +39 -30
| @@ -92,6 +92,25 @@ func setWindowTabBar(t *testing.T, mw *window.MainWindow, tabBar *component.TabB | |||
| 92 | 92 | reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().Set(reflect.ValueOf(tabBar)) | |
| 93 | 93 | } | |
| 94 | 94 | ||
| 95 | + | func newTestTabBarShell(t *testing.T, tabIDs ...entity.TabID) *component.TabBar { | |
| 96 | + | t.Helper() | |
| 97 | + | ||
| 98 | + | tabBar := &component.TabBar{} | |
| 99 | + | buttons := make(map[entity.TabID]*component.TabButton, len(tabIDs)) | |
| 100 | + | for _, tabID := range tabIDs { | |
| 101 | + | buttons[tabID] = &component.TabButton{} | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | rv := reflect.ValueOf(tabBar).Elem() | |
| 105 | + | fv := rv.FieldByName("buttons") | |
| 106 | + | if !fv.IsValid() { | |
| 107 | + | t.Fatalf("TabBar missing buttons field") | |
| 108 | + | } | |
| 109 | + | reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().Set(reflect.ValueOf(buttons)) | |
| 110 | + | ||
| 111 | + | return tabBar | |
| 112 | + | } | |
| 113 | + | ||
| 95 | 114 | func windowTabBarActiveID(t *testing.T, mw *window.MainWindow) entity.TabID { | |
| 96 | 115 | t.Helper() | |
| 97 | 116 | if mw == nil || mw.TabBar() == nil { | |
| @@ -110,10 +129,15 @@ func windowTitle(t *testing.T, mw *window.MainWindow) string { | |||
| 110 | 129 | ||
| 111 | 130 | func windowTabBarVisible(t *testing.T, mw *window.MainWindow) bool { | |
| 112 | 131 | t.Helper() | |
| 113 | - | if mw == nil || mw.TabBar() == nil || mw.TabBar().Box() == nil { | |
| 132 | + | if mw == nil || mw.TabBar() == nil { | |
| 114 | 133 | return false | |
| 115 | 134 | } | |
| 116 | - | return mw.TabBar().Box().GetVisible() | |
| 135 | + | rv := reflect.ValueOf(mw.TabBar()).Elem() | |
| 136 | + | fv := rv.FieldByName("visible") | |
| 137 | + | if !fv.IsValid() { | |
| 138 | + | t.Fatalf("TabBar missing visible field") | |
| 139 | + | } | |
| 140 | + | return *(*bool)(unsafe.Pointer(fv.UnsafeAddr())) | |
| 117 | 141 | } | |
| 118 | 142 | ||
| 119 | 143 | func newTestShellToaster(t *testing.T) (*component.Toaster, *layoutmocks.MockBoxWidget, *layoutmocks.MockLabelWidget) { | |
| @@ -282,25 +306,14 @@ func TestApp_OpenFreshWindowRollsBackOnTabCreationFailure(t *testing.T) { | |||
| 282 | 306 | } | |
| 283 | 307 | ||
| 284 | 308 | func TestApp_OpenFreshWindowTargetsNewWindowTabBar(t *testing.T) { | |
| 285 | - | EnsureAdwaitaInitialized() | |
| 286 | - | appID := AppID | |
| 287 | - | gtkApp := gtk.NewApplication(&appID, gio.GApplicationNonUniqueValue) | |
| 288 | - | if gtkApp == nil { | |
| 289 | - | t.Fatal("gtk application creation failed") | |
| 290 | - | } | |
| 291 | - | defer gtkApp.Unref() | |
| 292 | - | ||
| 293 | 309 | existingTabID := entity.TabID("existing-tab") | |
| 294 | - | oldWindow, err := window.New(context.Background(), gtkApp, "top") | |
| 295 | - | if err != nil { | |
| 296 | - | t.Fatalf("old window creation failed: %v", err) | |
| 297 | - | } | |
| 298 | - | newWindow, err := window.New(context.Background(), gtkApp, "top") | |
| 299 | - | if err != nil { | |
| 300 | - | t.Fatalf("new window creation failed: %v", err) | |
| 301 | - | } | |
| 310 | + | createdTabID := entity.TabID("id-1") | |
| 311 | + | oldWindow := &window.MainWindow{} | |
| 312 | + | newWindow := &window.MainWindow{} | |
| 313 | + | setWindowTabBar(t, oldWindow, newTestTabBarShell(t)) | |
| 314 | + | setWindowTabBar(t, newWindow, newTestTabBarShell(t, createdTabID)) | |
| 302 | 315 | tabs := entity.NewTabList() | |
| 303 | - | tabsUC := usecase.NewManageTabsUseCase(func() string { return "id-1" }) | |
| 316 | + | tabsUC := usecase.NewManageTabsUseCase(func() string { return string(createdTabID) }) | |
| 304 | 317 | ||
| 305 | 318 | app := &App{ | |
| 306 | 319 | tabs: tabs, | |
| @@ -327,17 +340,17 @@ func TestApp_OpenFreshWindowTargetsNewWindowTabBar(t *testing.T) { | |||
| 327 | 340 | t.Fatalf("OpenFreshWindow returned error: %v", err) | |
| 328 | 341 | } | |
| 329 | 342 | ||
| 330 | - | createdTabID := app.tabs.ActiveTabID | |
| 331 | - | if createdTabID == "" { | |
| 332 | - | t.Fatalf("created tab id = %q, want non-empty", createdTabID) | |
| 343 | + | gotCreatedTabID := app.tabs.ActiveTabID | |
| 344 | + | if gotCreatedTabID == "" { | |
| 345 | + | t.Fatalf("created tab id = %q, want non-empty", gotCreatedTabID) | |
| 333 | 346 | } | |
| 334 | - | if got := windowTabBarActiveID(t, newWindow); got != createdTabID { | |
| 335 | - | t.Fatalf("new window tab bar active tab = %q, want %q", got, createdTabID) | |
| 347 | + | if got := windowTabBarActiveID(t, newWindow); got != gotCreatedTabID { | |
| 348 | + | t.Fatalf("new window tab bar active tab = %q, want %q", got, gotCreatedTabID) | |
| 336 | 349 | } | |
| 337 | 350 | if got := windowTabBarActiveID(t, oldWindow); got != "" { | |
| 338 | 351 | t.Fatalf("old window tab bar active tab = %q, want empty", got) | |
| 339 | 352 | } | |
| 340 | - | if got := newWindow.TabBar().Box().GetVisible(); got { | |
| 353 | + | if got := windowTabBarVisible(t, newWindow); got { | |
| 341 | 354 | t.Fatalf("new window tab bar visible = %v, want false", got) | |
| 342 | 355 | } | |
| 343 | 356 | } | |
| @@ -725,11 +738,7 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityHonorsHideWhenSingleTabDisabled( | |||
| 725 | 738 | defer gtkApp.Unref() | |
| 726 | 739 | ||
| 727 | 740 | mainWindow := &window.MainWindow{} | |
| 728 | - | tabBar := component.NewTabBar() | |
| 729 | - | if tabBar == nil { | |
| 730 | - | t.Fatal("tab bar creation failed") | |
| 731 | - | } | |
| 732 | - | tabBar.AddTab(entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1")))) | |
| 741 | + | tabBar := newTestTabBarShell(t, entity.TabID("tab-1")) | |
| 733 | 742 | tabBar.SetVisible(false) | |
| 734 | 743 | setWindowTabBar(t, mainWindow, tabBar) | |
| 735 | 744 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | |
| @@ -92,6 +92,25 @@ func setWindowTabBar(t *testing.T, mw *window.MainWindow, tabBar *component.TabB | |||||
| 92 | reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().Set(reflect.ValueOf(tabBar)) | 92 | reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().Set(reflect.ValueOf(tabBar)) | ||
| 93 | } | 93 | } | ||
| 94 | 94 | ||||
| 95 | + | func newTestTabBarShell(t *testing.T, tabIDs ...entity.TabID) *component.TabBar { | |||
| 96 | + | t.Helper() | |||
| 97 | + | ||||
| 98 | + | tabBar := &component.TabBar{} | |||
| 99 | + | buttons := make(map[entity.TabID]*component.TabButton, len(tabIDs)) | |||
| 100 | + | for _, tabID := range tabIDs { | |||
| 101 | + | buttons[tabID] = &component.TabButton{} | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | rv := reflect.ValueOf(tabBar).Elem() | |||
| 105 | + | fv := rv.FieldByName("buttons") | |||
| 106 | + | if !fv.IsValid() { | |||
| 107 | + | t.Fatalf("TabBar missing buttons field") | |||
| 108 | + | } | |||
| 109 | + | reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().Set(reflect.ValueOf(buttons)) | |||
| 110 | + | ||||
| 111 | + | return tabBar | |||
| 112 | + | } | |||
| 113 | + | ||||
| 95 | func windowTabBarActiveID(t *testing.T, mw *window.MainWindow) entity.TabID { | 114 | func windowTabBarActiveID(t *testing.T, mw *window.MainWindow) entity.TabID { | ||
| 96 | t.Helper() | 115 | t.Helper() | ||
| 97 | if mw == nil || mw.TabBar() == nil { | 116 | if mw == nil || mw.TabBar() == nil { | ||
| @@ -110,10 +129,15 @@ func windowTitle(t *testing.T, mw *window.MainWindow) string { | |||||
| 110 | 129 | ||||
| 111 | func windowTabBarVisible(t *testing.T, mw *window.MainWindow) bool { | 130 | func windowTabBarVisible(t *testing.T, mw *window.MainWindow) bool { | ||
| 112 | t.Helper() | 131 | t.Helper() | ||
| 113 | - | if mw == nil || mw.TabBar() == nil || mw.TabBar().Box() == nil { | 132 | + | if mw == nil || mw.TabBar() == nil { |
| 114 | return false | 133 | return false | ||
| 115 | } | 134 | } | ||
| 116 | - | return mw.TabBar().Box().GetVisible() | 135 | + | rv := reflect.ValueOf(mw.TabBar()).Elem() |
| 136 | + | fv := rv.FieldByName("visible") | |||
| 137 | + | if !fv.IsValid() { | |||
| 138 | + | t.Fatalf("TabBar missing visible field") | |||
| 139 | + | } | |||
| 140 | + | return *(*bool)(unsafe.Pointer(fv.UnsafeAddr())) | |||
| 117 | } | 141 | } | ||
| 118 | 142 | ||||
| 119 | func newTestShellToaster(t *testing.T) (*component.Toaster, *layoutmocks.MockBoxWidget, *layoutmocks.MockLabelWidget) { | 143 | func newTestShellToaster(t *testing.T) (*component.Toaster, *layoutmocks.MockBoxWidget, *layoutmocks.MockLabelWidget) { | ||
| @@ -282,25 +306,14 @@ func TestApp_OpenFreshWindowRollsBackOnTabCreationFailure(t *testing.T) { | |||||
| 282 | } | 306 | } | ||
| 283 | 307 | ||||
| 284 | func TestApp_OpenFreshWindowTargetsNewWindowTabBar(t *testing.T) { | 308 | func TestApp_OpenFreshWindowTargetsNewWindowTabBar(t *testing.T) { | ||
| 285 | - | EnsureAdwaitaInitialized() | |||
| 286 | - | appID := AppID | |||
| 287 | - | gtkApp := gtk.NewApplication(&appID, gio.GApplicationNonUniqueValue) | |||
| 288 | - | if gtkApp == nil { | |||
| 289 | - | t.Fatal("gtk application creation failed") | |||
| 290 | - | } | |||
| 291 | - | defer gtkApp.Unref() | |||
| 292 | - | ||||
| 293 | existingTabID := entity.TabID("existing-tab") | 309 | existingTabID := entity.TabID("existing-tab") | ||
| 294 | - | oldWindow, err := window.New(context.Background(), gtkApp, "top") | 310 | + | createdTabID := entity.TabID("id-1") |
| 295 | - | if err != nil { | 311 | + | oldWindow := &window.MainWindow{} |
| 296 | - | t.Fatalf("old window creation failed: %v", err) | 312 | + | newWindow := &window.MainWindow{} |
| 297 | - | } | 313 | + | setWindowTabBar(t, oldWindow, newTestTabBarShell(t)) |
| 298 | - | newWindow, err := window.New(context.Background(), gtkApp, "top") | 314 | + | setWindowTabBar(t, newWindow, newTestTabBarShell(t, createdTabID)) |
| 299 | - | if err != nil { | |||
| 300 | - | t.Fatalf("new window creation failed: %v", err) | |||
| 301 | - | } | |||
| 302 | tabs := entity.NewTabList() | 315 | tabs := entity.NewTabList() | ||
| 303 | - | tabsUC := usecase.NewManageTabsUseCase(func() string { return "id-1" }) | 316 | + | tabsUC := usecase.NewManageTabsUseCase(func() string { return string(createdTabID) }) |
| 304 | 317 | ||||
| 305 | app := &App{ | 318 | app := &App{ | ||
| 306 | tabs: tabs, | 319 | tabs: tabs, | ||
| @@ -327,17 +340,17 @@ func TestApp_OpenFreshWindowTargetsNewWindowTabBar(t *testing.T) { | |||||
| 327 | t.Fatalf("OpenFreshWindow returned error: %v", err) | 340 | t.Fatalf("OpenFreshWindow returned error: %v", err) | ||
| 328 | } | 341 | } | ||
| 329 | 342 | ||||
| 330 | - | createdTabID := app.tabs.ActiveTabID | 343 | + | gotCreatedTabID := app.tabs.ActiveTabID |
| 331 | - | if createdTabID == "" { | 344 | + | if gotCreatedTabID == "" { |
| 332 | - | t.Fatalf("created tab id = %q, want non-empty", createdTabID) | 345 | + | t.Fatalf("created tab id = %q, want non-empty", gotCreatedTabID) |
| 333 | } | 346 | } | ||
| 334 | - | if got := windowTabBarActiveID(t, newWindow); got != createdTabID { | 347 | + | if got := windowTabBarActiveID(t, newWindow); got != gotCreatedTabID { |
| 335 | - | t.Fatalf("new window tab bar active tab = %q, want %q", got, createdTabID) | 348 | + | t.Fatalf("new window tab bar active tab = %q, want %q", got, gotCreatedTabID) |
| 336 | } | 349 | } | ||
| 337 | if got := windowTabBarActiveID(t, oldWindow); got != "" { | 350 | if got := windowTabBarActiveID(t, oldWindow); got != "" { | ||
| 338 | t.Fatalf("old window tab bar active tab = %q, want empty", got) | 351 | t.Fatalf("old window tab bar active tab = %q, want empty", got) | ||
| 339 | } | 352 | } | ||
| 340 | - | if got := newWindow.TabBar().Box().GetVisible(); got { | 353 | + | if got := windowTabBarVisible(t, newWindow); got { |
| 341 | t.Fatalf("new window tab bar visible = %v, want false", got) | 354 | t.Fatalf("new window tab bar visible = %v, want false", got) | ||
| 342 | } | 355 | } | ||
| 343 | } | 356 | } | ||
| @@ -725,11 +738,7 @@ func TestApp_UpdateBrowserWindowTabBarVisibilityHonorsHideWhenSingleTabDisabled( | |||||
| 725 | defer gtkApp.Unref() | 738 | defer gtkApp.Unref() | ||
| 726 | 739 | ||||
| 727 | mainWindow := &window.MainWindow{} | 740 | mainWindow := &window.MainWindow{} | ||
| 728 | - | tabBar := component.NewTabBar() | 741 | + | tabBar := newTestTabBarShell(t, entity.TabID("tab-1")) |
| 729 | - | if tabBar == nil { | |||
| 730 | - | t.Fatal("tab bar creation failed") | |||
| 731 | - | } | |||
| 732 | - | tabBar.AddTab(entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("workspace-1"), entity.NewPane(entity.PaneID("pane-1")))) | |||
| 733 | tabBar.SetVisible(false) | 742 | tabBar.SetVisible(false) | ||
| 734 | setWindowTabBar(t, mainWindow, tabBar) | 743 | setWindowTabBar(t, mainWindow, tabBar) | ||
| 735 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | 744 | bw := &browserWindow{id: "window-1", mainWindow: mainWindow} | ||
M internal/ui/component/tab_bar.go +3 -0
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | type TabBar struct { | |
| 12 | 12 | box *gtk.Box | |
| 13 | 13 | buttons map[entity.TabID]*TabButton | |
| 14 | + | visible bool | |
| 14 | 15 | ||
| 15 | 16 | // Active tab tracking | |
| 16 | 17 | activeTabID entity.TabID | |
| @@ -27,6 +28,7 @@ type TabBar struct { | |||
| 27 | 28 | func NewTabBar() *TabBar { | |
| 28 | 29 | tb := &TabBar{ | |
| 29 | 30 | buttons: make(map[entity.TabID]*TabButton), | |
| 31 | + | visible: true, | |
| 30 | 32 | } | |
| 31 | 33 | ||
| 32 | 34 | // Create horizontal box container | |
| @@ -159,6 +161,7 @@ func (tb *TabBar) ActiveTabID() entity.TabID { | |||
| 159 | 161 | ||
| 160 | 162 | // SetVisible shows or hides the tab bar. | |
| 161 | 163 | func (tb *TabBar) SetVisible(visible bool) { | |
| 164 | + | tb.visible = visible | |
| 162 | 165 | if tb.box == nil { | |
| 163 | 166 | return | |
| 164 | 167 | } | |
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | type TabBar struct { | 11 | type TabBar struct { | ||
| 12 | box *gtk.Box | 12 | box *gtk.Box | ||
| 13 | buttons map[entity.TabID]*TabButton | 13 | buttons map[entity.TabID]*TabButton | ||
| 14 | + | visible bool | |||
| 14 | 15 | ||||
| 15 | // Active tab tracking | 16 | // Active tab tracking | ||
| 16 | activeTabID entity.TabID | 17 | activeTabID entity.TabID | ||
| @@ -27,6 +28,7 @@ type TabBar struct { | |||||
| 27 | func NewTabBar() *TabBar { | 28 | func NewTabBar() *TabBar { | ||
| 28 | tb := &TabBar{ | 29 | tb := &TabBar{ | ||
| 29 | buttons: make(map[entity.TabID]*TabButton), | 30 | buttons: make(map[entity.TabID]*TabButton), | ||
| 31 | + | visible: true, | |||
| 30 | } | 32 | } | ||
| 31 | 33 | ||||
| 32 | // Create horizontal box container | 34 | // Create horizontal box container | ||
| @@ -159,6 +161,7 @@ func (tb *TabBar) ActiveTabID() entity.TabID { | |||||
| 159 | 161 | ||||
| 160 | // SetVisible shows or hides the tab bar. | 162 | // SetVisible shows or hides the tab bar. | ||
| 161 | func (tb *TabBar) SetVisible(visible bool) { | 163 | func (tb *TabBar) SetVisible(visible bool) { | ||
| 164 | + | tb.visible = visible | |||
| 162 | if tb.box == nil { | 165 | if tb.box == nil { | ||
| 163 | return | 166 | return | ||
| 164 | } | 167 | } | ||
M internal/ui/component/tab_button.go +3 -0
| @@ -85,6 +85,9 @@ func (tb *TabButton) SetActive(active bool) { | |||
| 85 | 85 | return | |
| 86 | 86 | } | |
| 87 | 87 | tb.isActive = active | |
| 88 | + | if tb.button == nil { | |
| 89 | + | return | |
| 90 | + | } | |
| 88 | 91 | ||
| 89 | 92 | if active { | |
| 90 | 93 | tb.button.AddCssClass("tab-button-active") | |
| @@ -85,6 +85,9 @@ func (tb *TabButton) SetActive(active bool) { | |||||
| 85 | return | 85 | return | ||
| 86 | } | 86 | } | ||
| 87 | tb.isActive = active | 87 | tb.isActive = active | ||
| 88 | + | if tb.button == nil { | |||
| 89 | + | return | |||
| 90 | + | } | |||
| 88 | 91 | ||||
| 89 | if active { | 92 | if active { | ||
| 90 | tb.button.AddCssClass("tab-button-active") | 93 | tb.button.AddCssClass("tab-button-active") | ||
M internal/ui/window/main_window.go +6 -0
| @@ -119,11 +119,17 @@ func (mw *MainWindow) assembleLayout() { | |||
| 119 | 119 | ||
| 120 | 120 | // Show presents the window. | |
| 121 | 121 | func (mw *MainWindow) Show() { | |
| 122 | + | if mw == nil || mw.window == nil { | |
| 123 | + | return | |
| 124 | + | } | |
| 122 | 125 | mw.window.Present() | |
| 123 | 126 | } | |
| 124 | 127 | ||
| 125 | 128 | // Close closes the window. | |
| 126 | 129 | func (mw *MainWindow) Close() { | |
| 130 | + | if mw == nil || mw.window == nil { | |
| 131 | + | return | |
| 132 | + | } | |
| 127 | 133 | mw.window.Close() | |
| 128 | 134 | } | |
| 129 | 135 | ||
| @@ -119,11 +119,17 @@ func (mw *MainWindow) assembleLayout() { | |||||
| 119 | 119 | ||||
| 120 | // Show presents the window. | 120 | // Show presents the window. | ||
| 121 | func (mw *MainWindow) Show() { | 121 | func (mw *MainWindow) Show() { | ||
| 122 | + | if mw == nil || mw.window == nil { | |||
| 123 | + | return | |||
| 124 | + | } | |||
| 122 | mw.window.Present() | 125 | mw.window.Present() | ||
| 123 | } | 126 | } | ||
| 124 | 127 | ||||
| 125 | // Close closes the window. | 128 | // Close closes the window. | ||
| 126 | func (mw *MainWindow) Close() { | 129 | func (mw *MainWindow) Close() { | ||
| 130 | + | if mw == nil || mw.window == nil { | |||
| 131 | + | return | |||
| 132 | + | } | |||
| 127 | mw.window.Close() | 133 | mw.window.Close() | ||
| 128 | } | 134 | } | ||
| 129 | 135 | ||||