My vscode setup for Mozilla Firefox Gecko coding

Building Firefox

If not done yet, get your PC configured to build Firefox for the Desktop: https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_build

I recommend having an “obj” directory outside of the source directory (usually “mozilla-central”, where hg clone should have put all the files, in the instructions above), so that vscode will have fewer files to monitor.
At the top of your source directory, create a text file called “mozconfig” (no file extension), and write this line: mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../obj-mc
mozconfig can contain other build options, I recommend the following:
mk_add_options AUTOCLOBBER=1
ac_add_options --enable-debug
ac_add_options --enable-debug-symbols
ac_add_options --enable-optimize
(You can change the last line to --disable-optimize when you need to debug something tricky without optimizations hiding and obfuscating the code, but it will make Firefox way slower!)

Compilation Database

After building Firefox with ./mach build, you also need to build a “compilation database” by running ./mach build-backend -b CompileDB; this is how vscode will know which files are #included.
You will need to do this after every build that may have added or removed files, I usually do it after every rebase to a new central, or when I know my patches add/remove files.

Utility Scripts

To make these operations easier, I have a number of small scripts in my ~/.bin directory, which is in my $PATH:
compiledb: ./mach build-backend -b CompileDB
b: ./mach build "$@"
bb: ./mach build binaries "$@"
cb: ./mach clobber && ./mach build "$@"
bcdb: ./mach build "$@" && ./mach build-backend -b CompileDB
cbcdb: ./mach clobber && ./mach build "$@" && ./mach build-backend -b CompileDB

My start-of-the-day routine is hg pull && hg rebase -d central && bcdb. While I work bb rebuilds just what is needed.

Installing vscode and Extensions

Install Visual Studio Code (aka vscode) : https://code.visualstudio.com/

Open vscode, and open the Firefox source directory (“Open folder…” on the welcome page, or File -> Open Folder…).

vscode will suggest a number of extensions to add, it’s probably safe to just add them all. The most important one is “C/C++” by Microsoft.
I would also recommend “Searchfox”. And I also like “Snake Trail”, it’s pretty 😄.

vscode Configuration

Now you need to configure vscode to better understand the Firefox code.

Open your setting as JSON: Ctrl+shift+P or Command+Shift+P and type “settings”, select “Preferences: Open Settings (JSON)”. It’s probably empty the first time, otherwise update it as needed. Here’s mine:

{
    "editor.rulers": [
        80
    ],
    "editor.renderControlCharacters": true,
    "editor.renderWhitespace": "boundary",
    "editor.largeFileOptimizations": false,
    "python.pythonPath": "C:\\mozilla-build\\python3\\python3.exe",
    "C_Cpp.clang_format_fallbackStyle": "Google",
    "C_Cpp.clang_format_path": "C:/Users/squel/.mozbuild/clang-tools/clang-tidy/bin/clang-format",
    "window.title": "${dirty}${activeEditorMedium}",
    "editor.scrollBeyondLastLine": false,
    "files.associations": {
        "*.js": "typescript"
    },
    "html.format.enable": false,
    "javascript.format.enable": false,
    "json.format.enable": false,
    "typescript.format.enable": false,
    "files.eol": "\n",
    "editor.fontFamily": "'JetBrains Mono', Consolas, 'Courier New', monospace",
    "editor.fontLigatures": true,
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true,
    "editor.formatOnType": true
}

Some of these are personal preferences, I’m sure you can work out what you really need vs what you prefer, and how to change some file paths.

Next, you also need to configure the C/C++ extension: Ctrl+shift+P or Command+Shift+P and type “c++”, select “C/C++: Edit Configurations (JSON)”. Here’s mine, for Windows obviously, and I’m working on the profiler so I’ve got some #defines I need:

{
  "configurations": [
    {
      "name": "Win32",
      "includePath": [
        "${workspaceFolder}",
        "${workspaceFolder}/**",
        "${workspaceFolder}/../obj-mc-dbg/dist/include",
        "${workspaceFolder}/../obj-mc-dbg/dist/include/**",
        "${workspaceFolder}/../obj-mc-dbg/ipc/ipdl/_ipdlheaders",
        "${workspaceFolder}/../obj-mc-dbg/ipc/ipdl/_ipdlheaders/**"
      ],
      "defines": [
        "_DEBUG",
        "UNICODE",
        "_UNICODE",
        "XP_WIN",
        "MOZ_GECKO_PROFILER",
        "MOZ_BASE_PROFILER"
      ],
      "windowsSdkVersion": "10.0.17763.0",
      "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Tools/MSVC/14.22.27905/bin/Hostx64/x64/cl.exe",
      "cStandard": "c11",
      "cppStandard": "c++17",
      "intelliSenseMode": "msvc-x64",
      "compileCommands": "${workspaceFolder}/../obj-mc/compile_commands.json"
    }
  ],
  "version": 4
}

