Probably not useful for anybody, but something I told myself I’d do: share out some C++ notes.

These notes are based on the 2016? Mozilla C++ and Gecko onboarding presentation by froydnj:

I don’t know why this talk is set to Mozilla-private, I’m working on getting it made public.

Anyway here we go.

Basic topics to cover:

  • Subsystems in Gecko
  • XPCOM strings and datatypes
  • infallible alloc
  • coding style
  • C++ guidelines

Additional topics suggested by Gecko experts:

  • Refcounting
  • Smart pointers
  • Electrolysis (e10s) (TODO: this video predates fission and should be brought up to date)
  • NSPR
  • Threading
  • C++ visibility

Outline of the talk:

  1. What happens when I click a link? (Intro to subsystems and overall network and rendering pipeline)
  2. Nuts and bolts of Gecko C++
  3. Code generation
  4. Memory management
    • Smart pointers
    • Reference counting schemes
  5. Gecko C++ data structures

Sequence: Network -> Parser -> DOM -> Styles -> Layout -> Display List -> Paint -> Composite

Docshell is where it all begins: Docshell sets up, then network loads

  • /docshell directory (see also /uriloader)
  • URI loading starts
  • Handles navigating between docs (back-forward movement)
  • Starts doc loading
  • Sets things up for network
  • Manages session history
  • See also uriloader (we probably should merge these eventually)

Networking code

  • /netwerk dir (the misspelling is deliberate and historical)
  • /netwerk/dns has DNS-related code
    • We use the OS for DNS resolution
    • We add in other stuff
    • DNS is done off the main thread and parallelized
  • /netwerk/protocol has code for protocols (HTTP, FTP, WebSocket, …)
  • /netwerk/cache2 handles document caching
    • We cache documents on disk for speed
    • There’s old code in /netwerk/cache, unclear why it’s not deleted. Ignore it.
  • /netwerk/cookie manages cookies

Once the network returns data, DocShell sets up listeners to handle the results.

Next: the parser, which handles the network response.


  • /parser directory
  • Multiple parsers:
    • XML (/parser/xm, /parser/expat)
    • old-style HTML /parser/htmlparser, only used for about:blank as of 2016
    • HTML5 (/parser/html)
    • Old-style parsers had a lot of quirks at handling parsing errors
    • HTML5 is much better specified for error handling
    • Expat is ~20 years old, we use it for XML parsing
      • It’s very simple and very fast

As the parser parses, it builds DOM nodes for all elements Loading other formats (CSS, JS, images) is driven by the parser, but handled elsewhere.


  • dom directory
  • Absolutely massive directory
    • Documents, elements all live in dom/base, /dom/html
    • Various web APIs often have individual subdirectories, like /dom/indexedDB
    • WebAPI definitions are imported into /dom/webidl for spec conformance verification
      • These definitions are also used to code generate JS bindings in /dom/bindings

DOM builds up the linked tree of DOM nodes Next, we need to position nodes on a 2D page.


  • Translates the DOM tree to 2D canvas.
  • Every visible DOM node gets its own Frame, then Layout figures out how to position the Frames relative to each other
  • /layout directory
  • also massive
    • Our CSS implementation is in /layout/style
    • /base, /generic: Frames (box around each element), Display Lists (organizing Frames so we can paint them quickly)
    • /layout/svg holds the SVG code
    • /layout/xul XUL

Given a bunch of boxes, now we need to paint them to the screen.


  • /gfx directory
  • also large, but mainly because we use third-party libraries
  • We have our own code for some 2D primitives in /gfx/2d, /gfx/thebes
  • Thebes: our old way of rendering to the screen
    • old enough that it doesn’t map well to modern graphics APIs
    • rewrite in progress (as of 2016)
  • Layers for compositing in /gfx/layers
    • All the boxes we had get organized into Layers
    • The static parts of the page are grouped together as one layer, paint that into a texture on the graphics card
    • Things that are changing get their own layers, sent off as textures to the graphics card
    • Ship all the layers off to the GPU to be squashed together and composited all at once
  • Lots of third-party graphics code managed by the gfx team:
    • /angle, /cairo, /graphite2, /harfbuzz, /ots, /skia, /ycbcr

