[web] Migrate Flutter Web DOM usage to JS static interop - 24. (flutter/engine#33352)

This commit is contained in:
joshualitt 2022-06-09 10:46:46 -07:00 committed by GitHub
parent 71b57d8468
commit 65e48da91c
7 changed files with 103 additions and 55 deletions

View File

@ -26,8 +26,10 @@ extension DomWindowExtension on DomWindow {
external DomConsole get console;
external num get devicePixelRatio;
external DomDocument get document;
external DomHistory get history;
external int? get innerHeight;
external int? get innerWidth;
external DomLocation get location;
external DomNavigator get navigator;
external DomVisualViewport? get visualViewport;
external DomPerformance get performance;
@ -61,7 +63,7 @@ extension DomNavigatorExtension on DomNavigator {
@JS()
@staticInterop
class DomDocument {}
class DomDocument extends DomNode {}
extension DomDocumentExtension on DomDocument {
external DomElement? get documentElement;
@ -142,6 +144,7 @@ extension DomProgressEventExtension on DomProgressEvent {
class DomNode extends DomEventTarget {}
extension DomNodeExtension on DomNode {
external String? get baseUri;
external DomNode? get firstChild;
external String get innerText;
external DomNode? get lastChild;
@ -691,6 +694,67 @@ extension DomKeyboardEventExtension on DomKeyboardEvent {
external bool getModifierState(String keyArg);
}
@JS()
@staticInterop
class DomHistory {}
extension DomHistoryExtension on DomHistory {
dynamic get state => js_util.dartify(js_util.getProperty(this, 'state'));
external void go([int? delta]);
void pushState(dynamic data, String title, String? url) =>
js_util.callMethod(this, 'pushState', <Object?>[
if (data is Map || data is Iterable) js_util.jsify(data) else data,
title,
url
]);
void replaceState(dynamic data, String title, String? url) =>
js_util.callMethod(this, 'replaceState', <Object?>[
if (data is Map || data is Iterable) js_util.jsify(data) else data,
title,
url
]);
}
@JS()
@staticInterop
class DomLocation {}
extension DomLocationExtension on DomLocation {
external String? get pathname;
external String? get search;
// We have to change the name here because 'hash' is inherited from [Object].
String get locationHash => js_util.getProperty(this, 'hash');
}
@JS()
@staticInterop
class DomPopStateEvent extends DomEvent {}
DomPopStateEvent createDomPopStateEvent(
String type, Map<Object?, Object?>? eventInitDict) =>
domCallConstructorString('PopStateEvent', <Object>[
type,
if (eventInitDict != null) js_util.jsify(eventInitDict)
])! as DomPopStateEvent;
extension DomPopStateEventExtension on DomPopStateEvent {
dynamic get state => js_util.dartify(js_util.getProperty(this, 'state'));
}
Object? domGetConstructor(String constructorName) =>
js_util.getProperty(domWindow, constructorName);
Object? domCallConstructorString(String constructorName, List<Object?> args) {
final Object? constructor = domGetConstructor(constructorName);
if (constructor == null) {
return null;
}
return js_util.callConstructor(constructor, args);
}
bool domInstanceOfString(Object? element, String objectType) =>
js_util.instanceof(element, domGetConstructor(objectType)!);
/// [_DomElementList] is the shared interface for APIs that return either
/// `NodeList` or `HTMLCollection`. Do *not* add any API to this class that
/// isn't support by both JS objects. Furthermore, this is an internal class and
@ -741,17 +805,3 @@ class _DomElementListWrapper extends Iterable<DomElement> {
@override
int get length => elementList.length;
}
Object? domGetConstructor(String constructorName) =>
js_util.getProperty(domWindow, constructorName);
Object? domCallConstructorString(String constructorName, List<Object?> args) {
final Object? constructor = domGetConstructor(constructorName);
if (constructor == null) {
return null;
}
return js_util.callConstructor(constructor, args);
}
bool domInstanceOfString(Object? element, String objectType) =>
js_util.instanceof(element, domGetConstructor(objectType)!);

View File

@ -2,11 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html' as html;
import 'package:meta/meta.dart';
import 'package:ui/ui.dart' as ui;
import '../dom.dart';
import '../platform_dispatcher.dart';
import '../services/message_codec.dart';
import '../services/message_codecs.dart';
@ -52,9 +51,7 @@ abstract class BrowserHistory {
bool _isDisposed = false;
void _setupStrategy(UrlStrategy strategy) {
_unsubscribe = strategy.addPopStateListener(
onPopState as html.EventListener,
);
_unsubscribe = strategy.addPopStateListener(onPopState as DomEventListener);
}
/// Release any resources held by this [BrowserHistory] instance.
@ -103,7 +100,7 @@ abstract class BrowserHistory {
///
/// Subclasses should send appropriate system messages to update the flutter
/// applications accordingly.
void onPopState(covariant html.PopStateEvent event);
void onPopState(covariant DomPopStateEvent event);
/// Restore any modifications to the html browser history during the lifetime
/// of this class.
@ -186,7 +183,7 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
}
@override
void onPopState(covariant html.PopStateEvent event) {
void onPopState(covariant DomPopStateEvent event) {
assert(urlStrategy != null);
// May be a result of direct url access while the flutter application is
// already running.
@ -263,7 +260,7 @@ class SingleEntryBrowserHistory extends BrowserHistory {
_setupStrategy(strategy);
final String path = currentPath;
if (!_isFlutterEntry(html.window.history.state)) {
if (!_isFlutterEntry(domWindow.history.state)) {
// An entry may not have come from Flutter, for example, when the user
// refreshes the page. They land directly on the "flutter" entry, so
// there's no need to set up the "origin" and "flutter" entries, we can
@ -314,7 +311,7 @@ class SingleEntryBrowserHistory extends BrowserHistory {
String? _userProvidedRouteName;
@override
void onPopState(covariant html.PopStateEvent event) {
void onPopState(covariant DomPopStateEvent event) {
if (_isOriginEntry(event.state)) {
_setupFlutterEntry(urlStrategy!);

View File

@ -5,16 +5,16 @@
@JS()
library js_url_strategy;
import 'dart:html' as html;
import 'package:js/js.dart';
import 'package:ui/ui.dart' as ui;
import '../dom.dart';
typedef _PathGetter = String Function();
typedef _StateGetter = Object? Function();
typedef _AddPopStateListener = ui.VoidCallback Function(html.EventListener);
typedef _AddPopStateListener = ui.VoidCallback Function(DomEventListener);
typedef _StringToString = String Function(String);
@ -47,7 +47,7 @@ abstract class JsUrlStrategy {
extension JsUrlStrategyExtension on JsUrlStrategy {
/// Adds a listener to the `popstate` event and returns a function that, when
/// invoked, removes the listener.
external ui.VoidCallback addPopStateListener(html.EventListener fn);
external ui.VoidCallback addPopStateListener(DomEventListener fn);
/// Returns the active path in the browser.
external String getPath();

View File

@ -3,11 +3,12 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:html' as html;
import 'package:js/js.dart' as js;
import 'package:ui/ui.dart' as ui;
import '../dom.dart';
import '../safe_browser_api.dart';
import 'js_url_strategy.dart';
/// Represents and reads route state from the browser's URL.
@ -21,7 +22,7 @@ abstract class UrlStrategy {
/// Adds a listener to the `popstate` event and returns a function that, when
/// invoked, removes the listener.
ui.VoidCallback addPopStateListener(html.EventListener fn);
ui.VoidCallback addPopStateListener(DomEventListener fn);
/// Returns the active path in the browser.
String getPath();
@ -82,9 +83,10 @@ class HashUrlStrategy extends UrlStrategy {
final PlatformLocation _platformLocation;
@override
ui.VoidCallback addPopStateListener(html.EventListener fn) {
_platformLocation.addPopStateListener(fn);
return () => _platformLocation.removePopStateListener(fn);
ui.VoidCallback addPopStateListener(DomEventListener fn) {
final DomEventListener wrappedFn = allowInterop(fn);
_platformLocation.addPopStateListener(wrappedFn);
return () => _platformLocation.removePopStateListener(wrappedFn);
}
@override
@ -156,7 +158,7 @@ class CustomUrlStrategy extends UrlStrategy {
final JsUrlStrategy delegate;
@override
ui.VoidCallback addPopStateListener(html.EventListener fn) =>
ui.VoidCallback addPopStateListener(DomEventListener fn) =>
delegate.addPopStateListener(js.allowInterop(fn));
@override
@ -194,13 +196,13 @@ abstract class PlatformLocation {
/// Registers an event listener for the `popstate` event.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
void addPopStateListener(html.EventListener fn);
void addPopStateListener(DomEventListener fn);
/// Unregisters the given listener (added by [addPopStateListener]) from the
/// `popstate` event.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
void removePopStateListener(html.EventListener fn);
void removePopStateListener(DomEventListener fn);
/// The `pathname` part of the URL in the browser address bar.
///
@ -256,17 +258,17 @@ class BrowserPlatformLocation extends PlatformLocation {
/// Default constructor for [BrowserPlatformLocation].
const BrowserPlatformLocation();
html.Location get _location => html.window.location;
html.History get _history => html.window.history;
DomLocation get _location => domWindow.location;
DomHistory get _history => domWindow.history;
@override
void addPopStateListener(html.EventListener fn) {
html.window.addEventListener('popstate', fn);
void addPopStateListener(DomEventListener fn) {
domWindow.addEventListener('popstate', fn);
}
@override
void removePopStateListener(html.EventListener fn) {
html.window.removeEventListener('popstate', fn);
void removePopStateListener(DomEventListener fn) {
domWindow.removeEventListener('popstate', fn);
}
@override
@ -276,7 +278,7 @@ class BrowserPlatformLocation extends PlatformLocation {
String get search => _location.search!;
@override
String get hash => _location.hash;
String get hash => _location.locationHash;
@override
Object? get state => _history.state;
@ -297,5 +299,5 @@ class BrowserPlatformLocation extends PlatformLocation {
}
@override
String? getBaseHref() => html.document.baseUri;
String? getBaseHref() => domDocument.baseUri;
}

View File

@ -6,7 +6,6 @@
// https://github.com/flutter/flutter/issues/100394
import 'dart:async';
import 'dart:html' as html;
import 'package:ui/ui.dart' as ui;
@ -149,15 +148,16 @@ class TestUrlStrategy extends UrlStrategy {
});
}
final List<html.EventListener> listeners = <html.EventListener>[];
final List<DomEventListener> listeners = <DomEventListener>[];
@override
ui.VoidCallback addPopStateListener(html.EventListener fn) {
listeners.add(fn);
ui.VoidCallback addPopStateListener(DomEventListener fn) {
final DomEventListener wrappedFn = allowInterop(fn);
listeners.add(wrappedFn);
return () {
// Schedule a micro task here to avoid removing the listener during
// iteration in [_firePopStateEvent].
scheduleMicrotask(() => listeners.remove(fn));
scheduleMicrotask(() => listeners.remove(wrappedFn));
};
}
@ -172,7 +172,7 @@ class TestUrlStrategy extends UrlStrategy {
/// like a real browser.
void _firePopStateEvent() {
assert(withinAppHistory);
final html.PopStateEvent event = html.PopStateEvent(
final DomPopStateEvent event = createDomPopStateEvent(
'popstate',
<String, dynamic>{'state': currentEntry.state},
);

View File

@ -6,12 +6,11 @@
// TODO(mdebbar): https://github.com/flutter/flutter/issues/51169
import 'dart:async';
import 'dart:html' as html;
import 'package:quiver/testing/async.dart';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart' show window;
import 'package:ui/src/engine.dart' show window, DomEventListener;
import 'package:ui/src/engine/browser_detection.dart';
import 'package:ui/src/engine/navigation.dart';
import 'package:ui/src/engine/services.dart';
@ -723,12 +722,12 @@ class TestPlatformLocation extends PlatformLocation {
String get search => throw UnimplementedError();
@override
void addPopStateListener(html.EventListener fn) {
void addPopStateListener(DomEventListener fn) {
throw UnimplementedError();
}
@override
void removePopStateListener(html.EventListener fn) {
void removePopStateListener(DomEventListener fn) {
throw UnimplementedError();
}

View File

@ -47,7 +47,7 @@ void testMain() {
final JsUrlStrategy jsUrlStrategy = JsUrlStrategy(
getPath: allowInterop(() => '/initial'),
getState: allowInterop(() => state),
addPopStateListener: allowInterop((html.EventListener listener) => () {}),
addPopStateListener: allowInterop((DomEventListener listener) => () {}),
prepareExternalUrl: allowInterop((String value) => ''),
pushState: allowInterop((Object? newState, String title, String url) {
expect(newState is Map, true);