mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
aec5e5666e
- bug 1083930 - cpu spin during large h2/spdy upload r=hurley (0949388a6a)
- Bug 1241906 - Spdy deadlock on suspended channel r=hurley (f40e9677d5)
- Bug 1247205 - dont loop on http2 softerror r=dragana (2ab3cb69ef)
- Bug 1246778 - dont loop in nshttpconnection during shutdown r=dragana (0677b9d34b)
- Bug 1201037 - only send "HTTP pings" on seemingly idle connections, r=mcmanus (134198bc79)
- Bug 1174899 - discarded spdy data with fin attributed to wrong stream r=bagder (f3b801c734)
- Bug 1236170 - Make Http2Session::UncompressAndDiscard push-aware. r=mcmanus (e71634e853)
- Bug 1240025 - incorrect close state on pushed stream r=hurley (eb2832177a)
- Bug 1227931 - init Http2Stream::mReceivedData in the constructor. r=nwgh (44f1d8e897)
- Bug 241788 - net_FilterURIString should filter \r\n\t from the entire URL r=honzab (734d9b8cae)
- Bug 1259459 - h2 0 length options puts end-stream on headers r=hurley (31ac211a9b)
- Bug 1174899 - fixup log format strings for spdy/h2 r=bagder (967c9ff71e)
- Bug 1211694 - dataLength has been added twice. r=mcmanus (6773981db3)
- cleanup (26517f5de0)
- Bug 1247998 - Let nsHttpChannel::AsyncOpen* throw after nsHttpHandler has been shutdown, r=mcmanus (90bb2364be)
- Bug 1231512 - Allow nsIHttpChannel.redirectTo() work also on an open channel, r=jduell (198fb72816)
- Bug 1242472 - Properly propagate mTopWindowURI through redirects. r=francois/ckerschb (1d27a15770)
- Bug 1133873 - some spdy logs r=hurley (cd95cfed5a)
- bug 1215724 - enable brotli on spdy r=hurley (83cca72fa5)
- Bug 137852 - Add a new working HTTP authentication identity to the begining of the session cache list. r=honzab (f670349771)
- Bug 1245414, part 1 - Delete the mfbt/decimal/LICENSE* files since upstream now just uses inline comments. r=Waldo (15bb211e14)
- Bug 1245414, part 2 - Update mfbt/decimal/update.sh to reflect Blink's switch from svn to git, and the different files we now pull. r=Waldo (4dd0b5916a)
- Bug 1245414, part 3 - Overwrite mfbt/decimal/Decimal.* with vanilla upstream copies. r=Waldo (98f7ba4711)
- Bug 1245414, part 4 - Update mfbt/decimal/zero-serialization.patch. r =Waldo (055e1354a7)
- Bug 1245414, part 5 - Update mfbt/decimal/comparison-with-nan.patch. r=Waldo (583e0f3e76)
- Bug 1245414, part 6 - Update mfbt/decimal/mfbt-abi-markers.patch. r=Waldo (148b1ac08b)
- Bug 1245414, part 7 - Update mfbt/decimal/to-moz-dependencies.patch. r=Waldo (2e2a6a33d7)
- Bug 1245414, part 8 - Remove mfbt/decimal/floor-ceiling.patch now that the issue is fixed upstream. r=Waldo (84fc02c068)
- Bug 1245414, part 9 - Disable mfbt/decimal/fix-wshadow-warnings.patch. r=cpeterson (4476d04c5d)
- Bug 1245414, part 10 - Apply the Mozilla patches via mfbt/decimal/update.sh. r=Waldo (1f95ef5524)
- Bug 1247082 - Suppress rendering of nsBackdropFrame for VR content r=dholbert (0ffeae4267)
- Bug 1206545 - Initialize AccessibleCaretEventHub in nsCanvasFrame. r=roc (687d4997fb)
- Bug 591737 - Add SummaryFrame. r=bz (1b750bfeb8)
- Bug 1165893 - Fix rounding issue in nsDisplaySelectionOverlay::Paint. r=mattwoodro (9994cc983a)
- Bug 1245450 - Only setup AutoSaveRestorePerspectiveIndex for the descendants of the element with perspective. r=roc (fe8a350417)
- Bug 1243282 - Wrap items having clips with a separator. r=mattwoodrow (915737e3d0)
- Bug 1223232 - Use GetUsedBorder() instead of the computed border value when calculating CB size. r=roc (f4c05b30c7)
- Bug 1223232 - Crashtest. (394e112818)
- Bug 1230665 - Make anonymous flex/grid items non-tabbable and non-focusable. r=roc (0d3f70e672)
- Bug 1142295 - Closing descriptor when GECKO_DISPLAY_REFLOW_RULES_FILE is setted. r=erahm (664ae6ba0a)
- minor change (b914bd2602)
- Bug 1237754 part 1 - [css-grid][css-align] Make 'align/justify-content:normal' behave as 'stretch' for Grid containers. r=dholbert (09a9a09629)
- Bug 1237754 part 2 - [css-grid][css-align] Test updates to account for new default behavior for 'align/justify-content'. (5e62e837ff)
- minor of Bug 1141931 part 2 (a12f5b430e)
- Bu 974309: Fixes the IsEditable() logic for table cells. r=ehsan (2a3caa932f)
- Bug 1238137 - Telemetry pings for main thread keyboard-driven scroll input methods. r=ehsan (e9c07427f9)
- Bug 1238137 - Telemetry pings for main thread scrolling to bring the caret into view after moving it in response to keyboard input. r=ehsan (834bc12b7a)
- Bug 1246405 - Declare mTextRun earlier to avoid alignment spill on 64-bit architectures. r=roc (7ba93b72c9)
- Fixing bug 440486. Work around a Windows XP fax dialog bug. r=rstrong. (a59409acd6)
- Bug 1240911 - Prevent SerializedStructuredCloneBuffer from escaping into the heap. r=amarchesini (2c0b7c474b)
- Bug 1240985 - Hold off processing some messages during timeout (r=dvander) (10f6f6d7a2)
- Bug 1146471 - Release thread asserts for IPC (r=dvander) (f94d0ee09a)
- Bug 1240985 - Fix bug where mAwaitingSyncReply can be overwritten in Send after Cancel (r=dvander) (7b95acdca6)
- Bug 1193861: Log to the process log when launching a sandboxed process on Windows. r=billm (0ad1afd0d0)
- Bug 1233061 - add override declarations for MessagePumpForNonMainUIThreads; r=billm (94b9a5bfe9)
- Bug 1172467: Fix an IPC channel file descriptor leak from Nuwa to the child process. r=khuey (908601ed0e)
- Bug 1240985 - Check WasTransactionCanceled after timeout (and avoid timing out) (r=dvander) (33aade0a92)
- Bug 1237458 - Use MOZ_RELEASE_ASSERT for IPC assertions (r=jld) (cb0f058205)
- Bug 1247429 - Warn instead of error if shmem deallocated before IPDL sends it. r=nical (3c94d99b21)
- Bug 1175999 - Deallocate mach SharedMemory properly. r=blassey. (542649b570)
- Bug 1188186 - Fix leak of FDs in |CreateTransport|. r=bds (a40b9a0c58)
- Bg 1240607 - Force CreateWindow hooks to be detours. r=jmathies (895d1c21c4)
- Bug 1209464: Fix missing neutered window region in MessageChannel::WaitForInterruptNotify. Regression from bug 1189709; r=jimm (204256880b)
- Bug 1229825 - Make GIF deinterlacer respect the frame rect bounds. r=tn (904f6bd9b7)
- Bug 1242093 - Fix assertion in Downscaler::ClearRow. r=njn (63ffe82e99)
- Bug 1235859 - Add FrameSize to non-skia downscaler. r=edwin (e7474630e0)
- Bug 1237709: During RasterImage error-handling cleanup, set UniquePtr mAnim to null instead of using reset(), to avoid leaking. r=dholbert (b064f9c20d)
- Bug 1235605 - Use CheckedInt in Deinterlacer and make its buffer allocation fallible. r=tn (f6f3858c65)
- cleanup (f02aa9441e)
- Bug 1242778: Add MOZ_COUNT_CTOR & MOZ_COUNT_DTOR calls to track leaks of imagelib's FrameAnimator class. r=tn (b1aa366694)
- Bug 1241728. Add crashtest. (17d80a3387)
- Bug 1241729. Add crashtest. (bd6d7337d7)
- Bug 1241728. Limit the size of images that we will downscale from to 1048576 pixels. r=edwin (ad38a82aad)
- Bug 1218782 - use fallible allocations in Downscaler.cpp; r=seth (b22caa1121)
- Bug 1224979. Check if we compute usable filters for the downscaler, and if not put the downscaler in error state so it's not used. r=edwin (8fb59463ef)
- Bug 1235297 - Annotate intentional switch fallthroughs to suppress -Wimplicit-fallthrough warnings in image/decoders/. r=tn (094c37c0fe)
- Bug 1238558 (part 1) - Add Decoder::BeforeFinishInternal(). r=tnikkel. (c7922054d6)
- Bug 1238558 (part 2) - Add a test. r=tnikkel. (7e09caf47f)
- Bug 1238551 (part 2) - Add a test. r=tn. (f548a2cb97)
- Bug 1238551 (part 1) - Reject BITMAPV3INFOHEADER BMP images. r=tn. (c4c8f95cb3)
- Bug 1240629. Don't buffer image file data that we are never going to look at in the gap between the header and the pixel data for BMP files. r=njn (f580910cd3)
- Bug 1237171 - Improve a case where ICO and BMP files disagree on an image size. r=tn. (615db65802)
- Bug 1220021 (part 1) - Don't treat 0RGB ICO files as transparent. r=seth. (b97298285f)
- Bug 1220021 (part 2) - Add four reftests. r=seth. (b1e7b58a98)
- Bug 1163856 (Part 2) - Fix tests that depended on image load event timing. r=tn (4304c676a0)
- Bug 1207958 - Fix heuristic for choosing which ICO sub-image to render - r=tn (3d4db5a033)
- Bug 987625 - Conditionally define MOZ_PNG_MAX_DIMENSION. r=jrmuizel (859bae490c)
- Bug 75077 - Interpolate interlaced PNG images instead of libpng blocky display. r=seth (bc17b43fa6)
- fix side-effect of 1219405 (6536821e18)
- Bug 1245845, part 1 - Stop Moz2D Path::CopyToBuilder/TransformedCopyToBuilder implicitly converting the Path's FillRule. r=Bas (ecc552f359)
- Bug 1245845, part 2 - Remove code that is now useless from gfxContext::EnsurePath. r=Bas (2430be2837)
- Bug 1237448 - Moz2Dify two functions in gfxSurfaceDrawable. r=roc. (bb768302c5)
- Bug 1231888 (follow-up) - Simplify CurrentSurface(). r=jrmuizel. (303cea98f3)
- Bug 1247380: Only copy the background if we can succesfully get a snapshot. r=jrmuizel (13b64445e9)
- Bug 1228507 - Initialize mBlendOpacity. r=Bas (b301a2c9f4)
- Bug 1238846 (part 2) - Remove gfxContext::mOriginalDT, which is unused. r=mattwoodrow. (a5b0f948b7)
- Bug 1240819 - cleanup dead branches in gfxXlibNativeRender.cpp. r=jrmuizel (57bbec6693)
- Bug 1234950 - When advancing APZ animations, use the next vsync timestamp instead of the current one, since that is what will be composited. r=mstange (421829d459)
- Bug 1021845 - Don't skip checkerboarding layers during compositing, even if the layer's visible region is empty. r=botond (6cf1497019)
- Bug 1230149 - check bigImgIter to see if it's not null. r=jmuizelaar (aeef579f9f)
- Bug 1248325 - Update BufferTextureHost::GetAsSurface() r=nical (39a8b3ca71)
- reapply per misspatch Bug 1200595 - Consolidate the TextureClient's destruction logic (68966e4dc3)
- Bug 1249245 - Add missing header gfxPrefs.h to GrallocTextureClient.cpp. r=cyu (676669eb01)
- Bug 1245057: Refer to |gfx::IntPoint| in |GrallocTextureHostOGL::SetCropRect|, r=sotaro (99e572f3f6)
- Bug 1240867 - Fix non-unified build bustage in OGLShaderProgram.cpp. r=nical (0071f08285)
- Bug 1238015 - Make sure PTexture actors are destroyed after all messages referring to them are sent. r=sotaro (250f99b4a4)
- Bug 1220895 - Add layerviewer for layer tree & display list visualization NPOTB. r=botond (fa211145a1)
- Bug 1213464 - ImageBridgeChild and CompositorChild should delete their Transport. r=billm (a37a0dbdfd)
- Bug 1234343 (part 1) - Make GfxMemoryImageReporter::sAmount signed. r=Bas. (18f0cb61ec)
- Bug 1234343 (part 2) - Add a missing GfxMemoryImageReporter::DidAlloc() call. r=Bas. (69df7f3674)
- Bug 1245249 - Check actor state before calling Send__delete__(); r=luke (65716a5915)
- Bug 1221418 - A better cleanup method for AsmJSCache::ChildRunnable, r=janv (5c8c023b9d)
- Bug 1235657 - Session storage needs to handle origin attributes correctly - part 1 - createOriginAttributesWithUserContextId, r=huseby (f2df8109ef)
- Bug 1245954 - Console StartTimer/StopTimer and IncrementCounter should run in the owning thread, r=bz (64f73d7759)
- Bug 1245957 - Adding assertions in Console about in which thread is running what, r=bz (291ee70e2d)
- Bug 1248022 - ConsoleEvent.styles can be a sequence of nullable strings, r=bz (b94ec79ac0)
- Bug 1245242 - Normalize to unit vector for DOMMatrix.rotateAxisAngleSelf. r=roc (3a9e684b4d)
- Bug 1236329. Back out the patch for bug 492933 (revision d8012b35413b) because it's not web-compatible in practice. r=smaug (f6540d84c3)
- mTarget can be null in CanvasRenderingContext2D::ClearRect(), return early if so. (13e8a4e26a)
816 lines
27 KiB
C++
816 lines
27 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "FrameAnimator.h"
|
|
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/Move.h"
|
|
#include "imgIContainer.h"
|
|
#include "LookupResult.h"
|
|
#include "MainThreadUtils.h"
|
|
#include "RasterImage.h"
|
|
|
|
#include "pixman.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace gfx;
|
|
|
|
namespace image {
|
|
|
|
int32_t
|
|
FrameAnimator::GetSingleLoopTime() const
|
|
{
|
|
// If we aren't done decoding, we don't know the image's full play time.
|
|
if (!mDoneDecoding) {
|
|
return -1;
|
|
}
|
|
|
|
// If we're not looping, a single loop time has no meaning
|
|
if (mAnimationMode != imgIContainer::kNormalAnimMode) {
|
|
return -1;
|
|
}
|
|
|
|
uint32_t looptime = 0;
|
|
for (uint32_t i = 0; i < mImage->GetNumFrames(); ++i) {
|
|
int32_t timeout = GetTimeoutForFrame(i);
|
|
if (timeout >= 0) {
|
|
looptime += static_cast<uint32_t>(timeout);
|
|
} else {
|
|
// If we have a frame that never times out, we're probably in an error
|
|
// case, but let's handle it more gracefully.
|
|
NS_WARNING("Negative frame timeout - how did this happen?");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return looptime;
|
|
}
|
|
|
|
TimeStamp
|
|
FrameAnimator::GetCurrentImgFrameEndTime() const
|
|
{
|
|
TimeStamp currentFrameTime = mCurrentAnimationFrameTime;
|
|
int32_t timeout =
|
|
GetTimeoutForFrame(mCurrentAnimationFrameIndex);
|
|
|
|
if (timeout < 0) {
|
|
// We need to return a sentinel value in this case, because our logic
|
|
// doesn't work correctly if we have a negative timeout value. We use
|
|
// one year in the future as the sentinel because it works with the loop
|
|
// in RequestRefresh() below.
|
|
// XXX(seth): It'd be preferable to make our logic work correctly with
|
|
// negative timeouts.
|
|
return TimeStamp::NowLoRes() +
|
|
TimeDuration::FromMilliseconds(31536000.0);
|
|
}
|
|
|
|
TimeDuration durationOfTimeout =
|
|
TimeDuration::FromMilliseconds(static_cast<double>(timeout));
|
|
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
|
|
|
|
return currentFrameEndTime;
|
|
}
|
|
|
|
FrameAnimator::RefreshResult
|
|
FrameAnimator::AdvanceFrame(TimeStamp aTime)
|
|
{
|
|
NS_ASSERTION(aTime <= TimeStamp::Now(),
|
|
"Given time appears to be in the future");
|
|
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
|
|
|
|
uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
|
|
uint32_t nextFrameIndex = currentFrameIndex + 1;
|
|
int32_t timeout = 0;
|
|
|
|
RefreshResult ret;
|
|
RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
|
|
|
|
// If we're done decoding, we know we've got everything we're going to get.
|
|
// If we aren't, we only display fully-downloaded frames; everything else
|
|
// gets delayed.
|
|
bool canDisplay = mDoneDecoding ||
|
|
(nextFrame && nextFrame->IsImageComplete());
|
|
|
|
if (!canDisplay) {
|
|
// Uh oh, the frame we want to show is currently being decoded (partial)
|
|
// Wait until the next refresh driver tick and try again
|
|
return ret;
|
|
}
|
|
|
|
// If we're done decoding the next frame, go ahead and display it now and
|
|
// reinit with the next frame's delay time.
|
|
if (mImage->GetNumFrames() == nextFrameIndex) {
|
|
// End of an animation loop...
|
|
|
|
// If we are not looping forever, initialize the loop counter
|
|
if (mLoopRemainingCount < 0 && LoopCount() >= 0) {
|
|
mLoopRemainingCount = LoopCount();
|
|
}
|
|
|
|
// If animation mode is "loop once", or we're at end of loop counter,
|
|
// it's time to stop animating.
|
|
if (mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
|
|
mLoopRemainingCount == 0) {
|
|
ret.animationFinished = true;
|
|
}
|
|
|
|
nextFrameIndex = 0;
|
|
|
|
if (mLoopRemainingCount > 0) {
|
|
mLoopRemainingCount--;
|
|
}
|
|
|
|
// If we're done, exit early.
|
|
if (ret.animationFinished) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
timeout = GetTimeoutForFrame(nextFrameIndex);
|
|
|
|
// Bad data
|
|
if (timeout < 0) {
|
|
ret.animationFinished = true;
|
|
ret.error = true;
|
|
}
|
|
|
|
if (nextFrameIndex == 0) {
|
|
ret.dirtyRect = mFirstFrameRefreshArea;
|
|
} else {
|
|
// Change frame
|
|
if (nextFrameIndex != currentFrameIndex + 1) {
|
|
nextFrame = GetRawFrame(nextFrameIndex);
|
|
}
|
|
|
|
if (!DoBlend(&ret.dirtyRect, currentFrameIndex,
|
|
nextFrameIndex)) {
|
|
// something went wrong, move on to next
|
|
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
|
|
nextFrame->SetCompositingFailed(true);
|
|
mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
|
mCurrentAnimationFrameIndex = nextFrameIndex;
|
|
|
|
ret.error = true;
|
|
return ret;
|
|
}
|
|
|
|
nextFrame->SetCompositingFailed(false);
|
|
}
|
|
|
|
mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
|
|
|
// If we can get closer to the current time by a multiple of the image's loop
|
|
// time, we should.
|
|
uint32_t loopTime = GetSingleLoopTime();
|
|
if (loopTime > 0) {
|
|
TimeDuration delay = aTime - mCurrentAnimationFrameTime;
|
|
if (delay.ToMilliseconds() > loopTime) {
|
|
// Explicitly use integer division to get the floor of the number of
|
|
// loops.
|
|
uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
|
|
mCurrentAnimationFrameTime +=
|
|
TimeDuration::FromMilliseconds(loops * loopTime);
|
|
}
|
|
}
|
|
|
|
// Set currentAnimationFrameIndex at the last possible moment
|
|
mCurrentAnimationFrameIndex = nextFrameIndex;
|
|
|
|
// If we're here, we successfully advanced the frame.
|
|
ret.frameAdvanced = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
FrameAnimator::RefreshResult
|
|
FrameAnimator::RequestRefresh(const TimeStamp& aTime)
|
|
{
|
|
// only advance the frame if the current time is greater than or
|
|
// equal to the current frame's end time.
|
|
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
|
|
|
|
// By default, an empty RefreshResult.
|
|
RefreshResult ret;
|
|
|
|
while (currentFrameEndTime <= aTime) {
|
|
TimeStamp oldFrameEndTime = currentFrameEndTime;
|
|
|
|
RefreshResult frameRes = AdvanceFrame(aTime);
|
|
|
|
// Accumulate our result for returning to callers.
|
|
ret.Accumulate(frameRes);
|
|
|
|
currentFrameEndTime = GetCurrentImgFrameEndTime();
|
|
|
|
// if we didn't advance a frame, and our frame end time didn't change,
|
|
// then we need to break out of this loop & wait for the frame(s)
|
|
// to finish downloading
|
|
if (!frameRes.frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
FrameAnimator::ResetAnimation()
|
|
{
|
|
mCurrentAnimationFrameIndex = 0;
|
|
mLastCompositedFrameIndex = -1;
|
|
}
|
|
|
|
void
|
|
FrameAnimator::SetDoneDecoding(bool aDone)
|
|
{
|
|
mDoneDecoding = aDone;
|
|
}
|
|
|
|
void
|
|
FrameAnimator::SetAnimationMode(uint16_t aAnimationMode)
|
|
{
|
|
mAnimationMode = aAnimationMode;
|
|
}
|
|
|
|
void
|
|
FrameAnimator::InitAnimationFrameTimeIfNecessary()
|
|
{
|
|
if (mCurrentAnimationFrameTime.IsNull()) {
|
|
mCurrentAnimationFrameTime = TimeStamp::Now();
|
|
}
|
|
}
|
|
|
|
void
|
|
FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime)
|
|
{
|
|
mCurrentAnimationFrameTime = aTime;
|
|
}
|
|
|
|
void
|
|
FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect)
|
|
{
|
|
mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect);
|
|
}
|
|
|
|
uint32_t
|
|
FrameAnimator::GetCurrentAnimationFrameIndex() const
|
|
{
|
|
return mCurrentAnimationFrameIndex;
|
|
}
|
|
|
|
nsIntRect
|
|
FrameAnimator::GetFirstFrameRefreshArea() const
|
|
{
|
|
return mFirstFrameRefreshArea;
|
|
}
|
|
|
|
LookupResult
|
|
FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
|
|
{
|
|
MOZ_ASSERT(aFrameNum != 0, "First frame is never composited");
|
|
|
|
// If we have a composited version of this frame, return that.
|
|
if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
|
|
return LookupResult(mCompositingFrame->DrawableRef(), MatchType::EXACT);
|
|
}
|
|
|
|
// Otherwise return the raw frame. DoBlend is required to ensure that we only
|
|
// hit this case if the frame is not paletted and doesn't require compositing.
|
|
LookupResult result =
|
|
SurfaceCache::Lookup(ImageKey(mImage),
|
|
RasterSurfaceKey(mSize,
|
|
DefaultSurfaceFlags(),
|
|
aFrameNum));
|
|
MOZ_ASSERT(!result || !result.DrawableRef()->GetIsPaletted(),
|
|
"About to return a paletted frame");
|
|
return result;
|
|
}
|
|
|
|
int32_t
|
|
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
|
|
{
|
|
int32_t rawTimeout = 0;
|
|
|
|
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
|
|
if (frame) {
|
|
AnimationData data = frame->GetAnimationData();
|
|
rawTimeout = data.mRawTimeout;
|
|
} else if (aFrameNum == 0) {
|
|
rawTimeout = mFirstFrameTimeout;
|
|
} else {
|
|
NS_WARNING("No frame; called GetTimeoutForFrame too early?");
|
|
return 100;
|
|
}
|
|
|
|
// Ensure a minimal time between updates so we don't throttle the UI thread.
|
|
// consider 0 == unspecified and make it fast but not too fast. Unless we
|
|
// have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
|
|
// 207059. The behavior of recent IE and Opera versions seems to be:
|
|
// IE 6/Win:
|
|
// 10 - 50ms go 100ms
|
|
// >50ms go correct speed
|
|
// Opera 7 final/Win:
|
|
// 10ms goes 100ms
|
|
// >10ms go correct speed
|
|
// It seems that there are broken tools out there that set a 0ms or 10ms
|
|
// timeout when they really want a "default" one. So munge values in that
|
|
// range.
|
|
if (rawTimeout >= 0 && rawTimeout <= 10) {
|
|
return 100;
|
|
}
|
|
|
|
return rawTimeout;
|
|
}
|
|
|
|
static void
|
|
DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
|
|
SurfaceMemoryCounterType aType,
|
|
nsTArray<SurfaceMemoryCounter>& aCounters,
|
|
MallocSizeOf aMallocSizeOf)
|
|
{
|
|
// Concoct a SurfaceKey for this surface.
|
|
SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
|
|
DefaultSurfaceFlags(),
|
|
/* aFrameNum = */ 0);
|
|
|
|
// Create a counter for this surface.
|
|
SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
|
|
|
|
// Extract the surface's memory usage information.
|
|
size_t heap = 0, nonHeap = 0;
|
|
aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap);
|
|
counter.Values().SetDecodedHeap(heap);
|
|
counter.Values().SetDecodedNonHeap(nonHeap);
|
|
|
|
// Record it.
|
|
aCounters.AppendElement(counter);
|
|
}
|
|
|
|
void
|
|
FrameAnimator::CollectSizeOfCompositingSurfaces(
|
|
nsTArray<SurfaceMemoryCounter>& aCounters,
|
|
MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
if (mCompositingFrame) {
|
|
DoCollectSizeOfCompositingSurfaces(mCompositingFrame,
|
|
SurfaceMemoryCounterType::COMPOSITING,
|
|
aCounters,
|
|
aMallocSizeOf);
|
|
}
|
|
|
|
if (mCompositingPrevFrame) {
|
|
DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame,
|
|
SurfaceMemoryCounterType::COMPOSITING_PREV,
|
|
aCounters,
|
|
aMallocSizeOf);
|
|
}
|
|
}
|
|
|
|
RawAccessFrameRef
|
|
FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
|
|
{
|
|
LookupResult result =
|
|
SurfaceCache::Lookup(ImageKey(mImage),
|
|
RasterSurfaceKey(mSize,
|
|
DefaultSurfaceFlags(),
|
|
aFrameNum));
|
|
return result ? result.DrawableRef()->RawAccessRef()
|
|
: RawAccessFrameRef();
|
|
}
|
|
|
|
//******************************************************************************
|
|
// DoBlend gets called when the timer for animation get fired and we have to
|
|
// update the composited frame of the animation.
|
|
bool
|
|
FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
|
uint32_t aPrevFrameIndex,
|
|
uint32_t aNextFrameIndex)
|
|
{
|
|
RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
|
|
RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
|
|
|
|
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
|
|
|
|
AnimationData prevFrameData = prevFrame->GetAnimationData();
|
|
if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
|
|
!mCompositingPrevFrame) {
|
|
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
|
|
}
|
|
|
|
bool isFullPrevFrame = prevFrameData.mRect.x == 0 &&
|
|
prevFrameData.mRect.y == 0 &&
|
|
prevFrameData.mRect.width == mSize.width &&
|
|
prevFrameData.mRect.height == mSize.height;
|
|
|
|
// Optimization: DisposeClearAll if the previous frame is the same size as
|
|
// container and it's clearing itself
|
|
if (isFullPrevFrame &&
|
|
(prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
|
|
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
|
|
}
|
|
|
|
AnimationData nextFrameData = nextFrame->GetAnimationData();
|
|
bool isFullNextFrame = nextFrameData.mRect.x == 0 &&
|
|
nextFrameData.mRect.y == 0 &&
|
|
nextFrameData.mRect.width == mSize.width &&
|
|
nextFrameData.mRect.height == mSize.height;
|
|
|
|
if (!nextFrame->GetIsPaletted()) {
|
|
// Optimization: Skip compositing if the previous frame wants to clear the
|
|
// whole image
|
|
if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
|
|
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
|
return true;
|
|
}
|
|
|
|
// Optimization: Skip compositing if this frame is the same size as the
|
|
// container and it's fully drawing over prev frame (no alpha)
|
|
if (isFullNextFrame &&
|
|
(nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
|
|
!nextFrameData.mHasAlpha) {
|
|
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Calculate area that needs updating
|
|
switch (prevFrameData.mDisposalMethod) {
|
|
default:
|
|
MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
|
|
case DisposalMethod::NOT_SPECIFIED:
|
|
case DisposalMethod::KEEP:
|
|
*aDirtyRect = nextFrameData.mRect;
|
|
break;
|
|
|
|
case DisposalMethod::CLEAR_ALL:
|
|
// Whole image container is cleared
|
|
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
|
break;
|
|
|
|
case DisposalMethod::CLEAR:
|
|
// Calc area that needs to be redrawn (the combination of previous and
|
|
// this frame)
|
|
// XXX - This could be done with multiple framechanged calls
|
|
// Having prevFrame way at the top of the image, and nextFrame
|
|
// way at the bottom, and both frames being small, we'd be
|
|
// telling framechanged to refresh the whole image when only two
|
|
// small areas are needed.
|
|
aDirtyRect->UnionRect(nextFrameData.mRect, prevFrameData.mRect);
|
|
break;
|
|
|
|
case DisposalMethod::RESTORE_PREVIOUS:
|
|
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
|
break;
|
|
}
|
|
|
|
// Optimization:
|
|
// Skip compositing if the last composited frame is this frame
|
|
// (Only one composited frame was made for this animation. Example:
|
|
// Only Frame 3 of a 10 frame image required us to build a composite frame
|
|
// On the second loop, we do not need to rebuild the frame
|
|
// since it's still sitting in compositingFrame)
|
|
if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
|
|
return true;
|
|
}
|
|
|
|
bool needToBlankComposite = false;
|
|
|
|
// Create the Compositing Frame
|
|
if (!mCompositingFrame) {
|
|
RefPtr<imgFrame> newFrame = new imgFrame;
|
|
nsresult rv = newFrame->InitForDecoder(mSize,
|
|
SurfaceFormat::B8G8R8A8);
|
|
if (NS_FAILED(rv)) {
|
|
mCompositingFrame.reset();
|
|
return false;
|
|
}
|
|
mCompositingFrame = newFrame->RawAccessRef();
|
|
needToBlankComposite = true;
|
|
} else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
|
|
|
|
// If we are not drawing on top of last composited frame,
|
|
// then we are building a new composite frame, so let's clear it first.
|
|
needToBlankComposite = true;
|
|
}
|
|
|
|
AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();
|
|
|
|
// More optimizations possible when next frame is not transparent
|
|
// But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
|
|
// this "no disposal" optimization is not possible,
|
|
// because the frame in "after disposal operation" state
|
|
// needs to be stored in compositingFrame, so it can be
|
|
// copied into compositingPrevFrame later.
|
|
bool doDisposal = true;
|
|
if (!nextFrameData.mHasAlpha &&
|
|
nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
|
|
if (isFullNextFrame) {
|
|
// Optimization: No need to dispose prev.frame when
|
|
// next frame is full frame and not transparent.
|
|
doDisposal = false;
|
|
// No need to blank the composite frame
|
|
needToBlankComposite = false;
|
|
} else {
|
|
if ((prevFrameData.mRect.x >= nextFrameData.mRect.x) &&
|
|
(prevFrameData.mRect.y >= nextFrameData.mRect.y) &&
|
|
(prevFrameData.mRect.x + prevFrameData.mRect.width <=
|
|
nextFrameData.mRect.x + nextFrameData.mRect.width) &&
|
|
(prevFrameData.mRect.y + prevFrameData.mRect.height <=
|
|
nextFrameData.mRect.y + nextFrameData.mRect.height)) {
|
|
// Optimization: No need to dispose prev.frame when
|
|
// next frame fully overlaps previous frame.
|
|
doDisposal = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (doDisposal) {
|
|
// Dispose of previous: clear, restore, or keep (copy)
|
|
switch (prevFrameData.mDisposalMethod) {
|
|
case DisposalMethod::CLEAR:
|
|
if (needToBlankComposite) {
|
|
// If we just created the composite, it could have anything in its
|
|
// buffer. Clear whole frame
|
|
ClearFrame(compositingFrameData.mRawData,
|
|
compositingFrameData.mRect);
|
|
} else {
|
|
// Only blank out previous frame area (both color & Mask/Alpha)
|
|
ClearFrame(compositingFrameData.mRawData,
|
|
compositingFrameData.mRect,
|
|
prevFrameData.mRect);
|
|
}
|
|
break;
|
|
|
|
case DisposalMethod::CLEAR_ALL:
|
|
ClearFrame(compositingFrameData.mRawData,
|
|
compositingFrameData.mRect);
|
|
break;
|
|
|
|
case DisposalMethod::RESTORE_PREVIOUS:
|
|
// It would be better to copy only the area changed back to
|
|
// compositingFrame.
|
|
if (mCompositingPrevFrame) {
|
|
AnimationData compositingPrevFrameData =
|
|
mCompositingPrevFrame->GetAnimationData();
|
|
|
|
CopyFrameImage(compositingPrevFrameData.mRawData,
|
|
compositingPrevFrameData.mRect,
|
|
compositingFrameData.mRawData,
|
|
compositingFrameData.mRect);
|
|
|
|
// destroy only if we don't need it for this frame's disposal
|
|
if (nextFrameData.mDisposalMethod !=
|
|
DisposalMethod::RESTORE_PREVIOUS) {
|
|
mCompositingPrevFrame.reset();
|
|
}
|
|
} else {
|
|
ClearFrame(compositingFrameData.mRawData,
|
|
compositingFrameData.mRect);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
|
|
case DisposalMethod::NOT_SPECIFIED:
|
|
case DisposalMethod::KEEP:
|
|
// Copy previous frame into compositingFrame before we put the new
|
|
// frame on top
|
|
// Assumes that the previous frame represents a full frame (it could be
|
|
// smaller in size than the container, as long as the frame before it
|
|
// erased itself)
|
|
// Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
|
|
// will always be a valid frame number.
|
|
if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
|
|
if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
|
|
// Just copy the bits
|
|
CopyFrameImage(prevFrameData.mRawData,
|
|
prevFrameData.mRect,
|
|
compositingFrameData.mRawData,
|
|
compositingFrameData.mRect);
|
|
} else {
|
|
if (needToBlankComposite) {
|
|
// Only blank composite when prev is transparent or not full.
|
|
if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
|
|
ClearFrame(compositingFrameData.mRawData,
|
|
compositingFrameData.mRect);
|
|
}
|
|
}
|
|
DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
|
|
prevFrameData.mPaletteDataLength,
|
|
prevFrameData.mHasAlpha,
|
|
compositingFrameData.mRawData,
|
|
compositingFrameData.mRect,
|
|
prevFrameData.mBlendMethod);
|
|
}
|
|
}
|
|
}
|
|
} else if (needToBlankComposite) {
|
|
// If we just created the composite, it could have anything in its
|
|
// buffers. Clear them
|
|
ClearFrame(compositingFrameData.mRawData,
|
|
compositingFrameData.mRect);
|
|
}
|
|
|
|
// Check if the frame we are composing wants the previous image restored after
|
|
// it is done. Don't store it (again) if last frame wanted its image restored
|
|
// too
|
|
if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
|
|
(prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
|
|
// We are storing the whole image.
|
|
// It would be better if we just stored the area that nextFrame is going to
|
|
// overwrite.
|
|
if (!mCompositingPrevFrame) {
|
|
RefPtr<imgFrame> newFrame = new imgFrame;
|
|
nsresult rv = newFrame->InitForDecoder(mSize,
|
|
SurfaceFormat::B8G8R8A8);
|
|
if (NS_FAILED(rv)) {
|
|
mCompositingPrevFrame.reset();
|
|
return false;
|
|
}
|
|
|
|
mCompositingPrevFrame = newFrame->RawAccessRef();
|
|
}
|
|
|
|
AnimationData compositingPrevFrameData =
|
|
mCompositingPrevFrame->GetAnimationData();
|
|
|
|
CopyFrameImage(compositingFrameData.mRawData,
|
|
compositingFrameData.mRect,
|
|
compositingPrevFrameData.mRawData,
|
|
compositingPrevFrameData.mRect);
|
|
|
|
mCompositingPrevFrame->Finish();
|
|
}
|
|
|
|
// blit next frame into it's correct spot
|
|
DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
|
|
nextFrameData.mPaletteDataLength,
|
|
nextFrameData.mHasAlpha,
|
|
compositingFrameData.mRawData,
|
|
compositingFrameData.mRect,
|
|
nextFrameData.mBlendMethod);
|
|
|
|
// Tell the image that it is fully 'downloaded'.
|
|
mCompositingFrame->Finish();
|
|
|
|
mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
|
|
|
|
return true;
|
|
}
|
|
|
|
//******************************************************************************
|
|
// Fill aFrame with black. Does also clears the mask.
|
|
void
|
|
FrameAnimator::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect)
|
|
{
|
|
if (!aFrameData) {
|
|
return;
|
|
}
|
|
|
|
memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4);
|
|
}
|
|
|
|
//******************************************************************************
|
|
void
|
|
FrameAnimator::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect,
|
|
const nsIntRect& aRectToClear)
|
|
{
|
|
if (!aFrameData || aFrameRect.width <= 0 || aFrameRect.height <= 0 ||
|
|
aRectToClear.width <= 0 || aRectToClear.height <= 0) {
|
|
return;
|
|
}
|
|
|
|
nsIntRect toClear = aFrameRect.Intersect(aRectToClear);
|
|
if (toClear.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
uint32_t bytesPerRow = aFrameRect.width * 4;
|
|
for (int row = toClear.y; row < toClear.y + toClear.height; ++row) {
|
|
memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0,
|
|
toClear.width * 4);
|
|
}
|
|
}
|
|
|
|
//******************************************************************************
|
|
// Whether we succeed or fail will not cause a crash, and there's not much
|
|
// we can do about a failure, so there we don't return a nsresult
|
|
bool
|
|
FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc,
|
|
const nsIntRect& aRectSrc,
|
|
uint8_t* aDataDest,
|
|
const nsIntRect& aRectDest)
|
|
{
|
|
uint32_t dataLengthSrc = aRectSrc.width * aRectSrc.height * 4;
|
|
uint32_t dataLengthDest = aRectDest.width * aRectDest.height * 4;
|
|
|
|
if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(aDataDest, aDataSrc, dataLengthDest);
|
|
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const nsIntRect& aSrcRect,
|
|
uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
|
|
uint8_t* aDstPixels, const nsIntRect& aDstRect,
|
|
BlendMethod aBlendMethod)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aSrcData);
|
|
NS_ENSURE_ARG_POINTER(aDstPixels);
|
|
|
|
// According to both AGIF and APNG specs, offsets are unsigned
|
|
if (aSrcRect.x < 0 || aSrcRect.y < 0) {
|
|
NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
// Outside the destination frame, skip it
|
|
if ((aSrcRect.x > aDstRect.width) || (aSrcRect.y > aDstRect.height)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aSrcPaletteLength) {
|
|
// Larger than the destination frame, clip it
|
|
int32_t width = std::min(aSrcRect.width, aDstRect.width - aSrcRect.x);
|
|
int32_t height = std::min(aSrcRect.height, aDstRect.height - aSrcRect.y);
|
|
|
|
// The clipped image must now fully fit within destination image frame
|
|
NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) &&
|
|
(aSrcRect.x + width <= aDstRect.width) &&
|
|
(aSrcRect.y + height <= aDstRect.height),
|
|
"FrameAnimator::DrawFrameTo: Invalid aSrcRect");
|
|
|
|
// clipped image size may be smaller than source, but not larger
|
|
NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
|
|
"FrameAnimator::DrawFrameTo: source must be smaller than dest");
|
|
|
|
// Get pointers to image data
|
|
const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
|
|
uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
|
|
const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
|
|
|
|
// Skip to the right offset
|
|
dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.width);
|
|
if (!aSrcHasAlpha) {
|
|
for (int32_t r = height; r > 0; --r) {
|
|
for (int32_t c = 0; c < width; c++) {
|
|
dstPixels[c] = colormap[srcPixels[c]];
|
|
}
|
|
// Go to the next row in the source resp. destination image
|
|
srcPixels += aSrcRect.width;
|
|
dstPixels += aDstRect.width;
|
|
}
|
|
} else {
|
|
for (int32_t r = height; r > 0; --r) {
|
|
for (int32_t c = 0; c < width; c++) {
|
|
const uint32_t color = colormap[srcPixels[c]];
|
|
if (color) {
|
|
dstPixels[c] = color;
|
|
}
|
|
}
|
|
// Go to the next row in the source resp. destination image
|
|
srcPixels += aSrcRect.width;
|
|
dstPixels += aDstRect.width;
|
|
}
|
|
}
|
|
} else {
|
|
pixman_image_t* src =
|
|
pixman_image_create_bits(
|
|
aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
|
|
aSrcRect.width, aSrcRect.height,
|
|
reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
|
|
aSrcRect.width * 4);
|
|
pixman_image_t* dst =
|
|
pixman_image_create_bits(PIXMAN_a8r8g8b8,
|
|
aDstRect.width,
|
|
aDstRect.height,
|
|
reinterpret_cast<uint32_t*>(aDstPixels),
|
|
aDstRect.width * 4);
|
|
|
|
auto op = aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC
|
|
: PIXMAN_OP_OVER;
|
|
pixman_image_composite32(op,
|
|
src,
|
|
nullptr,
|
|
dst,
|
|
0, 0,
|
|
0, 0,
|
|
aSrcRect.x, aSrcRect.y,
|
|
aSrcRect.width, aSrcRect.height);
|
|
|
|
pixman_image_unref(src);
|
|
pixman_image_unref(dst);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace image
|
|
} // namespace mozilla
|