For this introductory talk, we’re skipping the final stages of rendering.

What else is in Gecko?

Storage in /storage

  • We use SQLite for many things
  • IndexedDB, localStorage use SQLite
  • Browser history (the Places DB)
  • Cookies
  • ServiceWorker cache API is backed by SQLite
  • Not an ORM. We run SQL strings everywhere.
  • MozStorage provides a nice multithreaded API over SQLite
    • e.g. run query in background thread, then return results async

Images, /image

  • We wrote a .bmp decoder
  • For newer formats, we use third-party libraries: libpng, libjpeg, libgif
  • Decoding images & transforming into suitable form for the gfx code
  • Decoding is done off the main thread
  • We use a thread pool to parallelize up to the computer’s limits

Media in /media

  • Largely imported code for sound, image, video handling
  • Also where (imported) WebRTC implementation lives

Widget code in /widget

  • Interface between Gecko and OS native event and window handling
  • Abstraction layer (generic IDLs)
  • Separate directories for various platforms:
    • /android, /cocoa (mac), /donk (B2G, now deleted as of 2021), /gtk (linux), uikit (iOS), /windows

IPC (inter-process communication) code lives in /ipc

  • Code for managing multiple processes
  • Electrolysis (e10s) separates out browsing duties:
    • Parent process (handles user interaction)
    • Child proc: web content code, JS, etc. for responsiveness & security
  • This code originally came from Chromium ~5 years ago (i.e. 2011 I guess?)
    • It’s now been modified beyond all mergeability. An unintentional fork.
    • If you hit bugs, likely worth looking upstream for existing fixes.
  • Big chunks here:
    • sending / receiving messages
    • Object serialization / deserialization
    • A separate event loop (more on this in a bit)
    • IPDL code generator

JavaScript engine in /js

  • Our JS implementation: parsing, interpretation, JIT compiler, GC, etc.
  • To encourage embedding, we have public interfaces in /js/public
    • But we don’t invest in this right now
  • The source code lives in /js/src

XPConnect: /js/xpconnect

  • “Deep Magic”: manages reflection of objects between JS and C++
  • Also defines our security architecture for JS
    • See Script Security in source docs for details

MFBT /mfbt

  • “Mozilla Framework Based on Templates” (backronym and quite a stretch)
  • Code shared between Gecko and the JS engine
    • Classes we find useful
    • Polyfills for C++ stdlib things
    • The stdlib is inconsistent across platforms, so we have code to smooth it out
    • Close to C++11, aiming to delete large chunks of /mfbt someday

XPCOM /xpcom

  • “Cross-platform COM” (component object model)
  • Idea: use code in multiple languages via shared language-agnostic interfaces
    • This was inspired by some Microsoft ideas from the 90s
  • We tried to do this across platforms, with varying degrees of success.
  • Lots of building blocks in here:
    • Data structures (arrays, hashtables, strings)
    • Core event loop (timers, threads)
      • Main thread and nonmain threads
      • Interacts with IPC event loop in various ways
    • Parts of the JS interface to chrome code (XPConnect uses this stuff)
      • Interacts with XPConnect code, mostly machine-dependent parts
    • Not quite complete abstract IO interface (stream abstraction that provides bytes, etc)
      • Most implementations of the abstract interface live in /netwerk for historical reasons
    • Cycle collector: part of the reference counting memory management code
      • Much more on this later in the talk

NSPR /nspr

  • Netscape Portable Runtime, 20+ years old
  • Abstracts threads, locks, I/O, logging, etc.
  • We use NSPR when it’s convenient
    • Sometimes we have nice wrappers (XPCOM has other nice wrappers for some threading stuff)
    • Sometimes we use things directly (I/O)
    • Sometimes stdlib has better alternatives
  • Slowly working on removing NSPR
    • NSPR is a separate repo and team from Gecko
    • Separate release schedule, etc.

