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 | |
| 5 | import {GLOBAL_ATTRIBUTES, SPECS, VALID_MEMBERS} from './config.js'; |
| 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)); |
| 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 | */ |
| 79 | export 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 | */ |
| 104 | export 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 | } |