Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2023-01-10 08:22:51 +08:00
commit a48debaabe
62 changed files with 1748 additions and 1950 deletions

View file

@ -287,8 +287,6 @@ public:
virtual bool AddonHasPermission(const nsAString& aPerm);
virtual bool IsOnCSSUnprefixingWhitelist() override { return false; }
virtual bool IsCodebasePrincipal() const { return false; };
static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); }

View file

@ -21,7 +21,7 @@ interface nsIDOMDocument;
[ptr] native JSPrincipals(JSPrincipals);
[ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);
[scriptable, builtinclass, uuid(3da7b133-f1a0-4de9-a2bc-5c49014c1077)]
[scriptable, builtinclass, uuid(f75f502d-79fd-48be-a079-e5a7b8f80c8b)]
interface nsIPrincipal : nsISerializable
{
/**
@ -333,15 +333,6 @@ interface nsIPrincipal : nsISerializable
* Returns true iff this is the system principal.
*/
[infallible] readonly attribute boolean isSystemPrincipal;
/**
* Returns true if this principal's origin is recognized as being on the
* whitelist of sites that can use the CSS Unprefixing Service.
*
* (This interface provides a trivial implementation, just returning false;
* subclasses can implement something more complex as-needed.)
*/
[noscript,notxpcom,nostdcall] bool IsOnCSSUnprefixingWhitelist();
};
/**

View file

@ -35,7 +35,6 @@
using namespace mozilla;
static bool gIsWhitelistingTestDomains = false;
static bool gCodeBasePrincipalSupport = false;
static bool URIIsImmutable(nsIURI* aURI)
@ -61,10 +60,6 @@ NS_IMPL_CI_INTERFACE_GETTER(nsPrincipal,
/* static */ void
nsPrincipal::InitializeStatics()
{
Preferences::AddBoolVarCache(
&gIsWhitelistingTestDomains,
"layout.css.unprefixing-service.include-test-domains");
Preferences::AddBoolVarCache(&gCodeBasePrincipalSupport,
"signed.applets.codebase_principal_support",
false);
@ -483,196 +478,6 @@ nsPrincipal::Write(nsIObjectOutputStream* aStream)
return NS_OK;
}
// Helper-function to indicate whether the CSS Unprefixing Service
// whitelist should include dummy domains that are only intended for
// use in testing. (Controlled by a pref.)
static inline bool
IsWhitelistingTestDomains()
{
return gIsWhitelistingTestDomains;
}
// Checks if the given URI's host is on our "full domain" whitelist
// (i.e. if it's an exact match against a domain that needs unprefixing)
static bool
IsOnFullDomainWhitelist(nsIURI* aURI)
{
nsAutoCString hostStr;
nsresult rv = aURI->GetHost(hostStr);
NS_ENSURE_SUCCESS(rv, false);
// NOTE: This static whitelist is expected to be short. If that changes,
// we should consider a different representation; e.g. hash-set, prefix tree.
static const nsLiteralCString sFullDomainsOnWhitelist[] = {
// 0th entry only active when testing:
NS_LITERAL_CSTRING("test1.example.org"),
NS_LITERAL_CSTRING("map.baidu.com"),
NS_LITERAL_CSTRING("3g.163.com"),
NS_LITERAL_CSTRING("3glogo.gtimg.com"), // for 3g.163.com
NS_LITERAL_CSTRING("info.3g.qq.com"), // for 3g.qq.com
NS_LITERAL_CSTRING("3gimg.qq.com"), // for 3g.qq.com
NS_LITERAL_CSTRING("img.m.baidu.com"), // for [shucheng|ks].baidu.com
NS_LITERAL_CSTRING("m.mogujie.com"),
NS_LITERAL_CSTRING("touch.qunar.com"),
NS_LITERAL_CSTRING("mjs.sinaimg.cn"), // for sina.cn
NS_LITERAL_CSTRING("static.qiyi.com"), // for m.iqiyi.com
NS_LITERAL_CSTRING("cdn.kuaidi100.com"), // for m.kuaidi100.com
NS_LITERAL_CSTRING("m.pc6.com"),
NS_LITERAL_CSTRING("m.haosou.com"),
NS_LITERAL_CSTRING("m.mi.com"),
NS_LITERAL_CSTRING("wappass.baidu.com"),
NS_LITERAL_CSTRING("m.video.baidu.com"),
NS_LITERAL_CSTRING("m.video.baidu.com"),
NS_LITERAL_CSTRING("imgcache.gtimg.cn"), // for m.v.qq.com
NS_LITERAL_CSTRING("s.tabelog.jp"),
NS_LITERAL_CSTRING("s.yimg.jp"), // for s.tabelog.jp
NS_LITERAL_CSTRING("i.yimg.jp"), // for *.yahoo.co.jp
NS_LITERAL_CSTRING("ai.yimg.jp"), // for *.yahoo.co.jp
NS_LITERAL_CSTRING("m.finance.yahoo.co.jp"),
NS_LITERAL_CSTRING("daily.c.yimg.jp"), // for sp.daily.co.jp
NS_LITERAL_CSTRING("stat100.ameba.jp"), // for ameblo.jp
NS_LITERAL_CSTRING("user.ameba.jp"), // for ameblo.jp
NS_LITERAL_CSTRING("www.goo.ne.jp"),
NS_LITERAL_CSTRING("x.gnst.jp"), // for mobile.gnavi.co.jp
NS_LITERAL_CSTRING("c.x.gnst.jp"), // for mobile.gnavi.co.jp
NS_LITERAL_CSTRING("www.smbc-card.com"),
NS_LITERAL_CSTRING("static.card.jp.rakuten-static.com"), // for rakuten-card.co.jp
NS_LITERAL_CSTRING("img.travel.rakuten.co.jp"), // for travel.rakuten.co.jp
NS_LITERAL_CSTRING("img.mixi.net"), // for mixi.jp
NS_LITERAL_CSTRING("girlschannel.net"),
NS_LITERAL_CSTRING("www.fancl.co.jp"),
NS_LITERAL_CSTRING("s.cosme.net"),
NS_LITERAL_CSTRING("www.sapporobeer.jp"),
NS_LITERAL_CSTRING("www.mapion.co.jp"),
NS_LITERAL_CSTRING("touch.navitime.co.jp"),
NS_LITERAL_CSTRING("sp.mbga.jp"),
NS_LITERAL_CSTRING("ava-a.sp.mbga.jp"), // for sp.mbga.jp
NS_LITERAL_CSTRING("www.ntv.co.jp"),
NS_LITERAL_CSTRING("mobile.suntory.co.jp"), // for suntory.jp
NS_LITERAL_CSTRING("www.aeonsquare.net"),
NS_LITERAL_CSTRING("mw.nikkei.com"),
NS_LITERAL_CSTRING("www.nhk.or.jp"),
NS_LITERAL_CSTRING("www.tokyo-sports.co.jp"),
NS_LITERAL_CSTRING("www.bellemaison.jp"),
NS_LITERAL_CSTRING("www.kuronekoyamato.co.jp"),
NS_LITERAL_CSTRING("formassist.jp"), // for orico.jp
NS_LITERAL_CSTRING("sp.m.reuters.co.jp"),
NS_LITERAL_CSTRING("www.atre.co.jp"),
NS_LITERAL_CSTRING("www.jtb.co.jp"),
NS_LITERAL_CSTRING("www.sharp.co.jp"),
NS_LITERAL_CSTRING("www.biccamera.com"),
NS_LITERAL_CSTRING("weathernews.jp"),
NS_LITERAL_CSTRING("cache.ymail.jp"), // for www.yamada-denkiweb.com
};
static const size_t sNumFullDomainsOnWhitelist =
MOZ_ARRAY_LENGTH(sFullDomainsOnWhitelist);
// Skip 0th (dummy) entry in whitelist, unless a pref is enabled.
const size_t firstWhitelistIdx = IsWhitelistingTestDomains() ? 0 : 1;
for (size_t i = firstWhitelistIdx; i < sNumFullDomainsOnWhitelist; ++i) {
if (hostStr == sFullDomainsOnWhitelist[i]) {
return true;
}
}
return false;
}
// Checks if the given URI's host is on our "base domain" whitelist
// (i.e. if it's a subdomain of some host that we've whitelisted as needing
// unprefixing for all its subdomains)
static bool
IsOnBaseDomainWhitelist(nsIURI* aURI)
{
static const nsLiteralCString sBaseDomainsOnWhitelist[] = {
// 0th entry only active when testing:
NS_LITERAL_CSTRING("test2.example.org"),
NS_LITERAL_CSTRING("tbcdn.cn"), // for m.taobao.com
NS_LITERAL_CSTRING("alicdn.com"), // for m.taobao.com
NS_LITERAL_CSTRING("dpfile.com"), // for m.dianping.com
NS_LITERAL_CSTRING("hao123img.com"), // for hao123.com
NS_LITERAL_CSTRING("tabelog.k-img.com"), // for s.tabelog.com
NS_LITERAL_CSTRING("tsite.jp"), // for *.tsite.jp
};
static const size_t sNumBaseDomainsOnWhitelist =
MOZ_ARRAY_LENGTH(sBaseDomainsOnWhitelist);
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (tldService) {
// Skip 0th test-entry in whitelist, unless the testing pref is enabled.
const size_t firstWhitelistIdx = IsWhitelistingTestDomains() ? 0 : 1;
// Right now, the test base-domain "test2.example.org" is the only entry in
// its whitelist with a nonzero "depth". So we'll only bother going beyond
// 0 depth (to 1) if that entry is enabled. (No point in slowing down the
// normal codepath, for the benefit of a disabled test domain.) If we add a
// "real" base-domain with a depth of >= 1 to our whitelist, we can get rid
// of this conditional & just make this a static variable.
const uint32_t maxSubdomainDepth = IsWhitelistingTestDomains() ? 1 : 0;
for (uint32_t subdomainDepth = 0;
subdomainDepth <= maxSubdomainDepth; ++subdomainDepth) {
// Get the base domain (to depth |subdomainDepth|) from passed-in URI:
nsAutoCString baseDomainStr;
nsresult rv = tldService->GetBaseDomain(aURI, subdomainDepth,
baseDomainStr);
if (NS_FAILED(rv)) {
// aURI doesn't have |subdomainDepth| levels of subdomains. If we got
// here without a match yet, then aURI is not on our whitelist.
return false;
}
// Compare the base domain against each entry in our whitelist:
for (size_t i = firstWhitelistIdx; i < sNumBaseDomainsOnWhitelist; ++i) {
if (baseDomainStr == sBaseDomainsOnWhitelist[i]) {
return true;
}
}
}
}
return false;
}
// The actual (non-cached) implementation of IsOnCSSUnprefixingWhitelist():
static bool
IsOnCSSUnprefixingWhitelistImpl(nsIURI* aURI)
{
// Check scheme, so we can drop any non-HTTP/HTTPS URIs right away
nsAutoCString schemeStr;
nsresult rv = aURI->GetScheme(schemeStr);
NS_ENSURE_SUCCESS(rv, false);
// Only proceed if scheme is "http" or "https"
if (!(StringBeginsWith(schemeStr, NS_LITERAL_CSTRING("http")) &&
(schemeStr.Length() == 4 ||
(schemeStr.Length() == 5 && schemeStr[4] == 's')))) {
return false;
}
return (IsOnFullDomainWhitelist(aURI) ||
IsOnBaseDomainWhitelist(aURI));
}
bool
nsPrincipal::IsOnCSSUnprefixingWhitelist()
{
if (mIsOnCSSUnprefixingWhitelist.isNothing()) {
// Value not cached -- perform our lazy whitelist-check.
// (NOTE: If our URI is mutable, we just assume it's not on the whitelist,
// since our caching strategy won't work. This isn't expected to be common.)
mIsOnCSSUnprefixingWhitelist.emplace(
mCodebaseImmutable &&
IsOnCSSUnprefixingWhitelistImpl(mCodebase));
}
return *mIsOnCSSUnprefixingWhitelist;
}
/************************************************************************************************************************/
NS_IMPL_CLASSINFO(nsExpandedPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY,
@ -837,15 +642,6 @@ nsExpandedPrincipal::AddonHasPermission(const nsAString& aPerm)
return false;
}
bool
nsExpandedPrincipal::IsOnCSSUnprefixingWhitelist()
{
// CSS Unprefixing Whitelist is a per-origin thing; doesn't really make sense
// for an expanded principal. (And probably shouldn't be needed.)
return false;
}
nsresult
nsExpandedPrincipal::GetScriptLocation(nsACString& aStr)
{

View file

@ -25,7 +25,6 @@ public:
NS_IMETHOD GetDomain(nsIURI** aDomain) override;
NS_IMETHOD SetDomain(nsIURI* aDomain) override;
NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
virtual bool IsOnCSSUnprefixingWhitelist() override;
bool IsCodebasePrincipal() const override { return true; }
nsresult GetOriginInternal(nsACString& aOrigin) override;
@ -55,7 +54,6 @@ public:
bool mCodebaseImmutable;
bool mDomainImmutable;
bool mInitialized;
mozilla::Maybe<bool> mIsOnCSSUnprefixingWhitelist; // Lazily-computed
protected:
virtual ~nsPrincipal();
@ -81,7 +79,6 @@ public:
NS_IMETHOD SetDomain(nsIURI* aDomain) override;
NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
virtual bool AddonHasPermission(const nsAString& aPerm) override;
virtual bool IsOnCSSUnprefixingWhitelist() override;
virtual nsresult GetScriptLocation(nsACString &aStr) override;
nsresult GetOriginInternal(nsACString& aOrigin) override;

View file

@ -1770,7 +1770,7 @@ Manager::~Manager()
// Don't spin the event loop in the destructor waiting for the thread to
// shutdown. Defer this to the main thread, instead.
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(ioThread, &nsIThread::Shutdown)));
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(ioThread, &nsIThread::AsyncShutdown)));
}
void

View file

@ -3758,7 +3758,11 @@ CanvasRenderingContext2D::SetFontInternal(const nsAString& aFont,
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
if (!presShell) {
aError.Throw(NS_ERROR_FAILURE);
// Do not throw here. We may be in a situation where we're loading in an iframe
// that is sandboxed, and/or initially hidden with display:none, in which case
// we don't want to throw an error but silently fail.
// If we don't do this, JS trying to set context.font to something will abort,
// breaking e.g. third party serviced graphs.
return false;
}

View file

@ -12487,7 +12487,7 @@ ConnectionPool::ShutdownThread(ThreadInfo& aThreadInfo)
NS_DISPATCH_NORMAL));
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
NewRunnableMethod(thread, &nsIThread::Shutdown)));
NewRunnableMethod(thread, &nsIThread::AsyncShutdown)));
mTotalThreadCount--;
}

View file

@ -429,6 +429,9 @@ private:
#endif
DECL_GFX_PREF(Live, "gl.require-hardware", RequireHardwareGL, bool, false);
DECL_GFX_PREF(Live, "image.animated.decode-on-demand.threshold-kb", ImageAnimatedDecodeOnDemandThresholdKB, uint32_t, 256*1024);
DECL_GFX_PREF(Live, "image.animated.decode-on-demand.batch-size", ImageAnimatedDecodeOnDemandBatchSize, uint32_t, 6);
DECL_GFX_PREF(Live, "image.animated.resume-from-last-displayed", ImageAnimatedResumeFromLastDisplayed, bool, false);
DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500);
DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false);
@ -437,6 +440,7 @@ private:
DECL_GFX_PREF(Once, "image.layerize.always", ImageLayerizeAlways, bool, false);
DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000);
DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false);
DECL_GFX_PREF(Once, "image.mem.animated.discardable", ImageMemAnimatedDiscardable, bool, false);
DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);

View file

