bi-06 — Chromium C++ for Frontend Engineers
Phase 2, just-in-time · Spec area §4. Prerequisites: bi-01. Use alongside bi-03–bi-09.
Read this module in fragments, on demand. It is a reference, not a course. The correct way to use it is: hit an idiom you cannot read → look it up here → find a real example in the tree → carry on. Front-loading a C++ course before reading Blink is the single most common way people spend three weeks and learn nothing about browsers.
Cross-track hook: none.
Why a Principal Engineer needs this
You need to read Chromium C++ fluently and write a small, idiomatic amount of it. Those are different bars, and conflating them is why people over-prepare.
- Reading requires: ownership idioms, callbacks, threading annotations, Oilpan handles, and the ability to ignore templates you do not need.
- Writing (a §24 rung-2/3 contribution) additionally requires: matching local style, correct handle types, and not introducing lifetime bugs. Reviewers catch the rest.
You do not need: template metaprogramming, the standard-library algorithm catalogue, move-semantics edge cases, or exception handling — Chromium builds without exceptions, which removes a large chunk of normal C++ complexity from consideration.
The reading subset
Ownership: the four pointers
This is 80 % of reading comprehension. Every pointer in Chromium answers "who owns this and how long does it live."
| Type | Meaning | Where |
|---|---|---|
std::unique_ptr<T> | sole ownership; moved, never copied | everywhere outside Blink's GC heap |
scoped_refptr<T> | shared ownership, refcounted (RefCounted<T>) | //base, //cc, task runners |
raw_ptr<T> | non-owning pointer, hardened against use-after-free | member fields in non-GC code |
T* | non-owning, no guarantees | locals, parameters |
T& | non-owning, non-null | parameters that must exist |
raw_ptr<T> surprises people coming from older C++ or older Chromium: raw pointer members
are progressively being replaced by it because it converts a large class of use-after-free
vulnerabilities into crashes. When you see it, read it as "non-owning member, and someone
thought about lifetime here."
In Blink's GC heap the vocabulary is different — that is bi-04's table (Member, Persistent,
WeakMember, …). Mixing up the two vocabularies is the most common newcomer error. Rule
of thumb: inside blink:: classes that are GarbageCollected, use Oilpan handles; everywhere
else, the table above.
RAII and Scoped*
A ScopedFoo does something in its constructor and undoes it in its destructor. When reading
a function, Scoped* locals are the "and afterwards, this is restored" markers — they often
encode the invariant more clearly than the surrounding code. Blink's parser, style engine and
compositor all use them for state that must not leak across a scope.
Callbacks
Chromium's callbacks are base::OnceCallback / base::RepeatingCallback, created with
base::BindOnce / base::BindRepeating. Documented at length in docs/callback.md.
What you must be able to read:
BindOnce(&Class::Method, receiver, args...)— the first bound argument is the receiver.OnceCallbackruns once and must be moved, not copied.std::move(callback)at a call site is why.base::Unretained(this)is an explicit assertion: "I promise this outlives the callback." Treat everyUnretainedas a lifetime claim to verify — it is where use-after-free lives.WeakPtr<T>+WeakPtrFactory<T>— the callback silently does nothing if the object died. The safe default in UI code.
Reading skill: when you see a callback, ask what keeps the receiver alive. The answer is
one of: ownership, scoped_refptr, WeakPtr (may not run), or Unretained (a promise).
Threading annotations
SEQUENCE_CHECKER(sequence_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
scoped_refptr<base::SequencedTaskRunner> task_runner_;
task_runner_->PostTask(FROM_HERE, base::BindOnce(&Foo::Bar, ...));
SEQUENCE_CHECKER is executable documentation of "this object is only touched from one
sequence" (bi-02). Read it as a hard constraint; violating it is exactly the bug class Blink's
message-passing rule exists to avoid.
Strings
Blink uses WTF::String, AtomicString, StringView; Chromium proper uses std::string,
std::u16string, std::string_view. AtomicString is interned — pointer comparison is
identity comparison, which is why tag and attribute names are atoms. Conversions across the
boundary allocate, and that is a real cost in the bindings layer (bi-05).
Containers and small types
WTF::Vector,WTF::HashMap,WTF::HashSetin Blink;base::flat_map,base::span,std::optionalelsewhere.base::span<T>— pointer+length view. Chromium is actively migrating raw pointer/length pairs to spans for memory safety; expect it in new code, not old.std::optional<T>— "may be absent," instead of sentinel values.
Enums and the NOTREACHED() idiom
enum class everywhere. A switch over an enum ending in NOTREACHED() is a contract that
the enum is exhaustive. Add an enumerator and the compiler finds the switches; NOTREACHED()
catches the ones it cannot.
Generated code shapes
You will constantly read code whose definition is not in the tree (bi-01 Technique 4). Recognise the shapes:
mojom::FooPtr,mojo::Remote<mojom::Foo>,mojo::Receiver<mojom::Foo>— IPC endpoints.V8Foo::…— generated bindings.CSSPropertyID::kFoo,html_names::kDivTag— generated from.json5.
What to ignore
Template metaprogramming, constexpr machinery, SFINAE, allocator plumbing, and most of
//base's internals. If a header looks like a type-level puzzle, it is almost certainly not
what your question is about. Skipping confidently is a skill; bi-01's deferred-list
discipline is how you do it without losing the thread.
The writing subset
For a first contribution you additionally need:
- Match the file you are editing. Style is enforced by
git cl format— run it and stop thinking about formatting. - Correct handle type. In Blink GC classes,
Member<T>for owned graph edges. Getting this wrong is the most likely substantive review comment. DCHECKyour assumptions. Adding aDCHECKstating your new invariant is usually welcomed, and it is how you document intent in a codebase that discourages comments restating code.- Tests in the right style. Blink
foo_test.cc, Chromiumfoo_unittest.cc. Copy the nearest existing test's structure exactly. - No exceptions, no RTTI. If your instinct reaches for either, the design is off.
Deep dive: the style documents that actually govern
styleguide/c++/ is where the rules live. Sizes are informative:
| Document | Size | What it governs |
|---|---|---|
c++-features.md | ~78 KB | which C++ language/library features are allowed, banned, or under discussion |
c++.md | ~20 KB | the general Chromium C++ style |
c++-dos-and-donts.md | ~18 KB | accumulated guidance and anti-patterns |
blink-c++.md | ~8 KB | Blink-specific rules on top |
const.md, checks.md | const correctness; CHECK/DCHECK policy |
Read c++-features.md before using any modern C++ feature. Chromium gates language features
explicitly — a feature being in the standard does not mean it is permitted in the tree. Seventy-eight
kilobytes of "allowed / banned / under discussion" is a governance system, not a style file, and
proposing banned constructs in a CL is a fast way to burn reviewer patience.
The Blink-specific rules
blink-c++.md is short and its section headings are the whole message:
- Prefer WTF types over STL and base types
- Do not use
newanddelete - Don't mix
Create()factory methods and public constructors in one class - Naming
- Prefer enums or
StrongAliases to bare bools for function parameters
That last one is worth adopting in your own work regardless of language. DoThing(true, false)
is unreadable at the call site; DoThing(kAnimate, kDontNotify) is self-documenting. Chromium
enforces at the type level what most style guides only suggest.
"Do not use new and delete" is the visible consequence of the ownership vocabulary: everything
is MakeGarbageCollected<T>, std::make_unique<T>, or base::MakeRefCounted<T>. If you find
yourself reaching for raw new, you have not decided who owns the object.
Deep dive: CHECK vs DCHECK, as a design decision
The distinction is not "expensive vs cheap." It is a statement about what kind of failure this is.
| Macro | Retained in release? | Means |
|---|---|---|
DCHECK(x) | no | "this should be true; if not, we have a bug" |
CHECK(x) | yes | "if this is false, continuing is unsafe — crash instead" |
NOTREACHED() | yes | "this state is impossible by construction" |
DUMP_WILL_BE_CHECK | staged | a DCHECK being promoted to CHECK, with data collection first |
The rule of thumb Chromium applies: if the invariant failing would be a security or
memory-safety problem, it is a CHECK. Crashing is preferable to continuing with a violated
invariant an attacker might exploit. If it would merely be a wrong pixel, it is a DCHECK.
Two things follow for you as a reader:
- A
CHECKis a load-bearing invariant. When you find one, you have found something the authors decided was worth an outage to protect. Read it carefully before changing nearby code. DUMP_WILL_BE_CHECKis a migration in progress — someone wants this to be aCHECKbut is collecting field data first to make sure it will not crash real users. It marks an invariant believed true but not yet trusted, which is genuinely useful context.
This is a pattern worth stealing: stage your assertions. Collect data that an invariant holds before you make violating it fatal.
Deep dive: the memory-safety programme, and why the code looks like it does
A great deal of modern Chromium C++ is shaped by an ongoing memory-safety effort. Recognising the pieces stops them looking like arbitrary style.
| Mechanism | What it does |
|---|---|
raw_ptr<T> (MiraclePtr) | hardened non-owning member pointers; turns some UAF into a crash |
base::span<T> | replaces pointer+length pairs; bounds are carried with the data |
| PartitionAlloc | the allocator, with partitioning that makes some exploitation harder |
| Rust interop | new, isolated, untrusted-input parsers written in a safe language |
The Rule of Two (bi-02) | the architectural constraint that drives sandboxing decisions |
| Clang plugins | mechanical enforcement of Blink/Chromium-specific rules |
The span migration is the one you will notice most while reading: new code takes
base::span<const uint8_t> where old code took const uint8_t*, size_t. When you see both styles
in one file, you are looking at a partially-migrated area — which is also a hint that the file is
actively maintained.
The reading skill: distinguishing house style from migration in progress. If you copy the pattern next to your change and it happens to be the old one, a reviewer will ask you to use the new one. Look for the newest code in the file, not the nearest.
Deep dive: reading a Mojo-generated interface without the generated code
You will constantly read code that calls into generated Mojo bindings. The shapes:
mojo::Remote<mojom::blink::FooService> remote_; // I call the other side
mojo::Receiver<mojom::blink::FooService> receiver_; // I implement it
mojo::PendingRemote<...> / mojo::PendingReceiver<...> // an endpoint in transit
mojo::AssociatedRemote<...> / AssociatedReceiver<...> // shares a pipe: ORDERING preserved
The Associated* variants matter more than their obscurity suggests. Ordinary interfaces get
their own message pipes, so messages on two different interfaces have no ordering relationship
(bi-02). Associated interfaces share a pipe with a parent interface, which restores ordering.
When you read
AssociatedRemote, read it as: "someone was bitten by an ordering bug here."
Note also the mojom::blink:: namespace: Blink gets its own generated variant using WTF types,
while the browser side uses mojom:: with STL types. The same .mojom file generates two
different C++ APIs, which is why you sometimes find two types with the same name and different
string types. Landing on the wrong one is a classic wasted half-hour (bi-01 Technique 3).
Deep dive: a reading checklist for an unfamiliar class
Apply in order; it takes about ten minutes and answers most of the §44 gate.
- Is it
GarbageCollected? That decides the entire ownership vocabulary. - Read
Trace()first — the authoritative list of what it keeps alive. - Scan the member types:
Member(graph edge),raw_ptr(non-owning),unique_ptr(owned),scoped_refptr(shared),WeakPtr(may vanish). - Look for
SEQUENCE_CHECKER/THREAD_CHECKER— which thread does this belong to? - Read the
DCHECKs andCHECKs — the invariants, stated executably. - Find the
Create()factory orMakeGarbageCollectedcall sites — who constructs it, and who therefore owns its lifetime? - Open the
_test.cc— the enumerated edge cases. - Only then read method bodies, entering from your actual question.
Steps 2 and 5 answer "what invariant does it maintain" better than any prose you would write, and they are two minutes of work.
Lab
Deliberately small — this module is not where the learning is.
- Pick five functions you already read in bi-03/bi-04. For each, write one sentence naming every ownership decision it makes (who owns what, what may die).
- Find one
base::Unretainedin the tree. Determine what guarantees the receiver outlives the callback. If you cannot in ten minutes, note that — say why it was hard. - Find a class with
SEQUENCE_CHECKER. Name the sequence it belongs to, and how you know. - Find one
raw_ptr<T>member and oneMember<T>member. Explain why each is right in its context and what breaks if swapped. - Read one generated binding end to end (bi-05). List every idiom you could not name, then look each up here.
Deliverable: a personal one-page cheat sheet of the idioms you got stuck on. That page is worth more than this module.
Further Reading (primary sources first)
docs/callback.md— definitive; read "Introduction" and "Quick reference for basic stuff."docs/threading_and_tasks.mdand_faq.md.base/memory/raw_ptr.h— read the header comment for the rationale.third_party/blink/renderer/platform/heap/BlinkGCAPIReference.md— Oilpan handles.styleguide/c++/in-tree — the Chromium C++ style guide and the allowed-features list. Check it before using any modern C++ feature; Chromium gates them deliberately.third_party/blink/renderer/README.md§"Type dependencies".
Principal Engineer Review
-
You see
base::Unretained(this)in a callback posted to another sequence. What must be true for this to be correct, how would you verify it, and what would you propose instead? -
raw_ptr<T>turns some use-after-free bugs into crashes. Argue this is a security improvement; then argue a crash in production is its own outage. How does Chromium's threat model settle it? -
Chromium builds without exceptions. What does this simplify, what does it make more verbose, and how are error paths expressed instead?
-
Blink has two ownership vocabularies (Oilpan and
//base). Argue for unifying them. What is the actual obstacle? -
AtomicStringmakes name comparison a pointer compare. What does interning cost, and when is it the wrong choice? -
You are reviewing a first-time contributor's CL that adds a raw
T*member to aGarbageCollectedclass. Write the review comment: correct, specific, not discouraging. -
Chromium encodes invariants in
DCHECK,SEQUENCE_CHECKER, clang plugins and presubmits rather than in comments. Make the case this is better documentation than prose — and name where it fails. -
Which parts of modern C++ would you deliberately keep out of a large codebase you owned, and what does your list say about what you optimise for?