Electron Adventures: Episode 97: Ferrum and Chrome DevTools Protocol
We explored a wide variety of "a browser stapled to some backend" solutions, but why don't we just use a browser directly?
This almost works.
Ferrum gem
We'll be using Ruby for this episode, but everything I say is applicable to any other language, and all the issues are the same.
Ferrum starts by trying to find whichever version of Chrome or Chromium you might have installed. And if it's something unusual, you can pass in a custom path. Or just install Chrome/Chromium - that's not a huge ask these days.
Electron has a bit of an advantage here that you'll be using known version of Chrome, but really "any Chrome" is far better than something like neutralino or pywebkit are doing (pywebkit can literally run your app on Internet Explorer 11 even when Chrome is installed!).
Ferrum starts Chrome passing 32 command line arguments to isolate it and make it more controllable from the outside.
And then Ferrum uses Chrome DevTools Protocol to control the browser.
make_screenshot
Let's write one such program.
#!/usr/bin/env ruby
require "ferrum"
browser = Ferrum::Browser.new
browser.go_to("https://en.wikipedia.org/wiki/Cat")
browser.screenshot(path: "cat.png")
browser.quit
The browser is launched in headless mode, so you won't even see anything. The result is what you'd expect:
Ferrum starts the browser, issues a few commands to it, then quits.
That is super useful, either for end-to-end testing your app, or for web crawling. To make an app, we need quite a few more steps.
wikipedia_browser
Let's try to make an app. First, obviously disable headless mode. We run into the first problem that Ferrum wants to quit as soon as it's done with commands, so let's just add infinite sleeping loop at the end:
#!/usr/bin/env ruby
require "ferrum"
browser = Ferrum::Browser.new(
headless: false
)
browser.go_to("https://en.wikipedia.org/wiki/Cat")
loop do
sleep 60
end
That's what we get:
Well, that address bar and tab interface is not what we want. Ironically I tried to get tabs in Electron for CSV Editor, instead of creating tons of windows, and that was never possible. But now we want to get rid of that.
wikipedia_browser_2
Chrome has something called "kiosk mode" that strips that extra UI, and just leaves the main page. It took a few tried to get it working (--kiosk
option is officially there).
#!/usr/bin/env ruby
require "ferrum"
browser = Ferrum::Browser.new(
headless: false,
browser_options: {
"app" => "https://en.wikipedia.org/wiki/Cat",
},
)
loop do
sleep 60
end
And we got it looking like we want:
print_version
OK, that was cute, but to have a working app, we need a way to talk with the frontend. Well, Chrome DevTools Protocol lets us send commands. We can use browser.client.command
- and that's what Ferrum gem does under the hood. Many of the commands are actually quite complicated, and it's great that Ferrum handles that busywork, but we can do some simple ones:
#!/usr/bin/env ruby
require "ferrum"
browser = Ferrum::Browser.new
pp browser.client.command("Browser.getVersion")
browser.quit
Which prints:
{"protocolVersion"=>"1.3",
"product"=>"HeadlessChrome/95.0.4638.69",
"revision"=>"@6a1600ed572fedecd573b6c2b90a22fe6392a410",
"userAgent"=>
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/95.0.4638.69 Safari/537.36",
"jsVersion"=>"9.5.172.25"}
search_wikipedia
For some more complex commands let's just use what Ferrum provides:
#!/usr/bin/env ruby
require "ferrum"
browser = Ferrum::Browser.new(headless: false)
browser.go_to("https://en.wikipedia.org")
input = browser.at_css("input[name=search]")
input.focus.type("bengal tiger", :Enter)
loop do
sleep 60
end
This does exactly what we want:
Sending data to the backend
If we disregard minor issues with kiosk mode and that sleep loop, the only major missing thing is the ability of frontend to send data to the backend... And it's just not there.
I'm actually quite baffled by this, as it's so close to being viable for making apps.
Now to be fair we can emulate it. We can start an HTTP server, or a websocket, or have backend keep polling some promise pool on the frontend, or one of many such approaches.
I think if someone adds this, this will be a viable Electron alternative, as Chrome DevTools Protocol works with any backend language, and nearly everyone has Chrome installed already. Unfortunately until someone does, we sort of reached a dead end here.
Results
The series is nearing completion, but In the next episode we'll try one more overdue thing.
As usual, all the code for the episode is here.