@ -0,0 +1,324 @@
/* -*- 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 "AnimationFrameBuffer.h"
#include "mozilla/Move.h" // for Move
namespace mozilla {
namespace image {
AnimationFrameBuffer::AnimationFrameBuffer()
: mThreshold(0)
, mBatch(0)
, mPending(0)
, mAdvance(0)
, mInsertIndex(0)
, mGetIndex(0)
, mSizeKnown(false)
, mRedecodeError(false)
{ }
void
AnimationFrameBuffer::Initialize(size_t aThreshold,
size_t aBatch,
size_t aStartFrame)
{
MOZ_ASSERT(mThreshold == 0);
MOZ_ASSERT(mBatch == 0);
MOZ_ASSERT(mPending == 0);
MOZ_ASSERT(mAdvance == 0);
MOZ_ASSERT(mFrames.IsEmpty());
mThreshold = aThreshold;
mBatch = aBatch;
mAdvance = aStartFrame;
if (mBatch > SIZE_MAX/4) {
// Batch size is so big, we will just end up decoding the whole animation.
mBatch = SIZE_MAX/4;
} else if (mBatch < 1) {
// Never permit a batch size smaller than 1. We always want to be asking for
// at least one frame to start.
mBatch = 1;
}
// To simplify the code, we have the assumption that the threshold for
// entering discard-after-display mode is at least twice the batch size (since
// that is the most frames-pending-decode we will request) + 1 for the current
// frame. That way the redecoded frames being inserted will never risk
// overlapping the frames we will discard due to the animation progressing.
// That may cause us to use a little more memory than we want but that is an
// acceptable tradeoff for simplicity.
size_t minThreshold = 2 * mBatch + 1;
if (mThreshold < minThreshold) {
mThreshold = minThreshold;
}
// The maximum number of frames we should ever have decoded at one time is
// twice the batch. That is a good as number as any to start our decoding at.
mPending = mBatch * 2;
}
bool
AnimationFrameBuffer::Insert(RawAccessFrameRef&& aFrame)
{
// We should only insert new frames if we actually asked for them.
MOZ_ASSERT(mPending > 0);
if (mSizeKnown) {
// We only insert after the size is known if we are repeating the animation
// and we did not keep all of the frames. Replace whatever is there
// (probably an empty frame) with the new frame.
MOZ_ASSERT(MayDiscard());
// The first decode produced fewer frames than the redecodes, presumably
// because it hit an out-of-memory error which later attempts avoided. Just
// stop the animation because we can't tell the image that we have more
// frames now.
if (mInsertIndex >= mFrames.Length()) {
mRedecodeError = true;
mPending = 0;
return false;
}
if (mInsertIndex > 0) {
MOZ_ASSERT(!mFrames[mInsertIndex]);
mFrames[mInsertIndex] = Move(aFrame);
}
} else if (mInsertIndex == mFrames.Length()) {
// We are still on the first pass of the animation decoding, so this is
// the first time we have seen this frame.
mFrames.AppendElement(Move(aFrame));
if (mInsertIndex == mThreshold) {
// We just tripped over the threshold for the first time. This is our
// chance to do any clearing of already displayed frames. After this,
// we only need to release as we advance or force a restart.
MOZ_ASSERT(MayDiscard());
MOZ_ASSERT(mGetIndex < mInsertIndex);
for (size_t i = 1; i < mGetIndex; ++i) {
RawAccessFrameRef discard = Move(mFrames[i]);
}
}
} else if (mInsertIndex > 0) {
// We were forced to restart an animation before we decoded the last
// frame. If we were discarding frames, then we tossed what we had
// except for the first frame.
MOZ_ASSERT(mInsertIndex < mFrames.Length());
MOZ_ASSERT(!mFrames[mInsertIndex]);
MOZ_ASSERT(MayDiscard());
mFrames[mInsertIndex] = Move(aFrame);
} else { // mInsertIndex == 0
// We were forced to restart an animation before we decoded the last
// frame. We don't need the redecoded first frame because we always keep
// the original.
MOZ_ASSERT(MayDiscard());
}
MOZ_ASSERT(mFrames[mInsertIndex]);
++mInsertIndex;
// Ensure we only request more decoded frames if we actually need them. If we
// need to advance to a certain point in the animation on behalf of the owner,
// then do so. This ensures we keep decoding. If the batch size is really
// small (i.e. 1), it is possible advancing will request the decoder to
// "restart", but we haven't told it to stop yet. Note that we skip the first
// insert because we actually start "advanced" to the first frame anyways.
bool continueDecoding = --mPending > 0;
if (mAdvance > 0 && mInsertIndex > 1) {
continueDecoding |= AdvanceInternal();
--mAdvance;
}
return continueDecoding;
}
bool
AnimationFrameBuffer::MarkComplete()
{
// We may have stopped decoding at a different point in the animation than we
// did previously. That means the decoder likely hit a new error, e.g. OOM.
// This will prevent us from advancing as well, because we are missing the
// required frames to blend.
//
// XXX(aosmond): In an ideal world, we would be generating full frames, and
// the consumer of our data doesn't care about our internal state. It simply
// knows about the first frame, the current frame, and how long to display the
// current frame.
if (NS_WARN_IF(mInsertIndex != mFrames.Length())) {
MOZ_ASSERT(mSizeKnown);
mRedecodeError = true;
mPending = 0;
}
// We reached the end of the animation, the next frame we get, if we get
// another, will be the first frame again.
mInsertIndex = 0;
// Since we only request advancing when we want to resume at a certain point
// in the animation, we should never exceed the number of frames.
MOZ_ASSERT(mAdvance == 0);
if (!mSizeKnown) {
// We just received the last frame in the animation. Compact the frame array
// because we know we won't need to grow beyond here.
mSizeKnown = true;
mFrames.Compact();
if (!MayDiscard()) {
// If we did not meet the threshold, then we know we want to keep all of the
// frames. If we also hit the last frame, we don't want to ask for more.
mPending = 0;
}
}
return mPending > 0;
}
DrawableFrameRef
AnimationFrameBuffer::Get(size_t aFrame)
{
// We should not have asked for a frame if we never inserted.
if (mFrames.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE("Calling Get() when we have no frames");
return DrawableFrameRef();
}
// If we don't have that frame, return an empty frame ref.
if (aFrame >= mFrames.Length()) {
return DrawableFrameRef();
}
// We've got the requested frame because we are not discarding frames. While
// we typically should have not run out of frames since we ask for more before
// we want them, it is possible the decoder is behind.
if (!mFrames[aFrame]) {
MOZ_ASSERT(MayDiscard());
return DrawableFrameRef();
}
// If we are advancing on behalf of the animation, we don't expect it to be
// getting any frames (besides the first) until we get the desired frame.
MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
return mFrames[aFrame]->DrawableRef();
}
bool
AnimationFrameBuffer::AdvanceTo(size_t aExpectedFrame)
{
// The owner should only be advancing once it has reached the requested frame
// in the animation.
MOZ_ASSERT(mAdvance == 0);
bool restartDecoder = AdvanceInternal();
// Advancing should always be successful, as it should only happen after the
// owner has accessed the next (now current) frame.
MOZ_ASSERT(mGetIndex == aExpectedFrame);
return restartDecoder;
}
bool
AnimationFrameBuffer::AdvanceInternal()
{
// We should not have advanced if we never inserted.
if (mFrames.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE("Calling Advance() when we have no frames");
return false;
}
// We only want to change the current frame index if we have advanced. This
// means either a higher frame index, or going back to the beginning.
size_t framesLength = mFrames.Length();
// We should never have advanced beyond the frame buffer.
MOZ_ASSERT(mGetIndex < framesLength);
// We should never advance if the current frame is null -- it needs to know
// the timeout from it at least to know when to advance.
MOZ_ASSERT(mFrames[mGetIndex]);
if (++mGetIndex == framesLength) {
MOZ_ASSERT(mSizeKnown);
mGetIndex = 0;
}
// The owner should have already accessed the next frame, so it should also
// be available.
MOZ_ASSERT(mFrames[mGetIndex]);
// If we moved forward, that means we can remove the previous frame, assuming
// that frame is not the first frame. If we looped and are back at the first
// frame, we can remove the last frame.
if (MayDiscard()) {
RawAccessFrameRef discard;
if (mGetIndex > 1) {
discard = Move(mFrames[mGetIndex - 1]);
} else if (mGetIndex == 0) {
MOZ_ASSERT(mSizeKnown && framesLength > 1);
discard = Move(mFrames[framesLength - 1]);
}
}
if (!mRedecodeError && (!mSizeKnown || MayDiscard())) {
// Calculate how many frames we have requested ahead of the current frame.
size_t buffered = mPending;
if (mGetIndex > mInsertIndex) {
// It wrapped around and we are decoding the beginning again before the
// the display has finished the loop.
MOZ_ASSERT(mSizeKnown);
buffered += mInsertIndex + framesLength - mGetIndex - 1;
} else {
buffered += mInsertIndex - mGetIndex - 1;
}
if (buffered < mBatch) {
// If we have fewer frames than the batch size, then ask for more. If we
// do not have any pending, then we know that there is no active decoding.
mPending += mBatch;
return mPending == mBatch;
}
}
return false;
}
bool
AnimationFrameBuffer::Reset()
{
// The animation needs to start back at the beginning.
mGetIndex = 0;
mAdvance = 0;
if (!MayDiscard()) {
// If we haven't crossed the threshold, then we know by definition we have
// not discarded any frames. If we previously requested more frames, but
// it would have been more than we would have buffered otherwise, we can
// stop the decoding after one more frame.
if (mPending > 1 && mInsertIndex - 1 >= mBatch * 2) {
MOZ_ASSERT(!mSizeKnown);
mPending = 1;
}
// Either the decoder is still running, or we have enough frames already.
// No need for us to restart it.
return false;
}
// Discard all frames besides the first, because the decoder always expects
// that when it re-inserts a frame, it is not present. (It doesn't re-insert
// the first frame.)
for (size_t i = 1; i < mFrames.Length(); ++i) {
RawAccessFrameRef discard = Move(mFrames[i]);
}
mInsertIndex = 0;
// If we hit an error after redecoding, we never want to restart decoding.
if (mRedecodeError) {
MOZ_ASSERT(mPending == 0);
return false;
}
bool restartDecoder = mPending == 0;
mPending = 2 * mBatch;
return restartDecoder;
}
} // namespace image
} // namespace mozilla

View file

@ -0,0 +1,204 @@
/* -*- 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/. */
#ifndef mozilla_image_AnimationFrameBuffer_h
#define mozilla_image_AnimationFrameBuffer_h
#include "ISurfaceProvider.h"
namespace mozilla {
namespace image {
/**
* An AnimationFrameBuffer owns the frames outputted by an animated image
* decoder as well as directing its owner on how to drive the decoder,
* whether to produce more or to stop.
*
* Based upon its given configuration parameters, it will retain up to a
* certain number of frames in the buffer before deciding to discard previous
* frames, and relying upon the decoder to recreate older frames when the
* animation loops. It will also request that the decoder stop producing more
* frames when the display of the frames are far behind -- this allows other
* tasks and images which require decoding to take execution priority.
*
* The desire is that smaller animated images should be kept completely in
* memory while larger animated images should only keep a certain number of
* frames to minimize our memory footprint at the cost of CPU.
*/
class AnimationFrameBuffer final
{
public:
AnimationFrameBuffer();
/**
* Configure the frame buffer with a particular threshold and batch size. Note
* that the frame buffer may adjust the given values.
*
* @param aThreshold Maximum number of frames that may be stored in the frame
* buffer before it may discard already displayed frames.
* Once exceeded, it will discard the previous frame to the
* current frame whenever Advance is called. It always
* retains the first frame.
*
* @param aBatch Number of frames we request to be decoded each time it
* decides we need more.
*
* @param aStartFrame The starting frame for the animation. The frame buffer
* will auto-advance (and thus keep the decoding pipeline
* going) until it has reached this frame. Useful when the
* animation was progressing, but the surface was
* discarded, and we had to redecode.
*/
void Initialize(size_t aThreshold, size_t aBatch, size_t aStartFrame);
/**
* Access a specific frame from the frame buffer. It should generally access
* frames in sequential order, increasing in tandem with AdvanceTo calls. The
* first frame may be accessed at any time. The access order should start with
* the same value as that given in Initialize (aStartFrame).
*
* @param aFrame The frame index to access.
*
* @returns The frame, if available.
*/
DrawableFrameRef Get(size_t aFrame);
/**
* Inserts a frame into the frame buffer. If it has yet to fully decode the
* animated image yet, then it will append the frame to its internal buffer.
* If it has been fully decoded, it will replace the next frame in its buffer
* with the given frame.
*
* Once we have a sufficient number of frames buffered relative to the
* currently displayed frame, it will return false to indicate the caller
* should stop decoding.
*
* @param aFrame The frame to insert into the buffer.
*
* @returns True if the decoder should decode another frame.
*/
bool Insert(RawAccessFrameRef&& aFrame);
/**
* This should be called after the last frame has been inserted. If the buffer
* is discarding old frames, it may request more frames to be decoded. In this
* case that means the decoder should start again from the beginning. This
* return value should be used in preference to that of the Insert call.
*
* @returns True if the decoder should decode another frame.
*/
bool MarkComplete();
/**
* Advance the currently displayed frame of the frame buffer. If it reaches
* the end, it will loop back to the beginning. It should not be called unless
* a call to Get has returned a valid frame for the next frame index.
*
* As we advance, the number of frames we have buffered ahead of the current
* will shrink. Once that becomes too few, we will request a batch-sized set
* of frames to be decoded from the decoder.
*
* @param aExpectedFrame The frame we expect to have advanced to. This is
* used for confirmation purposes (e.g. asserts).
*
* @returns True if the caller should restart the decoder.
*/
bool AdvanceTo(size_t aExpectedFrame);
/**
* Resets the currently displayed frame of the frame buffer to the beginning.
* If the buffer is discarding old frames, it will actually discard all frames
* besides the first.
*
* @returns True if the caller should restart the decoder.
*/
bool Reset();
/**
* @returns True if frames post-advance may be discarded and redecoded on
* demand, else false.
*/
bool MayDiscard() const { return mFrames.Length() > mThreshold; }
/**
* @returns True if the frame buffer was ever marked as complete. This implies
* that the total number of frames is known and may be gotten from
* Frames().Length().
*/
bool SizeKnown() const { return mSizeKnown; }
/**
* @returns True if encountered an error during redecode which should cause
* the caller to stop inserting frames.
*/
bool HasRedecodeError() const { return mRedecodeError; }
/**
* @returns The current frame index we have advanced to.
*/
size_t Displayed() const { return mGetIndex; }
/**
* @returns Outstanding frames desired from the decoder.
*/
size_t PendingDecode() const { return mPending; }
/**
* @returns Outstanding frames to advance internally.
*/
size_t PendingAdvance() const { return mAdvance; }
/**
* @returns Number of frames we request to be decoded each time it decides we
* need more.
*/
size_t Batch() const { return mBatch; }
/**
* @returns Maximum number of frames before we start discarding previous
* frames post-advance.
*/
size_t Threshold() const { return mThreshold; }
/**
* @returns The frames of this animation, in order. May contain empty indices.
*/
const nsTArray<RawAccessFrameRef>& Frames() const { return mFrames; }
private:
bool AdvanceInternal();
/// The frames of this animation, in order, but may have holes if discarding.
nsTArray<RawAccessFrameRef> mFrames;
// The maximum number of frames we can have before discarding.
size_t mThreshold;
// The minimum number of frames that we want buffered ahead of the display.
size_t mBatch;
// The number of frames to decode before we stop.
size_t mPending;
// The number of frames we need to auto-advance to synchronize with the caller.
size_t mAdvance;
// The mFrames index in which to insert the next decoded frame.
size_t mInsertIndex;
// The mFrames index that we have advanced to.
size_t mGetIndex;
// True if the total number of frames is known.
bool mSizeKnown;
// True if we encountered an error while redecoding.
bool mRedecodeError;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_AnimationFrameBuffer_h

View file

@ -8,6 +8,7 @@
#include "gfxPrefs.h"
#include "nsProxyRelease.h"
#include "DecodePool.h"
#include "Decoder.h"
using namespace mozilla::gfx;
@ -17,7 +18,8 @@ namespace image {
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
const SurfaceKey& aSurfaceKey,
NotNull<Decoder*> aDecoder)
NotNull<Decoder*> aDecoder,
size_t aCurrentFrame)
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
AvailabilityState::StartAsPlaceholder())
, mImage(aImage.get())
@ -29,6 +31,22 @@ AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
"Use MetadataDecodingTask for metadata decodes");
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
"Use DecodedSurfaceProvider for single-frame image decodes");
// We still produce paletted surfaces for GIF which means the frames are
// smaller than one would expect for APNG. This may be removed if/when
// bug 1337111 lands and it is enabled by default.
size_t pixelSize = aDecoder->GetType() == DecoderType::GIF
? sizeof(uint8_t) : sizeof(uint32_t);
// Calculate how many frames we need to decode in this animation before we
// enter decode-on-demand mode.
IntSize frameSize = aSurfaceKey.Size();
size_t threshold =
(size_t(gfxPrefs::ImageAnimatedDecodeOnDemandThresholdKB()) * 1024) /
(pixelSize * frameSize.width * frameSize.height);
size_t batch = gfxPrefs::ImageAnimatedDecodeOnDemandBatchSize();
mFrames.Initialize(threshold, batch, aCurrentFrame);
}
AnimationSurfaceProvider::~AnimationSurfaceProvider()
@ -43,14 +61,77 @@ AnimationSurfaceProvider::DropImageReference()
return; // Nothing to do.
}
// RasterImage objects need to be destroyed on the main thread. We also need
// to destroy them asynchronously, because if our surface cache entry is
// destroyed and we were the only thing keeping |mImage| alive, RasterImage's
// destructor may call into the surface cache while whatever code caused us to
// get evicted is holding the surface cache lock, causing deadlock.
RefPtr<RasterImage> image = mImage;
mImage = nullptr;
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
// RasterImage objects need to be destroyed on the main thread.
SurfaceCache::ReleaseImageOnMainThread(mImage.forget());
}
void
AnimationSurfaceProvider::Reset()
{
// We want to go back to the beginning.
bool mayDiscard;
bool restartDecoder = false;
{
MutexAutoLock lock(mFramesMutex);
// If we have not crossed the threshold, we know we haven't discarded any
// frames, and thus we know it is safe move our display index back to the
// very beginning. It would be cleaner to let the frame buffer make this
// decision inside the AnimationFrameBuffer::Reset method, but if we have
// crossed the threshold, we need to hold onto the decoding mutex too. We
// should avoid blocking the main thread on the decoder threads.
mayDiscard = mFrames.MayDiscard();
if (!mayDiscard) {
restartDecoder = mFrames.Reset();
}
}
if (mayDiscard) {
// We are over the threshold and have started discarding old frames. In
// that case we need to seize the decoding mutex. Thankfully we know that
// we are in the process of decoding at most the batch size frames, so
// this should not take too long to acquire.
MutexAutoLock lock(mDecodingMutex);
// We may have hit an error while redecoding. Because FrameAnimator is
// tightly coupled to our own state, that means we would need to go through
// some heroics to resume animating in those cases. The typical reason for
// a redecode to fail is out of memory, and recycling should prevent most of
// those errors. When image.animated.generate-full-frames has shipped
// enabled on a release or two, we can simply remove the old FrameAnimator
// blending code and simplify this quite a bit -- just always pop the next
// full frame and timeout off the stack.
if (mDecoder) {
mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
MOZ_ASSERT(mDecoder);
MutexAutoLock lock2(mFramesMutex);
restartDecoder = mFrames.Reset();
} else {
MOZ_ASSERT(mFrames.HasRedecodeError());
}
}
if (restartDecoder) {
DecodePool::Singleton()->AsyncRun(this);
}
}
void
AnimationSurfaceProvider::Advance(size_t aFrame)
{
bool restartDecoder;
{
// Typical advancement of a frame.
MutexAutoLock lock(mFramesMutex);
restartDecoder = mFrames.AdvanceTo(aFrame);
}
if (restartDecoder) {
DecodePool::Singleton()->AsyncRun(this);
}
}
DrawableFrameRef
@ -63,19 +144,7 @@ AnimationSurfaceProvider::DrawableRef(size_t aFrame)
return DrawableFrameRef();
}
if (mFrames.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames");
return DrawableFrameRef();
}
// If we don't have that frame, return an empty frame ref.
if (aFrame >= mFrames.Length()) {
return DrawableFrameRef();
}
// We've got the requested frame. Return it.
MOZ_ASSERT(mFrames[aFrame]);
return mFrames[aFrame]->DrawableRef();
return mFrames.Get(aFrame);
}
bool
@ -88,13 +157,20 @@ AnimationSurfaceProvider::IsFinished() const
return false;
}
if (mFrames.IsEmpty()) {
if (mFrames.Frames().IsEmpty()) {
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
return false;
}
// As long as we have at least one finished frame, we're finished.
return mFrames[0]->IsFinished();
return mFrames.Frames()[0]->IsFinished();
}
bool
AnimationSurfaceProvider::IsFullyDecoded() const
{
MutexAutoLock lock(mFramesMutex);
return mFrames.SizeKnown() && !mFrames.MayDiscard();
}
size_t
@ -125,8 +201,10 @@ AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
// that we must be careful to always use the same ordering elsewhere.
MutexAutoLock lock(mFramesMutex);
for (const RawAccessFrameRef& frame : mFrames) {
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
for (const RawAccessFrameRef& frame : mFrames.Frames()) {
if (frame) {
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
}
}
}
@ -135,7 +213,7 @@ AnimationSurfaceProvider::Run()
{
MutexAutoLock lock(mDecodingMutex);
if (!mDecoder || !mImage) {
if (!mDecoder) {
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
return;
}
@ -150,15 +228,34 @@ AnimationSurfaceProvider::Run()
// Since we're not sure, rather than call CheckForNewFrameAtYield() here
// we call CheckForNewFrameAtTerminalState(), which handles both of these
// possibilities.
CheckForNewFrameAtTerminalState();
// We're done!
bool continueDecoding = CheckForNewFrameAtTerminalState();
FinishDecoding();
return;
// Even if it is the last frame, we may not have enough frames buffered
// ahead of the current. If we are shutting down, we want to ensure we
// release the thread as soon as possible. The animation may advance even
// during shutdown, which keeps us decoding, and thus blocking the decode
// pool during teardown.
if (!mDecoder || !continueDecoding ||
DecodePool::Singleton()->IsShuttingDown()) {
return;
}
// Restart from the very beginning because the decoder was recreated.
continue;
}
// If there is output available we want to change the entry in the surface
// cache from a placeholder to an actual surface now before NotifyProgress
// call below so that when consumers get the frame complete notification
// from the NotifyProgress they can actually get a surface from the surface
// cache.
bool checkForNewFrameAtYieldResult = false;
if (result == LexerResult(Yield::OUTPUT_AVAILABLE)) {
checkForNewFrameAtYieldResult = CheckForNewFrameAtYield();
}
// Notify for the progress we've made so far.
if (mDecoder->HasProgress()) {
if (mImage && mDecoder->HasProgress()) {
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
}
@ -168,38 +265,52 @@ AnimationSurfaceProvider::Run()
return;
}
// There's new output available - a new frame! Grab it.
// There's new output available - a new frame! Grab it. If we don't need any
// more for the moment we can break out of the loop. If we are shutting
// down, we want to ensure we release the thread as soon as possible. The
// animation may advance even during shutdown, which keeps us decoding, and
// thus blocking the decode pool during teardown.
MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
CheckForNewFrameAtYield();
if (!checkForNewFrameAtYieldResult ||
DecodePool::Singleton()->IsShuttingDown()) {
return;
}
}
}
void
bool
AnimationSurfaceProvider::CheckForNewFrameAtYield()
{
mDecodingMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mDecoder);
bool justGotFirstFrame = false;
bool continueDecoding;
{
MutexAutoLock lock(mFramesMutex);
// Try to get the new frame from the decoder.
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
MOZ_ASSERT(mDecoder->HasFrameToTake());
mDecoder->ClearHasFrameToTake();
if (!frame) {
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
return;
return true;
}
// We should've gotten a different frame than last time.
MOZ_ASSERT_IF(!mFrames.IsEmpty(),
mFrames.LastElement().get() != frame.get());
MOZ_ASSERT_IF(!mFrames.Frames().IsEmpty(),
mFrames.Frames().LastElement().get() != frame.get());
// Append the new frame to the list.
mFrames.AppendElement(Move(frame));
continueDecoding = mFrames.Insert(Move(frame));
if (mFrames.Length() == 1) {
// We only want to handle the first frame if it is the first pass for the
// animation decoder. The owning image will be cleared after that.
size_t frameCount = mFrames.Frames().Length();
if (frameCount == 1 && mImage) {
justGotFirstFrame = true;
}
}
@ -207,32 +318,49 @@ AnimationSurfaceProvider::CheckForNewFrameAtYield()
if (justGotFirstFrame) {
AnnounceSurfaceAvailable();
}
return continueDecoding;
}
void
bool
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
{
mDecodingMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mDecoder);
bool justGotFirstFrame = false;
bool continueDecoding;
{
MutexAutoLock lock(mFramesMutex);
// The decoder may or may not have a new frame for us at this point. Avoid
// reinserting the same frame again.
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
if (!frame) {
return;
// If the decoder didn't finish a new frame (ie if, after starting the
// frame, it got an error and aborted the frame and the rest of the decode)
// that means it won't be reporting it to the image or FrameAnimator so we
// should ignore it too, that's what HasFrameToTake tracks basically.
if (!mDecoder->HasFrameToTake()) {
frame = RawAccessFrameRef();
} else {
MOZ_ASSERT(frame);
mDecoder->ClearHasFrameToTake();
}
if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
return; // We already have this one.
if (!frame || (!mFrames.Frames().IsEmpty() &&
mFrames.Frames().LastElement().get() == frame.get())) {
return mFrames.MarkComplete();
}
// Append the new frame to the list.
mFrames.AppendElement(Move(frame));
mFrames.Insert(Move(frame));
continueDecoding = mFrames.MarkComplete();
if (mFrames.Length() == 1) {
// We only want to handle the first frame if it is the first pass for the
// animation decoder. The owning image will be cleared after that.
if (mFrames.Frames().Length() == 1 && mImage) {
justGotFirstFrame = true;
}
}
@ -240,6 +368,8 @@ AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
if (justGotFirstFrame) {
AnnounceSurfaceAvailable();
}
return continueDecoding;
}
void
@ -260,14 +390,27 @@ void
AnimationSurfaceProvider::FinishDecoding()
{
mDecodingMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mImage);
MOZ_ASSERT(mDecoder);
// Send notifications.
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
if (mImage) {
// Send notifications.
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
}
// Destroy our decoder; we don't need it anymore.
mDecoder = nullptr;
// Determine if we need to recreate the decoder, in case we are discarding
// frames and need to loop back to the beginning.
bool recreateDecoder;
{
MutexAutoLock lock(mFramesMutex);
recreateDecoder = !mFrames.HasRedecodeError() && mFrames.MayDiscard();
}
if (recreateDecoder) {
mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
MOZ_ASSERT(mDecoder);
} else {
mDecoder = nullptr;
}
// We don't need a reference to our image anymore, either, and we don't want
// one. We may be stored in the surface cache for a long time after decoding

View file

@ -13,6 +13,7 @@
#include "FrameAnimator.h"
#include "IDecodingTask.h"
#include "ISurfaceProvider.h"
#include "AnimationFrameBuffer.h"
namespace mozilla {
namespace image {
@ -31,7 +32,8 @@ public:
AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
const SurfaceKey& aSurfaceKey,
NotNull<Decoder*> aDecoder);
NotNull<Decoder*> aDecoder,
size_t aCurrentFrame);
//////////////////////////////////////////////////////////////////////////////
@ -44,10 +46,13 @@ public:
DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
bool IsFinished() const override;
bool IsFullyDecoded() const override;
size_t LogicalSizeInBytes() const override;
void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
size_t& aHeapSizeOut,
size_t& aNonHeapSizeOut) override;
void Reset() override;
void Advance(size_t aFrame) override;
protected:
DrawableFrameRef DrawableRef(size_t aFrame) override;
@ -77,11 +82,15 @@ private:
virtual ~AnimationSurfaceProvider();
void DropImageReference();
void CheckForNewFrameAtYield();
void CheckForNewFrameAtTerminalState();
void AnnounceSurfaceAvailable();
void FinishDecoding();
// @returns Whether or not we should continue decoding.
bool CheckForNewFrameAtYield();
// @returns Whether or not we should restart decoding.
bool CheckForNewFrameAtTerminalState();
/// The image associated with our decoder.
RefPtr<RasterImage> mImage;
@ -95,7 +104,7 @@ private:
mutable Mutex mFramesMutex;
/// The frames of this animation, in order.
nsTArray<RawAccessFrameRef> mFrames;
AnimationFrameBuffer mFrames;
};
} // namespace image

