Skip to content

Commit

Permalink
Only emit APIs that are standards track and not experimental
Browse files Browse the repository at this point in the history
The current implementation only emits APIs that are on the
standards track and supported in Chrome, Firefox, and Safari.
This leaves out widely used APIs like Trusted Types, so this
change relaxes those requirements. In order to support this
change, a number of changes are included:

- BrowserCompatData is modified to handle some slight
discrepancies in how compatibility data is stored, including
global APIs, namespaces, static members, and event handlers.
- Interfaces and namespaces are generated based on whether they
are standards track and experimental. If they are not generated,
any references to them will be replaced by the equivalent JS
type.
- Likewise, inheritance for interfaces is modified to subtype
the first generated interface in the inheritance hierarchy.
- Dictionaries and typedef-like types are generated based on
whether they are used as they don't have compatibility data. In
order to determine this, whenever we generate a _RawType, we
mark it as used, and recursively generate the types needed.
- For each API within an interface, compat data in that interface
and its superinterfaces are used to determine if an API is
generated.
- In order to support the above changes, intermediate
representations for some members (attributes, fields, constants)
are added. There are other members that might be worth moving to
an IR, but that refactoring can be done in a future CL.

Closes a number of issues:

dart-lang#209
dart-lang#234
dart-lang#216
dart-lang#205
dart-lang#203
dart-lang#192
  • Loading branch information
srujzs committed May 13, 2024
1 parent d776645 commit 7ed6d20
Show file tree
Hide file tree
Showing 3 changed files with 394 additions and 193 deletions.
96 changes: 83 additions & 13 deletions tool/generator/bcd.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import 'filesystem_api.dart';
/// property status (standards track, experimental, deprecated) and supported
/// browser (chrome, safari, firefox) info.
class BrowserCompatData {
static final _eventHandlers = <String, Set<BCDPropertyStatus>>{};

/// Returns whether [name] is an event handler that is supported in any
/// interface.
static bool isEventHandlerSupported(String name) =>
_eventHandlers[name]?.any((bcd) => bcd.shouldGenerate) == true;

static BrowserCompatData read() {
final path =
p.join('node_modules', '@mdn', 'browser-compat-data', 'data.json');
Expand All @@ -22,10 +29,48 @@ class BrowserCompatData {
) as JSString)
.toDart;

final api = (jsonDecode(content) as Map)['api'] as Map<String, dynamic>;
final interfaces = api.symbolNames
.map((key) => BCDInterfaceStatus(key, api[key] as Map<String, dynamic>))
.toList();
final contentMap = jsonDecode(content) as Map;
final api = contentMap['api'] as Map<String, dynamic>;
// MDN files WebAssembly compat data in a separate folder, so we need to
// unify.
final webassembly = (contentMap['webassembly']
as Map<String, dynamic>)['api'] as Map<String, dynamic>;
api.addAll(webassembly);
// Add info for the namespace as well.
api['WebAssembly'] = webassembly;

final interfaces = <BCDInterfaceStatus>{};
final globals = <String, Map<String, dynamic>>{};
final globalInterfaces = <BCDInterfaceStatus>{};
const globalsFilePrefix = 'api/_globals';

for (final symbolName in api.symbolNames) {
final apiInfo = api[symbolName] as Map<String, dynamic>;
final interface = BCDInterfaceStatus(symbolName, apiInfo);
if (interface._sourceFile.startsWith(globalsFilePrefix)) {
// MDN stores global members e.g. `isSecureContext` in the same location
// as the interfaces. These are not interfaces, but rather properties
// that should go in `Window` and `WorkerGlobalScope`. We cache the
// compat data and add them directly to the relevant interfaces later.
// https://github.com/mdn/browser-compat-data/blob/main/docs/data-guidelines/api.md#global-apis
globals[symbolName] = apiInfo;
// The compat data for the console namespace is within this property. It
// should be exposed both as a global and as a namespace.
if (symbolName == 'console') interfaces.add(interface);
} else {
interfaces.add(interface);
}
if (symbolName == 'Window' || symbolName == 'WorkerGlobalScope') {
globalInterfaces.add(interface);
}
}

globals.forEach((name, apiInfo) {
for (final globalInterface in globalInterfaces) {
globalInterface.addProperty(name, apiInfo);
}
});

return BrowserCompatData(Map.fromIterable(
interfaces,
key: (i) => (i as BCDInterfaceStatus).name,
Expand All @@ -37,29 +82,53 @@ class BrowserCompatData {
BrowserCompatData(this.interfaces);

BCDInterfaceStatus? retrieveInterfaceFor(String name) => interfaces[name];

bool shouldGenerateInterface(String name) =>
retrieveInterfaceFor(name)?.shouldGenerate ?? false;
}

class BCDInterfaceStatus extends BCDItem {
late final Map<String, BCDPropertyStatus> properties;
final _properties = <String, BCDPropertyStatus>{};

BCDInterfaceStatus(super.name, super.json) {
properties = Map.fromIterable(
json.symbolNames,
value: (name) => BCDPropertyStatus(
name as String, json[name] as Map<String, dynamic>, this),
);
for (final symbolName in json.symbolNames) {
addProperty(symbolName, json[symbolName] as Map<String, dynamic>);
}
}

void addProperty(String property, Map<String, dynamic> compat) {
// Event compatibility data is stored as `<name_of_event>_event`. In order
// to have compatibility data for `onX` properties, we need to replace such
// property names. See https://github.com/mdn/browser-compat-data/blob/main/docs/data-guidelines/api.md#dom-events-eventname_event
// for more details.
late BCDPropertyStatus status;
const eventSuffix = '_event';
if (property.endsWith(eventSuffix)) {
property = 'on${property.replaceAll(eventSuffix, '')}';
status = BCDPropertyStatus(property, compat, this);
BrowserCompatData._eventHandlers
.putIfAbsent(property, () => {})
.add(status);
} else {
status = BCDPropertyStatus(property, compat, this);
}
_properties[property] = status;
}

BCDPropertyStatus? retrievePropertyFor(String name) => properties[name];
BCDPropertyStatus? retrievePropertyFor(String name, {bool isStatic = false}) {
if (isStatic) name = '${name}_static';
return _properties[name];
}

bool get shouldGenerate =>
standardTrack && chromeSupported && firefoxSupported && safariSupported;
bool get shouldGenerate => standardTrack && !experimental;
}

class BCDPropertyStatus extends BCDItem {
final BCDInterfaceStatus parent;

BCDPropertyStatus(super.name, super.json, this.parent);

bool get shouldGenerate => standardTrack && !experimental;
}

abstract class BCDItem {
Expand All @@ -69,6 +138,7 @@ abstract class BCDItem {
BCDItem(this.name, this.json);

Map<String, dynamic> get _compat => json['__compat'] as Map<String, dynamic>;
String get _sourceFile => _compat['source_file'] as String;
Map<String, dynamic> get _status => _compat['status'] as Map<String, dynamic>;
Map<String, dynamic> get _support =>
_compat['support'] as Map<String, dynamic>;
Expand Down
2 changes: 1 addition & 1 deletion tool/generator/generate_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ Future<TranslationResult> generateBindings(
final ast = entry[1] as JSArray<webidl.Node>;
translator.collect(shortname, ast);
}
translator.setOrUpdateInterfacelikes();
translator.setOrUpdateInterfacesAndNamespaces();
return translator.translate();
}
Loading

0 comments on commit 7ed6d20

Please sign in to comment.