Comments (7)
This is how I'm currently using this in the benchmarks:
errc := make(chan error, b.N*3)
nw := rpcc.NoWait(errc)
for _, err := range []error{
rpcc.Invoke(ctx, "Input.dispatchKeyEvent", down, nil, conn, nw),
rpcc.Invoke(ctx, "Input.dispatchKeyEvent", char, nil, conn, nw),
rpcc.Invoke(ctx, "Input.dispatchKeyEvent", up, nil, conn, nw),
} {
if err != nil {
b.Error(err)
}
}
for i := 0; i < b.N*3; i++ {
err := <-errc
if err != nil {
b.Error(err)
}
}
Not sure how friendly the above is though, easy to forget to allocate enough buffer space in the channel. Also, you lose reference to which command failed. A nicer API (in that regard) might look something like:
calls := []*rpcc.Call{
rpcc.NewCall("Input.dispatchKeyEvent", down, nil),
rpcc.NewCall("Input.dispatchKeyEvent", char, nil),
rpcc.NewCall("Input.dispatchKeyEvent", up, nil),
}
err := rpcc.Send(ctx, conn, calls...)
if err != nil {
return err
}
for _, call := range calls {
err := call.Wait()
if err != nil {
return err
}
}
The downside here is increasing the API surface with more types (Call
, NewCall
and the function Send
). Currently Call
is unexported (rpcCall
) which I find beneficial for internal usage. My thinking here is that having access to the call makes debugging / logging easier, e.g. finding out which call caused the error, etc. On the other hand, re-using a call is not possible, which might result in confusion and errors.
I'll keep thinking about this some more.
from cdp.
I should note that I haven't benchmarked this yet, but I'd imagine it should speed things up a bit. I can provide benchmarks if it'd help with the decision-making process :-)
from cdp.
Right now it would be possible to execute each command in a goroutine, but order isn't fully guaranteed, and this looks like a use-case where that guarantee is needed.
This kind of extension is something I've been keeping in mind, and we should be able to support it by extending e.g. Invoke with options:
func Invoke(ctx context.Context, method string, args, reply interface{}, conn *Conn, opts ...InvokeOpts) error { ... }
And to support invoking any command asynchronously, we'd change the interface slightly:
type Input interface {
// ...
DispatchKeyEvent(context.Context, *input.DispatchKeyEventArgs, opts ...rpcc.InvokeOpts) error
The reason I didn't introduce this yet is that I didn't have any options to put there :-P, but this will be fully backwards-compatible.
Usage (maybe?):
errc := make(chan error, 1)
_ = c.Input.DispatchKeyEvent(ctx, args, rpcc.InvokeAsync(errc))
// ...
err = <-errc
I'm undecided if the options should go in cdp
or used directly from rpcc
, perhaps cdp
is better so the user has less reason to deal with rpcc
.
PS. A benchmark would be much appreciated! :-)
from cdp.
Hey @matthewmueller, I've been giving this a lot of thought but I'm having a hard time finding a really good fit for this API change.
Do you have any ideas for how you would like to use it in practice?
I have some ideas that more or less could work, but I also want the behavior to be explicit and clear when reading the code.
// Option + Wait function.
nw := cdp.NoWait()
c.Page.Enable(ctx, nw)
c.Network.Enable(ctx, nil, nw)
c.DOM.Enable(ctx, nw)
shot, _ := c.Page.CaptureScreenshot(ctx, nil, nw) // Reply cannot be used yet.
err := cdp.Wait(ctx, nw)
// Safe to use `shot`.
// Async as a waitable + option.
async := cdp.Async()
c.Page.Enable(ctx, async.Option())
c.Network.Enable(ctx, nil, async.Option())
c.DOM.Enable(ctx, async.Option())
err := async.Wait(ctx) // or <-async.Done()
Or, running the async commands in their own scope, so to speak:
// NoWait provides non-blocking option, but blocks
// until all commands are completed.
var shot *page.CaptureScreenshotReply
err := cdp.NoWait(func(nw cdp.CommandOption) {
c.Page.Enable(ctx, nw)
c.Network.Enable(ctx, nil, nw)
c.DOM.Enable(ctx, nw)
shot, _ = c.Page.CaptureScreenshot(ctx, nil, nw)
})
// Separate client that does not wait for results.
err := cdp.NoWait(func(c *cdp.Client) {
c.Page.Enable(ctx)
c.Network.Enable(ctx, nil)
c.DOM.Enable(ctx)
})
// Async as a part of context.
err := cdp.NoWait(ctx, func(ctx context.Context) {
c.Page.Enable(ctx)
c.Network.Enable(ctx, nil)
c.DOM.Enable(ctx)
})
And finally, jokingly (or am I 😜😝):
// Full retard *JavaScript*.
async := cdp.AsyncContext(ctx)
p1 := c.Page.Enable(async) // Error, but p1.(*cdp.Promise) == OK ;P
p2 := c.Network.Enable(async, nil)
p3 := c.DOM.Enable(async)
err := cdp.Await(p1, p2, p3)
from cdp.
@mafredri Really appreciate the thought. I think you may be right that it may not warrant a large API change. The only use case I can really think of where this matters is keypresses, screencasting and scrolls and even then, only in certain cases (where you want to keep your event loop as speedy as possible).
It does make a difference though, I'm planning on doing something more official, but I was seeing around a 70ms improvement by just sending the keyboard event (to a local chrome process) and not waiting for the (empty) response.
I think one way forward is to just expose the raw RPCC API. The piece of the application I need this is in is quite performance sensitive, so I ended up writing a custom websocket implementation for that piece. I would have liked to just use the RPCC for this, but I didn't think it quite fit in the way I needed it. For everything else that I do, I won't need it to be performant like this and I'll definitely prefer just using the regular CDP API with the niceties of c.Input.DispatchKeyboardEvent()
😀
The interface that I ended up going with looks like this:
// Websocket interface
type Websocket interface {
// Async (wait for write acknowledgement then return)
Write(ctx context.Context, method string, params interface{}) error
// RPC style (wait for ID to come back)
Send(ctx context.Context, method string, params interface{}, reply interface{}) error
// Subscriber has the same API as CDP's events
Subscribe(ctx context.Context, method string) (*Subscriber, error)
// Wait returns when the websocket dies (after a cleanup is tried)
Wait() error
// Try to close gracefully
Close(error) error
}
I'm happy to share more of the implementation if you're interested. Like I mentioned, it's similar to CDP's RPCC, but a little too different for a PR haha.
from cdp.
The only use case I can really think of where this matters is keypresses, screencasting and scrolls and even then, only in certain cases (where you want to keep your event loop as speedy as possible).
I agree, I too see going async as a special use-case, but definitely one that should exist. In that regard, I think it's an OK first step to only support this via direct interaction with rpcc
, at least in the beginning.
Currently the domain commands (strings) are not exported (e.g. "Network.enable"
) but I'll add those to make it slightly nicer to work with rpcc
.
(Sidenote: I've also thought of turning these into types, e.g. network.Enable.Do(ctx, args)
. This would allow us to e.g. add a second method DoAsync
, but this introduces two ways, client & command, to do the same thing which I'm not very excited about.)
It does make a difference though, I'm planning on doing something more official, but I was seeing around a 70ms improvement by just sending the keyboard event (to a local chrome process) and not waiting for the (empty) response.
Wow, that's not insignificant. This piqued my interest, I might have to write some benchmarks for rpcc
myself 😆.
I would have liked to just use the RPCC for this, but I didn't think it quite fit in the way I needed it.
Could you expand on this a bit? I would love to see if we can match the offerings of rpcc
to your requirements 😁. Is it more than the requirement to execute commands without waiting for the result?
I'm happy to share more of the implementation if you're interested. Like I mentioned, it's similar to CDP's RPCC, but a little too different for a PR haha.
Please do, I'm very curious if this can be aligned with rpcc
.
PS. Speaking of performance considerations, I'm thinking of evaluating gobwas/ws as a replacement for gorilla/websocket
.
from cdp.
There is indeed a huge difference, I wrote some benchmarks and ran some tests. Both the sync and async version are executing three Input.dispatchKeyEvent
commands over rpcc
(rawKeyDown
, char
and keyUp
) to produce a newline.
Page: about:blank
, no content:
$ go test -bench=Speed -run=Bench -benchtime 2s -v
BenchmarkRpccSpeed/Sync-4 2000 1309191 ns/op 1129 B/op 33 allocs/op
BenchmarkRpccSpeed/Async-4 10000 329931 ns/op 1101 B/op 27 allocs/op
PASS
ok github.com/mafredri/cdp 6.132s
Page about:blank
, focused <textarea>
:
The difference here is 102
(sync) vs 1102
(async) newlines inside the <textarea>
.
$ go test -bench=Speed/Sync -run=Bench -benchtime 0.5s -v
BenchmarkRpccSpeed/Sync-4 100 6668981 ns/op 1152 B/op 33 allocs/op
PASS
ok github.com/mafredri/cdp 0.699s
$ go test -bench=Speed/Async -run=Bench -benchtime 0.5s -v
BenchmarkRpccSpeed/Async-4 1000 1787161 ns/op 1001 B/op 27 allocs/op
PASS
ok github.com/mafredri/cdp 1.900s
from cdp.
Related Issues (20)
- rpcc.Conn.Close returns an error if the target is already closed HOT 3
- session.Manager does not properly close if connection was closed with non-nil error HOT 3
- Defunct issue with R1Soft CDP HOT 1
- HTTP error / timeout when printing certain images in html HOT 1
- BrowserContextID is not accessible anymore HOT 2
- Is possible to wait for an Ajax request? HOT 2
- Lifecycle events not fire in order and this ones are fired twice HOT 2
- RequestIntercepted can't set respond HOT 2
- session.Manager.watch may not finish when unexpected error happens on websocket
- Non-wrapped error HOT 3
- How to optionally download files while also dealing with regular web pages HOT 6
- Connection for every thread HOT 2
- Default example not working dial tcp [::1]:9222: connectex HOT 1
- Rpc command not found HOT 4
- Attempted to use with node, too many chrome assumptions? HOT 4
- Problem interacting with the remote debugging host over HTTP HOT 5
- Breaking changes in node js debugging in v0.34.0 HOT 4
- Remote Pipe
- resolving URL from scriptID in pausedClient HOT 2
- Default example doesn't work - Page.Enable wasn't found HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cdp.