| 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 |