$30 off During Our Annual Pro Sale. View Details »

Chromium Embedded Framework - Go + JS

Chromium Embedded Framework - Go + JS

A talk I gave at Brooklyn JS #19 (May 21, 2015) about using Chromium Embedded Framework to connect Go and JS applications

The video of the demo is available here: https://www.youtube.com/watch?v=xR4dSiMEe-8
Here are the presenter notes which will give more context:

brooklyn js talk notes


I'm @aq

5 facts about me

grew up less then a mile from here which leads me to
now repping the HV and live in a 350 year old house, save the date Oct 23rd: Catskills Conf is coming
3) Used to be the Chief Taco Officer at Paperless Post, now I'm the Chief Scientist and get to work on things
like I'm about to show you
4) Also been doing JS/Ruby/Go Perfomance and Organization consulting so hire me
5) I host a weekly podcast about Music, Food and Programming with MRB called Beats, Rye & Types (check out the episode with Jed)

A story of a yak shave

Started with the need to take a JS application that relied on canvas to turn data into images. It's important to note that we actually needed a full browser and not just JS - we needed canvas and a way to get that rendered buffer from canvas

Explored some node options

node-canvas - not precise or 1:1 poor font handling
node-webkit - almost there, but more geared to GUI applications

I really wanted to figure out a way to do this in Go because I've been writing a lot of Go and wanted to be able to reuse the communication, monitoring and tooling that I had already created.

Explored some existing webkit wrappers but nothing really worked well, until I found Chromium embedded framework (CEF) and cef2go.

cef2go takes advantage of the fact that cef exposes a simple C api/bindings and go has amazing support for embedding utilizing c code through cef2go

cef2go was in a sort of bad state, so I spent a bunch of time cleaning it up and getting it working on linux

eventually I got to a place where I could boot up a "browser" and execute JS in it. The cool thing is that because of Go's concurrency model it took no extra work to just loop and create many browsers. In this case, the idea would be 1-per CPU.

The next part was getting data back from JS into the go process and back to the client of the application.

ExecuteJavascript is async so you can't just get the return value.

The "normal" way to do this was to set up an http server and handle AJAX post calls from the JS. This actually was pretty easy, I combined the cef boot + http setup in a little wrapper called chrome farm. Created a little library to manage this called chromefarm.

It looked something like:

Go code
JS code

But i knew there had to be SOME way to call directly back to go from JS.

Turns out, through some roundabout ways, you can create JS methods bound to the V8 instance in CEF that are backed by NATIVE methods.
Native in this case means a C function that is compiled alongside CEF, but using the magic of CGO that method can actually be backed by a Go function.


Go => C => JS => C => GO

Creating these native methods was sort of a pain though, so using the property of JS that allows methods to have variable argument arity we can bind a single method as a "native" method that can then call any "registered" method on the go side.

In the JS this looks like:

cef.callback('myGoCallback', "any", "type", 0, "args", true)

And on the go side we just have to register what we want to do with this callback:


we also have to translate the individual types to the native types we want in Go.

But this actually works, and means we can pass data back and forth from a running browser to Go.

Quick demo:


For us it enabled a really stable and fast platform for turning canvas based renders into images.

But ton's of possibilities, it could bring the power of anything you want to do in a modern chrome browser to go and really the other way around, too. You could have an app that's primarily JS but run in a cef shell and have access to all the powerful system level (and concurrent code) that go can provide.

I mean the real reason is because its an epic and beautiful hack.

Aaron Quint

May 21, 2015

More Decks by Aaron Quint

Other Decks in Programming


  1. I’m @aq

    View Slide

  2. 5 facts about @aq

    View Slide

  3. View Slide

  4. Follow
    Oct 23-25

    View Slide

  5. View Slide

  6. Ruby, JS, Go Performance etc
    but you can hire me!

    View Slide

  7. View Slide

  8. The Story of
    a Yak Shave

    View Slide

  9. View Slide

  10. Some node.js options:

    View Slide

  11. not precise or 1:1; poor font handling

    View Slide

  12. almost there, but more geared to GUI

    View Slide

  13. View Slide

  14. Have the ability to reuse existing code and
    knowledge about production
    I wanted to see if go
    was possible

    View Slide

  15. aka Chromium Embedded Framework
    Enter and cef2go

    View Slide

  16. chromium
    C & C++ API
    cef2go (cgo)

    View Slide

  17. C & C++ API
    your go app
    your js app
    Service Request/
    JS Exec/

    View Slide

  18. 1 for i := 0; i < 3; i++ {
    2 browser := cef.CreateBrowser(browserSettings, url, true)
    3 browser.ExecuteJavaScript("console.log('we outchea');", "sup.js", 1)
    4 }

    View Slide

  19. 3 browser.ExecuteJavaScript("console.log('we outchea');", "sup.js", 1)

    View Slide

  20. github.com/paperlesspost/chromefarm
    Solve it with AJAX

    View Slide

  21. 2
    10 func main() {
    12 farm := chromefarm.NewFarm()
    14 // This serves the postback.html for the browser to load
    15 http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
    17 http.ServeFile(res, req, "postback.html")
    18 })
    19 // this handles the callback from JS/AJAX
    20 http.HandleFunc("/callback", func(res http.ResponseWriter, req *http.Request) {
    21 message := req.FormValue("message")
    22 log.Println("Received message from browser: %s", message)
    25 })
    26 farm.OnReady(func() {
    29 for i := 0; i < 2; i++ {
    30 browser := farm.CreateBrowser(url)
    31 browser.ExecuteJavaScript(fmt.Sprintf("console.log('sup %d');", i), "eva
    32 }
    33 })
    34 farm.Start(":3000")
    36 }
    8 <br/>9 $.post('/callback', {message: "I've loaded successfully"}, function(resp) {<br/>10 console.log(resp);<br/>11 });<br/>12

    View Slide

  22. This has to get MORE awesome

    View Slide

  23. 24 extCode := `
    25 var cef2go;
    26 if (!cef2go) {
    27 cef2go = {};
    28 }
    29 (function() {
    30 cef2go.callback = function() {
    31 native function callback();
    32 return callback.apply(this, arguments);
    33 }
    34 })();
    35 `
    36 C.cef_register_extension(CEFString("v8/cef2go"), CEFString(extCode), handler)

    View Slide

  24. C C

    View Slide

  25. cef.callback('myGoCallback', "any", "type", 0, "args", true)

    View Slide

  26. 1 // cef.callback('myGoCallback', "any", "type", 0.0, 13, true)
    2 cef.RegisterV8Callback("myGoCallback", cef.V8Callback(func(args []*cef.V8Value) {
    3 arg0 := args[0].ToString() //=> "any"
    4 arg1 := args[1].ToString() //=> "type"
    5 arg2 := args[2].ToFloat32() //=> 0.0
    6 arg3 := args[3].ToInt32() //=> 13
    7 arg4 := args[4].ToBool() //=> true
    8 // I'm in go now #yolo
    9 }))

    View Slide

  27. View Slide

  28. BUt WHY?!

    View Slide

  29. Or a JS app that takes advantage of Go’s
    concurrency + tools
    A Go app that makes
    use of the browser + JS

    View Slide

  30. I mean the real reason is because its
    an epic and beautiful hack.

    View Slide

  31. @aq

    View Slide