Skip to content

Commit 63bc031

Browse files
authored
Add custom dfns/headings extraction logic for WebGL1 (#1894)
The HTML of the WebGL1 spec was mostly written by hand. It contains a few actual definitions that follow the definitions data model, but it also defines ~15+8 anchors for IDL attributes and methods that do not. These anchors are typically used in BCD. A new pre-processing step was added to turn these anchors into proper `<dfn>` tags in WebGL1. As with SVG 2, that pre-processing step needs to parse the IDL to scope definitions properly (the prose is clunky, see inline comments in the code). Similarly, a couple of headings have an additional anchor that is more human-friendly than `5.2` and that BCD typically targets. These IDs are now captured in the `alternateIds` array.
1 parent 35af4a3 commit 63bc031

File tree

4 files changed

+205
-11
lines changed

4 files changed

+205
-11
lines changed

src/browserlib/extract-dfns.mjs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ export default function (spec, idToHeading = {}) {
327327
// RFC8610 defines CDDL
328328
preProcessRFC8610();
329329
break;
330+
case "webgl1":
331+
preProcessWebGL1();
332+
break;
330333
}
331334

332335
const dfnEls = [...document.querySelectorAll(definitionsSelector)];
@@ -1008,4 +1011,84 @@ function preProcessRFC8610() {
10081011
dfn.dataset.export = '';
10091012
dfn.textContent = el.textContent;
10101013
el.replaceWith(dfn);
1014+
}
1015+
1016+
1017+
/**
1018+
* WebGL 1.0 defines a few (~15) IDL attributes without following the
1019+
* definitions data model. These IDL constructs have IDs that look like
1020+
* `DOM-[interface]-[attr]`.
1021+
*
1022+
* The spec also defines a few IDL methods with anchors so that they can be
1023+
* referenced. BCD and MDN typically link to these.
1024+
*
1025+
* Not much choice to understand what the anchors map to and create the
1026+
* appropriate dfns, we need to extract and parse the whole IDL
1027+
*/
1028+
function preProcessWebGL1() {
1029+
const idl = extractWebIdl();
1030+
const idlTree = parse(idl);
1031+
1032+
const attributes = [...document.querySelectorAll('.attribute-name a[id^=DOM-]')];
1033+
for (const attribute of attributes) {
1034+
const dfn = document.createElement('dfn');
1035+
// Notes:
1036+
// - The interface name appears in the ID but... name cannot be trusted
1037+
// because it targets the concrete interface and not the underlying mixin
1038+
// when one exists, whereas we want to create a dfn scoped to the mixin.
1039+
// - Fortunately, no two interfaces define the same attribute in WebGL1, so
1040+
// we can just match on the attribute name
1041+
const attrName = attribute.textContent.trim();
1042+
const idlItems = idlTree.filter(i => i.members?.find(m =>
1043+
m.type === 'attribute' && m.name === attrName));
1044+
if (idlItems.length === 0) {
1045+
console.warn('[reffy]', `could not find attribute ${attrName}`);
1046+
continue;
1047+
}
1048+
if (idlItems.length > 1) {
1049+
console.warn('[reffy]', `more than one matching attribute found for ${attrName}`);
1050+
continue;
1051+
}
1052+
dfn.id = attribute.id;
1053+
dfn.dataset.dfnType = 'attribute';
1054+
dfn.dataset.dfnFor = idlItems[0].name;
1055+
dfn.textContent = attrName;
1056+
attribute.replaceWith(dfn);
1057+
}
1058+
1059+
const methods = [...document.querySelectorAll('.idl-code a[name]')];
1060+
for (const method of methods) {
1061+
const dfn = document.createElement('dfn');
1062+
// Notes:
1063+
// - The anchor also wraps possible flags and the return type
1064+
// - The return type is best ignored: The IDL block was fixed to use
1065+
// `undefined` but the prose still uses `void` (sigh!).
1066+
// - The parameter names and types are after the anchor. We need to look at
1067+
// them because some of the anchors target overloaded methods... We'll also
1068+
// use them to create appropriate linking texts for the methods.
1069+
// - We cannot match on parameter names for overloaded methods because the
1070+
// spec uses *different* parameter names in the IDL block and in the prose
1071+
// that defines the method (re-sigh!). Matching on the number of parameters is
1072+
// enough to disambiguate between overloaded methods.
1073+
const methodName = method.textContent.split(' ').pop();
1074+
const methodArgs = method.parentNode.textContent
1075+
// Note the "s" flag as parameters may be split over multiple lines
1076+
.match(/\((.*?)\)/s)[1]
1077+
.split(',')
1078+
.map(arg => arg.split(' ').pop());
1079+
const idlItem = idlTree.find(i => i.members?.find(m =>
1080+
m.type === 'operation' &&
1081+
m.name === methodName &&
1082+
m.arguments.length === methodArgs.length));
1083+
if (!idlItem) {
1084+
console.warn('[reffy]', `could not find method ${methodName}`);
1085+
continue;
1086+
}
1087+
dfn.id = method.getAttribute('name');
1088+
dfn.dataset.dfnType = 'method';
1089+
dfn.dataset.dfnFor = idlItem.name;
1090+
dfn.dataset.lt = `${methodName}(${methodArgs.join(', ')})`;
1091+
dfn.textContent = method.textContent;
1092+
method.replaceWith(dfn);
1093+
}
10111094
}

src/browserlib/map-ids-to-headings.mjs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,15 @@ export default function () {
8989
const ids = [];
9090

9191
const heading = parentSection.heading;
92+
const anchor = heading.querySelector('a[name]');
93+
if (anchor) {
94+
ids.push(anchor.getAttribute('name'));
95+
href = getAbsoluteUrl(anchor, { singlePage, attribute: 'name' });
96+
}
9297
if (heading.id) {
93-
ids.push(heading.id);
98+
ids.push(heading.id);
9499
href = getAbsoluteUrl(heading, { singlePage });
95100
}
96-
else {
97-
const anchor = heading.querySelector('a[name]');
98-
if (anchor) {
99-
ids.push(anchor.getAttribute('name'));
100-
href = getAbsoluteUrl(anchor, { singlePage, attribute: 'name' });
101-
}
102-
}
103101

104102
if (parentSection.root && parentSection.root.id) {
105103
ids.push(parentSection.root.id);
@@ -112,12 +110,12 @@ export default function () {
112110

113111
const mapping = {};
114112
if (ids.length) {
115-
mapping.id = ids.pop();
113+
mapping.id = ids.pop();
116114
}
117115
mapping.href = href;
118116
mapping.title = trimmedText.replace(reNumber, '');
119117
if (ids.length) {
120-
mapping.alternateIds = ids;
118+
mapping.alternateIds = ids;
121119
}
122120
mappingTable[nodeid] = mapping;
123121

test/extract-dfns.js

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,31 @@ interface SVGAnimatedLengthList {
8383
</table></div>
8484
`;
8585

86+
87+
const baseWebGL1 = `
88+
<!-- we use the IDL declarations to find the interface of methods -->
89+
<pre class=idl>
90+
[Exposed=(Window,Worker)]
91+
interface WebGLActiveInfo {
92+
readonly attribute GLint size;
93+
readonly attribute GLenum type;
94+
readonly attribute DOMString name;
95+
};
96+
97+
interface mixin WebGLRenderingContextBase {
98+
attribute PredefinedColorSpace unpackColorSpace;
99+
};
100+
interface mixin WebGLRenderingContextOverloads {
101+
undefined texImage2D(GLenum target, GLint level, GLint internalformat,
102+
GLsizei width, GLsizei height, GLint border, GLenum format,
103+
GLenum type, [AllowShared] ArrayBufferView? pixels);
104+
undefined texImage2D(GLenum target, GLint level, GLint internalformat,
105+
GLenum format, GLenum type, TexImageSource source); // May throw DOMException
106+
};
107+
</pre>
108+
`;
109+
110+
86111
const baseDfn = {
87112
id: 'foo',
88113
linkingText: [ 'Foo' ],
@@ -902,7 +927,82 @@ When initialize(<var>newItem</var>) is called, the following steps are run:</p>`
902927
definedIn: 'dt'
903928
}
904929
]
905-
}
930+
},
931+
932+
{
933+
title: "extracts attribute definitions from WebGL1",
934+
html: `<p>
935+
<code class="attribute-name">
936+
<a id="DOM-WebGLRenderingContext-unpackColorSpace">
937+
unpackColorSpace
938+
</a>
939+
</code>
940+
of type <code><a href="https://html.spec.whatwg.org/multipage/canvas.html#predefinedcolorspace">PredefinedColorSpace</a></code>
941+
<a href="#refsPREDEFINEDCOLORSPACE">(specification)</a>
942+
</p>`,
943+
changesToBaseDfn: [
944+
{
945+
id: "DOM-WebGLRenderingContext-unpackColorSpace",
946+
href: "about:blank#DOM-WebGLRenderingContext-unpackColorSpace",
947+
type: "attribute",
948+
linkingText: [
949+
"unpackColorSpace"
950+
],
951+
for: [
952+
"WebGLRenderingContextBase"
953+
],
954+
access: "public"
955+
}
956+
],
957+
spec: "webgl1"
958+
},
959+
960+
{
961+
title: "extracts method definitions from WebGL1",
962+
html: `<dl class="methods">
963+
<dt class="idl-code"><a name="TEXIMAGE2D">void texImage2D</a>(GLenum target, GLint level, GLint internalformat,
964+
GLsizei width, GLsizei height, GLint border, GLenum format,
965+
GLenum type, [AllowShared] ArrayBufferView? pixels)
966+
<span class="gl-spec">(<a href="http://registry.khronos.org/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf#nameddest=section-3.7.1">OpenGL ES 2.0 §3.7.1</a>, <a class="nonnormative" href="http://www.khronos.org/opengles/sdk/2.0/docs/man/xhtml/glTexImage2D.xml">man page</a>)</span>
967+
</dt>
968+
<dt>
969+
<p class="idl-code"><a name="TEXIMAGE2D_HTML">void texImage2D</a>(GLenum target, GLint level, GLint internalformat,
970+
GLenum format, GLenum type, TexImageSource source) /* May throw DOMException */
971+
<span class="gl-spec">(<a href="http://registry.khronos.org/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf#nameddest=section-3.7.1">OpenGL ES 2.0 §3.7.1</a>, <a class="nonnormative" href="http://www.khronos.org/opengles/sdk/2.0/docs/man/xhtml/glTexImage2D.xml">man page</a>)</span>
972+
</p>
973+
</dt>
974+
<dd></dd>
975+
</dl>`,
976+
changesToBaseDfn: [
977+
{
978+
id: "TEXIMAGE2D",
979+
href: "about:blank#TEXIMAGE2D",
980+
type: "method",
981+
linkingText: [
982+
"texImage2D(target, level, internalformat, width, height, border, format, type, pixels)"
983+
],
984+
for: [
985+
"WebGLRenderingContextOverloads"
986+
],
987+
access: "public",
988+
definedIn: "dt",
989+
},
990+
{
991+
id: "TEXIMAGE2D_HTML",
992+
href: "about:blank#TEXIMAGE2D_HTML",
993+
type: "method",
994+
linkingText: [
995+
"texImage2D(target, level, internalformat, format, type, source)"
996+
],
997+
for: [
998+
"WebGLRenderingContextOverloads"
999+
],
1000+
access: "public",
1001+
definedIn: "dt"
1002+
}
1003+
],
1004+
spec: "webgl1"
1005+
},
9061006
];
9071007

9081008
describe("Test definition extraction", function () {
@@ -922,6 +1022,9 @@ describe("Test definition extraction", function () {
9221022
case "SVG2":
9231023
pageContent = baseSVG2;
9241024
break;
1025+
case "webgl1":
1026+
pageContent = baseWebGL1;
1027+
break;
9251028
};
9261029
pageContent += html + "<script>let spec = '" + spec + "';</script>"
9271030
page

test/extract-headings.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ const testHeadings = [
9393
</pre>`,
9494
res: [{id: "title", href: "about:blank#title", title: "Title", number: "A", level: 1}]
9595
},
96+
{
97+
title: "documents alternate IDs in WebGL1",
98+
html: `
99+
<h2 id="5.2">
100+
<span class="secno">5.2</span>
101+
<a name="WEBGLCONTEXTATTRIBUTES">WebGLContextAttributes</a>
102+
</h2>
103+
`,
104+
res: [{id: "5.2", href: "about:blank#5.2", title: "WebGLContextAttributes", number: "5.2", level: 2, alternateIds: ["WEBGLCONTEXTATTRIBUTES"]}]
105+
}
96106
];
97107

98108
describe("Test headings extraction", function () {

0 commit comments

Comments
 (0)