mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Revert "[web] Remove the JS API for url strategy" (flutter/engine#42468)
Reverts flutter/engine#42134 This is blocking the engine into framework roller: See: https://cirrus-ci.com/task/5610586755563520 ``` Analyzing 3 items... error ⢠The class 'UrlStrategy' can't be extended outside of its library because it's an interface class ⢠dev/integration_tests/web_e2e_tests/test_driver/url_strategy_integration.dart:48:31 ⢠invalid_use_of_type_outside_library 1 issue found. (ran in 321.8s) ð ð ```
This commit is contained in:
parent
c30b19a6d9
commit
fd5cbcfe04
@ -1975,7 +1975,10 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/url_strategy.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart + ../../../flutter/LICENSE
|
||||
@ -4636,7 +4639,10 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/url_strategy.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart
|
||||
|
||||
@ -113,6 +113,8 @@ export 'engine/key_map.g.dart';
|
||||
export 'engine/keyboard_binding.dart';
|
||||
export 'engine/mouse_cursor.dart';
|
||||
export 'engine/navigation/history.dart';
|
||||
export 'engine/navigation/js_url_strategy.dart';
|
||||
export 'engine/navigation/url_strategy.dart';
|
||||
export 'engine/noto_font.dart';
|
||||
export 'engine/onscreen_logging.dart';
|
||||
export 'engine/picture.dart';
|
||||
|
||||
@ -8,6 +8,7 @@ import 'dart:js_interop';
|
||||
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
import 'package:web_test_fonts/web_test_fonts.dart';
|
||||
|
||||
/// The mode the app is running in.
|
||||
@ -132,6 +133,10 @@ Future<void> initializeEngineServices({
|
||||
// Store `jsConfiguration` so user settings are available to the engine.
|
||||
configuration.setUserConfiguration(jsConfiguration);
|
||||
|
||||
// Setup the hook that allows users to customize URL strategy before running
|
||||
// the app.
|
||||
_addUrlStrategyListener();
|
||||
|
||||
// Called by the Web runtime just before hot restarting the app.
|
||||
//
|
||||
// This extension cleans up resources that are registered with browser's
|
||||
@ -258,6 +263,26 @@ Future<void> _downloadAssetFonts() async {
|
||||
}
|
||||
}
|
||||
|
||||
void _addUrlStrategyListener() {
|
||||
jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) {
|
||||
if (jsStrategy == null) {
|
||||
ui_web.urlStrategy = null;
|
||||
} else {
|
||||
// Because `JSStrategy` could be anything, we check for the
|
||||
// `addPopStateListener` property and throw if it is missing.
|
||||
if (!hasJsProperty(jsStrategy, 'addPopStateListener')) {
|
||||
throw StateError(
|
||||
'Unexpected JsUrlStrategy: $jsStrategy is missing '
|
||||
'`addPopStateListener` property');
|
||||
}
|
||||
ui_web.urlStrategy = CustomUrlStrategy.fromJs(jsStrategy);
|
||||
}
|
||||
});
|
||||
registerHotRestartListener(() {
|
||||
jsSetUrlStrategy = null;
|
||||
});
|
||||
}
|
||||
|
||||
/// Whether to disable the font fallback system.
|
||||
///
|
||||
/// We need to disable font fallbacks for some framework tests because
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
// 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.
|
||||
|
||||
export 'navigation/history.dart';
|
||||
export 'navigation/js_url_strategy.dart';
|
||||
export 'navigation/url_strategy.dart';
|
||||
@ -0,0 +1,87 @@
|
||||
// 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.
|
||||
|
||||
@JS()
|
||||
library js_url_strategy;
|
||||
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../dom.dart';
|
||||
|
||||
typedef _PathGetter = String Function();
|
||||
|
||||
typedef _StateGetter = Object? Function();
|
||||
|
||||
typedef _AddPopStateListener = ui.VoidCallback Function(DartDomEventListener);
|
||||
|
||||
typedef _StringToString = String Function(String);
|
||||
|
||||
typedef _StateOperation = void Function(
|
||||
Object? state, String title, String url);
|
||||
|
||||
typedef _HistoryMove = Future<void> Function(double count);
|
||||
|
||||
/// 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
|
||||
@staticInterop
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
extension JsUrlStrategyExtension on JsUrlStrategy {
|
||||
/// Adds a listener to the `popstate` event and returns a function that, when
|
||||
/// invoked, removes the listener.
|
||||
external ui.VoidCallback addPopStateListener(DartDomEventListener 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 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
|
||||
external Future<void> go(double count);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
// 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 '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';
|
||||
|
||||
/// 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.
|
||||
CustomUrlStrategy.fromJs(this.delegate);
|
||||
|
||||
final JsUrlStrategy delegate;
|
||||
|
||||
@override
|
||||
ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) =>
|
||||
delegate.addPopStateListener(allowInterop((DomEvent event) =>
|
||||
fn((event as DomPopStateEvent).state)
|
||||
));
|
||||
|
||||
@override
|
||||
String getPath() => delegate.getPath();
|
||||
|
||||
@override
|
||||
Object? getState() => delegate.getState();
|
||||
|
||||
@override
|
||||
String prepareExternalUrl(String internalUrl) =>
|
||||
delegate.prepareExternalUrl(internalUrl);
|
||||
|
||||
@override
|
||||
void pushState(Object? state, String title, String url) =>
|
||||
delegate.pushState(state, title, url);
|
||||
|
||||
@override
|
||||
void replaceState(Object? state, String title, String url) =>
|
||||
delegate.replaceState(state, title, url);
|
||||
|
||||
@override
|
||||
Future<void> go(int count) => delegate.go(count.toDouble());
|
||||
}
|
||||
@ -30,7 +30,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 implements ui_web.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, ''));
|
||||
|
||||
@ -16,6 +16,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
import '../engine.dart' show DimensionsProvider, registerHotRestartListener, renderer;
|
||||
import 'dom.dart';
|
||||
import 'navigation/history.dart';
|
||||
import 'navigation/js_url_strategy.dart';
|
||||
import 'platform_dispatcher.dart';
|
||||
import 'services.dart';
|
||||
import 'util.dart';
|
||||
@ -323,6 +324,16 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
|
||||
ui.Size? webOnlyDebugPhysicalSizeOverride;
|
||||
}
|
||||
|
||||
typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?);
|
||||
|
||||
/// A JavaScript hook to customize the URL strategy of a Flutter app.
|
||||
//
|
||||
// DO NOT CHANGE THE JS NAME, IT IS PUBLIC API AT THIS POINT.
|
||||
//
|
||||
// TODO(mdebbar): Add integration test https://github.com/flutter/flutter/issues/66852
|
||||
@JS('_flutter_web_set_location_strategy')
|
||||
external set jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy);
|
||||
|
||||
/// The Web implementation of [ui.SingletonFlutterWindow].
|
||||
class EngineSingletonFlutterWindow extends EngineFlutterWindow {
|
||||
EngineSingletonFlutterWindow(
|
||||
|
||||
@ -82,7 +82,11 @@ typedef PopStateListener = void Function(Object? state);
|
||||
///
|
||||
/// By default, the [HashUrlStrategy] subclass is used if the app doesn't
|
||||
/// specify one.
|
||||
abstract interface class UrlStrategy {
|
||||
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);
|
||||
@ -135,7 +139,7 @@ abstract interface class UrlStrategy {
|
||||
/// // Somewhere before calling `runApp()` do:
|
||||
/// setUrlStrategy(const HashUrlStrategy());
|
||||
/// ```
|
||||
class HashUrlStrategy implements UrlStrategy {
|
||||
class HashUrlStrategy extends UrlStrategy {
|
||||
/// Creates an instance of [HashUrlStrategy].
|
||||
///
|
||||
/// The [PlatformLocation] parameter is useful for testing to mock out browser
|
||||
|
||||
@ -8,8 +8,9 @@ import 'package:quiver/testing/async.dart';
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart' show window;
|
||||
import 'package:ui/src/engine/dom.dart' show DomEvent, createDomPopStateEvent;
|
||||
import 'package:ui/src/engine/navigation/history.dart';
|
||||
import 'package:ui/src/engine/dom.dart'
|
||||
show DomEvent, createDomPopStateEvent;
|
||||
import 'package:ui/src/engine/navigation.dart';
|
||||
import 'package:ui/src/engine/services.dart';
|
||||
import 'package:ui/src/engine/test_embedding.dart';
|
||||
import 'package:ui/ui_web/src/ui_web.dart';
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:js_util' as js_util;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart' hide window;
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
import '../common/matchers.dart';
|
||||
import 'history_test.dart';
|
||||
@ -48,15 +48,27 @@ void testMain() {
|
||||
expect(window.viewId, 0);
|
||||
});
|
||||
|
||||
test('window.defaultRouteName should work with a custom url strategy', () async {
|
||||
const String path = '/initial';
|
||||
const Object state = <dynamic, dynamic>{'origin': true};
|
||||
|
||||
final _SampleUrlStrategy customStrategy = _SampleUrlStrategy(path, state);
|
||||
await window.debugInitializeHistory(customStrategy, useSingle: true);
|
||||
test('window.defaultRouteName should work with JsUrlStrategy', () async {
|
||||
dynamic state = <dynamic, dynamic>{};
|
||||
final JsUrlStrategy jsUrlStrategy = JsUrlStrategy(
|
||||
getPath: allowInterop(() => '/initial'),
|
||||
getState: allowInterop(() => state),
|
||||
addPopStateListener: allowInterop((DartDomEventListener listener) => allowInterop(() {})),
|
||||
prepareExternalUrl: allowInterop((String value) => ''),
|
||||
pushState: allowInterop((Object? newState, String title, String url) {
|
||||
expect(newState is Map, true);
|
||||
}),
|
||||
replaceState: allowInterop((Object? newState, String title, String url) {
|
||||
expect(newState is Map, true);
|
||||
state = newState;
|
||||
}),
|
||||
go: allowInterop(([double? delta]) async {
|
||||
expect(delta, -1);
|
||||
}));
|
||||
final CustomUrlStrategy strategy =
|
||||
CustomUrlStrategy.fromJs(jsUrlStrategy);
|
||||
await window.debugInitializeHistory(strategy, useSingle: true);
|
||||
expect(window.defaultRouteName, '/initial');
|
||||
// Also make sure that the custom url strategy was actually used.
|
||||
expect(customStrategy.wasUsed, isTrue);
|
||||
});
|
||||
|
||||
test('window.defaultRouteName should not change', () async {
|
||||
@ -429,12 +441,7 @@ void testMain() {
|
||||
|
||||
test('can disable location strategy', () async {
|
||||
// Disable URL strategy.
|
||||
expect(
|
||||
() {
|
||||
ui_web.urlStrategy = null;
|
||||
},
|
||||
returnsNormally,
|
||||
);
|
||||
expect(() => jsSetUrlStrategy(null), returnsNormally);
|
||||
// History should be initialized.
|
||||
expect(window.browserHistory, isNotNull);
|
||||
// But without a URL strategy.
|
||||
@ -448,35 +455,27 @@ void testMain() {
|
||||
expect(window.browserHistory.currentPath, '/');
|
||||
});
|
||||
|
||||
test('cannot set url strategy after it was initialized', () async {
|
||||
test('js interop throws on wrong type', () {
|
||||
expect(() => jsSetUrlStrategy(123), throwsA(anything));
|
||||
expect(() => jsSetUrlStrategy('foo'), throwsA(anything));
|
||||
expect(() => jsSetUrlStrategy(false), throwsA(anything));
|
||||
expect(() => jsSetUrlStrategy(<Object?>[]), throwsA(anything));
|
||||
});
|
||||
|
||||
test('cannot set url strategy after it is initialized', () async {
|
||||
final TestUrlStrategy testStrategy = TestUrlStrategy.fromEntry(
|
||||
const TestHistoryEntry('initial state', null, '/'),
|
||||
);
|
||||
await window.debugInitializeHistory(testStrategy, useSingle: true);
|
||||
|
||||
expect(
|
||||
() {
|
||||
ui_web.urlStrategy = null;
|
||||
},
|
||||
throwsA(isAssertionError),
|
||||
);
|
||||
expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError));
|
||||
});
|
||||
|
||||
test('cannot set url strategy more than once', () async {
|
||||
// First time is okay.
|
||||
expect(
|
||||
() {
|
||||
ui_web.urlStrategy = null;
|
||||
},
|
||||
returnsNormally,
|
||||
);
|
||||
expect(() => jsSetUrlStrategy(null), returnsNormally);
|
||||
// Second time is not allowed.
|
||||
expect(
|
||||
() {
|
||||
ui_web.urlStrategy = null;
|
||||
},
|
||||
throwsA(isAssertionError),
|
||||
);
|
||||
expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError));
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/77817
|
||||
@ -487,41 +486,10 @@ void testMain() {
|
||||
});
|
||||
}
|
||||
|
||||
class _SampleUrlStrategy implements ui_web.UrlStrategy {
|
||||
_SampleUrlStrategy(this._path, this._state);
|
||||
|
||||
final String _path;
|
||||
final Object? _state;
|
||||
|
||||
bool wasUsed = false;
|
||||
|
||||
@override
|
||||
String getPath() => _path;
|
||||
|
||||
@override
|
||||
Object? getState() => _state;
|
||||
|
||||
@override
|
||||
ui.VoidCallback addPopStateListener(DartDomEventListener listener) {
|
||||
wasUsed = true;
|
||||
return () {};
|
||||
}
|
||||
|
||||
@override
|
||||
String prepareExternalUrl(String value) => '';
|
||||
|
||||
@override
|
||||
void pushState(Object? newState, String title, String url) {
|
||||
wasUsed = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void replaceState(Object? newState, String title, String url) {
|
||||
wasUsed = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> go(int delta) async {
|
||||
wasUsed = true;
|
||||
}
|
||||
void jsSetUrlStrategy(dynamic strategy) {
|
||||
js_util.callMethod<void>(
|
||||
domWindow,
|
||||
'_flutter_web_set_location_strategy',
|
||||
<dynamic>[strategy],
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user