OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, 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 part of html; | |
6 | |
7 typedef Object ComputeValue(); | |
8 | |
9 class _MeasurementRequest<T> { | |
10 final ComputeValue computeValue; | |
11 final Completer<T> completer; | |
12 Object value; | |
13 bool exception = false; | |
14 _MeasurementRequest(this.computeValue, this.completer); | |
15 } | |
16 | |
17 typedef void _MeasurementCallback(); | |
18 | |
19 /** | |
20 * This class attempts to invoke a callback as soon as the current event stack | |
21 * unwinds, but before the browser repaints. | |
22 */ | |
23 abstract class _MeasurementScheduler { | |
24 bool _nextMeasurementFrameScheduled = false; | |
25 _MeasurementCallback _callback; | |
26 | |
27 _MeasurementScheduler(this._callback); | |
28 | |
29 /** | |
30 * Creates the best possible measurement scheduler for the current platform. | |
31 */ | |
32 factory _MeasurementScheduler.best(_MeasurementCallback callback) { | |
33 if (MutationObserver.supported) { | |
34 return new _MutationObserverScheduler(callback); | |
35 } | |
36 return new _PostMessageScheduler(callback); | |
37 } | |
38 | |
39 /** | |
40 * Schedules a measurement callback if one has not been scheduled already. | |
41 */ | |
42 void maybeSchedule() { | |
43 if (this._nextMeasurementFrameScheduled) { | |
44 return; | |
45 } | |
46 this._nextMeasurementFrameScheduled = true; | |
47 this._schedule(); | |
48 } | |
49 | |
50 /** | |
51 * Does the actual scheduling of the callback. | |
52 */ | |
53 void _schedule(); | |
54 | |
55 /** | |
56 * Handles the measurement callback and forwards it if necessary. | |
57 */ | |
58 void _onCallback() { | |
59 // Ignore spurious messages. | |
60 if (!_nextMeasurementFrameScheduled) { | |
61 return; | |
62 } | |
63 _nextMeasurementFrameScheduled = false; | |
64 this._callback(); | |
65 } | |
66 } | |
67 | |
68 /** | |
69 * Scheduler which uses window.postMessage to schedule events. | |
70 */ | |
71 class _PostMessageScheduler extends _MeasurementScheduler { | |
72 const _MEASUREMENT_MESSAGE = "DART-MEASURE"; | |
73 | |
74 _PostMessageScheduler(_MeasurementCallback callback): super(callback) { | |
75 // Messages from other windows do not cause a security risk as | |
76 // all we care about is that _handleMessage is called | |
77 // after the current event loop is unwound and calling the function is | |
78 // a noop when zero requests are pending. | |
79 window.on.message.add(this._handleMessage); | |
80 } | |
81 | |
82 void _schedule() { | |
83 window.postMessage(_MEASUREMENT_MESSAGE, "*"); | |
84 } | |
85 | |
86 _handleMessage(e) { | |
87 this._onCallback(); | |
88 } | |
89 } | |
90 | |
91 /** | |
92 * Scheduler which uses a MutationObserver to schedule events. | |
93 */ | |
94 class _MutationObserverScheduler extends _MeasurementScheduler { | |
95 MutationObserver _observer; | |
96 Element _dummy; | |
97 | |
98 _MutationObserverScheduler(_MeasurementCallback callback): super(callback) { | |
99 // Mutation events get fired as soon as the current event stack is unwound | |
100 // so we just make a dummy event and listen for that. | |
101 _observer = new MutationObserver(this._handleMutation); | |
102 _dummy = new DivElement(); | |
103 _observer.observe(_dummy, attributes: true); | |
104 } | |
105 | |
106 void _schedule() { | |
107 // Toggle it to trigger the mutation event. | |
108 _dummy.hidden = !_dummy.hidden; | |
109 } | |
110 | |
111 _handleMutation(List<MutationRecord> mutations, MutationObserver observer) { | |
112 this._onCallback(); | |
113 } | |
114 } | |
115 | |
116 | |
117 List<_MeasurementRequest> _pendingRequests; | |
118 List<TimeoutHandler> _pendingMeasurementFrameCallbacks; | |
119 _MeasurementScheduler _measurementScheduler = null; | |
120 | |
121 void _maybeScheduleMeasurementFrame() { | |
122 if (_measurementScheduler == null) { | |
123 _measurementScheduler = | |
124 new _MeasurementScheduler.best(_completeMeasurementFutures); | |
125 } | |
126 _measurementScheduler.maybeSchedule(); | |
127 } | |
128 | |
129 /** | |
130 * Registers a [callback] which is called after the next batch of measurements | |
131 * completes. Even if no measurements completed, the callback is triggered | |
132 * when they would have completed to avoid confusing bugs if it happened that | |
133 * no measurements were actually requested. | |
134 */ | |
135 void _addMeasurementFrameCallback(TimeoutHandler callback) { | |
136 if (_pendingMeasurementFrameCallbacks == null) { | |
137 _pendingMeasurementFrameCallbacks = <TimeoutHandler>[]; | |
138 _maybeScheduleMeasurementFrame(); | |
139 } | |
140 _pendingMeasurementFrameCallbacks.add(callback); | |
141 } | |
142 | |
143 /** | |
144 * Returns a [Future] whose value will be the result of evaluating | |
145 * [computeValue] during the next safe measurement interval. | |
146 * The next safe measurement interval is after the current event loop has | |
147 * unwound but before the browser has rendered the page. | |
148 * It is important that the [computeValue] function only queries the html | |
149 * layout and html in any way. | |
150 */ | |
151 Future _createMeasurementFuture(ComputeValue computeValue, | |
152 Completer completer) { | |
153 if (_pendingRequests == null) { | |
154 _pendingRequests = <_MeasurementRequest>[]; | |
155 _maybeScheduleMeasurementFrame(); | |
156 } | |
157 _pendingRequests.add(new _MeasurementRequest(computeValue, completer)); | |
158 return completer.future; | |
159 } | |
160 | |
161 /** | |
162 * Complete all pending measurement futures evaluating them in a single batch | |
163 * so that the the browser is guaranteed to avoid multiple layouts. | |
164 */ | |
165 void _completeMeasurementFutures() { | |
166 // We must compute all new values before fulfilling the futures as | |
167 // the onComplete callbacks for the futures could modify the DOM making | |
168 // subsequent measurement calculations expensive to compute. | |
169 if (_pendingRequests != null) { | |
170 for (_MeasurementRequest request in _pendingRequests) { | |
171 try { | |
172 request.value = request.computeValue(); | |
173 } catch (e) { | |
174 request.value = e; | |
175 request.exception = true; | |
176 } | |
177 } | |
178 } | |
179 | |
180 final completedRequests = _pendingRequests; | |
181 final readyMeasurementFrameCallbacks = _pendingMeasurementFrameCallbacks; | |
182 _pendingRequests = null; | |
183 _pendingMeasurementFrameCallbacks = null; | |
184 if (completedRequests != null) { | |
185 for (_MeasurementRequest request in completedRequests) { | |
186 if (request.exception) { | |
187 request.completer.completeException(request.value); | |
188 } else { | |
189 request.completer.complete(request.value); | |
190 } | |
191 } | |
192 } | |
193 | |
194 if (readyMeasurementFrameCallbacks != null) { | |
195 for (TimeoutHandler handler in readyMeasurementFrameCallbacks) { | |
196 // TODO(jacobr): wrap each call to a handler in a try-catch block. | |
197 handler(); | |
198 } | |
199 } | |
200 } | |
OLD | NEW |