Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 50bfa81

Browse files
authored
[web] Hide JS types from dart:ui_web (#42252)
In order to use the exported `PlatformLocation` cross-platform, we need to remove any JS types from the interface. Namely, `DomEventListener`. Required by flutter/flutter#126851
1 parent e97d841 commit 50bfa81

File tree

4 files changed

+66
-22
lines changed

4 files changed

+66
-22
lines changed

lib/web_ui/lib/src/engine/dom.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2119,7 +2119,7 @@ extension DomHistoryExtension on DomHistory {
21192119
external JSVoid _go1();
21202120
@JS('go')
21212121
external JSVoid _go2(JSNumber delta);
2122-
void go([double? delta]) {
2122+
void go([int? delta]) {
21232123
if (delta == null) {
21242124
_go1();
21252125
} else {

lib/web_ui/lib/ui_web/src/ui_web/navigation/platform_location.dart

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:meta/meta.dart';
56
import 'package:ui/src/engine.dart';
67

78
import 'url_strategy.dart';
89

10+
/// Function type that handles pop state events.
11+
typedef EventListener = dynamic Function(Object event);
12+
913
/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes
1014
/// to be platform agnostic and testable.
1115
///
@@ -15,13 +19,13 @@ abstract interface class PlatformLocation {
1519
/// Registers an event listener for the `popstate` event.
1620
///
1721
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
18-
void addPopStateListener(DomEventListener fn);
22+
void addPopStateListener(EventListener fn);
1923

2024
/// Unregisters the given listener (added by [addPopStateListener]) from the
2125
/// `popstate` event.
2226
///
2327
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
24-
void removePopStateListener(DomEventListener fn);
28+
void removePopStateListener(EventListener fn);
2529

2630
/// The `pathname` part of the URL in the browser address bar.
2731
///
@@ -64,14 +68,17 @@ abstract interface class PlatformLocation {
6468
/// * `go(3)` moves forward 3 steps in hisotry.
6569
///
6670
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go
67-
void go(double count);
71+
void go(int count);
6872

6973
/// The base href where the Flutter app is being served.
7074
///
7175
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
7276
String? getBaseHref();
7377
}
7478

79+
final Map<EventListener, DomEventListener> _popStateListenersCache =
80+
<EventListener, DomEventListener>{};
81+
7582
/// Delegates to real browser APIs to provide platform location functionality.
7683
class BrowserPlatformLocation implements PlatformLocation {
7784
/// Default constructor for [BrowserPlatformLocation].
@@ -80,14 +87,24 @@ class BrowserPlatformLocation implements PlatformLocation {
8087
DomLocation get _location => domWindow.location;
8188
DomHistory get _history => domWindow.history;
8289

90+
@visibleForTesting
91+
DomEventListener getOrCreateDomEventListener(EventListener fn) {
92+
return _popStateListenersCache.putIfAbsent(fn, () => createDomEventListener(fn));
93+
}
94+
8395
@override
84-
void addPopStateListener(DomEventListener fn) {
85-
domWindow.addEventListener('popstate', fn);
96+
void addPopStateListener(EventListener fn) {
97+
domWindow.addEventListener('popstate', getOrCreateDomEventListener(fn));
8698
}
8799

88100
@override
89-
void removePopStateListener(DomEventListener fn) {
90-
domWindow.removeEventListener('popstate', fn);
101+
void removePopStateListener(EventListener fn) {
102+
assert(
103+
_popStateListenersCache.containsKey(fn),
104+
'Removing a listener that was never added or was removed already.',
105+
);
106+
domWindow.removeEventListener('popstate', getOrCreateDomEventListener(fn));
107+
_popStateListenersCache.remove(fn);
91108
}
92109

93110
@override
@@ -113,7 +130,7 @@ class BrowserPlatformLocation implements PlatformLocation {
113130
}
114131

115132
@override
116-
void go(double count) {
133+
void go(int count) {
117134
_history.go(count);
118135
}
119136

lib/web_ui/lib/ui_web/src/ui_web/navigation/url_strategy.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ class HashUrlStrategy extends UrlStrategy {
151151

152152
@override
153153
ui.VoidCallback addPopStateListener(PopStateListener fn) {
154-
final DomEventListener wrappedFn = createDomEventListener((DomEvent event) {
154+
void wrappedFn(Object event) {
155155
// `fn` expects `event.state`, not a `DomEvent`.
156156
fn((event as DomPopStateEvent).state);
157-
});
157+
}
158158
_platformLocation.addPopStateListener(wrappedFn);
159159
return () => _platformLocation.removePopStateListener(wrappedFn);
160160
}
@@ -199,7 +199,7 @@ class HashUrlStrategy extends UrlStrategy {
199199

200200
@override
201201
Future<void> go(int count) {
202-
_platformLocation.go(count.toDouble());
202+
_platformLocation.go(count);
203203
return _waitForPopState();
204204
}
205205

lib/web_ui/test/engine/history_test.dart

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,19 @@
33
// found in the LICENSE file.
44

55
import 'dart:async';
6-
import 'dart:js_interop'
7-
show JSExportedDartFunction, JSExportedDartFunctionToFunction;
86

97
import 'package:quiver/testing/async.dart';
108
import 'package:test/bootstrap/browser.dart';
119
import 'package:test/test.dart';
1210
import 'package:ui/src/engine.dart' show window;
1311
import 'package:ui/src/engine/dom.dart'
14-
show DomEvent, DomEventListener, createDomPopStateEvent;
12+
show DomEvent, createDomPopStateEvent;
1513
import 'package:ui/src/engine/navigation.dart';
1614
import 'package:ui/src/engine/services.dart';
1715
import 'package:ui/src/engine/test_embedding.dart';
1816
import 'package:ui/ui_web/src/ui_web.dart';
1917

18+
import '../common/matchers.dart';
2019
import '../common/spy.dart';
2120

2221
Map<String, dynamic> _wrapOriginState(dynamic state) {
@@ -690,6 +689,35 @@ void testMain() {
690689
expect(state, expected);
691690
});
692691
});
692+
693+
group('$BrowserPlatformLocation', () {
694+
test('getOrCreateDomEventListener caches funcions', () {
695+
const BrowserPlatformLocation location = BrowserPlatformLocation();
696+
void myListener(Object event) {}
697+
698+
expect(
699+
identical(
700+
location.getOrCreateDomEventListener(myListener),
701+
location.getOrCreateDomEventListener(myListener),
702+
),
703+
isTrue,
704+
);
705+
});
706+
707+
test('throws if removing an invalid listener', () {
708+
const BrowserPlatformLocation location = BrowserPlatformLocation();
709+
void myAddedListener(Object event) {}
710+
void myNonAddedListener(Object event) {}
711+
712+
location.addPopStateListener(myAddedListener);
713+
expect(() => location.removePopStateListener(myAddedListener), returnsNormally);
714+
// Removing the same listener twice should throw.
715+
expect(() => location.removePopStateListener(myAddedListener), throwsAssertionError);
716+
717+
// A listener that was never added.
718+
expect(() => location.removePopStateListener(myNonAddedListener), throwsAssertionError);
719+
});
720+
});
693721
}
694722

695723
Future<void> routeUpdated(String routeName) {
@@ -736,7 +764,7 @@ class TestPlatformLocation implements PlatformLocation {
736764
@override
737765
dynamic state;
738766

739-
List<DomEventListener> popStateListeners = <DomEventListener>[];
767+
List<EventListener> popStateListeners = <EventListener>[];
740768

741769
@override
742770
String pathname = '';
@@ -753,19 +781,18 @@ class TestPlatformLocation implements PlatformLocation {
753781
if (state != null) 'state': state,
754782
},
755783
);
756-
for (final DomEventListener listener in popStateListeners) {
757-
final Function fn = (listener as JSExportedDartFunction).toDart;
758-
fn(event);
784+
for (final EventListener listener in popStateListeners) {
785+
listener(event);
759786
}
760787
}
761788

762789
@override
763-
void addPopStateListener(DomEventListener fn) {
790+
void addPopStateListener(EventListener fn) {
764791
popStateListeners.add(fn);
765792
}
766793

767794
@override
768-
void removePopStateListener(DomEventListener fn) {
795+
void removePopStateListener(EventListener fn) {
769796
throw UnimplementedError();
770797
}
771798

@@ -780,7 +807,7 @@ class TestPlatformLocation implements PlatformLocation {
780807
}
781808

782809
@override
783-
void go(double count) {
810+
void go(int count) {
784811
throw UnimplementedError();
785812
}
786813

0 commit comments

Comments
 (0)