Skip to content
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 an initEvents public function with a Browser structure #1046

Open
sxmxta opened this issue Apr 26, 2024 · 5 comments
Open

Add an initEvents public function with a Browser structure #1046

sxmxta opened this issue Apr 26, 2024 · 5 comments
Labels
enhance New feature or request

Comments

@sxmxta
Copy link

sxmxta commented Apr 26, 2024

Hello
I am the author of the Energy open-source framework
This framework was developed by Go based on CEF
Someone requested to use Energy to create an automated tool for visual graphic control, and gave some suggestions, including Rod
Afterwards, I researched rod and found that it is possible to use the CEF API to directly send devtools protocol methods without the need for websocket or opening the devtools remote debugging port.
Later, I tried to modify the message sending and receiving events and processing of rod, and changed it to CEF API sending messages and CEF API callback events receiving messages. This is completely feasible. And it won't invade rod
But after modification, it was found that the simplest solution is to provide a public InitEvents function in the borwser structure of rod, which is currently non-public

browser.go
Before modification

func (b *Browser) initEvents() {
	ctx, cancel := context.WithCancel(b.ctx)
	b.event = goob.New(ctx)
	event := b.client.Event()

	go func() {
		defer cancel()
		for e := range event {
			b.event.Publish(&Message{
				SessionID: proto.TargetSessionID(e.SessionID),
				Method:    e.Method,
				lock:      &sync.Mutex{},
				data:      e.Params,
			})
		}
	}()
}

After modification

func (b *Browser) InitEvents(){
	if b.event == nil {
		b.initEvents()
	}
}

func (b *Browser) initEvents() {
	ctx, cancel := context.WithCancel(b.ctx)
	b.event = goob.New(ctx)
	event := b.client.Event()

	go func() {
		defer cancel()
		for e := range event {
			b.event.Publish(&Message{
				SessionID: proto.TargetSessionID(e.SessionID),
				Method:    e.Method,
				lock:      &sync.Mutex{},
				data:      e.Params,
			})
		}
	}()
}

After creation, fully utilize the functions provided by rod

@sxmxta sxmxta added the enhance New feature or request label Apr 26, 2024
Copy link

Please add a valid Rod Version: v0.0.0 to your issue. Current version is v0.115.0

generated by check-issue

@sxmxta sxmxta closed this as completed Apr 26, 2024
@sxmxta sxmxta reopened this Apr 26, 2024
@sxmxta
Copy link
Author

sxmxta commented Apr 26, 2024

Try using browser Connect () initializes InitEvents, but it blocks the UI thread.

There won't be any problem with InitEvents directly

@ysmood
Copy link
Collaborator

ysmood commented Apr 26, 2024

How about use the cdp directly? Look at the code client.Event():

func ExampleClient() {
ctx := context.Background()
// launch a browser
url := launcher.New().MustLaunch()
// create a controller
client := cdp.New().Start(cdp.MustConnectWS(url))
go func() {
for range client.Event() {
// you must consume the events
utils.Noop()
}
}()
// Such as call this endpoint on the api doc:
// https://chromedevtools.github.io/devtools-protocol/tot/Page#method-navigate
// This will create a new tab and navigate to the test.com
res, err := client.Call(ctx, "", "Target.createTarget", map[string]string{
"url": "http://test.com",
})
utils.E(err)
fmt.Println(len(gson.New(res).Get("targetId").Str()))
// close browser by using the proto lib to encode json
_ = proto.BrowserClose{}.Call(client)
// Output: 32
}

@sxmxta
Copy link
Author

sxmxta commented Apr 26, 2024

How about use the cdp directly? Look at the code client.Event():

func ExampleClient() {
ctx := context.Background()
// launch a browser
url := launcher.New().MustLaunch()
// create a controller
client := cdp.New().Start(cdp.MustConnectWS(url))
go func() {
for range client.Event() {
// you must consume the events
utils.Noop()
}
}()
// Such as call this endpoint on the api doc:
// https://chromedevtools.github.io/devtools-protocol/tot/Page#method-navigate
// This will create a new tab and navigate to the test.com
res, err := client.Call(ctx, "", "Target.createTarget", map[string]string{
"url": "http://test.com",
})
utils.E(err)
fmt.Println(len(gson.New(res).Get("targetId").Str()))
// close browser by using the proto lib to encode json
_ = proto.BrowserClose{}.Call(client)
// Output: 32
}

Hello, this method is not suitable.
I have switched to another method, using "go Browser.Connect()" instead of initEvents().

Because it directly passes a []byte, I need to know the id and method, and I don't want to deserialize again to obtain them. I am using the Chromium().ExecuteDevToolsMethod(messageId int32, method string, dictionaryValue *ICefDictionaryValue) function,
so I have replaced it with "go Browser.Connect()". Let's try this approach for now.

func (m *Energy) CreateBrowser() {
	if !m.created {
		m.created = true
		go m.rodBrowser.Connect()
		//m.rodBrowser.InitEvents()
		if m.window != nil {
			// window
			if m.window.IsLCL() {
				cef.RunOnMainThread(func() {
					m.window.Show()
				})
			} else {
				m.window.Show()
			}
		} else if m.chromiumBrowser != nil {
			m.chromiumBrowser.CreateBrowser()
		}
	}
}


// Call a method and wait for its response.
func (m *Energy) Call(ctx context.Context, sessionID, method string, params interface{}) ([]byte, error) {
	req := &cdp.Request{
		ID:        int(atomic.AddUint64(&m.count, 1)),
		SessionID: sessionID,
		Method:    method,
		Params:    params,
	}
	m.logger.Println(req)
	data, err := json.Marshal(params)
	utils.E(err)
	done := make(chan Result)
	once := sync.Once{}
	m.pending.Store(req.ID, func(res Result) {
		once.Do(func() {
			select {
			case <-ctx.Done():
			case done <- res:
			}
		})
	})
	defer m.pending.Delete(req.ID)
	//m.logger.Println("send-data:", string(data))
	//m.chromium.SendDevToolsMessage(string(data))// Linux cannot be used
	dict := JSONParse(data)
	m.ChromiumBrowser().Chromium().ExecuteDevToolsMethod(int32(req.ID), req.Method, dict)
	defer dict.Free()
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	case res := <-done:
		return res.Msg, res.Err
	}
}

@ysmood
Copy link
Collaborator

ysmood commented May 5, 2024

You don't have to marshal it manually, you can use the proto lib:

// Package main ...
package main

import (
	"fmt"

	"github.com/go-rod/rod/lib/cdp"
	"github.com/go-rod/rod/lib/launcher"
	"github.com/go-rod/rod/lib/proto"
	"github.com/go-rod/rod/lib/utils"
)

func main() {
	// launch a browser
	url := launcher.New().MustLaunch()

	// create a controller
	client := cdp.New().Start(cdp.MustConnectWS(url))

	go func() {
		for range client.Event() {
			// you must consume the events
			utils.Noop()
		}
	}()

	res, err := proto.TargetCreateTarget{URL: "http://test.com"}.Call(client)
	if err != nil {
		panic(err)
	}

	fmt.Println(res.TargetID)

	_ = proto.BrowserClose{}.Call(client)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhance New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants