Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 6db2f3e

Browse files
author
Jonah Williams
authored
Revert "[web] Remove the JS API for url strategy" (#42468)
Reverts #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) � � ```
1 parent e83bcf8 commit 6db2f3e

File tree

11 files changed

+235
-76
lines changed

11 files changed

+235
-76
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,7 +1975,10 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart
19751975
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart + ../../../flutter/LICENSE
19761976
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart + ../../../flutter/LICENSE
19771977
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart + ../../../flutter/LICENSE
1978+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation.dart + ../../../flutter/LICENSE
19781979
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart + ../../../flutter/LICENSE
1980+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart + ../../../flutter/LICENSE
1981+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/url_strategy.dart + ../../../flutter/LICENSE
19791982
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart + ../../../flutter/LICENSE
19801983
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart + ../../../flutter/LICENSE
19811984
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
46364639
FILE: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart
46374640
FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart
46384641
FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart
4642+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation.dart
46394643
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart
4644+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart
4645+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/url_strategy.dart
46404646
FILE: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart
46414647
FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart
46424648
FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart

lib/web_ui/lib/src/engine.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export 'engine/key_map.g.dart';
113113
export 'engine/keyboard_binding.dart';
114114
export 'engine/mouse_cursor.dart';
115115
export 'engine/navigation/history.dart';
116+
export 'engine/navigation/js_url_strategy.dart';
117+
export 'engine/navigation/url_strategy.dart';
116118
export 'engine/noto_font.dart';
117119
export 'engine/onscreen_logging.dart';
118120
export 'engine/picture.dart';

lib/web_ui/lib/src/engine/initialization.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:js_interop';
88

99
import 'package:ui/src/engine.dart';
1010
import 'package:ui/ui.dart' as ui;
11+
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
1112
import 'package:web_test_fonts/web_test_fonts.dart';
1213

1314
/// The mode the app is running in.
@@ -132,6 +133,10 @@ Future<void> initializeEngineServices({
132133
// Store `jsConfiguration` so user settings are available to the engine.
133134
configuration.setUserConfiguration(jsConfiguration);
134135

136+
// Setup the hook that allows users to customize URL strategy before running
137+
// the app.
138+
_addUrlStrategyListener();
139+
135140
// Called by the Web runtime just before hot restarting the app.
136141
//
137142
// This extension cleans up resources that are registered with browser's
@@ -258,6 +263,26 @@ Future<void> _downloadAssetFonts() async {
258263
}
259264
}
260265

266+
void _addUrlStrategyListener() {
267+
jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) {
268+
if (jsStrategy == null) {
269+
ui_web.urlStrategy = null;
270+
} else {
271+
// Because `JSStrategy` could be anything, we check for the
272+
// `addPopStateListener` property and throw if it is missing.
273+
if (!hasJsProperty(jsStrategy, 'addPopStateListener')) {
274+
throw StateError(
275+
'Unexpected JsUrlStrategy: $jsStrategy is missing '
276+
'`addPopStateListener` property');
277+
}
278+
ui_web.urlStrategy = CustomUrlStrategy.fromJs(jsStrategy);
279+
}
280+
});
281+
registerHotRestartListener(() {
282+
jsSetUrlStrategy = null;
283+
});
284+
}
285+
261286
/// Whether to disable the font fallback system.
262287
///
263288
/// We need to disable font fallbacks for some framework tests because
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
export 'navigation/history.dart';
6+
export 'navigation/js_url_strategy.dart';
7+
export 'navigation/url_strategy.dart';
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@JS()
6+
library js_url_strategy;
7+
8+
import 'dart:js_interop';
9+
10+
import 'package:ui/ui.dart' as ui;
11+
12+
import '../dom.dart';
13+
14+
typedef _PathGetter = String Function();
15+
16+
typedef _StateGetter = Object? Function();
17+
18+
typedef _AddPopStateListener = ui.VoidCallback Function(DartDomEventListener);
19+
20+
typedef _StringToString = String Function(String);
21+
22+
typedef _StateOperation = void Function(
23+
Object? state, String title, String url);
24+
25+
typedef _HistoryMove = Future<void> Function(double count);
26+
27+
/// The JavaScript representation of a URL strategy.
28+
///
29+
/// This is used to pass URL strategy implementations across a JS-interop
30+
/// bridge from the app to the engine.
31+
@JS()
32+
@anonymous
33+
@staticInterop
34+
abstract class JsUrlStrategy {
35+
/// Creates an instance of [JsUrlStrategy] from a bag of URL strategy
36+
/// functions.
37+
external factory JsUrlStrategy({
38+
required _PathGetter getPath,
39+
required _StateGetter getState,
40+
required _AddPopStateListener addPopStateListener,
41+
required _StringToString prepareExternalUrl,
42+
required _StateOperation pushState,
43+
required _StateOperation replaceState,
44+
required _HistoryMove go,
45+
});
46+
}
47+
48+
extension JsUrlStrategyExtension on JsUrlStrategy {
49+
/// Adds a listener to the `popstate` event and returns a function that, when
50+
/// invoked, removes the listener.
51+
external ui.VoidCallback addPopStateListener(DartDomEventListener fn);
52+
53+
/// Returns the active path in the browser.
54+
external String getPath();
55+
56+
/// Returns the history state in the browser.
57+
///
58+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
59+
external Object? getState();
60+
61+
/// Given a path that's internal to the app, create the external url that
62+
/// will be used in the browser.
63+
external String prepareExternalUrl(String internalUrl);
64+
65+
/// Push a new history entry.
66+
///
67+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
68+
external void pushState(Object? state, String title, String url);
69+
70+
/// Replace the currently active history entry.
71+
///
72+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState
73+
external void replaceState(Object? state, String title, String url);
74+
75+
/// Moves forwards or backwards through the history stack.
76+
///
77+
/// A negative [count] value causes a backward move in the history stack. And
78+
/// a positive [count] value causs a forward move.
79+
///
80+
/// Examples:
81+
///
82+
/// * `go(-2)` moves back 2 steps in history.
83+
/// * `go(3)` moves forward 3 steps in hisotry.
84+
///
85+
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go
86+
external Future<void> go(double count);
87+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:ui/ui.dart' as ui;
8+
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
9+
10+
import '../dom.dart';
11+
import '../safe_browser_api.dart';
12+
import 'js_url_strategy.dart';
13+
14+
/// Wraps a custom implementation of [ui_web.UrlStrategy] that was previously converted
15+
/// to a [JsUrlStrategy].
16+
class CustomUrlStrategy extends ui_web.UrlStrategy {
17+
/// Wraps the [delegate] in a [CustomUrlStrategy] instance.
18+
CustomUrlStrategy.fromJs(this.delegate);
19+
20+
final JsUrlStrategy delegate;
21+
22+
@override
23+
ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) =>
24+
delegate.addPopStateListener(allowInterop((DomEvent event) =>
25+
fn((event as DomPopStateEvent).state)
26+
));
27+
28+
@override
29+
String getPath() => delegate.getPath();
30+
31+
@override
32+
Object? getState() => delegate.getState();
33+
34+
@override
35+
String prepareExternalUrl(String internalUrl) =>
36+
delegate.prepareExternalUrl(internalUrl);
37+
38+
@override
39+
void pushState(Object? state, String title, String url) =>
40+
delegate.pushState(state, title, url);
41+
42+
@override
43+
void replaceState(Object? state, String title, String url) =>
44+
delegate.replaceState(state, title, url);
45+
46+
@override
47+
Future<void> go(int count) => delegate.go(count.toDouble());
48+
}

