dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.21k stars 1.57k forks source link

Error: Expected a value of type 'MouseEvent$', but got one of type 'Event$' #53626

Open erwamartin opened 1 year ago

erwamartin commented 1 year ago

I'm facing an issue where I'm getting an error coming from the Dart SDK when using the Dashlane Chrome extension to fill a text input:

Uncaught (in promise) Error: Expected a value of type 'MouseEvent$', but got one of type 'Event$'
    at Object.throw_ [as throw] (errors.dart:291:21)
    at Object.castError (errors.dart:127:3)
    at Object.cast [as as] (operations.dart:742:12)
    at MouseEvent$.as_C [as as] (classes.dart:660:14)
    at Object._argumentErrors (operations.dart:364:42)
    at Object._checkAndCall (operations.dart:570:22)
    at Object.dcall (operations.dart:579:39)
    at html_dart2js.dart:37236:58
    at _rootRunUnary (zone.dart:1415:13)
    at async._CustomZone.new.runUnary (zone.dart:1308:19)
    at async._CustomZone.new.runUnaryGuarded (zone.dart:1217:7)
    at HTMLDivElement.<anonymous> (zone.dart:1254:26)

The input is created like this:

final InputElement _emailInput = InputElement()
    ..placeholder = 'Your email address'
    ..type = 'email';

How can we ignore events that are not implementing MouseEvent?

Details

sigmundch commented 1 year ago

I'm not sure if we have enough context yet, but my current hunch is that this should hopefully be addressable in your application logic.

The error location points to https://github.com/dart-lang/sdk/blob/3.1.2/sdk/lib/html/dart2js/html_dart2js.dart#L37236:

  _EventStreamSubscription(
      this._target, this._eventType, void onData(T event)?, this._useCapture)
      : _onData = onData == null
            ? null
            // If removed, we would need an `is` check on a function type which
            // is ultimately more expensive.
            // ignore: avoid_dynamic_calls
            : _wrapZone<Event>((e) => (onData as dynamic)(e)) {
                                                         ^ HERE

The (onData as dynamic)(e) is invoking an event listener, which is added by using some of the custom event streams exposed by dart:html (like element.onFoo.listen(callback)). The compiled code is checking that the T allows the value of e. It appears the compiler believes that in your case the onData callback expects a MouseEvent as input. This could be because the callback has a type that is too narrow, or because there is an error in dart:html on what kind of event is expected from a specific kind of event stream.

Could you share how are you listening for events on this input element in your application? That could give us the missing clue to figure out what is at fault here. If the MouseEvent type is declared in your application, the fix may simply be to relax that type.

eernstg commented 1 year ago

If _wrapZone<Event>((e) => (onData as dynamic)(e)) were changed to _wrapZone<T>((e) => (onData as dynamic)(e)) then we'd avoid having a function accepting a T wrapped by a function that accepts an Event (so the outer function seems to be happy about a non-T event, but the inner one still throws). We will then have a function accepting a T wrapped by another function that accepts a T.

If _onData is later invoked (dynamically, or with an argument of type dynamic) with a non-T event then we will still get the run-time error. But it is now possible to do things like if (_onData is void Function(Event)) _onData(myEvent), and actually avoid calling _onData when we have an event that it doesn't want. We could also do if (myEvent is T) _onData(myEvent), which is even better (but this is not being done today because then we wouldn't have had that type error in the first place).

I don't know whether this kind of testing is actually used, I'm just commenting on this particular snippet of code from a language perspective. However, this change seems to be an improvement in type safety, no matter how that function object is used later on.