Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS interop pain #55012

Closed
travishaagen opened this issue Feb 26, 2024 · 7 comments
Closed

JS interop pain #55012

travishaagen opened this issue Feb 26, 2024 · 7 comments
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. web-js-interop Issues that impact all js interop

Comments

@travishaagen
Copy link

travishaagen commented Feb 26, 2024

Hey. I have a build of Chromium with a custom Sensor object, and I'm trying to bind to it. The Sensor is similar to this one found in html_dart2js.dart:

@Native("Accelerometer")
class Accelerometer extends Sensor {
  // To suppress missing implicit constructor warnings.
  factory Accelerometer._() {
    throw new UnsupportedError("Not supported");
  }

  factory Accelerometer([Map? sensorOptions]) {
    if (sensorOptions != null) {
      var sensorOptions_1 = convertDartToNative_Dictionary(sensorOptions);
      return Accelerometer._create_1(sensorOptions_1);
    }
    return Accelerometer._create_2();
  }
  static Accelerometer _create_1(sensorOptions) =>
      JS('Accelerometer', 'new Accelerometer(#)', sensorOptions);
  static Accelerometer _create_2() =>
      JS('Accelerometer', 'new Accelerometer()');

  num? get x native;

  num? get y native;

  num? get z native;
}

However, the above is using some internal APIs, and I can't seem to get past setting sensorOptions on my version.

@JS()
extension type CustomSensor._(JSObject _) implements JSObject {
  external CustomSensor(SensorOptions sensorOptions);

  external void start();

  external void stop();

  external num get myValue;

  external void addEventListener(String type, JSFunction? callback, [JSAny options]);
}

@JS()
@anonymous
extension type SensorOptions._(JSObject _) implements JSObject {
  external factory SensorOptions({num frequency});

  external num get frequency;
}

The errors complain about the constructor. SensorOptions is supposed to be like a JS dictionary object, so that's why I was trying @anonymous. Any ideas?

@lrhn lrhn added the area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. label Feb 26, 2024
@navaronbracke
Copy link

navaronbracke commented Feb 26, 2024

This works fine on my end:

import 'dart:js_interop';

/// The JS `Accelerometer` as an extension typed JS binding.
///
/// The extension type defines the primary constructor as private.
@JS()
@staticInterop
extension type Accelerometer._(JSObject _) implements JSObject {
  external factory Accelerometer([SensorOptions? options]);

  external num? get x;

  external num? get y;

  external num? get z;

  external bool? get hasReading;

  external bool? get activated;
}

@JS()
@staticInterop
@anonymous
extension type SensorOptions._(JSObject _) implements JSObject {
  external factory SensorOptions({double frequency});
}

@srujzs
Copy link
Contributor

srujzs commented Feb 26, 2024

FYI - You don't need the @staticInterop or @anonymous annotations anymore, they don't do anything for extension types and might just get in the way. Extension types are static by definition, so @staticInterop isn't necessary. Simply having a constructor with named arguments will make it an object literal constructor, so @anonymous isn't needed anymore either. I should add an error to make that obvious.

I assume you're coming across the bug mentioned in the above link where you need to have an @JS annotated library until 3.4 for object literal constructors, so this should work:

@JS()
library;

import 'dart:js_interop';

extension type CustomSensor._(JSObject _) implements JSObject {
  external CustomSensor(SensorOptions sensorOptions);

  external void start();
  external void stop();
  external num get myValue;
  external void addEventListener(String type, JSFunction? callback, [JSAny options]);
}

extension type SensorOptions._(JSObject _) implements JSObject {
  external factory SensorOptions({num frequency});

  external num get frequency;
}

Let me know if you're hitting some other bug instead.

@srujzs srujzs added the web-js-interop Issues that impact all js interop label Feb 26, 2024
@travishaagen
Copy link
Author

Thank you for the replies! Turns out it wasn't the Sensor constructor throwing an error, but my attempts to pass a Dart function to this callback argument:

external void addEventListener(String type, JSFunction? callback, [JSAny options]);

@srujzs
Copy link
Contributor

srujzs commented Feb 26, 2024

Ah yeah, you'll need a .toJS call so that it can be converted to a JS function: https://dart.dev/interop/js-interop/js-types#conversions.

@travishaagen
Copy link
Author

Compiler appeared happier with,

var readingInterop = js_util.allowInterop(readingCallback) as JSExportedDartFunction;
mySensor.addEventListener("reading", readingInterop);

What are my options for handling the Event that gets passed to the callback? The event I'm receiving has custom properties.

@srujzs
Copy link
Contributor

srujzs commented Feb 26, 2024

var readingInterop = js_util.allowInterop(readingCallback) as JSExportedDartFunction;

I'd avoid using allowInterop (or js_util in general) for this purpose since it won't be Wasm-compatible. Can you share what compilation issues you came across with using Function.toJS? I'm guessing the compiler gave you an error on one or more of the types of the Dart callback you tried to convert? If so, this might be relevant and all you may need to do is provide the right types.

What are my options for handling the Event that gets passed to the callback? The event I'm receiving has custom properties.

If it's a custom Event, you can declare your own interop type like how we do in package:web e.g.

extension type CustomEvent(JSObject o) implements Event {
  // declare whatever custom properties here
}

implements Event allows you to reuse the Event members from package:web which I presume you'd want. If not, you can omit that clause, or just have it implement JSObject. Then, use CustomEvent as the type of the Event in the callback:

void readingInterop(CustomEvent event) {
  // do something with `event`
}
mySensor.addEventListener("reading", readingInterop.toJS);

@travishaagen
Copy link
Author

Thanks both of you for the help!

I was passing in a Function callback, but by using a local, typed callback function it started working.

I used a StreamController to broadcast the events to subscribers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. web-js-interop Issues that impact all js interop
Projects
None yet
Development

No branches or pull requests

4 participants