chrome-devtools-frontend
Version:
Chrome DevTools UI
151 lines (140 loc) • 4.49 kB
JavaScript
// 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;
}