detached 81824f0

fix(workspace): handle stacked panes correctly in popup insertion

- Add stacked pane detection logic to insertPopupPane
- Split around entire stack container instead of individual panes
- Fix widget reparenting to prevent WebView corruption
- Add webkit.IsPaned() checks to avoid GTK assertion failures
commit
81824f0e896f778fef853c0231550a0dba300cef
author
bamen · · 2025-10-05 17:15 UTC
committer
bamen · · 2025-10-05 21:09 UTC
parent
00b5779
signature
unsigned
5 files · +113 -48
M gui/package-lock.json +15 -0 generated file: collapsed
M internal/app/browser/workspace_pane_ops.go +70 -40
@@ -340,12 +340,39 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
340340 webkit.WidgetRealizeInContainer(webViewWidget)
341341 }
342342
343- if target.container == 0 {
344- return errors.New("existing pane missing container")
343+ // CRITICAL: Handle stacked panes correctly - same logic as splitNode
344+ // When the target is inside a stacked pane, we need to split around the entire stack,
345+ // not the individual pane within the stack
346+ originalTarget := target
347+ var stackContainer *paneNode
348+ if target.parent != nil && target.parent.isStacked {
349+ // Target is in a stack - we'll create the split around the entire stack
350+ stackContainer = target.parent
351+ log.Printf("[workspace] target is in stack, will split around the stack: originalTarget=%p stackContainer=%p", originalTarget, stackContainer)
352+ }
353+
354+ // Determine what we're actually splitting
355+ var splitTarget *paneNode
356+ var splitTargetContainer uintptr
357+
358+ if stackContainer != nil {
359+ // Case: splitting from inside a stack - we split around the entire stack
360+ splitTarget = stackContainer
361+ splitTargetContainer = stackContainer.container
362+ log.Printf("[workspace] splitting around stack: stackContainer=%p", splitTarget)
363+ } else {
364+ // Case: normal split from a simple pane or direct stack target
365+ splitTarget = target
366+ splitTargetContainer = target.container
367+ log.Printf("[workspace] normal split from pane: target=%p", splitTarget)
368+ }
369+
370+ if splitTargetContainer == 0 {
371+ return errors.New("split target missing container")
345372 }
346373
347374 orientation, existingFirst := mapDirection(direction)
348- log.Printf("[workspace] inserting popup direction=%s orientation=%v existingFirst=%v target.parent=%p", direction, orientation, existingFirst, target.parent)
375+ log.Printf("[workspace] inserting popup direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p", direction, orientation, existingFirst, splitTarget.parent)
349376
350377 paned := webkit.NewPaned(orientation)
351378 if paned == 0 {
@@ -365,7 +392,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
365392 wm.initializePaneWidgets(newLeaf, newContainer)
366393
367394 split := &paneNode{
368- parent: target.parent,
395+ parent: splitTarget.parent,
369396 orientation: orientation,
370397 isLeaf: false,
371398 }
@@ -374,45 +401,47 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
374401 parent := split.parent
375402
376403 // CRITICAL: Capture parent paned's divider position BEFORE reparenting to preserve layout
404+ // Only attempt this if the parent container is actually a paned widget
377405 var parentDividerPos int
378- if parent != nil && parent.container != 0 && webkit.WidgetIsValid(parent.container) {
406+ if parent != nil && parent.container != 0 && webkit.WidgetIsValid(parent.container) && webkit.IsPaned(parent.container) {
379407 parentDividerPos = webkit.PanedGetPosition(parent.container)
380408 log.Printf("[workspace] captured parent divider position: %d", parentDividerPos)
409+ } else if parent != nil && parent.container != 0 {
410+ log.Printf("[workspace] parent container is not a paned widget (likely a stacked container)")
381411 }
382412
383413 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
384- existingContainer := target.container
385- if existingContainer != 0 && webkit.WidgetIsValid(existingContainer) {
414+ if splitTargetContainer != 0 && webkit.WidgetIsValid(splitTargetContainer) {
386415 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
387- if webkit.WidgetHasCSSClass(existingContainer, activePaneClass) {
388- webkit.WidgetRemoveCSSClass(existingContainer, activePaneClass)
416+ if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
417+ webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
389418 }
390419
391420 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline
392421 // This forces WebKit to detach its compositor, preventing rendering corruption
393- webkit.WidgetHide(existingContainer)
394- log.Printf("[workspace] Hidden existing container before reparenting: %#x", existingContainer)
422+ webkit.WidgetHide(splitTargetContainer)
423+ log.Printf("[workspace] Hidden split target container before reparenting: %#x", splitTargetContainer)
395424 }
396425
397426 // Prevent GTK from auto-removing controllers while the widget is temporarily unparented.
398- wm.safelyDetachControllersBeforeReparent(target)
427+ wm.safelyDetachControllersBeforeReparent(splitTarget)
399428
400429 // Detach existing container from its current GTK parent before inserting into new paned.
401430 if parent == nil {
402- // Target is the root - remove it from the window
403- log.Printf("[workspace] removing existing container=%#x from window", existingContainer)
431+ // Split target is the root - remove it from the window
432+ log.Printf("[workspace] removing split target container=%#x from window", splitTargetContainer)
404433 if wm.window != nil {
405434 wm.window.SetChild(0)
406435 }
407436 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
408437 } else if parent.container != 0 {
409- // Target has a parent paned - unparent it from there
410- log.Printf("[workspace] unparenting existing container from parent paned")
438+ // Split target has a parent paned - unparent it from there
439+ log.Printf("[workspace] unparenting split target container from parent paned")
411440 if webkit.WidgetIsValid(parent.container) {
412441 log.Printf("[workspace] executing unparent: parentPtr=%#x", parent.container)
413- if parent.left == target {
442+ if parent.left == splitTarget {
414443 webkit.PanedSetStartChild(parent.container, 0)
415- } else if parent.right == target {
444+ } else if parent.right == splitTarget {
416445 webkit.PanedSetEndChild(parent.container, 0)
417446 }
418447 webkit.WidgetQueueAllocate(parent.container)
@@ -421,20 +450,20 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
421450
422451 // Add both containers to the new paned
423452 if existingFirst {
424- split.left = target
453+ split.left = splitTarget
425454 split.right = newLeaf
426- webkit.PanedSetStartChild(paned, existingContainer)
455+ webkit.PanedSetStartChild(paned, splitTargetContainer)
427456 webkit.PanedSetEndChild(paned, newContainer)
428- log.Printf("[workspace] added existing=%#x as start child, new=%#x as end child", existingContainer, newContainer)
457+ log.Printf("[workspace] added splitTarget=%#x as start child, new=%#x as end child", splitTargetContainer, newContainer)
429458 } else {
430459 split.left = newLeaf
431- split.right = target
460+ split.right = splitTarget
432461 webkit.PanedSetStartChild(paned, newContainer)
433- webkit.PanedSetEndChild(paned, existingContainer)
434- log.Printf("[workspace] added new=%#x as start child, existing=%#x as end child", newContainer, existingContainer)
462+ webkit.PanedSetEndChild(paned, splitTargetContainer)
463+ log.Printf("[workspace] added new=%#x as start child, splitTarget=%#x as end child", newContainer, splitTargetContainer)
435464 }
436465
437- target.parent = split
466+ splitTarget.parent = split
438467 newLeaf.parent = split
439468
440469 if parent == nil {
@@ -447,10 +476,10 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
447476 } else {
448477 if webkit.WidgetIsValid(parent.container) {
449478 log.Printf("[workspace] executing parent reparent: parentPtr=%#x, paned=%#x", parent.container, paned)
450- if parent.left == target {
479+ if parent.left == splitTarget {
451480 parent.left = split
452481 webkit.PanedSetStartChild(parent.container, paned)
453- } else if parent.right == target {
482+ } else if parent.right == splitTarget {
454483 parent.right = split
455484 webkit.PanedSetEndChild(parent.container, paned)
456485 }
@@ -459,7 +488,8 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
459488 log.Printf("[workspace] paned inserted into parent successfully")
460489
461490 // CRITICAL: Restore parent paned's divider position to preserve existing layout
462- if parentDividerPos > 0 {
491+ // Only attempt this if we captured a position and the parent is actually a paned widget
492+ if parentDividerPos > 0 && webkit.IsPaned(parent.container) {
463493 webkit.PanedSetPosition(parent.container, parentDividerPos)
464494 log.Printf("[workspace] restored parent divider position: %d", parentDividerPos)
465495 }
@@ -502,26 +532,26 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
502532 return false
503533 }, split)
504534
505- // CRITICAL: Show the existing container and force GTK to recreate rendering surface after reparenting
535+ // CRITICAL: Show the split target container and force GTK to recreate rendering surface after reparenting
506536 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues
507- if target != nil && target.container != 0 {
537+ if splitTarget != nil && splitTarget.container != 0 {
508538 wm.scheduleIdleGuarded(func() bool {
509- if target == nil || !target.widgetValid || target.container == 0 {
539+ if splitTarget == nil || !splitTarget.widgetValid || splitTarget.container == 0 {
510540 return false
511541 }
512542 // Show widget to reconnect WebKit rendering pipeline
513- webkit.WidgetShow(target.container)
543+ webkit.WidgetShow(splitTarget.container)
514544 // Force GTK to recalculate size and recreate rendering surface
515- webkit.WidgetQueueResize(target.container)
516- webkit.WidgetQueueDraw(target.container)
517- log.Printf("[workspace] Shown and queued resize+draw for target container after reparenting")
545+ webkit.WidgetQueueResize(splitTarget.container)
546+ webkit.WidgetQueueDraw(splitTarget.container)
547+ log.Printf("[workspace] Shown and queued resize+draw for split target container after reparenting")
518548 return false
519- }, target)
549+ }, splitTarget)
520550 }
521551
522552 wm.viewToNode[newPane.webView] = newLeaf
523553 wm.ensureHover(newLeaf)
524- wm.ensureHover(target)
554+ wm.ensureHover(splitTarget)
525555 wm.app.panes = append(wm.app.panes, newPane)
526556 if newPane.zoomController != nil {
527557 newPane.zoomController.ApplyInitialZoom()
@@ -529,9 +559,9 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
529559
530560 // Update CSS classes for all panes now that we have multiple panes
531561
532- reattachTargets := []*paneNode{target}
533- if target != nil && target.isStacked {
534- reattachTargets = append(reattachTargets, target.stackedPanes...)
562+ reattachTargets := []*paneNode{splitTarget}
563+ if splitTarget != nil && splitTarget.isStacked {
564+ reattachTargets = append(reattachTargets, splitTarget.stackedPanes...)
535565 }
536566 guardNodes := append([]*paneNode{newLeaf}, reattachTargets...)
537567 wm.scheduleIdleGuarded(func() bool {
@@ -340,12 +340,39 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
340 webkit.WidgetRealizeInContainer(webViewWidget)340 webkit.WidgetRealizeInContainer(webViewWidget)
341 }341 }
342 342
343- if target.container == 0 {343+ // CRITICAL: Handle stacked panes correctly - same logic as splitNode
344- return errors.New("existing pane missing container")344+ // When the target is inside a stacked pane, we need to split around the entire stack,
345+ // not the individual pane within the stack
346+ originalTarget := target
347+ var stackContainer *paneNode
348+ if target.parent != nil && target.parent.isStacked {
349+ // Target is in a stack - we'll create the split around the entire stack
350+ stackContainer = target.parent
351+ log.Printf("[workspace] target is in stack, will split around the stack: originalTarget=%p stackContainer=%p", originalTarget, stackContainer)
352+ }
353+
354+ // Determine what we're actually splitting
355+ var splitTarget *paneNode
356+ var splitTargetContainer uintptr
357+
358+ if stackContainer != nil {
359+ // Case: splitting from inside a stack - we split around the entire stack
360+ splitTarget = stackContainer
361+ splitTargetContainer = stackContainer.container
362+ log.Printf("[workspace] splitting around stack: stackContainer=%p", splitTarget)
363+ } else {
364+ // Case: normal split from a simple pane or direct stack target
365+ splitTarget = target
366+ splitTargetContainer = target.container
367+ log.Printf("[workspace] normal split from pane: target=%p", splitTarget)
368+ }
369+
370+ if splitTargetContainer == 0 {
371+ return errors.New("split target missing container")
345 }372 }
346 373
347 orientation, existingFirst := mapDirection(direction)374 orientation, existingFirst := mapDirection(direction)
348- log.Printf("[workspace] inserting popup direction=%s orientation=%v existingFirst=%v target.parent=%p", direction, orientation, existingFirst, target.parent)375+ log.Printf("[workspace] inserting popup direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p", direction, orientation, existingFirst, splitTarget.parent)
349 376
350 paned := webkit.NewPaned(orientation)377 paned := webkit.NewPaned(orientation)
351 if paned == 0 {378 if paned == 0 {
@@ -365,7 +392,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
365 wm.initializePaneWidgets(newLeaf, newContainer)392 wm.initializePaneWidgets(newLeaf, newContainer)
366 393
367 split := &paneNode{394 split := &paneNode{
368- parent: target.parent,395+ parent: splitTarget.parent,
369 orientation: orientation,396 orientation: orientation,
370 isLeaf: false,397 isLeaf: false,
371 }398 }
@@ -374,45 +401,47 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
374 parent := split.parent401 parent := split.parent
375 402
376 // CRITICAL: Capture parent paned's divider position BEFORE reparenting to preserve layout403 // CRITICAL: Capture parent paned's divider position BEFORE reparenting to preserve layout
404+ // Only attempt this if the parent container is actually a paned widget
377 var parentDividerPos int405 var parentDividerPos int
378- if parent != nil && parent.container != 0 && webkit.WidgetIsValid(parent.container) {406+ if parent != nil && parent.container != 0 && webkit.WidgetIsValid(parent.container) && webkit.IsPaned(parent.container) {
379 parentDividerPos = webkit.PanedGetPosition(parent.container)407 parentDividerPos = webkit.PanedGetPosition(parent.container)
380 log.Printf("[workspace] captured parent divider position: %d", parentDividerPos)408 log.Printf("[workspace] captured parent divider position: %d", parentDividerPos)
409+ } else if parent != nil && parent.container != 0 {
410+ log.Printf("[workspace] parent container is not a paned widget (likely a stacked container)")
381 }411 }
382 412
383 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption413 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
384- existingContainer := target.container414+ if splitTargetContainer != 0 && webkit.WidgetIsValid(splitTargetContainer) {
385- if existingContainer != 0 && webkit.WidgetIsValid(existingContainer) {
386 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)415 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
387- if webkit.WidgetHasCSSClass(existingContainer, activePaneClass) {416+ if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
388- webkit.WidgetRemoveCSSClass(existingContainer, activePaneClass)417+ webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
389 }418 }
390 419
391 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline420 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline
392 // This forces WebKit to detach its compositor, preventing rendering corruption421 // This forces WebKit to detach its compositor, preventing rendering corruption
393- webkit.WidgetHide(existingContainer)422+ webkit.WidgetHide(splitTargetContainer)
394- log.Printf("[workspace] Hidden existing container before reparenting: %#x", existingContainer)423+ log.Printf("[workspace] Hidden split target container before reparenting: %#x", splitTargetContainer)
395 }424 }
396 425
397 // Prevent GTK from auto-removing controllers while the widget is temporarily unparented.426 // Prevent GTK from auto-removing controllers while the widget is temporarily unparented.
398- wm.safelyDetachControllersBeforeReparent(target)427+ wm.safelyDetachControllersBeforeReparent(splitTarget)
399 428
400 // Detach existing container from its current GTK parent before inserting into new paned.429 // Detach existing container from its current GTK parent before inserting into new paned.
401 if parent == nil {430 if parent == nil {
402- // Target is the root - remove it from the window431+ // Split target is the root - remove it from the window
403- log.Printf("[workspace] removing existing container=%#x from window", existingContainer)432+ log.Printf("[workspace] removing split target container=%#x from window", splitTargetContainer)
404 if wm.window != nil {433 if wm.window != nil {
405 wm.window.SetChild(0)434 wm.window.SetChild(0)
406 }435 }
407 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed436 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
408 } else if parent.container != 0 {437 } else if parent.container != 0 {
409- // Target has a parent paned - unparent it from there438+ // Split target has a parent paned - unparent it from there
410- log.Printf("[workspace] unparenting existing container from parent paned")439+ log.Printf("[workspace] unparenting split target container from parent paned")
411 if webkit.WidgetIsValid(parent.container) {440 if webkit.WidgetIsValid(parent.container) {
412 log.Printf("[workspace] executing unparent: parentPtr=%#x", parent.container)441 log.Printf("[workspace] executing unparent: parentPtr=%#x", parent.container)
413- if parent.left == target {442+ if parent.left == splitTarget {
414 webkit.PanedSetStartChild(parent.container, 0)443 webkit.PanedSetStartChild(parent.container, 0)
415- } else if parent.right == target {444+ } else if parent.right == splitTarget {
416 webkit.PanedSetEndChild(parent.container, 0)445 webkit.PanedSetEndChild(parent.container, 0)
417 }446 }
418 webkit.WidgetQueueAllocate(parent.container)447 webkit.WidgetQueueAllocate(parent.container)
@@ -421,20 +450,20 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
421 450
422 // Add both containers to the new paned451 // Add both containers to the new paned
423 if existingFirst {452 if existingFirst {
424- split.left = target453+ split.left = splitTarget
425 split.right = newLeaf454 split.right = newLeaf
426- webkit.PanedSetStartChild(paned, existingContainer)455+ webkit.PanedSetStartChild(paned, splitTargetContainer)
427 webkit.PanedSetEndChild(paned, newContainer)456 webkit.PanedSetEndChild(paned, newContainer)
428- log.Printf("[workspace] added existing=%#x as start child, new=%#x as end child", existingContainer, newContainer)457+ log.Printf("[workspace] added splitTarget=%#x as start child, new=%#x as end child", splitTargetContainer, newContainer)
429 } else {458 } else {
430 split.left = newLeaf459 split.left = newLeaf
431- split.right = target460+ split.right = splitTarget
432 webkit.PanedSetStartChild(paned, newContainer)461 webkit.PanedSetStartChild(paned, newContainer)
433- webkit.PanedSetEndChild(paned, existingContainer)462+ webkit.PanedSetEndChild(paned, splitTargetContainer)
434- log.Printf("[workspace] added new=%#x as start child, existing=%#x as end child", newContainer, existingContainer)463+ log.Printf("[workspace] added new=%#x as start child, splitTarget=%#x as end child", newContainer, splitTargetContainer)
435 }464 }
436 465
437- target.parent = split466+ splitTarget.parent = split
438 newLeaf.parent = split467 newLeaf.parent = split
439 468
440 if parent == nil {469 if parent == nil {
@@ -447,10 +476,10 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
447 } else {476 } else {
448 if webkit.WidgetIsValid(parent.container) {477 if webkit.WidgetIsValid(parent.container) {
449 log.Printf("[workspace] executing parent reparent: parentPtr=%#x, paned=%#x", parent.container, paned)478 log.Printf("[workspace] executing parent reparent: parentPtr=%#x, paned=%#x", parent.container, paned)
450- if parent.left == target {479+ if parent.left == splitTarget {
451 parent.left = split480 parent.left = split
452 webkit.PanedSetStartChild(parent.container, paned)481 webkit.PanedSetStartChild(parent.container, paned)
453- } else if parent.right == target {482+ } else if parent.right == splitTarget {
454 parent.right = split483 parent.right = split
455 webkit.PanedSetEndChild(parent.container, paned)484 webkit.PanedSetEndChild(parent.container, paned)
456 }485 }
@@ -459,7 +488,8 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
459 log.Printf("[workspace] paned inserted into parent successfully")488 log.Printf("[workspace] paned inserted into parent successfully")
460 489
461 // CRITICAL: Restore parent paned's divider position to preserve existing layout490 // CRITICAL: Restore parent paned's divider position to preserve existing layout
462- if parentDividerPos > 0 {491+ // Only attempt this if we captured a position and the parent is actually a paned widget
492+ if parentDividerPos > 0 && webkit.IsPaned(parent.container) {
463 webkit.PanedSetPosition(parent.container, parentDividerPos)493 webkit.PanedSetPosition(parent.container, parentDividerPos)
464 log.Printf("[workspace] restored parent divider position: %d", parentDividerPos)494 log.Printf("[workspace] restored parent divider position: %d", parentDividerPos)
465 }495 }
@@ -502,26 +532,26 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
502 return false532 return false
503 }, split)533 }, split)
504 534
505- // CRITICAL: Show the existing container and force GTK to recreate rendering surface after reparenting535+ // CRITICAL: Show the split target container and force GTK to recreate rendering surface after reparenting
506 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues536 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues
507- if target != nil && target.container != 0 {537+ if splitTarget != nil && splitTarget.container != 0 {
508 wm.scheduleIdleGuarded(func() bool {538 wm.scheduleIdleGuarded(func() bool {
509- if target == nil || !target.widgetValid || target.container == 0 {539+ if splitTarget == nil || !splitTarget.widgetValid || splitTarget.container == 0 {
510 return false540 return false
511 }541 }
512 // Show widget to reconnect WebKit rendering pipeline542 // Show widget to reconnect WebKit rendering pipeline
513- webkit.WidgetShow(target.container)543+ webkit.WidgetShow(splitTarget.container)
514 // Force GTK to recalculate size and recreate rendering surface544 // Force GTK to recalculate size and recreate rendering surface
515- webkit.WidgetQueueResize(target.container)545+ webkit.WidgetQueueResize(splitTarget.container)
516- webkit.WidgetQueueDraw(target.container)546+ webkit.WidgetQueueDraw(splitTarget.container)
517- log.Printf("[workspace] Shown and queued resize+draw for target container after reparenting")547+ log.Printf("[workspace] Shown and queued resize+draw for split target container after reparenting")
518 return false548 return false
519- }, target)549+ }, splitTarget)
520 }550 }
521 551
522 wm.viewToNode[newPane.webView] = newLeaf552 wm.viewToNode[newPane.webView] = newLeaf
523 wm.ensureHover(newLeaf)553 wm.ensureHover(newLeaf)
524- wm.ensureHover(target)554+ wm.ensureHover(splitTarget)
525 wm.app.panes = append(wm.app.panes, newPane)555 wm.app.panes = append(wm.app.panes, newPane)
526 if newPane.zoomController != nil {556 if newPane.zoomController != nil {
527 newPane.zoomController.ApplyInitialZoom()557 newPane.zoomController.ApplyInitialZoom()
@@ -529,9 +559,9 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
529 559
530 // Update CSS classes for all panes now that we have multiple panes560 // Update CSS classes for all panes now that we have multiple panes
531 561
532- reattachTargets := []*paneNode{target}562+ reattachTargets := []*paneNode{splitTarget}
533- if target != nil && target.isStacked {563+ if splitTarget != nil && splitTarget.isStacked {
534- reattachTargets = append(reattachTargets, target.stackedPanes...)564+ reattachTargets = append(reattachTargets, splitTarget.stackedPanes...)
535 }565 }
536 guardNodes := append([]*paneNode{newLeaf}, reattachTargets...)566 guardNodes := append([]*paneNode{newLeaf}, reattachTargets...)
537 wm.scheduleIdleGuarded(func() bool {567 wm.scheduleIdleGuarded(func() bool {
M pkg/webkit/keyboard_cgo.go +1 -1
@@ -514,7 +514,7 @@ func goOnFaviconURIChanged(id C.ulong, pageURI *C.char, faviconURI *C.char) {
514514 pageURLStr := C.GoString(pageURI)
515515 faviconURLStr := C.GoString(faviconURI)
516516
517- log.Printf("[favicon] WebKit favicon URI changed for WebView ID %d - page: %s, favicon: %s", uid, pageURLStr, faviconURLStr)
517+ log.Printf("[favicon] WebKit favicon URI changed for WebView ID %d - favicon: %s", uid, faviconURLStr)
518518
519519 regMu.RLock()
520520 vw := viewByID[uid]
@@ -514,7 +514,7 @@ func goOnFaviconURIChanged(id C.ulong, pageURI *C.char, faviconURI *C.char) {
514 pageURLStr := C.GoString(pageURI)514 pageURLStr := C.GoString(pageURI)
515 faviconURLStr := C.GoString(faviconURI)515 faviconURLStr := C.GoString(faviconURI)
516 516
517- log.Printf("[favicon] WebKit favicon URI changed for WebView ID %d - page: %s, favicon: %s", uid, pageURLStr, faviconURLStr)517+ log.Printf("[favicon] WebKit favicon URI changed for WebView ID %d - favicon: %s", uid, faviconURLStr)
518 518
519 regMu.RLock()519 regMu.RLock()
520 vw := viewByID[uid]520 vw := viewByID[uid]
M pkg/webkit/webview_callbacks.h +26 -4
@@ -63,18 +63,40 @@ static inline void on_uri_scheme(WebKitURISchemeRequest* request, gpointer user_
6363 }
6464 }
6565
66+static inline char* mask_uri_for_log(const char* uri) {
67+ if (!uri) return g_strdup("NULL");
68+ const char* p = strstr(uri, "://");
69+ const char* start = p ? p + 3 : uri;
70+ const char* slash = strchr(start, '/');
71+ size_t host_len = slash ? (size_t)(slash - start) : strlen(start);
72+ const size_t max_len = 64;
73+ if (host_len <= max_len) {
74+ return g_strndup(start, host_len);
75+ } else {
76+ char* prefix = g_strndup(start, max_len);
77+ char* res = g_strdup_printf("%s...", prefix);
78+ g_free(prefix);
79+ return res;
80+ }
81+}
82+
6683 static inline void on_favicon_changed(WebKitFaviconDatabase* database, gchar* page_uri, gchar* favicon_uri, gpointer user_data) {
6784 (void)database;
68- printf("[favicon-c] Favicon changed - page: %s, favicon: %s, WebView ID: %lu\n",
69- page_uri ? page_uri : "NULL", favicon_uri ? favicon_uri : "NULL", (unsigned long)user_data);
7085
7186 if (!favicon_uri || !page_uri) {
7287 printf("[favicon-c] Invalid favicon or page URI for WebView ID %lu\n", (unsigned long)user_data);
7388 return;
7489 }
7590
76- // Pass the page URI and favicon URI to Go
77- // WebKit manages the actual favicon data - we just need to store the URI mapping
91+ // Log only a masked/shortened form of the URIs (no full URIs)
92+ char* masked_page = mask_uri_for_log(page_uri);
93+ char* masked_favicon = mask_uri_for_log(favicon_uri);
94+ printf("[favicon-c] Favicon changed - page: %s, favicon: %s, WebView ID: %lu\n",
95+ masked_page, masked_favicon, (unsigned long)user_data);
96+ g_free(masked_page);
97+ g_free(masked_favicon);
98+
99+ // Pass the full page URI and favicon URI to Go
78100 goOnFaviconURIChanged((unsigned long)user_data, page_uri, favicon_uri);
79101 }
80102
@@ -63,18 +63,40 @@ static inline void on_uri_scheme(WebKitURISchemeRequest* request, gpointer user_
63 }63 }
64 }64 }
65 65
66+static inline char* mask_uri_for_log(const char* uri) {
67+ if (!uri) return g_strdup("NULL");
68+ const char* p = strstr(uri, "://");
69+ const char* start = p ? p + 3 : uri;
70+ const char* slash = strchr(start, '/');
71+ size_t host_len = slash ? (size_t)(slash - start) : strlen(start);
72+ const size_t max_len = 64;
73+ if (host_len <= max_len) {
74+ return g_strndup(start, host_len);
75+ } else {
76+ char* prefix = g_strndup(start, max_len);
77+ char* res = g_strdup_printf("%s...", prefix);
78+ g_free(prefix);
79+ return res;
80+ }
81+}
82+
66 static inline void on_favicon_changed(WebKitFaviconDatabase* database, gchar* page_uri, gchar* favicon_uri, gpointer user_data) {83 static inline void on_favicon_changed(WebKitFaviconDatabase* database, gchar* page_uri, gchar* favicon_uri, gpointer user_data) {
67 (void)database;84 (void)database;
68- printf("[favicon-c] Favicon changed - page: %s, favicon: %s, WebView ID: %lu\n",
69- page_uri ? page_uri : "NULL", favicon_uri ? favicon_uri : "NULL", (unsigned long)user_data);
70 85
71 if (!favicon_uri || !page_uri) {86 if (!favicon_uri || !page_uri) {
72 printf("[favicon-c] Invalid favicon or page URI for WebView ID %lu\n", (unsigned long)user_data);87 printf("[favicon-c] Invalid favicon or page URI for WebView ID %lu\n", (unsigned long)user_data);
73 return;88 return;
74 }89 }
75 90
76- // Pass the page URI and favicon URI to Go91+ // Log only a masked/shortened form of the URIs (no full URIs)
77- // WebKit manages the actual favicon data - we just need to store the URI mapping92+ char* masked_page = mask_uri_for_log(page_uri);
93+ char* masked_favicon = mask_uri_for_log(favicon_uri);
94+ printf("[favicon-c] Favicon changed - page: %s, favicon: %s, WebView ID: %lu\n",
95+ masked_page, masked_favicon, (unsigned long)user_data);
96+ g_free(masked_page);
97+ g_free(masked_favicon);
98+
99+ // Pass the full page URI and favicon URI to Go
78 goOnFaviconURIChanged((unsigned long)user_data, page_uri, favicon_uri);100 goOnFaviconURIChanged((unsigned long)user_data, page_uri, favicon_uri);
79 }101 }
80 102
M pkg/webkit/webview_cgo.go +1 -3
@@ -1020,7 +1020,7 @@ type WebView struct {
10201020 windowTypeCallback func(WindowType, *WindowFeatures)
10211021 parentWebView *WebView
10221022 isRelated bool
1023- isActive bool // Track if this WebView is currently active/focused
1023+ isActive bool // Track if this WebView is currently active/focused
10241024 navigationPolicyHandler func(url string, isUserGesture bool) bool // Handler to decide if navigation should be allowed
10251025 }
10261026
@@ -1229,7 +1229,6 @@ func NewWebView(cfg *Config) (*WebView, error) {
12291229 C.enable_favicon_database(networkSession)
12301230
12311231 // Notify favicon changes to Go for caching
1232- log.Printf("[favicon] Connecting favicon signal for WebView ID %d", v.id)
12331232 C.connect_favicon_changed_with_id(viewWidget, C.ulong(v.id))
12341233 // Apply hardware acceleration and related settings based on cfg.Rendering
12351234 if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil {
@@ -1910,7 +1909,6 @@ func (w *WebView) dispatchFaviconChanged(data []byte) {
19101909 }
19111910
19121911 func (w *WebView) dispatchFaviconURIChanged(pageURI, faviconURI string) {
1913- log.Printf("[favicon] Page %s has favicon at %s", pageURI, faviconURI)
19141912 if w != nil && w.faviconURIHandler != nil {
19151913 w.faviconURIHandler(pageURI, faviconURI)
19161914 }
@@ -1020,7 +1020,7 @@ type WebView struct {
1020 windowTypeCallback func(WindowType, *WindowFeatures)1020 windowTypeCallback func(WindowType, *WindowFeatures)
1021 parentWebView *WebView1021 parentWebView *WebView
1022 isRelated bool1022 isRelated bool
1023- isActive bool // Track if this WebView is currently active/focused1023+ isActive bool // Track if this WebView is currently active/focused
1024 navigationPolicyHandler func(url string, isUserGesture bool) bool // Handler to decide if navigation should be allowed1024 navigationPolicyHandler func(url string, isUserGesture bool) bool // Handler to decide if navigation should be allowed
1025 }1025 }
1026 1026
@@ -1229,7 +1229,6 @@ func NewWebView(cfg *Config) (*WebView, error) {
1229 C.enable_favicon_database(networkSession)1229 C.enable_favicon_database(networkSession)
1230 1230
1231 // Notify favicon changes to Go for caching1231 // Notify favicon changes to Go for caching
1232- log.Printf("[favicon] Connecting favicon signal for WebView ID %d", v.id)
1233 C.connect_favicon_changed_with_id(viewWidget, C.ulong(v.id))1232 C.connect_favicon_changed_with_id(viewWidget, C.ulong(v.id))
1234 // Apply hardware acceleration and related settings based on cfg.Rendering1233 // Apply hardware acceleration and related settings based on cfg.Rendering
1235 if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil {1234 if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil {
@@ -1910,7 +1909,6 @@ func (w *WebView) dispatchFaviconChanged(data []byte) {
1910 }1909 }
1911 1910
1912 func (w *WebView) dispatchFaviconURIChanged(pageURI, faviconURI string) {1911 func (w *WebView) dispatchFaviconURIChanged(pageURI, faviconURI string) {
1913- log.Printf("[favicon] Page %s has favicon at %s", pageURI, faviconURI)
1914 if w != nil && w.faviconURIHandler != nil {1912 if w != nil && w.faviconURIHandler != nil {
1915 w.faviconURIHandler(pageURI, faviconURI)1913 w.faviconURIHandler(pageURI, faviconURI)
1916 }1914 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close