Part 2: Gecko C++

C++ Style Guide

  • We have a style guide
  • Not all code conforms to the style guide
    • We’re working on it
  • Please use the style guide for new code
    • Follow local conventions when necessary
    • /dom, /layout are stricter than /gfx
    • Use clang-format if you like

C++ Features

  • We don’t use exceptions
    • Historically, exceptions weren’t good on some platforms
    • They’ve gotten better, but converting Gecko to be exception-safe would be a huge chore
  • We don’t use RTTI (dynamic_cast, e.g.)
    • Bloat, doesn’t work with JS-implemented things
    • We already have an equivalent dynamic cast that’s JS compatible
    • RTTI not supported on Android
  • We have a page with our C++ feature support matrix across C++ versions and different compilers

Brief note on portability

  • Across platforms, rate of new feature adoption differs, quality of implementation differs
  • Guarantees provided by interfaces may not be strong enough for us
    • e.g. we need memory reporting, std::vector doesn’t support this
  • Hence, we reimplement the wheel in some places

C++ std:: classes

  • Historically, we avoid std:: things
  • We don’t use exceptions, std does
  • It’s not necessarily available everywhere
  • Please use Gecko versions if possible
  • Please file bugs on Gecko versions if you find an API annoying

Infallible allocation (new T(...) or new T[])

  • By convention, if out of memory, allocation will crash, rather than returning a null pointer
  • We don’t return null, so we never need to rely on consistent null-checking
  • Useful for debugging things
    • e.g. if you try to allocate 100MB, crash immediately.
    • Otherwise, you get a null pointer back, sometime later you may dereference a null pointer–but it’s very hard to see how/where that happened.
    • So, this makes things simpler to debug
    • Also shorter: no need for lots of null checks
  • If you need an array but don’t control the allocation amount, we do have options
    • Fallible allocation: new (fallible) T(...)
    • Similar to nothrow in stdlib, predates it

C++ Visibility

  • We ship one big shared library, xul.dll, /
  • Improves startup time
  • Exporting symbols from XUL is expensive
    • Runtime penalties, size penalties
    • Exposed interface penalties (malware hooks, etc)
  • Strive to export as little as possible
    • Generally the Right Thing happens by default


  • Main (UI) thread
    • Handles all user interaction, most content stuff
    • Important to avoid long blocking operations
    • Particularly important to avoid I/O on the main thread
  • Lots of (mostly idle) helper threads
    • SQLite, media/image decoding, network cache, JS JIT compilation, JS GC, timers

3. Code Generation

  • Used extensively in Gecko
  • 3 main kinds of code generation
    1. XPIDL: old bindings between JS and C++
    2. IPDL: multi-process communication
    3. WebIDL: new way of doing bindings between JS and C++
  • We have other, one-off code generators throughout the tree
  • All written in Python

XPIDL files

  • Most useful for understanding how we get from JS to C++
  • “Cross-platform IDL”
  • Defining interfaces for either C++ or JS to implement
  • Generates C++ class boilerplate
  • Generates metadata for calling into C++ from JS (argument conversions)
  • Docs on MDN (RIP, TODO find them and pull into firefox docs somewhere)

XPIDL: Generated code

  • Generates headers for each .idl in $OBJDIR/dist/include
  • nsIFile.idl generates nsIFile.h
  • IDL can define multiple interfaces per file => generated header will have multiple class definitions
  • Each interface nsIFoo generates:
    • nsIFoo class definition (empty methods)
    • NS_DECL_IFOO macro for declaring all of nsIFoo’s interface in C++
    • Every interface has a UUID, there are NS_IFILE_IID and NS_IFILE_STR macros for the UUID
    • other macros, rarely needed

Some IDL -> C++ examples:

IDL: const unsigned long NORMAL_FILE_TYPE=0; (note: you don’t get strongly-typed enums) C++: enum { NORMAL_FILE_TYPE=0 };

