Skip to content

Commit

Permalink
ui_web library (flutter#40608)
Browse files Browse the repository at this point in the history
`ui_web` library
  • Loading branch information
eyebrowsoffire authored Mar 30, 2023
1 parent 571c5de commit a6deb36
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 109 deletions.
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,8 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/window.dart + ../../../flutte
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/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 @@ -4615,6 +4617,8 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/window.dart
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/window.dart
FILE: ../../../flutter/lib/web_ui/skwasm/canvas.cpp
FILE: ../../../flutter/lib/web_ui/skwasm/contour_measure.cpp
Expand Down
40 changes: 20 additions & 20 deletions lib/web_ui/lib/src/engine/navigation/history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@

import 'package:meta/meta.dart';
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;

import '../dom.dart';
import '../platform_dispatcher.dart';
import '../services/message_codec.dart';
import '../services/message_codecs.dart';
import 'url_strategy.dart';

/// Infers the history mode from the existing browser history state, then
/// creates the appropriate instance of [BrowserHistory] for it.
///
/// If it can't infer, it creates a [MultiEntriesBrowserHistory] by default.
BrowserHistory createHistoryForExistingState(UrlStrategy? urlStrategy) {
BrowserHistory createHistoryForExistingState(ui_web.UrlStrategy? urlStrategy) {
if (urlStrategy != null) {
final Object? state = urlStrategy.getState();
if (SingleEntryBrowserHistory._isOriginEntry(state) || SingleEntryBrowserHistory._isFlutterEntry(state)) {
Expand Down Expand Up @@ -45,13 +45,13 @@ abstract class BrowserHistory {
late ui.VoidCallback _unsubscribe;

/// The strategy to interact with html browser history.
UrlStrategy? get urlStrategy;
ui_web.UrlStrategy? get urlStrategy;

bool _isTornDown = false;
bool _isDisposed = false;

void _setupStrategy(UrlStrategy strategy) {
_unsubscribe = strategy.addPopStateListener(onPopState as DomEventListener);
void _setupStrategy(ui_web.UrlStrategy strategy) {
_unsubscribe = strategy.addPopStateListener(onPopState);
}

/// Release any resources held by this [BrowserHistory] instance.
Expand Down Expand Up @@ -100,7 +100,7 @@ abstract class BrowserHistory {
///
/// Subclasses should send appropriate system messages to update the flutter
/// applications accordingly.
void onPopState(covariant DomPopStateEvent event);
void onPopState(Object? state);

/// Restore any modifications to the html browser history during the lifetime
/// of this class.
Expand All @@ -123,7 +123,7 @@ abstract class BrowserHistory {
/// a Router for routing.
class MultiEntriesBrowserHistory extends BrowserHistory {
MultiEntriesBrowserHistory({required this.urlStrategy}) {
final UrlStrategy? strategy = urlStrategy;
final ui_web.UrlStrategy? strategy = urlStrategy;
if (strategy == null) {
return;
}
Expand All @@ -138,7 +138,7 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
}

@override
final UrlStrategy? urlStrategy;
final ui_web.UrlStrategy? urlStrategy;

late int _lastSeenSerialCount;
int get _currentSerialCount {
Expand Down Expand Up @@ -183,15 +183,15 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
}

@override
void onPopState(covariant DomPopStateEvent event) {
void onPopState(Object? state) {
assert(urlStrategy != null);
// May be a result of direct url access while the flutter application is
// already running.
if (!_hasSerialCount(event.state)) {
if (!_hasSerialCount(state)) {
// In this case we assume this will be the next history entry from the
// last seen entry.
urlStrategy!.replaceState(
_tagWithSerialCount(event.state, _lastSeenSerialCount + 1),
_tagWithSerialCount(state, _lastSeenSerialCount + 1),
'flutter',
currentPath);
}
Expand All @@ -201,7 +201,7 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
const JSONMethodCodec().encodeMethodCall(
MethodCall('pushRouteInformation', <dynamic, dynamic>{
'location': currentPath,
'state': event.state?['state'],
'state': (state as Map<dynamic, dynamic>?)?['state'],
})),
(_) {},
);
Expand All @@ -220,7 +220,7 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
assert(_hasSerialCount(currentState));
final int backCount = _currentSerialCount;
if (backCount > 0) {
await urlStrategy!.go(-backCount.toDouble());
await urlStrategy!.go(-backCount);
}
// Unwrap state.
assert(_hasSerialCount(currentState) && _currentSerialCount == 0);
Expand Down Expand Up @@ -252,7 +252,7 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
/// Router for routing.
class SingleEntryBrowserHistory extends BrowserHistory {
SingleEntryBrowserHistory({required this.urlStrategy}) {
final UrlStrategy? strategy = urlStrategy;
final ui_web.UrlStrategy? strategy = urlStrategy;
if (strategy == null) {
return;
}
Expand All @@ -271,7 +271,7 @@ class SingleEntryBrowserHistory extends BrowserHistory {
}

@override
final UrlStrategy? urlStrategy;
final ui_web.UrlStrategy? urlStrategy;

static const MethodCall _popRouteMethodCall = MethodCall('popRoute');
static const String _kFlutterTag = 'flutter';
Expand Down Expand Up @@ -311,8 +311,8 @@ class SingleEntryBrowserHistory extends BrowserHistory {

String? _userProvidedRouteName;
@override
void onPopState(covariant DomPopStateEvent event) {
if (_isOriginEntry(event.state)) {
void onPopState(Object? state) {
if (_isOriginEntry(state)) {
_setupFlutterEntry(urlStrategy!);

// 2. Send a 'popRoute' platform message so the app can handle it accordingly.
Expand All @@ -321,7 +321,7 @@ class SingleEntryBrowserHistory extends BrowserHistory {
const JSONMethodCodec().encodeMethodCall(_popRouteMethodCall),
(_) {},
);
} else if (_isFlutterEntry(event.state)) {
} else if (_isFlutterEntry(state)) {
// We get into this scenario when the user changes the url manually. It
// causes a new entry to be pushed on top of our "flutter" one. When this
// happens it first goes to the "else" section below where we capture the
Expand Down Expand Up @@ -358,14 +358,14 @@ class SingleEntryBrowserHistory extends BrowserHistory {
/// This method should be called when the Origin Entry is active. It just
/// replaces the state of the entry so that we can recognize it later using
/// [_isOriginEntry] inside [_popStateListener].
void _setupOriginEntry(UrlStrategy strategy) {
void _setupOriginEntry(ui_web.UrlStrategy strategy) {
strategy.replaceState(_wrapOriginState(currentState), 'origin', '');
}

/// This method is used manipulate the Flutter Entry which is always the
/// active entry while the Flutter app is running.
void _setupFlutterEntry(
UrlStrategy strategy, {
ui_web.UrlStrategy strategy, {
bool replace = false,
String? path,
}) {
Expand Down
71 changes: 13 additions & 58 deletions lib/web_ui/lib/src/engine/navigation/url_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,12 @@
import 'dart:async';

import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;

import '../dom.dart';
import '../safe_browser_api.dart';
import 'js_url_strategy.dart';

/// Represents and reads route state from the browser's URL.
///
/// By default, the [HashUrlStrategy] subclass is used if the app doesn't
/// specify one.
abstract class UrlStrategy {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const UrlStrategy();

/// Adds a listener to the `popstate` event and returns a function that, when
/// invoked, removes the listener.
ui.VoidCallback addPopStateListener(DomEventListener fn);

/// Returns the active path in the browser.
String getPath();

/// The state of the current browser history entry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
Object? getState();

/// Given a path that's internal to the app, create the external url that
/// will be used in the browser.
String prepareExternalUrl(String internalUrl);

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

/// Replace the currently active history entry.
///
/// 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
Future<void> go(double count);
}

/// 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.
Expand All @@ -71,7 +23,7 @@ abstract class UrlStrategy {
/// // Somewhere before calling `runApp()` do:
/// setUrlStrategy(const HashUrlStrategy());
/// ```
class HashUrlStrategy extends UrlStrategy {
class HashUrlStrategy extends ui_web.UrlStrategy {
/// Creates an instance of [HashUrlStrategy].
///
/// The [PlatformLocation] parameter is useful for testing to mock out browser
Expand All @@ -82,8 +34,9 @@ class HashUrlStrategy extends UrlStrategy {
final PlatformLocation _platformLocation;

@override
ui.VoidCallback addPopStateListener(DomEventListener fn) {
final DomEventListener wrappedFn = allowInterop(fn);
ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) {
final DomEventListener wrappedFn =
allowInterop((DomEvent event) => fn((event as DomPopStateEvent).state));
_platformLocation.addPopStateListener(wrappedFn);
return () => _platformLocation.removePopStateListener(wrappedFn);
}
Expand Down Expand Up @@ -128,8 +81,8 @@ class HashUrlStrategy extends UrlStrategy {
}

@override
Future<void> go(double count) {
_platformLocation.go(count);
Future<void> go(int count) {
_platformLocation.go(count.toDouble());
return _waitForPopState();
}

Expand All @@ -150,15 +103,17 @@ class HashUrlStrategy extends UrlStrategy {

/// Wraps a custom implementation of [UrlStrategy] that was previously converted
/// to a [JsUrlStrategy].
class CustomUrlStrategy extends UrlStrategy {
class CustomUrlStrategy extends ui_web.UrlStrategy {
/// Wraps the [delegate] in a [CustomUrlStrategy] instance.
CustomUrlStrategy.fromJs(this.delegate);

final JsUrlStrategy delegate;

@override
ui.VoidCallback addPopStateListener(DomEventListener fn) =>
delegate.addPopStateListener(allowInterop(fn));
ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) =>
delegate.addPopStateListener(allowInterop((DomEvent event) =>
fn((event as DomPopStateEvent).state)
));

@override
String getPath() => delegate.getPath();
Expand All @@ -179,7 +134,7 @@ class CustomUrlStrategy extends UrlStrategy {
delegate.replaceState(state, title, url);

@override
Future<void> go(double count) => delegate.go(count);
Future<void> go(int count) => delegate.go(count.toDouble());
}

/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes
Expand Down
24 changes: 10 additions & 14 deletions lib/web_ui/lib/src/engine/test_embedding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import 'dart:async';

import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;

import '../engine.dart';

Expand Down Expand Up @@ -54,7 +55,7 @@ class TestHistoryEntry {
///
/// It keeps a list of history entries and event listeners in memory and
/// manipulates them in order to achieve the desired functionality.
class TestUrlStrategy extends UrlStrategy {
class TestUrlStrategy extends ui_web.UrlStrategy {
/// Creates a instance of [TestUrlStrategy] with an empty string as the
/// path.
factory TestUrlStrategy() => TestUrlStrategy.fromEntry(const TestHistoryEntry(null, null, ''));
Expand Down Expand Up @@ -132,12 +133,12 @@ class TestUrlStrategy extends UrlStrategy {
}

@override
Future<void> go(double count) {
Future<void> go(int count) {
assert(withinAppHistory);
// Browsers don't move in history immediately. They do it at the next
// event loop. So let's simulate that.
return _nextEventLoop(() {
_currentEntryIndex = _currentEntryIndex + count.round();
_currentEntryIndex = _currentEntryIndex + count;
if (withinAppHistory) {
_firePopStateEvent();
}
Expand All @@ -148,16 +149,15 @@ class TestUrlStrategy extends UrlStrategy {
});
}

final List<DomEventListener> listeners = <DomEventListener>[];
final List<ui_web.PopStateListener> listeners = <ui_web.PopStateListener>[];

@override
ui.VoidCallback addPopStateListener(DomEventListener fn) {
final DomEventListener wrappedFn = allowInterop(fn);
listeners.add(wrappedFn);
ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) {
listeners.add(fn);
return () {
// Schedule a micro task here to avoid removing the listener during
// iteration in [_firePopStateEvent].
scheduleMicrotask(() => listeners.remove(wrappedFn));
scheduleMicrotask(() => listeners.remove(fn));
};
}

Expand All @@ -172,16 +172,12 @@ class TestUrlStrategy extends UrlStrategy {
/// like a real browser.
void _firePopStateEvent() {
assert(withinAppHistory);
final DomPopStateEvent event = createDomPopStateEvent(
'popstate',
<String, dynamic>{'state': currentEntry.state},
);
for (int i = 0; i < listeners.length; i++) {
listeners[i](event);
listeners[i](currentEntry.state);
}

if (_debugLogHistoryActions) {
print('$runtimeType: fired popstate event $event');
print('$runtimeType: fired popstate with state ${currentEntry.state}');
}
}

Expand Down
Loading

0 comments on commit a6deb36

Please sign in to comment.