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");
  webview.src = "https://google.com";
  document.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;
      },
 });
}
chrome = makeproxy(chrome);
vivaldi = makeproxy(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.

enter higher dimension
freaky mode:
generated Jul 13 2025 at 08:13:02 by /usr/bin/bash