Writing custom Open-Source UI for Vivaldi Browser
I haven't seen a single browser I've genuinely liked. I've always had my issues with everything I've tried. Maybe I'm just too picky with software, but I've always wondered if I could make my own to fit my needs. Of course,
The closest thing I've found to a browser that I truly like is vivaldi, a chromium fork. It's in an odd position, the modifications to chromium itself are open source (or "source available" if you want to be a pedant), but the react-based UI is not, instead shipped as a minified javascript bundle.
At about 1:00 AM EST on a random night, these two thoughts came together and I realized that I could probably figure out a way to write my own UI for vivaldi, completely replacing their closed source one. And so:
Figuring out how vivaldi works
I didn't really know anything else about the UI other than the fact
that it was implemented in javascript. From a quick google search I
found that you can go to chrome://inspect/#apps
and select
the chrome-extension://<vivaldi-id>/window.html
to
open a devtools window for the vivaldi UI.
Already this teaches us something interesting - the vivaldi UI is implemented as a chrome extension. This is pretty neat by itself and it probably means that the IPC between the browser and the UI is going to be familiar to the way extension APIs work.
Looking a bit further in the inspect window, I can see that the
window content itself is mounted with the custom
<webview>
element, and the rest of the ui seems
relatively normal for a react app.
Alright, I think I have everything I need.
I find /opt/vivaldi -name "window.html"
and locate the
file at /opt/vivaldi/resources/vivaldi/window.html
.
I copy the entire folder to a new working directory and delete the
vivaldi bundle.js
and start editing the
window.html
file. As a sanity check I just delete the
entire contents of the file and replace it with a simple
<h1>Hello World</h1>
. When I launch the
browser, now only the hello world shows up in the window. perfect!
Adding a <script src="mybundle.js">
seems to work,
and at this point if I add
window.addEventListener("load", () => {
let webview = document.createElement("webview");
.src = "https://google.com";
webviewdocument.body.appendChild(webview);
; })
I can see google load in a tiny webview.
Finally, if i redirect the webview to
chrome://inspect/#apps
I can pop a devtools window for this
window (so I can actually see what i'm doing) and start building a quick
UI around this.
The Actual Browser
A frame holding a website isn't enough to build a browser (or I could have just used CEF/electron), so I would need to find out how vivaldi communicates with the browser itself to implement functionality like extensions, devtools, history, etc.
There's two options here: reading the open source c++ code, or reverse engineering the minified javascript bundle. I hate reading c++, and especially chromium's c++ code, so I threw the bundle through webcrack and saw what I could find.
Immediately I noticed that the bundle was using the regular chrome
extension API (through the chrome
global), and some form of
vivaldi-specific API accessed with the vivaldi
global.
I could have found out the rest of the information from static analysis of the minified bundle, but at this point I realized that I could just dynamically observe the API calls by intercepting them as the browser runs.
I went back to the unmodified copy of the browser and inserted the
following code at the top of the vivaldi bundle.js
file:
function makeproxy(global) {
return new Proxy(global, {
get(t, p, r) {
let ret = Reflect.get(t, p, r);
if (typeof ret === "object") {
return new Proxy(ret, {
get(t, p, r) {
let ret = Reflect.get(t, p, r);
if (typeof ret === "function") {
return function() {
console.error(ret, arguments);
return ret(...arguments);
}
}return ret;
,
};
})
}return ret;
,
};
})
}= makeproxy(chrome);
chrome = makeproxy(vivaldi); vivaldi
Not the most beautiful code but it should work. Every time a function
is called on the chrome
or vivaldi
namespace,
it will log the function and its arguments to the console. Immediately
after launching the browser, the console was flooded with API calls,
giving me the rest of the information I needed to implement a basic
UI.
It turns out that the tabs were built on top of the standard
chrome.tabs
API. I just had to create new ones with
chrome.tabs.create
, and make a webview
with
the id
and tab_id
attributes set to the id
returned from the create
call. The windows I make are
mapped to regular chrome tabs now, so most of the work is done for
me.
Getting devtools to work on the new tabs was a little more tricky to
figure out, through a combination of looking through the webcrack output
and looking at the API calls I figured out how to implement them: first
call vivaldi.devtoolsPrivate.toggleDevtools
(which appears
to do nothing at first), then create a new webview
with the
attributes name="vivaldi-devtools-main"
and
inspect_tab_id
set to the correct id.
Extensions were also pretty easy to figure out, the browser already
handles installing from chrome webstore for you, and
chrome://extensions
isn't touched by vivaldi, so all I have
to implement is a way to trigger the popups for them. I can get a list
of them with the regular chrome.management.getAll
API,
vivaldi.extensionActionUtils.executeExtensionAction
to
trigger the actual action, and then just create a new
webview
with the src
set to the extension's
popup URL.
Wrapping up
That was way easier than I expected! I can now write the browser UI of my dreams. See this super silly one I made in a few minutes using css transforms, where you can walk around the browser like a 3D room:

I encourage you to also write your own custom UIs, have fun with the design!. My super basic proof of concept is here
Why not firefox?
I'm aware that the firefox UI is technically editable, but the implementation seems far more convoluted and leaves far more up to the user for implementing certain features. I was able to implement a mostly functional UI from scratch in a few hours, and I don't think I could have done the same with firefox.
It's also not an option for me since I use a lot of features in chrome devtools on the daily that doesn't exist in firefox. And it's generally nice to do development in the same browser I actually browse in.