← How much Rust in Firefox?

Methodology

How the numbers on How much Rust in Firefox? are produced, what they mean and how far to trust them. Every rule below is taken from the code that produces the data (dev/history.py and .github/workflows/deploy.yml). Figures measured for this document are at release 157 (FIREFOX_157_0_RELEASE, commit fdd757a2) unless another commit is named.

In short

The page counts lines in files, grouped into ten languages by file extension, for every major Firefox release from 46 to the newest one and for the current head of the default branch of mozilla-firefox/firefox. It offers two views of the same releases:

ViewWhat is counted
All filesEvery file tracked by git at the release tag (or the head) with a counted extension, mobile/ included
Browser filesThe same files minus mobile/ (mostly Android code), minus ten directories of build and developer tooling (vendored Python and Node packages included) and minus the files matched by the test rules

The charts combine two pairs of languages: JavaScript and TypeScript as one series, JavaScript/TypeScript, and Java and Kotlin as one series, Java/Kotlin, so they show eight series for the ten languages: the page has eight series colours and does not add more. The data keeps each pair apart (js and ts, java and kt), and the page's tooltips (and its tables, in Lines mode) give the split.

Firefox 157 in Browser files, headers split as described below:

LanguageLinesShare
C++11,689,44645.8%
Rust5,613,87322.0%
C4,693,30718.4%
JavaScript/TypeScript (JavaScript 2,270,727, TypeScript 67,204)2,337,9319.2%
Assembly531,3632.1%
Python319,0291.3%
HTML/CSS182,9250.7%
Java/Kotlin (Java 55,497, Kotlin 72,324)127,8210.5%
Total25,495,695100%

C and C++ include their share of the .h lines (fractional lines are rounded here). The Rust share is 22.02% before rounding. In All files at 157 Rust is 6,172,779 of 51,249,799 lines, 12.04%.

What a "line" is

Which files

The counter reads git objects only, never a working tree: git ls-tree -r <commit> lists every entry of the commit's tree and git cat-file --batch reads the content of each counted blob once.

Extensions

Matching is case-sensitive and uses the last extension only (foo.sys.mjs counts as .mjs; .C or .JS would not be counted; the 157 tree has no such files). .S and .s are both listed, so both count.

Key in the dataShown asExtensions
rustRust.rs
cC.c
cppC++.cc .cpp .cxx .hxx .hpp .hh
hsplit between C and C++.h
jsJavaScript/TypeScript (with ts).jsm .jsx .js .mjs
tsJavaScript/TypeScript (with js).ts
htmlHTML/CSS.htm .html .xhtml .xht .css
pyPython.py
javaJava/Kotlin (with kt).java
ktJava/Kotlin (with java).kt
asmAssembly.asm .S .s

JavaScript and TypeScript, and Java and Kotlin, are stored separately and added together for the JavaScript/TypeScript and Java/Kotlin series on the charts (see "In short").

Everything else is left out, both from the language lines and from the total that shares are computed against. Some sizeable uncounted source extensions in the whole 157 tree, mobile/ included (examples, not a complete ranking; the 2,853 moz.build files, for instance, hold 224,982 lines and the .idl files 130,809):

ExtensionFilesLines
.inc147260,473
.mm (Objective-C++)416135,189

Kotlin script files (.kts) are not counted either: matching uses the last extension and only .kt is listed. Adding any of these extensions is a method change (see "Changing the method").

Headers

.h files can be C or C++, and the extension does not say which. The data keeps their lines apart, as h, and the page splits them at display time with the ratio stored in the data (header_split): 18.5% to C and 81.5% to C++. The code records only the figure ("by location about 81.5% of headers are C++"), not how it was measured or on which commit, and the same ratio is applied to every release and to both views, so treat the C/C++ boundary as approximate. The split moves lines between C and C++ only; the Rust share and the totals do not depend on it. .hpp and .hh are C++ headers and count as C++ directly (key cpp); only .h is split.

The two views

All files

