Understanding the Firefox Dev Tools Architecture, and How to Start Contributing to Firefox
Tags: javascript, react, redux, opensource
Published: 2020-01-28 08:25:15

---
title: "Understanding the Firefox Dev Tools Architecture, and How to Start Contributing to Firefox"
published: true
description: "Here's what I learned about the Firefox Dev Tools architecture, and how you can start contributing to Firefox too!"
tags: javascript, react, redux, opensource
canonical_url: https://www.valentinog.com/blog/firefox/
---

This year I decided to contribute more to open source projects and since I've made the resolution to **switch to Firefox** from Chrome I've also got the chance to **find an interesting bug to work on**.

In this post I want to share my learnings in the hope that more people could start contributing to Firefox.

## Contributing to Firefox: a bit of backstory

In Firefox Dev Tools I wanted a **"Store as global variable" option** in the **Scopes panel**. The Scopes panel appears in the **debugger tab** when you stop the script with a breakpoint, much like Chrome Dev Tools.

As of now the only thing that you can do on an object in the panel is adding a watchpoint, that is, a watcher which fires on properties get and set. You can [read more about watchpoints here](https://hacks.mozilla.org/2019/12/debugging-variables-with-watchpoints-in-firefox-72/).

With the idea in mind I reached the Firefox Dev Tools account on Twitter and the team replied promptly with a workaround:

{% twitter 1214886156241899525 %}

But it turns out there was already a bug filled for the feature I wanted: [Variables/values in Scopes should offer reps context menu](https://bugzilla.mozilla.org/show_bug.cgi?id=1573088).

So I jumped in and I tried to work on the bug. But before going into details let's see how to get started in contributing to Firefox Dev Tools.

Enjoy!

## Contributing to Firefox: how to get started

If you want to start contributing to Firefox your first stop must be [this website: Firefox Developer Tools](https://firefox-dev.tools/). Also, join the Slack channel.

**Read every section of the guide carefully** and try to not skip anything. There are great insights into the Firefox Developer Tools architecture, but I know, you want to get your hands dirty.

**[Clone the repo](https://docs.firefox-dev.tools/getting-started/build.html)** (you'll need Mercurial) and **make yourself comfortable with the codebase**. **It is big, and you'll need a lot of reading through the source code before starting to grasp anything**.

Don't try to rush it, don't be like me. **Your first patch will be likely need changes** because you don't know how Firefox Dev Tools works under the hood.

But that's a good thing because **learning from such a big codebase and interacting with more expert developers is invaluable**.

After cloning the repo and glancing through the code it will come the time to create **your first patch**. But first, **let's demystify the architecture of Firefox Dev Tools**.

## Firefox Dev Tools frontend: the architecture

The Firefox Dev Tools frontend is a big **React/Redux app made of many little apps**. For that reason you'll need a good understanding of Redux, namely:

- reducers
- actions
- middlewares
- store enhancers
- selectors

**Every tab in the developer tool is a React/Redux app** which lives in a separate folder in **mozilla-central/devtools/client**. Here's a breakdown:

```bash
$ tree -d -L 1 devtools/client/
devtools/client/
├── aboutdebugging
├── accessibility
├── application
├── bin
├── debugger
├── dom
├── framework
├── inspector
├── jsonview
├── locales
├── memory
├── netmonitor
├── performance
├── performance-new
├── preferences
├── responsive
├── shared
├── storage
├── styleeditor
├── themes
├── webconsole
├── webreplay
└── whats-new
```

You can recognize each tab: **debugger, inspector, memory, netmonitor, performance**, and so on.

As you'll find out by going through, each application follows more or less the same directory structure. Inside each app you'll find:

- **React components**
- **Redux actions, reducers**, and so on
- **tests**
- **utilities**

There are also a lot of other folders which I won't cover here, but you can learn more by checking out [this link](https://docs.firefox-dev.tools/files/).

Worth noting, each tab in the developer tool is also a **panel**. The debugger for example is defined in **devtools/client/debugger/panel.js**.

On top of that, the entirety of each panel grouped together is called **toolbox** and is defined in **devtools/client/framework/toolbox.js**.

The toolbox is in practice the entire dev tool window. **Each panel gets access to the toolbox, which is injected** at runtime. The debugger panel for example takes toolbox as a parameter:

```javascript
// devtools/client/debugger/panel.js
function DebuggerPanel(iframeWindow, toolbox) {
  this.panelWin = iframeWindow;
  this.panelWin.L10N = L10N;
  this.toolbox = toolbox;
}
```

Same is true for the inspector panel:

```javascript
// devtools/client/inspector/panel.js
function InspectorPanel(iframeWindow, toolbox) {
  this._inspector = new iframeWindow.Inspector(toolbox);
}
```

Turns out, **this frontend is only the tip of the iceberg**. There is also a **Firefox Dev Tools backend**. But we'll get to it later: let me show you my first attempt to implement "Store as global variable".

**TIP**: to browse the codebase you can also use [Searchfox](https://searchfox.org/mozilla-central/source/devtools).

## Store as global variable: a first attempt

After building Firefox ([instructions here](https://docs.firefox-dev.tools/getting-started/build.html)) I started off by looking around in **devtools/client/debugger**.

I found out my entry point in **[devtools/client/debugger/src/components/SecondaryPanes/Scopes.js](https://searchfox.org/mozilla-central/source/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js)**.

**This component is responsible for displaying Scopes**, and has already a **context menu** for adding watchpoints.

I started off by adding a new entry named "Store as global variable" to the menu. Here's the relevant part:
 
```javascript
  onContextMenu = (event: any, item: any) => {
    const {
      addWatchpoint,
      removeWatchpoint,
      storeAsGlobalVariable,
    } = this.props;

    // omitted for brevity

    const storeAsGlobalVariableItem = {
      id: "node-menu-store-as-global",
      label: storeAsGlobalLabel,
      disable: false,
      click: () => storeAsGlobalVariable(item),
    };

    const menuItems = [storeAsGlobalVariableItem, watchpointsSubmenuItem];
    showMenu(event, menuItems);
  };
``` 

As you can see the menu is shown in response to the [contextmenu event](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event).
 
The Scopes component is connected to the Redux store, that means it has access to **actions**. 

So I've added a new action in **devtools/client/debugger/packages/devtools-reps/src/object-inspector/actions.js**:

```javascript
function storeAsGlobalVariable(item) {
  return async function({ panel }) {

  // omit for brevity

    await panel.storeAsGlobalVariable(selectedObjectActor);
  }
}
```

With the help of [Nicolas from the Firefox team](https://twitter.com/nicolaschevobbe) I've got a first proof of concept. There is a lot more going on in this action and I've omitted some code for brevity's sake, but if you're familiar with Redux you can recognize a **thunk**. 

![Context menu](https://dev-to-uploads.s3.amazonaws.com/i/3ulkhuj5zryyf9g8j8t1.png)

Thunk is a middleware, and as for the project is configured **each thunk has access to the panel**.

If you remember from the previous section **a panel is a dev tools tab**. In this case **panel is the debugger tab**.

What happens in this action is that it takes item as a parameter and computes a selectedObjectActor. In brief, the item is the actual thing that has been clicked in the Scopes panel. selectedObjectActor instead is a reference (a string) that we need to pass to the **server**.

Yes, there is a server. In the next section I'll describe what I learned about it, while trying to explain how the entire architecture works.

Worth noting, most of the codebase is type annotated with **Flow**. I'm not familiar with it, but it's not so different from TypeScript, which I use most of the times.

By the way, since it's all JavaScript don't forget to install the dependencies for any given panel you're working on (you'll need to install Yarn). In my case:

```bash
cd ~/Code/mozilla-central/devtools/client/debugger
yarn install
```

After making changes don't forget to build the bundle and to rebuild Firefox as well, again in my case:

```bash
cd ~/Code/mozilla-central/devtools/client/debugger
node bin/bundle.js
~/Code/mozilla-central/mach build
```

Now you can run Firefox with:

```bash
~/Code/mozilla-central/mach run
# or simply ./mach run if you're in the same folder
```

The build produces a version of **Firefox called Nightly**. I use Firefox Developer Edition for development, Nightly is similar, but includes all the newest features. To learn more about all the Firefox versions [check out out this video by Jen Simmons](https://www.youtube.com/watch?v=qQ1oQJJn1nQ).

## Firefox Dev Tools server: the architecture

As I said, **the Firefox Dev Tools frontend is only the tip of the iceberg**. **Each panel in fact is a client**, and **has a corresponding actor on the server**.

That means there is the webconsole actor, the inspector actor, the network actor, and so on. It's not a 1:1 match, the debugger panel for example talks to the **thread actor**, but, you got the point.

Actors are defined in **devtools/server/actors**. The term **actor** refers also to any object that is being debugged (the debuggee).

In my action for example I passed a selectedObjectActor:

```javascript
function storeAsGlobalVariable(item) {
  return async function({ panel }) {

  // omit for brevity

    await panel.storeAsGlobalVariable(selectedObjectActor);
  }
}
```

Here **selectedObjectActor will be a string** like:

```text
"server0.conn0.child1/obj464"
``` 

This string is used by the client to tell the server "ehi, please, tell me about this object and give me back the result".

The thing to keep in mind then is that **it's not the Dev Tool frontend that evaluates the actual JavaScript code**.

**Everything lives on the server-side and client panels talk to the server via JSON objects**. Then it's the **server-side of Dev Tools that talks to the Firefox JavaScript engine** (SpiderMonkey) and reports back to the client panel if asked.

You can read more about the [Debugging protocol architecture here](https://docs.firefox-dev.tools/backend/protocol.html).

Now after introducing server and panels it's time to see **how all these components interact**.

## Firefox Dev Tools: from the toolbox to the server

Let's take a look again at the action:

```javascript
function storeAsGlobalVariable(item) {
  return async function({ panel }) {

  // omit for brevity

    await panel.storeAsGlobalVariable(selectedObjectActor);
  }
}
```

**panel** is a thunk argument and maps to **[devtools/client/debugger/panel.js](https://searchfox.org/mozilla-central/source/devtools/client/debugger/panel.js)**, the actual debugger panel.

If you look at the source code you can see that the constructor function for DebuggerPanel takes **toolbox** among the parameters. Toolbox is at **[devtools/client/framework/toolbox.js](https://searchfox.org/mozilla-central/source/devtools/client/framework/toolbox.js)** and has an [openSplitConsole method](https://searchfox.org/mozilla-central/source/devtools/client/framework/toolbox.js#2781).

That was exactly what I was searching for. Also, by looking at how the "Use in console" feature works in the inspector I tried to replicate the same feature. That lead me to create a new method in **devtools/client/debugger/panel.js**, storeAsGlobalVariable:

```javascript
  storeAsGlobalVariable: async function(selectedObjectActor) {
    await this.toolbox.openSplitConsole();
    const { hud } = this.toolbox.getPanel("webconsole");

    const script = `{ let i = 0;
      while (this.hasOwnProperty("temp" + i) && i < 1000) {
        i++;
      }
      this["temp" + i] = _self;
      "temp" + i;
    }`;

    const thread = this._selectors.getCurrentThread(this._getState());
    const { id } = this._selectors.getSelectedFrame(this._getState(), thread);

    const options = {
      selectedObjectActor,
      frameActor: id,
    };

    const res = await hud.evaluateJSAsync(script, options);
    hud.setInputValue(res.result);
  },
```

This method bears a bit of explanation. First, it receives **selectedObjectActor** from the Redux action. Next up it **opens the split console**, that is, the little console you can see some times in the Dev Tool.

It also **hooks the webconsole panel** which returns itself as **hud**.

You can also notice the script for creating a new global variable. Before forwarding the script for evaluation to the server this method computes also the **frameActor id**.

We already saw what is **selectedObjectActor**, a reference to an object in the Scope panel. The **frameActor id** is another string:

```text
server0.conn0.child1/frame26
```

In particular it is a reference to the context where we are currently in. **In other words if I pause a script with the debugger, that context where the script is paused is the actual frame**.

These two references, selectedObjectActor and frameActor are **needed by an actor which lives in the server**. More on this in the next section.

## Store as global variable: the working POC

This is the part that required the help of Nicolas. Let's look again at the previous method:

```javascript
  storeAsGlobalVariable: async function(selectedObjectActor) {
    // omitted for brevity
    const options = {
      selectedObjectActor,
      frameActor: id,
    };

    const res = await hud.evaluateJSAsync(script, options);
    hud.setInputValue(res.result);
  },
```

Long story short, the call to **hud.evaluateJSAsync** is forwarded to the server and lands on **evalWithDebugger** defined in [devtools/server/actors/webconsole/eval-with-debugger.js](https://searchfox.org/mozilla-central/source/devtools/server/actors/webconsole/eval-with-debugger.js#106).

This function required a tweak because it prevented **_self** from being defined. **_self** is used by the server as a placeholder during evaluation. Here _self is replaced by the actual object:

```javascript
    const script = `{ let i = 0;
      while (this.hasOwnProperty("temp" + i) && i < 1000) {
        i++;
      }
      this["temp" + i] = _self;
      "temp" + i;
    }`;
```

When the evaluation is done the appropriate object is assigned to a **temporary variable named temp0, temp1, and so on**.

And with the fix in place ([you can see the history here](https://bugzilla.mozilla.org/show_bug.cgi?id=1573088)) I've got a working proof of concept:

![working proof of concept](https://dev-to-uploads.s3.amazonaws.com/i/uyq0zznjvdfrfe0rm9lr.png)

**When the script is paused and the Scopes panel is opened** I can (in my build for now) **save an object in a global variable** after the split panel opens.

## Conclusions

Firefox is a fantastic browser and the **developers tools are second to none**.

At the time of writing the feature is far from finished, there are some edge cases to cover. But I was eager to share this little open source story to clarify my understanding about such a big system.

## Acknowledgments

Thanks to Jason Laster and Nicolas Chevobbe for answering to my questions and providing guidance on my first patch for Firefox.