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
5 changes: 2 additions & 3 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvas_pool.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Expand Down Expand Up @@ -462,9 +463,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_renderer.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/history.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/js_url_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/url_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/history.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/clip.dart
Expand Down
5 changes: 2 additions & 3 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ part 'engine/alarm_clock.dart';
part 'engine/assets.dart';
part 'engine/bitmap_canvas.dart';
part 'engine/browser_detection.dart';
part 'engine/browser_location.dart';
part 'engine/canvaskit/canvas.dart';
part 'engine/canvaskit/canvaskit_canvas.dart';
part 'engine/canvaskit/canvaskit_api.dart';
Expand Down Expand Up @@ -62,9 +63,7 @@ part 'engine/dom_canvas.dart';
part 'engine/dom_renderer.dart';
part 'engine/engine_canvas.dart';
part 'engine/frame_reference.dart';
part 'engine/navigation/history.dart';
part 'engine/navigation/js_url_strategy.dart';
part 'engine/navigation/url_strategy.dart';
part 'engine/history.dart';
part 'engine/html/backdrop_filter.dart';
part 'engine/html/canvas.dart';
part 'engine/html/clip.dart';
Expand Down
211 changes: 211 additions & 0 deletions lib/web_ui/lib/src/engine/browser_location.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// 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.

// @dart = 2.10
part of engine;

// TODO(mdebbar): add other strategies.

// Some parts of this file were inspired/copied from the AngularDart router.

/// [LocationStrategy] is responsible for representing and reading route state
/// from the browser's URL.
///
/// At the moment, only one strategy is implemented: [HashLocationStrategy].
///
/// This is used by [BrowserHistory] to interact with browser history APIs.
abstract class LocationStrategy {
const LocationStrategy();

/// Subscribes to popstate events and returns a function that could be used to
/// unsubscribe from popstate events.
ui.VoidCallback onPopState(html.EventListener fn);

/// The active path in the browser history.
String get path;

/// The state of the current browser history entry.
dynamic get state;

/// 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.
void pushState(dynamic state, String title, String url);

/// Replace the currently active history entry.
void replaceState(dynamic state, String title, String url);

/// Go to the previous history entry.
Future<void> back({int count = 1});
}

/// This is an implementation of [LocationStrategy] that uses the browser URL's
/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
/// to represent its state.
///
/// In order to use this [LocationStrategy] for an app, it needs to be set in
/// [ui.window.locationStrategy]:
///
/// ```dart
/// import 'package:flutter_web/material.dart';
/// import 'package:flutter_web/ui.dart' as ui;
///
/// void main() {
/// ui.window.locationStrategy = const ui.HashLocationStrategy();
/// runApp(MyApp());
/// }
/// ```
class HashLocationStrategy extends LocationStrategy {
final PlatformLocation _platformLocation;

const HashLocationStrategy(
[this._platformLocation = const BrowserPlatformLocation()]);

@override
ui.VoidCallback onPopState(html.EventListener fn) {
_platformLocation.onPopState(fn);
return () => _platformLocation.offPopState(fn);
}

@override
String get path {
// the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty
String path = _platformLocation.hash ?? '';
assert(path.isEmpty || path.startsWith('#'));

// We don't want to return an empty string as a path. Instead we default to "/".
if (path.isEmpty || path == '#') {
return '/';
}
// At this point, we know [path] starts with "#" and isn't empty.
return path.substring(1);
}

@override
dynamic get state => _platformLocation.state;

@override
String prepareExternalUrl(String internalUrl) {
// It's convention that if the hash path is empty, we omit the `#`; however,
// if the empty URL is pushed it won't replace any existing fragment. So
// when the hash path is empty, we instead return the location's path and
// query.
return internalUrl.isEmpty
? '${_platformLocation.pathname}${_platformLocation.search}'
: '#$internalUrl';
}

@override
void pushState(dynamic state, String title, String url) {
_platformLocation.pushState(state, title, prepareExternalUrl(url));
}

@override
void replaceState(dynamic state, String title, String url) {
_platformLocation.replaceState(state, title, prepareExternalUrl(url));
}

@override
Future<void> back({int count = 1}) {
_platformLocation.back(count);
return _waitForPopState();
}

/// Waits until the next popstate event is fired.
///
/// This is useful for example to wait until the browser has handled the
/// `history.back` transition.
Future<void> _waitForPopState() {
final Completer<void> completer = Completer<void>();
late ui.VoidCallback unsubscribe;
unsubscribe = onPopState((_) {
unsubscribe();
completer.complete();
});
return completer.future;
}
}

/// [PlatformLocation] encapsulates all calls to DOM apis, which allows the
/// [LocationStrategy] classes to be platform agnostic and testable.
///
/// The [PlatformLocation] class is used directly by all implementations of
/// [LocationStrategy] when they need to interact with the DOM apis like
/// pushState, popState, etc...
abstract class PlatformLocation {
const PlatformLocation();

void onPopState(html.EventListener fn);
void offPopState(html.EventListener fn);

void onHashChange(html.EventListener fn);
void offHashChange(html.EventListener fn);

String get pathname;
String get search;
String? get hash;
dynamic get state;

void pushState(dynamic state, String title, String url);
void replaceState(dynamic state, String title, String url);
void back(int count);
}

/// An implementation of [PlatformLocation] for the browser.
class BrowserPlatformLocation extends PlatformLocation {
html.Location get _location => html.window.location;
html.History get _history => html.window.history;

const BrowserPlatformLocation();

@override
void onPopState(html.EventListener fn) {
html.window.addEventListener('popstate', fn);
}

@override
void offPopState(html.EventListener fn) {
html.window.removeEventListener('popstate', fn);
}

@override
void onHashChange(html.EventListener fn) {
html.window.addEventListener('hashchange', fn);
}

@override
void offHashChange(html.EventListener fn) {
html.window.removeEventListener('hashchange', fn);
}

@override
String get pathname => _location.pathname!;

@override
String get search => _location.search!;

@override
String get hash => _location.hash;

@override
dynamic get state => _history.state;

@override
void pushState(dynamic state, String title, String url) {
_history.pushState(state, title, url);
}

@override
void replaceState(dynamic state, String title, String url) {
_history.replaceState(state, title, url);
}

@override
void back(int count) {
_history.go(-count);
}
}
Loading