mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
channel: master version: 1.1.0 source-ref: master published-at-utc: 2026-03-29T11:32:04Z
218 lines
3.9 KiB
Go
218 lines
3.9 KiB
Go
package backend
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
browserStderrTailMaxLines = 40
|
|
browserStderrTailMaxBytes = 4 * 1024
|
|
)
|
|
|
|
type browserProcessExitResult struct {
|
|
Err error
|
|
StderrTail string
|
|
}
|
|
|
|
type browserProcessMonitor struct {
|
|
cmd *exec.Cmd
|
|
stderr io.ReadCloser
|
|
stderrTail *tailTextBuffer
|
|
stderrDone chan struct{}
|
|
waitDone chan struct{}
|
|
|
|
mu sync.Mutex
|
|
result browserProcessExitResult
|
|
debugPort int
|
|
}
|
|
|
|
func newBrowserProcessMonitor(cmd *exec.Cmd) (*browserProcessMonitor, error) {
|
|
if cmd == nil {
|
|
return nil, fmt.Errorf("browser command is nil")
|
|
}
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &browserProcessMonitor{
|
|
cmd: cmd,
|
|
stderr: stderr,
|
|
stderrTail: newTailTextBuffer(browserStderrTailMaxLines, browserStderrTailMaxBytes),
|
|
stderrDone: make(chan struct{}),
|
|
waitDone: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
func (m *browserProcessMonitor) Start() {
|
|
go m.captureStderr()
|
|
go m.waitForExit()
|
|
}
|
|
|
|
func (m *browserProcessMonitor) Done() <-chan struct{} {
|
|
return m.waitDone
|
|
}
|
|
|
|
func (m *browserProcessMonitor) HasExited() bool {
|
|
select {
|
|
case <-m.waitDone:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (m *browserProcessMonitor) Result() browserProcessExitResult {
|
|
<-m.waitDone
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.result
|
|
}
|
|
|
|
func (m *browserProcessMonitor) Wait() error {
|
|
return m.Result().Err
|
|
}
|
|
|
|
func (m *browserProcessMonitor) DebugPort() (int, bool) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.debugPort <= 0 {
|
|
return 0, false
|
|
}
|
|
return m.debugPort, true
|
|
}
|
|
|
|
func (m *browserProcessMonitor) SetDebugPort(port int) {
|
|
if port <= 0 {
|
|
return
|
|
}
|
|
|
|
m.mu.Lock()
|
|
if m.debugPort <= 0 {
|
|
m.debugPort = port
|
|
}
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
func (m *browserProcessMonitor) captureStderr() {
|
|
defer close(m.stderrDone)
|
|
|
|
if m.stderr == nil {
|
|
return
|
|
}
|
|
defer m.stderr.Close()
|
|
|
|
scanner := bufio.NewScanner(m.stderr)
|
|
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
m.stderrTail.Append(line)
|
|
if port, ok := parseBrowserDebugPortFromStderrLine(line); ok {
|
|
m.SetDebugPort(port)
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
m.stderrTail.Append(fmt.Sprintf("[stderr read error] %v", err))
|
|
}
|
|
}
|
|
|
|
func (m *browserProcessMonitor) waitForExit() {
|
|
err := m.cmd.Wait()
|
|
<-m.stderrDone
|
|
|
|
m.mu.Lock()
|
|
m.result = browserProcessExitResult{
|
|
Err: err,
|
|
StderrTail: m.stderrTail.String(),
|
|
}
|
|
m.mu.Unlock()
|
|
close(m.waitDone)
|
|
}
|
|
|
|
type tailTextBuffer struct {
|
|
maxLines int
|
|
maxBytes int
|
|
|
|
mu sync.Mutex
|
|
lines []string
|
|
totalBytes int
|
|
}
|
|
|
|
func newTailTextBuffer(maxLines int, maxBytes int) *tailTextBuffer {
|
|
if maxLines <= 0 {
|
|
maxLines = 1
|
|
}
|
|
if maxBytes <= 0 {
|
|
maxBytes = 1024
|
|
}
|
|
|
|
return &tailTextBuffer{
|
|
maxLines: maxLines,
|
|
maxBytes: maxBytes,
|
|
}
|
|
}
|
|
|
|
func (b *tailTextBuffer) Append(line string) {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" {
|
|
return
|
|
}
|
|
if len(trimmed) > b.maxBytes {
|
|
trimmed = trimmed[len(trimmed)-b.maxBytes:]
|
|
}
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
b.lines = append(b.lines, trimmed)
|
|
b.totalBytes += len(trimmed) + 1
|
|
for len(b.lines) > b.maxLines || b.totalBytes > b.maxBytes {
|
|
if len(b.lines) == 0 {
|
|
b.totalBytes = 0
|
|
break
|
|
}
|
|
b.totalBytes -= len(b.lines[0]) + 1
|
|
b.lines = b.lines[1:]
|
|
}
|
|
}
|
|
|
|
func (b *tailTextBuffer) String() string {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return strings.Join(b.lines, "\n")
|
|
}
|
|
|
|
func parseBrowserDebugPortFromStderrLine(line string) (int, bool) {
|
|
const marker = "DevTools listening on "
|
|
|
|
idx := strings.Index(line, marker)
|
|
if idx < 0 {
|
|
return 0, false
|
|
}
|
|
|
|
rawURL := strings.TrimSpace(line[idx+len(marker):])
|
|
if rawURL == "" {
|
|
return 0, false
|
|
}
|
|
|
|
parsed, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
|
|
port, err := strconv.Atoi(parsed.Port())
|
|
if err != nil || port <= 0 {
|
|
return 0, false
|
|
}
|
|
return port, true
|
|
}
|