package rustwright
import (
"errors"
"fmt"
"runtime"
"unsafe"
"github.com/ebitengine/purego"
)
type nativeAPI struct {
handle uintptr
lastError func() uintptr
stringFree func(uintptr)
bytesFree func(uintptr, uintptr)
chromiumExecutablePath func(*uintptr) int32
chromiumLaunch func(*byte, *uintptr) int32
browserNewPage func(uintptr, *uintptr) int32
browserClose func(uintptr) int32
browserWSEndpoint func(uintptr) uintptr
browserFree func(uintptr)
pageTargetID func(uintptr) uintptr
pageGoto func(uintptr, *byte, *byte, float64, *byte, *uintptr) int32
pageClick func(uintptr, *byte, float64) int32
pageFill func(uintptr, *byte, *byte, float64) int32
pageTitle func(uintptr, float64, *uintptr) int32
pageTextContent func(uintptr, *byte, float64, *uintptr) int32
pageEvaluate func(uintptr, *byte, *byte, float64, *uintptr) int32
pageScreenshot func(uintptr, *byte, *uintptr, *uintptr) int32
pageClose func(uintptr, float64, int32) int32
pageFree func(uintptr)
}
func loadNative(path string) (_ *nativeAPI, err error) {
if path == "" {
return nil, errors.New("rustwright: shared library path is empty")
}
h, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL)
if err != nil {
return nil, fmt.Errorf("rustwright: load %q: %w", path, err)
}
n := &nativeAPI{handle: h}
defer func() {
if recovered := recover(); recovered != nil {
_ = purego.Dlclose(h)
err = fmt.Errorf("rustwright: register C ABI: %v", recovered)
}
}()
purego.RegisterLibFunc(&n.lastError, h, "rw_last_error")
purego.RegisterLibFunc(&n.stringFree, h, "rw_string_free")
purego.RegisterLibFunc(&n.bytesFree, h, "rw_bytes_free")
purego.RegisterLibFunc(&n.chromiumExecutablePath, h, "rw_chromium_executable_path")
purego.RegisterLibFunc(&n.chromiumLaunch, h, "rw_chromium_launch")
purego.RegisterLibFunc(&n.browserNewPage, h, "rw_browser_new_page")
purego.RegisterLibFunc(&n.browserClose, h, "rw_browser_close")
purego.RegisterLibFunc(&n.browserWSEndpoint, h, "rw_browser_ws_endpoint")
purego.RegisterLibFunc(&n.browserFree, h, "rw_browser_free")
purego.RegisterLibFunc(&n.pageTargetID, h, "rw_page_target_id")
purego.RegisterLibFunc(&n.pageGoto, h, "rw_page_goto")
purego.RegisterLibFunc(&n.pageClick, h, "rw_page_click")
purego.RegisterLibFunc(&n.pageFill, h, "rw_page_fill")
purego.RegisterLibFunc(&n.pageTitle, h, "rw_page_title")
purego.RegisterLibFunc(&n.pageTextContent, h, "rw_page_text_content")
purego.RegisterLibFunc(&n.pageEvaluate, h, "rw_page_evaluate")
purego.RegisterLibFunc(&n.pageScreenshot, h, "rw_page_screenshot")
purego.RegisterLibFunc(&n.pageClose, h, "rw_page_close")
purego.RegisterLibFunc(&n.pageFree, h, "rw_page_free")
return n, nil
}
func (n *nativeAPI) onOSThread(call func() int32) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
status := call()
if status == 0 {
return nil
}
return n.copyLastError(status)
}
func (n *nativeAPI) copyLastError(status int32) error {
ptr := n.lastError() message := copyCString(ptr)
if message == "" {
message = "native call failed without an error message"
}
return fmt.Errorf("rustwright: %s (status %d)", message, status)
}
func (n *nativeAPI) directString(call func() uintptr, operation string) (string, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ptr := call()
if ptr == 0 {
err := n.copyLastError(-1)
return "", fmt.Errorf("%s: %w", operation, err)
}
value := copyCString(ptr)
n.stringFree(ptr)
return value, nil
}
func cString(value string) ([]byte, *byte, error) {
for i := 0; i < len(value); i++ {
if value[i] == 0 {
return nil, nil, errors.New("rustwright: strings passed to the C ABI cannot contain NUL")
}
}
buf := make([]byte, len(value)+1)
copy(buf, value)
return buf, &buf[0], nil
}
func optionalCString(value string, present bool) ([]byte, *byte, error) {
if !present {
return nil, nil, nil
}
return cString(value)
}
func copyCString(ptr uintptr) string {
if ptr == 0 {
return ""
}
base := pointerFromUintptr(ptr)
const maxInt = int(^uint(0) >> 1)
length := 0
for length < maxInt && *(*byte)(unsafe.Add(base, length)) != 0 {
length++
}
if length == 0 {
return ""
}
return string(unsafe.Slice((*byte)(base), length))
}
func pointerFromUintptr(ptr uintptr) unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&ptr))
}