lib/web_ui/lib/src/engine/test_embedding.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class TestHistoryEntry {
3030
///
3131
/// It keeps a list of history entries and event listeners in memory and
3232
/// manipulates them in order to achieve the desired functionality.
33-
class TestUrlStrategy implements ui_web.UrlStrategy {
33+
class TestUrlStrategy extends ui_web.UrlStrategy {
3434
/// Creates a instance of [TestUrlStrategy] with an empty string as the
3535
/// path.
3636
factory TestUrlStrategy() => TestUrlStrategy.fromEntry(const TestHistoryEntry(null, null, ''));

lib/web_ui/lib/src/engine/window.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
1616
import '../engine.dart' show DimensionsProvider, registerHotRestartListener, renderer;
1717
import 'dom.dart';
1818
import 'navigation/history.dart';
19+
import 'navigation/js_url_strategy.dart';
1920
import 'platform_dispatcher.dart';
2021
import 'services.dart';
2122
import 'util.dart';
@@ -323,6 +324,16 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
323324
ui.Size? webOnlyDebugPhysicalSizeOverride;
324325
}
325326

327+
typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?);
328+
329+
/// A JavaScript hook to customize the URL strategy of a Flutter app.
330+
//
331+
// DO NOT CHANGE THE JS NAME, IT IS PUBLIC API AT THIS POINT.
332+
//
333+
// TODO(mdebbar): Add integration test https://github.com/flutter/flutter/issues/66852
334+
@JS('_flutter_web_set_location_strategy')
335+
external set jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy);
336+
326337
/// The Web implementation of [ui.SingletonFlutterWindow].
327338
class EngineSingletonFlutterWindow extends EngineFlutterWindow {
328339
EngineSingletonFlutterWindow(

lib/web_ui/lib/ui_web/src/ui_web/navigation/url_strategy.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ typedef PopStateListener = void Function(Object? state);
8282
///
8383
/// By default, the [HashUrlStrategy] subclass is used if the app doesn't
8484
/// specify one.
85-
abstract interface class UrlStrategy {
85+
abstract class UrlStrategy {
86+
/// Abstract const constructor. This constructor enables subclasses to provide
87+
/// const constructors so that they can be used in const expressions.
88+
const UrlStrategy();
89+
8690
/// Adds a listener to the `popstate` event and returns a function that, when
8791
/// invoked, removes the listener.
8892
ui.VoidCallback addPopStateListener(PopStateListener fn);
@@ -135,7 +139,7 @@ abstract interface class UrlStrategy {
135139
/// // Somewhere before calling `runApp()` do:
136140
/// setUrlStrategy(const HashUrlStrategy());
137141
/// ```
138-
class HashUrlStrategy implements UrlStrategy {
142+
class HashUrlStrategy extends UrlStrategy {
139143
/// Creates an instance of [HashUrlStrategy].
140144
///
141145
/// The [PlatformLocation] parameter is useful for testing to mock out browser

lib/web_ui/test/engine/history_test.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import 'package:quiver/testing/async.dart';
88
import 'package:test/bootstrap/browser.dart';
99
import 'package:test/test.dart';
1010
import 'package:ui/src/engine.dart' show window;
11-
import 'package:ui/src/engine/dom.dart' show DomEvent, createDomPopStateEvent;
12-
import 'package:ui/src/engine/navigation/history.dart';
11+
import 'package:ui/src/engine/dom.dart'
12+
show DomEvent, createDomPopStateEvent;
13+
import 'package:ui/src/engine/navigation.dart';
1314
import 'package:ui/src/engine/services.dart';
1415
import 'package:ui/src/engine/test_embedding.dart';
1516
import 'package:ui/ui_web/src/ui_web.dart';

0 commit comments

Comments
 (0)