Every counted file at the commit, with no path excluded. This is the closest to what the original chart measured (that chart ran git ls-files and wc -l at the head, did not count .mjs and split headers 1/3 to 2/3). It is dominated by tests and test data: web-platform-tests (testing/web-platform/tests/, about 163,000 files at 157) and test262, which is vendored twice since 152 (js/src/tests/test262/ and a copy inside web-platform-tests; see "Reading the series").

Browser files

The same files minus the paths under the prefixes of browser_excluded_prefixes (mobile/ and ten tooling directories) and minus test paths (is_test() in dev/history.py). Everything else stays: the desktop code for every platform (Windows, macOS, Linux and the others), vendored third-party code whether or not a given build compiles it, devtools and so on. It is not "what ships in the download": it is what the repository holds outside the mobile apps, the listed tooling directories and the tests.

The excluded prefixes are plain path-prefix matches from the top of the repository, like mobile/. They were picked by hand from the 157 tree and checked against older releases. Lines are the non-test lines a prefix removes from Browser files at 157:

PrefixWhat it isWhy it is excludedLines at 157
mobile/The Android apps: Firefox for Android, Focus, GeckoView, Android ComponentsNot the desktop browser550,595
third_party/python/Vendored Python packages (pip, setuptools, pygments, aiohttp with its C code, ...)Used by the build system and the tools, not by the browser891,420
third_party/node/Vendored Node packages: webpack, Babel and their dependencies, and the React/Redux runtime packages (80,369 lines with their small dependencies)Used at build time to bundle code; new at 157 (see below)706,931
python/mach, mozbuild, mozlint and the other Mozilla Python tools; vendored Python packages too before 55Build system and developer tools103,933
tools/@types/TypeScript declaration files (.d.ts) describing Gecko's APIsOnly type-check the JavaScript; never compiled or shipped84,999
taskcluster/The CI task graph, its Docker images and CI scriptsContinuous integration44,332
tools/lint/The linters: ESLint configuration and plugins, Python and Rust lint codeDeveloper tooling21,607
build/clang-plugin/Mozilla's clang static-analysis pluginRuns inside the compiler; not part of the browser12,311
build/pgo/The pages and server used to train profile-guided-optimisation buildsBuild-time training input11,735
tools/tryselect/mach try, which picks the CI jobs to run on the try serverDeveloper tooling9,442
docs/The Firefox source documentation (Sphinx configuration and extensions)Documentation1,694

The ten tooling prefixes remove 1,888,404 lines at 157, most of them vendored (third_party/python/ and third_party/node/ together 1,598,351): 956,435 Python lines, 702,552 JavaScript, 105,951 TypeScript, 94,744 C, 13,384 C++ and headers, 12,896 HTML/CSS and 2,442 Rust (tools/lint 1,835, taskcluster 607). Not everything under build/ and tools/ is tooling, so those directories are not excluded as a whole: tools/profiler/ is the Gecko Profiler, compiled into the browser (47,628 lines at 157); tools/fuzzing/ and tools/performance/ are compiled into Gecko too; build/unix/ holds elfhack and stdc++compat, which end up in Linux builds; build/rust/ holds small crates linked into libxul; and build/stlport/ (releases 46 to 51, 85,629 lines) was the C++ runtime of Android builds. config/ (5,614 lines at 157) mixes build scripts with wrapper headers used when compiling the browser, so it stays as well.

third_party/node/ is not pure tooling. At 157 the new tab page's build script (browser/extensions/newtab/build-newtab-bundles.py) runs webpack on third_party/node/node_modules, and its vendor bundle (content-src/vendor.mjs) imports React, ReactDOM, Redux, React Redux, PropTypes and React Transition Group from there. Code from those packages (80,369 lines in the tree) therefore reaches the shipped new tab page in bundled form, yet the packages are left out of Browser files with the rest of the directory. A second copy of React and Redux, toolkit/content/vendor/react/ (30,430 lines at 157, packaged by toolkit/content/jar.mn), is not under any excluded prefix and stays counted.

