Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
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
24 changes: 24 additions & 0 deletions inputfiles/patches/authenticator.kdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
removals {
enum AuthenticatorTransport {
smart-card // WebKit only as of 2023-05
}
dictionary AuthenticationExtensionsClientInputs {
// https://searchfox.org/mozilla-central/source/dom/webidl/WebAuthentication.webidl
// https://searchfox.org/wubkat/source/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.idl
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/credentialmanagement/authentication_extensions_client_inputs.idl
member appidExclude
member credBlob
member getCredBlob
member hmacGetSecret // No implementation as of 2025-05
member payment
}
dictionary AuthenticationExtensionsClientInputsJSON {
member appidExclude
}
dictionary AuthenticationExtensionsClientOutputs {
// (same as *Inputs)
member appidExclude // No implementation as of 2025-05
member hmacGetSecret // No implementation as of 2025-05
member payment // Blink only as of 2025-06
}
}
34 changes: 0 additions & 34 deletions inputfiles/removedTypes.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
},
"enums": {
"enum": {
"AuthenticatorTransport": {
"value": ["smart-card"] // WebKit only as of 2023-05
},
"ConnectionType": {
"value": ["wimax"]
},
Expand Down Expand Up @@ -264,37 +261,6 @@
}
}
},
"AuthenticationExtensionsClientInputs": {
"members": {
"member": {
// https://searchfox.org/mozilla-central/source/dom/webidl/WebAuthentication.webidl
// https://searchfox.org/wubkat/source/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.idl
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/credentialmanagement/authentication_extensions_client_inputs.idl
"appidExclude": null,
"credBlob": null,
"getCredBlob": null,
"hmacGetSecret": null, // No implementation as of 2025-05
"payment": null
}
}
},
"AuthenticationExtensionsClientInputsJSON": {
"members": {
"member": {
"appidExclude": null
}
}
},
"AuthenticationExtensionsClientOutputs": {
"members": {
"member": {
// (same as *Inputs)
"appidExclude": null, // No implementation as of 2025-05
"hmacGetSecret": null, // No implementation as of 2025-05
"payment": null // Blink only as of 2025-06
}
}
},
"CanvasRenderingContext2DSettings": {
"members": {
"member": {
Expand Down
3 changes: 2 additions & 1 deletion src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async function emitDom() {

const overriddenItems = await readInputJSON("overridingTypes.jsonc");
const addedItems = await readInputJSON("addedTypes.jsonc");
const patches = await readPatches();
const { patches, removalPatches } = await readPatches();
const comments = await readInputJSON("comments.json");
const documentationFromMDN = await generateDescriptions();
const removedItems = await readInputJSON("removedTypes.jsonc");
Expand Down Expand Up @@ -204,6 +204,7 @@ async function emitDom() {
webidl = merge(webidl, getRemovalData(webidl));
webidl = merge(webidl, getDocsData(webidl));
webidl = prune(webidl, removedItems);
webidl = prune(webidl, removalPatches);
webidl = merge(webidl, addedItems);
webidl = merge(webidl, overriddenItems);
webidl = merge(webidl, patches);
Expand Down
85 changes: 69 additions & 16 deletions src/build/patches.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parse, type Value, type Node } from "kdljs";
import { parse, type Value, type Node, Document } from "kdljs";
import type {
Enum,
Event,
Expand Down Expand Up @@ -77,22 +77,16 @@ function handleTypeParameters(value: Value) {
}

/**
* Converts patch files in KDL to match the [types](types.d.ts).
* Converts parsed KDL Document nodes to match the [types](types.d.ts).
*/
function parseKDL(kdlText: string): DeepPartial<WebIdl> {
const { output, errors } = parse(kdlText);

if (errors.length) {
throw new Error("KDL parse errors", { cause: errors });
}

const nodes = output!;
function convertKDLNodes(nodes: Node[]): DeepPartial<WebIdl> {
const enums: Record<string, Enum> = {};
const mixin: Record<string, DeepPartial<Interface>> = {};
const interfaces: Record<string, DeepPartial<Interface>> = {};
const dictionary: Record<string, DeepPartial<Dictionary>> = {};

for (const node of nodes) {
// Note: no "removals" handling here; caller is responsible for splitting
const name = string(node.values[0]);
switch (node.name) {
case "enum":
Expand Down Expand Up @@ -368,21 +362,80 @@ async function getAllFileURLs(folder: URL): Promise<URL[]> {
}

/**
* Read and parse a single KDL file.
* Read and parse a single KDL file into its KDL Document structure.
*/
export async function readPatch(fileUrl: URL): Promise<any> {
async function readPatchDocument(fileUrl: URL): Promise<Document> {
const text = await readFile(fileUrl, "utf8");
return parseKDL(text);
const { output, errors } = parse(text);
if (errors.length) {
throw new Error(`KDL parse errors in ${fileUrl.toString()}`, {
cause: errors,
});
}
return output!;
}
/**
* Recursively remove all 'name' fields from the object and its children, and
* replace any empty objects ({} or []) with null.
*/
function convertForRemovals(obj: unknown): unknown {
if (Array.isArray(obj)) {
const result = obj.map(convertForRemovals).filter((v) => v !== undefined);
return result.length === 0 ? null : result;
}
if (obj && typeof obj === "object") {
const newObj: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
if (key !== "name") {
const cleaned = convertForRemovals(value);
if (cleaned !== undefined) {
newObj[key] = cleaned;
}
}
}
// Replace empty objects with null
return Object.keys(newObj).length === 0 ? null : newObj;
}
return obj;
}

/**
* Read, parse, and merge all KDL files under the input folder.
* Splits the main patch content and the removals from each file for combined processing.
*
* Returns:
* {
* patches: merged patch contents (excluding removals),
* removalPatches: merged removals, with names stripped
* }
*/
export default async function readPatches(): Promise<any> {
export default async function readPatches(): Promise<{
patches: any;
removalPatches: any;
}> {
const patchDirectory = new URL("../../inputfiles/patches/", import.meta.url);
const fileUrls = await getAllFileURLs(patchDirectory);

const parsedContents = await Promise.all(fileUrls.map(readPatch));
// Stage 1: Parse all file KDLs into Documents
const documents = await Promise.all(fileUrls.map(readPatchDocument));

// Stage 2: Group by patches or removals
const merged = documents.flat();
const patchNodes = merged.filter((node) => node.name !== "removals");
const removalNodes = merged
.filter((node) => node.name === "removals")
.map((node) => node.children)
.flat();

// Stage 3: Convert the nodes for patches and removals respectively
const patches = convertKDLNodes(patchNodes);
const removalPatches = convertForRemovals(
convertKDLNodes(removalNodes),
) as DeepPartial<WebIdl>;

// Only because we don't use them
removalPatches.mixins = undefined;
removalPatches.interfaces = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this matter - if we omit this does the test fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes without it the tests fail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes without it the tests fail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes without it the tests fail

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah because interface: {} becomes interface: null. Hmmmmm I think this may be a footgun later when some patches become redundant and are removed...


return parsedContents.reduce((acc, current) => merge(acc, current), {});
return { patches, removalPatches };
}
Loading