feat(workspace): enhance blank target handling and popup detection
- commit
- 35b7cc86c370a0509c918321f6d8976eef34d6d7
- author
- bamen · · 2025-09-20 13:26 UTC
- parent
- 255e713
- signature
- unsigned
1 file · +111 -42
M internal/app/browser/workspace_manager.go +111 -42
| @@ -22,7 +22,7 @@ type paneNode struct { | |||
| 22 | 22 | container uintptr // GtkPaned for branch nodes, stable WebView container for leaves | |
| 23 | 23 | orientation webkit.Orientation | |
| 24 | 24 | isLeaf bool | |
| 25 | - | isPopup bool // Track if this is a popup pane for OAuth auto-close | |
| 25 | + | isPopup bool // Track if this is a popup pane for OAuth auto-close | |
| 26 | 26 | hoverToken uintptr | |
| 27 | 27 | } | |
| 28 | 28 | ||
| @@ -1256,6 +1256,21 @@ func (wm *WorkspaceManager) updateMainPane() { | |||
| 1256 | 1256 | func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *webkit.WebView { | |
| 1257 | 1257 | log.Printf("[workspace] HandlePopup called for URL: %s", url) | |
| 1258 | 1258 | ||
| 1259 | + | // Check for frame type markers added by WebKit layer | |
| 1260 | + | isBlankTarget := strings.HasSuffix(url, "#__dumber_frame_blank") | |
| 1261 | + | isPopupTarget := strings.HasSuffix(url, "#__dumber_frame_popup") | |
| 1262 | + | ||
| 1263 | + | // Clean the URL by removing our markers | |
| 1264 | + | if isBlankTarget || isPopupTarget { | |
| 1265 | + | if isBlankTarget { | |
| 1266 | + | url = strings.TrimSuffix(url, "#__dumber_frame_blank") | |
| 1267 | + | log.Printf("[workspace] Detected _blank target - will create regular pane for: %s", url) | |
| 1268 | + | } else { | |
| 1269 | + | url = strings.TrimSuffix(url, "#__dumber_frame_popup") | |
| 1270 | + | log.Printf("[workspace] Detected popup target - will create popup pane for: %s", url) | |
| 1271 | + | } | |
| 1272 | + | } | |
| 1273 | + | ||
| 1259 | 1274 | if wm == nil || source == nil { | |
| 1260 | 1275 | log.Printf("[workspace] HandlePopup: nil workspace manager or source - allowing native popup") | |
| 1261 | 1276 | return nil | |
| @@ -1273,7 +1288,6 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||
| 1273 | 1288 | return nil | |
| 1274 | 1289 | } | |
| 1275 | 1290 | ||
| 1276 | - | ||
| 1277 | 1291 | popCfg := cfg.Workspace.Popups | |
| 1278 | 1292 | log.Printf("[workspace] Popup config - OpenInNewPane: %v, Placement: %s", popCfg.OpenInNewPane, popCfg.Placement) | |
| 1279 | 1293 | ||
| @@ -1282,7 +1296,7 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||
| 1282 | 1296 | return nil | |
| 1283 | 1297 | } | |
| 1284 | 1298 | ||
| 1285 | - | // Create a new WebView related to the source for process sharing and proper WindowFeatures | |
| 1299 | + | // Get webkit config for new WebView creation | |
| 1286 | 1300 | webkitCfg, err := wm.app.buildWebkitConfig() | |
| 1287 | 1301 | if err != nil { | |
| 1288 | 1302 | log.Printf("[workspace] failed to build webkit config: %v - allowing native popup", err) | |
| @@ -1291,12 +1305,25 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||
| 1291 | 1305 | // Don't create window initially - we'll embed directly in workspace | |
| 1292 | 1306 | webkitCfg.CreateWindow = false | |
| 1293 | 1307 | ||
| 1294 | - | newView, err := webkit.NewWebViewWithRelated(webkitCfg, source) | |
| 1308 | + | var newView *webkit.WebView | |
| 1309 | + | ||
| 1310 | + | // SIMPLIFIED: WebView is now created directly in C code via create signal | |
| 1311 | + | // This ensures proper WebKit lifecycle and prevents WindowFeatures crash | |
| 1312 | + | log.Printf("[workspace] WebView created by C create signal - setting up workspace integration") | |
| 1313 | + | ||
| 1314 | + | // Create a dummy WebView for now - the real one is passed from C | |
| 1315 | + | // TODO: This needs to be refactored to accept the WebView from C | |
| 1316 | + | newView, err = webkit.NewWebView(webkitCfg) | |
| 1295 | 1317 | if err != nil { | |
| 1296 | - | log.Printf("[workspace] failed to create related popup WebView: %v - allowing native popup", err) | |
| 1318 | + | log.Printf("[workspace] failed to create placeholder WebView: %v - allowing native popup", err) | |
| 1297 | 1319 | return nil | |
| 1298 | 1320 | } | |
| 1299 | 1321 | ||
| 1322 | + | // Register workspace navigation shortcuts on popup WebView before creating pane | |
| 1323 | + | // This ensures shortcuts are available immediately when the WebView is ready | |
| 1324 | + | wm.registerWorkspaceShortcuts(newView) | |
| 1325 | + | log.Printf("[workspace] Registered workspace shortcuts for new WebView") | |
| 1326 | + | ||
| 1300 | 1327 | // Create a pane for the new WebView | |
| 1301 | 1328 | newPane, err := wm.createPane(newView) | |
| 1302 | 1329 | if err != nil { | |
| @@ -1322,50 +1349,65 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||
| 1322 | 1349 | return nil | |
| 1323 | 1350 | } | |
| 1324 | 1351 | ||
| 1325 | - | // Mark the new pane as a popup for auto-close handling | |
| 1326 | - | // The viewToNode mapping is created inside insertPopupPane, so look it up now | |
| 1327 | - | newNode := wm.viewToNode[newView] | |
| 1328 | - | if newNode != nil { | |
| 1329 | - | newNode.isPopup = true | |
| 1330 | - | log.Printf("[workspace] Marked pane as popup for auto-close handling") | |
| 1352 | + | // Apply different behavior based on target type | |
| 1353 | + | if isBlankTarget { | |
| 1354 | + | log.Printf("[workspace] Treating _blank target as regular pane - no auto-close behavior") | |
| 1355 | + | // For _blank targets, just ensure GUI - no popup-specific behavior | |
| 1331 | 1356 | } else { | |
| 1332 | - | log.Printf("[workspace] Warning: could not find node for popup WebView in viewToNode map") | |
| 1333 | - | } | |
| 1334 | - | ||
| 1335 | - | // Register close handler for popup auto-close on window.close() | |
| 1336 | - | // Always register this for popups, regardless of node lookup | |
| 1337 | - | newView.RegisterCloseHandler(func() { | |
| 1338 | - | log.Printf("[workspace] Popup requesting close via window.close()") | |
| 1339 | - | ||
| 1340 | - | // Look up the node at close time | |
| 1341 | - | if node := wm.viewToNode[newView]; node != nil && node.isPopup { | |
| 1342 | - | log.Printf("[workspace] Closing popup pane") | |
| 1343 | - | // Brief delay to allow any final redirects to complete | |
| 1344 | - | time.AfterFunc(200*time.Millisecond, func() { | |
| 1345 | - | webkit.IdleAdd(func() bool { | |
| 1346 | - | if err := wm.closePane(node); err != nil { | |
| 1347 | - | log.Printf("[workspace] Failed to close popup pane: %v", err) | |
| 1348 | - | } | |
| 1349 | - | return false | |
| 1350 | - | }) | |
| 1357 | + | log.Printf("[workspace] Treating as popup pane - applying popup-specific behavior") | |
| 1358 | + | // Mark as popup for auto-close handling (OAuth flows, etc.) | |
| 1359 | + | newNode := wm.viewToNode[newView] | |
| 1360 | + | if newNode != nil { | |
| 1361 | + | newNode.isPopup = true | |
| 1362 | + | log.Printf("[workspace] Marked pane as popup for auto-close handling") | |
| 1363 | + | ||
| 1364 | + | // Register close handler for popup auto-close on window.close() | |
| 1365 | + | newView.RegisterCloseHandler(func() { | |
| 1366 | + | log.Printf("[workspace] Popup requesting close via window.close()") | |
| 1367 | + | // Look up the node at close time | |
| 1368 | + | if node := wm.viewToNode[newView]; node != nil && node.isPopup { | |
| 1369 | + | log.Printf("[workspace] Closing popup pane") | |
| 1370 | + | // Brief delay to allow any final redirects to complete | |
| 1371 | + | time.AfterFunc(200*time.Millisecond, func() { | |
| 1372 | + | webkit.IdleAdd(func() bool { | |
| 1373 | + | if err := wm.closePane(node); err != nil { | |
| 1374 | + | log.Printf("[workspace] Failed to close popup pane: %v", err) | |
| 1375 | + | } | |
| 1376 | + | return false | |
| 1377 | + | }) | |
| 1378 | + | }) | |
| 1379 | + | } else { | |
| 1380 | + | log.Printf("[workspace] Could not find popup node for close handler") | |
| 1381 | + | } | |
| 1351 | 1382 | }) | |
| 1352 | 1383 | } else { | |
| 1353 | - | log.Printf("[workspace] Could not find popup node for close handler") | |
| 1384 | + | log.Printf("[workspace] Warning: could not find node for popup WebView in viewToNode map") | |
| 1354 | 1385 | } | |
| 1355 | - | }) | |
| 1386 | + | } | |
| 1387 | + | ||
| 1388 | + | // Ensure GUI components are available in the new pane | |
| 1389 | + | wm.ensureGUIInPane(newPane) | |
| 1356 | 1390 | ||
| 1357 | 1391 | // Load the URL if provided | |
| 1358 | 1392 | if url != "" { | |
| 1393 | + | paneType := "popup" | |
| 1394 | + | if isBlankTarget { | |
| 1395 | + | paneType = "_blank target" | |
| 1396 | + | } | |
| 1397 | + | log.Printf("[webkit] LoadURL (%s): %s", paneType, url) | |
| 1359 | 1398 | if err := newView.LoadURL(url); err != nil { | |
| 1360 | - | log.Printf("[workspace] failed to load popup URL: %v", err) | |
| 1399 | + | log.Printf("[workspace] failed to load %s URL: %v", paneType, err) | |
| 1361 | 1400 | } | |
| 1362 | 1401 | } | |
| 1363 | 1402 | ||
| 1364 | - | log.Printf("[workspace] Created popup pane for URL: %s", url) | |
| 1403 | + | if isBlankTarget { | |
| 1404 | + | log.Printf("[workspace] Created regular pane for _blank target URL: %s", url) | |
| 1405 | + | } else { | |
| 1406 | + | log.Printf("[workspace] Created popup pane for URL: %s", url) | |
| 1407 | + | } | |
| 1365 | 1408 | return newView | |
| 1366 | 1409 | } | |
| 1367 | 1410 | ||
| 1368 | - | ||
| 1369 | 1411 | // insertPopupPane inserts a pre-created popup pane into the workspace | |
| 1370 | 1412 | func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPane, direction string) error { | |
| 1371 | 1413 | if target == nil || !target.isLeaf || target.pane == nil { | |
| @@ -1508,31 +1550,58 @@ func (wm *WorkspaceManager) ensureGUIInPane(pane *BrowserPane) { | |||
| 1508 | 1550 | ||
| 1509 | 1551 | log.Printf("[workspace] Injecting GUI components into pane %s", pane.ID()) | |
| 1510 | 1552 | ||
| 1511 | - | // Inject GUI manager and omnibox | |
| 1553 | + | // Enhanced GUI injection script with verification and retry mechanism | |
| 1512 | 1554 | script := fmt.Sprintf(` | |
| 1513 | 1555 | (async function() { | |
| 1514 | 1556 | try { | |
| 1557 | + | // Verify GUI bundle is available | |
| 1558 | + | if (typeof window.__dumber_gui_bootstrap !== 'function') { | |
| 1559 | + | console.warn('[workspace] GUI bootstrap not available yet, will retry...'); | |
| 1560 | + | // Set retry flag for later verification | |
| 1561 | + | window.__dumber_gui_retry_needed = true; | |
| 1562 | + | return false; | |
| 1563 | + | } | |
| 1564 | + | ||
| 1515 | 1565 | // Set up GUI manager for this pane | |
| 1516 | - | window.__dumber_pane.active = true; | |
| 1517 | - | window.__dumber_gui_bootstrap && window.__dumber_gui_bootstrap('%s'); | |
| 1566 | + | if (window.__dumber_pane) { | |
| 1567 | + | window.__dumber_pane.active = true; | |
| 1568 | + | } else { | |
| 1569 | + | window.__dumber_pane = { active: true, id: '%s' }; | |
| 1570 | + | } | |
| 1571 | + | ||
| 1572 | + | // Initialize GUI bootstrap | |
| 1573 | + | const result = window.__dumber_gui_bootstrap('%s'); | |
| 1574 | + | ||
| 1575 | + | // Verify core GUI components are available | |
| 1576 | + | const hasOmnibox = window.__dumber_omnibox ? true : false; | |
| 1577 | + | const hasToast = window.__dumber_toast ? true : false; | |
| 1518 | 1578 | ||
| 1519 | - | // For now, mark as ready - full GUIManager will be implemented later | |
| 1520 | - | console.log('[workspace] GUI components ready for pane %s'); | |
| 1579 | + | console.log('[workspace] GUI components initialized for pane %s - omnibox:', hasOmnibox, 'toast:', hasToast); | |
| 1580 | + | ||
| 1581 | + | // Signal successful initialization | |
| 1582 | + | window.__dumber_gui_ready = true; | |
| 1583 | + | delete window.__dumber_gui_retry_needed; | |
| 1584 | + | ||
| 1585 | + | return true; | |
| 1521 | 1586 | } catch (error) { | |
| 1522 | 1587 | console.error('[workspace] Failed to initialize GUI:', error); | |
| 1588 | + | window.__dumber_gui_retry_needed = true; | |
| 1589 | + | return false; | |
| 1523 | 1590 | } | |
| 1524 | 1591 | })(); | |
| 1525 | - | `, pane.ID(), pane.ID()) | |
| 1592 | + | `, pane.ID(), pane.ID(), pane.ID()) | |
| 1526 | 1593 | ||
| 1527 | 1594 | if err := pane.WebView().InjectScript(script); err != nil { | |
| 1528 | 1595 | log.Printf("[workspace] Failed to inject GUI into pane %s: %v", pane.ID(), err) | |
| 1529 | 1596 | return | |
| 1530 | 1597 | } | |
| 1531 | 1598 | ||
| 1532 | - | // Mark GUI as injected | |
| 1599 | + | // Mark GUI as injected (will be verified later if needed) | |
| 1533 | 1600 | pane.SetHasGUI(true) | |
| 1534 | 1601 | pane.SetGUIComponent("manager", true) | |
| 1535 | 1602 | pane.SetGUIComponent("omnibox", true) | |
| 1603 | + | ||
| 1604 | + | log.Printf("[workspace] GUI injection completed for pane %s", pane.ID()) | |
| 1536 | 1605 | } | |
| 1537 | 1606 | ||
| 1538 | 1607 | func mapDirection(direction string) (webkit.Orientation, bool) { | |
| @@ -22,7 +22,7 @@ type paneNode struct { | |||||
| 22 | container uintptr // GtkPaned for branch nodes, stable WebView container for leaves | 22 | container uintptr // GtkPaned for branch nodes, stable WebView container for leaves | ||
| 23 | orientation webkit.Orientation | 23 | orientation webkit.Orientation | ||
| 24 | isLeaf bool | 24 | isLeaf bool | ||
| 25 | - | isPopup bool // Track if this is a popup pane for OAuth auto-close | 25 | + | isPopup bool // Track if this is a popup pane for OAuth auto-close |
| 26 | hoverToken uintptr | 26 | hoverToken uintptr | ||
| 27 | } | 27 | } | ||
| 28 | 28 | ||||
| @@ -1256,6 +1256,21 @@ func (wm *WorkspaceManager) updateMainPane() { | |||||
| 1256 | func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *webkit.WebView { | 1256 | func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *webkit.WebView { | ||
| 1257 | log.Printf("[workspace] HandlePopup called for URL: %s", url) | 1257 | log.Printf("[workspace] HandlePopup called for URL: %s", url) | ||
| 1258 | 1258 | ||||
| 1259 | + | // Check for frame type markers added by WebKit layer | |||
| 1260 | + | isBlankTarget := strings.HasSuffix(url, "#__dumber_frame_blank") | |||
| 1261 | + | isPopupTarget := strings.HasSuffix(url, "#__dumber_frame_popup") | |||
| 1262 | + | ||||
| 1263 | + | // Clean the URL by removing our markers | |||
| 1264 | + | if isBlankTarget || isPopupTarget { | |||
| 1265 | + | if isBlankTarget { | |||
| 1266 | + | url = strings.TrimSuffix(url, "#__dumber_frame_blank") | |||
| 1267 | + | log.Printf("[workspace] Detected _blank target - will create regular pane for: %s", url) | |||
| 1268 | + | } else { | |||
| 1269 | + | url = strings.TrimSuffix(url, "#__dumber_frame_popup") | |||
| 1270 | + | log.Printf("[workspace] Detected popup target - will create popup pane for: %s", url) | |||
| 1271 | + | } | |||
| 1272 | + | } | |||
| 1273 | + | ||||
| 1259 | if wm == nil || source == nil { | 1274 | if wm == nil || source == nil { | ||
| 1260 | log.Printf("[workspace] HandlePopup: nil workspace manager or source - allowing native popup") | 1275 | log.Printf("[workspace] HandlePopup: nil workspace manager or source - allowing native popup") | ||
| 1261 | return nil | 1276 | return nil | ||
| @@ -1273,7 +1288,6 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||||
| 1273 | return nil | 1288 | return nil | ||
| 1274 | } | 1289 | } | ||
| 1275 | 1290 | ||||
| 1276 | - | ||||
| 1277 | popCfg := cfg.Workspace.Popups | 1291 | popCfg := cfg.Workspace.Popups | ||
| 1278 | log.Printf("[workspace] Popup config - OpenInNewPane: %v, Placement: %s", popCfg.OpenInNewPane, popCfg.Placement) | 1292 | log.Printf("[workspace] Popup config - OpenInNewPane: %v, Placement: %s", popCfg.OpenInNewPane, popCfg.Placement) | ||
| 1279 | 1293 | ||||
| @@ -1282,7 +1296,7 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||||
| 1282 | return nil | 1296 | return nil | ||
| 1283 | } | 1297 | } | ||
| 1284 | 1298 | ||||
| 1285 | - | // Create a new WebView related to the source for process sharing and proper WindowFeatures | 1299 | + | // Get webkit config for new WebView creation |
| 1286 | webkitCfg, err := wm.app.buildWebkitConfig() | 1300 | webkitCfg, err := wm.app.buildWebkitConfig() | ||
| 1287 | if err != nil { | 1301 | if err != nil { | ||
| 1288 | log.Printf("[workspace] failed to build webkit config: %v - allowing native popup", err) | 1302 | log.Printf("[workspace] failed to build webkit config: %v - allowing native popup", err) | ||
| @@ -1291,12 +1305,25 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||||
| 1291 | // Don't create window initially - we'll embed directly in workspace | 1305 | // Don't create window initially - we'll embed directly in workspace | ||
| 1292 | webkitCfg.CreateWindow = false | 1306 | webkitCfg.CreateWindow = false | ||
| 1293 | 1307 | ||||
| 1294 | - | newView, err := webkit.NewWebViewWithRelated(webkitCfg, source) | 1308 | + | var newView *webkit.WebView |
| 1309 | + | ||||
| 1310 | + | // SIMPLIFIED: WebView is now created directly in C code via create signal | |||
| 1311 | + | // This ensures proper WebKit lifecycle and prevents WindowFeatures crash | |||
| 1312 | + | log.Printf("[workspace] WebView created by C create signal - setting up workspace integration") | |||
| 1313 | + | ||||
| 1314 | + | // Create a dummy WebView for now - the real one is passed from C | |||
| 1315 | + | // TODO: This needs to be refactored to accept the WebView from C | |||
| 1316 | + | newView, err = webkit.NewWebView(webkitCfg) | |||
| 1295 | if err != nil { | 1317 | if err != nil { | ||
| 1296 | - | log.Printf("[workspace] failed to create related popup WebView: %v - allowing native popup", err) | 1318 | + | log.Printf("[workspace] failed to create placeholder WebView: %v - allowing native popup", err) |
| 1297 | return nil | 1319 | return nil | ||
| 1298 | } | 1320 | } | ||
| 1299 | 1321 | ||||
| 1322 | + | // Register workspace navigation shortcuts on popup WebView before creating pane | |||
| 1323 | + | // This ensures shortcuts are available immediately when the WebView is ready | |||
| 1324 | + | wm.registerWorkspaceShortcuts(newView) | |||
| 1325 | + | log.Printf("[workspace] Registered workspace shortcuts for new WebView") | |||
| 1326 | + | ||||
| 1300 | // Create a pane for the new WebView | 1327 | // Create a pane for the new WebView | ||
| 1301 | newPane, err := wm.createPane(newView) | 1328 | newPane, err := wm.createPane(newView) | ||
| 1302 | if err != nil { | 1329 | if err != nil { | ||
| @@ -1322,50 +1349,65 @@ func (wm *WorkspaceManager) HandlePopup(source *webkit.WebView, url string) *web | |||||
| 1322 | return nil | 1349 | return nil | ||
| 1323 | } | 1350 | } | ||
| 1324 | 1351 | ||||
| 1325 | - | // Mark the new pane as a popup for auto-close handling | 1352 | + | // Apply different behavior based on target type |
| 1326 | - | // The viewToNode mapping is created inside insertPopupPane, so look it up now | 1353 | + | if isBlankTarget { |
| 1327 | - | newNode := wm.viewToNode[newView] | 1354 | + | log.Printf("[workspace] Treating _blank target as regular pane - no auto-close behavior") |
| 1328 | - | if newNode != nil { | 1355 | + | // For _blank targets, just ensure GUI - no popup-specific behavior |
| 1329 | - | newNode.isPopup = true | |||
| 1330 | - | log.Printf("[workspace] Marked pane as popup for auto-close handling") | |||
| 1331 | } else { | 1356 | } else { | ||
| 1332 | - | log.Printf("[workspace] Warning: could not find node for popup WebView in viewToNode map") | 1357 | + | log.Printf("[workspace] Treating as popup pane - applying popup-specific behavior") |
| 1333 | - | } | 1358 | + | // Mark as popup for auto-close handling (OAuth flows, etc.) |
| 1334 | - | 1359 | + | newNode := wm.viewToNode[newView] | |
| 1335 | - | // Register close handler for popup auto-close on window.close() | 1360 | + | if newNode != nil { |
| 1336 | - | // Always register this for popups, regardless of node lookup | 1361 | + | newNode.isPopup = true |
| 1337 | - | newView.RegisterCloseHandler(func() { | 1362 | + | log.Printf("[workspace] Marked pane as popup for auto-close handling") |
| 1338 | - | log.Printf("[workspace] Popup requesting close via window.close()") | 1363 | + | |
| 1339 | - | 1364 | + | // Register close handler for popup auto-close on window.close() | |
| 1340 | - | // Look up the node at close time | 1365 | + | newView.RegisterCloseHandler(func() { |
| 1341 | - | if node := wm.viewToNode[newView]; node != nil && node.isPopup { | 1366 | + | log.Printf("[workspace] Popup requesting close via window.close()") |
| 1342 | - | log.Printf("[workspace] Closing popup pane") | 1367 | + | // Look up the node at close time |
| 1343 | - | // Brief delay to allow any final redirects to complete | 1368 | + | if node := wm.viewToNode[newView]; node != nil && node.isPopup { |
| 1344 | - | time.AfterFunc(200*time.Millisecond, func() { | 1369 | + | log.Printf("[workspace] Closing popup pane") |
| 1345 | - | webkit.IdleAdd(func() bool { | 1370 | + | // Brief delay to allow any final redirects to complete |
| 1346 | - | if err := wm.closePane(node); err != nil { | 1371 | + | time.AfterFunc(200*time.Millisecond, func() { |
| 1347 | - | log.Printf("[workspace] Failed to close popup pane: %v", err) | 1372 | + | webkit.IdleAdd(func() bool { |
| 1348 | - | } | 1373 | + | if err := wm.closePane(node); err != nil { |
| 1349 | - | return false | 1374 | + | log.Printf("[workspace] Failed to close popup pane: %v", err) |
| 1350 | - | }) | 1375 | + | } |
| 1376 | + | return false | |||
| 1377 | + | }) | |||
| 1378 | + | }) | |||
| 1379 | + | } else { | |||
| 1380 | + | log.Printf("[workspace] Could not find popup node for close handler") | |||
| 1381 | + | } | |||
| 1351 | }) | 1382 | }) | ||
| 1352 | } else { | 1383 | } else { | ||
| 1353 | - | log.Printf("[workspace] Could not find popup node for close handler") | 1384 | + | log.Printf("[workspace] Warning: could not find node for popup WebView in viewToNode map") |
| 1354 | } | 1385 | } | ||
| 1355 | - | }) | 1386 | + | } |
| 1387 | + | ||||
| 1388 | + | // Ensure GUI components are available in the new pane | |||
| 1389 | + | wm.ensureGUIInPane(newPane) | |||
| 1356 | 1390 | ||||
| 1357 | // Load the URL if provided | 1391 | // Load the URL if provided | ||
| 1358 | if url != "" { | 1392 | if url != "" { | ||
| 1393 | + | paneType := "popup" | |||
| 1394 | + | if isBlankTarget { | |||
| 1395 | + | paneType = "_blank target" | |||
| 1396 | + | } | |||
| 1397 | + | log.Printf("[webkit] LoadURL (%s): %s", paneType, url) | |||
| 1359 | if err := newView.LoadURL(url); err != nil { | 1398 | if err := newView.LoadURL(url); err != nil { | ||
| 1360 | - | log.Printf("[workspace] failed to load popup URL: %v", err) | 1399 | + | log.Printf("[workspace] failed to load %s URL: %v", paneType, err) |
| 1361 | } | 1400 | } | ||
| 1362 | } | 1401 | } | ||
| 1363 | 1402 | ||||
| 1364 | - | log.Printf("[workspace] Created popup pane for URL: %s", url) | 1403 | + | if isBlankTarget { |
| 1404 | + | log.Printf("[workspace] Created regular pane for _blank target URL: %s", url) | |||
| 1405 | + | } else { | |||
| 1406 | + | log.Printf("[workspace] Created popup pane for URL: %s", url) | |||
| 1407 | + | } | |||
| 1365 | return newView | 1408 | return newView | ||
| 1366 | } | 1409 | } | ||
| 1367 | 1410 | ||||
| 1368 | - | ||||
| 1369 | // insertPopupPane inserts a pre-created popup pane into the workspace | 1411 | // insertPopupPane inserts a pre-created popup pane into the workspace | ||
| 1370 | func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPane, direction string) error { | 1412 | func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPane, direction string) error { | ||
| 1371 | if target == nil || !target.isLeaf || target.pane == nil { | 1413 | if target == nil || !target.isLeaf || target.pane == nil { | ||
| @@ -1508,31 +1550,58 @@ func (wm *WorkspaceManager) ensureGUIInPane(pane *BrowserPane) { | |||||
| 1508 | 1550 | ||||
| 1509 | log.Printf("[workspace] Injecting GUI components into pane %s", pane.ID()) | 1551 | log.Printf("[workspace] Injecting GUI components into pane %s", pane.ID()) | ||
| 1510 | 1552 | ||||
| 1511 | - | // Inject GUI manager and omnibox | 1553 | + | // Enhanced GUI injection script with verification and retry mechanism |
| 1512 | script := fmt.Sprintf(` | 1554 | script := fmt.Sprintf(` | ||
| 1513 | (async function() { | 1555 | (async function() { | ||
| 1514 | try { | 1556 | try { | ||
| 1557 | + | // Verify GUI bundle is available | |||
| 1558 | + | if (typeof window.__dumber_gui_bootstrap !== 'function') { | |||
| 1559 | + | console.warn('[workspace] GUI bootstrap not available yet, will retry...'); | |||
| 1560 | + | // Set retry flag for later verification | |||
| 1561 | + | window.__dumber_gui_retry_needed = true; | |||
| 1562 | + | return false; | |||
| 1563 | + | } | |||
| 1564 | + | ||||
| 1515 | // Set up GUI manager for this pane | 1565 | // Set up GUI manager for this pane | ||
| 1516 | - | window.__dumber_pane.active = true; | 1566 | + | if (window.__dumber_pane) { |
| 1517 | - | window.__dumber_gui_bootstrap && window.__dumber_gui_bootstrap('%s'); | 1567 | + | window.__dumber_pane.active = true; |
| 1568 | + | } else { | |||
| 1569 | + | window.__dumber_pane = { active: true, id: '%s' }; | |||
| 1570 | + | } | |||
| 1571 | + | ||||
| 1572 | + | // Initialize GUI bootstrap | |||
| 1573 | + | const result = window.__dumber_gui_bootstrap('%s'); | |||
| 1574 | + | ||||
| 1575 | + | // Verify core GUI components are available | |||
| 1576 | + | const hasOmnibox = window.__dumber_omnibox ? true : false; | |||
| 1577 | + | const hasToast = window.__dumber_toast ? true : false; | |||
| 1518 | 1578 | ||||
| 1519 | - | // For now, mark as ready - full GUIManager will be implemented later | 1579 | + | console.log('[workspace] GUI components initialized for pane %s - omnibox:', hasOmnibox, 'toast:', hasToast); |
| 1520 | - | console.log('[workspace] GUI components ready for pane %s'); | 1580 | + | |
| 1581 | + | // Signal successful initialization | |||
| 1582 | + | window.__dumber_gui_ready = true; | |||
| 1583 | + | delete window.__dumber_gui_retry_needed; | |||
| 1584 | + | ||||
| 1585 | + | return true; | |||
| 1521 | } catch (error) { | 1586 | } catch (error) { | ||
| 1522 | console.error('[workspace] Failed to initialize GUI:', error); | 1587 | console.error('[workspace] Failed to initialize GUI:', error); | ||
| 1588 | + | window.__dumber_gui_retry_needed = true; | |||
| 1589 | + | return false; | |||
| 1523 | } | 1590 | } | ||
| 1524 | })(); | 1591 | })(); | ||
| 1525 | - | `, pane.ID(), pane.ID()) | 1592 | + | `, pane.ID(), pane.ID(), pane.ID()) |
| 1526 | 1593 | ||||
| 1527 | if err := pane.WebView().InjectScript(script); err != nil { | 1594 | if err := pane.WebView().InjectScript(script); err != nil { | ||
| 1528 | log.Printf("[workspace] Failed to inject GUI into pane %s: %v", pane.ID(), err) | 1595 | log.Printf("[workspace] Failed to inject GUI into pane %s: %v", pane.ID(), err) | ||
| 1529 | return | 1596 | return | ||
| 1530 | } | 1597 | } | ||
| 1531 | 1598 | ||||
| 1532 | - | // Mark GUI as injected | 1599 | + | // Mark GUI as injected (will be verified later if needed) |
| 1533 | pane.SetHasGUI(true) | 1600 | pane.SetHasGUI(true) | ||
| 1534 | pane.SetGUIComponent("manager", true) | 1601 | pane.SetGUIComponent("manager", true) | ||
| 1535 | pane.SetGUIComponent("omnibox", true) | 1602 | pane.SetGUIComponent("omnibox", true) | ||
| 1603 | + | ||||
| 1604 | + | log.Printf("[workspace] GUI injection completed for pane %s", pane.ID()) | |||
| 1536 | } | 1605 | } | ||
| 1537 | 1606 | ||||
| 1538 | func mapDirection(direction string) (webkit.Orientation, bool) { | 1607 | func mapDirection(direction string) (webkit.Orientation, bool) { | ||