@@ -55,6 +55,32 @@ class ScopeEvent {
55
55
}
56
56
}
57
57
58
+ /**
59
+ * [ConcurrentScopeModificationException] is thrown when `domWrite` or `domRead` is called from
60
+ * one of the ascendants of the scope.
61
+ *
62
+ * The following examples will both throw [ConcurrentScopeModificationException] :
63
+ *
64
+ * rootScope.domWrite(() {
65
+ * childScope.domWrite((){});
66
+ * });
67
+ *
68
+ * rootScope.domRead(() {
69
+ * childScope.domRead((){});
70
+ * });
71
+ *
72
+ * Calling `domWrite` or `domRead` from the descendants is allowed. So the following will run:
73
+ *
74
+ * childScope.domWrite(() {
75
+ * rootScope.domWrite((){});
76
+ * });
77
+ *
78
+ */
79
+ class ConcurrentScopeModificationException implements Exception {
80
+ String toString () =>
81
+ "Either `domWrite` or `domRead` is called from one of the ascendants of the current scope" ;
82
+ }
83
+
58
84
/**
59
85
* Allows the configuration of [Scope.digest] iteration maximum time-to-live value. Digest keeps
60
86
* checking the state of the watcher getters until it can execute one full iteration with no
@@ -127,6 +153,11 @@ class Scope {
127
153
128
154
Scope _parentScope;
129
155
156
+ _FunctionChain _domReadHead, _domReadTail;
157
+ _FunctionChain _domWriteHead, _domWriteTail;
158
+ bool _scheduledDomWrite = false ;
159
+ bool _scheduledDomRead = false ;
160
+
130
161
Scope get parentScope => _parentScope;
131
162
132
163
final ScopeStats _stats;
@@ -413,6 +444,107 @@ class Scope {
413
444
}
414
445
return counts;
415
446
}
447
+
448
+ /**
449
+ * Internal. Use [View.domWrite] instead.
450
+ */
451
+ void domWrite (fn ()) {
452
+ checkForConcurrentModificationOnDomWrite ();
453
+
454
+ var chain = new _FunctionChain (fn);
455
+ if (_domWriteHead == null ) {
456
+ _domWriteHead = _domWriteTail = chain;
457
+ } else {
458
+ _domWriteTail = _domWriteTail._next = chain;
459
+ }
460
+
461
+ _setScheduledDomWrite ();
462
+ }
463
+
464
+ /**
465
+ * Internal. Use [View.domRead] instead.
466
+ */
467
+ void domRead (fn ()) {
468
+ checkForConcurrentModificationOnDomRead ();
469
+
470
+ var chain = new _FunctionChain (fn);
471
+ if (_domReadHead == null ) {
472
+ _domReadHead = _domReadTail = chain;
473
+ } else {
474
+ _domReadTail = _domReadTail._next = chain;
475
+ }
476
+
477
+ _setScheduledDomRead ();
478
+ }
479
+
480
+ void _runDomWrites () {
481
+ Scope child = _childHead;
482
+ while (child != null ) {
483
+ child._runDomWrites ();
484
+ child = child._next;
485
+ }
486
+
487
+ _callFunctionsInChain (_domWriteHead);
488
+ _domWriteHead = _domWriteTail = null ;
489
+ _scheduledDomWrite = false ;
490
+ }
491
+
492
+ void _runDomReads () {
493
+ Scope child = _childHead;
494
+ while (child != null ) {
495
+ child._runDomReads ();
496
+ child = child._next;
497
+ }
498
+
499
+ _callFunctionsInChain (_domReadHead);
500
+ _domReadHead = _domReadTail = null ;
501
+ _scheduledDomRead = false ;
502
+ }
503
+
504
+ void _callFunctionsInChain (_FunctionChain chain) {
505
+ while (chain != null ) {
506
+ try {
507
+ chain.fn ();
508
+ } catch (e, s) {
509
+ _exceptionHandler (e, s);
510
+ }
511
+ chain = chain._next;
512
+ }
513
+ }
514
+
515
+ void checkForConcurrentModificationOnDomWrite () {
516
+ if (_scheduledDomWrite) return ;
517
+
518
+ for (var scope = _parentScope; scope != null ; scope = scope._parentScope) {
519
+ if (scope._scheduledDomWrite) throw new ConcurrentScopeModificationException ();
520
+ }
521
+ }
522
+
523
+ void checkForConcurrentModificationOnDomRead () {
524
+ if (_scheduledDomRead) return ;
525
+
526
+ for (var scope = _parentScope; scope != null ; scope = scope._parentScope) {
527
+ if (scope._scheduledDomRead) throw new ConcurrentScopeModificationException ();
528
+ }
529
+ }
530
+
531
+ void _setScheduledDomWrite () {
532
+ if (_scheduledDomWrite) return ;
533
+
534
+ for (var scope = this ; scope != null ; scope = scope._parentScope) {
535
+ scope._scheduledDomWrite = true ;
536
+ }
537
+ }
538
+
539
+ void _setScheduledDomRead () {
540
+ if (_scheduledDomRead) return ;
541
+
542
+ for (var scope = this ; scope != null ; scope = scope._parentScope) {
543
+ scope._scheduledDomRead = true ;
544
+ }
545
+ }
546
+
547
+ ExceptionHandler get _exceptionHandler => rootScope._exceptionHandler;
416
548
}
417
549
418
550
_mapEqual (Map a, Map b) => a.length == b.length &&
@@ -587,8 +719,6 @@ class RootScope extends Scope {
587
719
final Map <String , AST > astCache = new HashMap <String , AST >();
588
720
589
721
_FunctionChain _runAsyncHead, _runAsyncTail;
590
- _FunctionChain _domWriteHead, _domWriteTail;
591
- _FunctionChain _domReadHead, _domReadTail;
592
722
593
723
final ScopeStats _scopeStats;
594
724
@@ -734,37 +864,25 @@ class RootScope extends Scope {
734
864
bool runObservers = true ;
735
865
try {
736
866
do {
737
- if (_domWriteHead != null ) _stats.domWriteStart ();
738
- while (_domWriteHead != null ) {
739
- try {
740
- _domWriteHead.fn ();
741
- } catch (e, s) {
742
- _exceptionHandler (e, s);
743
- }
744
- _domWriteHead = _domWriteHead._next;
745
- if (_domWriteHead == null ) _stats.domWriteEnd ();
867
+ if (_scheduledDomWrite) {
868
+ _stats.domWriteStart ();
869
+ _runDomWrites ();
870
+ _stats.domWriteEnd ();
746
871
}
747
- _domWriteTail = null ;
748
872
if (runObservers) {
749
873
runObservers = false ;
750
874
readOnlyGroup.detectChanges (exceptionHandler: _exceptionHandler,
751
875
fieldStopwatch: _scopeStats.fieldStopwatch,
752
876
evalStopwatch: _scopeStats.evalStopwatch,
753
877
processStopwatch: _scopeStats.processStopwatch);
754
878
}
755
- if (_domReadHead != null ) _stats.domReadStart ();
756
- while (_domReadHead != null ) {
757
- try {
758
- _domReadHead.fn ();
759
- } catch (e, s) {
760
- _exceptionHandler (e, s);
761
- }
762
- _domReadHead = _domReadHead._next;
763
- if (_domReadHead == null ) _stats.domReadEnd ();
879
+ if (_scheduledDomRead) {
880
+ _stats.domReadStart ();
881
+ _runDomReads ();
882
+ _stats.domReadEnd ();
764
883
}
765
- _domReadTail = null ;
766
884
_runAsyncFns ();
767
- } while (_domWriteHead != null || _domReadHead != null || _runAsyncHead != null );
885
+ } while (_scheduledDomWrite || _scheduledDomRead || _runAsyncHead != null );
768
886
_stats.flushEnd ();
769
887
assert ((() {
770
888
_stats.flushAssertStart ();
@@ -822,24 +940,6 @@ class RootScope extends Scope {
822
940
return count;
823
941
}
824
942
825
- void domWrite (fn ()) {
826
- var chain = new _FunctionChain (fn);
827
- if (_domWriteHead == null ) {
828
- _domWriteHead = _domWriteTail = chain;
829
- } else {
830
- _domWriteTail = _domWriteTail._next = chain;
831
- }
832
- }
833
-
834
- void domRead (fn ()) {
835
- var chain = new _FunctionChain (fn);
836
- if (_domReadHead == null ) {
837
- _domReadHead = _domReadTail = chain;
838
- } else {
839
- _domReadTail = _domReadTail._next = chain;
840
- }
841
- }
842
-
843
943
void destroy () {}
844
944
845
945
void _transitionState (String from, String to) {
0 commit comments