mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Make HotRestartCacheHandler standalone (flutter/engine#46906)
- `EmbeddingStrategy` shouldn't own the creation of `HotRestartCacheHandler`. - Simplify `HotRestartCacheHandler`'s JS-interop by using a `JSArray` directly instead of going through a Dart `List`.
This commit is contained in:
parent
d11e7434ff
commit
b743fa9f2e
@ -3591,3 +3591,10 @@ extension DomFinalizationRegistryExtension on DomFinalizationRegistry {
|
||||
/// Whether the current browser supports `FinalizationRegistry`.
|
||||
bool browserSupportsFinalizationRegistry =
|
||||
_finalizationRegistryConstructor != null;
|
||||
|
||||
@JS()
|
||||
@staticInterop
|
||||
extension JSArrayExtension on JSArray {
|
||||
external void push(JSAny value);
|
||||
external JSNumber get length;
|
||||
}
|
||||
|
||||
@ -4,13 +4,14 @@
|
||||
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
|
||||
import '../hot_restart_cache_handler.dart' show registerElementForCleanup;
|
||||
import 'embedding_strategy.dart';
|
||||
|
||||
/// An [EmbeddingStrategy] that renders flutter inside a target host element.
|
||||
///
|
||||
/// This strategy attempts to minimize DOM modifications outside of the host
|
||||
/// element, so it plays "nice" with other web frameworks.
|
||||
class CustomElementEmbeddingStrategy extends EmbeddingStrategy {
|
||||
class CustomElementEmbeddingStrategy implements EmbeddingStrategy {
|
||||
/// Creates a [CustomElementEmbeddingStrategy] to embed a Flutter view into [_hostElement].
|
||||
CustomElementEmbeddingStrategy(this._hostElement) {
|
||||
_hostElement.clearChildren();
|
||||
|
||||
@ -2,10 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/view_embedder/hot_restart_cache_handler.dart';
|
||||
|
||||
import 'custom_element_embedding_strategy.dart';
|
||||
import 'full_page_embedding_strategy.dart';
|
||||
@ -21,14 +18,6 @@ import 'full_page_embedding_strategy.dart';
|
||||
/// element, provided by the web app programmer through the engine
|
||||
/// initialization.
|
||||
abstract class EmbeddingStrategy {
|
||||
EmbeddingStrategy() {
|
||||
// Initialize code to handle hot-restart (debug only).
|
||||
assert(() {
|
||||
_hotRestartCache = HotRestartCacheHandler();
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
factory EmbeddingStrategy.create({DomElement? hostElement}) {
|
||||
if (hostElement != null) {
|
||||
return CustomElementEmbeddingStrategy(hostElement);
|
||||
@ -37,9 +26,6 @@ abstract class EmbeddingStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps a list of elements to be cleaned up at hot-restart.
|
||||
HotRestartCacheHandler? _hotRestartCache;
|
||||
|
||||
void initialize({
|
||||
Map<String, String>? hostElementAttributes,
|
||||
});
|
||||
@ -49,10 +35,4 @@ abstract class EmbeddingStrategy {
|
||||
|
||||
/// Attaches the resourceHost element into the hostElement.
|
||||
void attachResourcesHost(DomElement resourceHost, {DomElement? nextTo});
|
||||
|
||||
/// Registers a [DomElement] to be cleaned up after hot restart.
|
||||
@mustCallSuper
|
||||
void registerElementForCleanup(DomElement element) {
|
||||
_hotRestartCache?.registerElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,14 @@
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/util.dart' show setElementStyle;
|
||||
|
||||
import '../hot_restart_cache_handler.dart' show registerElementForCleanup;
|
||||
import 'embedding_strategy.dart';
|
||||
|
||||
/// An [EmbeddingStrategy] that takes over the whole web page.
|
||||
///
|
||||
/// This strategy takes over the <body> element, modifies the viewport meta-tag,
|
||||
/// and ensures that the root Flutter view covers the whole screen.
|
||||
class FullPageEmbeddingStrategy extends EmbeddingStrategy {
|
||||
class FullPageEmbeddingStrategy implements EmbeddingStrategy {
|
||||
@override
|
||||
void initialize({
|
||||
Map<String, String>? hostElementAttributes,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import '../dom.dart';
|
||||
@ -12,59 +13,61 @@ import '../dom.dart';
|
||||
/// to clear. Delay removal of old visible state to make the
|
||||
/// transition appear smooth.
|
||||
@JS('window.__flutterState')
|
||||
external JSArray? get _hotRestartStore;
|
||||
List<Object?>? get hotRestartStore =>
|
||||
_hotRestartStore?.toObjectShallow as List<Object?>?;
|
||||
external JSArray? get _jsHotRestartStore;
|
||||
|
||||
@JS('window.__flutterState')
|
||||
external set _hotRestartStore(JSArray? nodes);
|
||||
set hotRestartStore(List<Object?>? nodes) =>
|
||||
_hotRestartStore = nodes?.toJSAnyShallow as JSArray?;
|
||||
external set _jsHotRestartStore(JSArray? nodes);
|
||||
|
||||
/// Handles [DomElement]s that need to be removed after a hot-restart.
|
||||
///
|
||||
/// Elements are stored in an [_elements] list, backed by a global JS variable,
|
||||
/// named [defaultCacheName].
|
||||
/// This class shouldn't be used directly. It's only made public for testing
|
||||
/// purposes. Instead, use [registerElementForCleanup].
|
||||
///
|
||||
/// Elements are stored in a [JSArray] stored globally at `window.__flutterState`.
|
||||
///
|
||||
/// When the app hot-restarts (and a new instance of this class is created),
|
||||
/// everything in [_elements] is removed from the DOM.
|
||||
/// all elements in the global [JSArray] is removed from the DOM.
|
||||
class HotRestartCacheHandler {
|
||||
@visibleForTesting
|
||||
HotRestartCacheHandler() {
|
||||
if (_elements.isNotEmpty) {
|
||||
_resetHotRestartStore();
|
||||
}
|
||||
|
||||
/// Removes every element that was registered prior to the hot-restart from
|
||||
/// the DOM.
|
||||
void _resetHotRestartStore() {
|
||||
final JSArray? jsStore = _jsHotRestartStore;
|
||||
|
||||
if (jsStore != null) {
|
||||
// We are in a post hot-restart world, clear the elements now.
|
||||
_clearAllElements();
|
||||
}
|
||||
}
|
||||
|
||||
/// The js-interop layer backing [_elements].
|
||||
///
|
||||
/// Elements are stored in a JS global array named [defaultCacheName].
|
||||
late List<Object?>? _jsElements;
|
||||
|
||||
/// The elements that need to be cleaned up after hot-restart.
|
||||
List<Object?> get _elements {
|
||||
_jsElements = hotRestartStore;
|
||||
if (_jsElements == null) {
|
||||
_jsElements = <Object>[];
|
||||
hotRestartStore = _jsElements;
|
||||
}
|
||||
return _jsElements!;
|
||||
}
|
||||
|
||||
/// Removes every element from [_elements] and empties the list.
|
||||
void _clearAllElements() {
|
||||
for (final Object? element in _elements) {
|
||||
if (element is DomElement) {
|
||||
element.remove();
|
||||
final List<Object?> store = jsStore.toObjectShallow as List<Object?>;
|
||||
for (final Object? element in store) {
|
||||
if (element != null) {
|
||||
(element as DomElement).remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
hotRestartStore = <Object>[];
|
||||
_jsHotRestartStore = JSArray();
|
||||
}
|
||||
|
||||
/// Registers a [DomElement] to be removed after hot-restart.
|
||||
@visibleForTesting
|
||||
void registerElement(DomElement element) {
|
||||
final List<Object?> elements = _elements;
|
||||
elements.add(element);
|
||||
hotRestartStore = elements;
|
||||
_jsHotRestartStore!.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
final HotRestartCacheHandler? _hotRestartCache = () {
|
||||
// In release mode, we don't need a hot restart cache, so we leave it null.
|
||||
HotRestartCacheHandler? cache;
|
||||
assert(() {
|
||||
cache = HotRestartCacheHandler();
|
||||
return true;
|
||||
}());
|
||||
return cache;
|
||||
}();
|
||||
|
||||
/// Registers a [DomElement] to be cleaned up after hot restart.
|
||||
void registerElementForCleanup(DomElement element) {
|
||||
_hotRestartCache?.registerElement(element);
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/view_embedder/embedding_strategy/custom_element_embedding_strategy.dart';
|
||||
import 'package:ui/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart';
|
||||
import 'package:ui/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart';
|
||||
import 'package:ui/src/engine/view_embedder/hot_restart_cache_handler.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => doTests);
|
||||
@ -35,24 +34,4 @@ void doTests() {
|
||||
expect(strategy, isA<CustomElementEmbeddingStrategy>());
|
||||
});
|
||||
});
|
||||
|
||||
group('registerElementForCleanup', () {
|
||||
test('stores elements in a global domCache', () async {
|
||||
final EmbeddingStrategy strategy = EmbeddingStrategy.create();
|
||||
|
||||
final DomElement toBeCached = createDomElement('some-element-to-cache');
|
||||
final DomElement other = createDomElement('other-element-to-cache');
|
||||
final DomElement another = createDomElement('another-element-to-cache');
|
||||
|
||||
strategy.registerElementForCleanup(toBeCached);
|
||||
strategy.registerElementForCleanup(other);
|
||||
strategy.registerElementForCleanup(another);
|
||||
|
||||
final List<Object?> cache = hotRestartStore!;
|
||||
|
||||
expect(cache, hasLength(3));
|
||||
expect(cache.first, toBeCached);
|
||||
expect(cache.last, another);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -5,31 +5,58 @@
|
||||
@TestOn('browser')
|
||||
library;
|
||||
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/view_embedder/hot_restart_cache_handler.dart';
|
||||
|
||||
@JS('window.__flutterState')
|
||||
external JSArray? get _jsHotRestartStore;
|
||||
|
||||
@JS('window.__flutterState')
|
||||
external set _jsHotRestartStore(JSArray? nodes);
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => doTests);
|
||||
}
|
||||
|
||||
void doTests() {
|
||||
group('Constructor', () {
|
||||
test('Creates a cache in the JS environment', () async {
|
||||
final HotRestartCacheHandler cache = HotRestartCacheHandler();
|
||||
tearDown(() {
|
||||
_jsHotRestartStore = null;
|
||||
});
|
||||
|
||||
expect(cache, isNotNull);
|
||||
group('registerElementForCleanup', () {
|
||||
test('stores elements in a global cache', () async {
|
||||
final DomElement toBeCached = createDomElement('some-element-to-cache');
|
||||
final DomElement other = createDomElement('other-element-to-cache');
|
||||
final DomElement another = createDomElement('another-element-to-cache');
|
||||
|
||||
final List<Object?>? domCache = hotRestartStore;
|
||||
registerElementForCleanup(toBeCached);
|
||||
registerElementForCleanup(other);
|
||||
registerElementForCleanup(another);
|
||||
|
||||
expect(domCache, isNotNull);
|
||||
expect(domCache, isEmpty);
|
||||
expect(_jsHotRestartStore!.toDart, <DomElement>[
|
||||
toBeCached,
|
||||
other,
|
||||
another,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('registerElement', () {
|
||||
HotRestartCacheHandler? cache;
|
||||
group('HotRestartCacheHandler Constructor', () {
|
||||
test('Creates a cache in the JS environment', () async {
|
||||
HotRestartCacheHandler();
|
||||
|
||||
expect(_jsHotRestartStore, isNotNull);
|
||||
// For dart2wasm, we have to check the length this way.
|
||||
expect(_jsHotRestartStore!.length, 0.toJS);
|
||||
});
|
||||
});
|
||||
|
||||
group('HotRestartCacheHandler.registerElement', () {
|
||||
late HotRestartCacheHandler cache;
|
||||
|
||||
setUp(() {
|
||||
cache = HotRestartCacheHandler();
|
||||
@ -37,22 +64,18 @@ void doTests() {
|
||||
|
||||
test('Registers an element in the DOM cache', () async {
|
||||
final DomElement element = createDomElement('for-test');
|
||||
cache!.registerElement(element);
|
||||
cache.registerElement(element);
|
||||
|
||||
final List<Object?>? domCache = hotRestartStore;
|
||||
expect(domCache, hasLength(1));
|
||||
expect(domCache!.last, element);
|
||||
expect(_jsHotRestartStore!.toDart, <DomElement>[element]);
|
||||
});
|
||||
|
||||
test('Registers elements in the DOM cache', () async {
|
||||
final DomElement element = createDomElement('for-test');
|
||||
domDocument.body!.append(element);
|
||||
|
||||
cache!.registerElement(element);
|
||||
cache.registerElement(element);
|
||||
|
||||
final List<Object?>? domCache = hotRestartStore;
|
||||
expect(domCache, hasLength(1));
|
||||
expect(domCache!.last, element);
|
||||
expect(_jsHotRestartStore!.toDart, <DomElement>[element]);
|
||||
});
|
||||
|
||||
test('Clears registered elements from the DOM and the cache upon restart',
|
||||
@ -62,7 +85,7 @@ void doTests() {
|
||||
domDocument.body!.append(element);
|
||||
domDocument.body!.append(element2);
|
||||
|
||||
cache!.registerElement(element);
|
||||
cache.registerElement(element);
|
||||
|
||||
expect(element.isConnected, isTrue);
|
||||
expect(element2.isConnected, isTrue);
|
||||
@ -70,8 +93,8 @@ void doTests() {
|
||||
// Simulate a hot restart...
|
||||
cache = HotRestartCacheHandler();
|
||||
|
||||
final List<Object?>? domCache = hotRestartStore;
|
||||
expect(domCache, hasLength(0));
|
||||
// For dart2wasm, we have to check the length this way.
|
||||
expect(_jsHotRestartStore!.length, 0.toJS);
|
||||
expect(element.isConnected, isFalse); // Removed
|
||||
expect(element2.isConnected, isTrue);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user