Unverified Commit 7eecf887 authored by Mouad Debbar's avatar Mouad Debbar Committed by GitHub

[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
parent efac4215
...@@ -17,6 +17,5 @@ library flutter_web_plugins; ...@@ -17,6 +17,5 @@ library flutter_web_plugins;
export 'src/navigation/url_strategy.dart'; export 'src/navigation/url_strategy.dart';
export 'src/navigation/utils.dart'; export 'src/navigation/utils.dart';
export 'src/navigation_common/platform_location.dart';
export 'src/plugin_event_channel.dart'; export 'src/plugin_event_channel.dart';
export 'src/plugin_registry.dart'; export 'src/plugin_registry.dart';
...@@ -2,39 +2,23 @@ ...@@ -2,39 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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 'dart:ui_web' as ui_web;
import '../navigation_common/platform_location.dart';
import 'utils.dart'; import 'utils.dart';
export 'dart:ui_web' show UrlStrategy; export 'dart:ui_web'
show
/// Saves the current [UrlStrategy] to be accessed by [urlStrategy] or BrowserPlatformLocation,
/// [setUrlStrategy]. EventListener,
/// HashUrlStrategy,
/// This is particularly required for web plugins relying on valid URL PlatformLocation,
/// encoding. UrlStrategy,
// urlStrategy;
// 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;
/// Change the strategy to use for handling browser URL. /// Change the strategy to use for handling browser URL.
/// ///
/// Setting this to null disables all integration with the browser history. /// Setting this to null disables all integration with the browser history.
void setUrlStrategy(ui_web.UrlStrategy? strategy) { void setUrlStrategy(ui_web.UrlStrategy? strategy) {
_urlStrategy = strategy;
ui_web.urlStrategy = strategy; ui_web.urlStrategy = strategy;
} }
...@@ -43,99 +27,6 @@ void usePathUrlStrategy() { ...@@ -43,99 +27,6 @@ void usePathUrlStrategy() {
setUrlStrategy(PathUrlStrategy()); 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. /// 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: /// 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 { ...@@ -146,17 +37,19 @@ class HashUrlStrategy extends ui_web.UrlStrategy {
/// // Somewhere before calling `runApp()` do: /// // Somewhere before calling `runApp()` do:
/// setUrlStrategy(PathUrlStrategy()); /// setUrlStrategy(PathUrlStrategy());
/// ``` /// ```
class PathUrlStrategy extends HashUrlStrategy { class PathUrlStrategy extends ui_web.HashUrlStrategy {
/// Creates an instance of [PathUrlStrategy]. /// 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. /// interactions.
PathUrlStrategy([ PathUrlStrategy([
super.platformLocation, super.platformLocation,
]) : _basePath = stripTrailingSlash(extractPathname(checkBaseHref( ]) : _platformLocation = platformLocation,
_basePath = stripTrailingSlash(extractPathname(checkBaseHref(
platformLocation.getBaseHref(), platformLocation.getBaseHref(),
))); )));
final ui_web.PlatformLocation _platformLocation;
final String _basePath; final String _basePath;
@override @override
...@@ -176,61 +69,3 @@ class PathUrlStrategy extends HashUrlStrategy { ...@@ -176,61 +69,3 @@ class PathUrlStrategy extends HashUrlStrategy {
return '$_basePath$internalUrl'; 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 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'url_strategy.dart';
/// Function type that handles pop state events. /// Function type that handles pop state events.
typedef EventListener = dynamic Function(Object event); typedef EventListener = dynamic Function(Object event);
...@@ -10,11 +12,7 @@ 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 /// For convenience, the [PlatformLocation] class can be used by implementations
/// of [UrlStrategy] to interact with DOM apis like pushState, popState, etc. /// of [UrlStrategy] to interact with DOM apis like pushState, popState, etc.
abstract class PlatformLocation { abstract interface class PlatformLocation {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const PlatformLocation();
/// Registers an event listener for the `popstate` event. /// Registers an event listener for the `popstate` event.
/// ///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
...@@ -74,3 +72,46 @@ abstract class PlatformLocation { ...@@ -74,3 +72,46 @@ abstract class PlatformLocation {
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
String? getBaseHref(); 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 @@ ...@@ -5,7 +5,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui' as ui; 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. /// Callback that receives the new state of the browser history entry.
typedef PopStateListener = void Function(Object? state); typedef PopStateListener = void Function(Object? state);
...@@ -123,5 +125,5 @@ class PathUrlStrategy extends HashUrlStrategy { ...@@ -123,5 +125,5 @@ class PathUrlStrategy extends HashUrlStrategy {
/// ///
/// The [PlatformLocation] parameter is useful for testing to mock out browser /// The [PlatformLocation] parameter is useful for testing to mock out browser
/// integrations. /// integrations.
PathUrlStrategy([PlatformLocation? _]); const PathUrlStrategy([PlatformLocation? _]);
} }
...@@ -2,7 +2,5 @@ ...@@ -2,7 +2,5 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
export 'src/navigation_common/platform_location.dart';
export 'src/navigation_non_web/url_strategy.dart' export 'src/navigation_non_web/url_strategy.dart'
if (dart.library.ui_web) 'src/navigation/url_strategy.dart'; if (dart.library.ui_web) 'src/navigation/url_strategy.dart';
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:flutter_web_plugins/url_strategy.dart';
/// A mock implementation of [PlatformLocation] that doesn't access the browser. /// A mock implementation of [PlatformLocation] that doesn't access the browser.
class TestPlatformLocation extends PlatformLocation { class TestPlatformLocation implements PlatformLocation {
@override @override
String pathname = ''; String pathname = '';
......
...@@ -11,60 +11,6 @@ import 'package:flutter_web_plugins/url_strategy.dart'; ...@@ -11,60 +11,6 @@ import 'package:flutter_web_plugins/url_strategy.dart';
import 'common.dart'; import 'common.dart';
void main() { 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', () { group('$PathUrlStrategy', () {
late TestPlatformLocation location; late TestPlatformLocation location;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment