diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3552451 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/cmd/demo/main.go b/cmd/demo/main.go index ba713d4..eed9ecf 100644 --- a/cmd/demo/main.go +++ b/cmd/demo/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "github.com/jchv/go-webview2" @@ -23,6 +24,26 @@ func main() { } defer w.Destroy() w.SetSize(800, 600, webview2.HintFixed) + _ = w.NavigationStarting(onNavigationStarting) + _ = w.NavigationCompleted(onNavigationCompleted) w.Navigate("https://en.m.wikipedia.org/wiki/Main_Page") w.Run() } + +func onNavigationCompleted(httpStatusCode int32, isSuccess bool, navigationId uint64, webErrorStatus int32) { + fmt.Println("navigation completed:") + fmt.Println("http status code: ", httpStatusCode) + fmt.Println("success: ", isSuccess) + fmt.Println("navigation id: ", navigationId) + fmt.Println("web error status: ", webErrorStatus) +} + +func onNavigationStarting(additionalAllowedFrameAncestors string, isRedirected bool, isUserInitiated bool, navigationId uint64, uri string) bool { + fmt.Println("navigation starting:") + fmt.Println("redirected: ", isRedirected) + fmt.Println("user initiated: ", isUserInitiated) + fmt.Println("navigation id: ", navigationId) + fmt.Println("additional allowed frame ancestors: ", additionalAllowedFrameAncestors) + fmt.Println("uri: ", uri) + return false +} diff --git a/common.go b/common.go index 4723f1f..b70ec19 100644 --- a/common.go +++ b/common.go @@ -77,4 +77,18 @@ type WebView interface { // f must be a function // f must return either value and error or just error Bind(name string, f interface{}) error + + // NavigationStarting binds a callback function for right before the webview2 + // perform a navigation + // + // f must be a function + // f must return true, to proceed with the navigation + // f must return false, to cancel the navigation + NavigationStarting(f func(additionalAllowedFrameAncestors string, isRedirected bool, isUserInitiated bool, navigationId uint64, uri string) bool) error + + // NavigationCompleted binds a callback function for right after the webview2 + // has performed a navigation + // + // f must be a function + NavigationCompleted(f func(httpStatusCode int32, isSuccess bool, navigationId uint64, webErrorStatus int32)) error } diff --git a/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go b/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go index 73aa5fa..d16e8d1 100644 --- a/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go +++ b/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go @@ -5,6 +5,7 @@ type _ICoreWebView2NavigationCompletedEventArgsVtbl struct { GetIsSuccess ComProc GetWebErrorStatus ComProc GetNavigationId ComProc + GetHttpStatusCode ComProc } type ICoreWebView2NavigationCompletedEventArgs struct { diff --git a/pkg/edge/ICoreWebView2NavigationStartingEventArgs.go b/pkg/edge/ICoreWebView2NavigationStartingEventArgs.go new file mode 100644 index 0000000..1a7bffa --- /dev/null +++ b/pkg/edge/ICoreWebView2NavigationStartingEventArgs.go @@ -0,0 +1,21 @@ +package edge + +type _ICoreWebView2NavigationStartingEventArgsVtbl struct { + _IUnknownVtbl + GetUri ComProc + GetIsUserInitiated ComProc + GetIsRedirected ComProc + GetRequestHeaders ComProc + GetAdditionalAllowedFrameAncestors ComProc + PutCancel ComProc + GetNavigationId ComProc +} + +type ICoreWebView2NavigationStartingEventArgs struct { + vtbl *_ICoreWebView2NavigationStartingEventArgsVtbl +} + +func (i *ICoreWebView2NavigationStartingEventArgs) AddRef() uintptr { + r, _, _ := i.vtbl.AddRef.Call() + return r +} diff --git a/pkg/edge/ICoreWebView2NavigationStartingEventHandler.go b/pkg/edge/ICoreWebView2NavigationStartingEventHandler.go new file mode 100644 index 0000000..e6efc8e --- /dev/null +++ b/pkg/edge/ICoreWebView2NavigationStartingEventHandler.go @@ -0,0 +1,48 @@ +package edge + +type _ICoreWebView2NavigationStartingEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type ICoreWebView2NavigationStartingEventHandler struct { + vtbl *_ICoreWebView2NavigationStartingEventHandlerVtbl + impl _ICoreWebView2NavigationStartingEventHandlerImpl +} + +func _ICoreWebView2NavigationStartingEventHandlerIUnknownQueryInterface(this *ICoreWebView2NavigationStartingEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2NavigationStartingEventHandlerIUnknownAddRef(this *ICoreWebView2NavigationStartingEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2NavigationStartingEventHandlerIUnknownRelease(this *ICoreWebView2NavigationStartingEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2NavigationStartingEventHandlerInvoke(this *ICoreWebView2NavigationStartingEventHandler, sender *ICoreWebView2, args *ICoreWebView2NavigationStartingEventArgs) uintptr { + return this.impl.NavigationStarting(sender, args) +} + +type _ICoreWebView2NavigationStartingEventHandlerImpl interface { + _IUnknownImpl + NavigationStarting(sender *ICoreWebView2, args *ICoreWebView2NavigationStartingEventArgs) uintptr +} + +var _ICoreWebView2NavigationStartingEventHandlerFn = _ICoreWebView2NavigationStartingEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2NavigationStartingEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2NavigationStartingEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2NavigationStartingEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2NavigationStartingEventHandlerInvoke), +} + +func newICoreWebView2NavigationStartingEventHandler(impl _ICoreWebView2NavigationStartingEventHandlerImpl) *ICoreWebView2NavigationStartingEventHandler { + return &ICoreWebView2NavigationStartingEventHandler{ + vtbl: &_ICoreWebView2NavigationStartingEventHandlerFn, + impl: impl, + } +} diff --git a/pkg/edge/chromium.go b/pkg/edge/chromium.go index 51df7b0..41cc4fa 100644 --- a/pkg/edge/chromium.go +++ b/pkg/edge/chromium.go @@ -27,6 +27,7 @@ type Chromium struct { webResourceRequested *iCoreWebView2WebResourceRequestedEventHandler acceleratorKeyPressed *ICoreWebView2AcceleratorKeyPressedEventHandler navigationCompleted *ICoreWebView2NavigationCompletedEventHandler + navigationStarting *ICoreWebView2NavigationStartingEventHandler environment *ICoreWebView2Environment @@ -40,7 +41,8 @@ type Chromium struct { // Callbacks MessageCallback func(string) WebResourceRequestedCallback func(request *ICoreWebView2WebResourceRequest, args *ICoreWebView2WebResourceRequestedEventArgs) - NavigationCompletedCallback func(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) + NavigationCompletedCallback func(httpStatusCode int32, isSuccess bool, navigationId uint64, webErrorStatus int32) + NavigationStartingCallback func(additionalAllowedFrameAncestors string, isRedirected bool, isUserInitiated bool, navigationId uint64, uri string) bool AcceleratorKeyCallback func(uint) bool } @@ -64,6 +66,7 @@ func NewChromium() *Chromium { e.webResourceRequested = newICoreWebView2WebResourceRequestedEventHandler(e) e.acceleratorKeyPressed = newICoreWebView2AcceleratorKeyPressedEventHandler(e) e.navigationCompleted = newICoreWebView2NavigationCompletedEventHandler(e) + e.navigationStarting = newICoreWebView2NavigationStartingEventHandler(e) e.permissions = make(map[CoreWebView2PermissionKind]CoreWebView2PermissionState) return e @@ -211,6 +214,11 @@ func (e *Chromium) CreateCoreWebView2ControllerCompleted(res uintptr, controller uintptr(unsafe.Pointer(e.navigationCompleted)), uintptr(unsafe.Pointer(&token)), ) + _, _, _ = e.webview.vtbl.AddNavigationStarting.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(e.navigationStarting)), + uintptr(unsafe.Pointer(&token)), + ) _ = e.controller.AddAcceleratorKeyPressed(e.acceleratorKeyPressed, &token) @@ -331,8 +339,83 @@ func boolToInt(input bool) int { func (e *Chromium) NavigationCompleted(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr { if e.NavigationCompletedCallback != nil { - e.NavigationCompletedCallback(sender, args) + var isSuccess uint + var _, _, _ = args.vtbl.GetIsSuccess.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&isSuccess)), + ) + + var webErrorStatus int32 + var _, _, _ = args.vtbl.GetWebErrorStatus.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&webErrorStatus)), + ) + + var navigationId uint64 + var _, _, _ = args.vtbl.GetNavigationId.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&navigationId)), + ) + + var httpStatusCode int32 + var _, _, _ = args.vtbl.GetHttpStatusCode.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&httpStatusCode)), + ) + + e.NavigationCompletedCallback(httpStatusCode, isSuccess == 1, navigationId, webErrorStatus) + } + return 0 +} + +func (e *Chromium) NavigationStarting(sender *ICoreWebView2, args *ICoreWebView2NavigationStartingEventArgs) uintptr { + if e.NavigationStartingCallback != nil { + + var navigationId uint64 + var _, _, _ = args.vtbl.GetNavigationId.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&navigationId)), + ) + + var uriPtr *uint16 + var _, _, _ = args.vtbl.GetUri.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&uriPtr)), + ) + var uri = w32.Utf16PtrToString(uriPtr) + + var additionalAllowedFrameAncestorsPtr *uint16 + var _, _, _ = args.vtbl.GetAdditionalAllowedFrameAncestors.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&additionalAllowedFrameAncestorsPtr)), + ) + var additionalAllowedFrameAncestors = w32.Utf16PtrToString(additionalAllowedFrameAncestorsPtr) + + var isRedirected uint + var _, _, _ = args.vtbl.GetIsRedirected.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&isRedirected)), + ) + + var isUserInitiated uint + var _, _, _ = args.vtbl.GetIsUserInitiated.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&isUserInitiated)), + ) + + var proceed = e.NavigationStartingCallback(additionalAllowedFrameAncestors, isRedirected == 1, isUserInitiated == 1, navigationId, uri) + + if proceed { + return 0 + } + + var res uint + var _, _, _ = args.vtbl.PutCancel.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&res)), + ) } + return 0 } diff --git a/webview.go b/webview.go index 1a92dc9..d3d16d7 100644 --- a/webview.go +++ b/webview.go @@ -18,6 +18,11 @@ import ( "golang.org/x/sys/windows" ) +const ( + NavigationStartingBindingName = "navigation-starting" + NavigationCompletedBindingName = "navigation-completed" +) + var ( windowContext = map[uintptr]interface{}{} windowContextSync sync.RWMutex @@ -100,6 +105,8 @@ func NewWithOptions(options WebViewOptions) WebView { chromium := edge.NewChromium() chromium.MessageCallback = w.msgcb + chromium.NavigationCompletedCallback = w.navigationCompletedCallback + chromium.NavigationStartingCallback = w.navigationStartingCallback chromium.DataPath = options.DataPath chromium.SetPermission(edge.CoreWebView2PermissionKindClipboardRead, edge.CoreWebView2PermissionStateAllow) @@ -474,3 +481,69 @@ func (w *webview) Bind(name string, f interface{}) error { return nil } + +func (w *webview) navigationCompletedCallback(httpStatusCode int32, isSuccess bool, navigationId uint64, webErrorStatus int32) { + w.m.Lock() + var f, ok = w.bindings[NavigationCompletedBindingName] + w.m.Unlock() + + if !ok { + return + } + + var fValue = reflect.ValueOf(f) + var fArgs = []reflect.Value{ + reflect.ValueOf(httpStatusCode), + reflect.ValueOf(isSuccess), + reflect.ValueOf(navigationId), + reflect.ValueOf(webErrorStatus), + } + fValue.Call(fArgs) +} + +func (w *webview) NavigationCompleted(f func(httpStatusCode int32, isSuccess bool, navigationId uint64, webErrorStatus int32)) error { + w.m.Lock() + w.bindings[NavigationCompletedBindingName] = f + w.m.Unlock() + + return nil +} + +func (w *webview) navigationStartingCallback(additionalAllowedFrameAncestors string, isRedirected bool, isUserInitiated bool, navigationId uint64, uri string) bool { + w.m.Lock() + var f, ok = w.bindings[NavigationStartingBindingName] + w.m.Unlock() + + if !ok { + return true + } + + var fValue = reflect.ValueOf(f) + var fArgs = []reflect.Value{ + reflect.ValueOf(additionalAllowedFrameAncestors), + reflect.ValueOf(isRedirected), + reflect.ValueOf(isUserInitiated), + reflect.ValueOf(navigationId), + reflect.ValueOf(uri), + } + var returnedValues = fValue.Call(fArgs) + + if len(returnedValues) == 0 { + return true + } + + var firstValue = returnedValues[0] + if firstValue.Kind() != reflect.Bool { + return true + } + + return firstValue.Bool() +} + +func (w *webview) NavigationStarting(f func(additionalAllowedFrameAncestors string, isRedirected bool, isUserInitiated bool, navigationId uint64, uri string) bool) error { + w.m.Lock() + w.bindings[NavigationStartingBindingName] = f + w.m.Unlock() + + return nil +}