支持配置远程的Chrome调试端口(例如docker)
更好的支持按键处理
This commit is contained in:
parent
93f1a05ef4
commit
8b52007c65
50
chrome.go
50
chrome.go
@ -8,13 +8,16 @@ import (
|
|||||||
"apigo.cc/gojs/goja"
|
"apigo.cc/gojs/goja"
|
||||||
"github.com/go-rod/rod"
|
"github.com/go-rod/rod"
|
||||||
"github.com/go-rod/rod/lib/launcher"
|
"github.com/go-rod/rod/lib/launcher"
|
||||||
|
"github.com/go-rod/rod/lib/launcher/flags"
|
||||||
"github.com/ssgo/u"
|
"github.com/ssgo/u"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chrome struct {
|
type Chrome struct {
|
||||||
id string
|
id string
|
||||||
launcher *launcher.Launcher
|
chromeURL string
|
||||||
browser *rod.Browser
|
chromePath string
|
||||||
|
launcher *launcher.Launcher
|
||||||
|
browser *rod.Browser
|
||||||
}
|
}
|
||||||
|
|
||||||
var chromes = map[string]*Chrome{}
|
var chromes = map[string]*Chrome{}
|
||||||
@ -23,7 +26,8 @@ var chromesLock sync.Mutex
|
|||||||
func (ch *Chrome) Close(vm *goja.Runtime) {
|
func (ch *Chrome) Close(vm *goja.Runtime) {
|
||||||
logger := gojs.GetLogger(vm)
|
logger := gojs.GetLogger(vm)
|
||||||
if ch.browser != nil {
|
if ch.browser != nil {
|
||||||
logger.Info("关闭本地 Chrome 浏览器", "id", ch.id)
|
ver, _ := ch.browser.Version()
|
||||||
|
logger.Info("关闭Chrome浏览器", "id", ch.id, "browser", ver.Product, "userAgent", ver.UserAgent, "chromeURL", ch.chromeURL, "chromePath", ch.chromePath)
|
||||||
ch.browser.Close()
|
ch.browser.Close()
|
||||||
ch.browser = nil
|
ch.browser = nil
|
||||||
}
|
}
|
||||||
@ -44,7 +48,7 @@ func CloseAllChrome(vm *goja.Runtime) {
|
|||||||
ch.Close(vm)
|
ch.Close(vm)
|
||||||
}
|
}
|
||||||
logger := gojs.GetLogger(vm)
|
logger := gojs.GetLogger(vm)
|
||||||
logger.Info("关闭所有 Chrome 浏览器", "count", n)
|
logger.Info("关闭所有未主动关闭的Chrome浏览器", "count", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,26 +57,48 @@ func StartChrome(showWindow *bool, vm *goja.Runtime) (*Chrome, error) {
|
|||||||
ch := &Chrome{}
|
ch := &Chrome{}
|
||||||
ch.id = u.UniqueId()
|
ch.id = u.UniqueId()
|
||||||
ch.browser = rod.New()
|
ch.browser = rod.New()
|
||||||
if localBrowserPath, hasLocalBrowser := launcher.LookPath(); hasLocalBrowser {
|
ch.chromeURL = conf.ChromeURL
|
||||||
ch.launcher = launcher.New().Bin(localBrowserPath).Headless(!u.Bool(showWindow)).Set("no-sandbox").Set("disable-gpu").Set("disable-dev-shm-usage").Set("single-process")
|
if conf.ChromeURL == "" {
|
||||||
|
// 使用本地Chrome
|
||||||
|
ch.launcher = launcher.New()
|
||||||
|
if localBrowserPath, hasLocalBrowser := launcher.LookPath(); hasLocalBrowser {
|
||||||
|
ch.launcher.Bin(localBrowserPath)
|
||||||
|
ch.chromePath = localBrowserPath
|
||||||
|
}
|
||||||
|
// "--headless=new", "--no-sandbox", "--disable-dev-shm-usage", "--hide-scrollbars", "--font-render-hinting=none", "--disable-blink-features=AutomationControlled", "--disable-infobars", "--lang=zh-CN,zh", "--disable-extensions", "--disable-gpu", "--use-gl=swiftshader", "--ignore-gpu-blocklist", "--use-angle=swiftshader", "--disable-features=Translate"
|
||||||
|
ch.launcher.Headless(!u.Bool(showWindow)).Set("disable-dev-shm-usage").Set("single-process").Set("disable-blink-features", "AutomationControlled")
|
||||||
|
ch.launcher.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0")
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
ch.launcher.Set("disable-setuid-sandbox")
|
ch.launcher.Set("disable-setuid-sandbox")
|
||||||
case "windows":
|
case "windows":
|
||||||
ch.launcher.Set("disable-features=RendererCodeIntegrity")
|
ch.launcher.Set("disable-features=RendererCodeIntegrity")
|
||||||
}
|
}
|
||||||
localChromeURL, err := ch.launcher.Launch()
|
for _, opt := range conf.ChromeOption {
|
||||||
if err != nil {
|
a := u.SplitTrimN(opt, "=", 2)
|
||||||
|
if len(a) == 2 {
|
||||||
|
ch.launcher.Set(flags.Flag(a[0]), a[1])
|
||||||
|
} else {
|
||||||
|
ch.launcher.Set(flags.Flag(opt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if localChromeURL, err := ch.launcher.Launch(); err != nil {
|
||||||
ch.Close(vm)
|
ch.Close(vm)
|
||||||
return nil, gojs.Err(err)
|
return nil, gojs.Err(err)
|
||||||
|
} else {
|
||||||
|
ch.chromeURL = localChromeURL
|
||||||
|
if ch.chromePath == "" {
|
||||||
|
ch.chromePath = ch.launcher.Get(flags.Bin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.Info("启动本地 Chrome 浏览器", "id", ch.id, "url", localChromeURL, "path", localBrowserPath)
|
|
||||||
ch.browser.ControlURL(localChromeURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ch.browser.ControlURL(ch.chromeURL)
|
||||||
if err := ch.browser.Connect(); err != nil {
|
if err := ch.browser.Connect(); err != nil {
|
||||||
ch.Close(vm)
|
|
||||||
return nil, gojs.Err(err)
|
return nil, gojs.Err(err)
|
||||||
}
|
}
|
||||||
|
ver, _ := ch.browser.Version()
|
||||||
|
logger.Info("启动Chrome浏览器", "id", ch.id, "browser", ver.Product, "userAgent", ver.UserAgent, "chromeURL", ch.chromeURL, "chromePath", ch.chromePath)
|
||||||
|
|
||||||
chromesLock.Lock()
|
chromesLock.Lock()
|
||||||
chromes[ch.id] = ch
|
chromes[ch.id] = ch
|
||||||
|
5
http.go
5
http.go
@ -33,8 +33,9 @@ var defaultHttp = &Http{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var conf = struct {
|
var conf = struct {
|
||||||
Timeout int
|
Timeout int
|
||||||
ChromePath string
|
ChromeURL string
|
||||||
|
ChromeOption []string
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
302
page.go
302
page.go
@ -60,7 +60,7 @@ func (pg *Page) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pg *Page) GetResList() ([]Resource, error) {
|
func (pg *Page) GetResList() ([]Resource, error) {
|
||||||
r, err := proto.PageGetResourceTree{}.Call(pg.page)
|
r, err := proto.PageGetResourceTree{}.Call(pg.longTimeoutPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gojs.Err(err)
|
return nil, gojs.Err(err)
|
||||||
}
|
}
|
||||||
@ -381,9 +381,267 @@ func (pg *Page) Frame(selector string) (*Page, error) {
|
|||||||
return &Page{page: p}, nil
|
return &Page{page: p}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page级
|
var isControlKey = map[string]bool{
|
||||||
func (pg *Page) Press(key string) error {
|
"ShiftLeft": true,
|
||||||
return gojs.Err(pg.page.Keyboard.Press(input.Key(key[0])))
|
"ShiftRight": true,
|
||||||
|
"Shift": true,
|
||||||
|
"ControlLeft": true,
|
||||||
|
"ControlRight": true,
|
||||||
|
"Ctrl": true,
|
||||||
|
"Control": true,
|
||||||
|
"MetaLeft": true,
|
||||||
|
"MetaRight": true,
|
||||||
|
"Meta": true,
|
||||||
|
"Cmd": true,
|
||||||
|
"Windows": true,
|
||||||
|
"Win": true,
|
||||||
|
"AltLeft": true,
|
||||||
|
"AltRight": true,
|
||||||
|
"AltGraph": true,
|
||||||
|
"Option": true,
|
||||||
|
"Alt": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var KeyMap = map[string]input.Key{
|
||||||
|
"Escape": input.Escape,
|
||||||
|
"Esc": input.Escape,
|
||||||
|
"F1": input.F1,
|
||||||
|
"F2": input.F2,
|
||||||
|
"F3": input.F3,
|
||||||
|
"F4": input.F4,
|
||||||
|
"F5": input.F5,
|
||||||
|
"F6": input.F6,
|
||||||
|
"F7": input.F7,
|
||||||
|
"F8": input.F8,
|
||||||
|
"F9": input.F9,
|
||||||
|
"F10": input.F10,
|
||||||
|
"F11": input.F11,
|
||||||
|
"F12": input.F12,
|
||||||
|
"Backquote": input.Backquote,
|
||||||
|
"1": input.Digit1,
|
||||||
|
"2": input.Digit2,
|
||||||
|
"3": input.Digit3,
|
||||||
|
"4": input.Digit4,
|
||||||
|
"5": input.Digit5,
|
||||||
|
"6": input.Digit6,
|
||||||
|
"7": input.Digit7,
|
||||||
|
"8": input.Digit8,
|
||||||
|
"9": input.Digit9,
|
||||||
|
"0": input.Digit0,
|
||||||
|
"Digit1": input.Digit1,
|
||||||
|
"Digit2": input.Digit2,
|
||||||
|
"Digit3": input.Digit3,
|
||||||
|
"Digit4": input.Digit4,
|
||||||
|
"Digit5": input.Digit5,
|
||||||
|
"Digit6": input.Digit6,
|
||||||
|
"Digit7": input.Digit7,
|
||||||
|
"Digit8": input.Digit8,
|
||||||
|
"Digit9": input.Digit9,
|
||||||
|
"Digit0": input.Digit0,
|
||||||
|
"Minus": input.Minus,
|
||||||
|
"Equal": input.Equal,
|
||||||
|
"Backslash": input.Backslash,
|
||||||
|
"Backspace": input.Backspace,
|
||||||
|
"Tab": input.Tab,
|
||||||
|
"KeyQ": input.KeyQ,
|
||||||
|
"Q": input.KeyQ,
|
||||||
|
"KeyW": input.KeyW,
|
||||||
|
"W": input.KeyW,
|
||||||
|
"KeyE": input.KeyE,
|
||||||
|
"E": input.KeyE,
|
||||||
|
"KeyR": input.KeyR,
|
||||||
|
"R": input.KeyR,
|
||||||
|
"KeyT": input.KeyT,
|
||||||
|
"T": input.KeyT,
|
||||||
|
"KeyY": input.KeyY,
|
||||||
|
"Y": input.KeyY,
|
||||||
|
"KeyU": input.KeyU,
|
||||||
|
"U": input.KeyU,
|
||||||
|
"KeyI": input.KeyI,
|
||||||
|
"I": input.KeyI,
|
||||||
|
"KeyO": input.KeyO,
|
||||||
|
"O": input.KeyO,
|
||||||
|
"KeyP": input.KeyP,
|
||||||
|
"P": input.KeyP,
|
||||||
|
"BracketLeft": input.BracketLeft,
|
||||||
|
"BracketRight": input.BracketRight,
|
||||||
|
"CapsLock": input.CapsLock,
|
||||||
|
"KeyA": input.KeyA,
|
||||||
|
"A": input.KeyA,
|
||||||
|
"KeyS": input.KeyS,
|
||||||
|
"S": input.KeyS,
|
||||||
|
"KeyD": input.KeyD,
|
||||||
|
"D": input.KeyD,
|
||||||
|
"KeyF": input.KeyF,
|
||||||
|
"F": input.KeyF,
|
||||||
|
"KeyG": input.KeyG,
|
||||||
|
"G": input.KeyG,
|
||||||
|
"KeyH": input.KeyH,
|
||||||
|
"H": input.KeyH,
|
||||||
|
"KeyJ": input.KeyJ,
|
||||||
|
"J": input.KeyJ,
|
||||||
|
"KeyK": input.KeyK,
|
||||||
|
"K": input.KeyK,
|
||||||
|
"KeyL": input.KeyL,
|
||||||
|
"L": input.KeyL,
|
||||||
|
"Semicolon": input.Semicolon,
|
||||||
|
"Quote": input.Quote,
|
||||||
|
"Enter": input.Enter,
|
||||||
|
"ShiftLeft": input.ShiftLeft,
|
||||||
|
"KeyZ": input.KeyZ,
|
||||||
|
"Z": input.KeyZ,
|
||||||
|
"KeyX": input.KeyX,
|
||||||
|
"X": input.KeyX,
|
||||||
|
"KeyC": input.KeyC,
|
||||||
|
"C": input.KeyC,
|
||||||
|
"KeyV": input.KeyV,
|
||||||
|
"V": input.KeyV,
|
||||||
|
"KeyB": input.KeyB,
|
||||||
|
"B": input.KeyB,
|
||||||
|
"KeyN": input.KeyN,
|
||||||
|
"N": input.KeyN,
|
||||||
|
"KeyM": input.KeyM,
|
||||||
|
"M": input.KeyM,
|
||||||
|
"Comma": input.Comma,
|
||||||
|
"Period": input.Period,
|
||||||
|
"Slash": input.Slash,
|
||||||
|
"ShiftRight": input.ShiftRight,
|
||||||
|
"ControlLeft": input.ControlLeft,
|
||||||
|
"MetaLeft": input.MetaLeft,
|
||||||
|
"AltLeft": input.AltLeft,
|
||||||
|
"Space": input.Space,
|
||||||
|
" ": input.Space,
|
||||||
|
"AltRight": input.AltRight,
|
||||||
|
"AltGraph": input.AltGraph,
|
||||||
|
"MetaRight": input.MetaRight,
|
||||||
|
"ContextMenu": input.ContextMenu,
|
||||||
|
"ControlRight": input.ControlRight,
|
||||||
|
"PrintScreen": input.PrintScreen,
|
||||||
|
"ScrollLock": input.ScrollLock,
|
||||||
|
"Pause": input.Pause,
|
||||||
|
"PageUp": input.PageUp,
|
||||||
|
"PageDown": input.PageDown,
|
||||||
|
"Insert": input.Insert,
|
||||||
|
"Delete": input.Delete,
|
||||||
|
"Home": input.Home,
|
||||||
|
"End": input.End,
|
||||||
|
"ArrowLeft": input.ArrowLeft,
|
||||||
|
"ArrowUp": input.ArrowUp,
|
||||||
|
"ArrowRight": input.ArrowRight,
|
||||||
|
"ArrowDown": input.ArrowDown,
|
||||||
|
"Left": input.ArrowLeft,
|
||||||
|
"Up": input.ArrowUp,
|
||||||
|
"Right": input.ArrowRight,
|
||||||
|
"Down": input.ArrowDown,
|
||||||
|
"NumLock": input.NumLock,
|
||||||
|
"NumpadDivide": input.NumpadDivide,
|
||||||
|
"NumpadMultiply": input.NumpadMultiply,
|
||||||
|
"NumpadSubtract": input.NumpadSubtract,
|
||||||
|
"Numpad7": input.Numpad7,
|
||||||
|
"Numpad8": input.Numpad8,
|
||||||
|
"Numpad9": input.Numpad9,
|
||||||
|
"Numpad4": input.Numpad4,
|
||||||
|
"Numpad5": input.Numpad5,
|
||||||
|
"Numpad6": input.Numpad6,
|
||||||
|
"NumpadAdd": input.NumpadAdd,
|
||||||
|
"Numpad1": input.Numpad1,
|
||||||
|
"Numpad2": input.Numpad2,
|
||||||
|
"Numpad3": input.Numpad3,
|
||||||
|
"Numpad0": input.Numpad0,
|
||||||
|
"NumpadDecimal": input.NumpadDecimal,
|
||||||
|
"NumpadEnter": input.NumpadEnter,
|
||||||
|
"NumDivide": input.NumpadDivide,
|
||||||
|
"NumMultiply": input.NumpadMultiply,
|
||||||
|
"NumSubtract": input.NumpadSubtract,
|
||||||
|
"Num7": input.Numpad7,
|
||||||
|
"Num8": input.Numpad8,
|
||||||
|
"Num9": input.Numpad9,
|
||||||
|
"Num4": input.Numpad4,
|
||||||
|
"Num5": input.Numpad5,
|
||||||
|
"Num6": input.Numpad6,
|
||||||
|
"NumAdd": input.NumpadAdd,
|
||||||
|
"Num1": input.Numpad1,
|
||||||
|
"Num2": input.Numpad2,
|
||||||
|
"Num3": input.Numpad3,
|
||||||
|
"Num0": input.Numpad0,
|
||||||
|
"NumDecimal": input.NumpadDecimal,
|
||||||
|
"NumEnter": input.NumpadEnter,
|
||||||
|
"Ctrl": input.ControlLeft,
|
||||||
|
"Control": input.ControlLeft,
|
||||||
|
"Cmd": input.MetaLeft,
|
||||||
|
"Windows": input.MetaLeft,
|
||||||
|
"Meta": input.MetaLeft,
|
||||||
|
"Win": input.MetaLeft,
|
||||||
|
"Option": input.AltLeft,
|
||||||
|
"Alt": input.AltLeft,
|
||||||
|
",": input.Comma,
|
||||||
|
".": input.Period,
|
||||||
|
"/": input.Slash,
|
||||||
|
";": input.Semicolon,
|
||||||
|
"'": input.Quote,
|
||||||
|
"[": input.BracketLeft,
|
||||||
|
"]": input.BracketRight,
|
||||||
|
"\\": input.Backslash,
|
||||||
|
"-": input.Minus,
|
||||||
|
"=": input.Equal,
|
||||||
|
"`": input.Backquote,
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyFilter(controlKey bool, keys ...string) []string {
|
||||||
|
var filteredKeys []string
|
||||||
|
for _, key := range keys {
|
||||||
|
if (controlKey && isControlKey[key]) || (!controlKey && !isControlKey[key]) {
|
||||||
|
filteredKeys = append(filteredKeys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyAction(page *rod.Page, action string, keys ...string) error {
|
||||||
|
for _, key := range keys {
|
||||||
|
if k, ok := KeyMap[key]; ok {
|
||||||
|
var err error
|
||||||
|
switch action {
|
||||||
|
case "press":
|
||||||
|
err = page.Keyboard.Press(k)
|
||||||
|
case "release":
|
||||||
|
err = page.Keyboard.Release(k)
|
||||||
|
case "type":
|
||||||
|
err = page.Keyboard.Type(k)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return gojs.Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg *Page) KeyPress(keys ...string) error {
|
||||||
|
return keyAction(pg.page, "press", keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg *Page) KeyRelease(keys ...string) error {
|
||||||
|
return keyAction(pg.page, "release", keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg *Page) KeyType(keys ...string) error {
|
||||||
|
return keyAction(pg.page, "type", keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pg *Page) Key(keys ...string) error {
|
||||||
|
controlKeys := keyFilter(true, keys...)
|
||||||
|
// 按下所有控制键
|
||||||
|
err := keyAction(pg.page, "press", controlKeys...)
|
||||||
|
if err == nil {
|
||||||
|
// 输入所有非控制键
|
||||||
|
err = keyAction(pg.page, "type", keyFilter(false, keys...)...)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// 释放所有控制键
|
||||||
|
err = keyAction(pg.page, "release", controlKeys...)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pg *Page) SetUserAgent(ua string) error {
|
func (pg *Page) SetUserAgent(ua string) error {
|
||||||
@ -1092,11 +1350,43 @@ func (el *Element) WaitStable(ms *int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 元素级
|
// 元素级
|
||||||
func (el *Element) Press(key string) error {
|
func (el *Element) KeyPress(keys ...string) error {
|
||||||
if err := el.element.Focus(); err != nil {
|
if err := el.element.Focus(); err != nil {
|
||||||
return gojs.Err(err)
|
return gojs.Err(err)
|
||||||
}
|
}
|
||||||
return gojs.Err(el.element.Page().Keyboard.Press(input.Key(key[0])))
|
return keyAction(el.page, "press", keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *Element) KeyRelease(keys ...string) error {
|
||||||
|
if err := el.element.Focus(); err != nil {
|
||||||
|
return gojs.Err(err)
|
||||||
|
}
|
||||||
|
return keyAction(el.page, "release", keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *Element) KeyType(keys ...string) error {
|
||||||
|
if err := el.element.Focus(); err != nil {
|
||||||
|
return gojs.Err(err)
|
||||||
|
}
|
||||||
|
return keyAction(el.page, "type", keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *Element) Key(keys ...string) error {
|
||||||
|
if err := el.element.Focus(); err != nil {
|
||||||
|
return gojs.Err(err)
|
||||||
|
}
|
||||||
|
controlKeys := keyFilter(true, keys...)
|
||||||
|
// 按下所有控制键
|
||||||
|
err := keyAction(el.page, "press", controlKeys...)
|
||||||
|
if err == nil {
|
||||||
|
// 输入所有非控制键
|
||||||
|
err = keyAction(el.page, "type", keyFilter(false, keys...)...)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// 释放所有控制键
|
||||||
|
err = keyAction(el.page, "release", controlKeys...)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible 判断元素是否可见
|
// IsVisible 判断元素是否可见
|
||||||
|
Loading…
x
Reference in New Issue
Block a user