diff --git a/lib/web_ui/lib/src/engine/browser_location.dart b/lib/web_ui/lib/src/engine/browser_location.dart index 30bcfcdf64c37..b5259c0bfe2cd 100644 --- a/lib/web_ui/lib/src/engine/browser_location.dart +++ b/lib/web_ui/lib/src/engine/browser_location.dart @@ -9,6 +9,10 @@ part of engine; // Some parts of this file were inspired/copied from the AngularDart router. +/// Enable the path based browser location handling. +const bool usePathStrategy = + bool.fromEnvironment('flutter.web.usePathStrategy', defaultValue: false); + /// [LocationStrategy] is responsible for representing and reading route state /// from the browser's URL. /// @@ -44,17 +48,7 @@ abstract class LocationStrategy { /// to represent its state. /// /// In order to use this [LocationStrategy] for an app, it needs to be set in -/// [ui.window.locationStrategy]: -/// -/// ```dart -/// import 'package:flutter_web/material.dart'; -/// import 'package:flutter_web/ui.dart' as ui; -/// -/// void main() { -/// ui.window.locationStrategy = const ui.HashLocationStrategy(); -/// runApp(MyApp()); -/// } -/// ``` +/// [engine.window.locationStrategy]: class HashLocationStrategy extends LocationStrategy { final PlatformLocation _platformLocation; @@ -121,6 +115,63 @@ class HashLocationStrategy extends LocationStrategy { } } +/// This is an implementation of [LocationStrategy] that uses the browser URL's +/// [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) +/// to represent its state. +/// +/// In order to use this [LocationStrategy] for an app, it needs to be set in +/// [engine.window.locationStrategy.locationStrategy]: +class PathLocationStrategy extends LocationStrategy { + final PlatformLocation _platformLocation; + + const PathLocationStrategy( + [this._platformLocation = const BrowserPlatformLocation()]); + + @override + ui.VoidCallback onPopState(html.EventListener fn) { + _platformLocation.onPopState(fn); + return () => _platformLocation.offPopState(fn); + } + + @override + String get path => '${_platformLocation.pathname}${_platformLocation.search}'; + + @override + String prepareExternalUrl(String internalUrl) { + return internalUrl.isEmpty ? path : '$internalUrl'; + } + + @override + void pushState(dynamic state, String title, String url) { + _platformLocation.pushState(state, title, prepareExternalUrl(url)); + } + + @override + void replaceState(dynamic state, String title, String url) { + _platformLocation.replaceState(state, title, prepareExternalUrl(url)); + } + + @override + Future back() { + _platformLocation.back(); + return _waitForPopState(); + } + + /// Waits until the next popstate event is fired. + /// + /// This is useful for example to wait until the browser has handled the + /// `history.back` transition. + Future _waitForPopState() { + final Completer completer = Completer(); + ui.VoidCallback unsubscribe; + unsubscribe = onPopState((_) { + unsubscribe(); + completer.complete(); + }); + return completer.future; + } +} + /// [PlatformLocation] encapsulates all calls to DOM apis, which allows the /// [LocationStrategy] classes to be platform agnostic and testable. /// diff --git a/lib/web_ui/lib/src/ui/initialization.dart b/lib/web_ui/lib/src/ui/initialization.dart index b1456489bb793..d0e906b103c76 100644 --- a/lib/web_ui/lib/src/ui/initialization.dart +++ b/lib/web_ui/lib/src/ui/initialization.dart @@ -10,7 +10,11 @@ Future webOnlyInitializePlatform({ engine.AssetManager assetManager, }) async { if (!debugEmulateFlutterTesterEnvironment) { - engine.window.locationStrategy = const engine.HashLocationStrategy(); + if (engine.usePathStrategy) { + engine.window.locationStrategy = const engine.PathLocationStrategy(); + } else { + engine.window.locationStrategy = const engine.HashLocationStrategy(); + } } engine.webOnlyInitializeEngine();