Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[web] Expose PlatformLocation and HashUrlStrategy through ui_web #41163

Merged
merged 11 commits into from
May 10, 2023
6 changes: 4 additions & 2 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2061,7 +2061,8 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/text.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/tile_mode.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/ui.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/url_strategy.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/platform_location.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/url_strategy.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/window.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/canvas.cpp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/skwasm/contour_measure.cpp + ../../../flutter/LICENSE
Expand Down Expand Up @@ -4664,7 +4665,8 @@ FILE: ../../../flutter/lib/web_ui/lib/text.dart
FILE: ../../../flutter/lib/web_ui/lib/tile_mode.dart
FILE: ../../../flutter/lib/web_ui/lib/ui.dart
FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web.dart
FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/url_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/platform_location.dart
FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/url_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/window.dart
FILE: ../../../flutter/lib/web_ui/skwasm/canvas.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/contour_measure.cpp
Expand Down
213 changes: 1 addition & 212 deletions lib/web_ui/lib/src/engine/navigation/url_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,99 +11,7 @@ import '../dom.dart';
import '../safe_browser_api.dart';
import 'js_url_strategy.dart';

/// This is an implementation of [UrlStrategy] that uses the browser URL's
/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
/// to represent its state.
///
/// In order to use this [UrlStrategy] 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
/// interations.
const HashUrlStrategy(
[this._platformLocation = const BrowserPlatformLocation()]);

final PlatformLocation _platformLocation;

@override
ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) {
final DomEventListener wrappedFn = createDomEventListener((DomEvent event) {
// `fn` expects `event.state`, not a `DomEvent`.
fn((event as DomPopStateEvent).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 instead return the location's path and
// query.
return internalUrl.isEmpty
? '${_platformLocation.pathname}${_platformLocation.search}'
: '#$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.toDouble());
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;
}
}

/// Wraps a custom implementation of [UrlStrategy] that was previously converted
/// Wraps a custom implementation of [ui_web.UrlStrategy] that was previously converted
/// to a [JsUrlStrategy].
class CustomUrlStrategy extends ui_web.UrlStrategy {
/// Wraps the [delegate] in a [CustomUrlStrategy] instance.
Expand Down Expand Up @@ -138,122 +46,3 @@ class CustomUrlStrategy extends ui_web.UrlStrategy {
@override
Future<void> go(int count) => delegate.go(count.toDouble());
}

/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes
/// to be platform agnostic and testable.
///
/// 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();

/// Registers an event listener for the `popstate` event.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
void addPopStateListener(DomEventListener fn);

/// Unregisters the given listener (added by [addPopStateListener]) from the
/// `popstate` event.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
void removePopStateListener(DomEventListener fn);

/// The `pathname` part of the URL in the browser address bar.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname
String get pathname;

/// The `query` part of the URL in the browser address bar.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/search
String get search;

/// The `hash]` part of the URL in the browser address bar.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/hash
String? get hash;

/// The `state` in the current history entry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
Object? get state;

/// Adds a new entry to the browser history stack.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
void pushState(Object? state, String title, String url);

/// Replaces the current entry in the browser history stack.
///
/// 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 causs a forward move.
///
/// Examples:
///
/// * `go(-2)` moves back 2 steps in history.
/// * `go(3)` moves forward 3 steps in hisotry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go
void go(double count);

/// The base href where the Flutter app is being served.
///
/// 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 extends PlatformLocation {
/// Default constructor for [BrowserPlatformLocation].
const BrowserPlatformLocation();

DomLocation get _location => domWindow.location;
DomHistory get _history => domWindow.history;

@override
void addPopStateListener(DomEventListener fn) {
domWindow.addEventListener('popstate', fn);
}

@override
void removePopStateListener(DomEventListener fn) {
domWindow.removeEventListener('popstate', fn);
}

@override
String get pathname => _location.pathname!;

@override
String get search => _location.search!;

@override
String get hash => _location.locationHash;

@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(double count) {
_history.go(count);
}

@override
String? getBaseHref() => domDocument.baseUri;
}
3 changes: 1 addition & 2 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import '../engine.dart' show DimensionsProvider, registerHotRestartListener, ren
import 'dom.dart';
import 'navigation/history.dart';
import 'navigation/js_url_strategy.dart';
import 'navigation/url_strategy.dart';
import 'platform_dispatcher.dart';
import 'services.dart';
import 'test_embedding.dart';
Expand Down Expand Up @@ -381,7 +380,7 @@ external set jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy);
ui_web.UrlStrategy? _createDefaultUrlStrategy() {
return ui.debugEmulateFlutterTesterEnvironment
? TestUrlStrategy.fromEntry(const TestHistoryEntry('default', null, '/'))
: const HashUrlStrategy();
: const ui_web.HashUrlStrategy();
}

/// The Web implementation of [ui.SingletonFlutterWindow].
Expand Down
3 changes: 2 additions & 1 deletion lib/web_ui/lib/ui_web/src/ui_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
// ignore: unnecessary_library_directive
library ui_web;

export 'ui_web/url_strategy.dart';
export 'ui_web/navigation/platform_location.dart';
export 'ui_web/navigation/url_strategy.dart';
126 changes: 126 additions & 0 deletions lib/web_ui/lib/ui_web/src/ui_web/navigation/platform_location.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2013 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:ui/src/engine.dart';

import 'url_strategy.dart';

/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes
/// to be platform agnostic and testable.
///
/// 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();
mdebbar marked this conversation as resolved.
Show resolved Hide resolved

/// Registers an event listener for the `popstate` event.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
void addPopStateListener(DomEventListener fn);

/// Unregisters the given listener (added by [addPopStateListener]) from the
/// `popstate` event.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
void removePopStateListener(DomEventListener fn);

/// The `pathname` part of the URL in the browser address bar.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname
String get pathname;

/// The `query` part of the URL in the browser address bar.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/search
String get search;

/// The `hash]` part of the URL in the browser address bar.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/hash
String? get hash;

/// The `state` in the current history entry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
Object? get state;

/// Adds a new entry to the browser history stack.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
void pushState(Object? state, String title, String url);

/// Replaces the current entry in the browser history stack.
///
/// 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 causs a forward move.
///
/// Examples:
///
/// * `go(-2)` moves back 2 steps in history.
/// * `go(3)` moves forward 3 steps in hisotry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go
void go(double count);

/// The base href where the Flutter app is being served.
///
/// 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 extends PlatformLocation {
/// Default constructor for [BrowserPlatformLocation].
const BrowserPlatformLocation();

DomLocation get _location => domWindow.location;
DomHistory get _history => domWindow.history;

@override
void addPopStateListener(DomEventListener fn) {
domWindow.addEventListener('popstate', fn);
}

@override
void removePopStateListener(DomEventListener fn) {
domWindow.removeEventListener('popstate', fn);
}

@override
String get pathname => _location.pathname!;

@override
String get search => _location.search!;

@override
String get hash => _location.locationHash;

@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(double count) {
_history.go(count);
}

@override
String? getBaseHref() => domDocument.baseUri;
}