127 lines
3.6 KiB
Go
127 lines
3.6 KiB
Go
//go:build windows
|
|
|
|
package browser
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
jobObjectExtendedLimitClass = 9
|
|
jobObjectLimitKillOnJobClose = 0x2000
|
|
wmClose = 0x0010
|
|
)
|
|
|
|
var (
|
|
kernel32Job = syscall.NewLazyDLL("kernel32.dll")
|
|
createJobObjectW = kernel32Job.NewProc("CreateJobObjectW")
|
|
setInformationJobObject = kernel32Job.NewProc("SetInformationJobObject")
|
|
assignProcessToJobObject = kernel32Job.NewProc("AssignProcessToJobObject")
|
|
terminateJobObject = kernel32Job.NewProc("TerminateJobObject")
|
|
enumWindows = user32.NewProc("EnumWindows")
|
|
getWindowProcessID = user32.NewProc("GetWindowThreadProcessId")
|
|
postMessageW = user32.NewProc("PostMessageW")
|
|
)
|
|
|
|
type jobHandle struct{ handle syscall.Handle }
|
|
|
|
type jobObjectBasicLimitInformation struct {
|
|
PerProcessUserTimeLimit int64
|
|
PerJobUserTimeLimit int64
|
|
LimitFlags uint32
|
|
MinimumWorkingSetSize uintptr
|
|
MaximumWorkingSetSize uintptr
|
|
ActiveProcessLimit uint32
|
|
Affinity uintptr
|
|
PriorityClass uint32
|
|
SchedulingClass uint32
|
|
}
|
|
|
|
type ioCounters struct{ ReadOperationCount, WriteOperationCount, OtherOperationCount, ReadTransferCount, WriteTransferCount, OtherTransferCount uint64 }
|
|
|
|
type jobObjectExtendedLimitInformation struct {
|
|
BasicLimitInformation jobObjectBasicLimitInformation
|
|
IoInfo ioCounters
|
|
ProcessMemoryLimit uintptr
|
|
JobMemoryLimit uintptr
|
|
PeakProcessMemoryUsed uintptr
|
|
PeakJobMemoryUsed uintptr
|
|
}
|
|
|
|
func (j *jobHandle) assign(process *os.Process) error {
|
|
job, _, err := createJobObjectW.Call(0, 0)
|
|
if job == 0 {
|
|
return fmt.Errorf("create browser job object: %w", err)
|
|
}
|
|
j.handle = syscall.Handle(job)
|
|
info := jobObjectExtendedLimitInformation{BasicLimitInformation: jobObjectBasicLimitInformation{LimitFlags: jobObjectLimitKillOnJobClose}}
|
|
if result, _, callErr := setInformationJobObject.Call(job, jobObjectExtendedLimitClass, uintptr(unsafe.Pointer(&info)), unsafe.Sizeof(info)); result == 0 {
|
|
j.close()
|
|
return fmt.Errorf("configure browser job object: %w", callErr)
|
|
}
|
|
processHandle, err := syscall.OpenProcess(0x0100|0x0001, false, uint32(process.Pid))
|
|
if err != nil {
|
|
j.close()
|
|
return fmt.Errorf("open browser process for job object: %w", err)
|
|
}
|
|
defer syscall.CloseHandle(processHandle)
|
|
if result, _, callErr := assignProcessToJobObject.Call(job, uintptr(processHandle)); result == 0 {
|
|
j.close()
|
|
return fmt.Errorf("assign browser process to job object: %w", callErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (j *jobHandle) close() {
|
|
if j == nil || j.handle == 0 {
|
|
return
|
|
}
|
|
_ = syscall.CloseHandle(j.handle)
|
|
j.handle = 0
|
|
}
|
|
|
|
func (j *jobHandle) terminate() error {
|
|
if j == nil || j.handle == 0 {
|
|
return errors.New("browser job object is unavailable")
|
|
}
|
|
result, _, err := terminateJobObject.Call(uintptr(j.handle), 1)
|
|
if result == 0 {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *osProcess) Stop(ctx context.Context, force bool) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if force {
|
|
return p.job.terminate()
|
|
}
|
|
return postCloseToProcess(p.command.Process.Pid)
|
|
}
|
|
|
|
func postCloseToProcess(pid int) error {
|
|
if pid <= 0 {
|
|
return errors.New("invalid browser PID")
|
|
}
|
|
callback := syscall.NewCallback(func(hwnd uintptr, lParam uintptr) uintptr {
|
|
var windowPID uint32
|
|
getWindowProcessID.Call(hwnd, uintptr(unsafe.Pointer(&windowPID)))
|
|
if int(windowPID) == pid {
|
|
postMessageW.Call(hwnd, wmClose, 0, 0)
|
|
}
|
|
return 1
|
|
})
|
|
result, _, err := enumWindows.Call(callback, uintptr(pid))
|
|
if result == 0 && err != syscall.Errno(0) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|