Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading