OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 part of fn; | |
6 | |
7 void _parentInsertBefore(sky.ParentNode parent, | |
8 sky.Node node, | |
9 sky.Node ref) { | |
10 if (ref != null) { | |
11 ref.insertBefore([node]); | |
12 } else { | |
13 parent.appendChild(node); | |
14 } | |
15 } | |
16 | |
17 abstract class Node { | |
18 String _key = null; | |
19 sky.Node _root = null; | |
20 | |
21 // TODO(abarth): Both Elements and Components have |events| but |Text| | |
22 // doesn't. Should we add a common base class to contain |events|? | |
23 final EventMap events = new EventMap(); | |
24 | |
25 Node({ Object key }) { | |
26 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | |
27 } | |
28 | |
29 // Return true IFF the old node has *become* the new node (should be | |
30 // retained because it is stateful) | |
31 bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore); | |
32 | |
33 void _remove() { | |
34 assert(_root != null); | |
35 _root.remove(); | |
36 _root = null; | |
37 } | |
38 } | |
39 | |
40 class Text extends Node { | |
41 String data; | |
42 | |
43 // Text nodes are special cases of having non-unique keys (which don't need | |
44 // to be assigned as part of the API). Since they are unique in not having | |
45 // children, there's little point to reordering, so we always just re-assign | |
46 // the data. | |
47 Text(this.data) : super(key:'*text*'); | |
48 | |
49 bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | |
50 if (old == null) { | |
51 _root = new sky.Text(data); | |
52 _parentInsertBefore(host, _root, insertBefore); | |
53 return false; | |
54 } | |
55 | |
56 _root = old._root; | |
57 (_root as sky.Text).data = data; | |
58 return false; | |
59 } | |
60 } | |
61 | |
62 final List<Node> _emptyList = new List<Node>(); | |
63 | |
64 abstract class Element extends Node { | |
65 | |
66 String get _tagName; | |
67 | |
68 Element get _emptyElement; | |
69 | |
70 String inlineStyle; | |
71 | |
72 List<Node> _children = null; | |
73 String _className = ''; | |
74 | |
75 Element({ | |
76 Object key, | |
77 List<Node> children, | |
78 Style style, | |
79 | |
80 this.inlineStyle | |
81 }) : super(key:key) { | |
82 | |
83 _className = style == null ? '': style._className; | |
84 _children = children == null ? _emptyList : children; | |
85 | |
86 if (_debugWarnings()) { | |
87 _debugReportDuplicateIds(); | |
88 } | |
89 } | |
90 | |
91 void _remove() { | |
92 super._remove(); | |
93 if (_children != null) { | |
94 for (var child in _children) { | |
95 child._remove(); | |
96 } | |
97 } | |
98 _children = null; | |
99 } | |
100 | |
101 void _debugReportDuplicateIds() { | |
102 var idSet = new HashSet<String>(); | |
103 for (var child in _children) { | |
104 if (child is Text) { | |
105 continue; // Text nodes all have the same key and are never reordered. | |
106 } | |
107 | |
108 if (!idSet.add(child._key)) { | |
109 throw '''If multiple (non-Text) nodes of the same type exist as children | |
110 of another node, they must have unique keys.'''; | |
111 } | |
112 } | |
113 } | |
114 | |
115 void _syncEvents([Element old]) { | |
116 List<EventHandler> newHandlers = events._handlers; | |
117 int newStartIndex = 0; | |
118 int newEndIndex = newHandlers.length; | |
119 | |
120 List<EventHandler> oldHandlers = old.events._handlers; | |
121 int oldStartIndex = 0; | |
122 int oldEndIndex = oldHandlers.length; | |
123 | |
124 // Skip over leading handlers that match. | |
125 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { | |
126 EventHandler newHander = newHandlers[newStartIndex]; | |
127 EventHandler oldHandler = oldHandlers[oldStartIndex]; | |
128 if (newHander.type != oldHandler.type | |
129 || newHander.listener != oldHandler.listener) | |
130 break; | |
131 ++newStartIndex; | |
132 ++oldStartIndex; | |
133 } | |
134 | |
135 // Skip over trailing handlers that match. | |
136 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { | |
137 EventHandler newHander = newHandlers[newEndIndex - 1]; | |
138 EventHandler oldHandler = oldHandlers[oldEndIndex - 1]; | |
139 if (newHander.type != oldHandler.type | |
140 || newHander.listener != oldHandler.listener) | |
141 break; | |
142 --newEndIndex; | |
143 --oldEndIndex; | |
144 } | |
145 | |
146 sky.Element root = _root as sky.Element; | |
147 | |
148 for (int i = oldStartIndex; i < oldEndIndex; ++i) { | |
149 EventHandler oldHandler = oldHandlers[i]; | |
150 root.removeEventListener(oldHandler.type, oldHandler.listener); | |
151 } | |
152 | |
153 for (int i = newStartIndex; i < newEndIndex; ++i) { | |
154 EventHandler newHander = newHandlers[i]; | |
155 root.addEventListener(newHander.type, newHander.listener); | |
156 } | |
157 } | |
158 | |
159 void _syncNode([Element old]) { | |
160 if (old == null) { | |
161 old = _emptyElement; | |
162 } | |
163 | |
164 _syncEvents(old); | |
165 | |
166 sky.Element root = _root as sky.Element; | |
167 if (_className != old._className) { | |
168 root.setAttribute('class', _className); | |
169 } | |
170 | |
171 if (inlineStyle != old.inlineStyle) { | |
172 root.setAttribute('style', inlineStyle); | |
173 } | |
174 } | |
175 | |
176 bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | |
177 // print("---Syncing children of $_key"); | |
178 | |
179 Element oldElement = old as Element; | |
180 | |
181 if (oldElement == null) { | |
182 // print("...no oldElement, initial render"); | |
183 | |
184 _root = sky.document.createElement(_tagName); | |
185 _syncNode(); | |
186 | |
187 for (var child in _children) { | |
188 child._sync(null, _root, null); | |
189 assert(child._root is sky.Node); | |
190 } | |
191 | |
192 _parentInsertBefore(host, _root, insertBefore); | |
193 return false; | |
194 } | |
195 | |
196 _root = oldElement._root; | |
197 oldElement._root = null; | |
198 sky.Element root = (_root as sky.Element); | |
199 | |
200 _syncNode(oldElement); | |
201 | |
202 var startIndex = 0; | |
203 var endIndex = _children.length; | |
204 | |
205 var oldChildren = oldElement._children; | |
206 var oldStartIndex = 0; | |
207 var oldEndIndex = oldChildren.length; | |
208 | |
209 sky.Node nextSibling = null; | |
210 Node currentNode = null; | |
211 Node oldNode = null; | |
212 | |
213 void sync(int atIndex) { | |
214 if (currentNode._sync(oldNode, root, nextSibling)) { | |
215 // oldNode was stateful and must be retained. | |
216 assert(oldNode != null); | |
217 currentNode = oldNode; | |
218 _children[atIndex] = currentNode; | |
219 } | |
220 assert(currentNode._root is sky.Node); | |
221 } | |
222 | |
223 // Scan backwards from end of list while nodes can be directly synced | |
224 // without reordering. | |
225 // print("...scanning backwards"); | |
226 while (endIndex > startIndex && oldEndIndex > oldStartIndex) { | |
227 currentNode = _children[endIndex - 1]; | |
228 oldNode = oldChildren[oldEndIndex - 1]; | |
229 | |
230 if (currentNode._key != oldNode._key) { | |
231 break; | |
232 } | |
233 | |
234 // print('> syncing matched at: $endIndex : $oldEndIndex'); | |
235 endIndex--; | |
236 oldEndIndex--; | |
237 sync(endIndex); | |
238 nextSibling = currentNode._root; | |
239 } | |
240 | |
241 HashMap<String, Node> oldNodeIdMap = null; | |
242 | |
243 bool oldNodeReordered(String key) { | |
244 return oldNodeIdMap != null && | |
245 oldNodeIdMap.containsKey(key) && | |
246 oldNodeIdMap[key] == null; | |
247 } | |
248 | |
249 void advanceOldStartIndex() { | |
250 oldStartIndex++; | |
251 while (oldStartIndex < oldEndIndex && | |
252 oldNodeReordered(oldChildren[oldStartIndex]._key)) { | |
253 oldStartIndex++; | |
254 } | |
255 } | |
256 | |
257 void ensureOldIdMap() { | |
258 if (oldNodeIdMap != null) | |
259 return; | |
260 | |
261 oldNodeIdMap = new HashMap<String, Node>(); | |
262 for (int i = oldStartIndex; i < oldEndIndex; i++) { | |
263 var node = oldChildren[i]; | |
264 if (node is! Text) { | |
265 oldNodeIdMap.putIfAbsent(node._key, () => node); | |
266 } | |
267 } | |
268 } | |
269 | |
270 bool searchForOldNode() { | |
271 if (currentNode is Text) | |
272 return false; // Never re-order Text nodes. | |
273 | |
274 ensureOldIdMap(); | |
275 oldNode = oldNodeIdMap[currentNode._key]; | |
276 if (oldNode == null) | |
277 return false; | |
278 | |
279 oldNodeIdMap[currentNode._key] = null; // mark it reordered. | |
280 // print("Reparenting ${currentNode._key}"); | |
281 _parentInsertBefore(root, oldNode._root, nextSibling); | |
282 return true; | |
283 } | |
284 | |
285 // Scan forwards, this time we may re-order; | |
286 // print("...scanning forward"); | |
287 nextSibling = root.firstChild; | |
288 while (startIndex < endIndex && oldStartIndex < oldEndIndex) { | |
289 currentNode = _children[startIndex]; | |
290 oldNode = oldChildren[oldStartIndex]; | |
291 | |
292 if (currentNode._key == oldNode._key) { | |
293 // print('> syncing matched at: $startIndex : $oldStartIndex'); | |
294 assert(currentNode.runtimeType == oldNode.runtimeType); | |
295 nextSibling = nextSibling.nextSibling; | |
296 sync(startIndex); | |
297 startIndex++; | |
298 advanceOldStartIndex(); | |
299 continue; | |
300 } | |
301 | |
302 oldNode = null; | |
303 if (searchForOldNode()) { | |
304 // print('> reordered to $startIndex'); | |
305 } else { | |
306 // print('> inserting at $startIndex'); | |
307 } | |
308 | |
309 sync(startIndex); | |
310 startIndex++; | |
311 } | |
312 | |
313 // New insertions | |
314 oldNode = null; | |
315 // print('...processing remaining insertions'); | |
316 while (startIndex < endIndex) { | |
317 // print('> inserting at $startIndex'); | |
318 currentNode = _children[startIndex]; | |
319 sync(startIndex); | |
320 startIndex++; | |
321 } | |
322 | |
323 // Removals | |
324 // print('...processing remaining removals'); | |
325 currentNode = null; | |
326 while (oldStartIndex < oldEndIndex) { | |
327 oldNode = oldChildren[oldStartIndex]; | |
328 // print('> ${oldNode._key} removing from $oldEndIndex'); | |
329 oldNode._remove(); | |
330 advanceOldStartIndex(); | |
331 } | |
332 | |
333 oldElement._children = null; | |
334 return false; | |
335 } | |
336 } | |
337 | |
338 class Container extends Element { | |
339 | |
340 String get _tagName => 'div'; | |
341 | |
342 static final Container _emptyContainer = new Container(); | |
343 | |
344 Element get _emptyElement => _emptyContainer; | |
345 | |
346 Container({ | |
347 Object key, | |
348 List<Node> children, | |
349 Style style, | |
350 String inlineStyle | |
351 }) : super( | |
352 key: key, | |
353 children: children, | |
354 style: style, | |
355 inlineStyle: inlineStyle | |
356 ); | |
357 } | |
358 | |
359 class Image extends Element { | |
360 | |
361 String get _tagName => 'img'; | |
362 | |
363 static final Image _emptyImage = new Image(); | |
364 Element get _emptyElement => _emptyImage; | |
365 | |
366 String src; | |
367 int width; | |
368 int height; | |
369 | |
370 Image({ | |
371 Object key, | |
372 List<Node> children, | |
373 Style style, | |
374 String inlineStyle, | |
375 this.width, | |
376 this.height, | |
377 this.src | |
378 }) : super( | |
379 key: key, | |
380 children: children, | |
381 style: style, | |
382 inlineStyle: inlineStyle | |
383 ); | |
384 | |
385 void _syncNode([Element old]) { | |
386 super._syncNode(old); | |
387 | |
388 Image oldImage = old != null ? old : _emptyImage; | |
389 sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement; | |
390 if (src != oldImage.src) { | |
391 skyImage.src = src; | |
392 } | |
393 | |
394 if (width != oldImage.width) { | |
395 skyImage.style['width'] = '${width}px'; | |
396 } | |
397 if (height != oldImage.height) { | |
398 skyImage.style['height'] = '${height}px'; | |
399 } | |
400 } | |
401 } | |
402 | |
403 class Anchor extends Element { | |
404 | |
405 String get _tagName => 'a'; | |
406 | |
407 static final Anchor _emptyAnchor = new Anchor(); | |
408 | |
409 String href; | |
410 | |
411 Anchor({ | |
412 Object key, | |
413 List<Node> children, | |
414 Style style, | |
415 String inlineStyle, | |
416 this.width, | |
417 this.height, | |
418 this.href | |
419 }) : super( | |
420 key: key, | |
421 children: children, | |
422 style: style, | |
423 inlineStyle: inlineStyle | |
424 ); | |
425 | |
426 void _syncNode([Element old]) { | |
427 Anchor oldAnchor = old != null ? old as Anchor : _emptyAnchor; | |
428 super._syncNode(oldAnchor); | |
429 | |
430 sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement; | |
431 if (href != oldAnchor.href) { | |
432 skyAnchor.href = href; | |
433 } | |
434 } | |
435 } | |
OLD | NEW |