diff --git a/lib/web_ui/lib/src/engine/history.dart b/lib/web_ui/lib/src/engine/history.dart index ad7558f7ecfc5..7a30d62d8f345 100644 --- a/lib/web_ui/lib/src/engine/history.dart +++ b/lib/web_ui/lib/src/engine/history.dart @@ -53,6 +53,10 @@ class BrowserHistory { } } + /// Returns the currently active location strategy. + @visibleForTesting + LocationStrategy get locationStrategy => _locationStrategy; + /// The path of the current location of the user's browser. String get currentPath => _locationStrategy?.path ?? '/'; diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index efa7ea83d0cc7..0e65080fa00a4 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -17,6 +17,12 @@ ui.VoidCallback scheduleFrameCallback; class EngineWindow extends ui.Window { EngineWindow() { _addBrightnessMediaQueryListener(); + js.context['_flutter_web_set_location_strategy'] = (LocationStrategy strategy) { + locationStrategy = strategy; + }; + registerHotRestartListener(() { + js.context['_flutter_web_set_location_strategy'] = null; + }); } @override @@ -178,6 +184,10 @@ class EngineWindow extends ui.Window { _browserHistory.locationStrategy = strategy; } + /// Returns the currently active location strategy. + @visibleForTesting + LocationStrategy get locationStrategy => _browserHistory.locationStrategy; + @override ui.VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged; ui.VoidCallback _onTextScaleFactorChanged; diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index 4062be970c3f1..8924a06798d5f 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -3,6 +3,9 @@ // found in the LICENSE file. // @dart = 2.6 +import 'dart:async'; +import 'dart:html' as html; +import 'dart:js_util' as js_util; import 'dart:typed_data'; import 'package:test/test.dart'; @@ -12,24 +15,18 @@ const MethodCodec codec = JSONMethodCodec(); void emptyCallback(ByteData date) {} -TestLocationStrategy _strategy; -TestLocationStrategy get strategy => _strategy; -set strategy(TestLocationStrategy newStrategy) { - window.locationStrategy = _strategy = newStrategy; -} - void main() { test('window.defaultRouteName should not change', () { - strategy = TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial')); + window.locationStrategy = TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial')); expect(window.defaultRouteName, '/initial'); // Changing the URL in the address bar later shouldn't affect [window.defaultRouteName]. - strategy.replaceState(null, null, '/newpath'); + window.locationStrategy.replaceState(null, null, '/newpath'); expect(window.defaultRouteName, '/initial'); }); test('window.defaultRouteName should reset after navigation platform message', () { - strategy = TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial')); + window.locationStrategy = TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial')); // Reading it multiple times should return the same value. expect(window.defaultRouteName, '/initial'); expect(window.defaultRouteName, '/initial'); @@ -46,4 +43,47 @@ void main() { // reset to "/". expect(window.defaultRouteName, '/'); }); + + test('can disable location strategy', () async { + final testStrategy = TestLocationStrategy.fromEntry( + TestHistoryEntry(null, null, '/'), + ); + window.locationStrategy = testStrategy; + + expect(window.locationStrategy, testStrategy); + // A single listener should've been setup. + expect(testStrategy.listeners, hasLength(1)); + // The initial entry should be there, plus another "flutter" entry. + expect(testStrategy.history, hasLength(2)); + expect(testStrategy.history[0].state, {'origin': true}); + expect(testStrategy.history[1].state, {'flutter': true}); + expect(testStrategy.currentEntry, testStrategy.history[1]); + + // Now, let's disable location strategy and make sure things get cleaned up. + expect(() => jsSetLocationStrategy(null), returnsNormally); + expect(window.locationStrategy, isNull); + + // The listener is removed asynchronously. + await Future.delayed(const Duration(milliseconds: 10)); + + // No more listeners. + expect(testStrategy.listeners, isEmpty); + // History should've moved back to the initial entry. + expect(testStrategy.history[0].state, {'origin': true}); + expect(testStrategy.currentEntry, testStrategy.history[0]); + }); + + test('js interop throws on wrong type', () { + expect(() => jsSetLocationStrategy(123), throwsA(anything)); + expect(() => jsSetLocationStrategy('foo'), throwsA(anything)); + expect(() => jsSetLocationStrategy(false), throwsA(anything)); + }); +} + +void jsSetLocationStrategy(dynamic strategy) { + js_util.callMethod( + html.window, + '_flutter_web_set_location_strategy', + [strategy], + ); }