IDL: void append(in AString node); C++: NS_IMETHOD Append(const nsAString&);

  • Note the IDL parameters need to be annotated with in or out to indicate if they are passed in or received out

IDL: bool exists(); C++: NS_IMETHOD Exists(bool*);

IDL: readonly attribute long long fileSize; C++: NS_IMETHOD GetFileSize(int64_t*);

  • By convention, readonly attributes return a getter

IDL: attribute AString leafName; C++: NS_IMETHOD GetLeafName(nsAString&); and NS_IMETHOD SetLeafName(const nsAString&)

  • By convention, modifiable attributes generate getter and setter in C++

  • By convention, return values are a pointer.
  • NS_IMETHOD is short for virtual nsresult + windows-specific goo
  • nsresult is our status code
    • Success case: NS_OK
      • Please don’t use the other success values
    • Failure: NS_ERROR_FAILURE and many others
      • Please return the most specific error available
    • Test the return value with NS_SUCCEEDED(val) or NS_FAILED(val), don’t check directly
    • Complete list of errors: /xpcom/base/ErrorList.h


  • Return values are passed through outparams
  • Only set outparam when you’re returning NS_OK
  • Don’t use the outparam at caller until you check NS_SUCCEEDED
  • Always propagate errors to caller(s)
  • XPConnect maps errors to JS exceptions automatically

Getters / Setters

  • Both can fail
  • Same outparam convention as methods


  • IPDL files describe multi-process messaging “protocols
    • List of messages sent by parent or child
  • Code generator generates the uninteresting, repetitive parts
    • e.g. serialization / deserialization
  • Protocols are sorted into a hierarchy
    • e.g. PContent does top-level e10s per tab
    • PContent manages other protocols
    • Similarly, child has its own hierarchy / tree of protocols
  • Provides C++ structure for the interesting parts (defines virtual handler methods)
  • Lives in ipc/ipdl/
    • Not great fun to work with, infrequently modified
    • C++ code goes to $OBJDIR/ipc/ipdl
    • C++ headers to $OBJDIR/ipc/ipdl/_ipdlheaders
    • Headers organized by C++ namespace



Memory Management

Owned pointers

std::unique_ptr - encapsulates lots of details of passing ownership of pointers around (for exclusive-ownership)

  • Not available because not all our platforms support it

But we do have Mozilla::UniquePtr (in mfbt)

  • Same interface as std::unique_ptr
  • Anywhere you might need to pass raw pointers around, use UniquePtr instead

The old way: (gone from Gecko as of 2021, it seems)

  • nsAutoPtr<T> and nsAutoArrayPtr<T>
    • Similar to std::auto_ptr
    • Like auto_ptr, these are deprecated too

Shared pointers

  • We do this by reference counting
  • AddRef() and Release() methods
    • Doesn’t matter virtualness or not
    • You just need the names and parameters, if any
  • Unlike std::shared_ptr, which manages proxies to objects, in Gecko, objects manage their own refcounts directly
  • If you just want to pass around shared data, std::shared_ptr is easier
  • For Gecko, we control all the data, so we just manage refcount directly

Reference counting: thread safety

  • Thread safety: thread-safe and non-threadsafe options are supported
  • std::shared_ptr only available in thread-safe version
  • If you’re passing objects among threads, please use thread-safe refcounting
  • DEBUG builds verify thread usage by:
    • marking the owning thread that allocates the object using nonsafe reference counting
    • all future refcount operations will check if they are on the owning thread, and crash if not
    • else, bad things may happen in non-debug builds

Smart pointers

  • Used to avoid needing to manually call AddRef() / Release()
  • RefPtr<T>
    • found in /mfbt
    • Used for concrete classes
  • nsCOMPtr<T>
    • Very old, XPCOM smart pointer
    • Used with interface classes: XPIDL, nsI*
    • Cooperates with many other things
      • Lots of do_Thing helpers


  • Reference counting is not perfect
  • Easy to create cyclic references => memory leaks
  • Not just C++ <=> C++ cycles, either.
    • Cycles can include JS objects!
  • Avoiding this: we tried avoiding cycles, using raw pointers (scary)