A path is a test path if any of these hold:

  1. It starts with testing/, js/src/tests/, js/src/jit-test/, js/src/jsapi-tests/, js/src/octane/ or third_party/webkit/PerformanceTests/.
  2. Any directory in it is named test, tests, gtest, gtests, mochitest, mochitests, xpcshell, reftest, reftests, crashtest, crashtests, __tests__, androidTest, test262, unittests, googletest, testdata, fixtures, browser_tests, jsapi-tests or jit-test; or ends in -test, -tests, _test or _tests; or starts with test-, tests-, test_ or tests_; or is a testing directory below the top level.
  3. It is a Rust file named tests.rs, test.rs, or ending in _test.rs or _tests.rs.

Limits of the test rules:

Releases and dates

The head point

The head is the commit the workflow's depth-1 checkout of mozilla-firefox/firefox lands on (the tip of the default branch). It is counted on every workflow run with the same counter and rules as a release. It lives only in the deployed build/data.json, never in data/history.json, because it changes every week.

The weekly pipeline

.github/workflows/deploy.yml is scheduled for every Sunday at 22:12 UTC and also runs on every push to main, on pull requests and by hand. GitHub often starts scheduled runs late: the 59 scheduled runs from 2025-08-24 to 2026-10-05 started between 22:18 and 00:53 UTC, the last five after 23:50, so a week's data can lag until early Monday (UTC). Those runs all ran the workflow of an earlier version of this repository with the same schedule; as of 2026-10-06 the current workflow has not yet run on a schedule (the first is due on 2026-10-11). One job, at most one run at a time per ref:

  1. Test: python3 -m unittest discover -s dev (no network).
  2. Append: history.py append lists the remote release tags (git ls-remote --tags), and for each major missing from data/history.json fetches its tag at depth 1, counts it and appends one line.
  3. Build: history.py build-site counts the head and writes build/data.json; the page, its icons and this document (rendered to methodology.html by dev/render_docs.py) are added, and a check fails the step if an output file is missing or empty, the site data does not match data/history.json or the stored releases are not 46, 47, 48 and so on without a gap.
  4. Commit (only on main, never for a pull request): if data/ changed, commit data: add releases to main.
  5. Deploy (only on main, never for a pull request): publish build/ to the gh-pages branch.

A failure in steps 1 to 3 commits and deploys nothing.

Data files

data/history.json (committed)

One header line, then one release per line (a new release is a one-line diff). Shortened:

{"method_version":4,"browser_excluded_prefixes":["mobile/","build/clang-plugin/","build/pgo/","docs/","python/",
 "taskcluster/","third_party/node/","third_party/python/","tools/@types/","tools/lint/","tools/tryselect/"],
 "header_split":{"c":0.185,"cpp":0.815},"releases":[
{"v":157,"tag":"FIREFOX_157_0_RELEASE","sha":"fdd757a2...","date":"2026-09-24",
 "all":{"rust":6172779,"c":3992382,"cpp":8522133,"h":5075167,"js":16228932,"ts":532871,"html":6838595,"py":2159226,
        "java":147346,"kt":1048566,"asm":531802},
 "browser":{"rust":5613873,"c":3795203,"cpp":7732935,"h":4854615,"js":2270727,"ts":67204,"html":182925,"py":319029,
            "java":55497,"kt":72324,"asm":531363}}
]}
FieldMeaning
method_versionVersion of the counting rules the records were made with (4)
browser_excluded_prefixesPath prefixes skipped in Browser files (mobile/ and the ten tooling prefixes); All files skips no path. append and build-site read the list from here, so new records follow the stored one
header_splitShare of h lines given to C and to C++ at display time
v, tag, sha, dateMajor version, the tag counted, its commit and the commit's date
all, browserLines per language key in each view; c and cpp exclude .h headers, which are h; js and ts, java and kt are separate

Totals are not stored: a total is the sum of the eleven language keys. all minus browser is the lines of test files, of mobile/ and of the tooling prefixes together, not the test lines alone.

build/data.json (deployed as data.json)