View file

@ -72,7 +72,7 @@ public:
{
// Threads have to be shut down from another thread, so we'll ask the
// main thread to do it for us.
NS_DispatchToMainThread(NewRunnableMethod(aThisThread, &nsIThread::Shutdown));
NS_DispatchToMainThread(NewRunnableMethod(aThisThread, &nsIThread::AsyncShutdown));
}
/**
@ -87,6 +87,12 @@ public:
mMonitor.NotifyAll();
}
bool IsShuttingDown() const
{
MonitorAutoLock lock(mMonitor);
return mShuttingDown;
}
/// Pushes a new decode work item.
void PushWork(IDecodingTask* aTask)
{
@ -150,7 +156,7 @@ private:
nsThreadPoolNaming mThreadNaming;
// mMonitor guards the queues and mShuttingDown.
Monitor mMonitor;
mutable Monitor mMonitor;
nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
bool mShuttingDown;
@ -299,6 +305,12 @@ DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*)
return NS_OK;
}
bool
DecodePool::IsShuttingDown() const
{
return mImpl->IsShuttingDown();
}
void
DecodePool::AsyncRun(IDecodingTask* aTask)
{

View file

@ -54,6 +54,10 @@ public:
/// same as the number of decoding threads we're actually using.
static uint32_t NumberOfCores();
/// True if the DecodePool is being shutdown. This may only be called by
/// threads from the pool to check if they should keep working or not.
bool IsShuttingDown() const;
/// Ask the DecodePool to run @aTask asynchronously and return immediately.
void AsyncRun(IDecodingTask* aTask);

View file

@ -49,7 +49,8 @@ DecodedSurfaceProvider::DropImageReference()
// get evicted is holding the surface cache lock, causing deadlock.
RefPtr<RasterImage> image = mImage;
mImage = nullptr;
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
SurfaceCache::ReleaseImageOnMainThread(image.forget(),
/* aAlwaysProxy = */ true);
}
DrawableFrameRef

View file

@ -36,6 +36,7 @@ Decoder::Decoder(RasterImage* aImage)
, mHaveExplicitOutputSize(false)
, mInFrame(false)
, mFinishedNewFrame(false)
, mHasFrameToTake(false)
, mReachedTerminalState(false)
, mDecodeDone(false)
, mError(false)
@ -53,7 +54,7 @@ Decoder::~Decoder()
if (mImage && !NS_IsMainThread()) {
// Dispatch mImage to main thread to prevent it from being destructed by the
// decode thread.
NS_ReleaseOnMainThread(mImage.forget());
SurfaceCache::ReleaseImageOnMainThread(mImage.forget());
}
}
@ -254,6 +255,8 @@ Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
mCurrentFrame.get());
if (mCurrentFrame) {
mHasFrameToTake = true;
// Gather the raw pointers the decoders will use.
mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
@ -474,6 +477,7 @@ Decoder::PostError()
mCurrentFrame->Abort();
mInFrame = false;
--mFrameCount;
mHasFrameToTake = false;
}
}

View file

@ -200,6 +200,11 @@ public:
mIterator.emplace(Move(aIterator));
}
SourceBuffer* GetSourceBuffer() const
{
return mIterator->Owner();
}
/**
* Should this decoder send partial invalidations?
*/
@ -244,6 +249,12 @@ public:
/// Are we in the middle of a frame right now? Used for assertions only.
bool InFrame() const { return mInFrame; }
/// Type of decoder.
virtual DecoderType GetType() const
{
return DecoderType::UNKNOWN;
}
enum DecodeStyle {
PROGRESSIVE, // produce intermediate frames representing the partial
// state of the image
@ -339,6 +350,11 @@ public:
: RawAccessFrameRef();
}
bool HasFrameToTake() const { return mHasFrameToTake; }
void ClearHasFrameToTake() {
MOZ_ASSERT(mHasFrameToTake);
mHasFrameToTake = false;
}
protected:
friend class nsICODecoder;
@ -493,6 +509,10 @@ private:
bool mInFrame : 1;
bool mFinishedNewFrame : 1; // True if PostFrameStop() has been called since
// the last call to TakeCompleteFrameCount().
// Has a new frame that AnimationSurfaceProvider can take. Unfortunately this
// has to be separate from mFinishedNewFrame because the png decoder yields a
// new frame before calling PostFrameStop().
bool mHasFrameToTake : 1;
bool mReachedTerminalState : 1;
bool mDecodeDone : 1;
bool mError : 1;

View file

@ -181,7 +181,8 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
NotNull<SourceBuffer*> aSourceBuffer,
const IntSize& aIntrinsicSize,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags)
SurfaceFlags aSurfaceFlags,
size_t aCurrentFrame)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
@ -213,7 +214,8 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
NotNull<RefPtr<AnimationSurfaceProvider>> provider =
WrapNotNull(new AnimationSurfaceProvider(aImage,
surfaceKey,
WrapNotNull(decoder)));
WrapNotNull(decoder),
aCurrentFrame));
// Attempt to insert the surface provider into the surface cache right away so
// we won't trigger any more decoders with the same parameters.
@ -226,6 +228,29 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
return task.forget();
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::CloneAnimationDecoder(Decoder* aDecoder)
{
MOZ_ASSERT(aDecoder);
MOZ_ASSERT(aDecoder->HasAnimation());
RefPtr<Decoder> decoder = GetDecoder(aDecoder->GetType(), nullptr,
/* aIsRedecode = */ true);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aDecoder->GetSourceBuffer()->Iterator());
decoder->SetDecoderFlags(aDecoder->GetDecoderFlags());
decoder->SetSurfaceFlags(aDecoder->GetSurfaceFlags());
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
return decoder.forget();
}
/* static */ already_AddRefed<IDecodingTask>
DecoderFactory::CreateMetadataDecoder(DecoderType aType,
NotNull<RasterImage*> aImage,

View file

@ -92,6 +92,7 @@ public:
* @param aDecoderFlags Flags specifying the behavior of this decoder.
* @param aSurfaceFlags Flags specifying the type of output this decoder
* should produce.
* @param aCurrentFrame The current frame the decoder should auto advance to.
*/
static already_AddRefed<IDecodingTask>
CreateAnimationDecoder(DecoderType aType,
@ -99,7 +100,17 @@ public:
NotNull<SourceBuffer*> aSourceBuffer,
const gfx::IntSize& aIntrinsicSize,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags);
SurfaceFlags aSurfaceFlags,
size_t aCurrentFrame);
/**
* Creates and initializes a decoder for animated images, cloned from the
* given decoder.
*
* @param aDecoder Decoder to clone.
*/
static already_AddRefed<Decoder>
CloneAnimationDecoder(Decoder* aDecoder);
/**
* Creates and initializes a metadata decoder of type @aType. This decoder

View file

@ -80,7 +80,7 @@ DynamicImage::OnImageDataComplete(nsIRequest* aRequest,
}
void
DynamicImage::OnSurfaceDiscarded()
DynamicImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{ }
void

View file

@ -53,7 +53,7 @@ public:
nsresult aStatus,
bool aLastPart) override;
virtual void OnSurfaceDiscarded() override;
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
virtual uint64_t InnerWindowID() const override;

View file

@ -11,6 +11,7 @@
#include "LookupResult.h"
#include "MainThreadUtils.h"
#include "RasterImage.h"
#include "gfxPrefs.h"
#include "pixman.h"
@ -25,9 +26,75 @@ namespace image {
///////////////////////////////////////////////////////////////////////////////
void
AnimationState::SetDoneDecoding(bool aDone)
AnimationState::UpdateState(bool aAnimationFinished,
RasterImage *aImage,
const gfx::IntSize& aSize)
{
mDoneDecoding = aDone;
LookupResult result =
SurfaceCache::Lookup(ImageKey(aImage),
RasterSurfaceKey(aSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
UpdateStateInternal(result, aAnimationFinished);
}
void
AnimationState::UpdateStateInternal(LookupResult& aResult,
bool aAnimationFinished)
{
// Update mDiscarded and mIsCurrentlyDecoded.
if (aResult.Type() == MatchType::NOT_FOUND) {
// no frames, we've either been discarded, or never been decoded before.
mDiscarded = mHasBeenDecoded;
mIsCurrentlyDecoded = false;
} else if (aResult.Type() == MatchType::PENDING) {
// no frames yet, but a decoder is or will be working on it.
mDiscarded = false;
mIsCurrentlyDecoded = false;
} else {
MOZ_ASSERT(aResult.Type() == MatchType::EXACT);
mDiscarded = false;
// If mHasBeenDecoded is true then we know the true total frame count and
// we can use it to determine if we have all the frames now so we know if
// we are currently fully decoded.
// If mHasBeenDecoded is false then we'll get another UpdateState call
// when the decode finishes.
if (mHasBeenDecoded) {
Maybe<uint32_t> frameCount = FrameCount();
MOZ_ASSERT(frameCount.isSome());
mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded();
}
}
// Update the value of mCompositedFrameInvalid.
if (mIsCurrentlyDecoded || aAnimationFinished) {
// Animated images that have finished their animation (ie because it is a
// finite length animation) don't have RequestRefresh called on them, and so
// mCompositedFrameInvalid would never get cleared. We clear it here (and
// also in RasterImage::Decode when we create a decoder for an image that
// has finished animated so it can display sooner than waiting until the
// decode completes). We also do it if we are fully decoded. This is safe
// to do for images that aren't finished animating because before we paint
// the refresh driver will call into us to advance to the correct frame,
// and that will succeed because we have all the frames.
mCompositedFrameInvalid = false;
} else if (aResult.Type() == MatchType::NOT_FOUND ||
aResult.Type() == MatchType::PENDING) {
if (mHasBeenDecoded) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
mCompositedFrameInvalid = true;
}
}
// Otherwise don't change the value of mCompositedFrameInvalid, it will be
// updated by RequestRefresh.
}
void
AnimationState::NotifyDecodeComplete()
{
mHasBeenDecoded = true;
}
void
@ -52,7 +119,7 @@ AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
return;
}
MOZ_ASSERT(!mDoneDecoding, "Adding new frames after decoding is finished?");
MOZ_ASSERT(!mHasBeenDecoded, "Adding new frames after decoding is finished?");
MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?");
mFrameCount = aFrameCount;
@ -61,7 +128,7 @@ AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
Maybe<uint32_t>
AnimationState::FrameCount() const
{
return mDoneDecoding ? Some(mFrameCount) : Nothing();
return mHasBeenDecoded ? Some(mFrameCount) : Nothing();
}
void
@ -84,6 +151,21 @@ AnimationState::SetAnimationFrameTime(const TimeStamp& aTime)
mCurrentAnimationFrameTime = aTime;
}
bool
AnimationState::MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime)
{
if (!gfxPrefs::ImageAnimatedResumeFromLastDisplayed() ||
mCurrentAnimationFrameTime >= aTime) {
return false;
}
// We are configured to stop an animation when it is out of view, and restart
// it from the same point when it comes back into view. The same applies if it
// was discarded while out of view.
mCurrentAnimationFrameTime = aTime;
return true;
}
uint32_t
AnimationState::GetCurrentAnimationFrameIndex() const
{
@ -98,7 +180,7 @@ AnimationState::LoopLength() const
return FrameTimeout::Forever();
}
MOZ_ASSERT(mDoneDecoding, "We know the loop length but decoding isn't done?");
MOZ_ASSERT(mHasBeenDecoded, "We know the loop length but decoding isn't done?");
// If we're not looping, a single loop time has no meaning.
if (mAnimationMode != imgIContainer::kNormalAnimMode) {
@ -113,32 +195,41 @@ AnimationState::LoopLength() const
// FrameAnimator implementation.
///////////////////////////////////////////////////////////////////////////////
TimeStamp
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
Maybe<TimeStamp>
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState,
DrawableSurface& aFrames) const
{
TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
FrameTimeout timeout = GetTimeoutForFrame(aState.mCurrentAnimationFrameIndex);
Maybe<FrameTimeout> timeout =
GetTimeoutForFrame(aState, aFrames, aState.mCurrentAnimationFrameIndex);
if (timeout == FrameTimeout::Forever()) {
if (timeout.isNothing()) {
MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
return Nothing();
}
if (*timeout == FrameTimeout::Forever()) {
// We need to return a sentinel value in this case, because our logic
// doesn't work correctly if we have an infinitely long timeout. 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
// infinitely long timeouts.
return TimeStamp::NowLoRes() +
TimeDuration::FromMilliseconds(31536000.0);
return Some(TimeStamp::NowLoRes() +
TimeDuration::FromMilliseconds(31536000.0));
}
TimeDuration durationOfTimeout =
TimeDuration::FromMilliseconds(double(timeout.AsMilliseconds()));
TimeDuration::FromMilliseconds(double(timeout->AsMilliseconds()));
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
return currentFrameEndTime;
return Some(currentFrameEndTime);
}
RefreshResult
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
FrameAnimator::AdvanceFrame(AnimationState& aState,
DrawableSurface& aFrames,
TimeStamp aTime)
{
NS_ASSERTION(aTime <= TimeStamp::Now(),
"Given time appears to be in the future");
@ -198,19 +289,27 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
// the appropriate notification on the main thread. Make sure we stay in sync
// with AnimationState.
MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aFrames, nextFrameIndex);
// We should always check to see if we have the next frame even if we have
// previously finished decoding. If we needed to redecode (e.g. due to a draw
// failure) we would have discarded all the old frames and may not yet have
// the new ones.
if (!nextFrame || !nextFrame->IsFinished()) {
// Uh oh, the frame we want to show is currently being decoded (partial)
// Wait until the next refresh driver tick and try again
// Uh oh, the frame we want to show is currently being decoded (partial).
// Similar to the above case, we could be blocked by network or decoding,
// and so we should advance our current time rather than risk jumping
// through the animation. We will wait until the next refresh driver tick
// and try again.
aState.mCurrentAnimationFrameTime = aTime;
return ret;
}
if (GetTimeoutForFrame(nextFrameIndex) == FrameTimeout::Forever()) {
Maybe<FrameTimeout> nextFrameTimeout = GetTimeoutForFrame(aState, aFrames, nextFrameIndex);
// GetTimeoutForFrame can only return none if frame doesn't exist,
// but we just got it above.
MOZ_ASSERT(nextFrameTimeout.isSome());
if (*nextFrameTimeout == FrameTimeout::Forever()) {
ret.mAnimationFinished = true;
}
@ -220,12 +319,16 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
// Change frame
if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
if (!DoBlend(aFrames, &ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
// something went wrong, move on to next
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
nextFrame->SetCompositingFailed(true);
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
aState.mCompositedFrameRequested = false;
aFrames.Advance(nextFrameIndex);
return ret;
}
@ -233,7 +336,9 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
nextFrame->SetCompositingFailed(false);
}
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
// If we can get closer to the current time by a multiple of the image's loop
// time, we should. We can only do this if we're done decoding; otherwise, we
@ -254,6 +359,8 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
// Set currentAnimationFrameIndex at the last possible moment
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
aState.mCompositedFrameRequested = false;
aFrames.Advance(nextFrameIndex);
// If we're here, we successfully advanced the frame.
ret.mFrameAdvanced = true;
@ -261,42 +368,124 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
return ret;
}
RefreshResult
FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime)
void
FrameAnimator::ResetAnimation(AnimationState& aState)
{
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
aState.ResetAnimation();
// Our surface provider is synchronized to our state, so we need to reset its
// state as well, if we still have one.
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
if (!result) {
return;
}
result.Surface().Reset();
}
RefreshResult
FrameAnimator::RequestRefresh(AnimationState& aState,
const TimeStamp& aTime,
bool aAnimationFinished)
{
// By default, an empty RefreshResult.
RefreshResult ret;
while (currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = currentFrameEndTime;
if (aState.IsDiscarded()) {
aState.MaybeAdvanceAnimationFrameTime(aTime);
return ret;
}
RefreshResult frameRes = AdvanceFrame(aState, aTime);
// Get the animation frames once now, and pass them down to callees because
// the surface could be discarded at anytime on a different thread. This is
// must easier to reason about then trying to write code that is safe to
// having the surface disappear at anytime.
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
aState.UpdateStateInternal(result, aAnimationFinished);
if (aState.IsDiscarded() || !result) {
aState.MaybeAdvanceAnimationFrameTime(aTime);
if (!ret.mDirtyRect.IsEmpty()) {
ret.mFrameAdvanced = true;
}
return ret;
}
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
Maybe<TimeStamp> currentFrameEndTime =
GetCurrentImgFrameEndTime(aState, result.Surface());
if (currentFrameEndTime.isNothing()) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
MOZ_ASSERT(aState.mCompositedFrameInvalid);
// Nothing we can do but wait for our previous current frame to be decoded
// again so we can determine what to do next.
aState.MaybeAdvanceAnimationFrameTime(aTime);
return ret;
}
// If nothing has accessed the composited frame since the last time we
// advanced, then there is no point in continuing to advance the animation.
// This has the effect of freezing the animation while not in view.
if (!aState.mCompositedFrameRequested &&
aState.MaybeAdvanceAnimationFrameTime(aTime)) {
return ret;
}
while (*currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = *currentFrameEndTime;
RefreshResult frameRes = AdvanceFrame(aState, result.Surface(), aTime);
// Accumulate our result for returning to callers.
ret.Accumulate(frameRes);
currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
currentFrameEndTime = GetCurrentImgFrameEndTime(aState, result.Surface());
// AdvanceFrame can't advance to a frame that doesn't exist yet.
MOZ_ASSERT(currentFrameEndTime.isSome());
// 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.mFrameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
if (!frameRes.mFrameAdvanced && (*currentFrameEndTime == oldFrameEndTime)) {
break;
}
}
// Advanced to the correct frame, the composited frame is now valid to be drawn.
if (*currentFrameEndTime > aTime) {
aState.mCompositedFrameInvalid = false;
}
MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid);
return ret;
}
LookupResult
FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
FrameAnimator::GetCompositedFrame(AnimationState& aState)
{
aState.mCompositedFrameRequested = true;
if (aState.mCompositedFrameInvalid) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
MOZ_ASSERT(aState.GetHasBeenDecoded());
MOZ_ASSERT(!aState.GetIsCurrentlyDecoded());
return LookupResult(MatchType::NOT_FOUND);
}
// If we have a composited version of this frame, return that.
if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
if (mLastCompositedFrameIndex >= 0 &&
(uint32_t(mLastCompositedFrameIndex) == aState.mCurrentAnimationFrameIndex)) {
return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
MatchType::EXACT);
}
@ -314,7 +503,7 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
// Seek to the appropriate frame. If seeking fails, it means that we couldn't
// get the frame we're looking for; treat this as if the lookup failed.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) {
return LookupResult(MatchType::NOT_FOUND);
}
@ -324,17 +513,19 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
return result;
}
FrameTimeout
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
Maybe<FrameTimeout>
FrameAnimator::GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const
{
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
RawAccessFrameRef frame = GetRawFrame(aFrames, aFrameNum);
if (frame) {
AnimationData data = frame->GetAnimationData();
return data.mTimeout;
return Some(data.mTimeout);
}
NS_WARNING("No frame; called GetTimeoutForFrame too early?");
return FrameTimeout::FromRawMilliseconds(100);
MOZ_ASSERT(aState.mHasBeenDecoded && !aState.mIsCurrentlyDecoded);
return Nothing();
}
static void
@ -382,37 +573,29 @@ FrameAnimator::CollectSizeOfCompositingSurfaces(
}
RawAccessFrameRef
FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
FrameAnimator::GetRawFrame(DrawableSurface& aFrames, uint32_t aFrameNum) const
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
if (!result) {
return RawAccessFrameRef();
}
// Seek to the frame we want. If seeking fails, it means we couldn't get the
// frame we're looking for, so we bail here to avoid returning the wrong frame
// to the caller.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
if (NS_FAILED(aFrames.Seek(aFrameNum))) {
return RawAccessFrameRef(); // Not available yet.
}
return result.Surface()->RawAccessRef();
return aFrames->RawAccessRef();
}
//******************************************************************************
// DoBlend gets called when the timer for animation get fired and we have to
// update the composited frame of the animation.
bool
FrameAnimator::DoBlend(IntRect* aDirtyRect,
FrameAnimator::DoBlend(DrawableSurface& aFrames,
IntRect* aDirtyRect,
uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex)
{
RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
RawAccessFrameRef prevFrame = GetRawFrame(aFrames, aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aFrames, aNextFrameIndex);
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");

View file

@ -15,11 +15,13 @@
#include "nsCOMPtr.h"
#include "nsRect.h"
#include "SurfaceCache.h"
#include "gfxPrefs.h"
namespace mozilla {
namespace image {
class RasterImage;
class DrawableSurface;
class AnimationState
{
@ -31,14 +33,63 @@ public:
, mLoopCount(-1)
, mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0))
, mAnimationMode(aAnimationMode)
, mDoneDecoding(false)
, mHasBeenDecoded(false)
, mIsCurrentlyDecoded(false)
, mCompositedFrameInvalid(false)
, mCompositedFrameRequested(false)
, mDiscarded(false)
{ }
/**
* Call when this image is finished decoding so we know that there aren't any
* more frames coming.
* Call this whenever a decode completes, a decode starts, or the image is
* discarded. It will update the internal state. Specifically mDiscarded,
* mCompositedFrameInvalid, and mIsCurrentlyDecoded.
*/
void SetDoneDecoding(bool aDone);
void UpdateState(bool aAnimationFinished,
RasterImage *aImage,
const gfx::IntSize& aSize);
private:
void UpdateStateInternal(LookupResult& aResult,
bool aAnimationFinished);
public:
/**
* Call when a decode of this image has been completed.
*/
void NotifyDecodeComplete();
/**
* Returns true if this image has been fully decoded before.
*/
bool GetHasBeenDecoded() { return mHasBeenDecoded; }
/**
* Returns true if this image has been discarded and a decoded has not yet
* been created to redecode it.
*/
bool IsDiscarded() { return mDiscarded; }
/**
* Sets the composited frame as valid or invalid.
*/
void SetCompositedFrameInvalid(bool aInvalid) {
MOZ_ASSERT(!aInvalid || gfxPrefs::ImageMemAnimatedDiscardable());
mCompositedFrameInvalid = aInvalid;
}
/**
* Returns whether the composited frame is valid to draw to the screen.
*/
bool GetCompositedFrameInvalid() {
return mCompositedFrameInvalid;
}
/**
* Returns whether the image is currently full decoded..
*/
bool GetIsCurrentlyDecoded() {
return mIsCurrentlyDecoded;
}
/**
* Call when you need to re-start animating. Ensures we start from the first
@ -81,6 +132,13 @@ public:
*/
void SetAnimationFrameTime(const TimeStamp& aTime);
/**
* Set the animation frame time to @aTime if we are configured to stop the
* animation when not visible and aTime is later than the current time.
* Returns true if the time was updated, else false.
*/
bool MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime);
/**
* The current frame we're on, from 0 to (numFrames - 1).
*/
@ -140,8 +198,48 @@ private:
//! The animation mode of this image. Constants defined in imgIContainer.
uint16_t mAnimationMode;
//! Whether this image is done being decoded.
bool mDoneDecoding;
/**
* The following four bools (mHasBeenDecoded, mIsCurrentlyDecoded,
* mCompositedFrameInvalid, mDiscarded) track the state of the image with
* regards to decoding. They all start out false, including mDiscarded,
* because we want to treat being discarded differently from "not yet decoded
* for the first time".
*
* (When we are decoding the image for the first time we want to show the
* image at the speed of data coming in from the network or the speed
* specified in the image file, whichever is slower. But when redecoding we
* want to show nothing until the frame for the current time has been
* decoded. The prevents the user from seeing the image "fast forward"
* to the expected spot.)
*
* When the image is decoded for the first time mHasBeenDecoded and
* mIsCurrentlyDecoded get set to true. When the image is discarded
* mIsCurrentlyDecoded gets set to false, and mCompositedFrameInvalid
* & mDiscarded get set to true. When we create a decoder to redecode the
* image mDiscarded gets set to false. mCompositedFrameInvalid gets set to
* false when we are able to advance to the frame that should be showing
* for the current time. mIsCurrentlyDecoded gets set to true when the
* redecode finishes.
*/
//! Whether this image has been decoded at least once.
bool mHasBeenDecoded;
//! Whether this image is currently fully decoded.
bool mIsCurrentlyDecoded;
//! Whether the composited frame is valid to draw to the screen, note that
//! the composited frame can exist and be filled with image data but not
//! valid to draw to the screen.
bool mCompositedFrameInvalid;
//! Whether the composited frame was requested from the animator since the
//! last time we advanced the animation.
bool mCompositedFrameRequested;
//! Whether this image is currently discarded. Only set to true after the
//! image has been decoded at least once.
bool mDiscarded;
};
/**
@ -190,6 +288,12 @@ public:
MOZ_COUNT_DTOR(FrameAnimator);
}
/**
* Call when you need to re-start animating. Ensures we start from the first
* frame.
*/
void ResetAnimation(AnimationState& aState);
/**
* Re-evaluate what frame we're supposed to be on, and do whatever blending
* is necessary to get us to that frame.
@ -197,14 +301,16 @@ public:
* Returns the result of that blending, including whether the current frame
* changed and what the resulting dirty rectangle is.
*/
RefreshResult RequestRefresh(AnimationState& aState, const TimeStamp& aTime);
RefreshResult RequestRefresh(AnimationState& aState,
const TimeStamp& aTime,
bool aAnimationFinished);
/**
* If we have a composited frame for @aFrameNum, returns it. Otherwise,
* returns an empty LookupResult. It is an error to call this method with
* aFrameNum == 0, because the first frame is never composited.
* Get the full frame for the current frame of the animation (it may or may
* not have required compositing). It may not be available because it hasn't
* been decoded yet, in which case we return an empty LookupResult.
*/
LookupResult GetCompositedFrame(uint32_t aFrameNum);
LookupResult GetCompositedFrame(AnimationState& aState);
/**
* Collect an accounting of the memory occupied by the compositing surfaces we
@ -227,24 +333,32 @@ private: // methods
* @returns a RefreshResult that shows whether the frame was successfully
* advanced, and its resulting dirty rect.
*/
RefreshResult AdvanceFrame(AnimationState& aState, TimeStamp aTime);
RefreshResult AdvanceFrame(AnimationState& aState,
DrawableSurface& aFrames,
TimeStamp aTime);
/**
* Get the @aIndex-th frame in the frame index, ignoring results of blending.
*/
RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const;
RawAccessFrameRef GetRawFrame(DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/// @return the given frame's timeout.
FrameTimeout GetTimeoutForFrame(uint32_t aFrameNum) const;
/// @return the given frame's timeout if it is available
Maybe<FrameTimeout> GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/**
* Get the time the frame we're currently displaying is supposed to end.
*
* In the error case, returns an "infinity" timestamp.
* In the error case (like if the requested frame is not currently
* decoded), returns None().
*/
TimeStamp GetCurrentImgFrameEndTime(AnimationState& aState) const;
Maybe<TimeStamp> GetCurrentImgFrameEndTime(AnimationState& aState,
DrawableSurface& aFrames) const;
bool DoBlend(gfx::IntRect* aDirtyRect,
bool DoBlend(DrawableSurface& aFrames,
gfx::IntRect* aDirtyRect,
uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex);

View file

@ -54,6 +54,12 @@ public:
/// @return true if DrawableRef() will return a completely decoded surface.
virtual bool IsFinished() const = 0;
/// @return true if the underlying decoder is currently fully decoded. For
/// animated images, this means that at least every frame has been decoded
/// at least once. It does not guarantee that all of the frames are present,
/// as the surface provider has the option to discard as it deems necessary.
virtual bool IsFullyDecoded() const { return IsFinished(); }
/// @return the number of bytes of memory this ISurfaceProvider is expected to
/// require. Optimizations may result in lower real memory usage. Trivial
/// overhead is ignored. Because this value is used in bookkeeping, it's
@ -75,6 +81,9 @@ public:
ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
}
virtual void Reset() { }
virtual void Advance(size_t aFrame) { }
/// @return the availability state of this ISurfaceProvider, which indicates
/// whether DrawableRef() could successfully return a surface. Should only be
/// called from SurfaceCache code as it relies on SurfaceCache for
@ -189,6 +198,36 @@ public:
return mDrawableRef ? NS_OK : NS_ERROR_FAILURE;
}
void Reset()
{
if (!mProvider) {
MOZ_ASSERT_UNREACHABLE("Trying to reset a static DrawableSurface?");
return;
}
mProvider->Reset();
}
void Advance(size_t aFrame)
{
if (!mProvider) {
MOZ_ASSERT_UNREACHABLE("Trying to advance a static DrawableSurface?");
return;
}
mProvider->Advance(aFrame);
}
bool IsFullyDecoded() const
{
if (!mProvider) {
MOZ_ASSERT_UNREACHABLE("Trying to check decoding state of a static DrawableSurface?");
return false;
}
return mProvider->IsFullyDecoded();
}
explicit operator bool() const { return mHaveSurface; }
imgFrame* operator->() { return DrawableRef().get(); }

