diff --git a/packages/flutter_web_plugins/lib/flutter_web_plugins.dart b/packages/flutter_web_plugins/lib/flutter_web_plugins.dart index dda02e2404b..d9c5f208ce8 100644 --- a/packages/flutter_web_plugins/lib/flutter_web_plugins.dart +++ b/packages/flutter_web_plugins/lib/flutter_web_plugins.dart @@ -15,9 +15,8 @@ /// describing how the `url_launcher` package was created using [flutter_web_plugins]. library flutter_web_plugins; -export 'src/navigation/js_url_strategy.dart'; export 'src/navigation/url_strategy.dart'; export 'src/navigation/utils.dart'; -export 'src/navigation_common/url_strategy.dart'; +export 'src/navigation_common/platform_location.dart'; export 'src/plugin_event_channel.dart'; export 'src/plugin_registry.dart'; diff --git a/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart b/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart deleted file mode 100644 index 5e68c310e8b..00000000000 --- a/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// TODO(goderbauer): Remove this ignore when the documentation for the -// now private, then public typedefs is clear. -// ignore_for_file: library_private_types_in_public_api - -@JS() -library js_location_strategy; - -import 'dart:async'; -import 'dart:html' as html; -import 'dart:ui' as ui; - -import 'package:js/js.dart'; -import 'package:meta/meta.dart'; - -import '../navigation_common/url_strategy.dart'; - -typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); - -/// A JavaScript hook to customize the URL strategy of a Flutter app. -// -// Keep this in sync with the JS name in the web engine. Find it at: -// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart -// -// TODO(mdebbar): Add integration test https://github.com/flutter/flutter/issues/66852 -@JS('_flutter_web_set_location_strategy') -external _JsSetUrlStrategy get jsSetUrlStrategy; - -typedef _PathGetter = String Function(); - -typedef _StateGetter = Object? Function(); - -typedef _AddPopStateListener = ui.VoidCallback Function(EventListener); - -typedef _StringToString = String Function(String); - -typedef _StateOperation = void Function(Object state, String title, String url); - -typedef _HistoryMove = Future Function(int count); - -/// Given a Dart implementation of URL strategy, converts it to a JavaScript -/// URL strategy to be passed through JS interop. -JsUrlStrategy convertToJsUrlStrategy(UrlStrategy strategy) { - return JsUrlStrategy( - getPath: allowInterop(strategy.getPath), - getState: allowInterop(strategy.getState), - addPopStateListener: allowInterop(strategy.addPopStateListener), - prepareExternalUrl: allowInterop(strategy.prepareExternalUrl), - pushState: allowInterop(strategy.pushState), - replaceState: allowInterop(strategy.replaceState), - go: allowInterop(strategy.go), - ); -} - -/// The JavaScript representation of a URL strategy. -/// -/// This is used to pass URL strategy implementations across a JS-interop -/// bridge from the app to the engine. -@JS() -@anonymous -abstract class JsUrlStrategy { - /// Creates an instance of [JsUrlStrategy] from a bag of URL strategy - /// functions. - external factory JsUrlStrategy({ - @required _PathGetter getPath, - @required _StateGetter getState, - @required _AddPopStateListener addPopStateListener, - @required _StringToString prepareExternalUrl, - @required _StateOperation pushState, - @required _StateOperation replaceState, - @required _HistoryMove go, - }); - - /// Adds a listener to the `popstate` event and returns a function that - /// removes the listener. - external ui.VoidCallback addPopStateListener(html.EventListener fn); - - /// Returns the active path in the browser. - external String getPath(); - - /// Returns the history state in the browser. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state - external Object getState(); - - /// Given a path that's internal to the app, create the external url that - /// will be used in the browser. - external String prepareExternalUrl(String internalUrl); - - /// Push a new history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState - external void pushState(Object? state, String title, String url); - - /// Replace the currently active history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState - external void replaceState(Object? state, String title, String url); - - /// Moves forwards or backwards through the history stack. - /// - /// A negative [count] value causes a backward move in the history stack. And - /// a positive [count] value causes a forward move. - /// - /// Examples: - /// - /// * `go(-2)` moves back 2 steps in history. - /// * `go(3)` moves forward 3 steps in history. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go - external Future go(int count); -} diff --git a/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart b/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart index 0ef349d7098..8647627574c 100644 --- a/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart @@ -5,11 +5,13 @@ import 'dart:async'; import 'dart:html' as html; import 'dart:ui' as ui; +import 'dart:ui_web' as ui_web; -import '../navigation_common/url_strategy.dart'; -import 'js_url_strategy.dart'; +import '../navigation_common/platform_location.dart'; import 'utils.dart'; +export 'dart:ui_web' show UrlStrategy; + /// Saves the current [UrlStrategy] to be accessed by [urlStrategy] or /// [setUrlStrategy]. /// @@ -20,25 +22,20 @@ import 'utils.dart'; // Find it at: // https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/window.dart#L360 // -UrlStrategy? _urlStrategy = const HashUrlStrategy(); +ui_web.UrlStrategy? _urlStrategy = const HashUrlStrategy(); /// Returns the present [UrlStrategy] for handling the browser URL. /// /// In case null is returned, the browser integration has been manually /// disabled by [setUrlStrategy]. -UrlStrategy? get urlStrategy => _urlStrategy; +ui_web.UrlStrategy? get urlStrategy => _urlStrategy; /// Change the strategy to use for handling browser URL. /// /// Setting this to null disables all integration with the browser history. -void setUrlStrategy(UrlStrategy? strategy) { +void setUrlStrategy(ui_web.UrlStrategy? strategy) { _urlStrategy = strategy; - - JsUrlStrategy? jsUrlStrategy; - if (strategy != null) { - jsUrlStrategy = convertToJsUrlStrategy(strategy); - } - jsSetUrlStrategy(jsUrlStrategy); + ui_web.urlStrategy = strategy; } /// Use the [PathUrlStrategy] to handle the browser URL. @@ -60,7 +57,7 @@ void usePathUrlStrategy() { /// // Somewhere before calling `runApp()` do: /// setUrlStrategy(const HashUrlStrategy()); /// ``` -class HashUrlStrategy extends UrlStrategy { +class HashUrlStrategy extends ui_web.UrlStrategy { /// Creates an instance of [HashUrlStrategy]. /// /// The [PlatformLocation] parameter is useful for testing to mock out browser @@ -71,9 +68,13 @@ class HashUrlStrategy extends UrlStrategy { final PlatformLocation _platformLocation; @override - ui.VoidCallback addPopStateListener(EventListener fn) { - _platformLocation.addPopStateListener(fn); - return () => _platformLocation.removePopStateListener(fn); + ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) { + void wrappedFn(Object event) { + // `fn` expects `event.state`, not a `html.Event`. + fn((event as html.PopStateEvent).state); + } + _platformLocation.addPopStateListener(wrappedFn); + return () => _platformLocation.removePopStateListener(wrappedFn); } @override diff --git a/packages/flutter_web_plugins/lib/src/navigation_common/url_strategy.dart b/packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart similarity index 59% rename from packages/flutter_web_plugins/lib/src/navigation_common/url_strategy.dart rename to packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart index 6e5364ab0df..3317a706c82 100644 --- a/packages/flutter_web_plugins/lib/src/navigation_common/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart @@ -2,63 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; -import 'dart:ui' as ui; - -/// Signature of an html event listener. -/// -/// We have to redefine it because non-web platforms can't import dart:html. +/// Function type that handles pop state events. typedef EventListener = dynamic Function(Object event); -/// Represents and reads route state from the browser's URL. -/// -/// By default, the [HashUrlStrategy] subclass is used if the app doesn't -/// specify one. -abstract class UrlStrategy { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const UrlStrategy(); - - /// Adds a listener to the `popstate` event and returns a function that, when - /// invoked, removes the listener. - ui.VoidCallback addPopStateListener(EventListener fn); - - /// Returns the active path in the browser. - String getPath(); - - /// The state of the current browser history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state - Object? getState(); - - /// Given a path that's internal to the app, create the external url that - /// will be used in the browser. - String prepareExternalUrl(String internalUrl); - - /// Push a new history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState - void pushState(Object? state, String title, String url); - - /// Replace the currently active history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState - void replaceState(Object? state, String title, String url); - - /// Moves forwards or backwards through the history stack. - /// - /// A negative [count] value causes a backward move in the history stack. And - /// a positive [count] value causes a forward move. - /// - /// Examples: - /// - /// * `go(-2)` moves back 2 steps in history. - /// * `go(3)` moves forward 3 steps in history. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go - Future go(int count); -} - /// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes /// to be platform agnostic and testable. /// diff --git a/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart b/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart index d411dec9af2..149299712c8 100644 --- a/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart @@ -5,7 +5,68 @@ import 'dart:async'; import 'dart:ui' as ui; -import '../navigation_common/url_strategy.dart'; +import '../navigation_common/platform_location.dart'; + +/// Callback that receives the new state of the browser history entry. +typedef PopStateListener = void Function(Object? state); + +/// Represents and reads route state from the browser's URL. +/// +/// By default, the [HashUrlStrategy] subclass is used if the app doesn't +/// specify one. +abstract class UrlStrategy { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const UrlStrategy(); + + /// Adds a listener to the `popstate` event and returns a function that, when + /// invoked, removes the listener. + ui.VoidCallback addPopStateListener(PopStateListener fn) { + // No-op. + return () {}; + } + + /// Returns the active path in the browser. + String getPath() => ''; + + /// The state of the current browser history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state + Object? getState() => null; + + /// Given a path that's internal to the app, create the external url that + /// will be used in the browser. + String prepareExternalUrl(String internalUrl) => ''; + + /// Push a new history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState + void pushState(Object? state, String title, String url) { + // No-op. + } + + /// Replace the currently active history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState + void replaceState(Object? state, String title, String url) { + // No-op. + } + + /// Moves forwards or backwards through the history stack. + /// + /// A negative [count] value causes a backward move in the history stack. And + /// a positive [count] value causes a forward move. + /// + /// Examples: + /// + /// * `go(-2)` moves back 2 steps in history. + /// * `go(3)` moves forward 3 steps in history. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go + Future go(int count) async { + // No-op. + } +} /// Returns the present [UrlStrategy] for handling the browser URL. /// @@ -45,36 +106,6 @@ class HashUrlStrategy extends UrlStrategy { /// The [PlatformLocation] parameter is useful for testing to mock out browser /// integrations. const HashUrlStrategy([PlatformLocation? _]); - - @override - ui.VoidCallback addPopStateListener(EventListener fn) { - // No-op. - return () {}; - } - - @override - String getPath() => ''; - - @override - Object? getState() => null; - - @override - String prepareExternalUrl(String internalUrl) => ''; - - @override - void pushState(Object? state, String title, String url) { - // No-op. - } - - @override - void replaceState(Object? state, String title, String url) { - // No-op. - } - - @override - Future go(int count) async { - // No-op. - } } /// Uses the browser URL's pathname to represent Flutter's route name. @@ -92,11 +123,5 @@ class PathUrlStrategy extends HashUrlStrategy { /// /// The [PlatformLocation] parameter is useful for testing to mock out browser /// integrations. - PathUrlStrategy([super.platformLocation]); - - @override - String getPath() => ''; - - @override - String prepareExternalUrl(String internalUrl) => ''; + PathUrlStrategy([PlatformLocation? _]); } diff --git a/packages/flutter_web_plugins/lib/url_strategy.dart b/packages/flutter_web_plugins/lib/url_strategy.dart index c40b9a2bafc..0ebf7b4a77d 100644 --- a/packages/flutter_web_plugins/lib/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/url_strategy.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'src/navigation_common/url_strategy.dart'; +export 'src/navigation_common/platform_location.dart'; export 'src/navigation_non_web/url_strategy.dart' - if (dart.library.html) 'src/navigation/url_strategy.dart'; + if (dart.library.ui_web) 'src/navigation/url_strategy.dart'; diff --git a/packages/flutter_web_plugins/test/navigation/common.dart b/packages/flutter_web_plugins/test/navigation/common.dart new file mode 100644 index 00000000000..0f65f42f2df --- /dev/null +++ b/packages/flutter_web_plugins/test/navigation/common.dart @@ -0,0 +1,47 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_web_plugins/url_strategy.dart'; + +/// A mock implementation of [PlatformLocation] that doesn't access the browser. +class TestPlatformLocation extends PlatformLocation { + @override + String pathname = ''; + + @override + String search = ''; + + @override + String hash = ''; + + @override + Object? get state => null; + + /// Mocks the base href of the document. + String baseHref = ''; + + @override + void addPopStateListener(EventListener fn) { + throw UnimplementedError(); + } + + @override + void removePopStateListener(EventListener fn) { + throw UnimplementedError(); + } + + @override + void pushState(Object? state, String title, String url) {} + + @override + void replaceState(Object? state, String title, String url) {} + + @override + void go(int count) { + throw UnimplementedError(); + } + + @override + String getBaseHref() => baseHref; +} diff --git a/packages/flutter_web_plugins/test/navigation/non_web_url_strategy_test.dart b/packages/flutter_web_plugins/test/navigation/non_web_url_strategy_test.dart new file mode 100644 index 00000000000..6f16105c044 --- /dev/null +++ b/packages/flutter_web_plugins/test/navigation/non_web_url_strategy_test.dart @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('!chrome') +library; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_web_plugins/url_strategy.dart'; + +import 'common.dart'; + +void main() { + group('Non-web UrlStrategy', () { + late TestPlatformLocation location; + + setUp(() { + location = TestPlatformLocation(); + }); + + test('Can create and set a $HashUrlStrategy', () { + expect(() { + final HashUrlStrategy strategy = HashUrlStrategy(location); + setUrlStrategy(strategy); + }, returnsNormally); + }); + + test('Can create and set a $PathUrlStrategy', () { + expect(() { + final PathUrlStrategy strategy = PathUrlStrategy(location); + setUrlStrategy(strategy); + }, returnsNormally); + }); + + test('Can usePathUrlStrategy', () { + expect(() { + usePathUrlStrategy(); + }, returnsNormally); + }); + }); +} diff --git a/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart b/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart index 20ea42fbc07..a797051b288 100644 --- a/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart +++ b/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart @@ -6,7 +6,9 @@ library; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:flutter_web_plugins/url_strategy.dart'; + +import 'common.dart'; void main() { group('$HashUrlStrategy', () { @@ -142,45 +144,3 @@ void main() { }); }); } - -/// A mock implementation of [PlatformLocation] that doesn't access the browser. -class TestPlatformLocation extends PlatformLocation { - @override - String pathname = ''; - - @override - String search = ''; - - @override - String hash = ''; - - @override - Object? get state => null; - - /// Mocks the base href of the document. - String baseHref = ''; - - @override - void addPopStateListener(EventListener fn) { - throw UnimplementedError(); - } - - @override - void removePopStateListener(EventListener fn) { - throw UnimplementedError(); - } - - @override - void pushState(Object? state, String title, String url) {} - - @override - void replaceState(Object? state, String title, String url) {} - - @override - void go(int count) { - throw UnimplementedError(); - } - - @override - String getBaseHref() => baseHref; -}