data/history.json plus a head object (sha, date, all, browser, the same shapes) and three fields kept for readers of the old chart: meta_date (time of the run), title_date (for example Oct 2026) and lang (lines per language at the head, All files, headers split). Consumers should sum only the eleven language keys.

Changing the method

method_version is 4: the rules described here, which count Kotlin (.kt), TypeScript (.ts), .hpp and .hh as C++ and .S and .s as Assembly, count a blob with a NUL byte as 0 lines, and leave mobile/ and the ten tooling prefixes out of Browser files. Any change to the rules (the counted extensions, the test rules, the excluded prefixes or how lines are counted) bumps it and requires regenerating every release, so all stored records always follow one set of rules. Changing the header_split ratio is not a bump: headers are stored raw, so the new ratio applies to every release at display time; say so in the commit that changes it. The history of the rules themselves is in the git history of this repository.

Reproducing the numbers

Requirements: Python 3.9 or newer (standard library only) and git (2.44 or newer to regenerate the history from a partial clone, which relies on GIT_NO_LAZY_FETCH; older git gives the same counts but fetches missing blobs one at a time, which is very slow).

python3 -m unittest discover -s dev          # unit tests, no network
python3 dev/history.py --help                # the subcommands: regenerate the history, append releases,
                                             # count a head, write the site data
git clone --depth 1 https://github.com/mozilla-firefox/firefox.git firefox
# then run the Append and Build steps of .github/workflows/deploy.yml, and view the result:
python3 -m http.server -d build              # then open http://localhost:8000

Regenerating data/history.json from scratch is done locally, not in CI, on a blobless clone that has the release tags; python3 dev/history.py --help gives the commands. Rebuilding gives the same file apart from newly released versions; compare it with the committed file whenever method_version changes.

Checks

CheckResult
Unit tests (python3 -m unittest discover -s dev, run by every workflow run)The release set (125 and newest-major rules), the test rules, excluded prefixes, the file layout, the zero-timestamp rule, append and the site data on small generated repositories
Rust in All files and mobile/No .rs file under mobile/ at 157, so Rust in All files is the same with or without mobile/ (6,172,779 lines)
Java in All files at 157147,346 lines: 56,790 outside mobile/ and 90,556 under it
Unusual tree entries at 157No symbolic links, no submodules, no .C or .JS files
Kotlin at 157All files 1,048,566 lines (970,364 under mobile/), Browser files 72,324
Binary .ts files at 1579 MPEG transport streams, 33,851 newline bytes, count 0: TypeScript in All files is 532,871 lines (566,722 in all .ts files minus 33,851)
Binary rule, against counts made without it (the keys rust c h js html py java, every release)Browser files identical; All files lower only in Rust (57 lines, 119 to 157), JavaScript and HTML/CSS, at most 1,634 lines together in a release
.hpp .hh .S .s at 157All files cpp grows by exactly their 714,955 lines and asm by their 235,839: none of them is binary
Tooling prefixes, against the previous rules (mobile/ only), every releasev, tag, sha, date and All files identical; Browser files lower by exactly the non-test lines under the ten tooling prefixes, per language
Independent recount at 46, 60, 125 and 157 (own git ls-tree/git cat-file loop, own extension map and NUL rule, the same test rules)All files and Browser files equal to the stored records
Shipped code under tools/ and build/ at 157Still in Browser files: tools/profiler 47,628 lines, tools/fuzzing 15,366, tools/performance 1,381, build/unix 4,632, build/rust 1,595

Reading the series

Some steps in the charts are real changes to the repository, not counting artefacts. The steps below are explained here; others are not explained yet. For example, the Java/Kotlin series in All files rises by 105,340 lines at 55 and by 70,346 at 79 and falls by 110,629 at 151.

Known limitations and biases

What "Rust share" says, and what it does not

It says: of the newline-terminated lines in non-binary files with the counted extensions, in the chosen view, this fraction is in .rs files.

It does not say:

← How much Rust in Firefox? · Source of this page