Skip to content

Commit

Permalink
[web] Expose PlatformLocation and HashUrlStrategy through ui_web (#41163
Browse files Browse the repository at this point in the history
)

1. Expose `PlatformLocation` and its subclass `BrowserPlatformLocation` through the new `ui_web`.
2. Expose `HashUrlStrategy` too. It's useful for users who want to extend and customize it instead of building their own from scratch.
3. `ui_web/url_strategy.dart` => `ui_web/navigation/url_strategy.dart`.
  • Loading branch information
mdebbar committed May 10, 2023
1 parent ef771f9 commit a0925f1
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 280 deletions.
6 changes: 4 additions & 2 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2068,7 +2068,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 @@ -4684,7 +4685,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';
122 changes: 122 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,122 @@
// 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 interface class 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 implements 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;
}

0 comments on commit a0925f1

Please sign in to comment.