UNPKG

chrome-devtools-frontend

Version:
151 lines (140 loc) 4.49 kB
// Copyright 2022 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import {APPLICABLE_MEMBERS, GLOBAL_ATTRIBUTES, SPECS} from './config.js'; import {merge} from './util.js'; /** * All the members relevant for generating the DOM pinned properties dataset * from WebIDL interfaces, mixins and dictionaries. */ const ACCEPTED_MEMBER_TYPES = new Set(['attribute', 'field']); /** * Generates the DOM pinned properties dataset. * * @param {array} specs A list of specs. Each spec specifies its name and * all the idl definitions it contains. * @returns {object} output An object with WebIDL type names as keys and their * WebIDL properties and inheritance/include chains as values. */ export function getIDLProps(specs, output = {}) { for (const spec of specs) { transform(spec, output); } return output; } function transform({name, idls}, output = {}) { const makeEntry = () => ({ inheritance: null, includes: [], props: {}, }); for (const idl of idls) { switch (idl.type) { case 'interface': case 'interface mixin': case 'dictionary': { output[idl.name] = output[idl.name] ?? makeEntry(); let props = idl.members?.filter(member => ACCEPTED_MEMBER_TYPES.has(member.type)); props = props?.map(member => [member.name, {specs: [name]}]); merge(output[idl.name], { inheritance: idl.inheritance, props: Object.fromEntries(props), }); break; } case 'includes': { output[idl.target] = output[idl.target] ?? makeEntry(); merge(output[idl.target], { includes: [idl.includes], }); break; } case 'callback': case 'callback interface': case 'enum': case 'typedef': case 'namespace': { break; } default: { console.warn('Skipping unknown WebIDL type', idl.type); } } } } /** * Adds additional metadata to the DOM pinned properties dataset. * * Currently: * - Adds a field specifying whether a member is a global attribute or not. * - Adds information about which properties are "applicable" members for * certain "states" their parent WebIDL type can be in, such as for the * HTMLInputElement where the set of valid members are determined by the "type" * property. See `APPLICABLE_MEMBERS`. * * @param {*} output */ export function addMetadata(output) { for (const [key, value] of Object.entries(output)) { for (const [name, prop] of Object.entries(value.props)) { prop.global = GLOBAL_ATTRIBUTES.has(name); const rules = APPLICABLE_MEMBERS[key]; if (!rules) { continue; } for (const {rule, members} of rules) { if (members.has(name.toLowerCase())) { merge(prop, {rules: [rule]}); } } value.rules = rules.map(({rule}) => rule); } } return output; } /** * Minimizes the DOM pinned properties dataset to remove the bits of data that * don't contain information. For example, empty inheritance/includes chains. * * This should be done right at the end, before writing into the output file, to * allow for certain diagnostics (such as finding "missing types"). * * @param {*} output * @returns {object} */ export function minimize(output) { for (const [key, value] of Object.entries(output)) { if (!value.inheritance) { // Remove empty inheritance chains. delete value.inheritance; } if (!value.includes.length) { // Remove empty include chains. delete value.includes; } const props = Object.entries(value.props); if (!props.length) { // Remove empty 'prop' lists. delete value.props; } else { for (const [, value] of props) { if (!value.global) { // Remove the 'global' flag if it's false. delete value.global; } if (value.specs.length === 1 && value.specs[0] === 'html') { // Remove the 'specs' list if it's just "html". delete value.specs; } else { // Combine multiple spec names into a single value. value.specs = value.specs.reduce((acc, name) => acc | SPECS[name], 0); } } } // Remove the entire entry if there's nothing left after the cleanup above. if (!Object.entries(value).length) { delete output[key]; } } return output; }