Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7ee1ecb
PORT-0 (T118): checked registry of Chrome source citations
thejackshelton Sep 30, 2026
9be7d41
Merge origin/master into port-0-registry
thejackshelton Sep 30, 2026
1304bc6
PORT-0 amendment (T118J ruling): per-entry LGPL rulings, clean-room l…
thejackshelton Sep 30, 2026
680fa23
Regenerate native engines after the comment-only units.ts/linebreak.t…
thejackshelton Sep 30, 2026
e53dd19
PORT-0: scan JavaScript sources too; refuse bad registry input in the…
thejackshelton Sep 30, 2026
de628aa
Merge remote-tracking branch 'origin/master' into port-0-registry
thejackshelton Oct 1, 2026
56dd9b8
PORT-0: THIRD_PARTY_NOTICES.md in .macroscope/ignore.md as generated …
thejackshelton Oct 1, 2026
a412e76
Host-only packages/parity/out/lanes.json on the merge of origin/maste…
thejackshelton Oct 1, 2026
47d53f5
PORT-0: list css/math.ts MathGrammar under css_parsing_utils.cc (a ci…
thejackshelton Oct 1, 2026
5c83dec
Merge origin/master into port-0-registry (generated conflicts taken f…
thejackshelton Oct 1, 2026
28b6ff4
Regenerate native engines on the merge of origin/master (pnpm native:…
thejackshelton Oct 1, 2026
ca23dba
PORT-0 catch-up: register the Chrome citations master added since the…
thejackshelton Oct 1, 2026
e6b5407
Regenerate on the PORT-0 catch-up: pnpm notices:gen (THIRD_PARTY_NOTI…
thejackshelton Oct 1, 2026
6154bed
packages/parity/out/lanes.json from the device run on the PORT-0 catc…
thejackshelton Oct 1, 2026
d95373d
PR #52 round 1: chrome-ports.test.ts scans every code file (.tsx/.jsx…
thejackshelton Oct 1, 2026
2ff5683
chrome-ports.test: compare this test's own path with git's '/'-separa…
thejackshelton Oct 1, 2026
f0d24a8
chrome-ports.test: a citation is a whole path token: no path characte…
thejackshelton Oct 1, 2026
9c0b902
Merge origin/master into port-0-registry (lanes.json taken from maste…
thejackshelton Oct 1, 2026
8818d67
chrome-ports.test pre-push re-audit: the non-script scan also reads S…
thejackshelton Oct 1, 2026
be0bd70
packages/parity/out/lanes.json from the device run after merging orig…
thejackshelton Oct 1, 2026
56f438e
PR #52 round 5: a registry symbol must be a top-level declaration, so…
thejackshelton Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ packages/wpt/generated/
.vercel/
packages/dragon/README.md
packages/dragon/LICENSE
packages/dragon/THIRD_PARTY_NOTICES.md
2 changes: 2 additions & 0 deletions .macroscope/ignore.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ docs/research/inline-spike/probe/**
docs/research/skia-aa-oracle/**
docs/research/skia-oracle/**
docs/research/dtxt/widths.json
# Written by scripts/gen-third-party-notices.ts (pnpm notices:gen) from docs/ports.json; chrome-ports.test.ts checks it is current
THIRD_PARTY_NOTICES.md
# General shapes, so a new capture directory is skipped without an edit here
**/captures/**
**/reftest-captures/**
Expand Down
475 changes: 475 additions & 0 deletions THIRD_PARTY_NOTICES.md

Large diffs are not rendered by default.

1,862 changes: 1,862 additions & 0 deletions docs/ports.json

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions docs/ports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Chrome ports registry

`docs/ports.json` lists every Chrome source file that Dragon's TypeScript cites, following the decision "Porting Chrome's algorithms (owner, 2026-09-30)" in `docs/decisions.md`. `packages/parity/test/chrome-ports.test.ts` checks it offline on every `pnpm test`.

## What an entry records

- `upstream`: the path in the Chromium tree at tag `145.0.7632.6`. Skia files are under `third_party/skia/` and come from the Skia revision that the tag's DEPS pins (`skiaRevision`); V8 files are under `v8/` at the DEPS-pinned V8 revision (`v8Revision`).
- `citedAs`: old file names that Dragon comments still use (for example `ng_length_utils.cc`, which is `length_utils.cc` at the tag).
- `licence`: the kind of header on the file: `bsd-chromium` ("The Chromium Authors"), `bsd-skia`, `bsd-google`, `bsd-apple`, `bsd-other`, `mit-harfbuzz`, `fdlibm-sun` (the Sun Microsystems fdlibm notice in V8's `ieee754.cc`), or `lgpl`. `licencePhrase` is the sentence in the header that sets the kind, and `copyright` is the header's first copyright lines.
- `fileSha256` and `headerSha256`: the sha256 of the whole file at the tag, and of its header (the file's leading comment lines up to the first code line, blank lines between comment blocks included, trailing blank lines dropped, ending with one newline). When Chrome moves to a new tag, a changed `fileSha256` shows which ports to diff and carry over.
- `ranges`: the upstream lines of the ported code. `cited` ranges come from the Dragon comment. `located` ranges are the definitions of the upstream functions or constants that the comment names. `whole-file` means the comment cites the file as a whole.
- `dragon`: each Dragon file and top-level declaration that cites the file (`symbol` is `null` when a file header cites it). `use` is `port` when the code reproduces the upstream code, and `reference` when a deviation record, report, test or capture script only points at it.
- `ruling`: the T118J ruling (`docs/goals/milestone-2-proof/notes/T118J-lgpl-ruling.md`), required on every `lgpl` entry. `class` A means Dragon follows the spec or a Chrome observation, so its uses are `reference`. B means Dragon's code follows the LGPL code closely and must be rewritten clean-room (`task` T123); only these files are on the test's `KNOWN_LGPL_CLEAN_ROOM` list, and only they may keep a `port` use until T123 lands. C marks a port from a BSD file that replaces an LGPL one. `basis` gives the spec section or "Chrome observation", and `proof` lists the tests that pin the behaviour (`path`, `path:n`, `path:a-b` or `path#text`; each must exist). A ruling with no proof says why in its `note`.
- `note` and `attribution`: free text. `attribution` records a notice that must stay with the ported code (rapidhash, fdlibm).
- `noticeText`: the id, in `licenceTexts`, of the licence text that covers the file in `THIRD_PARTY_NOTICES.md`. `chromium-bsd` and `skia-bsd` are the Chromium and Skia LICENSE files; `header-bsd-N` are the BSD texts found in file headers (Apple and Google); `lgpl` entries have none.

`notChrome` lists cited files that are not Chrome code (ICU, JNI and C standard headers, HarfBuzz's build source and Dragon's own C header): `cited` holds each path exactly as a source file writes it, and `files` the files that cite it. Only those exact paths are exempt, so a Chrome file with the same name still needs an entry.

## What the test checks

- Every `.cc`, `.cpp`, `.mm` or `.h` file named in a comment (nested block comments included), a string (multi-line strings included) or a C `#include <...>` of a code file, or anywhere in a Markless `.tsrx` file or module map, under `packages/`, `scripts/` or `examples/` (TypeScript and JavaScript in every variant including `.tsx`, `.jsx` and `.d.ts`; Swift, Kotlin, Java, Zig, C-family, `.tsrx` and module maps; shell; files git tracks or would track; generated output is skipped) resolves to an exact `notChrome` path or to exactly one entry (by full path, by a trailing part of the path, or by a `citedAs` name), and that entry lists the citing file.
- Every `lgpl` entry has a ruling; none has a `port` use unless it is class B and on `KNOWN_LGPL_CLEAN_ROOM`; every proof file exists; the licence phrase matches the kind.
- A `bsd-other` or `fdlibm-sun` file's copyright line and every paragraph of its licence text stay in each Dragon file that ports it (compared without comment markers or line wrapping).
- Every tracked file in those directories is either scanned code or known data, so a new kind of code file fails the test until the scan covers it.
- `THIRD_PARTY_NOTICES.md` is exactly what `pnpm notices:gen` (`scripts/gen-third-party-notices.ts`) writes from `docs/ports.json` and `vendor/harfbuzz/COPYING`. The published `dragon` package ships it next to `LICENSE`.
- Every `dragon` file exists, every `symbol` is still a top-level declaration in it, and the file still cites the entry.
- A `notChrome` path is never under a Chromium source root and never names a registry entry.
- The skipped directories (`generated/`, `build/`, `dist/` and the like) hold only code that says it is generated.

## Adding a port

1. Cite the upstream file in the Dragon comment, with the full path when a bare name could match more than one entry.
2. Fetch the file at the tag: `curl -s "https://chromium.googlesource.com/chromium/src/+/refs/tags/145.0.7632.6/<path>?format=TEXT" | base64 -d > file` (Skia: `https://skia.googlesource.com/skia/+/<skiaRevision>/<path under third_party/skia>?format=TEXT`; V8: `https://chromium.googlesource.com/v8/v8/+/<v8Revision>/<path under v8>?format=TEXT`).
3. Read the header. If it is LGPL, do not port the file. Implement from the spec text and match Chrome by test.
4. Add the entry with `shasum -a 256 file` for `fileSha256`, the sha256 of the header for `headerSha256`, the line range, the Dragon file and declaration, and its `noticeText` (add the header's licence text to `licenceTexts` if it is a new one).
5. Run `pnpm notices:gen` and commit `THIRD_PARTY_NOTICES.md`.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"pr:review": "node scripts/pr-review.ts",
"layout:subset": "node packages/translate/src/cli/subset.ts",
"linebreak:gen": "node scripts/gen-linebreak-data.ts",
"notices:gen": "node scripts/gen-third-party-notices.ts",
"linebreak:conformance": "node scripts/gen-linebreak-data.ts --conformance",
"native:gen": "node packages/translate/src/cli/gen.ts",
"native:swift": "node packages/translate/src/cli/native.ts swift",
Expand Down
5 changes: 3 additions & 2 deletions packages/dragon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
},
"files": [
"build/**/*.js",
"build/**/*.d.ts"
"build/**/*.d.ts",
"THIRD_PARTY_NOTICES.md"
],
"publishConfig": {
"exports": {
Expand All @@ -37,6 +38,6 @@
"@dragon/layout": "workspace:*"
},
"scripts": {
"prepack": "cp ../../README.md ../../LICENSE ."
"prepack": "cp ../../README.md ../../LICENSE ../../THIRD_PARTY_NOTICES.md ."
}
}
3 changes: 2 additions & 1 deletion packages/dragon/src/css/units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export type UnitEntry = { readonly unit: string; readonly dimension: 'length'; r
/** The canonical length unit: a unitless zero length (CSS2 §4.3.2) resolves to 0px. */
export const CANONICAL_LENGTH_UNIT = 'px';

// Blink css_primitive_value.h: the absolute-unit ratios, each derived in double exactly as Chrome derives it.
// css-values-4 §6.2 absolute lengths: 1in = 96px = 2.54cm, 1cm = 10mm, 1mm = 4Q, 1in = 72pt = 6pc. Each ratio is computed in
// double from px per inch or per cm as written below; units.test.ts pins the results to Chrome's (css_primitive_value.h).
const PX_PER_IN = 96;
const PX_PER_CM = PX_PER_IN / 2.54;
const PX_PER_MM = PX_PER_CM / 10;
Expand Down
11 changes: 6 additions & 5 deletions packages/dragon/src/fonts/family-list.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// The font-family property value as Chrome 145 parses and serializes it: css_parsing_utils.cc ConsumeFontFamily and
// css_markup.cc SerializeFontFamily at 145.0.7632.6.
// The font-family property value as Chrome 145 parses and serializes it: css_parsing_utils.cc ConsumeFontFamily for parsing;
// serialization follows CSSOM (serialize a string, a family name as an identifier when it is one), and matches what Chrome
// 145.0.7632.6 prints (Chrome does this in css_markup.cc; checked by escapes.test.ts and fonts/wire.test.ts).
import { asciiLower, tokenize, TokenStream } from './css-tokens.ts';
import { consumeFamilyName, consumeGenericFamily } from './font-face.ts';

Expand Down Expand Up @@ -31,7 +32,7 @@ const INFERRED_GENERIC = ['cursive', 'fantasy', 'monospace', 'sans-serif', 'seri
const isNameStart = (c: number): boolean => (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || c === 0x5f || c >= 0x80;
const isNameChar = (c: number): boolean => isNameStart(c) || (c >= 0x30 && c <= 0x39) || c === 0x2d;

/** css_markup.cc IsCSSTokenizerIdentifier. */
/** Whether s is one CSS ident (css-syntax-3: it would start an ident sequence and holds only ident code points), as Chrome's css_markup.cc tests it. */
function isTokenizerIdentifier(s: string): boolean {
const cps = [...s].map((c) => c.codePointAt(0) as number);
let i = 0;
Expand All @@ -46,7 +47,7 @@ export function isInvalidFontFamily(name: string): boolean {
return CSS_WIDE.includes(asciiLower(name)) || asciiLower(name) === 'default' || INFERRED_GENERIC.includes(name) || !isTokenizerIdentifier(name);
}

/** css_markup.cc SerializeString. */
/** CSSOM "serialize a string": control characters as hex escapes, quote and backslash escaped (Chrome's css_markup.cc prints the same). */
export function serializeString(s: string): string {
let out = '"';
for (const ch of s) {
Expand All @@ -58,7 +59,7 @@ export function serializeString(s: string): string {
return `${out}"`;
}

/** css_markup.cc SerializeFontFamily. */
/** A family name as CSSOM serializes it: bare when it is a valid family identifier, else a quoted string (as Chrome's css_markup.cc does). */
export function serializeFamilyName(name: string): string {
return isInvalidFontFamily(name) ? serializeString(name) : name;
}
Expand Down
41 changes: 38 additions & 3 deletions packages/dragon/src/fonts/wtf-hash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
// The iteration order of a Blink HeapHashMap keyed by FontSelectionCapabilities. FontFaceCache picks the first best capability
// group in that order, so a tie between groups is decided by it. Chromium 145.0.7632.6 sources are cited per function.
//
// rapidhash below is ported from third_party/rapidhash/rapidhash.h in Chromium 145.0.7632.6, under this notice:
//
// rapidhash - Very fast, high quality, platform-independent hashing algorithm.
// Copyright (C) 2024 Nicolas De Carli
//
// Based on 'wyhash', by Wang Yi <godspeed_china@yeah.net>
//
// BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// You can contact the author at:
// - rapidhash source repository: https://github.com/Nicoshev/rapidhash

const M64 = (1n << 64n) - 1n;
// third_party/rapidhash/rapidhash.h: RAPID_SEED and rapid_secret.
Expand Down Expand Up @@ -51,9 +86,9 @@ export function capabilitiesHash(uniqueValues: readonly [number, number, number]
}

/**
* wtf/hash_table.h: insert probes i = (i + probe_count) & mask from h & mask; after an insert, ShouldExpand
* ((keys + deleted) * kMaxLoad(2) >= size) doubles the table (kMinimumTableSize 8) and reinserts in old slot order.
* Returns the keys in iteration order (slot order).
* Dragon's model of the slot order of an open-addressed hash table like Chrome's (wtf/hash_table.h): 8 slots to start, keys
* placed at hash & mask with quadratic (triangular) probing, the table doubled and refilled in old slot order once it is half
* full. Returns the keys in iteration order (slot order); fonts/units.test.ts "selection ties" checks the result against Chrome.
*/
export function hashTableOrder<K>(keys: readonly K[], hash: (k: K) => number): K[] {
let size = 0;
Expand Down
5 changes: 3 additions & 2 deletions packages/dragon/src/forms/appearance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// The used appearance of a button or range: Blink layout_theme.cc AdjustAppearanceWithAuthorStyle and IsControlStyled.
// A push button with an author background or border devolves to CSS painting; a slider never does. A range's thumb has its own
// The used appearance of a button or range, per css-ui-4's devolvable widgets and the devolve probe captured from Chrome
// (appearance.generated.ts; Chrome decides this in layout_theme.cc). A push button with an author background or border devolves
// to CSS painting; a slider never does. A range's thumb has its own
// appearance (html.css gives ::-webkit-slider-thumb appearance:auto), so the whole range is CSS-painted only when the input and the
// thumb are both appearance:none.
import { NO_FORM_FAULTS } from './faults.ts';
Expand Down
8 changes: 4 additions & 4 deletions packages/dragon/src/forms/button-inner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// How Chrome 145 lays out a button's contents (the Blink rule FORM-a moves into the engine):
// - html_button_element.cc CreateLayoutObject: a button whose display is flex, inline-flex, grid, inline-grid (or grid-lanes,
// layout custom) is an ordinary container of that kind; every other button is a LayoutBlockFlow. There is no anonymous inner
// box in Chrome 145 (LayoutButton and its UpdateAnonymousChildStyle are gone).
// How Chrome 145 lays out a button's contents (the Blink rule FORM-a moves into the engine), as HTML's button rendering section
// describes it and forms.test.ts checks against Chrome:
// - a button whose display is flex, inline-flex, grid or inline-grid is an ordinary container of that kind; every other button
// lays out as a block with no anonymous inner box (Chrome makes this choice in html_button_element.cc).
// - html.css `button { -internal-align-content-block: center }`, applied by block_layout_algorithm_utils.cc AlignBlockContent:
// the block button's in-flow contents move down by free space / 2, free space clamped at 0 for buttons (safe centring),
// whatever the author's align-content.
Expand Down
7 changes: 4 additions & 3 deletions packages/dragon/src/images/natural-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ export type ImageHeader = { readonly format: 'png'; readonly facts: PngFacts } |
const INCH = 2;
const DEFAULT_DPI = 72;

/** round(physical * 72 / (num / den)) === stated, in exact integers; std::round rounds halves up for these positive values. */
/** round(physical * 72 / (num / den)) === stated, in exact integers; rounding halves up, as Chrome does for these positive values. */
const roundsTo = (physical: number, [num, den]: readonly [number, number], stated: number): boolean =>
(2 * stated - 1) * num <= 2 * physical * DEFAULT_DPI * den && 2 * physical * DEFAULT_DPI * den < (2 * stated + 1) * num;

/**
* Blink ExtractDensityCorrectedSize (image_decoder.cc): with ResolutionUnit inch, a non-empty resolution and the Exif pixel
* dimensions, the pixel dimensions are the size when the physical size scaled by 72 / resolution rounds to them.
* The density-corrected natural size (HTML §4.8.4.3.6): with ResolutionUnit inch, a non-empty resolution and the Exif pixel
* dimensions, the pixel dimensions are the size when the physical size scaled by 72 / resolution rounds to them. Dragon works
* in exact rationals; images.test.ts pins the rules measured in Chrome (which applies them in image_decoder.cc).
*/
export function densityCorrectedSize(physical: NaturalSize, exif: ExifFacts | null): NaturalSize {
if (exif === null || exif.resolutionUnit !== INCH || exif.resolution === null || exif.pixelSize === null) return physical;
Expand Down
Loading
Loading