Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Hello!

I'm @aq

5 facts about me

1) BROOOOKLLLYYYN
grew up less then a mile from here which leads me to
2) KINGSTON
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.

SO

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:

registerCallback

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:

So WHY?

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
Tweet

More Decks by Aaron Quint

Other Decks in Programming

Transcript

  1. Have the ability to reuse existing code and knowledge about

    production I wanted to see if go was possible
  2. C & C++ API cef2go your go app your js

    app Service Request/ Response chromium JS Exec/ Response
  3. 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 } 5
  4. 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 } 7 <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script> 8 <script type="text/javascript"> 9 $.post('/callback', {message: "I've loaded successfully"}, function(resp) { 10 console.log(resp); 11 }); 12 </script> 13 </body>
  5. 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)
  6. C C

  7. 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 })) 10
  8. Or a JS app that takes advantage of Go’s concurrency

    + tools A Go app that makes use of the browser + JS