reffy 7.2.10 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -9
- package/index.js +3 -5
- package/package.json +4 -4
- package/reffy.js +61 -15
- package/src/browserlib/extract-cssdfn.mjs +15 -3
- package/src/browserlib/extract-events.mjs +20 -20
- package/src/browserlib/reffy.json +1 -1
- package/src/lib/post-processor.js +269 -0
- package/src/lib/specs-crawler.js +93 -127
- package/src/lib/util.js +76 -6
- package/src/postprocessing/csscomplete.js +50 -0
- package/src/postprocessing/events.js +197 -0
- package/src/{cli/generate-idlnames.js → postprocessing/idlnames.js} +102 -143
- package/src/postprocessing/idlparsed.js +31 -0
- package/src/cli/generate-idlparsed.js +0 -139
package/src/lib/specs-crawler.js
CHANGED
|
@@ -14,16 +14,15 @@ const fs = require('fs');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const specs = require('web-specs');
|
|
16
16
|
const cssDfnParser = require('./css-grammar-parser');
|
|
17
|
-
const
|
|
18
|
-
const { generateIdlNames, saveIdlNames } = require('../cli/generate-idlnames');
|
|
17
|
+
const postProcessor = require('./post-processor');
|
|
19
18
|
const {
|
|
20
19
|
completeWithAlternativeUrls,
|
|
21
20
|
expandBrowserModules,
|
|
22
21
|
expandCrawlResult,
|
|
23
22
|
expandSpecResult,
|
|
24
|
-
getGeneratedIDLNamesByCSSProperty,
|
|
25
23
|
isLatestLevelThatPasses,
|
|
26
24
|
processSpecification,
|
|
25
|
+
requireFromWorkingDirectory,
|
|
27
26
|
setupBrowser,
|
|
28
27
|
teardownBrowser,
|
|
29
28
|
createFolderIfNeeded
|
|
@@ -72,111 +71,52 @@ async function specOrFallback(spec, fallbackFolder, fallbackData) {
|
|
|
72
71
|
*/
|
|
73
72
|
async function crawlSpec(spec, crawlOptions) {
|
|
74
73
|
crawlOptions = crawlOptions || {};
|
|
75
|
-
|
|
74
|
+
|
|
75
|
+
const urlToCrawl = crawlOptions.publishedVersion ?
|
|
76
76
|
(spec.release ? spec.release : spec.nightly) :
|
|
77
77
|
spec.nightly;
|
|
78
78
|
const fallbackFolder = crawlOptions.fallback ?
|
|
79
79
|
path.dirname(crawlOptions.fallback) : '';
|
|
80
80
|
|
|
81
|
-
if (spec.error) {
|
|
82
|
-
return specOrFallback(spec, fallbackFolder, crawlOptions.fallbackData?.results);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
81
|
try {
|
|
86
82
|
const fallback = crawlOptions.fallbackData?.results?.find(s => s.url === spec.url);
|
|
87
83
|
let cacheInfo = {};
|
|
88
84
|
if (crawlOptions.fallbackData?.crawler === `reffy-${reffyVersion}`) {
|
|
89
85
|
cacheInfo = Object.assign({}, fallback?.crawlCacheInfo);
|
|
90
86
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
(spec,
|
|
94
|
-
const idToHeading = modules.find(m => m.needsIdToHeadingMap) ?
|
|
95
|
-
window.reffy.mapIdsToHeadings() : null;
|
|
96
|
-
const res = {
|
|
97
|
-
crawled: window.location.toString()
|
|
98
|
-
};
|
|
99
|
-
modules.forEach(mod => {
|
|
100
|
-
res[mod.property] = window.reffy[mod.name](spec, idToHeading);
|
|
101
|
-
});
|
|
102
|
-
return res;
|
|
103
|
-
},
|
|
104
|
-
[spec, crawlOptions.modules],
|
|
105
|
-
{ quiet: crawlOptions.quiet,
|
|
106
|
-
forceLocalFetch: crawlOptions.forceLocalFetch,
|
|
107
|
-
...cacheInfo}
|
|
108
|
-
);
|
|
109
|
-
if (result.status === "notmodified" && fallback) {
|
|
110
|
-
crawlOptions.quiet ?? console.warn(`skipping ${spec.url}, no change`);
|
|
111
|
-
const copy = Object.assign({}, fallback);
|
|
112
|
-
return expandSpecResult(copy, fallbackFolder);
|
|
87
|
+
let result = null;
|
|
88
|
+
if (crawlOptions.useCrawl) {
|
|
89
|
+
result = await expandSpecResult(spec, crawlOptions.useCrawl);
|
|
113
90
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
.forEach(propDfn => {
|
|
126
|
-
propDfn.linkingText.forEach(lt => {
|
|
127
|
-
if (!result.css.properties.hasOwnProperty(lt)) {
|
|
128
|
-
result.css.properties[lt] = {
|
|
129
|
-
name: lt
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
});
|
|
91
|
+
else {
|
|
92
|
+
result = await processSpecification(
|
|
93
|
+
urlToCrawl,
|
|
94
|
+
(spec, modules) => {
|
|
95
|
+
const idToHeading = modules.find(m => m.needsIdToHeadingMap) ?
|
|
96
|
+
window.reffy.mapIdsToHeadings() : null;
|
|
97
|
+
const res = {
|
|
98
|
+
crawled: window.location.toString()
|
|
99
|
+
};
|
|
100
|
+
modules.forEach(mod => {
|
|
101
|
+
res[mod.property] = window.reffy[mod.name](spec, idToHeading);
|
|
133
102
|
});
|
|
103
|
+
return res;
|
|
104
|
+
},
|
|
105
|
+
[spec, crawlOptions.modules],
|
|
106
|
+
{ quiet: crawlOptions.quiet,
|
|
107
|
+
forceLocalFetch: crawlOptions.forceLocalFetch,
|
|
108
|
+
...cacheInfo}
|
|
109
|
+
);
|
|
110
|
+
if (result.status === "notmodified" && fallback) {
|
|
111
|
+
crawlOptions.quiet ?? console.warn(`skipping ${spec.url}, no change`);
|
|
112
|
+
const copy = Object.assign({}, fallback);
|
|
113
|
+
return expandSpecResult(copy, fallbackFolder);
|
|
134
114
|
}
|
|
115
|
+
}
|
|
135
116
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// real CSS properties. In practice, they are. Let's remove them from
|
|
140
|
-
// the extract.
|
|
141
|
-
['property-name', '--*'].forEach(prop => {
|
|
142
|
-
if ((result.css.properties || {})[prop]) {
|
|
143
|
-
delete result.css.properties[prop];
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Specific rule for CSS extracts:
|
|
148
|
-
// Parse extracted CSS definitions and add generated IDL attribute names
|
|
149
|
-
Object.entries(result.css.properties || {}).forEach(([prop, dfn]) => {
|
|
150
|
-
if (dfn.value || dfn.newValues) {
|
|
151
|
-
try {
|
|
152
|
-
dfn.parsedValue = cssDfnParser.parsePropDefValue(
|
|
153
|
-
dfn.value || dfn.newValues);
|
|
154
|
-
} catch (e) {
|
|
155
|
-
dfn.valueParseError = e.message;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
dfn.styleDeclaration = getGeneratedIDLNamesByCSSProperty(prop);
|
|
159
|
-
});
|
|
160
|
-
Object.entries(result.css.descriptors || {}).forEach(([desc, dfn]) => {
|
|
161
|
-
if (dfn.value) {
|
|
162
|
-
try {
|
|
163
|
-
dfn.parsedValue = cssDfnParser.parsePropDefValue(
|
|
164
|
-
dfn.value);
|
|
165
|
-
} catch (e) {
|
|
166
|
-
dfn.valueParseError = e.message;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
Object.entries(result.css.valuespaces || {}).forEach(([vs, dfn]) => {
|
|
171
|
-
if (dfn.value) {
|
|
172
|
-
try {
|
|
173
|
-
dfn.parsedValue = cssDfnParser.parsePropDefValue(
|
|
174
|
-
dfn.value);
|
|
175
|
-
} catch (e) {
|
|
176
|
-
dfn.valueParseError = e.message;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
117
|
+
// Run post-processing modules at the spec level
|
|
118
|
+
for (const mod of (crawlOptions.post ?? [])) {
|
|
119
|
+
await postProcessor.run(mod, result, crawlOptions);
|
|
180
120
|
}
|
|
181
121
|
|
|
182
122
|
// Copy results back into initial spec object
|
|
@@ -187,9 +127,12 @@ async function crawlSpec(spec, crawlOptions) {
|
|
|
187
127
|
crawlOptions.modules.forEach(mod => {
|
|
188
128
|
if (result[mod.property]) {
|
|
189
129
|
spec[mod.property] = result[mod.property];
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
crawlOptions.post?.forEach(mod => {
|
|
133
|
+
const prop = mod.property ?? mod.name;
|
|
134
|
+
if (postProcessor.appliesAtLevel(mod, 'spec') && result[prop]) {
|
|
135
|
+
spec[prop] = result[prop];
|
|
193
136
|
}
|
|
194
137
|
});
|
|
195
138
|
}
|
|
@@ -236,12 +179,6 @@ async function saveSpecResults(spec, settings) {
|
|
|
236
179
|
continue;
|
|
237
180
|
}
|
|
238
181
|
folders[mod.property] = await getSubfolder(mod.property);
|
|
239
|
-
|
|
240
|
-
// Specific rule for IDL:
|
|
241
|
-
// Raw IDL goes to "idl" subfolder, parsed IDL goes to "idlparsed"
|
|
242
|
-
if (mod.property === 'idl') {
|
|
243
|
-
folders.idlparsed = await getSubfolder('idlparsed');
|
|
244
|
-
}
|
|
245
182
|
}
|
|
246
183
|
|
|
247
184
|
function getBaseJSON(spec) {
|
|
@@ -301,9 +238,6 @@ async function saveSpecResults(spec, settings) {
|
|
|
301
238
|
if (spec.idl) {
|
|
302
239
|
spec.idl = await saveIdl(spec);
|
|
303
240
|
}
|
|
304
|
-
if (spec.idlparsed) {
|
|
305
|
-
spec.idlparsed = await saveIdlParsed(spec, settings.output);
|
|
306
|
-
}
|
|
307
241
|
|
|
308
242
|
// Save CSS dumps
|
|
309
243
|
function defineCSSContent(spec) {
|
|
@@ -331,14 +265,16 @@ async function saveSpecResults(spec, settings) {
|
|
|
331
265
|
(typeof thing == 'object') && (Object.keys(thing).length === 0);
|
|
332
266
|
}
|
|
333
267
|
|
|
334
|
-
// Save all other extracts
|
|
268
|
+
// Save all other extracts from crawling modules
|
|
335
269
|
const remainingModules = modules.filter(mod =>
|
|
336
270
|
!mod.metadata && mod.property !== 'css' && mod.property !== 'idl');
|
|
337
271
|
for (const mod of remainingModules) {
|
|
338
272
|
await saveExtract(spec, mod.property, spec => !isEmpty(spec[mod.property]));
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Save extracts from post-processing modules that run at the spec level
|
|
276
|
+
for (const mod of (settings.post ?? [])) {
|
|
277
|
+
await postProcessor.save(mod, spec, settings);
|
|
342
278
|
}
|
|
343
279
|
|
|
344
280
|
return spec;
|
|
@@ -372,10 +308,17 @@ async function crawlList(speclist, crawlOptions) {
|
|
|
372
308
|
}
|
|
373
309
|
}
|
|
374
310
|
|
|
375
|
-
// Prepare Puppeteer instance
|
|
376
|
-
|
|
311
|
+
// Prepare Puppeteer instance unless we already have crawl results and
|
|
312
|
+
// we're only interested in post-processing
|
|
313
|
+
let list = null;
|
|
314
|
+
if (crawlOptions.useCrawl) {
|
|
315
|
+
list = speclist;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
await setupBrowser(crawlOptions.modules);
|
|
319
|
+
list = speclist.map(completeWithAlternativeUrls);
|
|
320
|
+
}
|
|
377
321
|
|
|
378
|
-
const list = speclist.map(completeWithAlternativeUrls);
|
|
379
322
|
const listAndPromise = list.map(spec => {
|
|
380
323
|
let resolve = null;
|
|
381
324
|
let reject = null;
|
|
@@ -418,7 +361,9 @@ async function crawlList(speclist, crawlOptions) {
|
|
|
418
361
|
const results = await Promise.all(listAndPromise.map(crawlSpecAndPromise));
|
|
419
362
|
|
|
420
363
|
// Close Puppeteer instance
|
|
421
|
-
|
|
364
|
+
if (!crawlOptions.useCrawl) {
|
|
365
|
+
teardownBrowser();
|
|
366
|
+
}
|
|
422
367
|
|
|
423
368
|
return results;
|
|
424
369
|
}
|
|
@@ -564,8 +509,12 @@ function crawlSpecs(options) {
|
|
|
564
509
|
});
|
|
565
510
|
}
|
|
566
511
|
|
|
567
|
-
const
|
|
568
|
-
|
|
512
|
+
const crawlIndex = options?.useCrawl ?
|
|
513
|
+
requireFromWorkingDirectory(options.useCrawl) :
|
|
514
|
+
null;
|
|
515
|
+
|
|
516
|
+
const requestedList = crawlIndex ? crawlIndex.results :
|
|
517
|
+
options?.specs ? prepareListOfSpecs(options.specs) :
|
|
569
518
|
specs;
|
|
570
519
|
|
|
571
520
|
// Make a shallow copy of passed options parameter and expand modules
|
|
@@ -579,9 +528,11 @@ function crawlSpecs(options) {
|
|
|
579
528
|
for (const mod of options.modules) {
|
|
580
529
|
if (mod.extractsPerSeries) {
|
|
581
530
|
await adjustExtractsPerSeries(results, mod.property, options);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for (const mod of options.post ?? []) {
|
|
534
|
+
if (postProcessor.extractsPerSeries(mod)) {
|
|
535
|
+
await adjustExtractsPerSeries(results, mod.property, options);
|
|
585
536
|
}
|
|
586
537
|
}
|
|
587
538
|
return results;
|
|
@@ -612,14 +563,21 @@ function crawlSpecs(options) {
|
|
|
612
563
|
}
|
|
613
564
|
})
|
|
614
565
|
.then(async crawlIndex => {
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
566
|
+
// Run post-processing modules at the crawl level
|
|
567
|
+
for (const mod of (options.post ?? [])) {
|
|
568
|
+
if (!postProcessor.appliesAtLevel(mod, 'crawl')) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const crawlResults = await expandCrawlResult(
|
|
572
|
+
crawlIndex, options.output, postProcessor.dependsOn(mod));
|
|
573
|
+
const result = await postProcessor.run(mod, crawlResults, options);
|
|
574
|
+
await postProcessor.save(mod, result, options);
|
|
575
|
+
|
|
576
|
+
if (!options.output) {
|
|
577
|
+
console.log();
|
|
578
|
+
console.log(JSON.stringify(result, null, 2));
|
|
579
|
+
}
|
|
619
580
|
}
|
|
620
|
-
const crawlResults = await expandCrawlResult(crawlIndex, options.output, ['idlparsed', 'dfns']);
|
|
621
|
-
const idlNames = generateIdlNames(crawlResults.results, options);
|
|
622
|
-
await saveIdlNames(idlNames, options.output);
|
|
623
581
|
});
|
|
624
582
|
}
|
|
625
583
|
|
|
@@ -627,5 +585,13 @@ function crawlSpecs(options) {
|
|
|
627
585
|
/**************************************************
|
|
628
586
|
Export methods for use as module
|
|
629
587
|
**************************************************/
|
|
630
|
-
|
|
631
|
-
|
|
588
|
+
// TODO: consider more alignment between the two crawl functions or
|
|
589
|
+
// find more explicit names to distinguish between them:
|
|
590
|
+
// - "crawlList" takes an explicit list of specs as input, does not run the
|
|
591
|
+
// post-processor, and returns the results without saving them to files.
|
|
592
|
+
// - "crawlSpecs" takes options as input, runs all steps and saves results
|
|
593
|
+
// to files (or outputs the results to the console). It does not return
|
|
594
|
+
// anything.
|
|
595
|
+
module.exports.crawlSpecs = (...args) => Array.isArray(args[0]) ?
|
|
596
|
+
crawlList.apply(this, args) :
|
|
597
|
+
crawlSpecs.apply(this, args);
|
package/src/lib/util.js
CHANGED
|
@@ -744,10 +744,10 @@ function isLatestLevelThatPasses(spec, list, predicate) {
|
|
|
744
744
|
return true;
|
|
745
745
|
}
|
|
746
746
|
while (spec.seriesNext) {
|
|
747
|
+
spec = list.find(s => s.shortname === spec.seriesNext);
|
|
747
748
|
if (!spec) {
|
|
748
749
|
break;
|
|
749
750
|
}
|
|
750
|
-
spec = list.find(s => s.shortname === spec.seriesNext);
|
|
751
751
|
if ((spec.seriesComposition === 'full') && predicate(spec)) {
|
|
752
752
|
return false;
|
|
753
753
|
}
|
|
@@ -816,10 +816,19 @@ async function expandSpecResult(spec, baseFolder, properties) {
|
|
|
816
816
|
// Also drop header that may have been added when extract was
|
|
817
817
|
// serialized.
|
|
818
818
|
if (contents.startsWith('// GENERATED CONTENT - DO NOT EDIT')) {
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
819
|
+
const hasWindowsEndings = contents.includes('\r\n\r\n');
|
|
820
|
+
if (hasWindowsEndings) {
|
|
821
|
+
const endOfHeader = contents.indexOf('\r\n\r\n');
|
|
822
|
+
contents = contents.substring(endOfHeader + 4)
|
|
823
|
+
// remove trailing newline added in saveIdl
|
|
824
|
+
.slice(0, -2);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
const endOfHeader = contents.indexOf('\n\n');
|
|
828
|
+
contents = contents.substring(endOfHeader + 2)
|
|
829
|
+
// remove trailing newline added in saveIdl
|
|
830
|
+
.slice(0, -1);
|
|
831
|
+
}
|
|
823
832
|
}
|
|
824
833
|
spec.idl = contents;
|
|
825
834
|
}
|
|
@@ -930,6 +939,66 @@ async function createFolderIfNeeded(folder) {
|
|
|
930
939
|
}
|
|
931
940
|
|
|
932
941
|
|
|
942
|
+
/**
|
|
943
|
+
* Tree hierarchies on which events may bubble
|
|
944
|
+
*
|
|
945
|
+
* First interface is the tree root, further interfaces are deeper levels in
|
|
946
|
+
* the tree.
|
|
947
|
+
*/
|
|
948
|
+
const trees = {
|
|
949
|
+
// DOM tree:
|
|
950
|
+
// https://dom.spec.whatwg.org/#node-trees
|
|
951
|
+
'dom': ['Window', 'Document', 'Element', 'Node'],
|
|
952
|
+
|
|
953
|
+
// IndexedDB tree (defined through "get the parent" algorithms)
|
|
954
|
+
// https://www.w3.org/TR/IndexedDB/#ref-for-get-the-parent%E2%91%A0
|
|
955
|
+
// https://www.w3.org/TR/IndexedDB/#ref-for-get-the-parent%E2%91%A1
|
|
956
|
+
'IndexedDB': ['IDBDatabase', 'IDBTransaction', 'IDBRequest'],
|
|
957
|
+
|
|
958
|
+
// Web Bluetooth tree
|
|
959
|
+
// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth-tree-bluetooth-tree
|
|
960
|
+
'web-bluetooth': [
|
|
961
|
+
'Bluetooth', 'BluetoothDevice', 'BluetoothRemoteGATTService',
|
|
962
|
+
'BluetoothRemoteGATTCharacteristic', 'BluetoothRemoteGATTDescriptor'],
|
|
963
|
+
|
|
964
|
+
// Serial tree
|
|
965
|
+
// https://wicg.github.io/serial/#serialport-interface
|
|
966
|
+
'serial': ['Serial', 'SerialPort']
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Return information about the tree hierarchy the IDL interface is linked to.
|
|
972
|
+
*
|
|
973
|
+
* @function
|
|
974
|
+
* @param {String} iface Name of the IDL interface to link to a tree
|
|
975
|
+
* @param {Array(Object)} interfaces A list of all known IDL interfaces with
|
|
976
|
+
* inheritance information in an "inheritance" property.
|
|
977
|
+
* @return {Object} An object with a "tree" property set to the shortname of the
|
|
978
|
+
* spec that defines the tree hierarchy, an "interface" property set to the
|
|
979
|
+
* interface name of the closest interface in the inheritance chain of the
|
|
980
|
+
* given interface that belongs to the tree, and a "depth" property that gives
|
|
981
|
+
* the depth of that interface in the tree hierarchy (where 0 is the tree
|
|
982
|
+
* root). The object is null if the interface cannot be associated with a
|
|
983
|
+
* tree.
|
|
984
|
+
*/
|
|
985
|
+
function getInterfaceTreeInfo(iface, interfaces) {
|
|
986
|
+
while (iface) {
|
|
987
|
+
for (const [tree, nodes] of Object.entries(trees)) {
|
|
988
|
+
if (nodes.includes(iface)) {
|
|
989
|
+
return {
|
|
990
|
+
tree,
|
|
991
|
+
interface: iface,
|
|
992
|
+
depth: nodes.findIndex(i => i === iface)
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
iface = interfaces.find(i => i.name === iface)?.inheritance;
|
|
997
|
+
}
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
|
|
933
1002
|
module.exports = {
|
|
934
1003
|
fetch,
|
|
935
1004
|
requireFromWorkingDirectory,
|
|
@@ -942,5 +1011,6 @@ module.exports = {
|
|
|
942
1011
|
expandCrawlResult,
|
|
943
1012
|
expandSpecResult,
|
|
944
1013
|
getGeneratedIDLNamesByCSSProperty,
|
|
945
|
-
createFolderIfNeeded
|
|
1014
|
+
createFolderIfNeeded,
|
|
1015
|
+
getInterfaceTreeInfo
|
|
946
1016
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-processing module that adds CSS property definitions found in prose
|
|
3
|
+
* from the dfns extract, clean up property definitions that should never have
|
|
4
|
+
* been extracted, and adds the generated IDL attribute names in a
|
|
5
|
+
* styleDeclaration sub-property.
|
|
6
|
+
*
|
|
7
|
+
* Module runs at the spec level. It does not create a distinct property but
|
|
8
|
+
* rather completes the `css` property with additional info.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { getGeneratedIDLNamesByCSSProperty } = require('../lib/util');
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
dependsOn: ['css', 'dfns'],
|
|
15
|
+
input: 'spec',
|
|
16
|
+
|
|
17
|
+
run: async function(spec, options) {
|
|
18
|
+
if (spec.dfns && spec.css) {
|
|
19
|
+
spec.dfns
|
|
20
|
+
.filter(dfn => dfn.type == "property" && !dfn.informative)
|
|
21
|
+
.forEach(propDfn => {
|
|
22
|
+
propDfn.linkingText.forEach(lt => {
|
|
23
|
+
if (!spec.css.properties.hasOwnProperty(lt)) {
|
|
24
|
+
spec.css.properties[lt] = {
|
|
25
|
+
name: lt
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (spec.css) {
|
|
33
|
+
// Add generated IDL attribute names
|
|
34
|
+
Object.entries(spec.css.properties || {}).forEach(([prop, dfn]) => {
|
|
35
|
+
dfn.styleDeclaration = getGeneratedIDLNamesByCSSProperty(prop);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Drop the sample definition (property-name) in CSS2 and the custom
|
|
39
|
+
// property definition (--*) in CSS Variables that specs incorrectly flag
|
|
40
|
+
// as real CSS properties.
|
|
41
|
+
['property-name', '--*'].forEach(prop => {
|
|
42
|
+
if ((spec.css.properties || {})[prop]) {
|
|
43
|
+
delete spec.css.properties[prop];
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return spec;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-processing module that consolidates events extracts into one extract
|
|
3
|
+
* per event.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { isLatestLevelThatPasses, getInterfaceTreeInfo } = require('../lib/util');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
dependsOn: ['events'],
|
|
10
|
+
input: 'crawl',
|
|
11
|
+
property: 'events',
|
|
12
|
+
|
|
13
|
+
run: async function(crawl, options) {
|
|
14
|
+
// Collect list of mixin interfaces
|
|
15
|
+
const mixins = {};
|
|
16
|
+
const parsedInterfaces = [];
|
|
17
|
+
crawl.results.forEach(s => {
|
|
18
|
+
if (s.idlparsed) {
|
|
19
|
+
if (s.idlparsed.idlNames) {
|
|
20
|
+
Object.values(s.idlparsed.idlNames).forEach(dfn => {
|
|
21
|
+
if (dfn.type === 'interface' && !dfn.partial) {
|
|
22
|
+
parsedInterfaces.push(dfn);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (s.idlparsed.idlExtendedNames) {
|
|
27
|
+
Object.keys(s.idlparsed.idlExtendedNames).forEach(n => {
|
|
28
|
+
s.idlparsed.idlExtendedNames[n].forEach(f => {
|
|
29
|
+
if (f.type === 'includes') {
|
|
30
|
+
if (!mixins[f.includes]) mixins[f.includes] = [];
|
|
31
|
+
mixins[f.includes].push(n);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Only consider latest spec in a series to avoid fake duplicates
|
|
40
|
+
const results = crawl.results.filter(spec =>
|
|
41
|
+
(spec.seriesComposition !== 'delta' && isLatestLevelThatPasses(spec, crawl.results, s => s.events)) ||
|
|
42
|
+
(spec.seriesComposition === 'delta' && spec.events));
|
|
43
|
+
|
|
44
|
+
// Update events in place
|
|
45
|
+
const events = results.map(spec => spec.events.map(e => Object.assign({ spec: spec }, e))).flat();
|
|
46
|
+
for (const event of events) {
|
|
47
|
+
expandMixinTargets(event, mixins);
|
|
48
|
+
setBubblingPerTarget(event, parsedInterfaces);
|
|
49
|
+
cleanTargetInTrees(event, parsedInterfaces);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Consolidate events extended in other specs
|
|
53
|
+
const eventsToDrop = events
|
|
54
|
+
.filter(event => event.isExtension)
|
|
55
|
+
.map(event => {
|
|
56
|
+
const err = extendEvent(event, events);
|
|
57
|
+
if (err) {
|
|
58
|
+
// Event could not be extended, let's keep extension event
|
|
59
|
+
console.warn(err);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Event successfully extended, extension can be dropped
|
|
64
|
+
return event;
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.filter(event => !!event);
|
|
68
|
+
|
|
69
|
+
return events
|
|
70
|
+
.filter(event => !eventsToDrop.includes(event))
|
|
71
|
+
.map(event => {
|
|
72
|
+
delete event.spec;
|
|
73
|
+
return event;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
function expandMixinTargets(event, mixins) {
|
|
80
|
+
const expandedTargets = event.targets?.map(i => mixins[i] || i)?.flat();
|
|
81
|
+
// This assumes a mixin matches more than one interface
|
|
82
|
+
if (expandedTargets && expandedTargets.length !== event.targets?.length) {
|
|
83
|
+
event.targets = expandedTargets;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
function setBubblingPerTarget(event, parsedInterfaces) {
|
|
91
|
+
// if an event targets an interface in a tree
|
|
92
|
+
// but the root of the tree wasn't detected as a target
|
|
93
|
+
// we can assume bubbles is false
|
|
94
|
+
// (ideally, we should check the existence of the event handler on the
|
|
95
|
+
// root interface, but there is no easy way to get a consolidated IDL view
|
|
96
|
+
// of the root at the moment)
|
|
97
|
+
if (!event.targets) return;
|
|
98
|
+
const updatedTargets = [];
|
|
99
|
+
const detected = {};
|
|
100
|
+
const treeInterfaces = [];
|
|
101
|
+
for (let iface of event.targets) {
|
|
102
|
+
const treeInfo = getInterfaceTreeInfo(iface, parsedInterfaces);
|
|
103
|
+
if (!treeInfo) {
|
|
104
|
+
updatedTargets.push({target: iface});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const { tree, depth } = treeInfo;
|
|
108
|
+
if (!detected[tree]) {
|
|
109
|
+
detected[tree] = {root: false, nonroot: false};
|
|
110
|
+
}
|
|
111
|
+
if (depth === 0) {
|
|
112
|
+
// bubbling doesn't matter on the root interface
|
|
113
|
+
updatedTargets.push({target: iface});
|
|
114
|
+
detected[tree].root = true;
|
|
115
|
+
} else {
|
|
116
|
+
treeInterfaces.push(iface);
|
|
117
|
+
detected[tree].nonroot = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// if the event is sent at targets in a tree, but isn't detected
|
|
121
|
+
// on the root target, and no bubbling info is available,
|
|
122
|
+
// assume it doesn't bubble
|
|
123
|
+
if (Object.values(detected).length) {
|
|
124
|
+
if (!event.hasOwnProperty('bubbles') && Object.values(detected).every(x => !x.root && x.nonroot )) {
|
|
125
|
+
event.bubbles = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (let iface of treeInterfaces) {
|
|
129
|
+
if (event.hasOwnProperty('bubbles')) {
|
|
130
|
+
updatedTargets.push({target: iface, bubbles: event.bubbles});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
event.targets = updatedTargets;
|
|
134
|
+
delete event.bubbles;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
function cleanTargetInTrees(event, parsedInterfaces) {
|
|
139
|
+
// When several targets are attached to an event that bubbles
|
|
140
|
+
// keep only the "deepest" target
|
|
141
|
+
if (event.bubbles && event.targets?.length > 1) {
|
|
142
|
+
const filteredTargets = deepestInterfaceInTree(event.targets, parsedInterfaces);
|
|
143
|
+
if (filteredTargets.length !== event.targets.length) {
|
|
144
|
+
event.targets = filteredTargets;
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
function deepestInterfaceInTree(targets, parsedInterfaces) {
|
|
153
|
+
let deepestInTrees = {};
|
|
154
|
+
let filteredTargets = [];
|
|
155
|
+
for (let {target, bubbles} of targets) {
|
|
156
|
+
const treeInfo = getInterfaceTreeInfo(target, parsedInterfaces);
|
|
157
|
+
if (!treeInfo) { // Not in a tree, we keep it in
|
|
158
|
+
filteredTargets.push({target});
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const { tree, depth } = treeInfo;
|
|
162
|
+
const currentDeepest = deepestInTrees[tree]?.target;
|
|
163
|
+
if (currentDeepest) {
|
|
164
|
+
const { depth: currentDeepestDepth } = getInterfaceTreeInfo(currentDeepest, parsedInterfaces);
|
|
165
|
+
if (depth > currentDeepestDepth) {
|
|
166
|
+
deepestInTrees[tree] = {target, bubbles};
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
deepestInTrees[tree] = {target, bubbles};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return filteredTargets.concat(Object.values(deepestInTrees));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
function extendEvent(event, events) {
|
|
177
|
+
const extendedEvent =
|
|
178
|
+
events.find(e => !e.isExtension && e.href === event.href) ||
|
|
179
|
+
events.find(e => !e.isExtension && event.href.startsWith(e.spec.crawled) && e.type === event.type);
|
|
180
|
+
if (!extendedEvent) {
|
|
181
|
+
// make this a fatal error
|
|
182
|
+
return `Found extended event with link ${event.href} in ${event.spec.shortname}, but did not find a matching original event`;
|
|
183
|
+
}
|
|
184
|
+
if (extendedEvent.interface && event.interface && extendedEvent.interface !== event.interface) {
|
|
185
|
+
return `Found extended event with link ${event.href} in ${event.spec.shortname} set to use interface ${event.interface}, different from original event interface ${extendedEvent.interface} in ${extendedEvent.spec.shortname}`;
|
|
186
|
+
}
|
|
187
|
+
// Document potential additional targets
|
|
188
|
+
const newTargets = event.targets?.filter(t => !extendedEvent.targets?.find(tt => tt.target === t.target));
|
|
189
|
+
if (newTargets) {
|
|
190
|
+
extendedEvent.targets = (extendedEvent.targets || []).concat(newTargets);
|
|
191
|
+
}
|
|
192
|
+
// Document the fact that the event has been extended
|
|
193
|
+
if (!extendedEvent.extendedIn) {
|
|
194
|
+
extendedEvent.extendedIn = [];
|
|
195
|
+
}
|
|
196
|
+
extendedEvent.extendedIn.push({ spec: event.spec.series.shortname, href: event.src?.href });
|
|
197
|
+
}
|