From 7eecf8874f553cf6606343cc648380cc475fda3b Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Wed, 31 May 2023 13:35:50 -0400 Subject: [PATCH] [web] Use PlatformLocation from ui_web (#126851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../lib/flutter_web_plugins.dart | 1 - .../lib/src/navigation/url_strategy.dart | 191 ++---------------- .../platform_location.dart | 51 ++++- .../src/navigation_non_web/url_strategy.dart | 6 +- .../flutter_web_plugins/lib/url_strategy.dart | 2 - .../test/navigation/common.dart | 2 +- .../test/navigation/url_strategy_test.dart | 54 ----- 7 files changed, 64 insertions(+), 243 deletions(-) rename packages/flutter_web_plugins/lib/src/{navigation_common => navigation_non_web}/platform_location.dart (76%) diff --git a/packages/flutter_web_plugins/lib/flutter_web_plugins.dart b/packages/flutter_web_plugins/lib/flutter_web_plugins.dart index d9c5f208ce..3b27fff155 100644 --- a/packages/flutter_web_plugins/lib/flutter_web_plugins.dart +++ b/packages/flutter_web_plugins/lib/flutter_web_plugins.dart @@ -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'; 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 59294723b5..2c579fdcc7 100644 --- a/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/src/navigation/url_strategy.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 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 _waitForPopState() { - final Completer completer = Completer(); - 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(); -} diff --git a/packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart b/packages/flutter_web_plugins/lib/src/navigation_non_web/platform_location.dart similarity index 76% rename from packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart rename to packages/flutter_web_plugins/lib/src/navigation_non_web/platform_location.dart index 3317a706c8..fb3e1338aa 100644 --- a/packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart +++ b/packages/flutter_web_plugins/lib/src/navigation_non_web/platform_location.dart @@ -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; +} 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 149299712c..f9622608bd 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,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? _]); } diff --git a/packages/flutter_web_plugins/lib/url_strategy.dart b/packages/flutter_web_plugins/lib/url_strategy.dart index 0ebf7b4a77..79cc9265c2 100644 --- a/packages/flutter_web_plugins/lib/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/url_strategy.dart @@ -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'; diff --git a/packages/flutter_web_plugins/test/navigation/common.dart b/packages/flutter_web_plugins/test/navigation/common.dart index 0f65f42f2d..e01616bf64 100644 --- a/packages/flutter_web_plugins/test/navigation/common.dart +++ b/packages/flutter_web_plugins/test/navigation/common.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 = ''; 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 aa08f027ca..24cf328cae 100644 --- a/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart +++ b/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart @@ -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;