The "compileCommands" is what reads the CompileDB we built earlier, and the "includePath"s help find headers that are copied or generated in a few places in the obj directory.

From here, vscode should be able to interpret most C++ files, and navigate to other symbols: Right-click on a symbol and you should see “Go to definition” and other commands.

BEWARE! When going to declarations or definitions, vscode may open a copy in the obj directory, meaning that changes there will not actually modify the original file, and you will probably lose your changes when building again!
To avoid such mistakes I would recommend installing both:
– The “Workspace Watchdog” extension, which changes the background color of files outside the workspace.
– The “Read-Only Mode Support” extension, which will warn you when you modify and save a file outside of the workspace.

Other Resources

Developing Mozilla C++ code with clangd and VSCode — This may provide more accurate results than my methods above, but I have not tried it yet.

searchfox.org — The best online way to navigate the Firefox source code.

JetBrains Mono — “A typeface for developers”, free, with ligatures! A great successor to Consolas.

Avoid C++ implicit conversions from bool

To most C++ programmers — or at least those like me with a short memory — it may feel like bool has always been there. However it is still not present in its ancestor C (though C99 has _Bool), and it was only introduced in C++98. At that time, there was resistance to it, and examination of existing code (that was mostly using macros to simulate a boolean type) showed that preventing implicit conversion between boolean values and integer numbers would have broken a lot of things. So in C++, bool-to-int conversion is implicit, with false converting to 0 and true to 1.

I think that this implicit conversion from bool is generally not needed in “real-world” code, and that it is in fact a source of bugs.

For example, this tweet shows that because of this implicit conversion, a typo like in int x = 1 < y; (where < was meant to be the left-shift operator <<) didn’t raise any alarm.

In my coding experience, I cannot think of useful cases where I need to convert a bool to something else, apart from printf statements used during debugging — but for these, I have taken the habit of doing an explicit conversion anyway, e.g.: printf("b=%d", int(b));.

I can certainly imagine cases where a bool could be converted to 0 or 1, to be used in a mathematical expression, e.g.: a = useC * c, but I think this would be better expressed as a = useC ? c : 0.

Of course my experience is limited, and there may be genuine situations where this conversion could genuinely help — Please let me know. But I think that in these cases, making the conversion explicit should be trivial, and also useful by making it more obvious (a = int(useC) * c). Now, if such conversion is needed for a lot of your code, maybe it’s a sign that the bool type may not be the right choice.

Note: clang-tidy offers readability-implicit-bool-conversion, but by default it catches more than just conversions from bool to int .

Always-Valid C++ Types

Types don’t always contain what their name implies

Many C++ user-defined types may at time carry information that does not correspond to what their names say. E.g.: a Name object may be empty, which is not a valid name. All code that manipulates these types must handle such invalid cases, or trust that they have been verified elsewhere, which is a common source of bugs.

As an example, let’s consider a type that should contain a MIME string, e.g.: “audio/mp3”. A naive implementation may start like this:

class MIME {
public:
    MIME(const string& mime) : _mime(mime) {}
    bool is_valid() const { /* proper tests */ }
private:
    std::string _mime;
};

Notice that there is no validity checks in the constructor, so any code that manipulates MIME objects will need to call is_valid() before trusting that these objects contain valid MIME data. But sometimes in the spirit of optimization it is tempting to write:

// Only call with a valid mime string!
bool is_audio(const MIME& mime) {
    // This should catch any bad calls, right?
    assert(mime.is_valid());
    ...
}

And this may well pass all your testing, because you have already tested MIME::is_valid() elsewhere so you will think you don’t need to check for invalid MIME strings in is_audio() again… Until you start getting crash reports from the field, when your users handle data of doubtful origin.

Ensure types are valid by construction

One solution would be to add validity checks in the constructor, and throw an exception when a bad string is provided. However this seems quite a strong action to take, as strings that are not MIME types are quite common in the real world, and it would force the user to handle the MIME validation before trying to create a MIME object. Also some projects prefer not to use exceptions at all (like Firefox).

My preferred solution is to hide the too-trusting constructor, and to instead provide a factory function that will verify the given string and will only create a MIME object when that string is valid. Any object generated from this function can now be trusted to always contain valid data.

class MIME {
public:
    static std::optional create(const std::string& mime) {
        if (`mime` is a valid MIME string) {
            return { MIME(mime); }
        }
        return {};
    }
private:
    MIME(const string& mime) : _mime(mime) {}
    std::string _mime;
};

C++17’s optional is of great help here as it forces the caller to do the right thing: The return value must obviously be checked for a valid MIME object, and that object may only be accessed when actually present. Afterwards, that MIME object may be copied, or just passed to other functions as a direct reference; and by contract there is no need for further validity tests.

If you cannot use C++17 yet (and don’t have access to a similar helper type), you could instead return a unique_ptr, at the extra cost of a heap allocation — which may make sense anyway for heavy objects.