View file

@ -211,7 +211,7 @@ public:
/**
* Called when the SurfaceCache discards a surface belonging to this image.
*/
virtual void OnSurfaceDiscarded() = 0;
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) = 0;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
virtual uint64_t InnerWindowID() const = 0;
@ -249,7 +249,7 @@ public:
}
#endif
virtual void OnSurfaceDiscarded() override { }
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override { }
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override
{

View file

@ -111,8 +111,32 @@ BadImage(const char* aMessage, RefPtr<T>& aImage)
return aImage.forget();
}
static void
SetSourceSizeHint(RasterImage* aImage, uint32_t aSize)
{
// Pass anything usable on so that the RasterImage can preallocate
// its source buffer.
if (aSize == 0) {
return;
}
// Bound by something reasonable
uint32_t sizeHint = std::min<uint32_t>(aSize, 20000000);
nsresult rv = aImage->SetSourceSizeHint(sizeHint);
if (NS_FAILED(rv)) {
// Flush memory, try to get some back, and try again.
rv = nsMemory::HeapMinimize(true);
nsresult rv2 = aImage->SetSourceSizeHint(sizeHint);
// If we've still failed at this point, things are going downhill.
if (NS_FAILED(rv) || NS_FAILED(rv2)) {
NS_WARNING("About to hit OOM in imagelib!");
}
}
}
/* static */ already_AddRefed<Image>
ImageFactory::CreateAnonymousImage(const nsCString& aMimeType)
ImageFactory::CreateAnonymousImage(const nsCString& aMimeType,
uint32_t aSizeHint /* = 0 */)
{
nsresult rv;
@ -127,6 +151,7 @@ ImageFactory::CreateAnonymousImage(const nsCString& aMimeType)
return BadImage("RasterImage::Init failed", newImage);
}
SetSourceSizeHint(newImage, aSizeHint);
return newImage.forget();
}
@ -231,25 +256,7 @@ ImageFactory::CreateRasterImage(nsIRequest* aRequest,
newImage->SetInnerWindowID(aInnerWindowId);
uint32_t len = GetContentSize(aRequest);
// Pass anything usable on so that the RasterImage can preallocate
// its source buffer.
if (len > 0) {
// Bound by something reasonable
uint32_t sizeHint = std::min<uint32_t>(len, 20000000);
rv = newImage->SetSourceSizeHint(sizeHint);
if (NS_FAILED(rv)) {
// Flush memory, try to get some back, and try again.
rv = nsMemory::HeapMinimize(true);
nsresult rv2 = newImage->SetSourceSizeHint(sizeHint);
// If we've still failed at this point, things are going downhill.
if (NS_FAILED(rv) || NS_FAILED(rv2)) {
NS_WARNING("About to hit OOM in imagelib!");
}
}
}
SetSourceSizeHint(newImage, GetContentSize(aRequest));
return newImage.forget();
}

View file

@ -51,9 +51,10 @@ public:
* the usual image loading mechanism.
*
* @param aMimeType The mimetype of the image.
* @param aSizeHint The length of the source data for the image.
*/
static already_AddRefed<Image>
CreateAnonymousImage(const nsCString& aMimeType);
CreateAnonymousImage(const nsCString& aMimeType, uint32_t aSizeHint = 0);
/**
* Creates a new multipart/x-mixed-replace image wrapper, and initializes it

View file

@ -88,9 +88,9 @@ ImageWrapper::OnImageDataComplete(nsIRequest* aRequest,
}
void
ImageWrapper::OnSurfaceDiscarded()
ImageWrapper::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{
return mInnerImage->OnSurfaceDiscarded();
return mInnerImage->OnSurfaceDiscarded(aSurfaceKey);
}
void

View file

@ -45,7 +45,7 @@ public:
nsresult aStatus,
bool aLastPart) override;
virtual void OnSurfaceDiscarded() override;
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
virtual uint64_t InnerWindowID() const override;

View file

@ -174,7 +174,7 @@ RasterImage::RequestRefresh(const TimeStamp& aTime)
RefreshResult res;
if (mAnimationState) {
MOZ_ASSERT(mFrameAnimator);
res = mFrameAnimator->RequestRefresh(*mAnimationState, aTime);
res = mFrameAnimator->RequestRefresh(*mAnimationState, aTime, mAnimationFinished);
}
if (res.mFrameAdvanced) {
@ -275,8 +275,7 @@ RasterImage::LookupFrameInternal(const IntSize& aSize,
MOZ_ASSERT(mFrameAnimator);
MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(),
"Can't composite frames with non-default surface flags");
const size_t index = mAnimationState->GetCurrentAnimationFrameIndex();
return mFrameAnimator->GetCompositedFrame(index);
return mFrameAnimator->GetCompositedFrame(*mAnimationState);
}
SurfaceFlags surfaceFlags = ToSurfaceFlags(aFlags);
@ -332,6 +331,7 @@ RasterImage::LookupFrame(const IntSize& aSize,
// one. (Or we're sync decoding and the existing decoder hasn't even started
// yet.) Trigger decoding so it'll be available next time.
MOZ_ASSERT(aPlaybackType != PlaybackType::eAnimated ||
gfxPrefs::ImageMemAnimatedDiscardable() ||
!mAnimationState || mAnimationState->KnownFrameCount() < 1,
"Animated frames should be locked");
@ -399,7 +399,7 @@ RasterImage::WillDrawOpaqueNow()
return false;
}
if (mAnimationState) {
if (mAnimationState && !gfxPrefs::ImageMemAnimatedDiscardable()) {
// We never discard frames of animated images.
return true;
}
@ -426,11 +426,32 @@ RasterImage::WillDrawOpaqueNow()
}
void
RasterImage::OnSurfaceDiscarded()
RasterImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{
MOZ_ASSERT(mProgressTracker);
NS_DispatchToMainThread(NewRunnableMethod(mProgressTracker, &ProgressTracker::OnDiscard));
bool animatedFramesDiscarded =
mAnimationState && aSurfaceKey.Playback() == PlaybackType::eAnimated;
RefPtr<RasterImage> image = this;
NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
image->OnSurfaceDiscardedInternal(animatedFramesDiscarded);
}));
}
void
RasterImage::OnSurfaceDiscardedInternal(bool aAnimatedFramesDiscarded)
{
MOZ_ASSERT(NS_IsMainThread());
if (aAnimatedFramesDiscarded && mAnimationState) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
}
if (mProgressTracker) {
mProgressTracker->OnDiscard();
}
}
//******************************************************************************
@ -706,9 +727,11 @@ RasterImage::SetMetadata(const ImageMetadata& aMetadata,
mAnimationState.emplace(mAnimationMode);
mFrameAnimator = MakeUnique<FrameAnimator>(this, mSize);
// We don't support discarding animated images (See bug 414259).
// Lock the image and throw away the key.
LockImage();
if (!gfxPrefs::ImageMemAnimatedDiscardable()) {
// We don't support discarding animated images (See bug 414259).
// Lock the image and throw away the key.
LockImage();
}
if (!aFromMetadataDecode) {
// The metadata decode reported that this image isn't animated, but we
@ -829,7 +852,8 @@ RasterImage::ResetAnimation()
}
MOZ_ASSERT(mAnimationState, "Should have AnimationState");
mAnimationState->ResetAnimation();
MOZ_ASSERT(mFrameAnimator, "Should have FrameAnimator");
mFrameAnimator->ResetAnimation(*mAnimationState);
NotifyProgress(NoProgress, mAnimationState->FirstFrameRefreshArea());
@ -1015,11 +1039,16 @@ RasterImage::Discard()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(CanDiscard(), "Asked to discard but can't");
MOZ_ASSERT(!mAnimationState, "Asked to discard for animated image");
MOZ_ASSERT(!mAnimationState || gfxPrefs::ImageMemAnimatedDiscardable(),
"Asked to discard for animated image");
// Delete all the decoded frames.
SurfaceCache::RemoveImage(ImageKey(this));
if (mAnimationState) {
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
}
// Notify that we discarded.
if (mProgressTracker) {
mProgressTracker->OnDiscard();
@ -1028,8 +1057,8 @@ RasterImage::Discard()
bool
RasterImage::CanDiscard() {
return mHasSourceData && // ...have the source data...
!mAnimationState; // Can never discard animated images
return mHasSourceData && // ...have the source data...
(!mAnimationState || gfxPrefs::ImageMemAnimatedDiscardable()); // Can discard animated images if the pref is set
}
NS_IMETHODIMP
@ -1154,10 +1183,20 @@ RasterImage::Decode(const IntSize& aSize,
// Create a decoder.
RefPtr<IDecodingTask> task;
if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) {
bool animated = mAnimationState && aPlaybackType == PlaybackType::eAnimated;
if (animated) {
size_t currentFrame = mAnimationState->GetCurrentAnimationFrameIndex();
task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize,
decoderFlags, surfaceFlags);
decoderFlags, surfaceFlags,
currentFrame);
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
// If the animation is finished we can draw right away because we just draw
// the final frame all the time from now on. See comment in
// AnimationState::UpdateState.
if (mAnimationFinished) {
mAnimationState->SetCompositedFrameInvalid(false);
}
} else {
task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize, aSize,
@ -1597,7 +1636,8 @@ RasterImage::NotifyDecodeComplete(const DecoderFinalStatus& aStatus,
mHasBeenDecoded && mAnimationState) {
// We've finished a full decode of all animation frames and our AnimationState
// has been notified about them all, so let it know not to expect anymore.
mAnimationState->SetDoneDecoding(true);
mAnimationState->NotifyDecodeComplete();
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
}
// Only act on errors if we have no usable frames from the decoder.

View file

@ -163,7 +163,7 @@ public:
virtual nsresult StopAnimation() override;
// Methods inherited from Image
virtual void OnSurfaceDiscarded() override;
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
const override;
@ -321,7 +321,9 @@ private:
// never unlock so that animated images always have their lock count >= 1. In
// that case we use our animation consumers count as a proxy for lock count.
bool IsUnlocked() {
return (mLockCount == 0 || (mAnimationState && mAnimationConsumers == 0));
return (mLockCount == 0 ||
(!gfxPrefs::ImageMemAnimatedDiscardable() &&
(mAnimationState && mAnimationConsumers == 0)));
}
@ -378,6 +380,8 @@ private:
*/
void RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags);
void OnSurfaceDiscardedInternal(bool aAnimatedFramesDiscarded);
private: // data
nsIntSize mSize;
Orientation mOrientation;

View file

