blob: a2f330c09ca68bc39dd30516312eabe04ebdec98 [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
Victor Porof7e0446e2022-06-15 12:03:33 +00005import {APPLICABLE_MEMBERS, GLOBAL_ATTRIBUTES, SPECS} from './config.js';
Victor Porof1a8a30d2022-06-13 13:07:10 +00006import {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));
Victor Porof7e0446e2022-06-15 12:03:33 +000043 props = props?.map(member => [member.name, {specs: [name]}]);
Victor Porof1a8a30d2022-06-13 13:07:10 +000044 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 *
Victor Porof7e0446e2022-06-15 12:03:33 +000074 * Currently:
75 * - Adds a field specifying whether a member is a global attribute or not.
76 * - Adds information about which properties are "applicable" members for
77 * certain "states" their parent WebIDL type can be in, such as for the
78 * HTMLInputElement where the set of valid members are determined by the "type"
79 * property. See `APPLICABLE_MEMBERS`.
Victor Porof1a8a30d2022-06-13 13:07:10 +000080 *
81 * @param {*} output
82 */
83export function addMetadata(output) {
84 for (const [key, value] of Object.entries(output)) {
Victor Porof7e0446e2022-06-15 12:03:33 +000085 for (const [name, prop] of Object.entries(value.props)) {
86 prop.global = GLOBAL_ATTRIBUTES.has(name);
87
88 const rules = APPLICABLE_MEMBERS[key];
89 if (!rules) {
90 continue;
91 }
92
93 for (const {rule, members} of rules) {
94 if (members.has(name.toLowerCase())) {
95 merge(prop, {rules: [rule]});
96 }
97 }
98
99 value.rules = rules.map(({rule}) => rule);
Victor Porof1a8a30d2022-06-13 13:07:10 +0000100 }
Victor Porof1a8a30d2022-06-13 13:07:10 +0000101 }
102 return output;
103}
104
105/**
106 * Minimizes the DOM pinned properties dataset to remove the bits of data that
107 * don't contain information. For example, empty inheritance/includes chains.
108 *
109 * This should be done right at the end, before writing into the output file, to
110 * allow for certain diagnostics (such as finding "missing types").
111 *
112 * @param {*} output
113 * @returns {object}
114 */
115export function minimize(output) {
116 for (const [key, value] of Object.entries(output)) {
117 if (!value.inheritance) {
118 // Remove empty inheritance chains.
119 delete value.inheritance;
120 }
121 if (!value.includes.length) {
122 // Remove empty include chains.
123 delete value.includes;
124 }
125 const props = Object.entries(value.props);
126 if (!props.length) {
127 // Remove empty 'prop' lists.
128 delete value.props;
129 } else {
130 for (const [, value] of props) {
131 if (!value.global) {
132 // Remove the 'global' flag if it's false.
133 delete value.global;
134 }
135 if (value.specs.length === 1 && value.specs[0] === 'html') {
136 // Remove the 'specs' list if it's just "html".
137 delete value.specs;
138 } else {
139 // Combine multiple spec names into a single value.
140 value.specs = value.specs.reduce((acc, name) => acc | SPECS[name], 0);
141 }
142 }
143 }
144 // Remove the entire entry if there's nothing left after the cleanup above.
145 if (!Object.entries(value).length) {
146 delete output[key];
147 }
148 }
149 return output;
150}