Skip to content

Commit

Permalink
Revert "[web] Remove the JS API for url strategy" (#42468)
Browse files Browse the repository at this point in the history
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)
  �  �  
  ```
  • Loading branch information
jonahwilliams committed Jun 1, 2023
1 parent e83bcf8 commit 6db2f3e
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 76 deletions.
6 changes: 6 additions & 0 deletions ci/licenses_golden/licenses_flutter
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/src/engine.dart
Expand Up @@ -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';
Expand Down
25 changes: 25 additions & 0 deletions lib/web_ui/lib/src/engine/initialization.dart
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions lib/web_ui/lib/src/engine/navigation.dart
@@ -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';
87 changes: 87 additions & 0 deletions lib/web_ui/lib/src/engine/navigation/js_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);
}
48 changes: 48 additions & 0 deletions lib/web_ui/lib/src/engine/navigation/url_strategy.dart
@@ -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());
}
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/test_embedding.dart
Expand Up @@ -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, ''));
Expand Down
11 changes: 11 additions & 0 deletions lib/web_ui/lib/src/engine/window.dart
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
Expand Down
8 changes: 6 additions & 2 deletions lib/web_ui/lib/ui_web/src/ui_web/navigation/url_strategy.dart
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions lib/web_ui/test/engine/history_test.dart
Expand Up @@ -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';
Expand Down

0 comments on commit 6db2f3e

Please sign in to comment.