Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions v3/pkg/application/webview_window_modal_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//go:build windows && !server

package application

import (
"os"
"strings"
"testing"
)

// Regression test for https://github.com/wailsapp/wails/issues/6054.
func TestModalParentIsReleasedBeforeWindowDestruction(t *testing.T) {
sourceBytes, err := os.ReadFile("webview_window_windows.go")
if err != nil {
t.Fatal(err)
}
source := string(sourceBytes)

assertCallOrder(
t,
source,
"func (w *windowsWebviewWindow) destroy()",
"func (w *windowsWebviewWindow) reload()",
"w.releaseModalParent(w.isFocused())",
"w32.DestroyWindow(w.hwnd)",
)
assertCallOrder(
t,
source,
"case w32.WM_CLOSE:",
"case w32.WM_SETCURSOR:",
"w.releaseModalParent(restoreParentActivation)",
"w32.DefWindowProc(w.hwnd, w32.WM_CLOSE, 0, 0)",
)
}

func TestModalParentActivationIsConditional(t *testing.T) {
sourceBytes, err := os.ReadFile("webview_window_windows.go")
if err != nil {
t.Fatal(err)
}
assertCallOrder(
t,
string(sourceBytes),
"func (w *windowsWebviewWindow) releaseModalParent(activate bool)",
"func (w *windowsWebviewWindow) reload()",
"if activate {",
"w32.SetActiveWindow(w.parentHWND)",
)
}

func assertCallOrder(t *testing.T, source, start, end, first, second string) {
t.Helper()
startIndex := strings.Index(source, start)
if startIndex < 0 {
t.Fatalf("start marker %q not found", start)
}
section := source[startIndex:]
endIndex := strings.Index(section, end)
if endIndex < 0 {
t.Fatalf("end marker %q not found after %q", end, start)
}
section = section[:endIndex]
firstIndex := strings.Index(section, first)
secondIndex := strings.Index(section, second)
if firstIndex < 0 || secondIndex < 0 {
t.Fatalf("expected %q and %q between %q and %q", first, second, start, end)
}
if firstIndex > secondIndex {
t.Fatalf("%q must occur before %q between %q and %q", first, second, start, end)
}
}
43 changes: 24 additions & 19 deletions v3/pkg/application/webview_window_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,17 +854,28 @@ func (w *windowsWebviewWindow) setRelativePosition(x int, y int) {
}

func (w *windowsWebviewWindow) destroy() {
// Re-enable parent window if this was a modal window
if w.parentHWND != 0 {
w32.EnableWindow(w.parentHWND, true)
w.parentHWND = 0
}
w.releaseModalParent(w.isFocused())

w.parent.markAsDestroyed()
// destroy the window
w32.DestroyWindow(w.hwnd)
}

// releaseModalParent makes an attached modal's owner eligible for activation
// again. If the modal is currently foreground, activate its owner before the
// modal is destroyed so Windows does not select the next unrelated top-level
// window. A background modal must not steal activation from another app.
func (w *windowsWebviewWindow) releaseModalParent(activate bool) {
if w.parentHWND == 0 {
return
}
w32.EnableWindow(w.parentHWND, true)
if activate {
w32.SetActiveWindow(w.parentHWND)
}
w.parentHWND = 0
}

func (w *windowsWebviewWindow) reload() {
w.execJS("window.location.reload();")
}
Expand Down Expand Up @@ -1645,21 +1656,15 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp
return 0
}

defer func() {
// Re-enable parent window if this was a modal window
if w.parentHWND != 0 {
w32.EnableWindow(w.parentHWND, true)
w.parentHWND = 0
}

windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.unregisterWindow(w)

}()

// Now do the actual close
// DefWindowProc destroys the modal and may select the next unrelated
// top-level window, so remember whether its owner should remain active.
restoreParentActivation := w.isFocused()
w.chromium.ShuttingDown()
return w32.DefWindowProc(w.hwnd, w32.WM_CLOSE, 0, 0)
w.releaseModalParent(restoreParentActivation)
result := w32.DefWindowProc(w.hwnd, w32.WM_CLOSE, 0, 0)
windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.unregisterWindow(w)
return result
case w32.WM_SETCURSOR:
if w.compositionCursor != 0 && w32.LOWORD(uint32(lparam)) == w32.HTCLIENT {
w32.SetCursor(w.compositionCursor)
Expand Down
Loading