Refcounting: Cycle Collector

  • Rather than manually break cycles, periodically detect & collect cycles
    • Integration with JS GC to trigger this
    • JS GC triggers things, then cycle collector looks for cycles
  • If an object participates in cycle collection, AddRef() / Release() do extra work to notify cycle collector
  • Generally only for DOM things (not network or gfx)
  • Lots of obtuse macros for making this almost-nice

Refcounting: declaration

  • Macros handle lots of this
  • For nsISupports things (anything with XPIDL associated to it):
    • NS_DECL_ISUPPORTS single-threaded
    • Drop these macros in your class declaration

Refcounting: definition

  • In concrete nsThing.cpp, list concrete classname and list of implemented interfaces
    • NS_IMPL_SUPPORTS(nsThing, nsIThing, ...);
  • Same syntax for thread-safe and non-thread-safe classes
  • For non-nsISupports things:
    • NS_INLINE_DECL_REFCOUNTING single-threaded

Refcounting: implementation

  • Generally, the macros provide all you’ll need
  • See xpcom/glue/nsISupportsImpl.h for the grotty details

Leak Checking

  • DEBUG only
  • We have leak checking tooling for refcounted things
  • Not as complete as Valgrind / Asan
  • Refcounted classes are automatically leak checked
  • Individual malloc/new isn’t counted
    • ASan / Valgrind test runs to check leaks, but totally separate from our debug builds

Leak Checking: macros

  • Also possible to leak check non-reference counted classes
  • Helpful to ensure new code manages memory correctly
  • Include reference to MOZ_COUNT_CTOR(klassname) in all constructors
  • In destructor, MOZ_COUNT_DTOR(klassname)


  • Checking hierarchies is difficult
    • We generally avoid hierarchies in Gecko
    • MOZ_COUNT_CTOR_INHERITED is available, but rarely used
    • Generally just use MOZ_COUNT_CTOR in the base class
  • Checking templates classes is difficult
    • Solution: use a non-templated base class
    • Same approach as inherited / hierarchies

More info:

  • BloatView, MDN article (RIP TODO find this)
    • Runs on DEBUG tests by default
    • Also has info on leaks not reproducible locally

Refcounting: ownership transfers

  • Want to avoid passing raw pointers to refcounted classes
  • You have a smart pointer to some class, allocate it, do stuff with it
  • How do you get a pointer to that object out of your method?
  • Can’t just return the raw pointer: the ref will get released, you’ll be passing a pointer to freed memory 🙀

Ownership transfer strategies

  • As usual with Gecko, there are many ways to do it
  • Oldest: outparam T**
    • Some class returns a pointer to T*, so, class pointer pointer
    • Common with XPIDL-style interfaces
  • More common on C++ side: already_AddRefed<T> return value
    • Generally indicates new object entirely yours
    • Means: I’m giving you a pointer that has had AddRef() called on it
    • Typically used in create interfaces as a return value
    • Means also: need to Release() it at some point
    • Can’t do much with it except assign it to a smart pointer
  • “Modern” way: return RefPtr<T> or nsCOMPtr<T>
    • Pass around or return smart pointers themselves
    • With recent C++ standard changes, now actually efficient
      • In the past, might or might not be efficient
    • Not currently super safe with our current smart pointer implementation with current compilers
    • Planning to make improvements

Ownership transfers: summary table

  Pointer outparam already_AddRefed<T> Smart pointer
