forked from firka/flutter
[web] Use PlatformLocation from ui_web (#126851)
Remove the following APIs and export them directly from `dart:ui_web`: - `urlStrategy` getter and setter - `HashUrlStrategy` - `PlatformLocation` and `BrowserPlatformLocation` (keep the façades for non-web platforms) Depends on https://github.com/flutter/engine/pull/42043 Depends on https://github.com/flutter/engine/pull/42252 Part of https://github.com/flutter/flutter/issues/126831
This commit is contained in:
@@ -17,6 +17,5 @@ library flutter_web_plugins;
|
||||
|
||||
export 'src/navigation/url_strategy.dart';
|
||||
export 'src/navigation/utils.dart';
|
||||
export 'src/navigation_common/platform_location.dart';
|
||||
export 'src/plugin_event_channel.dart';
|
||||
export 'src/plugin_registry.dart';
|
||||
|
||||
@@ -2,39 +2,23 @@
|
||||
// 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:html' as html;
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:ui_web' as ui_web;
|
||||
|
||||
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].
|
||||
///
|
||||
/// This is particularly required for web plugins relying on valid URL
|
||||
/// encoding.
|
||||
//
|
||||
// Keep this in sync with the default url strategy in the web engine.
|
||||
// Find it at:
|
||||
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/window.dart#L360
|
||||
//
|
||||
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].
|
||||
ui_web.UrlStrategy? get urlStrategy => _urlStrategy;
|
||||
export 'dart:ui_web'
|
||||
show
|
||||
BrowserPlatformLocation,
|
||||
EventListener,
|
||||
HashUrlStrategy,
|
||||
PlatformLocation,
|
||||
UrlStrategy,
|
||||
urlStrategy;
|
||||
|
||||
/// Change the strategy to use for handling browser URL.
|
||||
///
|
||||
/// Setting this to null disables all integration with the browser history.
|
||||
void setUrlStrategy(ui_web.UrlStrategy? strategy) {
|
||||
_urlStrategy = strategy;
|
||||
ui_web.urlStrategy = strategy;
|
||||
}
|
||||
|
||||
@@ -43,99 +27,6 @@ void usePathUrlStrategy() {
|
||||
setUrlStrategy(PathUrlStrategy());
|
||||
}
|
||||
|
||||
/// Uses the browser URL's [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
|
||||
/// to represent its state.
|
||||
///
|
||||
/// By default, this class is used as the URL strategy for the app. However,
|
||||
/// this class is still useful for apps that want to extend it.
|
||||
///
|
||||
/// In order to use [HashUrlStrategy] for an app, it needs to be set like this:
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
///
|
||||
/// // Somewhere before calling `runApp()` do:
|
||||
/// setUrlStrategy(const HashUrlStrategy());
|
||||
/// ```
|
||||
class HashUrlStrategy extends ui_web.UrlStrategy {
|
||||
/// Creates an instance of [HashUrlStrategy].
|
||||
///
|
||||
/// The [PlatformLocation] parameter is useful for testing to mock out browser
|
||||
/// interactions.
|
||||
const HashUrlStrategy(
|
||||
[this._platformLocation = const BrowserPlatformLocation()]);
|
||||
|
||||
final PlatformLocation _platformLocation;
|
||||
|
||||
@override
|
||||
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
|
||||
String getPath() {
|
||||
// the hash value is always prefixed with a `#`
|
||||
// and if it is empty then it will stay empty
|
||||
final String path = _platformLocation.hash;
|
||||
assert(path.isEmpty || path.startsWith('#'));
|
||||
|
||||
// We don't want to return an empty string as a path. Instead we default to "/".
|
||||
if (path.isEmpty || path == '#') {
|
||||
return '/';
|
||||
}
|
||||
// At this point, we know [path] starts with "#" and isn't empty.
|
||||
return path.substring(1);
|
||||
}
|
||||
|
||||
@override
|
||||
Object? getState() => _platformLocation.state;
|
||||
|
||||
@override
|
||||
String prepareExternalUrl(String internalUrl) {
|
||||
// It's convention that if the hash path is empty, we omit the `#`; however,
|
||||
// if the empty URL is pushed it won't replace any existing fragment. So
|
||||
// when the hash path is empty, we still return the location's path and
|
||||
// query.
|
||||
return '${_platformLocation.pathname}${_platformLocation.search}'
|
||||
'${internalUrl.isEmpty ? '' : '#$internalUrl'}';
|
||||
}
|
||||
|
||||
@override
|
||||
void pushState(Object? state, String title, String url) {
|
||||
_platformLocation.pushState(state, title, prepareExternalUrl(url));
|
||||
}
|
||||
|
||||
@override
|
||||
void replaceState(Object? state, String title, String url) {
|
||||
_platformLocation.replaceState(state, title, prepareExternalUrl(url));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> go(int count) {
|
||||
_platformLocation.go(count);
|
||||
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<void> _waitForPopState() {
|
||||
final Completer<void> completer = Completer<void>();
|
||||
late ui.VoidCallback unsubscribe;
|
||||
unsubscribe = addPopStateListener((_) {
|
||||
unsubscribe();
|
||||
completer.complete();
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses the browser URL's pathname to represent Flutter's route name.
|
||||
///
|
||||
/// In order to use [PathUrlStrategy] for an app, it needs to be set like this:
|
||||
@@ -146,17 +37,19 @@ class HashUrlStrategy extends ui_web.UrlStrategy {
|
||||
/// // Somewhere before calling `runApp()` do:
|
||||
/// setUrlStrategy(PathUrlStrategy());
|
||||
/// ```
|
||||
class PathUrlStrategy extends HashUrlStrategy {
|
||||
class PathUrlStrategy extends ui_web.HashUrlStrategy {
|
||||
/// Creates an instance of [PathUrlStrategy].
|
||||
///
|
||||
/// The [PlatformLocation] parameter is useful for testing to mock out browser
|
||||
/// The [ui_web.PlatformLocation] parameter is useful for testing to mock out browser
|
||||
/// interactions.
|
||||
PathUrlStrategy([
|
||||
super.platformLocation,
|
||||
]) : _basePath = stripTrailingSlash(extractPathname(checkBaseHref(
|
||||
]) : _platformLocation = platformLocation,
|
||||
_basePath = stripTrailingSlash(extractPathname(checkBaseHref(
|
||||
platformLocation.getBaseHref(),
|
||||
)));
|
||||
|
||||
final ui_web.PlatformLocation _platformLocation;
|
||||
final String _basePath;
|
||||
|
||||
@override
|
||||
@@ -176,61 +69,3 @@ class PathUrlStrategy extends HashUrlStrategy {
|
||||
return '$_basePath$internalUrl';
|
||||
}
|
||||
}
|
||||
|
||||
/// Delegates to real browser APIs to provide platform location functionality.
|
||||
class BrowserPlatformLocation extends PlatformLocation {
|
||||
/// Default constructor for [BrowserPlatformLocation].
|
||||
const BrowserPlatformLocation();
|
||||
|
||||
// Default value for [pathname] when it's not set in window.location.
|
||||
// According to MDN this should be ''. Chrome seems to return '/'.
|
||||
static const String _defaultPathname = '';
|
||||
|
||||
// Default value for [search] when it's not set in window.location.
|
||||
// According to both chrome, and the MDN, this is ''.
|
||||
static const String _defaultSearch = '';
|
||||
|
||||
html.Location get _location => html.window.location;
|
||||
|
||||
html.History get _history => html.window.history;
|
||||
|
||||
@override
|
||||
void addPopStateListener(html.EventListener fn) {
|
||||
html.window.addEventListener('popstate', fn);
|
||||
}
|
||||
|
||||
@override
|
||||
void removePopStateListener(html.EventListener fn) {
|
||||
html.window.removeEventListener('popstate', fn);
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathname => _location.pathname ?? _defaultPathname;
|
||||
|
||||
@override
|
||||
String get search => _location.search ?? _defaultSearch;
|
||||
|
||||
@override
|
||||
String get hash => _location.hash;
|
||||
|
||||
@override
|
||||
Object? get state => _history.state;
|
||||
|
||||
@override
|
||||
void pushState(Object? state, String title, String url) {
|
||||
_history.pushState(state, title, url);
|
||||
}
|
||||
|
||||
@override
|
||||
void replaceState(Object? state, String title, String url) {
|
||||
_history.replaceState(state, title, url);
|
||||
}
|
||||
|
||||
@override
|
||||
void go(int count) {
|
||||
_history.go(count);
|
||||
}
|
||||
|
||||
@override
|
||||
String? getBaseHref() => getBaseElementHrefFromDom();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'url_strategy.dart';
|
||||
|
||||
/// Function type that handles pop state events.
|
||||
typedef EventListener = dynamic Function(Object event);
|
||||
|
||||
@@ -10,11 +12,7 @@ typedef EventListener = dynamic Function(Object event);
|
||||
///
|
||||
/// For convenience, the [PlatformLocation] class can be used by implementations
|
||||
/// of [UrlStrategy] to interact with DOM apis like pushState, popState, etc.
|
||||
abstract class PlatformLocation {
|
||||
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||
/// const constructors so that they can be used in const expressions.
|
||||
const PlatformLocation();
|
||||
|
||||
abstract interface class PlatformLocation {
|
||||
/// Registers an event listener for the `popstate` event.
|
||||
///
|
||||
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
|
||||
@@ -74,3 +72,46 @@ abstract class PlatformLocation {
|
||||
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
String? getBaseHref();
|
||||
}
|
||||
|
||||
/// Delegates to real browser APIs to provide platform location functionality.
|
||||
class BrowserPlatformLocation implements PlatformLocation {
|
||||
@override
|
||||
void addPopStateListener(EventListener fn) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
@override
|
||||
void removePopStateListener(EventListener fn) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathname => '';
|
||||
|
||||
@override
|
||||
String get search => '';
|
||||
|
||||
@override
|
||||
String get hash => '';
|
||||
|
||||
@override
|
||||
Object? get state => null;
|
||||
|
||||
@override
|
||||
void pushState(Object? state, String title, String url) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
@override
|
||||
void replaceState(Object? state, String title, String url) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
@override
|
||||
void go(int count) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
@override
|
||||
String? getBaseHref() => null;
|
||||
}
|
||||
@@ -5,7 +5,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import '../navigation_common/platform_location.dart';
|
||||
import 'platform_location.dart';
|
||||
|
||||
export 'platform_location.dart';
|
||||
|
||||
/// Callback that receives the new state of the browser history entry.
|
||||
typedef PopStateListener = void Function(Object? state);
|
||||
@@ -123,5 +125,5 @@ class PathUrlStrategy extends HashUrlStrategy {
|
||||
///
|
||||
/// The [PlatformLocation] parameter is useful for testing to mock out browser
|
||||
/// integrations.
|
||||
PathUrlStrategy([PlatformLocation? _]);
|
||||
const PathUrlStrategy([PlatformLocation? _]);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,5 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
export 'src/navigation_common/platform_location.dart';
|
||||
|
||||
export 'src/navigation_non_web/url_strategy.dart'
|
||||
if (dart.library.ui_web) 'src/navigation/url_strategy.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
||||
|
||||
/// A mock implementation of [PlatformLocation] that doesn't access the browser.
|
||||
class TestPlatformLocation extends PlatformLocation {
|
||||
class TestPlatformLocation implements PlatformLocation {
|
||||
@override
|
||||
String pathname = '';
|
||||
|
||||
|
||||
@@ -11,60 +11,6 @@ import 'package:flutter_web_plugins/url_strategy.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('$HashUrlStrategy', () {
|
||||
late TestPlatformLocation location;
|
||||
|
||||
setUp(() {
|
||||
location = TestPlatformLocation();
|
||||
});
|
||||
|
||||
test('allows null state', () {
|
||||
final HashUrlStrategy strategy = HashUrlStrategy(location);
|
||||
expect(() => strategy.pushState(null, '', '/'), returnsNormally);
|
||||
expect(() => strategy.replaceState(null, '', '/'), returnsNormally);
|
||||
});
|
||||
|
||||
test('leading slash is optional', () {
|
||||
final HashUrlStrategy strategy = HashUrlStrategy(location);
|
||||
|
||||
location.hash = '#/';
|
||||
expect(strategy.getPath(), '/');
|
||||
|
||||
location.hash = '#/foo';
|
||||
expect(strategy.getPath(), '/foo');
|
||||
|
||||
location.hash = '#foo';
|
||||
expect(strategy.getPath(), 'foo');
|
||||
});
|
||||
|
||||
test('path should not be empty', () {
|
||||
final HashUrlStrategy strategy = HashUrlStrategy(location);
|
||||
|
||||
location.hash = '';
|
||||
expect(strategy.getPath(), '/');
|
||||
|
||||
location.hash = '#';
|
||||
expect(strategy.getPath(), '/');
|
||||
});
|
||||
|
||||
test('allows location path/search before fragment', () {
|
||||
const String internalUrl = '/menu?foo=bar';
|
||||
final HashUrlStrategy strategy = HashUrlStrategy(location);
|
||||
|
||||
location.pathname = '/';
|
||||
expect(strategy.prepareExternalUrl(internalUrl), '/#/menu?foo=bar');
|
||||
|
||||
location.pathname = '/main';
|
||||
expect(strategy.prepareExternalUrl(internalUrl), '/main#/menu?foo=bar');
|
||||
|
||||
location.search = '?foo=bar';
|
||||
expect(
|
||||
strategy.prepareExternalUrl(internalUrl),
|
||||
'/main?foo=bar#/menu?foo=bar',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('$PathUrlStrategy', () {
|
||||
late TestPlatformLocation location;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user