blob: 75d8796aea9a2ba1c476d65a157842e23dd0ff38 [file] [log] [blame]
Victor Porof1a8a30d2022-06-13 13:07:10 +00001// Copyright 2022 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {GLOBAL_ATTRIBUTES, SPECS, VALID_MEMBERS} from './config.js';
6import {merge} from './util.js';
7
8/**
9 * All the members relevant for generating the DOM pinned properties dataset
10 * from WebIDL interfaces, mixins and dictionaries.
11 */
12const ACCEPTED_MEMBER_TYPES = new Set(['attribute', 'field']);
13
14/**
15 * Generates the DOM pinned properties dataset.
16 *
17 * @param {array} specs A list of specs. Each spec specifies its name and
18 * all the idl definitions it contains.
19 * @returns {object} output An object with WebIDL type names as keys and their
20 * WebIDL properties and inheritance/include chains as values.
21 */
22export function getIDLProps(specs, output = {}) {
23 for (const spec of specs) {
24 transform(spec, output);
25 }
26 return output;
27}
28
29function transform({name, idls}, output = {}) {
30 const makeEntry = () => ({
31 inheritance: null,
32 includes: [],
33 props: {},
34 });
35
36 for (const idl of idls) {
37 switch (idl.type) {
38 case 'interface':
39 case 'interface mixin':
40 case 'dictionary': {
41 output[idl.name] = output[idl.name] ?? makeEntry();
42 let props = idl.members?.filter(member => ACCEPTED_MEMBER_TYPES.has(member.type));
43 props = props?.map(member => [member.name, {global: GLOBAL_ATTRIBUTES.has(member.name), specs: [name]}, ]);
44 merge(output[idl.name], {
45 inheritance: idl.inheritance,
46 props: Object.fromEntries(props),
47 });
48 break;
49 }
50 case 'includes': {
51 output[idl.target] = output[idl.target] ?? makeEntry();
52 merge(output[idl.target], {
53 includes: [idl.includes],
54 });
55 break;
56 }
57 case 'callback':
58 case 'callback interface':
59 case 'enum':
60 case 'typedef':
61 case 'namespace': {
62 break;
63 }
64 default: {
65 console.warn('Skipping unknown WebIDL type', idl.type);
66 }
67 }
68 }
69}
70
71/**
72 * Adds additional metadata to the DOM pinned properties dataset.
73 *
74 * Currently only adds information about which properties are valid based on
75 * some state, such as for the HTMLInputElement. See `VALID_MEMBERS`.
76 *
77 * @param {*} output
78 */
79export function addMetadata(output) {
80 for (const [key, value] of Object.entries(output)) {
81 const rule = VALID_MEMBERS[key];
82 if (!rule) {
83 continue;
84 }
85 const states = Object.entries(rule).map(([selector, allowlist]) => {
86 const valid = Object.entries(value.props).filter(([prop]) => allowlist.has(prop.toLowerCase()));
87 return [selector, Object.fromEntries(valid)];
88 });
89 value.states = Object.fromEntries(states);
90 }
91 return output;
92}
93
94/**
95 * Minimizes the DOM pinned properties dataset to remove the bits of data that
96 * don't contain information. For example, empty inheritance/includes chains.
97 *
98 * This should be done right at the end, before writing into the output file, to
99 * allow for certain diagnostics (such as finding "missing types").
100 *
101 * @param {*} output
102 * @returns {object}
103 */
104export function minimize(output) {
105 for (const [key, value] of Object.entries(output)) {
106 if (!value.inheritance) {
107 // Remove empty inheritance chains.
108 delete value.inheritance;
109 }
110 if (!value.includes.length) {
111 // Remove empty include chains.
112 delete value.includes;
113 }
114 const props = Object.entries(value.props);
115 if (!props.length) {
116 // Remove empty 'prop' lists.
117 delete value.props;
118 } else {
119 for (const [, value] of props) {
120 if (!value.global) {
121 // Remove the 'global' flag if it's false.
122 delete value.global;
123 }
124 if (value.specs.length === 1 && value.specs[0] === 'html') {
125 // Remove the 'specs' list if it's just "html".
126 delete value.specs;
127 } else {
128 // Combine multiple spec names into a single value.
129 value.specs = value.specs.reduce((acc, name) => acc | SPECS[name], 0);
130 }
131 }
132 }
133 // Remove the entire entry if there's nothing left after the cleanup above.
134 if (!Object.entries(value).length) {
135 delete output[key];
136 }
137 }
138 return output;
139}