Skip to content

Commit 278a040

Browse files
rileyporterCommit Bot
authored and
Commit Bot
committed
Add WeakReference and Finalizer patches for ddc and dart2js
Routes implementation to JavaScript WeakRef and FinalizerRegistry APIs using JS foreign function calls. Uses Wrapper names for the Dart library to avoid issues with DDC. Bug: #47775, #47776 Change-Id: Iad82bd83ac10c666d08a2c042a8ed6109b8b58c8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/229180 Reviewed-by: Stephen Adams <[email protected]> Reviewed-by: Sigmund Cherem <[email protected]> Reviewed-by: Nicholas Shahan <[email protected]> Commit-Queue: Riley Porter <[email protected]>
1 parent 33e1740 commit 278a040

File tree

8 files changed

+209
-7
lines changed

8 files changed

+209
-7
lines changed

sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import 'dart:_js_helper'
1717
Primitives,
1818
PrivateSymbol,
1919
quoteStringForRegExp,
20-
undefined;
20+
undefined,
21+
wrapZoneUnaryCallback;
2122
import 'dart:_runtime' as dart;
2223
import 'dart:_foreign_helper' show JS, JSExportName;
2324
import 'dart:_native_typed_data' show NativeUint8List;
@@ -165,19 +166,55 @@ class Expando<T extends Object> {
165166
}
166167
}
167168

169+
// Patch for WeakReference implementation.
168170
@patch
169171
class WeakReference<T extends Object> {
170172
@patch
171173
factory WeakReference(T object) {
172-
throw UnimplementedError("WeakReference");
174+
return _WeakReferenceWrapper<T>(object);
173175
}
174176
}
175177

178+
class _WeakReferenceWrapper<T extends Object> implements WeakReference<T> {
179+
final Object _weakRef;
180+
181+
_WeakReferenceWrapper(T object)
182+
: _weakRef = JS('!', 'new WeakRef(#)', object);
183+
184+
T? get target {
185+
var target = JS<T?>('', '#.deref()', _weakRef);
186+
// Coerce to null if JavaScript returns undefined.
187+
if (JS<bool>('!', 'target === void 0')) return null;
188+
return target;
189+
}
190+
}
191+
192+
// Patch for Finalizer implementation.
176193
@patch
177194
class Finalizer<T> {
178195
@patch
179196
factory Finalizer(void Function(T) object) {
180-
throw UnimplementedError("Finalizer");
197+
return _FinalizationRegistryWrapper<T>(object);
198+
}
199+
}
200+
201+
class _FinalizationRegistryWrapper<T> implements Finalizer<T> {
202+
final Object _registry;
203+
204+
_FinalizationRegistryWrapper(void Function(T) callback)
205+
: _registry = JS('!', 'new FinalizationRegistry(#)',
206+
wrapZoneUnaryCallback(callback));
207+
208+
void attach(Object value, T token, {Object? detach}) {
209+
if (detach != null) {
210+
JS('', '#.register(#, #, #)', _registry, value, token, detach);
211+
} else {
212+
JS('', '#.register(#, #)', _registry, value, token);
213+
}
214+
}
215+
216+
void detach(Object detachToken) {
217+
JS('', '#.unregister(#)', _registry, detachToken);
181218
}
182219
}
183220

sdk/lib/_internal/js_dev_runtime/private/js_helper.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
library dart._js_helper;
66

7+
import 'dart:async' show Zone;
78
import 'dart:collection';
89

910
import 'dart:_foreign_helper' show JS, JSExportName;
@@ -828,3 +829,11 @@ void assertInterop(Object? value) {
828829
/// Like [assertInterop], except iterates over a list of arguments
829830
/// non-recursively.
830831
void assertInteropArgs(List<Object?> args) => args.forEach(assertInterop);
832+
833+
/// Wraps the given [callback] within the current Zone.
834+
void Function(T)? wrapZoneUnaryCallback<T>(void Function(T)? callback) {
835+
// For performance reasons avoid wrapping if we are in the root zone.
836+
if (Zone.current == Zone.root) return callback;
837+
if (callback == null) return null;
838+
return Zone.current.bindUnaryCallbackGuarded(callback);
839+
}

sdk/lib/_internal/js_runtime/lib/core_patch.dart

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'dart:_js_helper'
1313
checkInt,
1414
Closure,
1515
ConstantMap,
16+
convertDartClosureToJS,
1617
getRuntimeType,
1718
JsLinkedHashMap,
1819
jsonEncodeNative,
@@ -23,7 +24,8 @@ import 'dart:_js_helper'
2324
quoteStringForRegExp,
2425
getTraceFromException,
2526
RuntimeError,
26-
wrapException;
27+
wrapException,
28+
wrapZoneUnaryCallback;
2729

2830
import 'dart:_foreign_helper' show JS;
2931
import 'dart:_native_typed_data' show NativeUint8List;
@@ -117,19 +119,49 @@ class Expando<T extends Object> {
117119
}
118120
}
119121

122+
// Patch for WeakReference implementation.
120123
@patch
121124
class WeakReference<T extends Object> {
122125
@patch
123126
factory WeakReference(T object) {
124-
throw UnimplementedError("WeakReference");
127+
return _WeakReferenceWrapper<T>(object);
125128
}
126129
}
127130

131+
class _WeakReferenceWrapper<T extends Object> implements WeakReference<T> {
132+
final Object _weakRef;
133+
134+
_WeakReferenceWrapper(T object) : _weakRef = JS('', 'new WeakRef(#)', object);
135+
136+
T? get target => JS('', '#.deref()', _weakRef);
137+
}
138+
139+
// Patch for Finalizer implementation.
128140
@patch
129141
class Finalizer<T> {
130142
@patch
131143
factory Finalizer(void Function(T) object) {
132-
throw UnimplementedError("Finalizer");
144+
return _FinalizationRegistryWrapper<T>(object);
145+
}
146+
}
147+
148+
class _FinalizationRegistryWrapper<T> implements Finalizer<T> {
149+
final Object _registry;
150+
151+
_FinalizationRegistryWrapper(void Function(T) callback)
152+
: _registry = JS('', 'new FinalizationRegistry(#)',
153+
convertDartClosureToJS(wrapZoneUnaryCallback(callback), 1));
154+
155+
void attach(Object value, T token, {Object? detach}) {
156+
if (detach != null) {
157+
JS('', '#.register(#, #, #)', _registry, value, token, detach);
158+
} else {
159+
JS('', '#.register(#, #)', _registry, value, token);
160+
}
161+
}
162+
163+
void detach(Object detachToken) {
164+
JS('', '#.unregister(#)', _registry, detachToken);
133165
}
134166
}
135167

sdk/lib/_internal/js_runtime/lib/js_helper.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import 'dart:_js_embedded_names'
2727

2828
import 'dart:collection';
2929

30-
import 'dart:async' show Completer, DeferredLoadException, Future;
30+
import 'dart:async' show Completer, DeferredLoadException, Future, Zone;
3131

3232
import 'dart:_foreign_helper'
3333
show
@@ -3029,3 +3029,11 @@ void assertInteropArgs(List<Object?> args) {
30293029
Object? rawStartupMetrics() {
30303030
return JS('JSArray', '#.a', JS_EMBEDDED_GLOBAL('', STARTUP_METRICS));
30313031
}
3032+
3033+
/// Wraps the given [callback] within the current Zone.
3034+
void Function(T)? wrapZoneUnaryCallback<T>(void Function(T)? callback) {
3035+
// For performance reasons avoid wrapping if we are in the root zone.
3036+
if (Zone.current == Zone.root) return callback;
3037+
if (callback == null) return null;
3038+
return Zone.current.bindUnaryCallbackGuarded(callback);
3039+
}

tests/web/weak_reference_test.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Basic tests for the WeakReference and Finalizer API for the web backends.
6+
// Does not trigger garbage collection to heavily test the functionality.
7+
8+
import 'package:expect/minitest.dart';
9+
10+
class Foo {
11+
int close() {
12+
return 42; // no-op, dropped return value.
13+
}
14+
}
15+
16+
void callback(Foo f) {
17+
f.close();
18+
}
19+
20+
main() {
21+
test('weak reference', () {
22+
var list = ["foo"];
23+
var weakRef = WeakReference<List<String>>(list);
24+
expect(weakRef.target, equals(list));
25+
26+
// Javascript API throws when the representation of target is not 'object'
27+
// in the compiled Javascript.
28+
expect(() => WeakReference<String>("foo"), throws);
29+
expect(() => WeakReference<int>(1), throws);
30+
});
31+
32+
test('finalizer', () {
33+
var finalizer = Finalizer<Foo>(callback);
34+
var list = ["foo"];
35+
var foo = Foo();
36+
// Should not cause errors to attach or detach
37+
finalizer.attach(list, foo);
38+
finalizer.attach(list, foo, detach: list);
39+
finalizer.detach(list);
40+
41+
// Should not cause errors to use a different detach token
42+
var detachList = [1, 2, 3];
43+
finalizer.attach(list, foo, detach: detachList);
44+
finalizer.detach(detachList);
45+
46+
// JavaScript API returns false when unknown target detached.
47+
// Should not cause an error to detach unknown token.
48+
var unknownList = [2, 4, 6];
49+
finalizer.detach(unknownList);
50+
51+
// JavaScript API throws when target and detach token are not objects.
52+
expect(() => finalizer.attach("token string value", foo), throws);
53+
expect(() => finalizer.detach("detach string value"), throws);
54+
});
55+
}

tests/web/web.status

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,6 @@ dummy_compiler_test: SkipByDesign # Issue 30773. Test should be migrated as a un
5353
[ $compiler == none && $runtime == vm ]
5454
new_from_env_test: SkipByDesign # dart2js only test
5555
unconditional_dartio_import_test: SkipByDesign # dart2js only test
56+
57+
[ $compiler == dartkp || $runtime == vm ]
58+
weak_reference_test: SkipByDesign # Web only test

tests/web_2/weak_reference_test.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Basic tests for the WeakReference and Finalizer API for the web backends.
6+
// Does not trigger garbage collection to heavily test the functionality.
7+
8+
import 'package:expect/minitest.dart';
9+
10+
class Foo {
11+
int close() {
12+
return 42; // no-op, dropped return value.
13+
}
14+
}
15+
16+
void callback(Foo f) {
17+
f.close();
18+
}
19+
20+
main() {
21+
test('weak reference', () {
22+
var list = ["foo"];
23+
var weakRef = WeakReference<List<String>>(list);
24+
expect(weakRef.target, equals(list));
25+
26+
// Javascript API throws when the representation of target is not 'object'
27+
// in the compiled Javascript.
28+
expect(() => WeakReference<String>("foo"), throws);
29+
expect(() => WeakReference<int>(1), throws);
30+
});
31+
32+
test('finalizer', () {
33+
var finalizer = Finalizer<Foo>(callback);
34+
var list = ["foo"];
35+
var foo = Foo();
36+
// Should not cause errors to attach or detach
37+
finalizer.attach(list, foo);
38+
finalizer.attach(list, foo, detach: list);
39+
finalizer.detach(list);
40+
41+
// Should not cause errors to use a different detach token
42+
var detachList = [1, 2, 3];
43+
finalizer.attach(list, foo, detach: detachList);
44+
finalizer.detach(detachList);
45+
46+
// JavaScript API returns false when unknown target detached.
47+
// Should not cause an error to detach unknown token.
48+
var unknownList = [2, 4, 6];
49+
finalizer.detach(unknownList);
50+
51+
// JavaScript API throws when target and detach token are not objects.
52+
expect(() => finalizer.attach("token string value", foo), throws);
53+
expect(() => finalizer.detach("detach string value"), throws);
54+
});
55+
}

tests/web_2/web_2.status

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,6 @@ dummy_compiler_test: SkipByDesign # Issue 30773. Test should be migrated as a un
5353
[ $compiler == none && $runtime == vm ]
5454
new_from_env_test: SkipByDesign # dart2js only test
5555
unconditional_dartio_import_test: SkipByDesign # dart2js only test
56+
57+
[ $compiler == dartkp || $runtime == vm ]
58+
weak_reference_test: SkipByDesign # Web only test

0 commit comments

Comments
 (0)