Victor Porof | 1a8a30d | 2022-06-13 13:07:10 +0000 | [diff] [blame] | 1 | // 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 Porof | 7e0446e | 2022-06-15 12:03:33 +0000 | [diff] [blame^] | 5 | import {APPLICABLE_MEMBERS, GLOBAL_ATTRIBUTES, SPECS} from './config.js'; |
Victor Porof | 1a8a30d | 2022-06-13 13:07:10 +0000 | [diff] [blame] | 6 | import {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 | */ |
| 12 | const 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 | */ |
| 22 | export function getIDLProps(specs, output = {}) { |
| 23 | for (const spec of specs) { |
| 24 | transform(spec, output); |
| 25 | } |
| 26 | return output; |
| 27 | } |
| 28 | |
| 29 | function 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 Porof | 7e0446e | 2022-06-15 12:03:33 +0000 | [diff] [blame^] | 43 | props = props?.map(member => [member.name, {specs: [name]}]); |
Victor Porof | 1a8a30d | 2022-06-13 13:07:10 +0000 | [diff] [blame] | 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 | * |
Victor Porof | 7e0446e | 2022-06-15 12:03:33 +0000 | [diff] [blame^] | 74 | * 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 Porof | 1a8a30d | 2022-06-13 13:07:10 +0000 | [diff] [blame] | 80 | * |
| 81 | * @param {*} output |
| 82 | */ |
| 83 | export function addMetadata(output) { |
| 84 | for (const [key, value] of Object.entries(output)) { |
Victor Porof | 7e0446e | 2022-06-15 12:03:33 +0000 | [diff] [blame^] | 85 | 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 Porof | 1a8a30d | 2022-06-13 13:07:10 +0000 | [diff] [blame] | 100 | } |
Victor Porof | 1a8a30d | 2022-06-13 13:07:10 +0000 | [diff] [blame] | 101 | } |
| 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 | */ |
| 115 | export 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 | } |