My vscode setup for Mozilla Firefox Gecko coding

Update 2020-09-13: There is now better built-in support for vscode directly in the Firefox repository, by running ./mach ide vscode. See the documentation at https://firefox-source-docs.mozilla.org/contributing/vscode.html

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.

Full-width Bugzilla

Bugzilla imposes a column-width limit, which usually makes it nicer to read paragraphs of text:

However when comments contain logging output including stack traces, the automatic wrapping can make it much harder to follow, e.g.:

So I wrote a tiny script that may be used with Tampermonkey (and probably other similar extensions), which makes Bugzilla use the full browser width:

// ==UserScript==
// @name         Wide Bugzilla
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Make Bugzilla use the full browser width
// @author       Gerald Squelart
// @match        https://bugzilla.mozilla.org/show_bug.cgi?*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    document.getElementById("main-inner").style.maxWidth = "100%"
})();

Assuming your screen is wide enough, the log is now much more readable:

If you don’t want to use Tampermonkey or similar, you may just open a Javascript console and enter document.getElementById("main-inner").style.maxWidth = "100%" for the same effect on the current page.

And of course, you could always copy the log to your favorite non-wrapping editor. 😉

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 .

Burgundy Beef recipe (Bœuf Bourguignon)

My lazy-ish version of Burgundy beef, based on a recipe from my 1998 Petit Larousse de la Cuisine, which I brought with me when I moved to Australia. Sorry if I’m using the incorrect terminology, or explaining obvious things: I’m not a chef or even a good cook!

Ingredients, preparation:

  • 3 carrots, cut in slices less than 1cm.
  • 2 onions, cut in smallish slices (shape doesn’t matter, they will melt).
  • 1.2 kg beef (“casserole” or “gravy” works), diced less than 4cm per side. I also like to remove excess fat and yucky bits!
  • 200 g smoked bacon or speck, without rind, cut in strips.
  • 2 tbsp oil.
  • 2 tbsp plain flour.
  • 0.5 L veal or beef stock (in carton, or stock cubes melted in hot water), veg or chicken stock is fine too if that’s what you have.
  • 0.6 L red Burgundy wine (or other Pinot Noir).
  • 1 clove garlic, crushed or chopped very finely.
  • Bouquet garni (supermarkets should have it in teabag form, in the spice section; it’s a mix of thyme, bay leaf, parsley, and some other herbs).
  • salt, pepper.
  • (optional) Mushrooms, may be added near the end of cooking, but I don’t want to talk about it anymore — how can anyone eat fungus*? 🤢

Directions:

  1. Put the oil in a big pot (that can hold at least 3 L), high heat.
  2. When oil is hot (put a small bit of bacon, it should start sizzling), add bacon.
  3. Cook for a few minutes, stirring frequently, to taste (not crispy for me).
  4. Remove bacon, but keep the oil&juices in the pot.
  5. Add beef, cook for a few minutes, stirring to make sure all sides become brown. You may add a bit of salt, but not too much (bacon and later reduction could make it over-salty!)
  6. Add carrots and onions, stir, and cook for a few minutes more. Onions should become softer.
  7. (Optional) Discard juices.
  8. Sprinkle with flour, stir and let it cook a bit more.
  9. Add cooked bacon, stock, wine, garlic, bouquet garni, pepper. There should be enough liquid to cover everything. Stir.
  10. When it start to bubble, reduce heat to keep it simmering gently — not too hot, or some meat will stick to the bottom. Cover pot.
  11. Let it simmer for at least 2 hours, the more the better, overnight is the best! Regularly stir and adjust heat if needed. Enjoy the aromas floating around the whole house!
  12. Uncover, remove bouquet garni, increase the heat a bit for a stronger bubbling, but not full-on boil. Let it reduce (by evaporation – make sure your hood extractor fan is on), this may take 30-60 minutes. Try reducing by about 20% first, the sauce should feel a bit thicker, it should coat a spoon. If not sure, just try it like that in a meal, you can reduce more if you prefer it thicker.
  13. If needed, season with salt and pepper to taste.
  14. Serve with your choice of cooked vegetables. I think it goes best with mashed potato or rice. And some bread to get all of the remaining sauce off the plate! 🤤

Image under CC-SA license, adapted from Wikipedia.

* What do you mean, there’s fungus in this delicious blue cheese?? 😱

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<MIME> 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.