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

Commit 1585d68

Browse files
committed
[web] Support custom location strategies
1 parent 87eff25 commit 1585d68

11 files changed

+305
-174
lines changed

lib/web_ui/lib/src/engine.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ part 'engine/alarm_clock.dart';
2626
part 'engine/assets.dart';
2727
part 'engine/bitmap_canvas.dart';
2828
part 'engine/browser_detection.dart';
29-
part 'engine/browser_location.dart';
3029
part 'engine/canvaskit/canvas.dart';
3130
part 'engine/canvaskit/canvaskit_canvas.dart';
3231
part 'engine/canvaskit/canvaskit_api.dart';
@@ -63,7 +62,9 @@ part 'engine/dom_canvas.dart';
6362
part 'engine/dom_renderer.dart';
6463
part 'engine/engine_canvas.dart';
6564
part 'engine/frame_reference.dart';
66-
part 'engine/history.dart';
65+
part 'engine/history/history.dart';
66+
part 'engine/history/js_url_strategy.dart';
67+
part 'engine/history/url_strategy.dart';
6768
part 'engine/html/backdrop_filter.dart';
6869
part 'engine/html/canvas.dart';
6970
part 'engine/html/clip.dart';

lib/web_ui/lib/src/engine/history.dart renamed to lib/web_ui/lib/src/engine/history/history.dart

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,29 @@ abstract class BrowserHistory {
2525
late ui.VoidCallback _unsubscribe;
2626

2727
/// The strategy to interact with html browser history.
28-
LocationStrategy? get locationStrategy => _locationStrategy;
29-
LocationStrategy? _locationStrategy;
28+
JsUrlStrategy? get urlStrategy => _urlStrategy;
29+
JsUrlStrategy? _urlStrategy;
3030
/// Updates the strategy.
3131
///
3232
/// This method will also remove any previous modifications to the html
3333
/// browser history and start anew.
34-
Future<void> setLocationStrategy(LocationStrategy? strategy) async {
35-
if (strategy != _locationStrategy) {
36-
await _tearoffStrategy(_locationStrategy);
37-
_locationStrategy = strategy;
38-
await _setupStrategy(_locationStrategy);
34+
Future<void> setUrlStrategy(JsUrlStrategy? strategy) async {
35+
if (strategy != _urlStrategy) {
36+
await _tearoffStrategy(_urlStrategy);
37+
_urlStrategy = strategy;
38+
await _setupStrategy(_urlStrategy);
3939
}
4040
}
4141

42-
Future<void> _setupStrategy(LocationStrategy? strategy) async {
42+
Future<void> _setupStrategy(JsUrlStrategy? strategy) async {
4343
if (strategy == null) {
4444
return;
4545
}
4646
_unsubscribe = strategy.onPopState(onPopState as dynamic Function(html.Event));
4747
await setup();
4848
}
4949

50-
Future<void> _tearoffStrategy(LocationStrategy? strategy) async {
50+
Future<void> _tearoffStrategy(JsUrlStrategy? strategy) async {
5151
if (strategy == null) {
5252
return;
5353
}
@@ -58,28 +58,28 @@ abstract class BrowserHistory {
5858

5959
/// Exit this application and return to the previous page.
6060
Future<void> exit() async {
61-
if (_locationStrategy != null) {
62-
await _tearoffStrategy(_locationStrategy);
61+
if (_urlStrategy != null) {
62+
await _tearoffStrategy(_urlStrategy);
6363
// Now the history should be in the original state, back one more time to
6464
// exit the application.
65-
await _locationStrategy!.back();
66-
_locationStrategy = null;
65+
await _urlStrategy!.go(-1);
66+
_urlStrategy = null;
6767
}
6868
}
6969

7070
/// This method does the same thing as the browser back button.
7171
Future<void> back() {
72-
if (locationStrategy != null) {
73-
return locationStrategy!.back();
72+
if (_urlStrategy != null) {
73+
return _urlStrategy!.go(-1);
7474
}
7575
return Future<void>.value();
7676
}
7777

7878
/// The path of the current location of the user's browser.
79-
String get currentPath => locationStrategy?.path ?? '/';
79+
String get currentPath => urlStrategy?.getPath() ?? '/';
8080

8181
/// The state of the current location of the user's browser.
82-
dynamic get currentState => locationStrategy?.state;
82+
dynamic get currentState => urlStrategy?.getState();
8383

8484
/// Update the url with the given [routeName] and [state].
8585
void setRouteName(String? routeName, {dynamic? state});
@@ -137,10 +137,10 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
137137

138138
@override
139139
void setRouteName(String? routeName, {dynamic? state}) {
140-
if (locationStrategy != null) {
140+
if (urlStrategy != null) {
141141
assert(routeName != null);
142142
_lastSeenSerialCount += 1;
143-
locationStrategy!.pushState(
143+
urlStrategy!.pushState(
144144
_tagWithSerialCount(state, _lastSeenSerialCount),
145145
'flutter',
146146
routeName!,
@@ -150,13 +150,13 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
150150

151151
@override
152152
void onPopState(covariant html.PopStateEvent event) {
153-
assert(locationStrategy != null);
153+
assert(urlStrategy != null);
154154
// May be a result of direct url access while the flutter application is
155155
// already running.
156156
if (!_hasSerialCount(event.state)) {
157157
// In this case we assume this will be the next history entry from the
158158
// last seen entry.
159-
locationStrategy!.replaceState(
159+
urlStrategy!.replaceState(
160160
_tagWithSerialCount(event.state, _lastSeenSerialCount + 1),
161161
'flutter',
162162
currentPath);
@@ -179,7 +179,7 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
179179
@override
180180
Future<void> setup() {
181181
if (!_hasSerialCount(currentState)) {
182-
locationStrategy!.replaceState(
182+
urlStrategy!.replaceState(
183183
_tagWithSerialCount(currentState, 0),
184184
'flutter',
185185
currentPath
@@ -196,11 +196,11 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
196196
assert(_hasSerialCount(currentState));
197197
int backCount = _currentSerialCount;
198198
if (backCount > 0) {
199-
await locationStrategy!.back(count: backCount);
199+
await urlStrategy!.go(-backCount);
200200
}
201201
// Unwrap state.
202202
assert(_hasSerialCount(currentState) && _currentSerialCount == 0);
203-
locationStrategy!.replaceState(
203+
urlStrategy!.replaceState(
204204
currentState['state'],
205205
'flutter',
206206
currentPath,
@@ -254,16 +254,16 @@ class SingleEntryBrowserHistory extends BrowserHistory {
254254

255255
@override
256256
void setRouteName(String? routeName, {dynamic? state}) {
257-
if (locationStrategy != null) {
258-
_setupFlutterEntry(locationStrategy!, replace: true, path: routeName);
257+
if (urlStrategy != null) {
258+
_setupFlutterEntry(urlStrategy!, replace: true, path: routeName);
259259
}
260260
}
261261

262262
String? _userProvidedRouteName;
263263
@override
264264
void onPopState(covariant html.PopStateEvent event) {
265265
if (_isOriginEntry(event.state)) {
266-
_setupFlutterEntry(_locationStrategy!);
266+
_setupFlutterEntry(_urlStrategy!);
267267

268268
// 2. Send a 'popRoute' platform message so the app can handle it accordingly.
269269
if (window._onPlatformMessage != null) {
@@ -305,22 +305,22 @@ class SingleEntryBrowserHistory extends BrowserHistory {
305305
// 2. Then we remove the new entry.
306306
// This will take us back to our "flutter" entry and it causes a new
307307
// popstate event that will be handled in the "else if" section above.
308-
_locationStrategy!.back();
308+
_urlStrategy!.go(-1);
309309
}
310310
}
311311

312312
/// This method should be called when the Origin Entry is active. It just
313313
/// replaces the state of the entry so that we can recognize it later using
314314
/// [_isOriginEntry] inside [_popStateListener].
315-
void _setupOriginEntry(LocationStrategy strategy) {
315+
void _setupOriginEntry(JsUrlStrategy strategy) {
316316
assert(strategy != null); // ignore: unnecessary_null_comparison
317317
strategy.replaceState(_wrapOriginState(currentState), 'origin', '');
318318
}
319319

320320
/// This method is used manipulate the Flutter Entry which is always the
321321
/// active entry while the Flutter app is running.
322322
void _setupFlutterEntry(
323-
LocationStrategy strategy, {
323+
JsUrlStrategy strategy, {
324324
bool replace = false,
325325
String? path,
326326
}) {
@@ -342,19 +342,19 @@ class SingleEntryBrowserHistory extends BrowserHistory {
342342
// the "origin" and "flutter" entries, we can safely assume they are
343343
// already setup.
344344
} else {
345-
_setupOriginEntry(locationStrategy!);
346-
_setupFlutterEntry(locationStrategy!, replace: false, path: path);
345+
_setupOriginEntry(urlStrategy!);
346+
_setupFlutterEntry(urlStrategy!, replace: false, path: path);
347347
}
348348
return Future<void>.value();
349349
}
350350

351351
@override
352352
Future<void> tearDown() async {
353-
if (locationStrategy != null) {
353+
if (urlStrategy != null) {
354354
// We need to remove the flutter entry that we pushed in setup.
355-
await locationStrategy!.back();
355+
await urlStrategy!.go(-1);
356356
// Restores original state.
357-
locationStrategy!.replaceState(_unwrapOriginState(currentState), 'flutter', currentPath);
357+
urlStrategy!.replaceState(_unwrapOriginState(currentState), 'flutter', currentPath);
358358
}
359359
}
360360
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.10
6+
part of engine;
7+
8+
typedef _PathGetter = String Function();
9+
10+
typedef _StateGetter = dynamic Function();
11+
12+
typedef _OnPopState = ui.VoidCallback Function(html.EventListener);
13+
14+
typedef _StringToString = String Function(String);
15+
16+
typedef _StateOperation = void Function(
17+
dynamic state, String title, String url);
18+
19+
typedef _HistoryMove = Future<void> Function(int count);
20+
21+
/// Given a Dart implementation of URL strategy, it converts it to a JavaScript
22+
/// URL strategy that be passed through JS interop.
23+
JsUrlStrategy? convertToJsUrlStrategy(UrlStrategy? strategy) {
24+
if (strategy == null) {
25+
return null;
26+
}
27+
28+
return JsUrlStrategy(
29+
getPath: allowInterop(strategy.getPath),
30+
getState: allowInterop(strategy.getState),
31+
onPopState: allowInterop(strategy.onPopState),
32+
prepareExternalUrl: allowInterop(strategy.prepareExternalUrl),
33+
pushState: allowInterop(strategy.pushState),
34+
replaceState: allowInterop(strategy.replaceState),
35+
go: allowInterop(strategy.go),
36+
// getBaseHref: allowInterop(strategy.getBaseHref),
37+
);
38+
}
39+
40+
/// The JavaScript representation of a URL strategy.
41+
///
42+
/// This is used to pass URL strategy implementations across a JS-interop
43+
/// bridge.
44+
@JS()
45+
@anonymous
46+
abstract class JsUrlStrategy {
47+
/// Creates an instance of [JsUrlStrategy] from a bag of URL strategy
48+
/// functions.
49+
external factory JsUrlStrategy({
50+
@required _PathGetter getPath,
51+
@required _StateGetter getState,
52+
@required _OnPopState onPopState,
53+
@required _StringToString prepareExternalUrl,
54+
@required _StateOperation pushState,
55+
@required _StateOperation replaceState,
56+
@required _HistoryMove go,
57+
});
58+
59+
/// Subscribes to popstate events and returns a function that could be used to
60+
/// unsubscribe from popstate events.
61+
external ui.VoidCallback onPopState(html.EventListener fn);
62+
63+
/// Returns the active path in the browser.
64+
external String getPath();
65+
66+
/// Returns the history state in the browser.
67+
external dynamic getState();
68+
69+
/// Given a path that's internal to the app, create the external url that
70+
/// will be used in the browser.
71+
external String prepareExternalUrl(String internalUrl);
72+
73+
/// Push a new history entry.
74+
external void pushState(dynamic state, String title, String url);
75+
76+
/// Replace the currently active history entry.
77+
external void replaceState(dynamic state, String title, String url);
78+
79+
/// Moves forwards or backwards through the history stack.
80+
external Future<void> go(int count);
81+
82+
// TODO: add this:
83+
// external String getBaseHref();
84+
}

0 commit comments

Comments
 (0)