@ -204,30 +204,27 @@ SourceBuffer::Compact()
return NS_OK;
}
Maybe<Chunk> newChunk = CreateChunk(length, /* aRoundUp = */ false);
if (MOZ_UNLIKELY(!newChunk || newChunk->AllocationFailed())) {
NS_WARNING("Failed to allocate chunk for SourceBuffer compacting - OOM?");
Chunk& mergeChunk = mChunks[0];
if (MOZ_UNLIKELY(!mergeChunk.SetCapacity(length))) {
NS_WARNING("Failed to reallocate chunk for SourceBuffer compacting - OOM?");
return NS_OK;
}
// Copy our old chunks into the new chunk.
for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
size_t offset = newChunk->Length();
MOZ_ASSERT(offset < newChunk->Capacity());
MOZ_ASSERT(offset + mChunks[i].Length() <= newChunk->Capacity());
// Copy our old chunks into the newly reallocated first chunk.
for (uint32_t i = 1 ; i < mChunks.Length() ; ++i) {
size_t offset = mergeChunk.Length();
MOZ_ASSERT(offset < mergeChunk.Capacity());
MOZ_ASSERT(offset + mChunks[i].Length() <= mergeChunk.Capacity());
memcpy(newChunk->Data() + offset, mChunks[i].Data(), mChunks[i].Length());
newChunk->AddLength(mChunks[i].Length());
memcpy(mergeChunk.Data() + offset, mChunks[i].Data(), mChunks[i].Length());
mergeChunk.AddLength(mChunks[i].Length());
}
MOZ_ASSERT(newChunk->Length() == newChunk->Capacity(),
MOZ_ASSERT(mergeChunk.Length() == mergeChunk.Capacity(),
"Compacted chunk has slack space");
// Replace the old chunks with the new, compact chunk.
mChunks.Clear();
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(newChunk))))) {
return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
// Remove the redundant chunks.
mChunks.RemoveElementsAt(1, mChunks.Length() - 1);
mChunks.Compact();
return NS_OK;
@ -317,7 +314,7 @@ SourceBuffer::ExpectLength(size_t aExpectedLength)
return NS_OK;
}
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength))))) {
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength, /* aRoundUp */ false))))) {
return HandleError(NS_ERROR_OUT_OF_MEMORY);
}

View file

@ -187,6 +187,12 @@ public:
/// @return a count of the bytes in all chunks we've advanced through.
size_t ByteCount() const { return mByteCount; }
/// @return the source buffer which owns the iterator.
SourceBuffer* Owner() const {
MOZ_ASSERT(mOwner);
return mOwner;
}
private:
friend class SourceBuffer;
@ -352,7 +358,7 @@ private:
// Chunk type and chunk-related methods.
//////////////////////////////////////////////////////////////////////////////
class Chunk
class Chunk final
{
public:
explicit Chunk(size_t aCapacity)
@ -360,13 +366,18 @@ private:
, mLength(0)
{
MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk");
mData.reset(new (fallible) char[mCapacity]);
mData = static_cast<char*>(malloc(mCapacity));
}
~Chunk()
{
free(mData);
}
Chunk(Chunk&& aOther)
: mCapacity(aOther.mCapacity)
, mLength(aOther.mLength)
, mData(Move(aOther.mData))
, mData(aOther.mData)
{
aOther.mCapacity = aOther.mLength = 0;
aOther.mData = nullptr;
@ -374,9 +385,10 @@ private:
Chunk& operator=(Chunk&& aOther)
{
free(mData);
mCapacity = aOther.mCapacity;
mLength = aOther.mLength;
mData = Move(aOther.mData);
mData = aOther.mData;
aOther.mCapacity = aOther.mLength = 0;
aOther.mData = nullptr;
return *this;
@ -389,7 +401,7 @@ private:
char* Data() const
{
MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
return mData.get();
return mData;
}
void AddLength(size_t aAdditionalLength)
@ -398,13 +410,26 @@ private:
mLength += aAdditionalLength;
}
bool SetCapacity(size_t aCapacity)
{
MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
char* data = static_cast<char*>(realloc(mData, aCapacity));
if (!data) {
return false;
}
mData = data;
mCapacity = aCapacity;
return true;
}
private:
Chunk(const Chunk&) = delete;
Chunk& operator=(const Chunk&) = delete;
size_t mCapacity;
size_t mLength;
UniquePtr<char[]> mData;
char* mData;
};
nsresult AppendChunk(Maybe<Chunk>&& aChunk);
@ -448,7 +473,7 @@ private:
mutable Mutex mMutex;
/// The data in this SourceBuffer, stored as a series of Chunks.
FallibleTArray<Chunk> mChunks;
AutoTArray<Chunk, 1> mChunks;
/// Consumers which are waiting to be notified when new data is available.
nsTArray<RefPtr<IResumable>> mWaitingConsumers;

View file

@ -503,7 +503,7 @@ public:
// If the surface was not a placeholder, tell its image that we discarded it.
if (!aSurface->IsPlaceholder()) {
static_cast<Image*>(imageKey)->OnSurfaceDiscarded();
static_cast<Image*>(imageKey)->OnSurfaceDiscarded(aSurface->GetSurfaceKey());
}
StopTracking(aSurface, aAutoLock);
@ -835,6 +835,32 @@ public:
}
}
void ReleaseImageOnMainThread(already_AddRefed<image::Image>&& aImage,
const StaticMutexAutoLock& aAutoLock) {
RefPtr<image::Image> image = aImage;
if (!image) {
return;
}
bool needsDispatch = mReleasingImagesOnMainThread.IsEmpty();
mReleasingImagesOnMainThread.AppendElement(image);
if (!needsDispatch) {
// There is already a ongoing task for ClearReleasingImages().
return;
}
NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
SurfaceCache::ClearReleasingImages();
}));
}
void TakeReleasingImages(nsTArray<RefPtr<image::Image>>& aImage,
const StaticMutexAutoLock& aAutoLock) {
MOZ_ASSERT(NS_IsMainThread());
aImage.SwapElements(mReleasingImagesOnMainThread);
}
private:
already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
{
@ -942,7 +968,8 @@ private:
nsRefPtrHashtable<nsPtrHashKey<Image>,
ImageSurfaceCache> mImageCaches;
SurfaceTracker mExpirationTracker;
RefPtr<MemoryPressureObserver> mMemoryPressureObserver;
RefPtr<MemoryPressureObserver> mMemoryPressureObserver;
nsTArray<RefPtr<image::Image>> mReleasingImagesOnMainThread;
const uint32_t mDiscardFactor;
const Cost mMaxCost;
Cost mAvailableCost;
@ -1160,5 +1187,34 @@ SurfaceCache::MaximumCapacity()
return sInstance->MaximumCapacity();
}
/* static */
void SurfaceCache::ReleaseImageOnMainThread(
already_AddRefed<image::Image> aImage, bool aAlwaysProxy) {
if (NS_IsMainThread() && !aAlwaysProxy) {
RefPtr<image::Image> image = std::move(aImage);
return;
}
StaticMutexAutoLock lock(sInstanceMutex);
if (sInstance) {
sInstance->ReleaseImageOnMainThread(std::move(aImage), lock);
} else {
NS_ReleaseOnMainThread(std::move(aImage), /* aAlwaysProxy */ true);
}
}
/* static */
void SurfaceCache::ClearReleasingImages() {
MOZ_ASSERT(NS_IsMainThread());
nsTArray<RefPtr<image::Image>> images;
{
StaticMutexAutoLock lock(sInstanceMutex);
if (sInstance) {
sInstance->TakeReleasingImages(images, lock);
}
}
}
} // namespace image
} // namespace mozilla

View file

@ -417,6 +417,18 @@ struct SurfaceCache
*/
static size_t MaximumCapacity();
/**
* Release image on main thread.
* The function uses SurfaceCache to release pending releasing images quickly.
*/
static void ReleaseImageOnMainThread(already_AddRefed<image::Image> aImage,
bool aAlwaysProxy = false);
/**
* Clear all pending releasing images.
*/
static void ClearReleasingImages();
private:
virtual ~SurfaceCache() = 0; // Forbid instantiation.
};

View file