Identifying feature T** argument (usually last in args list, in XPIDL) return type return type
Passing ownership from a smart pointer p.forget(outparam); (stores the wrapped pointer into the outparam) return p.forget(); return p;
Caller (Starting with RefPtr <T> p; o->Method(getter_AddRefs(p)); p = o->Method(); p = o->Method();
Use cases XPIDL-style interfaces, especially for helper methods called by XPIDL methods static Create() methods many

QueryInterface: XPCOM’s dynamic_cast

Next couple sections: brief digression into nuts & bolts of XPIDL, XPCOM

  • Every class/interface in Gecko gets a UUID
    • tied specifically to the interface
  • Given a random pointer, nsISupports pointer
  • Can check if the pointer supports a given interface by passing it a UUID
  • We have infrastructure to simplify these checks


  • Given pointer nsCOMPtr<nsIFoo> foo
  • If you want to know if it supports nsIBar
  • Just do nsCOMPtr<nsIBar> bar = do_QueryInterface(foo);
  • bar will be non-null if foo was non-null and supports nsIBar
  • Handles null-checking foo, looking up interface UUID for nsIBar, looking up list of interface UUIDs supported by foo
  • Because QI does the null-check for you, you can QI through multiple interfaces and only null-check once at the end.


  • Used for creating objects across C++ and JS (for XPIDL)
  • Because we don’t use exceptions in C++, the constructor cannot return an error
  • So, we construct a minimal object, then call an init() method to finish initializing
  • To avoid using UUIDs, we have a thing called “contract ID” (typically defined in IDL)
    • Example: “;1”.
    • Stays the same even if the interface changes (hence, the UUID must be updated)
    • Reduces churn due to UUID evolution / interface evolution
  • Syntax enforces creation/initialization separation
    • Makes it easy to get ns_result from init() and relay to JS as a JS Exception
  • C++ syntax:
    • construction: nsCOMPtr<nsIFoo> f = do_CreateInstance(";1");
    • initialization: nsresult rv = f->init();
  • JS syntax:
    • construction: let f = Cc[";1"].createInstance(nsIFoo);
    • initialization: f.init();
  • On the C++ side, typically better to just directly init via the constructor, instead of doing all this indirection
  • On the JS side, this machinery is unavoidable, so these patterns are necessary


  • Services are XPCOM’s singletons
  • They are lazily created by do_GetService()
  • Enforced only by convention: if you do_CreateInstance instead, strange things may happen
    • Weirdness is especially likely if you do_CreateInstance more than once
  • do_GetService is prohibitively slow for common services
  • Instead, we have a faster way to get common services: services:: (aka Services.jsm on JS side)
    • defined in xpcom/build/ServiceList.h
  • Syntax examples:
    • do_GetService: nsCOMPtr<nsICatchyService> service = do_GetService(";1");
    • faster services global: nsCOMPtr<nsICatchyService> service = services::GetCatchyService();


  • Was meant to replace attribute access
  • nsIInterfaceRequestor is a fairly common interface
  • Used to ask an object if it has something corresponding to some interface UUID
  • Could return anything that conforms to the UUID:
    • might return a direct member of the concrete class
    • or the class itself
    • or something else entirely that happens to implement that interface UUID among many others
  • This is not a good pattern
    • e.g. browser code will QI to nsIInterfaceRequestor,
    • then get some interface,
    • then query that interface to nsIInterfaceRequestor, etc.
  • Long, ugly chain of operations to do something relatively simple
  • To make matters worse, it’s not always clear if you should do_GetInterface or QueryInterface
  • Don’t use this in new code.
  • Instead, just provide an attribute or method to access a concrete thing, and sidestep the UUID machinery
    • Example: given nsCOMPtr<nsIFoo> foo. How to get an nsIBar from foo?
      1. nsCOMPtr<nsIInterfaceRequestor> iir = do_QueryInterface(foo);
      2. nsComPtr<nsIBar> bar = do_GetInterface(iir);
    • bar is non-null if foo was non-null and supported nsIInterfaceRequestor and knew how to provide nsIBar things
    • null-check bar just once at the end and you’re done
  • Again: you may see this, but don’t use it in new code.

A few final bits still need to be copied over from my paper notes:

  • Gecko C++ Data Structures
    • XPCOM / MFBT Data Structures: Growable vectors, hashtables, lists, strings, string helpers
  • NSPR logging
  • Debugging: ./mach run, rr (time-traveling record replay debugger, linux only)