Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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/webauthn.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
102 changes: 79 additions & 23 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 @@ -76,33 +76,34 @@ function handleTypeParameters(value: Value) {
};
}

function undefinedIfEmpty(object: object, output: object) {
return Object.entries(object).length ? output : undefined;
}

/**
* 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":
enums[name] = handleEnum(node);
break;
case "interface-mixin":
mixin[name] = handleMixinandInterfaces(node, "mixin");
mixin[name] = merge(
mixin[name],
handleMixinAndInterfaces(node, "mixin"),
);
break;
case "interface":
interfaces[name] = handleMixinandInterfaces(node, "interface");
interfaces[name] = handleMixinAndInterfaces(node, "interface");
break;
case "dictionary":
dictionary[name] = handleDictionary(node);
Expand All @@ -113,10 +114,10 @@ function parseKDL(kdlText: string): DeepPartial<WebIdl> {
}

return {
enums: { enum: enums },
mixins: { mixin },
interfaces: { interface: interfaces },
dictionaries: { dictionary },
enums: undefinedIfEmpty(enums, { enum: enums }),
mixins: undefinedIfEmpty(mixin, { mixin }),
interfaces: undefinedIfEmpty(interfaces, { interface: interfaces }),
dictionaries: undefinedIfEmpty(dictionary, { dictionary }),
};
}

Expand Down Expand Up @@ -152,7 +153,7 @@ function handleEnum(node: Node): Enum {
* @param node The mixin node to handle.
* @param mixins The record of mixins to update.
*/
function handleMixinandInterfaces(
function handleMixinAndInterfaces(
node: Node,
type: "mixin" | "interface",
): DeepPartial<Interface> {
Expand Down Expand Up @@ -368,21 +369,76 @@ 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>;

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