Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1a228f4
Squashed commit of the following:
nikeokoronkwo Mar 15, 2026
257fd7d
[generator] Added support for source map backed stack traces
nikeokoronkwo Mar 15, 2026
313d67b
Fixed analysis and format issues
nikeokoronkwo Mar 16, 2026
7fac81b
check processStderr before split
nikeokoronkwo Mar 16, 2026
5a259e6
fix JSDate test overflow and simplify transformer code (#523)
kevmoo Mar 30, 2026
e0ea82d
Bump dart-lang/setup-dart in the github-actions group (#524)
dependabot[bot] Apr 1, 2026
15599ee
Update min SDKs, regenerate (#526)
kevmoo Apr 13, 2026
b026317
generator: be more robust against NPM timing issues (#528)
kevmoo Apr 13, 2026
d7daf7d
generator: add some sanity to the language version logic (#530)
kevmoo Apr 14, 2026
3a7e37a
Stabilize JS Interop CI and decouple webref dependencies (#532)
kevmoo Apr 19, 2026
5e7285f
chore: update node invocation to use the source maps we already gener…
kevmoo Apr 24, 2026
db91caf
chore: write BCD version to README in web_generator (#537)
kevmoo Apr 27, 2026
2c47e36
feat: generate descriptive documentation for union typedefs (#534)
kevmoo Apr 28, 2026
5ed9bed
js_interop_gen: minimize the number of `@docImport` comments we creat…
kevmoo Apr 29, 2026
7c908b1
test(js_interop_gen): add integration tests (#541)
kevmoo Apr 29, 2026
41c4049
refactor: move most of the AST elements to their own file (#543)
kevmoo May 4, 2026
294391c
[chore] Sort the members in elements.dart (#544)
kevmoo May 4, 2026
5d4fd3c
refactor(js_interop_gen): extract pure utilities and data classes (#545)
kevmoo May 6, 2026
d9a042b
Squashed commit of the following:
nikeokoronkwo Mar 15, 2026
05e0405
[generator] Added support for source map backed stack traces
nikeokoronkwo Mar 15, 2026
91ed45c
Fixed analysis and format issues
nikeokoronkwo Mar 16, 2026
10e99d6
check processStderr before split
nikeokoronkwo Mar 16, 2026
3af5130
Merge branch 'feat/stack_traces' of https://github.com/nikeokoronkwo/…
nikeokoronkwo May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/js_interop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
test_config: ['-p chrome', '-p chrome -c dart2wasm', '-p node']

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c
with:
sdk: ${{ matrix.sdk }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/no-response.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'dart-lang' }}
steps:
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f
with:
days-before-stale: -1
days-before-close: 14
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/web.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
test_config: ['-p chrome', '-p chrome -c dart2wasm']

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c
with:
sdk: ${{ matrix.sdk }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/web_generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
test_config: ['', '-p chrome', '-p chrome -c dart2wasm']

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: actions/setup-node@v6
with:
node-version: 22
Expand All @@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c

- run: dart pub get
Expand All @@ -62,7 +62,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c

- run: dart pub get
Expand Down
1 change: 1 addition & 0 deletions web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Added `childNodesAsList` to `Node` and `childrenAsList` to `Element` via
extensions to support mutable operations on node lists.
- Added `asList` to `NodeList` via extension.
- Removed `CustomEventProviders`. Moved these events to `EventStreamProviders`.

## 1.1.1

Expand Down
11 changes: 11 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ long-term web interop solution. To learn how to migrate from `dart:html`
APIs to `package:web`, see our
[migration guide](https://dart.dev/go/package-web).

### Specific APIs

#### `document.cookie`

When migrating from `dart:html` to `package:web`, note that `document.cookie` is now **non-nullable** and always returns a `String`.

In `dart:html`, this getter was nullable to support very old browser versions where `Document.cookie` was not available.
`package:web` follows the Web IDL specification and does not include this legacy compatibility.

If no cookies are present, `document.cookie` returns an empty string (`""`), not `null`.

## Generation

This package is generated by `web_generator`. See the
Expand Down
12 changes: 6 additions & 6 deletions web/lib/src/helpers/events/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -384,28 +384,28 @@ extension DocumentEventGetters on Document {

extension ElementCustomEvents on Element {
ElementStream<WheelEvent> get onMouseWheel =>
CustomEventProviders.mouseWheelEvent.forElement(this);
EventStreamProviders.mouseWheelEvent.forElement(this);

ElementStream<TransitionEvent> get onTransitionEnd =>
CustomEventProviders.transitionEndEvent.forElement(this);
EventStreamProviders.transitionEndEvent.forElement(this);
}

extension DocumentCustomEvents on Document {
Stream<Event> get onLoad => EventStreamProviders.loadEvent.forTarget(this);

Stream<WheelEvent> get onMouseWheel =>
CustomEventProviders.mouseWheelEvent.forTarget(this);
EventStreamProviders.mouseWheelEvent.forTarget(this);

Stream<Event> get onVisibilityChange =>
CustomEventProviders.visibilityChangeEvent.forTarget(this);
EventStreamProviders.visibilityChangeEvent.forTarget(this);
}

extension WindowCustomEvents on Window {
Stream<WheelEvent> get onMouseWheel =>
CustomEventProviders.mouseWheelEvent.forTarget(this);
EventStreamProviders.mouseWheelEvent.forTarget(this);

Stream<TransitionEvent> get onTransitionEnd =>
CustomEventProviders.transitionEndEvent.forTarget(this);
EventStreamProviders.transitionEndEvent.forTarget(this);
}

extension WebSocketEvents on WebSocket {
Expand Down
2 changes: 0 additions & 2 deletions web/lib/src/helpers/events/providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,7 @@ abstract final class EventStreamProviders {

static const EventStreamProvider<ProgressEvent> writeStartEvent =
EventStreamProvider<ProgressEvent>('writestart');
}

class CustomEventProviders {
/// Expose custom EventStreamProvider for `mousewheel`.
static const EventStreamProvider<WheelEvent> mouseWheelEvent =
CustomEventStreamProvider<WheelEvent>(_determineMouseWheelEventType);
Expand Down
199 changes: 2 additions & 197 deletions web/lib/src/helpers/events/streams.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'dart:async';
import 'dart:js_interop';

import '../../dom.dart' as html;
import '../../helpers.dart' show Device;

/// Helper class used to create streams abstracting DOM events. This is a
/// piece of the helper layer directly derived from a similar feature in
Expand Down Expand Up @@ -147,21 +146,11 @@ class _EventStreamSubscription<T extends html.Event>
html.EventListener? _onData;
final bool _useCapture;

// TODO(leafp): It would be better to write this as
// _onData = onData == null ? null :
// onData is void Function(Event)
// ? _wrapZone<Event>(onData)
// : _wrapZone<Event>((e) => onData(e as T))
// In order to support existing tests which pass the wrong type of events but
// use a more general listener, without causing as much slowdown for things
// which are typed correctly. But this currently runs afoul of restrictions
// on is checks for compatibility with the VM.
_EventStreamSubscription(
this._target, this._eventType, void Function(T)? onData, this._useCapture)
: _onData = onData == null
? null
// ignore: avoid_dynamic_calls
: _wrapZone<html.Event>((e) => (onData as dynamic)(e))?.toJS {
: _wrapZone<html.Event>((e) => onData(e as T))?.toJS {
_tryResume();
}

Expand Down Expand Up @@ -213,8 +202,7 @@ class _EventStreamSubscription<T extends html.Event>
_unlisten();
_onData = handleData == null
? null
// ignore: avoid_dynamic_calls
: _wrapZone<html.Event>((e) => (handleData as dynamic)(e))?.toJS;
: _wrapZone<html.Event>((e) => handleData(e as T))?.toJS;
_tryResume();
}

Expand Down Expand Up @@ -265,196 +253,13 @@ class _EventStreamSubscription<T extends html.Event>
Completer<E>().future;
}

/// Base class that supports listening for and dispatching browser events.
///
/// Normally events are accessed via the Stream getter:
///
/// element.onMouseOver.listen((e) => print('Mouse over!'));
///
/// To access bubbling events which are declared on one element, but may bubble
/// up to another element type (common for MediaElement events):
///
/// MediaElement.pauseEvent.forTarget(document.body).listen(...);
///
/// To useCapture on events:
///
/// Element.keyDownEvent.forTarget(element, useCapture: true).listen(...);
///
/// Custom events can be declared as:
///
/// class DataGenerator {
/// static EventStreamProvider<Event> dataEvent =
/// new EventStreamProvider('data');
/// }
///
/// Then listeners should access the event with:
///
/// DataGenerator.dataEvent.forTarget(element).listen(...);
///
/// Custom events can also be accessed as:
///
/// element.on['some_event'].listen(...);
///
/// This approach is generally discouraged as it loses the event typing and
/// some DOM events may have multiple platform-dependent event names under the
/// covers. By using the standard Stream getters you will get the platform
/// specific event name automatically.
class Events {
final html.EventTarget _ptr;

Events(this._ptr);

Stream<html.Event> operator [](String type) =>
_EventStream(_ptr, type, false);
}

class ElementEvents extends Events {
static final _webkitEvents = {
'animationend': 'webkitAnimationEnd',
'animationiteration': 'webkitAnimationIteration',
'animationstart': 'webkitAnimationStart',
'fullscreenchange': 'webkitfullscreenchange',
'fullscreenerror': 'webkitfullscreenerror',
'keyadded': 'webkitkeyadded',
'keyerror': 'webkitkeyerror',
'keymessage': 'webkitkeymessage',
'needkey': 'webkitneedkey',
'pointerlockchange': 'webkitpointerlockchange',
'pointerlockerror': 'webkitpointerlockerror',
'resourcetimingbufferfull': 'webkitresourcetimingbufferfull',
'transitionend': 'webkitTransitionEnd',
'speechchange': 'webkitSpeechChange'
};

ElementEvents(html.Element super.ptr);

@override
Stream<html.Event> operator [](String type) {
if (_webkitEvents.keys.contains(type.toLowerCase())) {
if (Device.isWebKit) {
return _ElementEventStreamImpl(
_ptr, _webkitEvents[type.toLowerCase()]!, false);
}
}
return _ElementEventStreamImpl(_ptr, type, false);
}
}

/// Helper class to implement custom events which wrap DOM events.
// TODO(b/261997228): Add support for CustomEvents now that WrappedEvent is not
// implementing the JS interop html.Event type.
class WrappedEvent {
final html.Event wrapped;

/// The CSS selector involved with event delegation.
String? _selector;

WrappedEvent(this.wrapped);

bool get bubbles => wrapped.bubbles;

bool get cancelable => wrapped.cancelable;

bool get composed => wrapped.composed;

html.EventTarget? get currentTarget => wrapped.currentTarget;

bool get defaultPrevented => wrapped.defaultPrevented;

int get eventPhase => wrapped.eventPhase;

bool get isTrusted => wrapped.isTrusted;

html.EventTarget? get target => wrapped.target;

double get timeStamp => wrapped.timeStamp.toDouble();

String get type => wrapped.type;

void preventDefault() {
wrapped.preventDefault();
}

void stopImmediatePropagation() {
wrapped.stopImmediatePropagation();
}

void stopPropagation() {
wrapped.stopPropagation();
}

List<html.EventTarget> composedPath() =>
wrapped.composedPath().toDart.cast<html.EventTarget>();

html.Element get matchingTarget {
if (_selector == null) {
throw UnsupportedError('Cannot call matchingTarget if this Event did'
' not arise as a result of event delegation.');
}
final currentTarget = this.currentTarget as html.Element?;
var target = this.target as html.Element?;
do {
if (target!.matches(_selector!)) return target;
target = target.parentElement;
} while (target != null && target != currentTarget!.parentElement);
throw StateError('No selector matched for populating matchedTarget.');
}
}

void Function(T)? _wrapZone<T>(void Function(T)? callback) {
// For performance reasons avoid wrapping if we are in the root zone.
if (Zone.current == Zone.root) return callback;
if (callback == null) return null;
return Zone.current.bindUnaryCallbackGuarded(callback);
}

void Function(T1, T2)? wrapBinaryZone<T1, T2>(void Function(T1, T2)? callback) {
// For performance reasons avoid wrapping if we are in the root zone.
if (Zone.current == Zone.root) return callback;
if (callback == null) return null;
return Zone.current.bindBinaryCallbackGuarded(callback);
}

/// A stream of custom events, which enables the user to "fire" (add) their own
/// custom events to a stream.
abstract class CustomStream<T extends html.Event> implements Stream<T> {
/// Add the following custom event to the stream for dispatching to interested
/// listeners.
void add(T event);
}

class CustomEventStreamImpl<T extends html.Event> extends Stream<T>
implements CustomStream<T> {
StreamController<T> streamController;

/// The type of event this stream is providing (e.g. "keydown").
String type;

CustomEventStreamImpl(this.type)
: streamController = StreamController.broadcast(sync: true);

// Delegate all regular Stream behavior to our wrapped Stream.
@override
StreamSubscription<T> listen(void Function(T)? onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) =>
streamController.stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);

@override
Stream<T> asBroadcastStream(
{void Function(StreamSubscription<T>)? onListen,
void Function(StreamSubscription<T>)? onCancel}) =>
streamController.stream;

@override
bool get isBroadcast => true;

@override
void add(T event) {
if (event.type == type) streamController.add(event);
}
}

/// A factory to expose DOM events as streams, where the DOM event name has to
/// be determined on the fly (for example, mouse wheel events).
class CustomEventStreamProvider<T extends html.Event>
Expand Down
Loading
Loading