1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is nsRefreshDriver.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2009
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : /*
40 : * Code to notify things that animate before a refresh, at an appropriate
41 : * refresh rate. (Perhaps temporary, until replaced by compositor.)
42 : */
43 :
44 : #include "mozilla/Util.h"
45 :
46 : #include "nsRefreshDriver.h"
47 : #include "nsPresContext.h"
48 : #include "nsComponentManagerUtils.h"
49 : #include "prlog.h"
50 : #include "nsAutoPtr.h"
51 : #include "nsCSSFrameConstructor.h"
52 : #include "nsIDocument.h"
53 : #include "nsGUIEvent.h"
54 : #include "nsEventDispatcher.h"
55 : #include "jsapi.h"
56 : #include "nsContentUtils.h"
57 : #include "mozilla/Preferences.h"
58 : #include "nsIViewManager.h"
59 :
60 : using mozilla::TimeStamp;
61 : using mozilla::TimeDuration;
62 :
63 : using namespace mozilla;
64 :
65 : #define DEFAULT_FRAME_RATE 60
66 : #define DEFAULT_THROTTLED_FRAME_RATE 1
67 :
68 : static bool sPrecisePref;
69 :
70 : /* static */ void
71 1404 : nsRefreshDriver::InitializeStatics()
72 : {
73 : Preferences::AddBoolVarCache(&sPrecisePref,
74 : "layout.frame_rate.precise",
75 1404 : false);
76 1404 : }
77 :
78 : /* static */ PRInt32
79 0 : nsRefreshDriver::DefaultInterval()
80 : {
81 0 : return NSToIntRound(1000.0 / DEFAULT_FRAME_RATE);
82 : }
83 :
84 : // Compute the interval to use for the refresh driver timer, in
85 : // milliseconds
86 : PRInt32
87 0 : nsRefreshDriver::GetRefreshTimerInterval() const
88 : {
89 : const char* prefName =
90 0 : mThrottled ? "layout.throttled_frame_rate" : "layout.frame_rate";
91 0 : PRInt32 rate = Preferences::GetInt(prefName, -1);
92 0 : if (rate <= 0) {
93 : // TODO: get the rate from the platform
94 0 : rate = mThrottled ? DEFAULT_THROTTLED_FRAME_RATE : DEFAULT_FRAME_RATE;
95 : }
96 0 : NS_ASSERTION(rate > 0, "Must have positive rate here");
97 0 : PRInt32 interval = NSToIntRound(1000.0/rate);
98 0 : if (mThrottled) {
99 0 : interval = NS_MAX(interval, mLastTimerInterval * 2);
100 : }
101 0 : mLastTimerInterval = interval;
102 0 : return interval;
103 : }
104 :
105 : PRInt32
106 0 : nsRefreshDriver::GetRefreshTimerType() const
107 : {
108 0 : if (mThrottled) {
109 0 : return nsITimer::TYPE_ONE_SHOT;
110 : }
111 0 : if (HaveFrameRequestCallbacks() || sPrecisePref) {
112 0 : return nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP;
113 : }
114 0 : return nsITimer::TYPE_REPEATING_SLACK;
115 : }
116 :
117 0 : nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext)
118 : : mPresContext(aPresContext),
119 : mFrozen(false),
120 : mThrottled(false),
121 : mTestControllingRefreshes(false),
122 : mTimerIsPrecise(false),
123 : mViewManagerFlushIsPending(false),
124 0 : mLastTimerInterval(0)
125 : {
126 0 : mRequests.Init();
127 0 : }
128 :
129 0 : nsRefreshDriver::~nsRefreshDriver()
130 : {
131 0 : NS_ABORT_IF_FALSE(ObserverCount() == 0,
132 : "observers should have unregistered");
133 0 : NS_ABORT_IF_FALSE(!mTimer, "timer should be gone");
134 0 : }
135 :
136 : // Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh
137 : // for description.
138 : void
139 0 : nsRefreshDriver::AdvanceTimeAndRefresh(PRInt64 aMilliseconds)
140 : {
141 0 : mTestControllingRefreshes = true;
142 0 : mMostRecentRefreshEpochTime += aMilliseconds * 1000;
143 0 : mMostRecentRefresh += TimeDuration::FromMilliseconds(aMilliseconds);
144 0 : nsCxPusher pusher;
145 0 : if (pusher.PushNull()) {
146 0 : Notify(nsnull);
147 0 : pusher.Pop();
148 : }
149 0 : }
150 :
151 : void
152 0 : nsRefreshDriver::RestoreNormalRefresh()
153 : {
154 0 : mTestControllingRefreshes = false;
155 0 : nsCxPusher pusher;
156 0 : if (pusher.PushNull()) {
157 0 : Notify(nsnull); // will call UpdateMostRecentRefresh()
158 0 : pusher.Pop();
159 : }
160 0 : }
161 :
162 : TimeStamp
163 0 : nsRefreshDriver::MostRecentRefresh() const
164 : {
165 0 : const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
166 :
167 0 : return mMostRecentRefresh;
168 : }
169 :
170 : PRInt64
171 0 : nsRefreshDriver::MostRecentRefreshEpochTime() const
172 : {
173 0 : const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
174 :
175 0 : return mMostRecentRefreshEpochTime;
176 : }
177 :
178 : bool
179 0 : nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver,
180 : mozFlushType aFlushType)
181 : {
182 0 : ObserverArray& array = ArrayFor(aFlushType);
183 0 : bool success = array.AppendElement(aObserver) != nsnull;
184 :
185 0 : EnsureTimerStarted(false);
186 :
187 0 : return success;
188 : }
189 :
190 : bool
191 0 : nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver,
192 : mozFlushType aFlushType)
193 : {
194 0 : ObserverArray& array = ArrayFor(aFlushType);
195 0 : return array.RemoveElement(aObserver);
196 : }
197 :
198 : bool
199 0 : nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
200 : {
201 0 : if (!mRequests.PutEntry(aRequest)) {
202 0 : return false;
203 : }
204 :
205 0 : EnsureTimerStarted(false);
206 :
207 0 : return true;
208 : }
209 :
210 : void
211 0 : nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
212 : {
213 0 : mRequests.RemoveEntry(aRequest);
214 0 : }
215 :
216 0 : void nsRefreshDriver::ClearAllImageRequests()
217 : {
218 0 : mRequests.Clear();
219 0 : }
220 :
221 : void
222 0 : nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
223 : {
224 0 : if (mTimer || mFrozen || !mPresContext) {
225 : // It's already been started, or we don't want to start it now or
226 : // we've been disconnected.
227 0 : return;
228 : }
229 :
230 0 : if (!aAdjustingTimer) {
231 : // If we didn't already have a timer and aAdjustingTimer is false,
232 : // then we just got our first observer (or an explicit call to
233 : // MostRecentRefresh by a caller who's likely to add an observer
234 : // shortly). This means we should fake a most-recent-refresh time
235 : // of now so that said observer gets a reasonable refresh time, so
236 : // things behave as though the timer had always been running.
237 0 : UpdateMostRecentRefresh();
238 : }
239 :
240 0 : mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
241 0 : if (!mTimer) {
242 0 : return;
243 : }
244 :
245 0 : PRInt32 timerType = GetRefreshTimerType();
246 0 : mTimerIsPrecise = (timerType == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
247 :
248 0 : nsresult rv = mTimer->InitWithCallback(this,
249 0 : GetRefreshTimerInterval(),
250 0 : timerType);
251 0 : if (NS_FAILED(rv)) {
252 0 : mTimer = nsnull;
253 : }
254 : }
255 :
256 : void
257 0 : nsRefreshDriver::StopTimer()
258 : {
259 0 : if (!mTimer) {
260 0 : return;
261 : }
262 :
263 0 : mTimer->Cancel();
264 0 : mTimer = nsnull;
265 : }
266 :
267 : PRUint32
268 0 : nsRefreshDriver::ObserverCount() const
269 : {
270 0 : PRUint32 sum = 0;
271 0 : for (PRUint32 i = 0; i < ArrayLength(mObservers); ++i) {
272 0 : sum += mObservers[i].Length();
273 : }
274 :
275 : // Even while throttled, we need to process layout and style changes. Style
276 : // changes can trigger transitions which fire events when they complete, and
277 : // layout changes can affect media queries on child documents, triggering
278 : // style changes, etc.
279 0 : sum += mStyleFlushObservers.Length();
280 0 : sum += mLayoutFlushObservers.Length();
281 0 : sum += mFrameRequestCallbackDocs.Length();
282 0 : sum += mViewManagerFlushIsPending;
283 0 : return sum;
284 : }
285 :
286 : PRUint32
287 0 : nsRefreshDriver::ImageRequestCount() const
288 : {
289 0 : return mRequests.Count();
290 : }
291 :
292 : void
293 0 : nsRefreshDriver::UpdateMostRecentRefresh()
294 : {
295 0 : if (mTestControllingRefreshes) {
296 0 : return;
297 : }
298 :
299 : // Call JS_Now first, since that can have nonzero latency in some rare cases.
300 0 : mMostRecentRefreshEpochTime = JS_Now();
301 0 : mMostRecentRefresh = TimeStamp::Now();
302 : }
303 :
304 : nsRefreshDriver::ObserverArray&
305 0 : nsRefreshDriver::ArrayFor(mozFlushType aFlushType)
306 : {
307 0 : switch (aFlushType) {
308 : case Flush_Style:
309 0 : return mObservers[0];
310 : case Flush_Layout:
311 0 : return mObservers[1];
312 : case Flush_Display:
313 0 : return mObservers[2];
314 : default:
315 0 : NS_ABORT_IF_FALSE(false, "bad flush type");
316 0 : return *static_cast<ObserverArray*>(nsnull);
317 : }
318 : }
319 :
320 : /*
321 : * nsISupports implementation
322 : */
323 :
324 0 : NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsITimerCallback)
325 :
326 : /*
327 : * nsITimerCallback implementation
328 : */
329 :
330 : NS_IMETHODIMP
331 0 : nsRefreshDriver::Notify(nsITimer *aTimer)
332 : {
333 0 : NS_PRECONDITION(!mFrozen, "Why are we notified while frozen?");
334 0 : NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?");
335 0 : NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
336 : "Shouldn't have a JSContext on the stack");
337 :
338 0 : if (mTestControllingRefreshes && aTimer) {
339 : // Ignore real refreshes from our timer (but honor the others).
340 0 : return NS_OK;
341 : }
342 :
343 0 : UpdateMostRecentRefresh();
344 :
345 0 : nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
346 0 : if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
347 : // Things are being destroyed, or we no longer have any observers.
348 : // We don't want to stop the timer when observers are initially
349 : // removed, because sometimes observers can be added and removed
350 : // often depending on what other things are going on and in that
351 : // situation we don't want to thrash our timer. So instead we
352 : // wait until we get a Notify() call when we have no observers
353 : // before stopping the timer.
354 0 : StopTimer();
355 0 : return NS_OK;
356 : }
357 :
358 : /*
359 : * The timer holds a reference to |this| while calling |Notify|.
360 : * However, implementations of |WillRefresh| are permitted to destroy
361 : * the pres context, which will cause our |mPresContext| to become
362 : * null. If this happens, we must stop notifying observers.
363 : */
364 0 : for (PRUint32 i = 0; i < ArrayLength(mObservers); ++i) {
365 0 : ObserverArray::EndLimitedIterator etor(mObservers[i]);
366 0 : while (etor.HasMore()) {
367 0 : nsRefPtr<nsARefreshObserver> obs = etor.GetNext();
368 0 : obs->WillRefresh(mMostRecentRefresh);
369 :
370 0 : if (!mPresContext || !mPresContext->GetPresShell()) {
371 0 : StopTimer();
372 0 : return NS_OK;
373 : }
374 : }
375 :
376 0 : if (i == 0) {
377 : // Grab all of our frame request callbacks up front.
378 0 : nsIDocument::FrameRequestCallbackList frameRequestCallbacks;
379 0 : for (PRUint32 i = 0; i < mFrameRequestCallbackDocs.Length(); ++i) {
380 0 : mFrameRequestCallbackDocs[i]->
381 0 : TakeFrameRequestCallbacks(frameRequestCallbacks);
382 : }
383 : // OK, now reset mFrameRequestCallbackDocs so they can be
384 : // readded as needed.
385 0 : mFrameRequestCallbackDocs.Clear();
386 :
387 0 : PRInt64 eventTime = mMostRecentRefreshEpochTime / PR_USEC_PER_MSEC;
388 0 : for (PRUint32 i = 0; i < frameRequestCallbacks.Length(); ++i) {
389 0 : frameRequestCallbacks[i]->Sample(eventTime);
390 : }
391 :
392 : // This is the Flush_Style case.
393 0 : if (mPresContext && mPresContext->GetPresShell()) {
394 0 : nsAutoTArray<nsIPresShell*, 16> observers;
395 0 : observers.AppendElements(mStyleFlushObservers);
396 0 : for (PRUint32 j = observers.Length();
397 0 : j && mPresContext && mPresContext->GetPresShell(); --j) {
398 : // Make sure to not process observers which might have been removed
399 : // during previous iterations.
400 0 : nsIPresShell* shell = observers[j - 1];
401 0 : if (!mStyleFlushObservers.Contains(shell))
402 0 : continue;
403 0 : NS_ADDREF(shell);
404 0 : mStyleFlushObservers.RemoveElement(shell);
405 0 : shell->FrameConstructor()->mObservingRefreshDriver = false;
406 0 : shell->FlushPendingNotifications(Flush_Style);
407 0 : NS_RELEASE(shell);
408 : }
409 : }
410 0 : } else if (i == 1) {
411 : // This is the Flush_Layout case.
412 0 : if (mPresContext && mPresContext->GetPresShell()) {
413 0 : nsAutoTArray<nsIPresShell*, 16> observers;
414 0 : observers.AppendElements(mLayoutFlushObservers);
415 0 : for (PRUint32 j = observers.Length();
416 0 : j && mPresContext && mPresContext->GetPresShell(); --j) {
417 : // Make sure to not process observers which might have been removed
418 : // during previous iterations.
419 0 : nsIPresShell* shell = observers[j - 1];
420 0 : if (!mLayoutFlushObservers.Contains(shell))
421 0 : continue;
422 0 : NS_ADDREF(shell);
423 0 : mLayoutFlushObservers.RemoveElement(shell);
424 0 : shell->mReflowScheduled = false;
425 0 : shell->mSuppressInterruptibleReflows = false;
426 0 : shell->FlushPendingNotifications(Flush_InterruptibleLayout);
427 0 : NS_RELEASE(shell);
428 : }
429 : }
430 : }
431 : }
432 :
433 : /*
434 : * Perform notification to imgIRequests subscribed to listen
435 : * for refresh events.
436 : */
437 :
438 0 : ImageRequestParameters parms = {mMostRecentRefresh};
439 0 : if (mRequests.Count()) {
440 0 : mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms);
441 0 : EnsureTimerStarted(false);
442 : }
443 :
444 0 : if (mViewManagerFlushIsPending) {
445 0 : mViewManagerFlushIsPending = false;
446 0 : mPresContext->GetPresShell()->GetViewManager()->ProcessPendingUpdates();
447 : }
448 :
449 0 : if (mThrottled ||
450 : (mTimerIsPrecise !=
451 0 : (GetRefreshTimerType() == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP))) {
452 : // Stop the timer now and restart it here. Stopping is in the mThrottled
453 : // case ok because either it's already one-shot, and it just fired, and all
454 : // we need to do is null it out, or it's repeating and we need to reset it
455 : // to be one-shot. Stopping and restarting in the case when we need to
456 : // switch from precise to slack timers or vice versa is unfortunately
457 : // required.
458 :
459 : // Note that the EnsureTimerStarted() call here is ok because
460 : // EnsureTimerStarted makes sure to not start the timer if it shouldn't be
461 : // started.
462 0 : StopTimer();
463 0 : EnsureTimerStarted(true);
464 : }
465 :
466 0 : return NS_OK;
467 : }
468 :
469 : PLDHashOperator
470 0 : nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
471 : void* aUserArg)
472 : {
473 : ImageRequestParameters* parms =
474 0 : static_cast<ImageRequestParameters*> (aUserArg);
475 0 : mozilla::TimeStamp mostRecentRefresh = parms->ts;
476 0 : imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey());
477 0 : NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request");
478 0 : nsCOMPtr<imgIContainer> image;
479 0 : req->GetImage(getter_AddRefs(image));
480 0 : if (image) {
481 0 : image->RequestRefresh(mostRecentRefresh);
482 : }
483 :
484 0 : return PL_DHASH_NEXT;
485 : }
486 :
487 : void
488 0 : nsRefreshDriver::Freeze()
489 : {
490 0 : NS_ASSERTION(!mFrozen, "Freeze called on already-frozen refresh driver");
491 0 : StopTimer();
492 0 : mFrozen = true;
493 0 : }
494 :
495 : void
496 0 : nsRefreshDriver::Thaw()
497 : {
498 0 : NS_ASSERTION(mFrozen, "Thaw called on an unfrozen refresh driver");
499 0 : mFrozen = false;
500 0 : if (ObserverCount() || ImageRequestCount()) {
501 : // FIXME: This isn't quite right, since our EnsureTimerStarted call
502 : // updates our mMostRecentRefresh, but the DoRefresh call won't run
503 : // and notify our observers until we get back to the event loop.
504 : // Thus MostRecentRefresh() will lie between now and the DoRefresh.
505 0 : NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
506 0 : EnsureTimerStarted(false);
507 : }
508 0 : }
509 :
510 : void
511 0 : nsRefreshDriver::SetThrottled(bool aThrottled)
512 : {
513 0 : if (aThrottled != mThrottled) {
514 0 : mThrottled = aThrottled;
515 0 : if (mTimer) {
516 : // We want to switch our timer type here, so just stop and
517 : // restart the timer.
518 0 : StopTimer();
519 0 : EnsureTimerStarted(true);
520 : }
521 : }
522 0 : }
523 :
524 : void
525 0 : nsRefreshDriver::DoRefresh()
526 : {
527 : // Don't do a refresh unless we're in a state where we should be refreshing.
528 0 : if (!mFrozen && mPresContext && mTimer) {
529 0 : Notify(nsnull);
530 : }
531 0 : }
532 :
533 : #ifdef DEBUG
534 : bool
535 0 : nsRefreshDriver::IsRefreshObserver(nsARefreshObserver *aObserver,
536 : mozFlushType aFlushType)
537 : {
538 0 : ObserverArray& array = ArrayFor(aFlushType);
539 0 : return array.Contains(aObserver);
540 : }
541 : #endif
542 :
543 : void
544 0 : nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
545 : {
546 0 : NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
547 : mFrameRequestCallbackDocs.NoIndex,
548 : "Don't schedule the same document multiple times");
549 0 : mFrameRequestCallbackDocs.AppendElement(aDocument);
550 : // No need to worry about restarting our timer in precise mode if it's
551 : // already running; that will happen automatically when it fires.
552 0 : EnsureTimerStarted(false);
553 0 : }
554 :
555 : void
556 0 : nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
557 : {
558 0 : mFrameRequestCallbackDocs.RemoveElement(aDocument);
559 : // No need to worry about restarting our timer in slack mode if it's already
560 : // running; that will happen automatically when it fires.
561 0 : }
|