Skip to content

[js interop] Classes inside namespaces on the window cannot be constructed #54531

@navaronbracke

Description

@navaronbracke

I was updating a JS interop part of a library and encountered that globalThis did not contain a property which should have pointed to a JS class.

The library that I am integrating is ZXing, a barcode scanning library, which supports the web.

To reproduce:

  1. flutter create web_sample --platforms=web
  2. Add the ZXing script in the head tag of the index.html
<script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>
  1. Add the following code sample to lib/interop.dart
lib/interop.dart
// ./lib/interop.dart
import 'dart:js_interop';

/// A static interop stub for the `Map` class.
///
/// This stub is here because the `js_types` from the Dart SDK do not yet provide a `Map` equivalent:
/// https://github.com/dart-lang/sdk/issues/54365
///
/// See also: https://github.com/dart-lang/sdk/issues/54365#issuecomment-1856995463
///
/// Object literals can be made using [jsify].
@JS('Map')
@staticInterop
class JsMap<K extends JSAny, V extends JSAny> implements JSObject {
  external factory JsMap();
}

extension JsMapExtension<K extends JSAny, V extends JSAny> on JsMap<K, V> {
  external V? get(K key);
  external JSVoid set(K key, V? value);
}

/// The JS interop class for the ZXing BrowserMultiFormatReader.
///
/// See https://github.com/zxing-js/library/blob/master/src/browser/BrowserMultiFormatReader.ts
@JS('ZXing.BrowserMultiFormatReader')
@staticInterop
class ZXingBrowserMultiFormatReader implements JSObject {
  /// Construct a new ZXingBrowserMultiFormatReader.
  ///
  /// The [hints] are the configuration options for the reader.
  /// The [timeBetweenScansMillis] is the allowed time between scans in milliseconds.
  ///
  /// See also: https://github.com/zxing-js/library/blob/master/src/core/DecodeHintType.ts
  external factory ZXingBrowserMultiFormatReader(
    JsMap? hints,
    int? timeBetweenScansMillis,
  );
}
  1. Replace main.dart with
main.dart
import 'dart:js_interop';
import 'dart:js_util';

import 'package:flutter/material.dart';

import 'interop.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ZXingBrowserMultiFormatReader? reader;

  @override
  void initState() {
    super.initState();

    final JSObject? zxing = getProperty(globalThis, 'ZXing');
    final JSObject? multiFormatReader = getProperty(globalThis, 'ZXing.BrowserMultiFormatReader');

    print('ZXing is defined? ${zxing.isDefinedAndNotNull}');
    print('ZXing.BrowserMultiFormatReader is defined? ${multiFormatReader.isDefinedAndNotNull}');

    reader = ZXingBrowserMultiFormatReader(
      JsMap(),
      500,
    );

    print('reader variable is defined? ${reader?.isDefinedAndNotNull}');
    print('reader is a ZXing.BrowserMultiFormatReader? ${instanceOfString(reader, 'ZXing.BrowserMultiFormatReader')}');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: const Center(),
    );
  }
}
  1. Run the application with flutter run -d chrome
  2. Observe the following log:
ZXing is defined? true
ZXing.BrowserMultiFormatReader is defined? false
reader variable is defined? true
reader is a ZXing.BrowserMultiFormatReader? false
  1. Open the browser console, and enter the expression "globalThis.ZXing.BrowserMultiFormatReader"
    This outputs
class extends L{constructor(t=null,e=500){const r=new ir;r.setHints(t),super(r,e)}decodeBitmap(t){return this.reader.decodeWithState(t)}}

It seems that the globalThis in Dart, is not fully in sync with the browser equivalent?
I have also tried using the callAsConstructor<T>() utility, where I had ZXing as a separate interop type,
which had BrowserMultiFormatReader as a JSFunction, but that ended up with the same result. (probably because that also looks at globalThis)

dart info

#### General info

- Dart 3.2.3 (stable) (Tue Dec 5 17:58:33 2023 +0000) on "macos_x64"
- on macos / Version 14.1.2 (Build 23B92)
- locale is en-BE

#### Process info

| Memory |  CPU | Elapsed time | Command line                                                                               |
| -----: | ---: | -----------: | ------------------------------------------------------------------------------------------ |
|  57 MB | 0.0% |     03:23:00 | dart devtools --machine --allow-embedding                                                  |
|  59 MB | 0.0% |        02:51 | dart devtools --no-launch-browser                                                          |
| 963 MB | 0.0% |     03:23:00 | dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.80.0            |
| 138 MB | 0.0% |     03:23:00 | flutter_tools.snapshot daemon                                                              |
| 202 MB | 1.3% |        03:20 | flutter_tools.snapshot run -d chrome                                                       |
| 602 MB | 0.0% |        03:19 | frontend_server.dart.snapshot --sdk-root <path>/ --incremental --target=dartdevc --experimental-emit-debug-metadata -DFLUTTER_WEB_AUTO_DETECT=true -DFLUTTER_WEB_CANVASKIT_URL=https:<path>/ --output-dill <path>/app.dill --packages <path>/package_config.json -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --filesystem-root <path>/flutter_tools.9p94Xi --filesystem-scheme org-dartlang-app --initialize-from-dill build/a072907fec955372e484c180f3334617.cache.dill.track.dill --platform file:<path>/ddc_outline_sound.dill --verbosity=error --sound-null-safety |

MacOS Sonoma 14.1.2
Google Chrome 120.0.6099.199 (Official Build) (x86_64)
Flutter 3.16.5 / Dart 3.2.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.web-js-interopIssues that impact all js interop

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions