Skip to content

Commit d25dd70

Browse files
authored
Merge pull request #403 from mkustermann/perf-type-checks
Use faster mechanism for type checks against DOM nodes
2 parents 0ca9d6a + 23f9377 commit d25dd70

File tree

6 files changed

+74
-24
lines changed

6 files changed

+74
-24
lines changed

packages/jaspr/lib/src/browser/dom_render_object.dart

+9-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:universal_web/web.dart' as web;
55

66
import '../foundation/constants.dart';
77
import '../foundation/events.dart';
8+
import '../foundation/type_checks.dart';
89
import '../framework/framework.dart';
910
import 'utils.dart';
1011

@@ -51,15 +52,15 @@ class DomRenderObject extends RenderObject {
5152
late web.Element elem;
5253

5354
var namespace = xmlns[tag];
54-
if (namespace == null && (parent?.node?.instanceOfString("Element") ?? false)) {
55+
if (namespace == null && (parent?.node?.isElement ?? false)) {
5556
namespace = (parent?.node as web.Element).namespaceURI;
5657
}
5758

5859
diff:
5960
if (node == null) {
6061
if (parent!.toHydrate.isNotEmpty) {
6162
for (final e in parent!.toHydrate) {
62-
if (e.instanceOfString('Element') && (e as web.Element).tagName.toLowerCase() == tag) {
63+
if (e.isElement && (e as web.Element).tagName.toLowerCase() == tag) {
6364
if (kVerboseMode) {
6465
print("Hydrate html node: $e");
6566
}
@@ -81,7 +82,7 @@ class DomRenderObject extends RenderObject {
8182
web.console.log("Create html node: $elem".toJS);
8283
}
8384
} else {
84-
if (!node.instanceOfString('Element') || (node as web.Element).tagName.toLowerCase() != tag) {
85+
if (!node.isElement || (node as web.Element).tagName.toLowerCase() != tag) {
8586
elem = _createElement(tag, namespace);
8687
final old = node!;
8788
node!.parentNode!.replaceChild(elem, old);
@@ -112,7 +113,7 @@ class DomRenderObject extends RenderObject {
112113
if (attributes != null && attributes.isNotEmpty) {
113114
for (final attr in attributes.entries) {
114115
if (attr.key == 'value' &&
115-
elem.instanceOfString('HTMLInputElement') &&
116+
elem.isHtmlInputElement &&
116117
(elem as web.HTMLInputElement).value != attr.value) {
117118
if (kVerboseMode) {
118119
print("Set input value: ${attr.value}");
@@ -122,7 +123,7 @@ class DomRenderObject extends RenderObject {
122123
}
123124

124125
if (attr.key == 'value' &&
125-
elem.instanceOfString('HTMLSelectElement') &&
126+
elem.isHtmlSelectElement &&
126127
(elem as web.HTMLSelectElement).value != attr.value) {
127128
if (kVerboseMode) {
128129
print("Set select value: ${attr.value}");
@@ -173,7 +174,7 @@ class DomRenderObject extends RenderObject {
173174
final toHydrate = parent!.toHydrate;
174175
if (toHydrate.isNotEmpty) {
175176
for (final e in toHydrate) {
176-
if (e.instanceOfString('Text')) {
177+
if (e.isText) {
177178
if (kVerboseMode) {
178179
print("Hydrate text node: $e");
179180
}
@@ -195,7 +196,7 @@ class DomRenderObject extends RenderObject {
195196
print("Create text node: $text");
196197
}
197198
} else {
198-
if (!node.instanceOfString('Text')) {
199+
if (!node.isText) {
199200
final elem = web.Text(text);
200201
(node as web.Element).replaceWith(elem as dynamic);
201202
node = elem;
@@ -227,7 +228,7 @@ class DomRenderObject extends RenderObject {
227228
final parentNode = node;
228229
final childNode = child.node;
229230

230-
assert(parentNode.instanceOfString('Element'));
231+
assert(parentNode.isElement);
231232
if (childNode == null) return;
232233

233234
final afterNode = after?.node;

packages/jaspr/lib/src/components/document/document_client.dart

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import 'dart:js_interop';
2-
31
import 'package:universal_web/web.dart' as web;
42

53
import '../../../browser.dart';
64
import '../../browser/utils.dart';
5+
import '../../foundation/type_checks.dart';
76

87
abstract class Document implements Component {
98
/// Attaches a set of attributes to the `<html>` element.
@@ -271,7 +270,7 @@ class AttachAdapter {
271270
};
272271

273272
String? keyFor(web.Node node) {
274-
if (!node.instanceOfString('Element')) return null;
273+
if (!node.isElement) return null;
275274
return switch (node as web.Element) {
276275
web.Element(id: String id) when id.isNotEmpty => id,
277276
web.Element(tagName: "TITLE" || "BASE") => '__${node.tagName}',

packages/jaspr/lib/src/components/raw_text/raw_text_web.dart

+7-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:universal_web/web.dart' as web;
44

55
import '../../../browser.dart';
66
import '../../browser/utils.dart';
7+
import '../../foundation/type_checks.dart';
78

89
/// Renders its input as raw HTML.
910
///
@@ -32,8 +33,8 @@ class RawNode extends Component {
3233
return RawNode(
3334
node,
3435
key: switch (node) {
35-
web.Text() when node.instanceOfString("Text") => ValueKey('text'),
36-
web.Element() when node.instanceOfString("Element") => ValueKey('element:${node.tagName}'),
36+
web.Text() when node.isText => ValueKey('text'),
37+
web.Element() when node.isElement => ValueKey('element:${node.tagName}'),
3738
_ => null,
3839
},
3940
);
@@ -61,9 +62,10 @@ class RawNodeElement extends BuildableRenderObjectElement {
6162
@override
6263
void updateRenderObject() {
6364
var next = component.node;
64-
if (next.instanceOfString("Text") && next is web.Text) {
65-
renderObject.updateText(next.textContent ?? '');
66-
} else if (next.instanceOfString("Element") && next is web.Element) {
65+
if (next.isText) {
66+
renderObject.updateText((next as web.Text).textContent ?? '');
67+
} else if (next.isElement) {
68+
next as web.Element;
6769
renderObject.updateElement(
6870
next.tagName.toLowerCase(), next.id, next.className, null, next.attributes.toMap(), null);
6971
} else {

packages/jaspr/lib/src/foundation/events.dart

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import 'package:universal_web/js_interop.dart';
21
import 'package:universal_web/web.dart' as web;
32

43
import '../components/html/html.dart';
54
import 'basic_types.dart';
65
import 'constants.dart';
6+
import 'type_checks.dart';
77

88
typedef EventCallback = void Function(web.Event event);
99
typedef EventCallbacks = Map<String, EventCallback>;
@@ -39,7 +39,7 @@ EventCallbacks events<V1, V2>({
3939
{
4040
if (onClick != null)
4141
'click': (event) {
42-
if (kIsWeb && event.target is web.HTMLAnchorElement && event.target.instanceOfString("HTMLAnchorElement")) {
42+
if (kIsWeb && (event.target.isHtmlAnchorElement)) {
4343
event.preventDefault();
4444
}
4545
onClick();
@@ -52,9 +52,10 @@ void Function(web.Event) _callWithValue<V>(String event, void Function(V) fn) {
5252
return (e) {
5353
var target = e.target;
5454
var value = switch (target) {
55-
web.HTMLInputElement() when target.instanceOfString("HTMLInputElement") => () {
55+
web.HTMLInputElement() when target.isHtmlInputElement => () {
5656
final targetType = target.type;
57-
final type = InputType.values.where((v) => v.name == targetType).firstOrNull;
57+
final type =
58+
InputType.values.where((v) => v.name == targetType).firstOrNull;
5859
return switch (type) {
5960
InputType.checkbox || InputType.radio => target.checked,
6061
InputType.number => target.valueAsNumber,
@@ -63,10 +64,10 @@ void Function(web.Event) _callWithValue<V>(String event, void Function(V) fn) {
6364
_ => target.value,
6465
};
6566
}(),
66-
web.HTMLTextAreaElement() when target.instanceOfString("HTMLTextAreaElement") => target.value,
67-
web.HTMLSelectElement() when target.instanceOfString("HTMLSelectElement") => [
67+
web.HTMLTextAreaElement() when target.isTextAreaElement => target.value,
68+
web.HTMLSelectElement() when target.isHtmlSelectElement => [
6869
for (final o in target.selectedOptions.toIterable())
69-
if (o is web.HTMLOptionElement && o.instanceOfString("HTMLOptionElement")) o.value,
70+
if (o.isHtmlOptionElement) (o as web.HTMLOptionElement).value,
7071
],
7172
_ => null,
7273
};

packages/jaspr/lib/src/foundation/sync/sync_web.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:convert';
22
import 'dart:js_interop';
33

44
import '../../../browser.dart';
5+
import '../../foundation/type_checks.dart';
56
import '../marker_utils.dart';
67

78
final _syncRegex = RegExp('^$syncMarkerPrefixRegex(.*)\$');
@@ -10,7 +11,7 @@ void initSyncState(SyncStateMixin sync) {
1011
var r = (sync.context as Element).parentRenderObjectElement?.renderObject as DomRenderObject?;
1112
if (r == null) return;
1213
for (var node in r.toHydrate) {
13-
if (node.instanceOfString("Text")) {
14+
if (node.isText) {
1415
continue;
1516
}
1617
if (node.instanceOfString("Comment")) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import 'package:universal_web/js_interop.dart';
2+
3+
// For type checks against DOM types we're looking up the constructor
4+
// functions of those DOM types *once* and cache them.
5+
//
6+
// That allows for much faster type checking via
7+
// `JSObject.instanceof(constructor)` as it avoids re-resolving the names
8+
// repeatedly.
9+
//
10+
// See also https://github.com/dart-lang/sdk/issues/60344
11+
extension DomTypeTests on JSAny? {
12+
bool get isElement => this == null ? false : instanceof(_cachedElementConstructor);
13+
bool get isHtmlInputElement => this == null ? false : instanceof(_cachedHtmlInputElementConstructor);
14+
bool get isHtmlAnchorElement => this == null ? false : instanceof(_cachedHtmlAnchorElementConstructor);
15+
bool get isHtmlSelectElement => this == null ? false : instanceof(_cachedHtmlSelectElementConstructor);
16+
bool get isTextAreaElement => this == null ? false : instanceof(_cachedHtmlTextAreaElementConstructor);
17+
bool get isHtmlOptionElement => this == null ? false : instanceof(_cachedHtmlOptionElementConstructor);
18+
bool get isText => this == null ? false : instanceof(_cachedTextConstructor);
19+
bool get isComment => this == null ? false : instanceof(_cachedCommentConstructor);
20+
}
21+
22+
final _cachedElementConstructor = _elementConstructor;
23+
final _cachedHtmlInputElementConstructor = _htmlInputElementConstructor;
24+
final _cachedHtmlAnchorElementConstructor = _htmlAnchorElementConstructor;
25+
final _cachedHtmlSelectElementConstructor = _htmlSelectElementConstructor;
26+
final _cachedHtmlTextAreaElementConstructor = _htmlTextAreaElementConstructor;
27+
final _cachedHtmlOptionElementConstructor = _htmlOptionElementConstructor;
28+
final _cachedTextConstructor = _textConstructor;
29+
final _cachedCommentConstructor = _commentConstructor;
30+
31+
@JS('Element')
32+
external JSFunction get _elementConstructor;
33+
@JS('HTMLInputElement')
34+
external JSFunction get _htmlInputElementConstructor;
35+
@JS('HTMLAnchorElement')
36+
external JSFunction get _htmlAnchorElementConstructor;
37+
@JS('HTMLSelectElement')
38+
external JSFunction get _htmlSelectElementConstructor;
39+
@JS('HTMLTextAreaElement')
40+
external JSFunction get _htmlTextAreaElementConstructor;
41+
@JS('HTMLOptionElement')
42+
external JSFunction get _htmlOptionElementConstructor;
43+
@JS('Text')
44+
external JSFunction get _textConstructor;
45+
@JS('Comment')
46+
external JSFunction get _commentConstructor;

0 commit comments

Comments
 (0)