@ -28,6 +28,7 @@
#include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h"
#include "mozilla/Tuple.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Variant.h"
@ -175,6 +176,43 @@ public:
return *result;
}
/**
* Write pixels to the surface by calling a lambda which may write as many
* pixels as there is remaining to complete the row. It is not completely
* memory safe as it trusts the underlying decoder not to overrun the given
* buffer, however it is an acceptable tradeoff for performance.
*
* Writing continues until every pixel in the surface has been written to
* (i.e., IsSurfaceFinished() returns true) or the lambda returns a WriteState
* which WritePixelBlocks() will return to the caller.
*
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
* size passed to ConfigureFilter().
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove the PixelType template parameter from this
* method.
*
* @param aFunc A lambda that functions as a generator, yielding at most the
* maximum number of pixels requested. The lambda must accept a
* pointer argument to the first pixel to write, a maximum
* number of pixels to write as part of the block, and return a
* NextPixel<PixelType> value.
*
* @return A WriteState value indicating the lambda generator's state.
* WritePixelBlocks() itself will return WriteState::FINISHED if
* writing has finished, regardless of the lambda's internal state.
*/
template <typename PixelType, typename Func>
WriteState WritePixelBlocks(Func aFunc)
{
Maybe<WriteState> result;
while (!(result = DoWritePixelBlockToRow<PixelType>(Forward<Func>(aFunc)))) { }
return *result;
}
/**
* A variant of WritePixels() that writes a single row of pixels to the
* surface one at a time by repeatedly calling a lambda that yields pixels.
@ -449,6 +487,50 @@ protected:
private:
/**
* An internal method used to implement WritePixelBlocks. This method writes
* up to the number of pixels necessary to complete the row and returns Some()
* if we either finished the entire surface or the lambda returned a
* WriteState indicating that we should return to the caller. If the row was
* successfully written without either of those things happening, it returns
* Nothing(), allowing WritePixelBlocks() to iterate to fill as many rows as
* possible.
*/
template <typename PixelType, typename Func>
Maybe<WriteState> DoWritePixelBlockToRow(Func aFunc)
{
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
if (IsSurfaceFinished()) {
return Some(WriteState::FINISHED); // We're already done.
}
PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
int32_t remainder = mInputSize.width - mCol;
int32_t written;
Maybe<WriteState> result;
Tie(written, result) = aFunc(&rowPtr[mCol], remainder);
if (written == remainder) {
MOZ_ASSERT(result.isNothing());
mCol = mInputSize.width;
AdvanceRow(); // We've finished the row.
return IsSurfaceFinished() ? Some(WriteState::FINISHED)
: Nothing();
}
MOZ_ASSERT(written >= 0 && written < remainder);
MOZ_ASSERT(result.isSome());
mCol += written;
if (*result == WriteState::FINISHED) {
ZeroOutRestOfSurface<PixelType>();
}
return result;
}
/**
* An internal method used to implement both WritePixels() and
* WritePixelsToRow(). Those methods differ only in their behavior after a row
@ -607,6 +689,20 @@ public:
return mHead->WritePixels<PixelType>(Forward<Func>(aFunc));
}
/**
* A variant of WritePixels() that writes up to a single row of pixels to the
* surface in blocks by repeatedly calling a lambda that yields up to the
* requested number of pixels.
*
* @see SurfaceFilter::WritePixelBlocks() for the canonical documentation.
*/
template <typename PixelType, typename Func>
WriteState WritePixelBlocks(Func aFunc)
{
MOZ_ASSERT(mHead, "Use before configured!");
return mHead->WritePixelBlocks<PixelType>(Forward<Func>(aFunc));
}
/**
* A variant of WritePixels() that writes a single row of pixels to the
* surface one at a time by repeatedly calling a lambda that yields pixels.

View file

@ -1113,7 +1113,7 @@ VectorImage::RequestDiscard()
}
void
VectorImage::OnSurfaceDiscarded()
VectorImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{
MOZ_ASSERT(mProgressTracker);

View file

@ -49,7 +49,7 @@ public:
nsresult aResult,
bool aLastPart) override;
virtual void OnSurfaceDiscarded() override;
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
/**
* Callback for SVGRootRenderingObserver.

View file

@ -124,6 +124,8 @@ class nsBMPDecoder : public Decoder
public:
~nsBMPDecoder();
DecoderType GetType() const override { return DecoderType::BMP; }
/// Obtains the internal output image buffer.
uint32_t* GetImageData() { return reinterpret_cast<uint32_t*>(mImageData); }

View file

@ -299,10 +299,12 @@ nsGIFDecoder2::ColormapIndexToPixel<uint8_t>(uint8_t aIndex)
}
template <typename PixelSize>
NextPixel<PixelSize>
nsGIFDecoder2::YieldPixel(const uint8_t* aData,
size_t aLength,
size_t* aBytesReadOut)
Tuple<int32_t, Maybe<WriteState>>
nsGIFDecoder2::YieldPixels(const uint8_t* aData,
size_t aLength,
size_t* aBytesReadOut,
PixelSize* aPixelBlock,
int32_t aBlockSize)
{
MOZ_ASSERT(aData);
MOZ_ASSERT(aBytesReadOut);
@ -311,108 +313,119 @@ nsGIFDecoder2::YieldPixel(const uint8_t* aData,
// Advance to the next byte we should read.
const uint8_t* data = aData + *aBytesReadOut;
// If we don't have any decoded data to yield, try to read some input and
// produce some.
if (mGIFStruct.stackp == mGIFStruct.stack) {
while (mGIFStruct.bits < mGIFStruct.codesize && *aBytesReadOut < aLength) {
// Feed the next byte into the decoder's 32-bit input buffer.
mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits;
mGIFStruct.bits += 8;
data += 1;
*aBytesReadOut += 1;
}
if (mGIFStruct.bits < mGIFStruct.codesize) {
return AsVariant(WriteState::NEED_MORE_DATA);
}
// Get the leading variable-length symbol from the data stream.
int code = mGIFStruct.datum & mGIFStruct.codemask;
mGIFStruct.datum >>= mGIFStruct.codesize;
mGIFStruct.bits -= mGIFStruct.codesize;
const int clearCode = ClearCode();
// Reset the dictionary to its original state, if requested
if (code == clearCode) {
mGIFStruct.codesize = mGIFStruct.datasize + 1;
mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
mGIFStruct.avail = clearCode + 2;
mGIFStruct.oldcode = -1;
return AsVariant(WriteState::NEED_MORE_DATA);
}
// Check for explicit end-of-stream code. It should only appear after all
// image data, but if that was the case we wouldn't be in this function, so
// this is always an error condition.
if (code == (clearCode + 1)) {
return AsVariant(WriteState::FAILURE);
}
if (mGIFStruct.oldcode == -1) {
if (code >= MAX_BITS) {
return AsVariant(WriteState::FAILURE); // The code's too big; something's wrong.
int32_t written = 0;
while (aBlockSize > written) {
// If we don't have any decoded data to yield, try to read some input and
// produce some.
if (mGIFStruct.stackp == mGIFStruct.stack) {
while (mGIFStruct.bits < mGIFStruct.codesize && *aBytesReadOut < aLength) {
// Feed the next byte into the decoder's 32-bit input buffer.
mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits;
mGIFStruct.bits += 8;
data += 1;
*aBytesReadOut += 1;
}
mGIFStruct.firstchar = mGIFStruct.oldcode = code;
// Yield a pixel at the appropriate index in the colormap.
mGIFStruct.pixels_remaining--;
return AsVariant(ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]));
}
int incode = code;
if (code >= mGIFStruct.avail) {
*mGIFStruct.stackp++ = mGIFStruct.firstchar;
code = mGIFStruct.oldcode;
if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong.
}
}
while (code >= clearCode) {
if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
return AsVariant(WriteState::FAILURE);
if (mGIFStruct.bits < mGIFStruct.codesize) {
return MakeTuple(written, Some(WriteState::NEED_MORE_DATA));
}
*mGIFStruct.stackp++ = mGIFStruct.suffix[code];
code = mGIFStruct.prefix[code];
// Get the leading variable-length symbol from the data stream.
int code = mGIFStruct.datum & mGIFStruct.codemask;
mGIFStruct.datum >>= mGIFStruct.codesize;
mGIFStruct.bits -= mGIFStruct.codesize;
if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong.
const int clearCode = ClearCode();
// Reset the dictionary to its original state, if requested
if (code == clearCode) {
mGIFStruct.codesize = mGIFStruct.datasize + 1;
mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
mGIFStruct.avail = clearCode + 2;
mGIFStruct.oldcode = -1;
return MakeTuple(written, Some(WriteState::NEED_MORE_DATA));
}
// Check for explicit end-of-stream code. It should only appear after all
// image data, but if that was the case we wouldn't be in this function, so
// this is always an error condition.
if (code == (clearCode + 1)) {
return MakeTuple(written, Some(WriteState::FAILURE));
}
if (mGIFStruct.oldcode == -1) {
if (code >= MAX_BITS) {
// The code's too big; something's wrong.
return MakeTuple(written, Some(WriteState::FAILURE));
}
mGIFStruct.firstchar = mGIFStruct.oldcode = code;
// Yield a pixel at the appropriate index in the colormap.
mGIFStruct.pixels_remaining--;
aPixelBlock[written++] =
ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]);
continue;
}
int incode = code;
if (code >= mGIFStruct.avail) {
*mGIFStruct.stackp++ = mGIFStruct.firstchar;
code = mGIFStruct.oldcode;
if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
// Stack overflow; something's wrong.
return MakeTuple(written, Some(WriteState::FAILURE));
}
}
while (code >= clearCode) {
if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
return MakeTuple(written, Some(WriteState::FAILURE));
}
*mGIFStruct.stackp++ = mGIFStruct.suffix[code];
code = mGIFStruct.prefix[code];
if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
// Stack overflow; something's wrong.
return MakeTuple(written, Some(WriteState::FAILURE));
}
}
*mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];
// Define a new codeword in the dictionary.
if (mGIFStruct.avail < 4096) {
mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
mGIFStruct.avail++;
// If we've used up all the codewords of a given length increase the
// length of codewords by one bit, but don't exceed the specified maximum
// codeword size of 12 bits.
if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
(mGIFStruct.avail < 4096)) {
mGIFStruct.codesize++;
mGIFStruct.codemask += mGIFStruct.avail;
}
}
mGIFStruct.oldcode = incode;
}
*mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];
// Define a new codeword in the dictionary.
if (mGIFStruct.avail < 4096) {
mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
mGIFStruct.avail++;
// If we've used up all the codewords of a given length increase the
// length of codewords by one bit, but don't exceed the specified maximum
// codeword size of 12 bits.
if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
(mGIFStruct.avail < 4096)) {
mGIFStruct.codesize++;
mGIFStruct.codemask += mGIFStruct.avail;
}
if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
return MakeTuple(written, Some(WriteState::FAILURE));
}
mGIFStruct.oldcode = incode;
// Yield a pixel at the appropriate index in the colormap.
mGIFStruct.pixels_remaining--;
aPixelBlock[written++]
= ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp);
}
if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
return AsVariant(WriteState::FAILURE);
}
// Yield a pixel at the appropriate index in the colormap.
mGIFStruct.pixels_remaining--;
return AsVariant(ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp));
return MakeTuple(written, Maybe<WriteState>());
}
/// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
@ -1030,8 +1043,12 @@ nsGIFDecoder2::ReadLZWData(const char* aData, size_t aLength)
size_t bytesRead = 0;
auto result = mGIFStruct.images_decoded == 0
? mPipe.WritePixels<uint32_t>([&]{ return YieldPixel<uint32_t>(data, length, &bytesRead); })
: mPipe.WritePixels<uint8_t>([&]{ return YieldPixel<uint8_t>(data, length, &bytesRead); });
? mPipe.WritePixelBlocks<uint32_t>([&](uint32_t* aPixelBlock, int32_t aBlockSize) {
return YieldPixels<uint32_t>(data, length, &bytesRead, aPixelBlock, aBlockSize);
})
: mPipe.WritePixelBlocks<uint8_t>([&](uint8_t* aPixelBlock, int32_t aBlockSize) {
return YieldPixels<uint8_t>(data, length, &bytesRead, aPixelBlock, aBlockSize);
});
if (MOZ_UNLIKELY(bytesRead > length)) {
MOZ_ASSERT_UNREACHABLE("Overread?");

View file

@ -24,6 +24,8 @@ class nsGIFDecoder2 : public Decoder
public:
~nsGIFDecoder2();
DecoderType GetType() const override { return DecoderType::GIF; }
protected:
LexerResult DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;
@ -61,8 +63,12 @@ private:
ColormapIndexToPixel(uint8_t aIndex);
/// A generator function that performs LZW decompression and yields pixels.
template <typename PixelSize> NextPixel<PixelSize>
YieldPixel(const uint8_t* aData, size_t aLength, size_t* aBytesReadOut);
template <typename PixelSize> Tuple<int32_t, Maybe<WriteState>>
YieldPixels(const uint8_t* aData,
size_t aLength,
size_t* aBytesReadOut,
PixelSize* aPixelBlock,
int32_t aBlockSize);
/// Checks if we have transparency, either because the header indicates that
/// there's alpha, or because the frame rect doesn't cover the entire image.

View file

@ -69,6 +69,7 @@ public:
/// @return The offset from the beginning of the ICO to the first resource.
size_t FirstResourceOffset() const;
DecoderType GetType() const override { return DecoderType::ICO; }
LexerResult DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;
nsresult FinishInternal() override;

View file

@ -37,6 +37,8 @@ class nsIconDecoder : public Decoder
public:
virtual ~nsIconDecoder();
DecoderType GetType() const override { return DecoderType::ICON; }
LexerResult DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;

View file

@ -52,6 +52,8 @@ class nsJPEGDecoder : public Decoder
public:
virtual ~nsJPEGDecoder();
DecoderType GetType() const override { return DecoderType::JPEG; }
virtual void SetSampleSize(int aSampleSize) override
{
mSampleSize = aSampleSize;

View file

@ -25,6 +25,8 @@ public:
/// @return true if this PNG is a valid ICO resource.
bool IsValidICO() const;
DecoderType GetType() const override { return DecoderType::PNG; }
protected:
nsresult InitInternal() override;
LexerResult DoDecode(SourceBufferIterator& aIterator,

View file

@ -21,6 +21,8 @@ class nsWebPDecoder final : public Decoder
public:
virtual ~nsWebPDecoder();
DecoderType GetType() const override { return DecoderType::WEBP; }
protected:
LexerResult DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;

View file

@ -67,15 +67,6 @@ imgTools::DecodeImage(nsIInputStream* aInStr,
NS_ENSURE_ARG_POINTER(aInStr);
// Create a new image container to hold the decoded data.
nsAutoCString mimeType(aMimeType);
RefPtr<image::Image> image = ImageFactory::CreateAnonymousImage(mimeType);
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
if (image->HasError()) {
return NS_ERROR_FAILURE;
}
// Prepare the input stream.
nsCOMPtr<nsIInputStream> inStream = aInStr;
if (!NS_InputStreamIsBuffered(aInStr)) {
@ -92,6 +83,16 @@ imgTools::DecodeImage(nsIInputStream* aInStr,
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(length <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
// Create a new image container to hold the decoded data.
nsAutoCString mimeType(aMimeType);
RefPtr<image::Image> image =
ImageFactory::CreateAnonymousImage(mimeType, uint32_t(length));
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
if (image->HasError()) {
return NS_ERROR_FAILURE;
}
// Send the source data to the Image.
rv = image->OnImageDataAvailable(nullptr, nullptr, inStream, 0,
uint32_t(length));

View file

@ -47,6 +47,7 @@ EXPORTS += [
]
UNIFIED_SOURCES += [
'AnimationFrameBuffer.cpp',
'AnimationSurfaceProvider.cpp',
'ClippedImage.cpp',
'DecodedSurfaceProvider.cpp',

View file

@ -1238,7 +1238,7 @@ RegExpParser<CharT>::CreateNamedCaptureAtIndex(const CharacterVector* name,
int index)
{
MOZ_ASSERT(0 < index && index <= captures_started_);
MOZ_ASSERT(name !== nullptr);
MOZ_ASSERT(name != nullptr);
RegExpCapture* capture = GetCapture(index);
MOZ_ASSERT(capture->name() == nullptr);

View file

@ -1,341 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
/* Implementation of a service that converts certain vendor-prefixed CSS
properties to their unprefixed equivalents, for sites on a whitelist. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function CSSUnprefixingService() {
}
CSSUnprefixingService.prototype = {
// Boilerplate:
classID: Components.ID("{f0729490-e15c-4a2f-a3fb-99e1cc946b42}"),
_xpcom_factory: XPCOMUtils.generateSingletonFactory(CSSUnprefixingService),
QueryInterface: XPCOMUtils.generateQI([Ci.nsICSSUnprefixingService]),
// See documentation in nsICSSUnprefixingService.idl
generateUnprefixedDeclaration: function(aPropName, aRightHalfOfDecl,
aUnprefixedDecl /*out*/) {
// Convert our input strings to lower-case, for easier string-matching.
// (NOTE: If we ever need to add support for unprefixing properties that
// have case-sensitive parts, then we should do these toLowerCase()
// conversions in a more targeted way, to avoid breaking those properties.)
aPropName = aPropName.toLowerCase();
aRightHalfOfDecl = aRightHalfOfDecl.toLowerCase();
// We have several groups of supported properties:
// FIRST GROUP: Properties that can just be handled as aliases:
// ============================================================
const propertiesThatAreJustAliases = {
"-webkit-background-size": "background-size",
"-webkit-box-flex": "flex-grow",
"-webkit-box-ordinal-group": "order",
"-webkit-box-sizing": "box-sizing",
"-webkit-transform": "transform",
"-webkit-transform-origin": "transform-origin",
};
let unprefixedPropName = propertiesThatAreJustAliases[aPropName];
if (unprefixedPropName !== undefined) {
aUnprefixedDecl.value = unprefixedPropName + ":" + aRightHalfOfDecl;
return true;
}
// SECOND GROUP: Properties that take a single keyword, where the
// unprefixed version takes a different (but analogous) set of keywords:
// =====================================================================
const propertiesThatNeedKeywordMapping = {
"-webkit-box-align" : {
unprefixedPropName : "align-items",
valueMap : {
"start" : "flex-start",
"center" : "center",
"end" : "flex-end",
"baseline" : "baseline",
"stretch" : "stretch"
}
},
"-webkit-box-orient" : {
unprefixedPropName : "flex-direction",
valueMap : {
"horizontal" : "row",
"inline-axis" : "row",
"vertical" : "column",
"block-axis" : "column"
}
},
"-webkit-box-pack" : {
unprefixedPropName : "justify-content",
valueMap : {
"start" : "flex-start",
"center" : "center",
"end" : "flex-end",
"justify" : "space-between"
}
},
};
let propInfo = propertiesThatNeedKeywordMapping[aPropName];
if (typeof(propInfo) != "undefined") {
// Regexp for parsing the right half of a declaration, for keyword-valued
// properties. Divides the right half of the declaration into:
// 1) any leading whitespace
// 2) the property value (one or more alphabetical character or hyphen)
// 3) anything after that (e.g. "!important", ";")
// Then we can look up the appropriate unprefixed-property value for the
// value (part 2), and splice that together with the other parts and with
// the unprefixed property-name to make the final declaration.
const keywordValuedPropertyRegexp = /^(\s*)([a-z\-]+)(.*)/;
let parts = keywordValuedPropertyRegexp.exec(aRightHalfOfDecl);
if (!parts) {
// Failed to parse a keyword out of aRightHalfOfDecl. (It probably has
// no alphabetical characters.)
return false;
}
let mappedKeyword = propInfo.valueMap[parts[2]];
if (mappedKeyword === undefined) {
// We found a keyword in aRightHalfOfDecl, but we don't have a mapping
// to an equivalent keyword for the unprefixed version of the property.
return false;
}
aUnprefixedDecl.value = propInfo.unprefixedPropName + ":" +
parts[1] + // any leading whitespace
mappedKeyword +
parts[3]; // any trailing text (e.g. !important, semicolon, etc)
return true;
}
// THIRD GROUP: Properties that may need arbitrary string-replacement:
// ===================================================================
const propertiesThatNeedStringReplacement = {
// "-webkit-transition" takes a multi-part value. If "-webkit-transform"
// appears as part of that value, replace it w/ "transform".
// And regardless, we unprefix the "-webkit-transition" property-name.
// (We could handle other prefixed properties in addition to 'transform'
// here, but in practice "-webkit-transform" is the main one that's
// likely to be transitioned & that we're concerned about supporting.)
"-webkit-transition": {
unprefixedPropName : "transition",
stringMap : {
"-webkit-transform" : "transform",
}
},
};
propInfo = propertiesThatNeedStringReplacement[aPropName];
if (typeof(propInfo) != "undefined") {
let newRightHalf = aRightHalfOfDecl;
for (let strToReplace in propInfo.stringMap) {
let replacement = propInfo.stringMap[strToReplace];
newRightHalf = newRightHalf.split(strToReplace).join(replacement);
}
aUnprefixedDecl.value = propInfo.unprefixedPropName + ":" + newRightHalf;
return true;
}
// No known mapping for property aPropName.
return false;
},
// See documentation in nsICSSUnprefixingService.idl
generateUnprefixedGradientValue: function(aPrefixedFuncName,
aPrefixedFuncBody,
aUnprefixedFuncName, /*[out]*/
aUnprefixedFuncBody /*[out]*/) {
var unprefixedFuncName, newValue;
if (aPrefixedFuncName == "-webkit-gradient") {
// Create expression for oldGradientParser:
var parts = this.oldGradientParser(aPrefixedFuncBody);
var type = parts[0].name;
newValue = this.standardizeOldGradientArgs(type, parts.slice(1));
unprefixedFuncName = type + "-gradient";
}else{ // we're dealing with more modern syntax - should be somewhat easier, at least for linear gradients.
// Fix three things: remove -webkit-, add 'to ' before reversed top/bottom keywords (linear) or 'at ' before position keywords (radial), recalculate deg-values
// -webkit-linear-gradient( [ [ <angle> | [top | bottom] || [left | right] ],]? <color-stop>[, <color-stop>]+);
if (aPrefixedFuncName != "-webkit-linear-gradient" &&
aPrefixedFuncName != "-webkit-radial-gradient") {
// Unrecognized prefixed gradient type
return false;
}
unprefixedFuncName = aPrefixedFuncName.replace(/-webkit-/, '');
// Keywords top, bottom, left, right: can be stand-alone or combined pairwise but in any order ('top left' or 'left top')
// These give the starting edge or corner in the -webkit syntax. The standardised equivalent is 'to ' plus opposite values for linear gradients, 'at ' plus same values for radial gradients
if(unprefixedFuncName.indexOf('linear') > -1){
newValue = aPrefixedFuncBody.replace(/(top|bottom|left|right)+\s*(top|bottom|left|right)*/, function(str){
var words = str.split(/\s+/);
for(var i=0; i<words.length; i++){
switch(words[i].toLowerCase()){
case 'top':
words[i] = 'bottom';
break;
case 'bottom':
words[i] = 'top';
break;
case 'left':
words[i] = 'right';
break;
case 'right':
words[i] = 'left';
}
}
str = words.join(' ');
return ( 'to ' + str);
});
}else{
newValue = aPrefixedFuncBody.replace(/(top|bottom|left|right)+\s/, 'at $1 ');
}
newValue = newValue.replace(/\d+deg/, function (val) {
return (360 - (parseInt(val)-90))+'deg';
});
}
aUnprefixedFuncName.value = unprefixedFuncName;
aUnprefixedFuncBody.value = newValue;
return true;
},
// Helpers for generateUnprefixedGradientValue():
// ----------------------------------------------
oldGradientParser : function(str){
/** This method takes a legacy -webkit-gradient() method call and parses it
to pull out the values, function names and their arguments.
It returns something like [{name:'-webkit-gradient',args:[{name:'linear'}, {name:'top left'} ... ]}]
*/
var objs = [{}], path=[], current, word='', separator_chars = [',', '(', ')'];
current = objs[0], path[0] = objs;
//str = str.replace(/\s*\(/g, '('); // sorry, ws in front of ( would make parsing a lot harder
for(var i = 0; i < str.length; i++){
if(separator_chars.indexOf(str[i]) === -1){
word += str[i];
}else{ // now we have a "separator" - presumably we've also got a "word" or value
current.name = word.trim();
//GM_log(word+' '+path.length+' '+str[i])
word = '';
if(str[i] === '('){ // we assume the 'word' is a function, for example -webkit-gradient() or rgb(), so we create a place to record the arguments
if(!('args' in current)){
current.args = [];
}
current.args.push({});
path.push(current.args);
current = current.args[current.args.length - 1];
path.push(current);
}else if(str[i] === ')'){ // function is ended, no more arguments - go back to appending details to the previous branch of the tree
current = path.pop(); // drop 'current'
current = path.pop(); // drop 'args' reference
}else{
path.pop(); // remove 'current' object from path, we have no arguments to add
var current_parent = path[path.length - 1] || objs; // last object on current path refers to array that contained the previous "current"
current_parent.push({}); // we need a new object to hold this "word" or value
current = current_parent[current_parent.length - 1]; // that object is now the 'current'
path.push(current);
//GM_log(path.length)
}
}
}
return objs;
},
/* Given an array of args for "-webkit-gradient(...)" returned by
* oldGradientParser(), this function constructs a string representing the
* equivalent arguments for a standard "linear-gradient(...)" or
* "radial-gradient(...)" expression.
*
* @param type Either 'linear' or 'radial'.
* @param args An array of args for a "-webkit-gradient(...)" expression,
* provided by oldGradientParser() (not including gradient type).
*/
standardizeOldGradientArgs : function(type, args){
var stdArgStr = "";
var stops = [];
if(/^linear/.test(type)){
// linear gradient, args 1 and 2 tend to be start/end keywords
var points = [].concat(args[0].name.split(/\s+/), args[1].name.split(/\s+/)); // example: [left, top, right, top]
// Old webkit syntax "uses a two-point syntax that lets you explicitly state where a linear gradient starts and ends"
// if start/end keywords are percentages, let's massage the values a little more..
var rxPercTest = /\d+\%/;
if(rxPercTest.test(points[0]) || points[0] == 0){
var startX = parseInt(points[0]), startY = parseInt(points[1]), endX = parseInt(points[2]), endY = parseInt(points[3]);
stdArgStr += ((Math.atan2(endY- startY, endX - startX)) * (180 / Math.PI)+90) + 'deg';
}else{
if(points[1] === points[3]){ // both 'top' or 'bottom, this linear gradient goes left-right
stdArgStr += 'to ' + points[2];
}else if(points[0] === points[2]){ // both 'left' or 'right', this linear gradient goes top-bottom
stdArgStr += 'to ' + points[3];
}else if(points[1] === 'top'){ // diagonal gradient - from top left to opposite corner is 135deg
stdArgStr += '135deg';
}else{
stdArgStr += '45deg';
}
}
}else if(/^radial/i.test(type)){ // oooh, radial gradients..
stdArgStr += 'circle ' + args[3].name.replace(/(\d+)$/, '$1px') + ' at ' + args[0].name.replace(/(\d+) /, '$1px ').replace(/(\d+)$/, '$1px');
}
var toColor;
for(var j = type === 'linear' ? 2 : 4; j < args.length; j++){
var position, color, colorIndex;
if(args[j].name === 'color-stop'){
position = args[j].args[0].name;
colorIndex = 1;
}else if (args[j].name === 'to') {
position = '100%';
colorIndex = 0;
}else if (args[j].name === 'from') {
position = '0%';
colorIndex = 0;
};
if (position.indexOf('%') === -1) { // original Safari syntax had 0.5 equivalent to 50%
position = (parseFloat(position) * 100) +'%';
};
color = args[j].args[colorIndex].name;
if (args[j].args[colorIndex].args) { // the color is itself a function call, like rgb()
color += '(' + this.colorValue(args[j].args[colorIndex].args) + ')';
};
if (args[j].name === 'from'){
stops.unshift(color + ' ' + position);
}else if(args[j].name === 'to'){
toColor = color;
}else{
stops.push(color + ' ' + position);
}
}
// translating values to right syntax
for(var j = 0; j < stops.length; j++){
stdArgStr += ', ' + stops[j];
}
if(toColor){
stdArgStr += ', ' + toColor + ' 100%';
}
return stdArgStr;
},
colorValue: function(obj){
var ar = [];
for (var i = 0; i < obj.length; i++) {
ar.push(obj[i].name);
};
return ar.join(', ');
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CSSUnprefixingService]);

View file

@ -1,2 +0,0 @@
component {f0729490-e15c-4a2f-a3fb-99e1cc946b42} CSSUnprefixingService.js
contract @mozilla.org/css-unprefixing-service;1 {f0729490-e15c-4a2f-a3fb-99e1cc946b42}

View file

