Unverified Commit 201f7311 authored by Jackson Gardner's avatar Jackson Gardner Committed by GitHub

Use direct dart API from `dart:ui_web` rather than JS shim. (#123443)

This is based off (and dependent on) these changes in the web engine that expose web-specific APIs: https://github.com/flutter/engine/pull/40608
parent 042eaf6b
...@@ -15,9 +15,8 @@ ...@@ -15,9 +15,8 @@
/// describing how the `url_launcher` package was created using [flutter_web_plugins]. /// describing how the `url_launcher` package was created using [flutter_web_plugins].
library flutter_web_plugins; library flutter_web_plugins;
export 'src/navigation/js_url_strategy.dart';
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/url_strategy.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';
// Copyright 2014 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.
// TODO(goderbauer): Remove this ignore when the documentation for the
// now private, then public typedefs is clear.
// ignore_for_file: library_private_types_in_public_api
@JS()
library js_location_strategy;
import 'dart:async';
import 'dart:html' as html;
import 'dart:ui' as ui;
import 'package:js/js.dart';
import 'package:meta/meta.dart';
import '../navigation_common/url_strategy.dart';
typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?);
/// A JavaScript hook to customize the URL strategy of a Flutter app.
//
// Keep this in sync with the JS name in the web engine. Find it at:
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart
//
// TODO(mdebbar): Add integration test https://github.com/flutter/flutter/issues/66852
@JS('_flutter_web_set_location_strategy')
external _JsSetUrlStrategy get jsSetUrlStrategy;
typedef _PathGetter = String Function();
typedef _StateGetter = Object? Function();
typedef _AddPopStateListener = ui.VoidCallback Function(EventListener);
typedef _StringToString = String Function(String);
typedef _StateOperation = void Function(Object state, String title, String url);
typedef _HistoryMove = Future<void> Function(int count);
/// Given a Dart implementation of URL strategy, converts it to a JavaScript
/// URL strategy to be passed through JS interop.
JsUrlStrategy convertToJsUrlStrategy(UrlStrategy strategy) {
return JsUrlStrategy(
getPath: allowInterop(strategy.getPath),
getState: allowInterop(strategy.getState),
addPopStateListener: allowInterop(strategy.addPopStateListener),
prepareExternalUrl: allowInterop(strategy.prepareExternalUrl),
pushState: allowInterop(strategy.pushState),
replaceState: allowInterop(strategy.replaceState),
go: allowInterop(strategy.go),
);
}
/// The JavaScript representation of a URL strategy.
///
/// This is used to pass URL strategy implementations across a JS-interop
/// bridge from the app to the engine.
@JS()
@anonymous
abstract class JsUrlStrategy {
/// Creates an instance of [JsUrlStrategy] from a bag of URL strategy
/// functions.
external factory JsUrlStrategy({
@required _PathGetter getPath,
@required _StateGetter getState,
@required _AddPopStateListener addPopStateListener,
@required _StringToString prepareExternalUrl,
@required _StateOperation pushState,
@required _StateOperation replaceState,
@required _HistoryMove go,
});
/// Adds a listener to the `popstate` event and returns a function that
/// removes the listener.
external ui.VoidCallback addPopStateListener(html.EventListener fn);
/// Returns the active path in the browser.
external String getPath();
/// Returns the history state in the browser.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
external Object getState();
/// Given a path that's internal to the app, create the external url that
/// will be used in the browser.
external String prepareExternalUrl(String internalUrl);
/// Push a new history entry.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
external 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
external 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 causes a forward move.
///
/// Examples:
///
/// * `go(-2)` moves back 2 steps in history.
/// * `go(3)` moves forward 3 steps in history.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go
external Future<void> go(int count);
}
...@@ -5,11 +5,13 @@ ...@@ -5,11 +5,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:html' as html; import 'dart:html' as html;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:ui_web' as ui_web;
import '../navigation_common/url_strategy.dart'; import '../navigation_common/platform_location.dart';
import 'js_url_strategy.dart';
import 'utils.dart'; import 'utils.dart';
export 'dart:ui_web' show UrlStrategy;
/// Saves the current [UrlStrategy] to be accessed by [urlStrategy] or /// Saves the current [UrlStrategy] to be accessed by [urlStrategy] or
/// [setUrlStrategy]. /// [setUrlStrategy].
/// ///
...@@ -20,25 +22,20 @@ import 'utils.dart'; ...@@ -20,25 +22,20 @@ import 'utils.dart';
// Find it at: // Find it at:
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/window.dart#L360 // https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/window.dart#L360
// //
UrlStrategy? _urlStrategy = const HashUrlStrategy(); ui_web.UrlStrategy? _urlStrategy = const HashUrlStrategy();
/// Returns the present [UrlStrategy] for handling the browser URL. /// Returns the present [UrlStrategy] for handling the browser URL.
/// ///
/// In case null is returned, the browser integration has been manually /// In case null is returned, the browser integration has been manually
/// disabled by [setUrlStrategy]. /// disabled by [setUrlStrategy].
UrlStrategy? get urlStrategy => _urlStrategy; 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(UrlStrategy? strategy) { void setUrlStrategy(ui_web.UrlStrategy? strategy) {
_urlStrategy = strategy; _urlStrategy = strategy;
ui_web.urlStrategy = strategy;
JsUrlStrategy? jsUrlStrategy;
if (strategy != null) {
jsUrlStrategy = convertToJsUrlStrategy(strategy);
}
jsSetUrlStrategy(jsUrlStrategy);
} }
/// Use the [PathUrlStrategy] to handle the browser URL. /// Use the [PathUrlStrategy] to handle the browser URL.
...@@ -60,7 +57,7 @@ void usePathUrlStrategy() { ...@@ -60,7 +57,7 @@ void usePathUrlStrategy() {
/// // Somewhere before calling `runApp()` do: /// // Somewhere before calling `runApp()` do:
/// setUrlStrategy(const HashUrlStrategy()); /// setUrlStrategy(const HashUrlStrategy());
/// ``` /// ```
class HashUrlStrategy extends UrlStrategy { class HashUrlStrategy extends ui_web.UrlStrategy {
/// Creates an instance of [HashUrlStrategy]. /// Creates an instance of [HashUrlStrategy].
/// ///
/// The [PlatformLocation] parameter is useful for testing to mock out browser /// The [PlatformLocation] parameter is useful for testing to mock out browser
...@@ -71,9 +68,13 @@ class HashUrlStrategy extends UrlStrategy { ...@@ -71,9 +68,13 @@ class HashUrlStrategy extends UrlStrategy {
final PlatformLocation _platformLocation; final PlatformLocation _platformLocation;
@override @override
ui.VoidCallback addPopStateListener(EventListener fn) { ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) {
_platformLocation.addPopStateListener(fn); void wrappedFn(Object event) {
return () => _platformLocation.removePopStateListener(fn); // `fn` expects `event.state`, not a `html.Event`.
fn((event as html.PopStateEvent).state);
}
_platformLocation.addPopStateListener(wrappedFn);
return () => _platformLocation.removePopStateListener(wrappedFn);
} }
@override @override
......
...@@ -2,63 +2,9 @@ ...@@ -2,63 +2,9 @@
// 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'; /// Function type that handles pop state events.
import 'dart:ui' as ui;
/// Signature of an html event listener.
///
/// We have to redefine it because non-web platforms can't import dart:html.
typedef EventListener = dynamic Function(Object event); typedef EventListener = dynamic Function(Object event);
/// 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(EventListener 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 causes a forward move.
///
/// Examples:
///
/// * `go(-2)` moves back 2 steps in history.
/// * `go(3)` moves forward 3 steps in history.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go
Future<void> go(int count);
}
/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes /// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes
/// to be platform agnostic and testable. /// to be platform agnostic and testable.
/// ///
......
...@@ -5,7 +5,68 @@ ...@@ -5,7 +5,68 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import '../navigation_common/url_strategy.dart'; import '../navigation_common/platform_location.dart';
/// Callback that receives the new state of the browser history entry.
typedef PopStateListener = void Function(Object? state);
/// 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(PopStateListener fn) {
// No-op.
return () {};
}
/// 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() => null;
/// 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) {
// No-op.
}
/// 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) {
// No-op.
}
/// 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 causes a forward move.
///
/// Examples:
///
/// * `go(-2)` moves back 2 steps in history.
/// * `go(3)` moves forward 3 steps in history.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go
Future<void> go(int count) async {
// No-op.
}
}
/// Returns the present [UrlStrategy] for handling the browser URL. /// Returns the present [UrlStrategy] for handling the browser URL.
/// ///
...@@ -45,36 +106,6 @@ class HashUrlStrategy extends UrlStrategy { ...@@ -45,36 +106,6 @@ class HashUrlStrategy extends UrlStrategy {
/// The [PlatformLocation] parameter is useful for testing to mock out browser /// The [PlatformLocation] parameter is useful for testing to mock out browser
/// integrations. /// integrations.
const HashUrlStrategy([PlatformLocation? _]); const HashUrlStrategy([PlatformLocation? _]);
@override
ui.VoidCallback addPopStateListener(EventListener fn) {
// No-op.
return () {};
}
@override
String getPath() => '';
@override
Object? getState() => null;
@override
String prepareExternalUrl(String internalUrl) => '';
@override
void pushState(Object? state, String title, String url) {
// No-op.
}
@override
void replaceState(Object? state, String title, String url) {
// No-op.
}
@override
Future<void> go(int count) async {
// No-op.
}
} }
/// Uses the browser URL's pathname to represent Flutter's route name. /// Uses the browser URL's pathname to represent Flutter's route name.
...@@ -92,11 +123,5 @@ class PathUrlStrategy extends HashUrlStrategy { ...@@ -92,11 +123,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([super.platformLocation]); PathUrlStrategy([PlatformLocation? _]);
@override
String getPath() => '';
@override
String prepareExternalUrl(String internalUrl) => '';
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// 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/url_strategy.dart'; 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.html) 'src/navigation/url_strategy.dart'; if (dart.library.ui_web) 'src/navigation/url_strategy.dart';
// Copyright 2014 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:flutter_web_plugins/url_strategy.dart';
/// A mock implementation of [PlatformLocation] that doesn't access the browser.
class TestPlatformLocation extends PlatformLocation {
@override
String pathname = '';
@override
String search = '';
@override
String hash = '';
@override
Object? get state => null;
/// Mocks the base href of the document.
String baseHref = '';
@override
void addPopStateListener(EventListener fn) {
throw UnimplementedError();
}
@override
void removePopStateListener(EventListener fn) {
throw UnimplementedError();
}
@override
void pushState(Object? state, String title, String url) {}
@override
void replaceState(Object? state, String title, String url) {}
@override
void go(int count) {
throw UnimplementedError();
}
@override
String getBaseHref() => baseHref;
}
// Copyright 2014 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.
@TestOn('!chrome')
library;
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
import 'common.dart';
void main() {
group('Non-web UrlStrategy', () {
late TestPlatformLocation location;
setUp(() {
location = TestPlatformLocation();
});
test('Can create and set a $HashUrlStrategy', () {
expect(() {
final HashUrlStrategy strategy = HashUrlStrategy(location);
setUrlStrategy(strategy);
}, returnsNormally);
});
test('Can create and set a $PathUrlStrategy', () {
expect(() {
final PathUrlStrategy strategy = PathUrlStrategy(location);
setUrlStrategy(strategy);
}, returnsNormally);
});
test('Can usePathUrlStrategy', () {
expect(() {
usePathUrlStrategy();
}, returnsNormally);
});
});
}
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
library; library;
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:flutter_web_plugins/url_strategy.dart';
import 'common.dart';
void main() { void main() {
group('$HashUrlStrategy', () { group('$HashUrlStrategy', () {
...@@ -142,45 +144,3 @@ void main() { ...@@ -142,45 +144,3 @@ void main() {
}); });
}); });
} }
/// A mock implementation of [PlatformLocation] that doesn't access the browser.
class TestPlatformLocation extends PlatformLocation {
@override
String pathname = '';
@override
String search = '';
@override
String hash = '';
@override
Object? get state => null;
/// Mocks the base href of the document.
String baseHref = '';
@override
void addPopStateListener(EventListener fn) {
throw UnimplementedError();
}
@override
void removePopStateListener(EventListener fn) {
throw UnimplementedError();
}
@override
void pushState(Object? state, String title, String url) {}
@override
void replaceState(Object? state, String title, String url) {}
@override
void go(int count) {
throw UnimplementedError();
}
@override
String getBaseHref() => baseHref;
}
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