-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add bindings and expose Navigation Starting and Navigation Completed #53
base: master
Are you sure you want to change the base?
Changes from all commits
5e3c707
841e73f
c10319f
83b788e
fd41d55
2269a8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ type _ICoreWebView2NavigationCompletedEventArgsVtbl struct { | |
GetIsSuccess ComProc | ||
GetWebErrorStatus ComProc | ||
GetNavigationId ComProc | ||
GetHttpStatusCode ComProc | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unsafe; Take a look at how it is done for Notice the function It should wind up looking something like this: package edge
...
type _ICoreWebView2NavigationCompletedEventArgs2Vtbl struct {
_ICoreWebView2NavigationCompletedEventArgsVtbl
GetHttpStatusCode ComProc
}
type ICoreWebView2NavigationCompletedEventArgs2 struct {
vtbl *_ICoreWebView2NavigationCompletedEventArgs2Vtbl
}
func (i *ICoreWebView2NavigationCompletedEventArgs) GetICoreWebView2NavigationCompletedEventArgs2() *ICoreWebView2NavigationCompletedEventArgs2 {
var result *ICoreWebView2NavigationCompletedEventArgs2
iidICoreWebView2NavigationCompletedEventArgs2 := NewGUID("{FDF8B738-EE1E-4DB2-A329-8D7D7B74D792}")
_, _, _ = i.vtbl.QueryInterface.Call(
uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(iidICoreWebView2NavigationCompletedEventArgs2)),
uintptr(unsafe.Pointer(&result)))
return result
} |
||
} | ||
|
||
type ICoreWebView2NavigationCompletedEventArgs struct { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package edge | ||
|
||
type _ICoreWebView2NavigationStartingEventArgsVtbl struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two problems here:
The Vtable order for [propget] HRESULT Uri([out, retval] LPWSTR* uri);
[propget] HRESULT IsUserInitiated([out, retval] BOOL* isUserInitiated);
[propget] HRESULT IsRedirected([out, retval] BOOL* isRedirected);
[propget] HRESULT RequestHeaders([out, retval] ICoreWebView2HttpRequestHeaders** requestHeaders);
[propget] HRESULT Cancel([out, retval] BOOL* cancel);
[propput] HRESULT Cancel([in] BOOL cancel);
[propget] HRESULT NavigationId([out, retval] UINT64* navigationId); (Note that your names are fine. The And // (... everything from the original ICoreWebView2NavigationStartingEventArgs)
[propget] HRESULT AdditionalAllowedFrameAncestors([out, retval] LPWSTR* value);
[propput] HRESULT AdditionalAllowedFrameAncestors([in] LPCWSTR value); (Note that there is a put from If you are wondering where this information comes from, it comes from the WebView2 NuGet package. You can download that here: https://www.nuget.org/packages/Microsoft.Web.WebView2 Just click "Download Package". The corresponding MSDN alone is not good enough, as MSDN displays the functions in a different order from their VTable ordering. We need these functions to be in VTable order for our memory map to align. |
||
_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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The This logic could be moved to |
||
var isSuccess uint | ||
var _, _, _ = args.vtbl.GetIsSuccess.Call( | ||
uintptr(unsafe.Pointer(args)), | ||
uintptr(unsafe.Pointer(&isSuccess)), | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although it is not strictly necessary, a way this code could be improved is by adding helpers into the func (a *ICoreWebView2NavigationCompletedEventArgs) IsSuccess() bool {
var isSuccess uint
var _, _, _ = args.vtbl.GetIsSuccess.Call(
uintptr(unsafe.Pointer(args)),
uintptr(unsafe.Pointer(&isSuccess)),
)
return isSuccess == 1
} (These should also error-check, but unfortunately that's already missing in a lot of places, so it's not really a big deal.) |
||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make the |
||
|
||
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 | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This binding mechanism is meant for the JavaScript-Go binding system. Instead, just use normal typed function pointers inside the webview struct, like is done in the chromium struct. These handlers ( |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of adding a new API for this, please use the |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both of the callbacks have rather long strings of arguments. I think it would be easier and more future-proof to make structs to hold these arguments, like: type NavigationStartingEventArgs struct {
AdditionalAllowedFrameAncestors string
IsRedirected bool
IsUserInitiated bool
NavigationID uint64
URI string
} Then the closure type could just be func(args NavigationStartingEventArgs) bool Ditto for NavigationCompleted. |
||
w.m.Lock() | ||
w.bindings[NavigationStartingBindingName] = f | ||
w.m.Unlock() | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned elsewhere, the
WebView
interface is frozen to preserve compatibility withwebview/webview
. The best way to add new functionality like this right now is to allow the user to pass a handler into the options struct ofNewWithOptions
.