@ -21,12 +21,6 @@ with Files('nsDOM*'):
DIRS += ['xbl-marquee']
TEST_DIRS += ['test']
XPIDL_SOURCES += [
'nsICSSUnprefixingService.idl',
]
XPIDL_MODULE = 'layout_base'
EXPORTS += [
'!nsStyleStructList.h',
'AnimationCommon.h',
@ -215,11 +209,6 @@ SOURCES += [
'nsLayoutStylesheetCache.cpp',
]
EXTRA_COMPONENTS += [
'CSSUnprefixingService.js',
'CSSUnprefixingService.manifest',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

View file

@ -43,7 +43,6 @@
#include "nsIMediaList.h"
#include "nsStyleUtil.h"
#include "nsIPrincipal.h"
#include "nsICSSUnprefixingService.h"
#include "mozilla/Sprintf.h"
#include "nsContentUtils.h"
#include "nsAutoPtr.h"
@ -66,10 +65,6 @@ typedef nsCSSProps::KTableEntry KTableEntry;
static bool sOpentypeSVGEnabled;
static bool sWebkitPrefixedAliasesEnabled;
static bool sWebkitDevicePixelRatioEnabled;
static bool sUnprefixingServiceEnabled;
#ifdef NIGHTLY_BUILD
static bool sUnprefixingServiceGloballyWhitelisted;
#endif
static bool sMozGradientsEnabled;
static bool sControlCharVisibility;
@ -799,9 +794,7 @@ protected:
enum {
eParseDeclaration_InBraces = 1 << 0,
eParseDeclaration_AllowImportant = 1 << 1,
// The declaration we're parsing was generated by the CSSUnprefixingService:
eParseDeclaration_FromUnprefixingSvc = 1 << 2
eParseDeclaration_AllowImportant = 1 << 1
};
enum nsCSSContextType {
eCSSContext_General,
@ -824,20 +817,6 @@ protected:
nsCSSKeyword LookupKeywordPrefixAware(nsAString& aKeywordStr,
const KTableEntry aKeywordTable[]);
bool ShouldUseUnprefixingService() const;
bool ParsePropertyWithUnprefixingService(const nsAString& aPropertyName,
css::Declaration* aDeclaration,
uint32_t aFlags,
bool aMustCallValueAppended,
bool* aChanged,
nsCSSContextType aContext);
// When we detect a webkit-prefixed gradient expression, this function can be
// used to parse its body into outparam |aValue|, with the help of the
// CSSUnprefixingService.
// Only call if ShouldUseUnprefixingService() returns true.
bool ParseWebkitPrefixedGradientWithService(nsAString& aPrefixedFuncName,
nsCSSValue& aValue);
bool ParseProperty(nsCSSPropertyID aPropID);
bool ParsePropertyByFunction(nsCSSPropertyID aPropID);
CSSParseResult ParseSingleValueProperty(nsCSSValue& aValue,
@ -1526,9 +1505,8 @@ IsCSSTokenCalcFunction(const nsCSSToken& aToken)
// This enum helps us track whether we've unprefixed "display: -webkit-box"
// (treating it as "display: flex") in an earlier declaration within a series
// of declarations. (This only impacts behavior when the function
// "ShouldUseUnprefixingService()" returns true, and that should only happen
// for a short whitelist of origins.)
// of declarations. (This only impacts behavior if
// sWebkitPrefixedAliasesEnabled is true.)
enum WebkitBoxUnprefixState : uint8_t {
eNotParsingDecls, // We are *not* currently parsing a sequence of
// CSS declarations. (default state)
@ -7188,217 +7166,42 @@ CSSParserImpl::LookupKeywordPrefixAware(nsAString& aKeywordStr,
{
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aKeywordStr);
if (!sWebkitPrefixedAliasesEnabled) {
// Not accepting webkit-prefixed keywords -> don't do anything special.
return keyword;
}
if (aKeywordTable == nsCSSProps::kDisplayKTable) {
// NOTE: This code will be considerably simpler once we can do away with
// all Unprefixing Service code, in bug 1259348. But for the time being, we
// have to support two different strategies for handling -webkit-box here:
// (1) "Native support" (sWebkitPrefixedAliasesEnabled): we assume that
// -webkit-box will parse correctly (via an entry in kDisplayKTable),
// and we simply make a note that we've parsed it (so that we can we
// can give later "-moz-box" styling special handling as noted below).
// (2) "Unprefixing Service support" (ShouldUseUnprefixingService): we
// convert "-webkit-box" directly to modern "flex" (& do the same for
// any later "-moz-box" styling).
//
// Note that sWebkitPrefixedAliasesEnabled and
// ShouldUseUnprefixingService() are mutually exlusive, because the latter
// explicitly defers to the former.
if ((keyword == eCSSKeyword__webkit_box ||
keyword == eCSSKeyword__webkit_inline_box)) {
const bool usingUnprefixingService = ShouldUseUnprefixingService();
if (sWebkitPrefixedAliasesEnabled || usingUnprefixingService) {
// Make a note that we're accepting some "-webkit-{inline-}box" styling,
// so we can give special treatment to subsequent "-moz-{inline}-box".
// (See special treatment below.)
if (mWebkitBoxUnprefixState == eHaveNotUnprefixed) {
mWebkitBoxUnprefixState = eHaveUnprefixed;
}
if (usingUnprefixingService) {
// When we're using the unprefixing service, we treat
// "display:-webkit-box" as if it were "display:flex"
// (and "-webkit-inline-box" as "inline-flex").
return (keyword == eCSSKeyword__webkit_box) ?
eCSSKeyword_flex : eCSSKeyword_inline_flex;
}
// Make a note that we're accepting some "-webkit-{inline-}box" styling,
// so we can give special treatment to subsequent "-moz-{inline}-box".
// (See special treatment below.)
if (mWebkitBoxUnprefixState == eHaveNotUnprefixed) {
mWebkitBoxUnprefixState = eHaveUnprefixed;
}
}
// If we've seen "display: -webkit-box" (or "-webkit-inline-box") in an
// earlier declaration and we honored it, then we have to watch out for
// later "display: -moz-box" (and "-moz-inline-box") declarations; they're
// likely just a halfhearted attempt at compatibility, and they actually
// end up stomping on our emulation of the earlier -webkit-box
// display-value, via the CSS cascade. To prevent this problem, we treat
// "display: -moz-box" & "-moz-inline-box" as if they were simply a
// repetition of the webkit equivalent that we already parsed.
if (mWebkitBoxUnprefixState == eHaveUnprefixed &&
(keyword == eCSSKeyword__moz_box ||
keyword == eCSSKeyword__moz_inline_box)) {
MOZ_ASSERT(sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService(),
"mDidUnprefixWebkitBoxInEarlierDecl should only be set if "
"we're supporting webkit-prefixed aliases, or if we're using "
"the css unprefixing service on this site");
if (sWebkitPrefixedAliasesEnabled) {
return (keyword == eCSSKeyword__moz_box) ?
eCSSKeyword__webkit_box : eCSSKeyword__webkit_inline_box;
}
// (If we get here, we're using the Unprefixing Service, which means
// we're unprefixing all the way to modern flexbox display values.)
} else if (mWebkitBoxUnprefixState == eHaveUnprefixed &&
(keyword == eCSSKeyword__moz_box ||
keyword == eCSSKeyword__moz_inline_box)) {
// If we've seen "display: -webkit-box" (or "-webkit-inline-box") in an
// earlier declaration and we honored it, then we have to watch out for
// later "display: -moz-box" (and "-moz-inline-box") declarations; they're
// likely just a halfhearted attempt at compatibility, and they actually
// end up stomping on our emulation of the earlier -webkit-box
// display-value, via the CSS cascade. To prevent this problem, we treat
// "display: -moz-box" & "-moz-inline-box" as if they were simply a
// repetition of the webkit equivalent that we already parsed.
MOZ_ASSERT(sWebkitPrefixedAliasesEnabled,
"The only way mWebkitBoxUnprefixState can be eHaveUnprefixed "
"is if we're supporting webkit-prefixed aliases");
return (keyword == eCSSKeyword__moz_box) ?
eCSSKeyword_flex : eCSSKeyword_inline_flex;
eCSSKeyword__webkit_box : eCSSKeyword__webkit_inline_box;
}
}
return keyword;
}
bool
CSSParserImpl::ShouldUseUnprefixingService() const
{
if (!sUnprefixingServiceEnabled) {
// Unprefixing is globally disabled.
return false;
}
if (sWebkitPrefixedAliasesEnabled) {
// Native webkit-prefix support is enabled, which trumps the unprefixing
// service for handling prefixed CSS. Don't try to use both at once.
return false;
}
#ifdef NIGHTLY_BUILD
if (sUnprefixingServiceGloballyWhitelisted) {
// Unprefixing is globally whitelisted,
// so no need to check mSheetPrincipal.
return true;
}
#endif
// Unprefixing enabled; see if our principal is whitelisted for unprefixing.
return mSheetPrincipal && mSheetPrincipal->IsOnCSSUnprefixingWhitelist();
}
bool
CSSParserImpl::ParsePropertyWithUnprefixingService(
const nsAString& aPropertyName,
css::Declaration* aDeclaration,
uint32_t aFlags,
bool aMustCallValueAppended,
bool* aChanged,
nsCSSContextType aContext)
{
MOZ_ASSERT(ShouldUseUnprefixingService(),
"Caller should've checked ShouldUseUnprefixingService()");
nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
NS_ENSURE_TRUE(unprefixingSvc, false);
// Save the state so we can jump back to this spot if our unprefixing fails
// (so we can behave as if we didn't even try to unprefix).
nsAutoCSSParserInputStateRestorer parserStateBeforeTryingToUnprefix(this);
// Caller has already parsed the first half of the declaration --
// aPropertyName and the ":". Now, we record the rest of the CSS declaration
// (the part after ':') into rightHalfOfDecl. (This is the property value,
// plus anything else up to the end of the declaration -- maybe "!important",
// maybe trailing junk characters, maybe a semicolon, maybe a trailing "}".)
bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
nsAutoString rightHalfOfDecl;
mScanner->StartRecording();
SkipDeclaration(checkForBraces);
mScanner->StopRecording(rightHalfOfDecl);
// Try to unprefix:
bool success;
nsAutoString unprefixedDecl;
nsresult rv =
unprefixingSvc->GenerateUnprefixedDeclaration(aPropertyName,
rightHalfOfDecl,
unprefixedDecl, &success);
if (NS_FAILED(rv) || !success) {
return false;
}
// Attempt to parse the unprefixed declaration:
nsAutoScannerChanger scannerChanger(this, unprefixedDecl);
success = ParseDeclaration(aDeclaration,
aFlags | eParseDeclaration_FromUnprefixingSvc,
aMustCallValueAppended, aChanged, aContext);
if (success) {
// We succeeded, so we'll leave the parser pointing at the end of
// the declaration; don't restore it to the pre-recording position.
parserStateBeforeTryingToUnprefix.DoNotRestore();
}
return success;
}
bool
CSSParserImpl::ParseWebkitPrefixedGradientWithService(
nsAString& aPrefixedFuncName,
nsCSSValue& aValue)
{
MOZ_ASSERT(ShouldUseUnprefixingService(),
"Should only call if we're allowed to use unprefixing service");
// Record the body of the "-webkit-*gradient" function into a string.
// Note: we're already just after the opening "(".
nsAutoString prefixedFuncBody;
mScanner->StartRecording();
bool gotCloseParen = SkipUntil(')');
mScanner->StopRecording(prefixedFuncBody);
if (gotCloseParen) {
// Strip off trailing close-paren, so that the value we pass to the
// unprefixing service is *just* the function-body (no parens).
prefixedFuncBody.Truncate(prefixedFuncBody.Length() - 1);
}
// NOTE: Even if we fail, we'll be leaving the parser's cursor just after
// the close of the "-webkit-*gradient(...)" expression. This is the same
// behavior that the other Parse*Gradient functions have in their failure
// cases -- they call "SkipUntil(')') before returning false. So this is
// probably what we want.
nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
NS_ENSURE_TRUE(unprefixingSvc, false);
bool success;
nsAutoString unprefixedFuncName;
nsAutoString unprefixedFuncBody;
nsresult rv =
unprefixingSvc->GenerateUnprefixedGradientValue(aPrefixedFuncName,
prefixedFuncBody,
unprefixedFuncName,
unprefixedFuncBody,
&success);
if (NS_FAILED(rv) || !success) {
return false;
}
// JS service thinks it successfully converted the gradient! Now let's try
// to parse the resulting string.
// First, add a close-paren if we originally recorded one (so that what we're
// about to put into the CSS parser is a faithful representation of what it
// would've seen if it were just parsing the original input stream):
if (gotCloseParen) {
unprefixedFuncBody.Append(char16_t(')'));
}
nsAutoScannerChanger scannerChanger(this, unprefixedFuncBody);
if (unprefixedFuncName.EqualsLiteral("linear-gradient")) {
return ParseLinearGradient(aValue, 0);
}
if (unprefixedFuncName.EqualsLiteral("radial-gradient")) {
return ParseRadialGradient(aValue, 0);
}
NS_ERROR("CSSUnprefixingService returned an unrecognized type of "
"gradient function");
return false;
}
//----------------------------------------------------------------------
bool
@ -7488,19 +7291,7 @@ CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration,
(aContext == eCSSContext_Page &&
!nsCSSProps::PropHasFlags(propID,
CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
if (NonMozillaVendorIdentifier(propertyName)) {
if (!mInSupportsCondition &&
aContext == eCSSContext_General &&
!(aFlags & eParseDeclaration_FromUnprefixingSvc) && // no recursion
ShouldUseUnprefixingService()) {
if (ParsePropertyWithUnprefixingService(propertyName,
aDeclaration, aFlags,
aMustCallValueAppended,
aChanged, aContext)) {
return true;
}
}
} else {
if (!NonMozillaVendorIdentifier(propertyName)) {
REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
@ -8120,18 +7911,6 @@ CSSParserImpl::ParseVariant(nsCSSValue& aValue,
}
return CSSParseResult::Ok;
}
if (ShouldUseUnprefixingService() &&
!gradientFlags &&
StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
// Copy 'tmp' into a string on the stack, since as soon as we
// start parsing, its backing store (in "tk") will be overwritten
nsAutoString prefixedFuncName(tmp);
if (!ParseWebkitPrefixedGradientWithService(prefixedFuncName, aValue)) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_IMAGE_RECT) != 0 &&
eCSSToken_Function == tk->mType &&
@ -10912,7 +10691,7 @@ CSSParserImpl::ParseWebkitGradientRadius(float& aRadius)
// (either a percentage or a number between 0 and 1.0), and a color (any
// valid CSS color). In addition the shorthand functions from and to are
// supported. These functions only require a color argument and are
// equivalent to color-stop(0, ...) and color-stop(1.0, …) respectively.
// equivalent to color-stop(0, ...) and color-stop(1.0, ?? respectively.
bool
CSSParserImpl::ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient)
{
@ -12458,7 +12237,7 @@ CSSParserImpl::IsFunctionTokenValidForImageLayerImage(
funcName.LowerCaseEqualsLiteral("-moz-repeating-radial-gradient") ||
funcName.LowerCaseEqualsLiteral("-moz-image-rect") ||
funcName.LowerCaseEqualsLiteral("-moz-element") ||
((sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService()) &&
(sWebkitPrefixedAliasesEnabled &&
(funcName.LowerCaseEqualsLiteral("-webkit-gradient") ||
funcName.LowerCaseEqualsLiteral("-webkit-linear-gradient") ||
funcName.LowerCaseEqualsLiteral("-webkit-radial-gradient") ||
@ -15355,17 +15134,17 @@ CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue)
return true;
}
bool
CSSParserImpl::ParseFontVariationSettings(nsCSSValue& aValue)
{
// TODO: Actually implement this.
// This stub is here because websites insist on considering this
// very hardware-dependent and O.S.-variable low-level font-control
// as a "critical feature" which it isn't as there is 0 guarantee
// that font variation settings are supported or honored by any
// operating system used by the client.
return true;
bool
CSSParserImpl::ParseFontVariationSettings(nsCSSValue& aValue)
{
// TODO: Actually implement this.
// This stub is here because websites insist on considering this
// very hardware-dependent and O.S.-variable low-level font-control
// as a "critical feature" which it isn't as there is 0 guarantee
// that font variation settings are supported or honored by any
// operating system used by the client.
return true;
}
bool
@ -18020,12 +17799,6 @@ nsCSSParser::Startup()
"layout.css.prefixes.webkit");
Preferences::AddBoolVarCache(&sWebkitDevicePixelRatioEnabled,
"layout.css.prefixes.device-pixel-ratio-webkit");
Preferences::AddBoolVarCache(&sUnprefixingServiceEnabled,
"layout.css.unprefixing-service.enabled");
#ifdef NIGHTLY_BUILD
Preferences::AddBoolVarCache(&sUnprefixingServiceGloballyWhitelisted,
"layout.css.unprefixing-service.globally-whitelisted");
#endif
Preferences::AddBoolVarCache(&sMozGradientsEnabled,
"layout.css.prefixes.gradients");
Preferences::AddBoolVarCache(&sControlCharVisibility,

View file

@ -1,76 +0,0 @@
/* -*- Mode: IDL; 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/. */
/* interface for a service that converts certain vendor-prefixed CSS properties
to their unprefixed equivalents */
#include "nsISupports.idl"
[scriptable, uuid(a5d6e2f4-d3ec-11e4-b002-782bcbaebb28)]
interface nsICSSUnprefixingService : nsISupports
{
/**
* This function helps to convert unsupported vendor-prefixed CSS into
* supported unprefixed CSS. Given a vendor-prefixed property name and a
* value (or e.g. value + trailing junk like " !important;}"), this function
* will attempt to produce an equivalent CSS declaration that uses a
* supported unprefixed CSS property.
*
* @param aPropName
* The vendor-prefixed property name.
*
* @param aRightHalfOfDecl
* Everything after the ":" in the CSS declaration. This includes
* the property's value, along with possibly some leading whitespace
* and trailing text like "!important", and possibly a ';' and/or
* '}' (along with any other bogus text the author happens to
* include before those, which will probably make the decl invalid).
*
* @param aUnprefixedDecl[out]
* The resulting unprefixed declaration, if we return true.
*
* @return true if we were able to unprefix -- i.e. if we were able to
* convert the property to a known unprefixed equivalent, and we also
* performed any known-to-be-necessary fixup on the value, and we put
* the result in aUnprefixedDecl.
* Otherwise, this function returns false.
*/
boolean generateUnprefixedDeclaration(in AString aPropName,
in AString aRightHalfOfDecl,
out AString aUnprefixedDecl);
/**
* @param aPrefixedFuncName
* The webkit-prefixed gradient function: either
* "-webkit-gradient", "-webkit-linear-gradient", or
* "-webkit-radial-gradient".
*
* @param aPrefixedFuncBody
* The body of the gradient function, inside (& not including) the
* parenthesis.
*
* @param aUnprefixedFuncName[out]
* The resulting unprefixed gradient function name:
* either "linear-gradient" or "radial-gradient".
*
* @param aUnprefixedFuncBody[out]
* The resulting unprefixed gradient function body, suitable for
* including in a "linear-gradient(...)" or "radial-gradient(...)"
* expression.
*
* @returns true if we were able to successfully parse aWebkitGradientStr
* and populate the outparams accordingly; false otherwise.
*
*/
boolean generateUnprefixedGradientValue(in AString aPrefixedFuncName,
in AString aPrefixedFuncBody,
out AString aUnprefixedFuncName,
out AString aUnprefixedFuncBody);
};
%{C++
#define NS_CSSUNPREFIXINGSERVICE_CONTRACTID \
"@mozilla.org/css-unprefixing-service;1"
%}

View file

@ -273,10 +273,6 @@ support-files = ../../reftests/fonts/markA.woff ../../reftests/fonts/markB.woff
[test_units_frequency.html]
[test_units_length.html]
[test_units_time.html]
[test_unprefixing_service.html]
support-files = unprefixing_service_iframe.html unprefixing_service_utils.js
[test_unprefixing_service_prefs.html]
support-files = unprefixing_service_iframe.html unprefixing_service_utils.js
[test_value_cloning.html]
[test_value_computation.html]
[test_value_storage.html]

View file

@ -1,93 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1107378
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1107378</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="unprefixing_service_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1107378">Mozilla Bug 1107378</a>
<div id="display">
<iframe id="testIframe"></iframe>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
/**
* This test checks that unprefixing is enabled for whitelisted domains, and
* that it's disabled for non-whitelisted domains.
*
* We do this using an iframe, in which we load a test file at a test domain,
* and we have the iframe report back to us (using postMessage) about
* whether unprefixing is working.
*
* High-level overview of the process here:
* - First, we tweak prefs to enable unprefixing & enable the test-only
* entries in our unprefixing whitelist.
* - The rest of this test is driven by the "startNextTest()" method.
* This method pops a hostname to test and loads a URL from that host
* in the iframe.
* - We then listen for test-results from the iframe, using the postMessage
* handler in unprefixing_service_utils.js.
* - When the iframe indicates that it's done, we call "startNextTest()"
* again to pop the next host & load *that* in the iframe.
* - When nothing remains to be popped, we're done.
*/
const IFRAME_TESTFILE = "unprefixing_service_iframe.html";
// This function gets invoked when our iframe finishes a given round of testing.
function startNextTest()
{
// Test the next whitelisted host, if any remain.
if (gWhitelistedHosts.length > 0) {
let host = gWhitelistedHosts.pop();
info("Verifying that CSS Unprefixing Service is active, " +
"at whitelisted test-host '" + host + "'");
testHost(host, true);
return;
}
// Test the next not-whitelisted host, if any remain.
if (gNotWhitelistedHosts.length > 0) {
let host = gNotWhitelistedHosts.pop();
info("Verifying that CSS Unprefixing Service is inactive, " +
"at non-whitelisted test-host '" + host + "'");
testHost(host, false);
return;
}
// Both arrays empty --> we're done.
SimpleTest.finish();
}
function begin()
{
// Before we start loading things in iframes, set up postMessage handler.
registerPostMessageListener(startNextTest);
// Turn on prefs & start the first test!
SpecialPowers.pushPrefEnv(
{ set: [[PREF_UNPREFIXING_SERVICE, true],
[PREF_INCLUDE_TEST_DOMAINS, true],
// Make sure *native* -webkit prefix support is turned off. It's
// not whitelist-restricted, so if we left it enabled, it'd prevent
// us from being able to detect CSSUnprefixingService's domain
// whitelisting in this test.
["layout.css.prefixes.webkit", false]]},
startNextTest);
}
begin();
</script>
</pre>
</body>
</html>

View file

@ -1,132 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1132743
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1132743</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="unprefixing_service_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1132743">Mozilla Bug 1132743</a>
<div id="display">
<iframe id="testIframe"></iframe>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
/**
* This test checks that our CSS unprefixing prefs are effective.
*
* We do this using an iframe, in which we load a test file at a test domain
* (whose whitelist-status depends on a pref), and we have the iframe report
* back to us (using postMessage) about whether unprefixing is working.
*
* High-level overview of the process here (starting with begin()):
* - First, we ensure that the pref...
* "layout.css.unprefixing-service.include-test-domains"
* ...is *unset* by default. (No point exposing it in about:config).
* - Then, we test that (as a result of this pref being unset) the
* unprefixing service is *inactive* at our test-domain, by default.
* - Then, via a series of calls to "startNextTest()"/"testHost()", we re-test
* the same test-domain with a variety of pref configurations, to ensure
* that unprefixing only happens there when we've preffed on the service
* *and* we've enabled the testing entries in the whiteslist.
*/
const IFRAME_TESTFILE = "unprefixing_service_iframe.html";
// Just test the first host in our known-whitelisted-hosts list.
const WHITELISTED_TEST_HOST = gWhitelistedHosts[0];
// Configurations of our prefs to test.
// Each is a 3-entry array, whose entries mean:
// (1) should we enable the CSS Unprefixing Service pref?
// (2) should we enable the "include test domains in whitelist" pref?
// (3) in this pref-configuration, should we expect to see unprefixing active
// on our whitelisted test-domain?
//
// As you can see, the only configuration which should produce unprefixing
// activity is when *both* prefs are enabled.
let gTestConfigs = [
[false, false, false],
[false, true, false],
[true, false, false],
[true, true, true],
];
// Test that a particular configuration of prefs will activate or inactivate
// the CSS unprefixing service, for styles loaded from WHITELISTED_TEST_HOST.
// aTestConfig is described above, in documentation for gTestConfigs.
function testConfig(aTestConfig)
{
if (aTestConfig.length != 3) {
ok(false, "bug in test; need 3 entries. see gTestConfigs documentation");
}
info("Verifying that CSS Unprefixing Service is " +
(aTestConfig[2] ? "active" : "inactive") +
" at test host, with prefs: " +
PREF_UNPREFIXING_SERVICE + "=" + aTestConfig[0] + ", " +
PREF_INCLUDE_TEST_DOMAINS + "=" + aTestConfig[1]);
SpecialPowers.pushPrefEnv(
{ set:
[[PREF_UNPREFIXING_SERVICE, aTestConfig[0]],
[PREF_INCLUDE_TEST_DOMAINS, aTestConfig[1]]]
},
function() {
testHost(WHITELISTED_TEST_HOST, aTestConfig[2]);
});
}
// This function gets invoked when our iframe finishes a given round of testing.
function startNextTest()
{
if (gTestConfigs.length > 0) {
// Grab the next test-config, and kick off a test for it.
testConfig(gTestConfigs.pop());
return;
}
// Array empty --> we're done.
SimpleTest.finish();
}
function begin()
{
// First, check that PREF_INCLUDE_TEST_DOMAINS is unset:
try {
let val = SpecialPowers.getBoolPref(PREF_INCLUDE_TEST_DOMAINS);
ok(false, "The test pref '" + PREF_INCLUDE_TEST_DOMAINS +
"' should be unspecified by default");
} catch(e) { /* Good, we threw; pref is unset. */ }
// Before we start loading things in iframes, set up postMessage handler.
registerPostMessageListener(startNextTest);
// To kick things off, we don't set any prefs; we just test the default state
// (which should have the "include test domains" pref implicitly disabled, &
// hence unprefixing should end up being disabled in our iframe). Subsequent
// tests are kicked off via postMessage-triggered calls to startNextTest(),
// which will tweak prefs and re-test.
info("Verifying that CSS Unprefixing Service is inactive at test host, " +
"with default pref configuration");
testHost(WHITELISTED_TEST_HOST, false);
}
// Before we start, make sure *native* -webkit prefix support is turned off.
// It's not whitelist-restricted (and behaves slightly differently), so if we
// left it enabled, it'd prevent us from being able to detect
// CSSUnprefixingService's domain whitelisting in this test.
SpecialPowers.pushPrefEnv({ set: [["layout.css.prefixes.webkit", false]]},
begin);
</script>
</pre>
</body>
</html>

View file

@ -1,394 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Helper file for testing CSS Unprefixing Service</title>
<script type="text/javascript" src="property_database.js"></script>
<style type="text/css">
#wrapper {
width: 500px;
}
</style>
</head>
<body>
<div id="wrapper">
<div id="content"></div>
</div>
<script type="application/javascript;version=1.7">
"use strict";
/** Helper file for testing the CSS Unprefixing Service **/
/* Testcases for CSS Unprefixing service.
*
* Each testcase MUST have the following fields:
* - decl: A CSS declaration with prefixed style, to be tested via elem.style.
* - targetPropName: The name of the property whose value should be
* affected by |decl|.
*
* And will have EITHER:
* - isInvalid: If set to something truthy, this implies that |decl| is
* invalid and should have no effect on |targetPropName|'s
* computed or specified style.
*
* ...OR:
* - expectedDOMStyleVal: The value that we expect to find in the specified
* style -- in elem.style.[targetPropName].
* - expectedCompStyleVal: The value that we expect to find in the computed
* style -- in getComputedStyle(...)[targetPropName]
* If omitted, this is assumed to be the same as
* expectedDOMStyleVal. (Usually they'll be the same.)
*/
const gTestcases = [
{ decl: "-webkit-box-flex:5",
targetPropName: "flex-grow",
expectedDOMStyleVal: "5" },
/* If author happens to specify modern flexbox style after prefixed style,
make sure the modern stuff is preserved. */
{ decl: "-webkit-box-flex:4;flex-grow:6",
targetPropName: "flex-grow",
expectedDOMStyleVal: "6" },
/* Tests for handling !important: */
{ decl: "-webkit-box-flex:3!important;",
targetPropName: "flex-grow",
expectedDOMStyleVal: "3" },
{ decl: "-webkit-box-flex:2!important;flex-grow:1",
targetPropName: "flex-grow",
expectedDOMStyleVal: "2" },
{ decl: "-webkit-box-flex:1!important bogusText;",
targetPropName: "flex-grow",
isInvalid: true },
// Make sure we handle weird capitalization in property & value, too:
{ decl: "-WEBKIT-BoX-aLign: baSELine",
targetPropName: "align-items",
expectedDOMStyleVal: "baseline" },
{ decl: "display:-webkit-box",
targetPropName: "display",
expectedDOMStyleVal: "flex" },
{ decl: "display:-webkit-box; display:-moz-box;",
targetPropName: "display",
expectedDOMStyleVal: "flex" },
{ decl: "display:-webkit-foobar; display:-moz-box;",
targetPropName: "display",
expectedDOMStyleVal: "-moz-box" },
// -webkit-box-align: baseline | center | end | start | stretch
// ...maps to:
// align-items: baseline | center | flex-end | flex-start | stretch
{ decl: "-webkit-box-align: baseline",
targetPropName: "align-items",
expectedDOMStyleVal: "baseline" },
{ decl: "-webkit-box-align: center",
targetPropName: "align-items",
expectedDOMStyleVal: "center" },
{ decl: "-webkit-box-align: end",
targetPropName: "align-items",
expectedDOMStyleVal: "flex-end" },
{ decl: "-webkit-box-align: start",
targetPropName: "align-items",
expectedDOMStyleVal: "flex-start" },
{ decl: "-webkit-box-align: stretch",
targetPropName: "align-items",
expectedDOMStyleVal: "stretch" },
// -webkit-box-direction is not supported, because it's unused & would be
// complicated to support. See note in CSSUnprefixingService.js for more.
// -webkit-box-ordinal-group: <number> maps directly to "order".
{ decl: "-webkit-box-ordinal-group: 2",
targetPropName: "order",
expectedDOMStyleVal: "2" },
{ decl: "-webkit-box-ordinal-group: 6000",
targetPropName: "order",
expectedDOMStyleVal: "6000" },
// -webkit-box-orient: horizontal | inline-axis | vertical | block-axis
// ...maps to:
// flex-direction: row | row | column | column
{ decl: "-webkit-box-orient: horizontal",
targetPropName: "flex-direction",
expectedDOMStyleVal: "row" },
{ decl: "-webkit-box-orient: inline-axis",
targetPropName: "flex-direction",
expectedDOMStyleVal: "row" },
{ decl: "-webkit-box-orient: vertical",
targetPropName: "flex-direction",
expectedDOMStyleVal: "column" },
{ decl: "-webkit-box-orient: block-axis",
targetPropName: "flex-direction",
expectedDOMStyleVal: "column" },
// -webkit-box-pack: start | center | end | justify
// ... maps to:
// justify-content: flex-start | center | flex-end | space-between
{ decl: "-webkit-box-pack: start",
targetPropName: "justify-content",
expectedDOMStyleVal: "flex-start" },
{ decl: "-webkit-box-pack: center",
targetPropName: "justify-content",
expectedDOMStyleVal: "center" },
{ decl: "-webkit-box-pack: end",
targetPropName: "justify-content",
expectedDOMStyleVal: "flex-end" },
{ decl: "-webkit-box-pack: justify",
targetPropName: "justify-content",
expectedDOMStyleVal: "space-between" },
// -webkit-transform: <transform> maps directly to "transform"
{ decl: "-webkit-transform: matrix(1, 2, 3, 4, 5, 6)",
targetPropName: "transform",
expectedDOMStyleVal: "matrix(1, 2, 3, 4, 5, 6)" },
// -webkit-transform-origin: <value> maps directly to "transform-origin"
{ decl: "-webkit-transform-origin: 0 0",
targetPropName: "transform-origin",
expectedDOMStyleVal: "0px 0px 0px",
expectedCompStyleVal: "0px 0px" },
{ decl: "-webkit-transform-origin: 100% 0",
targetPropName: "transform-origin",
expectedDOMStyleVal: "100% 0px 0px",
expectedCompStyleVal: "500px 0px" },
// -webkit-transition: <property> maps directly to "transition"
{ decl: "-webkit-transition: width 1s linear 2s",
targetPropName: "transition",
expectedDOMStyleVal: "width 1s linear 2s" },
// -webkit-transition **with** -webkit-prefixed property in value.
{ decl: "-webkit-transition: -webkit-transform 1s linear 2s",
targetPropName: "transition",
expectedDOMStyleVal: "transform 1s linear 2s" },
// (Re-test to check that it sets the "transition-property" subproperty.)
{ decl: "-webkit-transition: -webkit-transform 1s linear 2s",
targetPropName: "transition-property",
expectedDOMStyleVal: "transform" },
// Same as previous test, except with "-webkit-transform" in the
// middle of the value instead of at the beginning (still valid):
{ decl: "-webkit-transition: 1s -webkit-transform linear 2s",
targetPropName: "transition",
expectedDOMStyleVal: "transform 1s linear 2s" },
{ decl: "-webkit-transition: 1s -webkit-transform linear 2s",
targetPropName: "transition-property",
expectedDOMStyleVal: "transform" },
// -webkit-gradient(linear, ...) expressions:
{ decl: "background-image: -webkit-gradient(linear,0 0,0 100%,from(rgb(1, 2, 3)),to(rgb(104, 105, 106)))",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(180deg, rgb(1, 2, 3) 0%, rgb(104, 105, 106) 100%)"},
{ decl: "background-image: -webkit-gradient(linear, left top, right bottom, from(rgb(1, 2, 3)), to(rgb(201, 202, 203)))",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(135deg, rgb(1, 2, 3) 0%, rgb(201, 202, 203) 100%)"},
{ decl: "background-image: -webkit-gradient(linear, left center, right center, from(rgb(1, 2, 3)), to(rgb(201, 202, 203)))",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(to right, rgb(1, 2, 3) 0%, rgb(201, 202, 203) 100%)"},
{ decl: "background-image: -webkit-gradient(linear, left center, right center, from(rgb(0, 0, 0)), color-stop(30%, rgb(255, 0, 0)), color-stop(60%, rgb(0, 255, 0)), to(rgb(0, 0, 255)))",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(to right, rgb(0, 0, 0) 0%, rgb(255, 0, 0) 30%, rgb(0, 255, 0) 60%, rgb(0, 0, 255) 100%)"},
// -webkit-gradient(radial, ...) expressions:
{ decl: "background-image: -webkit-gradient(radial, center center, 0, center center, 50, from(black), to(white)",
targetPropName: "background-image",
expectedDOMStyleVal: "radial-gradient(50px at center center , black 0%, white 100%)",
// XXXdholbert Note: unnecessary space, see bug 1160063----^
expectedCompStyleVal: "radial-gradient(50px, rgb(0, 0, 0) 0%, rgb(255, 255, 255) 100%)", },
{ decl: "background-image: -webkit-gradient(radial, left bottom, 0, center center, 50, from(yellow), color-stop(20%, orange), color-stop(40%, red), color-stop(60%, green), color-stop(80%, blue), to(purple))",
targetPropName: "background-image",
expectedDOMStyleVal: "radial-gradient(50px at left bottom , yellow 0%, orange 20%, red 40%, green 60%, blue 80%, purple 100%)",
// XXXdholbert Note: unnecessary space, see bug 1160063--^
expectedCompStyleVal: "radial-gradient(50px at 0% 100%, rgb(255, 255, 0) 0%, rgb(255, 165, 0) 20%, rgb(255, 0, 0) 40%, rgb(0, 128, 0) 60%, rgb(0, 0, 255) 80%, rgb(128, 0, 128) 100%)" },
// -webkit-linear-gradient(...) expressions:
{ decl: "background-image: -webkit-linear-gradient(top, blue, green)",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(to bottom, blue, green)",
expectedCompStyleVal: "linear-gradient(rgb(0, 0, 255), rgb(0, 128, 0))", },
{ decl: "background-image: -webkit-linear-gradient(left, blue, green)",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(to right, blue, green)",
expectedCompStyleVal: "linear-gradient(to right, rgb(0, 0, 255), rgb(0, 128, 0))", },
{ decl: "background-image: -webkit-linear-gradient(left bottom, blue, green)",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(to right top, blue, green)",
expectedCompStyleVal: "linear-gradient(to top right, rgb(0, 0, 255), rgb(0, 128, 0))", },
{ decl: "background-image: -webkit-linear-gradient(130deg, blue, green)",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(320deg, blue, green)",
expectedCompStyleVal: "linear-gradient(320deg, rgb(0, 0, 255), rgb(0, 128, 0))", },
// -webkit-radial-gradient(...) expressions:
{ decl: "background-image: -webkit-radial-gradient(#000, #fff)",
targetPropName: "background-image",
expectedDOMStyleVal: "radial-gradient(rgb(0, 0, 0), rgb(255, 255, 255))", },
{ decl: "background-image: -webkit-radial-gradient(bottom right, white, black)",
targetPropName: "background-image",
expectedDOMStyleVal: "radial-gradient(at right bottom , white, black)",
// XXXdholbert Note: unnecessary space---------------^ see bug 1160063
expectedCompStyleVal: "radial-gradient(at 100% 100%, rgb(255, 255, 255), rgb(0, 0, 0))", },
// Combination of unprefixed & prefixed gradient styles in a single 'background-image' expression
{ decl: "background-image: -webkit-linear-gradient(black, white), radial-gradient(blue, purple), -webkit-gradient(linear,0 0,0 100%,from(red),to(orange))",
targetPropName: "background-image",
expectedDOMStyleVal: "linear-gradient(black, white), radial-gradient(blue, purple), linear-gradient(180deg, red 0%, orange 100%)",
expectedCompStyleVal: "linear-gradient(rgb(0, 0, 0), rgb(255, 255, 255)), radial-gradient(rgb(0, 0, 255), rgb(128, 0, 128)), linear-gradient(180deg, rgb(255, 0, 0) 0%, rgb(255, 165, 0) 100%)", },
];
function getComputedStyleWrapper(elem, prop)
{
return window.getComputedStyle(elem, null).getPropertyValue(prop);
}
// Shims for "is()" and "ok()", which defer to parent window using postMessage:
function is(aActual, aExpected, aDesc)
{
// Add URL to description:
aDesc += " (iframe url: '" + window.location + "')";
window.parent.postMessage({type: "is",
actual: aActual,
expected: aExpected,
desc: aDesc}, "*");
}
function ok(aCondition, aDesc)
{
// Add URL to description:
aDesc += " (iframe url: '" + window.location + "')";
window.parent.postMessage({type: "ok",
condition: aCondition,
desc: aDesc}, "*");
}
// Main test function to use, to test a given unprefixed CSS property.
// The argument aTestcase should be an entry from gTestcases above.
function runOneTest(aTestcase)
{
let elem = document.getElementById("content");
// (self-test/sanity-check:)
if (!aTestcase.decl || !aTestcase.targetPropName) {
ok(false, "Bug in test; missing 'decl' or 'targetPropName' field");
}
// Populate testcase's implied fields:
if (aTestcase.isInvalid) {
// (self-test/sanity-check:)
if (aTestcase.expectedDOMStyleVal || aTestcase.expectedCompStyleVal) {
ok(false, "Bug in test; testcase w/ 'isInvalid' field also provided " +
"an expected*Val field, but should not have");
}
aTestcase.expectedDOMStyleVal = '';
aTestcase.expectedCompStyleVal = // initial computed style:
getComputedStyleWrapper(elem, aTestcase.targetPropName);
} else {
// (self-test/sanity-check:)
if (!aTestcase.expectedDOMStyleVal) {
ok(false, "Bug in test; testcase must provide expectedDOMStyleVal " +
"(or set isInvalid if it's testing an invalid decl)");
}
// If expected computed style is unspecified, we assume it should match
// expected DOM style:
if (!aTestcase.expectedCompStyleVal) {
aTestcase.expectedCompStyleVal = aTestcase.expectedDOMStyleVal;
}
}
elem.setAttribute("style", aTestcase.decl);
// Check that DOM elem.style has the expected value:
is(elem.style[aTestcase.targetPropName], aTestcase.expectedDOMStyleVal,
"Checking if CSS Unprefixing Service produced expected result " +
"in elem.style['" + aTestcase.targetPropName + "'] " +
"when given decl '" + aTestcase.decl + "'");
// Check that computed style has the expected value:
// (only for longhand properties; shorthands aren't in computed style)
if (gCSSProperties[aTestcase.targetPropName].type == CSS_TYPE_LONGHAND) {
let computedValue = getComputedStyleWrapper(elem, aTestcase.targetPropName);
is(computedValue, aTestcase.expectedCompStyleVal,
"Checking if CSS Unprefixing Service produced expected result " +
"in computed value of property '" + aTestcase.targetPropName + "' " +
"when given decl '" + aTestcase.decl + "'");
}
elem.removeAttribute("style");
}
// Function used to quickly test that unprefixing is off:
function testUnprefixingDisabled()
{
let elem = document.getElementById("content");
let initialFlexGrow = getComputedStyleWrapper(elem, "flex-grow");
elem.setAttribute("style", "-webkit-box-flex:5");
is(getComputedStyleWrapper(elem, "flex-grow"), initialFlexGrow,
"'-webkit-box-flex' shouldn't affect computed 'flex-grow' " +
"when CSS Unprefixing Service is inactive");
let initialDisplay = getComputedStyleWrapper(elem, "display");
elem.setAttribute("style", "display:-webkit-box");
is(getComputedStyleWrapper(elem, "display"), initialDisplay,
"'display:-webkit-box' shouldn't affect computed 'display' " +
"when CSS Unprefixing Service is inactive");
elem.style.display = "-webkit-box";
is(getComputedStyleWrapper(elem, "display"), initialDisplay,
"Setting elem.style.display to '-webkit-box' shouldn't affect computed " +
"'display' when CSS Unprefixing Service is inactive");
}
// Focused test that CSS Unprefixing Service is functioning properly
// on direct tweaks to elem.style.display:
function testStyleDisplayDirectly()
{
let elem = document.getElementById("content");
elem.style.display = "-webkit-box";
is(elem.style.display, "flex",
"Setting elem.style.display to '-webkit-box' should produce 'flex' " +
"in elem.style.display, when CSS Unprefixing Service is active");
is(getComputedStyleWrapper(elem, "display"), "flex",
"Setting elem.style.display to '-webkit-box' should produce 'flex' " +
"in computed style, when CSS Unprefixing Service is active");
// clean up:
elem.style.display = "";
}
function startTest()
{
if (window.location.hash === "#expectEnabled") {
testStyleDisplayDirectly();
gTestcases.forEach(runOneTest);
} else if (window.location.hash === "#expectDisabled") {
testUnprefixingDisabled();
} else {
ok(false,
"Need a recognized 'window.location.hash' to indicate expectation. " +
"Got: '" + window.location.hash + "'");
}
window.parent.postMessage({type: "testComplete"}, "*");
}
startTest();
</script>
</body>
</html>

View file

@ -1,87 +0,0 @@
/* 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/. */
// Shared data & functionality used in tests for CSS Unprefixing Service.
// Whitelisted hosts:
// (per implementation of nsPrincipal::IsOnCSSUnprefixingWhitelist())
var gWhitelistedHosts = [
// test1.example.org is on the whitelist.
"test1.example.org",
// test2.example.org is on the "allow all subdomains" whitelist.
"test2.example.org",
"sub1.test2.example.org",
"sub2.test2.example.org"
];
// *NOT* whitelisted hosts:
var gNotWhitelistedHosts = [
// Though test1.example.org is on the whitelist, its subdomains are not.
"sub1.test1.example.org",
// mochi.test is not on the whitelist.
"mochi.test:8888"
];
// Names of prefs:
const PREF_UNPREFIXING_SERVICE =
"layout.css.unprefixing-service.enabled";
const PREF_INCLUDE_TEST_DOMAINS =
"layout.css.unprefixing-service.include-test-domains";
// Helper-function to make unique URLs in testHost():
var gCounter = 0;
function getIncreasingCounter() {
return gCounter++;
}
// This function tests a particular host in our iframe.
// @param aHost The host to be tested
// @param aExpectEnabled Should we expect unprefixing to be enabled for host?
function testHost(aHost, aExpectEnabled) {
// Build the URL:
let url = window.location.protocol; // "http:" or "https:"
url += "//";
url += aHost;
// Append the path-name, up to the actual filename (the final "/"):
const re = /(.*\/).*/;
url += window.location.pathname.replace(re, "$1");
url += IFRAME_TESTFILE;
// In case this is the same URL as last time, we add "?N" for some unique N,
// to make each URL different, so that the iframe actually (re)loads:
url += "?" + getIncreasingCounter();
// We give the URL a #suffix to indicate to the test whether it should expect
// that unprefixing is enabled or disabled:
url += (aExpectEnabled ? "#expectEnabled" : "#expectDisabled");
let iframe = document.getElementById("testIframe");
iframe.contentWindow.location = url;
// The iframe will report its results back via postMessage.
// Our caller had better have set up a postMessage listener.
}
// Register a postMessage() handler, to allow our cross-origin iframe to
// communicate back to the main page's mochitest functionality.
// The handler expects postMessage to be called with an object like:
// { type: ["is"|"ok"|"testComplete"], ... }
// The "is" and "ok" types will trigger the corresponding function to be
// called in the main page, with named arguments provided in the payload.
// The "testComplete" type will trigger the passed-in aTestCompleteCallback
// function to be invoked (e.g. to advance to the next testcase, or to finish
// the overall test, as-appropriate).
function registerPostMessageListener(aTestCompleteCallback) {
let receiveMessage = function(event) {
if (event.data.type === "is") {
is(event.data.actual, event.data.expected, event.data.desc);
} else if (event.data.type === "ok") {
ok(event.data.condition, event.data.desc);
} else if (event.data.type === "testComplete") {
aTestCompleteCallback();
} else {
ok(false, "unrecognized data in postMessage call");
}
};
window.addEventListener("message", receiveMessage, false);
}

View file

@ -2556,16 +2556,6 @@ pref("layout.css.prefixes.webkit", true);
// pref is set to false.)
pref("layout.css.prefixes.device-pixel-ratio-webkit", false);
// Is the CSS Unprefixing Service enabled? (This service emulates support
// for certain vendor-prefixed properties & values, for sites on a "fixlist".)
pref("layout.css.unprefixing-service.enabled", true);
#ifdef NIGHTLY_BUILD
// Is the CSS Unprefixing Service whitelisted for all domains?
// (This pref is only honored in Nightly builds and can be removed when
// Bug 1177263 is fixed.)
pref("layout.css.unprefixing-service.globally-whitelisted", false);
#endif
// Is support for the :scope selector enabled?
pref("layout.css.scope-pseudo.enabled", true);
@ -4195,6 +4185,19 @@ pref("toolkit.zoomManager.zoomValues", ".3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2
// Image-related prefs
//
// The maximum size (in kB) that the aggregate frames of an animation can use
// before it starts to discard already displayed frames and redecode them as
// necessary.
pref("image.animated.decode-on-demand.threshold-kb", 262144);
// The minimum number of frames we want to have buffered ahead of an
// animation's currently displayed frame.
pref("image.animated.decode-on-demand.batch-size", 6);
// Resume an animated image from the last displayed frame rather than
// advancing when out of view.
pref("image.animated.resume-from-last-displayed", true);
// The maximum size, in bytes, of the decoded images we cache
pref("image.cache.size", 5242880);
@ -4231,6 +4234,10 @@ pref("image.layerize.always", false);
// compressed data.
pref("image.mem.discardable", true);
// Discards inactive image frames of _animated_ images and re-decodes them on
// demand from compressed data. Has no effect if image.mem.discardable is false.
pref("image.mem.animated.discardable", true);
// Allows image locking of decoded image data in content processes.
pref("image.mem.allow_locking_in_content_processes", true);

View file

@ -238,7 +238,7 @@ DataChannelConnection::~DataChannelConnection()
// Avoid spinning the event thread from here (which if we're mainthread
// is in the event loop already)
NS_DispatchToMainThread(WrapRunnable(nsCOMPtr<nsIThread>(mInternalIOThread),
&nsIThread::Shutdown),
&nsIThread::AsyncShutdown),
NS_DISPATCH_NORMAL);
}
} else {

View file

@ -92,6 +92,7 @@ PageIconProtocolHandler.prototype = {
try {
channel.contentType = mime;
channel.contentLength = len;
// Pass the icon data to the output stream.
let stream = Cc["@mozilla.org/binaryoutputstream;1"]
.createInstance(Ci.nsIBinaryOutputStream);