зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-i inm-c.
This commit is contained in:
Коммит
38529e6433
|
@ -27,6 +27,7 @@ pref("browser.cache.memory.capacity", 1024); // kilobytes
|
|||
|
||||
/* image cache prefs */
|
||||
pref("image.cache.size", 1048576); // bytes
|
||||
pref("image.high_quality_downscaling.enabled", false);
|
||||
|
||||
/* offline cache prefs */
|
||||
pref("browser.offline-apps.notify", false);
|
||||
|
|
|
@ -187,6 +187,7 @@ var shell = {
|
|||
});
|
||||
|
||||
this.contentBrowser.src = homeURL;
|
||||
this.isHomeLoaded = false;
|
||||
|
||||
ppmm.addMessageListener("content-handler", this);
|
||||
},
|
||||
|
@ -312,6 +313,17 @@ var shell = {
|
|||
DOMApplicationRegistry.allAppsLaunchable = true;
|
||||
|
||||
this.sendEvent(window, 'ContentStart');
|
||||
|
||||
content.addEventListener('load', function shell_homeLoaded() {
|
||||
content.removeEventListener('load', shell_homeLoaded);
|
||||
shell.isHomeLoaded = true;
|
||||
|
||||
if ('pendingChromeEvents' in shell) {
|
||||
shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
|
||||
}
|
||||
delete shell.pendingChromeEvents;
|
||||
});
|
||||
|
||||
break;
|
||||
case 'MozApplicationManifest':
|
||||
try {
|
||||
|
@ -357,6 +369,15 @@ var shell = {
|
|||
},
|
||||
|
||||
sendChromeEvent: function shell_sendChromeEvent(details) {
|
||||
if (!this.isHomeLoaded) {
|
||||
if (!('pendingChromeEvents' in this)) {
|
||||
this.pendingChromeEvents = [];
|
||||
}
|
||||
|
||||
this.pendingChromeEvents.push(details);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendEvent(getContentWindow(), "mozChromeEvent",
|
||||
ObjectWrapper.wrap(details, getContentWindow()));
|
||||
},
|
||||
|
|
20
configure.in
20
configure.in
|
@ -4181,6 +4181,7 @@ MOZ_SAMPLE_TYPE_S16=
|
|||
MOZ_MEDIA=
|
||||
MOZ_OPUS=1
|
||||
MOZ_WEBM=1
|
||||
MOZ_DASH=
|
||||
MOZ_WEBRTC=1
|
||||
MOZ_SRTP=
|
||||
MOZ_WEBRTC_SIGNALING=
|
||||
|
@ -5271,6 +5272,24 @@ if test -n "$MOZ_WEBM"; then
|
|||
MOZ_VP8=1
|
||||
fi;
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Enable DASH-WebM support
|
||||
dnl ========================================================
|
||||
MOZ_ARG_ENABLE_BOOL(dash,
|
||||
[ --enable-dash Enable support for DASH-WebM],
|
||||
MOZ_DASH=1,
|
||||
MOZ_DASH=)
|
||||
|
||||
if test -n "$MOZ_DASH"; then
|
||||
if test -n "$MOZ_WEBM"; then
|
||||
AC_DEFINE(MOZ_DASH)
|
||||
else
|
||||
dnl Fail if WebM is not enabled as well as DASH.
|
||||
AC_MSG_ERROR([WebM is currently disabled and must be enabled for DASH
|
||||
to work.])
|
||||
fi
|
||||
fi;
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Enable media plugin support
|
||||
dnl ========================================================
|
||||
|
@ -8523,6 +8542,7 @@ AC_SUBST(MOZ_VORBIS)
|
|||
AC_SUBST(MOZ_TREMOR)
|
||||
AC_SUBST(MOZ_OPUS)
|
||||
AC_SUBST(MOZ_WEBM)
|
||||
AC_SUBST(MOZ_DASH)
|
||||
AC_SUBST(MOZ_MEDIA_PLUGINS)
|
||||
AC_SUBST(MOZ_OMX_PLUGIN)
|
||||
AC_SUBST(MOZ_VP8_ERROR_CONCEALMENT)
|
||||
|
|
|
@ -33,6 +33,9 @@ typedef uint16_t nsMediaReadyState;
|
|||
namespace mozilla {
|
||||
class MediaResource;
|
||||
}
|
||||
#ifdef MOZ_DASH
|
||||
class nsDASHDecoder;
|
||||
#endif
|
||||
|
||||
class nsHTMLMediaElement : public nsGenericHTMLElement,
|
||||
public nsIObserver
|
||||
|
@ -46,6 +49,10 @@ public:
|
|||
|
||||
typedef nsDataHashtable<nsCStringHashKey, nsCString> MetadataTags;
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
friend class nsDASHDecoder;
|
||||
#endif
|
||||
|
||||
enum CanPlayStatus {
|
||||
CANPLAY_NO,
|
||||
CANPLAY_MAYBE,
|
||||
|
@ -319,6 +326,12 @@ public:
|
|||
static bool IsMediaPluginsType(const nsACString& aType);
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
static bool IsDASHEnabled();
|
||||
static bool IsDASHMPDType(const nsACString& aType);
|
||||
static const char gDASHMPDTypes[1][21];
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the mime type for this element.
|
||||
*/
|
||||
|
|
|
@ -91,6 +91,9 @@
|
|||
#ifdef MOZ_WIDGET_GONK
|
||||
#include "nsMediaOmxDecoder.h"
|
||||
#endif
|
||||
#ifdef MOZ_DASH
|
||||
#include "nsDASHDecoder.h"
|
||||
#endif
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
static PRLogModuleInfo* gMediaElementLog;
|
||||
|
@ -2221,6 +2224,37 @@ nsHTMLMediaElement::IsMediaPluginsType(const nsACString& aType)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
/* static */
|
||||
const char nsHTMLMediaElement::gDASHMPDTypes[1][21] = {
|
||||
"application/dash+xml"
|
||||
};
|
||||
|
||||
/* static */
|
||||
bool
|
||||
nsHTMLMediaElement::IsDASHEnabled()
|
||||
{
|
||||
return Preferences::GetBool("media.dash.enabled");
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
nsHTMLMediaElement::IsDASHMPDType(const nsACString& aType)
|
||||
{
|
||||
if (!IsDASHEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < ArrayLength(gDASHMPDTypes); ++i) {
|
||||
if (aType.EqualsASCII(gDASHMPDTypes[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* static */
|
||||
nsHTMLMediaElement::CanPlayStatus
|
||||
nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
|
||||
|
@ -2250,6 +2284,13 @@ nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
|
|||
return CANPLAY_YES;
|
||||
}
|
||||
#endif
|
||||
#ifdef MOZ_DASH
|
||||
if (IsDASHMPDType(nsDependentCString(aMIMEType))) {
|
||||
// DASH manifest uses WebM codecs only.
|
||||
*aCodecList = gWebMCodecs;
|
||||
return CANPLAY_YES;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_GSTREAMER
|
||||
if (IsH264Type(nsDependentCString(aMIMEType))) {
|
||||
|
@ -2439,6 +2480,15 @@ nsHTMLMediaElement::CreateDecoder(const nsACString& aType)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
if (IsDASHMPDType(aType)) {
|
||||
nsRefPtr<nsDASHDecoder> decoder = new nsDASHDecoder();
|
||||
if (decoder->Init(this)) {
|
||||
return decoder.forget();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_GSTREAMER
|
||||
if (IsH264Type(aType)) {
|
||||
nsRefPtr<nsGStreamerDecoder> decoder = new nsGStreamerDecoder();
|
||||
|
|
|
@ -84,6 +84,10 @@ ifdef MOZ_GSTREAMER
|
|||
PARALLEL_DIRS += gstreamer
|
||||
endif
|
||||
|
||||
ifdef MOZ_DASH
|
||||
PARALLEL_DIRS += dash
|
||||
endif
|
||||
|
||||
ifdef MOZ_MEDIA_PLUGINS
|
||||
PARALLEL_DIRS += plugins
|
||||
endif
|
||||
|
|
|
@ -30,6 +30,18 @@
|
|||
#include "nsContentUtils.h"
|
||||
#include "nsBlobProtocolHandler.h"
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
PRLogModuleInfo* gMediaResourceLog;
|
||||
#define LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \
|
||||
(msg, ##__VA_ARGS__))
|
||||
// Debug logging macro with object pointer and class name.
|
||||
#define CMLOG(msg, ...) \
|
||||
LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__)
|
||||
#else
|
||||
#define LOG(msg, ...)
|
||||
#define CMLOG(msg, ...)
|
||||
#endif
|
||||
|
||||
static const uint32_t HTTP_OK_CODE = 200;
|
||||
static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
|
||||
|
||||
|
@ -43,8 +55,17 @@ ChannelMediaResource::ChannelMediaResource(nsMediaDecoder* aDecoder,
|
|||
mCacheStream(this),
|
||||
mLock("ChannelMediaResource.mLock"),
|
||||
mIgnoreResume(false),
|
||||
mSeekingForMetadata(false)
|
||||
mSeekingForMetadata(false),
|
||||
mByteRangeDownloads(false),
|
||||
mByteRangeFirstOpen(true),
|
||||
mSeekOffsetMonitor("media.dashseekmonitor"),
|
||||
mSeekOffset(-1)
|
||||
{
|
||||
#ifdef PR_LOGGING
|
||||
if (!gMediaResourceLog) {
|
||||
gMediaResourceLog = PR_NewLogModule("MediaResource");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ChannelMediaResource::~ChannelMediaResource()
|
||||
|
@ -201,10 +222,57 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
|
|||
}
|
||||
}
|
||||
|
||||
if (mOffset > 0 && responseStatus == HTTP_OK_CODE) {
|
||||
// If we get an OK response but we were seeking, we have to assume
|
||||
// that seeking doesn't work. We also need to tell the cache that
|
||||
// it's getting data for the start of the stream.
|
||||
// Check response code for byte-range requests (seeking, chunk requests).
|
||||
if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
|
||||
// Byte range requests should get partial response codes and should
|
||||
// accept ranges.
|
||||
if (!acceptsRanges) {
|
||||
CMLOG("Error! HTTP_PARTIAL_RESPONSE_CODE received but server says "
|
||||
"range requests are not accepted! Channel[%p]", hc.get());
|
||||
mDecoder->NetworkError();
|
||||
CloseChannel();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Parse Content-Range header.
|
||||
int64_t rangeStart = 0;
|
||||
int64_t rangeEnd = 0;
|
||||
int64_t rangeTotal = 0;
|
||||
rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Content-Range header text should be parse-able.
|
||||
CMLOG("Error processing \'Content-Range' for "
|
||||
"HTTP_PARTIAL_RESPONSE_CODE: rv[%x]channel [%p]", rv, hc.get());
|
||||
mDecoder->NetworkError();
|
||||
CloseChannel();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Give some warnings if the ranges are unexpected.
|
||||
// XXX These could be error conditions.
|
||||
NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart,
|
||||
"response range start does not match request");
|
||||
NS_WARN_IF_FALSE(mOffset == rangeStart,
|
||||
"response range start does not match current offset");
|
||||
NS_WARN_IF_FALSE(mByteRange.mEnd == rangeEnd,
|
||||
"response range end does not match request");
|
||||
// Notify media cache about the length and start offset of data received.
|
||||
// Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
|
||||
// For now, tell the decoder that the stream is infinite.
|
||||
if (rangeTotal != -1) {
|
||||
mCacheStream.NotifyDataLength(rangeTotal);
|
||||
} else {
|
||||
mDecoder->SetInfinite(true);
|
||||
}
|
||||
mCacheStream.NotifyDataStarted(rangeStart);
|
||||
|
||||
mOffset = rangeStart;
|
||||
acceptsRanges = true;
|
||||
} else if (((mOffset > 0) || !mByteRange.IsNull())
|
||||
&& (responseStatus == HTTP_OK_CODE)) {
|
||||
// If we get an OK response but we were seeking, or requesting a byte
|
||||
// range, then we have to assume that seeking doesn't work. We also need
|
||||
// to tell the cache that it's getting data for the start of the stream.
|
||||
mCacheStream.NotifyDataStarted(0);
|
||||
mOffset = 0;
|
||||
|
||||
|
@ -284,6 +352,53 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
|
||||
int64_t& aRangeStart,
|
||||
int64_t& aRangeEnd,
|
||||
int64_t& aRangeTotal)
|
||||
{
|
||||
NS_ENSURE_ARG(aHttpChan);
|
||||
|
||||
nsAutoCString rangeStr;
|
||||
nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
|
||||
rangeStr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
// Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
|
||||
int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
|
||||
int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
|
||||
int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
|
||||
|
||||
nsAutoCString aRangeStartText;
|
||||
rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
|
||||
aRangeStart = aRangeStartText.ToInteger64(&rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
nsAutoCString aRangeEndText;
|
||||
rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
|
||||
aRangeEnd = aRangeEndText.ToInteger64(&rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
nsAutoCString aRangeTotalText;
|
||||
rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
|
||||
if (aRangeTotalText[0] == '*') {
|
||||
aRangeTotal = -1;
|
||||
} else {
|
||||
aRangeTotal = aRangeTotalText.ToInteger64(&rv);
|
||||
NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
CMLOG("Received bytes [%d] to [%d] of [%d]",
|
||||
aRangeStart, aRangeEnd, aRangeTotal);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
|
||||
{
|
||||
|
@ -296,6 +411,14 @@ ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
|
|||
mChannelStatistics.Stop(TimeStamp::Now());
|
||||
}
|
||||
|
||||
// If we were loading a byte range, notify decoder and return.
|
||||
// Skip this for unterminated byte range requests, e.g. seeking for whole
|
||||
// file downloads.
|
||||
if (mByteRangeDownloads) {
|
||||
mDecoder->NotifyDownloadEnded(aStatus);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Note that aStatus might have succeeded --- this might be a normal close
|
||||
// --- even in situations where the server cut us off because we were
|
||||
// suspended. So we need to "reopen on error" in that case too. The only
|
||||
|
@ -361,6 +484,8 @@ ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
|
|||
|
||||
// Keep track of where we're up to
|
||||
closure->mResource->mOffset += aCount;
|
||||
LOG("%p [ChannelMediaResource]: CopySegmentToCache new mOffset = %d",
|
||||
closure->mResource, closure->mResource->mOffset);
|
||||
closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
|
||||
closure->mPrincipal);
|
||||
*aWriteCount = aCount;
|
||||
|
@ -400,6 +525,37 @@ ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
/* |OpenByteRange|
|
||||
* For terminated byte range requests, use this function.
|
||||
* Callback is |nsBuiltinDecoder|::|NotifyByteRangeDownloaded|().
|
||||
* See |CacheClientSeek| also.
|
||||
*/
|
||||
|
||||
nsresult
|
||||
ChannelMediaResource::OpenByteRange(nsIStreamListener** aStreamListener,
|
||||
MediaByteRange const & aByteRange)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
|
||||
mByteRangeDownloads = true;
|
||||
mByteRange = aByteRange;
|
||||
|
||||
// OpenByteRange may be called multiple times; same URL, different ranges.
|
||||
// For the first call using this URL, forward to Open for some init.
|
||||
if (mByteRangeFirstOpen) {
|
||||
mByteRangeFirstOpen = false;
|
||||
return Open(aStreamListener);
|
||||
}
|
||||
|
||||
// For subsequent calls, ensure channel is recreated with correct byte range.
|
||||
CloseChannel();
|
||||
|
||||
nsresult rv = RecreateChannel();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return OpenChannel(aStreamListener);
|
||||
}
|
||||
|
||||
nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
|
@ -481,9 +637,19 @@ void ChannelMediaResource::SetupChannelHeaders()
|
|||
// requests, and therefore seeking, early.
|
||||
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
|
||||
if (hc) {
|
||||
// Use |mByteRange| for a specific chunk, or |mOffset| if seeking in a
|
||||
// complete file download.
|
||||
nsAutoCString rangeString("bytes=");
|
||||
rangeString.AppendInt(mOffset);
|
||||
if (!mByteRange.IsNull()) {
|
||||
rangeString.AppendInt(mByteRange.mStart);
|
||||
mOffset = mByteRange.mStart;
|
||||
} else {
|
||||
rangeString.AppendInt(mOffset);
|
||||
}
|
||||
rangeString.Append("-");
|
||||
if (!mByteRange.IsNull()) {
|
||||
rangeString.AppendInt(mByteRange.mEnd);
|
||||
}
|
||||
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
|
||||
|
||||
// Send Accept header for video and audio types only (Bug 489071)
|
||||
|
@ -592,6 +758,12 @@ nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
|
|||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
// Remember |aOffset|, because Media Cache may request a diff offset later.
|
||||
if (mByteRangeDownloads) {
|
||||
ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
|
||||
mSeekOffset = aOffset;
|
||||
}
|
||||
|
||||
return mCacheStream.Seek(aWhence, aOffset);
|
||||
}
|
||||
|
||||
|
@ -790,6 +962,50 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
|
|||
--mSuspendCount;
|
||||
}
|
||||
|
||||
// Note: For chunked downloads, e.g. DASH, we need to determine which chunk
|
||||
// contains the requested offset, |mOffset|. This is either previously
|
||||
// requested in |Seek| or updated to the most recent bytes downloaded.
|
||||
// So the process below is:
|
||||
// 1 - Query decoder for chunk containing desired offset, |mOffset|.
|
||||
// Return silently if the offset is not available; suggests decoder is
|
||||
// yet to get range information.
|
||||
// Return with NetworkError for all other errors.
|
||||
//
|
||||
// 2 - Adjust |mByteRange|.mStart to |aOffset|, requested by media cache.
|
||||
// For seeking, the media cache always requests the start of the cache
|
||||
// block, so we need to adjust the first chunk of a seek.
|
||||
// E.g. For "DASH-WebM On Demand" this means the first chunk after
|
||||
// seeking will most likely be larger than the subsegment (cluster).
|
||||
//
|
||||
// 3 - Call |OpenByteRange| requesting |mByteRange| bytes.
|
||||
|
||||
if (mByteRangeDownloads) {
|
||||
// Query decoder for chunk containing desired offset.
|
||||
nsresult rv;
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
|
||||
// Ensure that media cache can only request an equal or smaller offset;
|
||||
// it may be trying to include the start of a cache block.
|
||||
NS_ENSURE_TRUE(aOffset <= mSeekOffset, NS_ERROR_ILLEGAL_VALUE);
|
||||
rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
|
||||
mSeekOffset = -1;
|
||||
}
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
// Assume decoder will request correct bytes when range information
|
||||
// becomes available. Return silently.
|
||||
return NS_OK;
|
||||
} else if (NS_FAILED(rv) || mByteRange.IsNull()) {
|
||||
// Decoder reported an error we don't want to handle here; just return.
|
||||
mDecoder->NetworkError();
|
||||
CloseChannel();
|
||||
return rv;
|
||||
}
|
||||
// Media cache may decrease offset to start of cache data block.
|
||||
// Adjust start of byte range accordingly.
|
||||
mByteRange.mStart = mOffset = aOffset;
|
||||
return OpenByteRange(nullptr, mByteRange);
|
||||
}
|
||||
|
||||
mOffset = aOffset;
|
||||
|
||||
if (mSuspendCount > 0) {
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/XPCOM.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsIStreamListener.h"
|
||||
|
@ -111,6 +113,12 @@ public:
|
|||
return mStart == 0 && mEnd == 0;
|
||||
}
|
||||
|
||||
// Clears byte range values.
|
||||
void Clear() {
|
||||
mStart = 0;
|
||||
mEnd = 0;
|
||||
}
|
||||
|
||||
int64_t mStart, mEnd;
|
||||
};
|
||||
|
||||
|
@ -282,6 +290,17 @@ public:
|
|||
*/
|
||||
virtual nsresult Open(nsIStreamListener** aStreamListener) = 0;
|
||||
|
||||
/**
|
||||
* Open the stream using a specific byte range only. Creates a stream
|
||||
* listener and returns it in aStreamListener; this listener needs to be
|
||||
* notified of incoming data. Byte range is specified in aByteRange.
|
||||
*/
|
||||
virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
|
||||
MediaByteRange const &aByteRange)
|
||||
{
|
||||
return Open(aStreamListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills aRanges with MediaByteRanges representing the data which is cached
|
||||
* in the media cache. Stream should be pinned during call and while
|
||||
|
@ -364,6 +383,8 @@ public:
|
|||
|
||||
// Main thread
|
||||
virtual nsresult Open(nsIStreamListener** aStreamListener);
|
||||
virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
|
||||
MediaByteRange const & aByteRange);
|
||||
virtual nsresult Close();
|
||||
virtual void Suspend(bool aCloseImmediately);
|
||||
virtual void Resume();
|
||||
|
@ -435,6 +456,14 @@ protected:
|
|||
// Closes the channel. Main thread only.
|
||||
void CloseChannel();
|
||||
|
||||
// Parses 'Content-Range' header and returns results via parameters.
|
||||
// Returns error if header is not available, values are not parse-able or
|
||||
// values are out of range.
|
||||
nsresult ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
|
||||
int64_t& aRangeStart,
|
||||
int64_t& aRangeEnd,
|
||||
int64_t& aRangeTotal);
|
||||
|
||||
void DoNotifyDataReceived();
|
||||
|
||||
static NS_METHOD CopySegmentToCache(nsIInputStream *aInStream,
|
||||
|
@ -480,6 +509,21 @@ protected:
|
|||
|
||||
// True if we are seeking to get the real duration of the file.
|
||||
bool mSeekingForMetadata;
|
||||
|
||||
// Start and end offset of the bytes to be requested.
|
||||
MediaByteRange mByteRange;
|
||||
|
||||
// True if resource was opened with a byte rage request.
|
||||
bool mByteRangeDownloads;
|
||||
|
||||
// Set to false once first byte range request has been made.
|
||||
bool mByteRangeFirstOpen;
|
||||
|
||||
// For byte range requests, set to the offset requested in |Seek|.
|
||||
// Used in |CacheClientSeek| to find the originally requested byte range.
|
||||
// Read/Write on multiple threads; use |mSeekMonitor|.
|
||||
ReentrantMonitor mSeekOffsetMonitor;
|
||||
int64_t mSeekOffset;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# -*- Mode: makefile; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- #
|
||||
# vim: set ts=2 et sw=2 tw=80: #
|
||||
#
|
||||
# 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/.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Steve Workman <sworkman@mozilla.com>
|
||||
|
||||
DEPTH := @DEPTH@
|
||||
topsrcdir := @top_srcdir@
|
||||
srcdir := @srcdir@
|
||||
VPATH := @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MODULE := content
|
||||
LIBRARY_NAME := gkcondash_s
|
||||
LIBXUL_LIBRARY := 1
|
||||
|
||||
EXPORTS := \
|
||||
nsDASHDecoder.h \
|
||||
nsDASHRepDecoder.h \
|
||||
nsDASHReader.h \
|
||||
$(NULL)
|
||||
|
||||
CPPSRCS := \
|
||||
nsDASHDecoder.cpp \
|
||||
nsDASHRepDecoder.cpp \
|
||||
nsDASHReader.cpp \
|
||||
$(NULL)
|
||||
|
||||
FORCE_STATIC_LIB := 1
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
LOCAL_INCLUDES := \
|
||||
-I$(topsrcdir)/netwerk/dash/mpd \
|
||||
-I$(srcdir)/../webm \
|
||||
-I$(srcdir)/../../base/src \
|
||||
-I$(srcdir)/../../html/content/src \
|
||||
$(MOZ_LIBVPX_INCLUDES) \
|
||||
$(NULL)
|
|
@ -0,0 +1,725 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* DASH - Dynamic Adaptive Streaming over HTTP.
|
||||
*
|
||||
* DASH is an adaptive bitrate streaming technology where a multimedia file is
|
||||
* partitioned into one or more segments and delivered to a client using HTTP.
|
||||
*
|
||||
* Interaction with nsBuiltinDecoderStateMachine, nsHTMLMediaElement,
|
||||
* ChannelMediaResource and sub-decoders (nsWebMDecoder).
|
||||
*
|
||||
*
|
||||
* nsBuiltinDecoderStateMachine nsHTMLMediaElement
|
||||
* 1 / \ 1 / 1
|
||||
* / \ /
|
||||
* 1 / \ 1 / 1
|
||||
* nsDASHReader ------ nsDASHDecoder ------------ ChannelMediaResource
|
||||
* |1 1 1 |1 \1 (for MPD Manifest)
|
||||
* | | ------------
|
||||
* |* |* \*
|
||||
* nsWebMReader ------- nsDASHRepDecoder ------- ChannelMediaResource
|
||||
* 1 1 1 1 (for media streams)
|
||||
*
|
||||
* One decoder and state-machine, as with current, non-DASH decoders.
|
||||
*
|
||||
* DASH adds multiple readers, decoders and resources, in order to manage
|
||||
* download and decode of the MPD manifest and individual media streams.
|
||||
*
|
||||
* Rep/|Representation| is for an individual media stream, e.g. audio
|
||||
* nsDASHRepDecoder is the decoder for a rep/|Representation|.
|
||||
*
|
||||
* FLOW
|
||||
*
|
||||
* 1 - Download and parse the MPD (DASH XML-based manifest).
|
||||
*
|
||||
* Media element creates new |nsDASHDecoder| object:
|
||||
* member var initialization to default values, including a/v sub-decoders.
|
||||
* nsBuiltinDecoder and nsMediaDecoder constructors are called.
|
||||
* nsBuiltinDecoder::Init() is called.
|
||||
*
|
||||
* Media element creates new |ChannelMediaResource|:
|
||||
* used to download MPD manifest.
|
||||
*
|
||||
* Media element calls |nsDASHDecoder|->Load() to download the MPD file:
|
||||
* creates an |nsDASHReader| object to forward calls to multiple
|
||||
* nsWebMReaders (corresponding to MPD |Representation|s i.e. streams).
|
||||
* Note: 1 |nsDASHReader| per DASH/WebM MPD.
|
||||
*
|
||||
* also calls |ChannelMediaResource|::Open().
|
||||
* uses nsHttpChannel to download MPD; notifies nsDASHDecoder.
|
||||
*
|
||||
* Meanwhile, back in |nsDASHDecoder|->Load():
|
||||
* nsBuiltinDecoderStateMachine is created.
|
||||
* has ref to |nsDASHReader| object.
|
||||
* state machine is scheduled.
|
||||
*
|
||||
* Media element finishes decoder setup:
|
||||
* element added to media URI table etc.
|
||||
*
|
||||
* -- At this point, objects are waiting on HTTP returning MPD data.
|
||||
*
|
||||
* MPD Download (Async |ChannelMediaResource| channel callbacks):
|
||||
* calls nsDASHDecoder::|NotifyDownloadEnded|().
|
||||
* nsDASHDecoder parses MPD XML to DOM to MPD classes.
|
||||
* gets |Segment| URLs from MPD for audio and video streams.
|
||||
* creates |nsIChannel|s, |ChannelMediaResource|s.
|
||||
* stores resources as member vars (to forward function calls later).
|
||||
* creates |nsWebMReader|s and |nsDASHRepDecoder|s.
|
||||
* DASHreader creates |nsWebMReader|s.
|
||||
* |Representation| decoders are connected to the |ChannelMediaResource|s.
|
||||
*
|
||||
* |nsDASHDecoder|->|LoadRepresentations|() starts download and decode.
|
||||
*
|
||||
*
|
||||
* 2 - Media Stream, Byte Range downloads.
|
||||
*
|
||||
* -- At this point the Segment media stream downloads are managed by
|
||||
* individual |ChannelMediaResource|s and |nsWebMReader|s.
|
||||
* A single |nsDASHDecoder| and |nsBuiltinDecoderStateMachine| manage them
|
||||
* and communicate to |nsHTMLMediaElement|.
|
||||
*
|
||||
* Each |nsDASHRepDecoder| gets init range and index range from its MPD
|
||||
* |Representation|. |nsDASHRepDecoder| uses ChannelMediaResource to start the
|
||||
* byte range downloads, calling |OpenByteRange| with a |MediaByteRange|
|
||||
* object.
|
||||
* Once the init and index segments have been downloaded and |ReadMetadata| has
|
||||
* completed, each |nsWebMReader| notifies it's peer |nsDASHRepDecoder|.
|
||||
* Note: the decoder must wait until index data is parsed because it needs to
|
||||
* get the offsets of the subsegments (WebM clusters) from the media file
|
||||
* itself.
|
||||
* Since byte ranges for subsegments are obtained, |nsDASHRepdecoder| continues
|
||||
* downloading the files in byte range chunks.
|
||||
*
|
||||
* XXX Note that this implementation of nsDASHRepDecoder is focused on DASH
|
||||
* WebM On Demand profile: on the todo list is an action item to make this
|
||||
* more abstract.
|
||||
*
|
||||
* Note on |Seek|: Currently, |nsMediaCache| requires that seeking start at the
|
||||
* beginning of the block in which the desired offset would be
|
||||
* found. As such, when |ChannelMediaResource| does a seek
|
||||
* using DASH WebM subsegments (clusters), it requests a start
|
||||
* offset that corresponds to the beginning of the block, not
|
||||
* the start offset of the cluster. For DASH Webm, which has
|
||||
* media encoded in single files, this is fine. Future work on
|
||||
* other profiles will require this to be re-examined.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
#include <prdtoa.h>
|
||||
#include "nsIURI.h"
|
||||
#include "nsIFileURL.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIContentPolicy.h"
|
||||
#include "nsIContentSecurityPolicy.h"
|
||||
#include "nsICachingChannel.h"
|
||||
#include "nsBuiltinDecoderStateMachine.h"
|
||||
#include "nsWebMDecoder.h"
|
||||
#include "nsWebMReader.h"
|
||||
#include "nsDASHReader.h"
|
||||
#include "nsDASHMPDParser.h"
|
||||
#include "nsDASHRepDecoder.h"
|
||||
#include "nsDASHDecoder.h"
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gBuiltinDecoderLog;
|
||||
#define LOG(msg, ...) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
|
||||
("%p [nsDASHDecoder] " msg, this, __VA_ARGS__))
|
||||
#define LOG1(msg) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
|
||||
("%p [nsDASHDecoder] " msg, this))
|
||||
#else
|
||||
#define LOG(msg, ...)
|
||||
#define LOG1(msg)
|
||||
#endif
|
||||
|
||||
nsDASHDecoder::nsDASHDecoder() :
|
||||
nsBuiltinDecoder(),
|
||||
mNotifiedLoadAborted(false),
|
||||
mBuffer(nullptr),
|
||||
mBufferLength(0),
|
||||
mMPDReaderThread(nullptr),
|
||||
mPrincipal(nullptr),
|
||||
mDASHReader(nullptr),
|
||||
mAudioRepDecoder(nullptr),
|
||||
mVideoRepDecoder(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsDASHDecoder);
|
||||
}
|
||||
|
||||
nsDASHDecoder::~nsDASHDecoder()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsDASHDecoder);
|
||||
}
|
||||
|
||||
nsDecoderStateMachine*
|
||||
nsDASHDecoder::CreateStateMachine()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
return new nsBuiltinDecoderStateMachine(this, mDASHReader);
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::ReleaseStateMachine()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
|
||||
|
||||
// Since state machine owns mDASHReader, remove reference to it.
|
||||
mDASHReader = nullptr;
|
||||
|
||||
nsBuiltinDecoder::ReleaseStateMachine();
|
||||
for (uint i = 0; i < mAudioRepDecoders.Length(); i++) {
|
||||
mAudioRepDecoders[i]->ReleaseStateMachine();
|
||||
}
|
||||
for (uint i = 0; i < mVideoRepDecoders.Length(); i++) {
|
||||
mVideoRepDecoders[i]->ReleaseStateMachine();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHDecoder::Load(MediaResource* aResource,
|
||||
nsIStreamListener** aStreamListener,
|
||||
nsMediaDecoder* aCloneDonor)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
mDASHReader = new nsDASHReader(this);
|
||||
|
||||
nsresult rv = OpenResource(aResource, aStreamListener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mDecoderStateMachine = CreateStateMachine();
|
||||
if (!mDecoderStateMachine) {
|
||||
LOG1("Failed to create state machine!");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::NotifyDownloadEnded(nsresult aStatus)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// Should be no download ended notification if MPD Manager exists.
|
||||
if (mMPDManager) {
|
||||
LOG("Network Error! Repeated MPD download notification but MPD Manager "
|
||||
"[%p] already exists!", mMPDManager.get());
|
||||
NetworkError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
LOG1("MPD downloaded.");
|
||||
|
||||
// mPrincipal must be set on main thread before dispatch to parser thread.
|
||||
mPrincipal = GetCurrentPrincipal();
|
||||
|
||||
// Create reader thread for |ChannelMediaResource|::|Read|.
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &nsDASHDecoder::ReadMPDBuffer);
|
||||
NS_ENSURE_TRUE(event, );
|
||||
|
||||
nsresult rv = NS_NewNamedThread("DASH MPD Reader",
|
||||
getter_AddRefs(mMPDReaderThread),
|
||||
event,
|
||||
MEDIA_THREAD_STACK_SIZE);
|
||||
if (NS_FAILED(rv) || !mMPDReaderThread) {
|
||||
LOG("Error creating MPD reader thread: rv[%x] thread [%p].",
|
||||
rv, mMPDReaderThread.get());
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
} else if (aStatus == NS_BINDING_ABORTED) {
|
||||
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
|
||||
if (mElement) {
|
||||
mElement->LoadAborted();
|
||||
}
|
||||
return;
|
||||
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
|
||||
LOG("Network error trying to download MPD: aStatus [%x].", aStatus);
|
||||
NetworkError();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::ReadMPDBuffer()
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread.");
|
||||
|
||||
LOG1("Started reading from the MPD buffer.");
|
||||
|
||||
int64_t length = mResource->GetLength();
|
||||
if (length <= 0 || length > DASH_MAX_MPD_SIZE) {
|
||||
LOG("MPD is larger than [%d]MB.", DASH_MAX_MPD_SIZE/(1024*1024));
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
|
||||
mBuffer = new char[length];
|
||||
|
||||
uint32_t count = 0;
|
||||
nsresult rv = mResource->Read(mBuffer, length, &count);
|
||||
// By this point, all bytes should be available for reading.
|
||||
if (NS_FAILED(rv) || count != length) {
|
||||
LOG("Error reading MPD buffer: rv [%x] count [%d] length [%d].",
|
||||
rv, count, length);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
// Store buffer length for processing on main thread.
|
||||
mBufferLength = static_cast<uint32_t>(length);
|
||||
|
||||
LOG1("Finished reading MPD buffer; back to main thread for parsing.");
|
||||
|
||||
// Dispatch event to Main thread to parse MPD buffer.
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &nsDASHDecoder::OnReadMPDBufferCompleted);
|
||||
rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error dispatching parse event to main thread: rv[%x]", rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::OnReadMPDBufferCompleted()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
if (mShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shutdown the thread.
|
||||
if (!mMPDReaderThread) {
|
||||
LOG1("Error: MPD reader thread does not exist!");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
nsresult rv = mMPDReaderThread->Shutdown();
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("MPD reader thread did not shutdown correctly! rv [%x]", rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
mMPDReaderThread = nullptr;
|
||||
|
||||
// Close the MPD resource.
|
||||
rv = mResource ? mResource->Close() : NS_ERROR_NULL_POINTER;
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Media Resource did not close correctly! rv [%x]", rv);
|
||||
NetworkError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start parsing the MPD data and loading the media.
|
||||
rv = ParseMPDBuffer();
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error parsing MPD buffer! rv [%x]", rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
rv = CreateRepDecoders();
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error creating decoders for Representations! rv [%x]", rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
|
||||
rv = LoadRepresentations();
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error loading Representations! rv [%x]", rv);
|
||||
NetworkError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify reader that it can start reading metadata. Sub-readers will still
|
||||
// block until sub-resources have downloaded data into the media cache.
|
||||
mDASHReader->ReadyToReadMetadata();
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHDecoder::ParseMPDBuffer()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(mBuffer, NS_ERROR_NULL_POINTER);
|
||||
|
||||
LOG1("Started parsing the MPD buffer.");
|
||||
|
||||
// Parse MPD buffer and get root DOM element.
|
||||
nsAutoPtr<nsDASHMPDParser> parser;
|
||||
parser = new nsDASHMPDParser(mBuffer.forget(), mBufferLength, mPrincipal,
|
||||
mResource->URI());
|
||||
mozilla::net::DASHMPDProfile profile;
|
||||
parser->Parse(getter_Transfers(mMPDManager), &profile);
|
||||
mBuffer = nullptr;
|
||||
NS_ENSURE_TRUE(mMPDManager, NS_ERROR_NULL_POINTER);
|
||||
|
||||
LOG("Finished parsing the MPD buffer. Profile is [%d].", profile);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHDecoder::CreateRepDecoders()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(mMPDManager, NS_ERROR_NULL_POINTER);
|
||||
|
||||
// Global settings for the presentation.
|
||||
int64_t startTime = mMPDManager->GetStartTime();
|
||||
mDuration = mMPDManager->GetDuration();
|
||||
NS_ENSURE_TRUE(startTime >= 0 && mDuration > 0, NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
// For each audio/video stream, create a |ChannelMediaResource| object.
|
||||
|
||||
for (int i = 0; i < mMPDManager->GetNumAdaptationSets(); i++) {
|
||||
IMPDManager::AdaptationSetType asType = mMPDManager->GetAdaptationSetType(i);
|
||||
for (int j = 0; j < mMPDManager->GetNumRepresentations(i); j++) {
|
||||
// Get URL string.
|
||||
nsAutoString segmentUrl;
|
||||
nsresult rv = mMPDManager->GetFirstSegmentUrl(i, j, segmentUrl);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Get segment |nsIURI|; use MPD's base URI in case of relative paths.
|
||||
nsCOMPtr<nsIURI> url;
|
||||
rv = NS_NewURI(getter_AddRefs(url), segmentUrl, nullptr, mResource->URI());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
#ifdef PR_LOGGING
|
||||
nsAutoCString newUrl;
|
||||
rv = url->GetSpec(newUrl);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
LOG("Using URL=\"%s\" for AdaptationSet [%d] Representation [%d]",
|
||||
newUrl.get(), i, j);
|
||||
#endif
|
||||
|
||||
// 'file://' URLs are not supported.
|
||||
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url);
|
||||
NS_ENSURE_FALSE(fileURL, NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
// Create |nsDASHRepDecoder| objects for each representation.
|
||||
if (asType == IMPDManager::DASH_VIDEO_STREAM) {
|
||||
Representation const * rep = mMPDManager->GetRepresentation(i, j);
|
||||
NS_ENSURE_TRUE(rep, NS_ERROR_NULL_POINTER);
|
||||
rv = CreateVideoRepDecoder(url, rep);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else if (asType == IMPDManager::DASH_AUDIO_STREAM) {
|
||||
Representation const * rep = mMPDManager->GetRepresentation(i, j);
|
||||
NS_ENSURE_TRUE(rep, NS_ERROR_NULL_POINTER);
|
||||
rv = CreateAudioRepDecoder(url, rep);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NS_ENSURE_TRUE(mVideoRepDecoder, NS_ERROR_NOT_INITIALIZED);
|
||||
NS_ENSURE_TRUE(mAudioRepDecoder, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHDecoder::CreateAudioRepDecoder(nsIURI* aUrl,
|
||||
mozilla::net::Representation const * aRep)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_ARG(aUrl);
|
||||
NS_ENSURE_ARG(aRep);
|
||||
NS_ENSURE_TRUE(mElement, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
// Create subdecoder and init with media element.
|
||||
nsDASHRepDecoder* audioDecoder = new nsDASHRepDecoder(this);
|
||||
NS_ENSURE_TRUE(audioDecoder->Init(mElement), NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
if (!mAudioRepDecoder) {
|
||||
mAudioRepDecoder = audioDecoder;
|
||||
}
|
||||
mAudioRepDecoders.AppendElement(audioDecoder);
|
||||
|
||||
// Create sub-reader; attach to DASH reader and sub-decoder.
|
||||
nsWebMReader* audioReader = new nsWebMReader(audioDecoder);
|
||||
if (mDASHReader) {
|
||||
mDASHReader->AddAudioReader(audioReader);
|
||||
}
|
||||
audioDecoder->SetReader(audioReader);
|
||||
|
||||
// Create media resource with URL and connect to sub-decoder.
|
||||
MediaResource* audioResource
|
||||
= CreateAudioSubResource(aUrl, static_cast<nsMediaDecoder*>(audioDecoder));
|
||||
NS_ENSURE_TRUE(audioResource, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
audioDecoder->SetResource(audioResource);
|
||||
audioDecoder->SetMPDRepresentation(aRep);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHDecoder::CreateVideoRepDecoder(nsIURI* aUrl,
|
||||
mozilla::net::Representation const * aRep)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_ARG(aUrl);
|
||||
NS_ENSURE_ARG(aRep);
|
||||
NS_ENSURE_TRUE(mElement, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
// Create subdecoder and init with media element.
|
||||
nsDASHRepDecoder* videoDecoder = new nsDASHRepDecoder(this);
|
||||
NS_ENSURE_TRUE(videoDecoder->Init(mElement), NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
if (!mVideoRepDecoder) {
|
||||
mVideoRepDecoder = videoDecoder;
|
||||
}
|
||||
mVideoRepDecoders.AppendElement(videoDecoder);
|
||||
|
||||
// Create sub-reader; attach to DASH reader and sub-decoder.
|
||||
nsWebMReader* videoReader = new nsWebMReader(videoDecoder);
|
||||
if (mDASHReader) {
|
||||
mDASHReader->AddVideoReader(videoReader);
|
||||
}
|
||||
videoDecoder->SetReader(videoReader);
|
||||
|
||||
// Create media resource with URL and connect to sub-decoder.
|
||||
MediaResource* videoResource
|
||||
= CreateVideoSubResource(aUrl, static_cast<nsMediaDecoder*>(videoDecoder));
|
||||
NS_ENSURE_TRUE(videoResource, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
videoDecoder->SetResource(videoResource);
|
||||
videoDecoder->SetMPDRepresentation(aRep);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mozilla::MediaResource*
|
||||
nsDASHDecoder::CreateAudioSubResource(nsIURI* aUrl,
|
||||
nsMediaDecoder* aAudioDecoder)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(aUrl, nullptr);
|
||||
NS_ENSURE_TRUE(aAudioDecoder, nullptr);
|
||||
|
||||
// Create channel for representation.
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
nsresult rv = CreateSubChannel(aUrl, getter_AddRefs(channel));
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
// Create resource for representation.
|
||||
MediaResource* audioResource
|
||||
= MediaResource::Create(aAudioDecoder, channel);
|
||||
NS_ENSURE_TRUE(audioResource, nullptr);
|
||||
|
||||
return audioResource;
|
||||
}
|
||||
|
||||
mozilla::MediaResource*
|
||||
nsDASHDecoder::CreateVideoSubResource(nsIURI* aUrl,
|
||||
nsMediaDecoder* aVideoDecoder)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(aUrl, nullptr);
|
||||
NS_ENSURE_TRUE(aVideoDecoder, nullptr);
|
||||
|
||||
// Create channel for representation.
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
nsresult rv = CreateSubChannel(aUrl, getter_AddRefs(channel));
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
// Create resource for representation.
|
||||
MediaResource* videoResource
|
||||
= MediaResource::Create(aVideoDecoder, channel);
|
||||
NS_ENSURE_TRUE(videoResource, nullptr);
|
||||
|
||||
return videoResource;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHDecoder::CreateSubChannel(nsIURI* aUrl, nsIChannel** aChannel)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_ARG(aUrl);
|
||||
|
||||
nsCOMPtr<nsILoadGroup> loadGroup = mElement->GetDocumentLoadGroup();
|
||||
NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
|
||||
|
||||
// Check for a Content Security Policy to pass down to the channel
|
||||
// created to load the media content.
|
||||
nsCOMPtr<nsIChannelPolicy> channelPolicy;
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
||||
nsresult rv = mElement->NodePrincipal()->GetCsp(getter_AddRefs(csp));
|
||||
NS_ENSURE_SUCCESS(rv,rv);
|
||||
if (csp) {
|
||||
channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
|
||||
channelPolicy->SetContentSecurityPolicy(csp);
|
||||
channelPolicy->SetLoadType(nsIContentPolicy::TYPE_MEDIA);
|
||||
}
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
rv = NS_NewChannel(getter_AddRefs(channel),
|
||||
aUrl,
|
||||
nullptr,
|
||||
loadGroup,
|
||||
nullptr,
|
||||
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY,
|
||||
channelPolicy);
|
||||
NS_ENSURE_SUCCESS(rv,rv);
|
||||
NS_ENSURE_TRUE(channel, NS_ERROR_NULL_POINTER);
|
||||
|
||||
NS_ADDREF(*aChannel = channel);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHDecoder::LoadRepresentations()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
nsresult rv;
|
||||
{
|
||||
// Hold the lock while we do this to set proper lock ordering
|
||||
// expectations for dynamic deadlock detectors: decoder lock(s)
|
||||
// should be grabbed before the cache lock.
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
||||
// Load the decoders for each |Representation|'s media streams.
|
||||
if (mAudioRepDecoder) {
|
||||
rv = mAudioRepDecoder->Load();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
if (mVideoRepDecoder) {
|
||||
rv = mVideoRepDecoder->Load();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Failed to open stream! rv [%x].", rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (mAudioRepDecoder) {
|
||||
mAudioRepDecoder->SetStateMachine(mDecoderStateMachine);
|
||||
}
|
||||
if (mVideoRepDecoder) {
|
||||
mVideoRepDecoder->SetStateMachine(mDecoderStateMachine);
|
||||
}
|
||||
|
||||
// Now that subreaders are init'd, it's ok to init state machine.
|
||||
return InitializeStateMachine(nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::NotifyDownloadEnded(nsDASHRepDecoder* aRepDecoder,
|
||||
nsresult aStatus,
|
||||
MediaByteRange &aRange)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// MPD Manager must exist, indicating MPD has been downloaded and parsed.
|
||||
if (!mMPDManager) {
|
||||
LOG1("Network Error! MPD Manager must exist, indicating MPD has been "
|
||||
"downloaded and parsed");
|
||||
NetworkError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Decoder for the media |Representation| must not be null.
|
||||
if (!aRepDecoder) {
|
||||
LOG1("Decoder for Representation is reported as null.");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
// Return error if |aRepDecoder| does not match current audio/video decoder.
|
||||
if (aRepDecoder != mAudioRepDecoder && aRepDecoder != mVideoRepDecoder) {
|
||||
LOG("Error! Decoder [%p] does not match current sub-decoders!",
|
||||
aRepDecoder);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
LOG("Byte range downloaded: decoder [%p] range requested [%d - %d]",
|
||||
aRepDecoder, aRange.mStart, aRange.mEnd);
|
||||
|
||||
// XXX Do Stream Switching here before loading next bytes, e.g.
|
||||
// decoder = PossiblySwitchDecoder(aRepDecoder);
|
||||
// decoder->LoadNextByteRange();
|
||||
aRepDecoder->LoadNextByteRange();
|
||||
return;
|
||||
} else if (aStatus == NS_BINDING_ABORTED) {
|
||||
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
|
||||
if (mElement) {
|
||||
mElement->LoadAborted();
|
||||
}
|
||||
return;
|
||||
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
|
||||
LOG("Network error trying to download MPD: aStatus [%x].", aStatus);
|
||||
NetworkError();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::LoadAborted()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
if (!mNotifiedLoadAborted && mElement) {
|
||||
mElement->LoadAborted();
|
||||
mNotifiedLoadAborted = true;
|
||||
LOG1("Load Aborted! Notifying media element.");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::Shutdown()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// Notify reader of shutdown first.
|
||||
if (mDASHReader) {
|
||||
mDASHReader->NotifyDecoderShuttingDown();
|
||||
}
|
||||
|
||||
// Call parent class shutdown.
|
||||
nsBuiltinDecoder::Shutdown();
|
||||
NS_ENSURE_TRUE(mShuttingDown, );
|
||||
|
||||
// Shutdown reader thread if not already done.
|
||||
if (mMPDReaderThread) {
|
||||
nsresult rv = mMPDReaderThread->Shutdown();
|
||||
NS_ENSURE_SUCCESS(rv, );
|
||||
mMPDReaderThread = nullptr;
|
||||
}
|
||||
|
||||
// Forward to sub-decoders.
|
||||
for (uint i = 0; i < mAudioRepDecoders.Length(); i++) {
|
||||
if (mAudioRepDecoders[i]) {
|
||||
mAudioRepDecoders[i]->Shutdown();
|
||||
}
|
||||
}
|
||||
for (uint i = 0; i < mVideoRepDecoders.Length(); i++) {
|
||||
if (mVideoRepDecoders[i]) {
|
||||
mVideoRepDecoders[i]->Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHDecoder::DecodeError()
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
nsBuiltinDecoder::DecodeError();
|
||||
} else {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &nsBuiltinDecoder::DecodeError);
|
||||
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error dispatching DecodeError event to main thread: rv[%x]", rv);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* DASH - Dynamic Adaptive Streaming over HTTP
|
||||
*
|
||||
* DASH is an adaptive bitrate streaming technology where a multimedia file is
|
||||
* partitioned into one or more segments and delivered to a client using HTTP.
|
||||
*
|
||||
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
|
||||
|
||||
#if !defined(nsDASHDecoder_h_)
|
||||
#define nsDASHDecoder_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsBuiltinDecoder.h"
|
||||
#include "nsDASHReader.h"
|
||||
|
||||
class nsDASHRepDecoder;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
class IMPDManager;
|
||||
class nsDASHMPDParser;
|
||||
class Representation;
|
||||
}// net
|
||||
}// mozilla
|
||||
|
||||
class nsDASHDecoder : public nsBuiltinDecoder
|
||||
{
|
||||
public:
|
||||
typedef class mozilla::net::IMPDManager IMPDManager;
|
||||
typedef class mozilla::net::nsDASHMPDParser nsDASHMPDParser;
|
||||
typedef class mozilla::net::Representation Representation;
|
||||
|
||||
// XXX Arbitrary max file size for MPD. 50MB seems generously large.
|
||||
static const uint32_t DASH_MAX_MPD_SIZE = 50*1024*1024;
|
||||
|
||||
nsDASHDecoder();
|
||||
~nsDASHDecoder();
|
||||
|
||||
// Clone not supported; just return nullptr.
|
||||
nsMediaDecoder* Clone() { return nullptr; }
|
||||
|
||||
// Creates a single state machine for all stream decoders.
|
||||
// Called from Load on the main thread only.
|
||||
nsDecoderStateMachine* CreateStateMachine();
|
||||
|
||||
// Loads the MPD from the network and subsequently loads the media streams.
|
||||
// Called from the main thread only.
|
||||
nsresult Load(MediaResource* aResource,
|
||||
nsIStreamListener** aListener,
|
||||
nsMediaDecoder* aCloneDonor);
|
||||
|
||||
// Notifies download of MPD file has ended.
|
||||
// Called on the main thread only.
|
||||
void NotifyDownloadEnded(nsresult aStatus);
|
||||
|
||||
// Notifies that a byte range download has ended. As per the DASH spec, this
|
||||
// allows for stream switching at the boundaries of the byte ranges.
|
||||
// Called on the main thread only.
|
||||
void NotifyDownloadEnded(nsDASHRepDecoder* aRepDecoder,
|
||||
nsresult aStatus,
|
||||
MediaByteRange &aRange);
|
||||
|
||||
// Drop reference to state machine and tell sub-decoders to do the same.
|
||||
// Only called during shutdown dance, on main thread only.
|
||||
void ReleaseStateMachine();
|
||||
|
||||
// Overridden to forward |Shutdown| to sub-decoders.
|
||||
// Called on the main thread only.
|
||||
void Shutdown();
|
||||
|
||||
// Called by sub-decoders when load has been aborted. Will notify media
|
||||
// element only once. Called on the main thread only.
|
||||
void LoadAborted();
|
||||
|
||||
// Notifies the element that decoding has failed. On main thread, call is
|
||||
// forwarded to |nsBuiltinDecoder|::|Error| immediately. On other threads,
|
||||
// a call is dispatched for execution on the main thread.
|
||||
void DecodeError();
|
||||
|
||||
private:
|
||||
// Reads the MPD data from resource to a byte stream.
|
||||
// Called on the MPD reader thread.
|
||||
void ReadMPDBuffer();
|
||||
|
||||
// Called when MPD data is completely read.
|
||||
// On the main thread.
|
||||
void OnReadMPDBufferCompleted();
|
||||
|
||||
// Parses the copied MPD byte stream.
|
||||
// On the main thread: DOM APIs complain when off the main thread.
|
||||
nsresult ParseMPDBuffer();
|
||||
|
||||
// Creates the sub-decoders for a |Representation|, i.e. media streams.
|
||||
// On the main thread.
|
||||
nsresult CreateRepDecoders();
|
||||
|
||||
// Creates audio/video decoders for individual |Representation|s.
|
||||
// On the main thread.
|
||||
nsresult CreateAudioRepDecoder(nsIURI* aUrl, Representation const * aRep);
|
||||
nsresult CreateVideoRepDecoder(nsIURI* aUrl, Representation const * aRep);
|
||||
|
||||
// Creates audio/video resources for individual |Representation|s.
|
||||
// On the main thread.
|
||||
MediaResource* CreateAudioSubResource(nsIURI* aUrl,
|
||||
nsMediaDecoder* aAudioDecoder);
|
||||
MediaResource* CreateVideoSubResource(nsIURI* aUrl,
|
||||
nsMediaDecoder* aVideoDecoder);
|
||||
|
||||
// Creates an http channel for a |Representation|.
|
||||
// On the main thread.
|
||||
nsresult CreateSubChannel(nsIURI* aUrl, nsIChannel** aChannel);
|
||||
|
||||
// Loads the media |Representations|, i.e. the media streams.
|
||||
// On the main thread.
|
||||
nsresult LoadRepresentations();
|
||||
|
||||
// True when media element has already been notified of an aborted load.
|
||||
bool mNotifiedLoadAborted;
|
||||
|
||||
// Ptr for the MPD data.
|
||||
nsAutoArrayPtr<char> mBuffer;
|
||||
// Length of the MPD data.
|
||||
uint32_t mBufferLength;
|
||||
// Ptr to the MPD Reader thread.
|
||||
nsCOMPtr<nsIThread> mMPDReaderThread;
|
||||
// Document Principal.
|
||||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||||
|
||||
// MPD Manager provides access to the MPD information.
|
||||
nsAutoPtr<IMPDManager> mMPDManager;
|
||||
|
||||
// Main reader object; manages all sub-readers for |Representation|s. Owned by
|
||||
// state machine; destroyed in state machine's destructor.
|
||||
nsDASHReader* mDASHReader;
|
||||
|
||||
// Sub-decoder for current audio |Representation|.
|
||||
nsRefPtr<nsDASHRepDecoder> mAudioRepDecoder;
|
||||
// Array of pointers for the |Representation|s in the audio |AdaptationSet|.
|
||||
nsTArray<nsRefPtr<nsDASHRepDecoder> > mAudioRepDecoders;
|
||||
|
||||
// Sub-decoder for current video |Representation|.
|
||||
nsRefPtr<nsDASHRepDecoder> mVideoRepDecoder;
|
||||
// Array of pointers for the |Representation|s in the video |AdaptationSet|.
|
||||
nsTArray<nsRefPtr<nsDASHRepDecoder> > mVideoRepDecoders;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,329 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* DASH - Dynamic Adaptive Streaming over HTTP
|
||||
*
|
||||
* DASH is an adaptive bitrate streaming technology where a multimedia file is
|
||||
* partitioned into one or more segments and delivered to a client using HTTP.
|
||||
*
|
||||
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
|
||||
|
||||
#include "nsTimeRanges.h"
|
||||
#include "VideoFrameContainer.h"
|
||||
#include "nsBuiltinDecoder.h"
|
||||
#include "nsDASHReader.h"
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gBuiltinDecoderLog;
|
||||
#define LOG(msg, ...) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
|
||||
("%p [nsDASHReader] " msg, this, __VA_ARGS__))
|
||||
#define LOG1(msg) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
|
||||
("%p [nsDASHReader] " msg, this))
|
||||
#else
|
||||
#define LOG(msg, ...)
|
||||
#define LOG1(msg)
|
||||
#endif
|
||||
|
||||
nsresult
|
||||
nsDASHReader::Init(nsBuiltinDecoderReader* aCloneDonor)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
NS_ASSERTION(mAudioReaders.Length() != 0 && mVideoReaders.Length() != 0,
|
||||
"Audio and video readers should exist already.");
|
||||
|
||||
nsresult rv;
|
||||
for (uint i = 0; i < mAudioReaders.Length(); i++) {
|
||||
rv = mAudioReaders[i]->Init(nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
for (uint i = 0; i < mVideoReaders.Length(); i++) {
|
||||
rv = mVideoReaders[i]->Init(nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHReader::AddAudioReader(nsBuiltinDecoderReader* aAudioReader)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(aAudioReader, );
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
|
||||
mAudioReaders.AppendElement(aAudioReader);
|
||||
// XXX For now, just pick the first reader to be default.
|
||||
if (!mAudioReader)
|
||||
mAudioReader = aAudioReader;
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHReader::AddVideoReader(nsBuiltinDecoderReader* aVideoReader)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(aVideoReader, );
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
|
||||
mVideoReaders.AppendElement(aVideoReader);
|
||||
// XXX For now, just pick the first reader to be default.
|
||||
if (!mVideoReader)
|
||||
mVideoReader = aVideoReader;
|
||||
}
|
||||
|
||||
int64_t
|
||||
nsDASHReader::VideoQueueMemoryInUse()
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
return (mVideoReader ? mVideoReader->VideoQueueMemoryInUse() : 0);
|
||||
}
|
||||
|
||||
int64_t
|
||||
nsDASHReader::AudioQueueMemoryInUse()
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
return (mAudioReader ? mAudioReader->AudioQueueMemoryInUse() : 0);
|
||||
}
|
||||
|
||||
bool
|
||||
nsDASHReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
int64_t aTimeThreshold)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
if (mVideoReader) {
|
||||
return mVideoReader->DecodeVideoFrame(aKeyframeSkip, aTimeThreshold);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nsDASHReader::DecodeAudioData()
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
return (mAudioReader ? mAudioReader->DecodeAudioData() : false);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHReader::ReadMetadata(nsVideoInfo* aInfo,
|
||||
nsHTMLMediaElement::MetadataTags** aTags)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
// Wait for MPD to be parsed and child readers created.
|
||||
LOG1("Waiting for metadata download.");
|
||||
nsresult rv = WaitForMetadata();
|
||||
// If we get an abort, return silently; the decoder is shutting down.
|
||||
if (NS_ERROR_ABORT == rv) {
|
||||
return NS_OK;
|
||||
}
|
||||
// Verify no other errors before continuing.
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Get metadata from child readers.
|
||||
nsVideoInfo audioInfo, videoInfo;
|
||||
|
||||
if (mVideoReader) {
|
||||
rv = mVideoReader->ReadMetadata(&videoInfo, aTags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mInfo.mHasVideo = videoInfo.mHasVideo;
|
||||
mInfo.mDisplay = videoInfo.mDisplay;
|
||||
}
|
||||
if (mAudioReader) {
|
||||
rv = mAudioReader->ReadMetadata(&audioInfo, aTags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mInfo.mHasAudio = audioInfo.mHasAudio;
|
||||
mInfo.mAudioRate = audioInfo.mAudioRate;
|
||||
mInfo.mAudioChannels = audioInfo.mAudioChannels;
|
||||
mInfo.mStereoMode = audioInfo.mStereoMode;
|
||||
}
|
||||
|
||||
*aInfo = mInfo;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHReader::Seek(int64_t aTime,
|
||||
int64_t aStartTime,
|
||||
int64_t aEndTime,
|
||||
int64_t aCurrentTime)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
nsresult rv;
|
||||
|
||||
if (mAudioReader) {
|
||||
rv = mAudioReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
if (mVideoReader) {
|
||||
rv = mVideoReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHReader::GetBuffered(nsTimeRanges* aBuffered,
|
||||
int64_t aStartTime)
|
||||
{
|
||||
NS_ENSURE_ARG(aBuffered);
|
||||
|
||||
MediaResource* resource = nullptr;
|
||||
nsBuiltinDecoder* decoder = nullptr;
|
||||
|
||||
// Need to find intersect of |nsTimeRanges| for audio and video.
|
||||
nsTimeRanges audioBuffered, videoBuffered;
|
||||
uint32_t audioRangeCount, videoRangeCount;
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// First, get buffered ranges for sub-readers.
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
if (mAudioReader) {
|
||||
decoder = mAudioReader->GetDecoder();
|
||||
NS_ENSURE_TRUE(decoder, NS_ERROR_NULL_POINTER);
|
||||
resource = decoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
|
||||
resource->Pin();
|
||||
rv = mAudioReader->GetBuffered(&audioBuffered, aStartTime);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
resource->Unpin();
|
||||
rv = audioBuffered.GetLength(&audioRangeCount);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
if (mVideoReader) {
|
||||
decoder = mVideoReader->GetDecoder();
|
||||
NS_ENSURE_TRUE(decoder, NS_ERROR_NULL_POINTER);
|
||||
resource = decoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
|
||||
resource->Pin();
|
||||
rv = mVideoReader->GetBuffered(&videoBuffered, aStartTime);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
resource->Unpin();
|
||||
rv = videoBuffered.GetLength(&videoRangeCount);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Now determine buffered data for available sub-readers.
|
||||
if (mAudioReader && mVideoReader) {
|
||||
// Calculate intersecting ranges.
|
||||
for (uint32_t i = 0; i < audioRangeCount; i++) {
|
||||
// |A|udio, |V|ideo, |I|ntersect.
|
||||
double startA, startV, startI;
|
||||
double endA, endV, endI;
|
||||
rv = audioBuffered.Start(i, &startA);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = audioBuffered.End(i, &endA);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
for (uint32_t j = 0; j < videoRangeCount; j++) {
|
||||
rv = videoBuffered.Start(i, &startV);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = videoBuffered.End(i, &endV);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// If video block is before audio block, compare next video block.
|
||||
if (startA > endV) {
|
||||
continue;
|
||||
// If video block is after audio block, all of them are; compare next
|
||||
// audio block.
|
||||
} else if (endA < startV) {
|
||||
break;
|
||||
}
|
||||
// Calculate intersections of current audio and video blocks.
|
||||
startI = (startA > startV) ? startA : startV;
|
||||
endI = (endA > endV) ? endV : endA;
|
||||
aBuffered->Add(startI, endI);
|
||||
}
|
||||
}
|
||||
} else if (mAudioReader) {
|
||||
*aBuffered = audioBuffered;
|
||||
} else if (mVideoReader) {
|
||||
*aBuffered = videoBuffered;
|
||||
} else {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
VideoData*
|
||||
nsDASHReader::FindStartTime(int64_t& aOutStartTime)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
|
||||
"Should be on state machine or decode thread.");
|
||||
|
||||
// Extract the start times of the bitstreams in order to calculate
|
||||
// the duration.
|
||||
int64_t videoStartTime = INT64_MAX;
|
||||
int64_t audioStartTime = INT64_MAX;
|
||||
VideoData* videoData = nullptr;
|
||||
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
if (HasVideo()) {
|
||||
// Forward to video reader.
|
||||
videoData
|
||||
= mVideoReader->DecodeToFirstData(&nsBuiltinDecoderReader::DecodeVideoFrame,
|
||||
VideoQueue());
|
||||
if (videoData) {
|
||||
videoStartTime = videoData->mTime;
|
||||
}
|
||||
}
|
||||
if (HasAudio()) {
|
||||
// Forward to audio reader.
|
||||
AudioData* audioData
|
||||
= mAudioReader->DecodeToFirstData(&nsBuiltinDecoderReader::DecodeAudioData,
|
||||
AudioQueue());
|
||||
if (audioData) {
|
||||
audioStartTime = audioData->mTime;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t startTime = NS_MIN(videoStartTime, audioStartTime);
|
||||
if (startTime != INT64_MAX) {
|
||||
aOutStartTime = startTime;
|
||||
}
|
||||
|
||||
return videoData;
|
||||
}
|
||||
|
||||
MediaQueue<AudioData>&
|
||||
nsDASHReader::AudioQueue()
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
NS_ASSERTION(mAudioReader, "mAudioReader is NULL!");
|
||||
return mAudioReader->AudioQueue();
|
||||
}
|
||||
|
||||
MediaQueue<VideoData>&
|
||||
nsDASHReader::VideoQueue()
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
NS_ASSERTION(mVideoReader, "mVideoReader is NULL!");
|
||||
return mVideoReader->VideoQueue();
|
||||
}
|
||||
|
||||
bool
|
||||
nsDASHReader::IsSeekableInBufferedRanges()
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
// At least one subreader must exist, and all subreaders must return true.
|
||||
return (mVideoReader || mAudioReader) &&
|
||||
!((mVideoReader && !mVideoReader->IsSeekableInBufferedRanges()) ||
|
||||
(mAudioReader && !mAudioReader->IsSeekableInBufferedRanges()));
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* DASH - Dynamic Adaptive Streaming over HTTP
|
||||
*
|
||||
* DASH is an adaptive bitrate streaming technology where a multimedia file is
|
||||
* partitioned into one or more segments and delivered to a client using HTTP.
|
||||
*
|
||||
* see nsDASHDecoder.cpp for comments on DASH object interaction
|
||||
*/
|
||||
|
||||
#if !defined(nsDASHReader_h_)
|
||||
#define nsDASHReader_h_
|
||||
|
||||
#include "nsBuiltinDecoderReader.h"
|
||||
|
||||
class nsDASHReader : public nsBuiltinDecoderReader
|
||||
{
|
||||
public:
|
||||
typedef mozilla::MediaResource MediaResource;
|
||||
|
||||
nsDASHReader(nsBuiltinDecoder* aDecoder) :
|
||||
nsBuiltinDecoderReader(aDecoder),
|
||||
mReadMetadataMonitor("media.dashreader.readmetadata"),
|
||||
mReadyToReadMetadata(false),
|
||||
mDecoderIsShuttingDown(false),
|
||||
mAudioReader(this),
|
||||
mVideoReader(this),
|
||||
mAudioReaders(this),
|
||||
mVideoReaders(this)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsDASHReader);
|
||||
}
|
||||
~nsDASHReader()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsDASHReader);
|
||||
}
|
||||
|
||||
// Adds a pointer to a audio/video reader for a media |Representation|.
|
||||
// Called on the main thread only.
|
||||
void AddAudioReader(nsBuiltinDecoderReader* aAudioReader);
|
||||
void AddVideoReader(nsBuiltinDecoderReader* aVideoReader);
|
||||
|
||||
// Waits for metadata bytes to be downloaded, then reads and parses them.
|
||||
// Called on the decode thread only.
|
||||
nsresult ReadMetadata(nsVideoInfo* aInfo,
|
||||
nsHTMLMediaElement::MetadataTags** aTags);
|
||||
|
||||
// Waits for |ReadyToReadMetadata| or |NotifyDecoderShuttingDown|
|
||||
// notification, whichever comes first. Ensures no attempt to read metadata
|
||||
// during |nsDASHDecoder|::|Shutdown|. Called on decode thread only.
|
||||
nsresult WaitForMetadata() {
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
ReentrantMonitorAutoEnter mon(mReadMetadataMonitor);
|
||||
while (true) {
|
||||
// Abort if the decoder has started shutting down.
|
||||
if (mDecoderIsShuttingDown) {
|
||||
return NS_ERROR_ABORT;
|
||||
} else if (mReadyToReadMetadata) {
|
||||
break;
|
||||
}
|
||||
mon.Wait();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Called on the main thread by |nsDASHDecoder| to notify that metadata bytes
|
||||
// have been downloaded.
|
||||
void ReadyToReadMetadata() {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
ReentrantMonitorAutoEnter mon(mReadMetadataMonitor);
|
||||
mReadyToReadMetadata = true;
|
||||
mon.NotifyAll();
|
||||
}
|
||||
|
||||
// Called on the main thread by |nsDASHDecoder| when it starts Shutdown. Will
|
||||
// wake metadata monitor if waiting for a silent return from |ReadMetadata|.
|
||||
void NotifyDecoderShuttingDown() {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
ReentrantMonitorAutoEnter metadataMon(mReadMetadataMonitor);
|
||||
mDecoderIsShuttingDown = true;
|
||||
// Notify |ReadMetadata| of the shutdown if it's waiting.
|
||||
metadataMon.NotifyAll();
|
||||
}
|
||||
|
||||
// Audio/video status are dependent on the presence of audio/video readers.
|
||||
// Call on decode thread only.
|
||||
bool HasAudio() {
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
return mAudioReader ? mAudioReader->HasAudio() : false;
|
||||
}
|
||||
bool HasVideo() {
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
return mVideoReader ? mVideoReader->HasVideo() : false;
|
||||
}
|
||||
|
||||
// Returns references to the audio/video queues of sub-readers. Called on
|
||||
// decode, state machine and audio threads.
|
||||
MediaQueue<AudioData>& AudioQueue();
|
||||
MediaQueue<VideoData>& VideoQueue();
|
||||
|
||||
// Called from nsBuiltinDecoderStateMachine on the main thread.
|
||||
nsresult Init(nsBuiltinDecoderReader* aCloneDonor);
|
||||
|
||||
// Used by |MediaMemoryReporter|.
|
||||
int64_t VideoQueueMemoryInUse();
|
||||
int64_t AudioQueueMemoryInUse();
|
||||
|
||||
// Called on the decode thread.
|
||||
bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold);
|
||||
bool DecodeAudioData();
|
||||
|
||||
// Converts seek time to byte offset. Called on the decode thread only.
|
||||
nsresult Seek(int64_t aTime,
|
||||
int64_t aStartTime,
|
||||
int64_t aEndTime,
|
||||
int64_t aCurrentTime);
|
||||
|
||||
// Called by state machine on multiple threads.
|
||||
nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime);
|
||||
|
||||
// Called on the state machine or decode threads.
|
||||
VideoData* FindStartTime(int64_t& aOutStartTime);
|
||||
|
||||
// Call by state machine on multiple threads.
|
||||
bool IsSeekableInBufferedRanges();
|
||||
|
||||
private:
|
||||
// Similar to |ReentrantMonitorAutoEnter|, this class enters the supplied
|
||||
// monitor in its constructor, but only if the conditional value |aEnter| is
|
||||
// true. Used here to allow read access on the sub-readers' owning thread,
|
||||
// i.e. the decode thread, while locking write accesses from all threads,
|
||||
// and read accesses from non-decode threads.
|
||||
class ReentrantMonitorConditionallyEnter
|
||||
{
|
||||
public:
|
||||
ReentrantMonitorConditionallyEnter(bool aEnter,
|
||||
ReentrantMonitor &aReentrantMonitor) :
|
||||
mReentrantMonitor(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsDASHReader::ReentrantMonitorConditionallyEnter);
|
||||
if (aEnter) {
|
||||
mReentrantMonitor = &aReentrantMonitor;
|
||||
NS_ASSERTION(mReentrantMonitor, "null monitor");
|
||||
mReentrantMonitor->Enter();
|
||||
}
|
||||
}
|
||||
~ReentrantMonitorConditionallyEnter(void)
|
||||
{
|
||||
if (mReentrantMonitor) {
|
||||
mReentrantMonitor->Exit();
|
||||
}
|
||||
MOZ_COUNT_DTOR(nsDASHReader::ReentrantMonitorConditionallyEnter);
|
||||
}
|
||||
private:
|
||||
// Restrict to constructor and destructor defined above.
|
||||
ReentrantMonitorConditionallyEnter();
|
||||
ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
|
||||
ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
|
||||
static void* operator new(size_t) CPP_THROW_NEW;
|
||||
static void operator delete(void*);
|
||||
|
||||
// Ptr to the |ReentrantMonitor| object. Null if |aEnter| in constructor
|
||||
// was false.
|
||||
ReentrantMonitor* mReentrantMonitor;
|
||||
};
|
||||
|
||||
// Monitor and booleans used to wait for metadata bytes to be downloaded, and
|
||||
// skip reading metadata if |nsDASHDecoder|'s shutdown is in progress.
|
||||
ReentrantMonitor mReadMetadataMonitor;
|
||||
bool mReadyToReadMetadata;
|
||||
bool mDecoderIsShuttingDown;
|
||||
|
||||
// Wrapper class protecting accesses to sub-readers. Asserts that the
|
||||
// decoder monitor has been entered for write access on all threads and read
|
||||
// access on all threads that are not the decode thread. Read access on the
|
||||
// decode thread does not need to be protected.
|
||||
class MonitoredSubReader
|
||||
{
|
||||
public:
|
||||
// Main constructor takes a pointer to the owning |nsDASHReader| to verify
|
||||
// correct entry into the decoder's |ReentrantMonitor|.
|
||||
MonitoredSubReader(nsDASHReader* aReader) :
|
||||
mReader(aReader),
|
||||
mSubReader(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsDASHReader::MonitoredSubReader);
|
||||
NS_ASSERTION(mReader, "Reader is null!");
|
||||
}
|
||||
// Note: |mSubReader|'s refcount will be decremented in this destructor.
|
||||
~MonitoredSubReader()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsDASHReader::MonitoredSubReader);
|
||||
}
|
||||
|
||||
// Override '=' to always assert thread is "in monitor" for writes/changes
|
||||
// to |mSubReader|.
|
||||
MonitoredSubReader& operator=(nsBuiltinDecoderReader* rhs)
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
mSubReader = rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Override '*' to assert threads other than the decode thread are "in
|
||||
// monitor" for ptr reads.
|
||||
operator nsBuiltinDecoderReader*() const
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
if (!mReader->GetDecoder()->OnDecodeThread()) {
|
||||
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
}
|
||||
return mSubReader;
|
||||
}
|
||||
|
||||
// Override '->' to assert threads other than the decode thread are "in
|
||||
// monitor" for |mSubReader| function calls.
|
||||
nsBuiltinDecoderReader* operator->() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
private:
|
||||
// Pointer to |nsDASHReader| object which owns this |MonitoredSubReader|.
|
||||
nsDASHReader* mReader;
|
||||
// Ref ptr to the sub reader.
|
||||
nsRefPtr<nsBuiltinDecoderReader> mSubReader;
|
||||
};
|
||||
|
||||
// Wrapped ref ptrs to current sub-readers of individual media
|
||||
// |Representation|s. Decoder monitor must be entered for write access on all
|
||||
// threads and read access on all threads that are not the decode thread.
|
||||
// Read access on the decode thread does not need to be protected.
|
||||
// Note: |MonitoredSubReader| class will assert correct monitor use.
|
||||
MonitoredSubReader mAudioReader;
|
||||
MonitoredSubReader mVideoReader;
|
||||
|
||||
// Wrapper class protecting accesses to sub-reader list. Asserts that the
|
||||
// decoder monitor has been entered for write access on all threads and read
|
||||
// access on all threads that are not the decode thread. Read access on the
|
||||
// decode thread does not need to be protected.
|
||||
// Note: Elems accessed via operator[] are not protected with monitor
|
||||
// assertion checks once obtained.
|
||||
class MonitoredSubReaderList
|
||||
{
|
||||
public:
|
||||
// Main constructor takes a pointer to the owning |nsDASHReader| to verify
|
||||
// correct entry into the decoder's |ReentrantMonitor|.
|
||||
MonitoredSubReaderList(nsDASHReader* aReader) :
|
||||
mReader(aReader)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsDASHReader::MonitoredSubReaderList);
|
||||
NS_ASSERTION(mReader, "Reader is null!");
|
||||
}
|
||||
// Note: Elements in |mSubReaderList| will have their refcounts decremented
|
||||
// in this destructor.
|
||||
~MonitoredSubReaderList()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsDASHReader::MonitoredSubReaderList);
|
||||
}
|
||||
|
||||
// Returns Length of |mSubReaderList| array. Will assert threads other than
|
||||
// the decode thread are "in monitor".
|
||||
uint32_t Length() const
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
if (!mReader->GetDecoder()->OnDecodeThread()) {
|
||||
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
}
|
||||
return mSubReaderList.Length();
|
||||
}
|
||||
|
||||
// Override '[]' to assert threads other than the decode thread are "in
|
||||
// monitor" for accessing individual elems. Note: elems returned do not
|
||||
// have monitor assertions builtin like |MonitoredSubReader| objects.
|
||||
nsRefPtr<nsBuiltinDecoderReader>& operator[](uint32_t i)
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
if (!mReader->GetDecoder()->OnDecodeThread()) {
|
||||
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
}
|
||||
return mSubReaderList[i];
|
||||
}
|
||||
|
||||
// Appends a reader to the end of |mSubReaderList|. Will always assert that
|
||||
// the thread is "in monitor".
|
||||
void
|
||||
AppendElement(nsBuiltinDecoderReader* aReader)
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
mSubReaderList.AppendElement(aReader);
|
||||
}
|
||||
private:
|
||||
// Pointer to |nsDASHReader| object which owns this |MonitoredSubReader|.
|
||||
nsDASHReader* mReader;
|
||||
// Ref ptrs to the sub readers.
|
||||
nsTArray<nsRefPtr<nsBuiltinDecoderReader> > mSubReaderList;
|
||||
};
|
||||
|
||||
// Ref ptrs to all sub-readers of individual media |Representation|s.
|
||||
// Decoder monitor must be entered for write access on all threads and read
|
||||
// access on all threads that are not the decode thread. Read acces on the
|
||||
// decode thread does not need to be protected.
|
||||
MonitoredSubReaderList mAudioReaders;
|
||||
MonitoredSubReaderList mVideoReaders;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,389 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* DASH - Dynamic Adaptive Streaming over HTTP
|
||||
*
|
||||
* DASH is an adaptive bitrate streaming technology where a multimedia file is
|
||||
* partitioned into one or more segments and delivered to a client using HTTP.
|
||||
*
|
||||
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
|
||||
|
||||
#include "prlog.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "SegmentBase.h"
|
||||
#include "nsBuiltinDecoderStateMachine.h"
|
||||
#include "nsDASHReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "nsDASHRepDecoder.h"
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gBuiltinDecoderLog;
|
||||
#define LOG(msg, ...) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
|
||||
("%p [nsDASHRepDecoder] " msg, this, __VA_ARGS__))
|
||||
#define LOG1(msg) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
|
||||
("%p [nsDASHRepDecoder] " msg, this))
|
||||
#else
|
||||
#define LOG(msg, ...)
|
||||
#define LOG1(msg)
|
||||
#endif
|
||||
|
||||
nsDecoderStateMachine*
|
||||
nsDASHRepDecoder::CreateStateMachine()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
// Do not create; just return current state machine.
|
||||
return mDecoderStateMachine;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHRepDecoder::SetStateMachine(nsDecoderStateMachine* aSM)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
mDecoderStateMachine = aSM;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::SetResource(MediaResource* aResource)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
mResource = aResource;
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::SetMPDRepresentation(Representation const * aRep)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
mMPDRepresentation = aRep;
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::SetReader(nsWebMReader* aReader)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
mReader = aReader;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHRepDecoder::Load(MediaResource* aResource,
|
||||
nsIStreamListener** aListener,
|
||||
nsMediaDecoder* aCloneDonor)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(mMPDRepresentation, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
// Get init range and index range from MPD.
|
||||
SegmentBase const * segmentBase = mMPDRepresentation->GetSegmentBase();
|
||||
NS_ENSURE_TRUE(segmentBase, NS_ERROR_NULL_POINTER);
|
||||
|
||||
// Get and set init range.
|
||||
segmentBase->GetInitRange(&mInitByteRange.mStart, &mInitByteRange.mEnd);
|
||||
NS_ENSURE_TRUE(!mInitByteRange.IsNull(), NS_ERROR_NOT_INITIALIZED);
|
||||
mReader->SetInitByteRange(mInitByteRange);
|
||||
|
||||
// Get and set index range.
|
||||
segmentBase->GetIndexRange(&mIndexByteRange.mStart, &mIndexByteRange.mEnd);
|
||||
NS_ENSURE_TRUE(!mIndexByteRange.IsNull(), NS_ERROR_NOT_INITIALIZED);
|
||||
mReader->SetIndexByteRange(mIndexByteRange);
|
||||
|
||||
// Determine byte range to Open.
|
||||
// For small deltas between init and index ranges, we need to bundle the byte
|
||||
// range requests together in order to deal with |nsMediaCache|'s control of
|
||||
// seeking (see |nsMediaCache|::|Update|). |nsMediaCache| will not initiate a
|
||||
// |ChannelMediaResource|::|CacheClientSeek| for the INDEX byte range if the
|
||||
// delta between it and the INIT byte ranges is less than
|
||||
// |SEEK_VS_READ_THRESHOLD|. To get around this, request all metadata bytes
|
||||
// now so |nsMediaCache| can assume the bytes are en route.
|
||||
int64_t delta = NS_MAX(mIndexByteRange.mStart, mInitByteRange.mStart)
|
||||
- NS_MIN(mIndexByteRange.mEnd, mInitByteRange.mEnd);
|
||||
MediaByteRange byteRange;
|
||||
if (delta <= SEEK_VS_READ_THRESHOLD) {
|
||||
byteRange.mStart = NS_MIN(mIndexByteRange.mStart, mInitByteRange.mStart);
|
||||
byteRange.mEnd = NS_MAX(mIndexByteRange.mEnd, mInitByteRange.mEnd);
|
||||
// Loading everything in one chunk .
|
||||
mMetadataChunkCount = 1;
|
||||
} else {
|
||||
byteRange = mInitByteRange;
|
||||
// Loading in two chunks: init and index.
|
||||
mMetadataChunkCount = 2;
|
||||
}
|
||||
mCurrentByteRange = byteRange;
|
||||
return mResource->OpenByteRange(nullptr, byteRange);
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::NotifyDownloadEnded(nsresult aStatus)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
if (!mMainDecoder) {
|
||||
LOG("Error! Main Decoder is reported as null: mMainDecoder [%p]",
|
||||
mMainDecoder.get());
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
// Decrement counter as metadata chunks are downloaded.
|
||||
// Note: Reader gets next chunk download via |ChannelMediaResource|:|Seek|.
|
||||
if (mMetadataChunkCount > 0) {
|
||||
LOG("Metadata chunk [%d] downloaded: range requested [%d - %d]",
|
||||
mMetadataChunkCount,
|
||||
mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
|
||||
mMetadataChunkCount--;
|
||||
} else {
|
||||
// Notify main decoder that a DATA byte range is downloaded.
|
||||
LOG("Byte range downloaded: status [%x] range requested [%d - %d]",
|
||||
aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
|
||||
mMainDecoder->NotifyDownloadEnded(this, aStatus,
|
||||
mCurrentByteRange);
|
||||
}
|
||||
} else if (aStatus == NS_BINDING_ABORTED) {
|
||||
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
|
||||
if (mMainDecoder) {
|
||||
mMainDecoder->LoadAborted();
|
||||
}
|
||||
return;
|
||||
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
|
||||
LOG("Network error trying to download MPD: aStatus [%x].", aStatus);
|
||||
NetworkError();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::OnReadMetadataCompleted()
|
||||
{
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
LOG1("Metadata has been read.");
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &nsDASHRepDecoder::LoadNextByteRange);
|
||||
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error dispatching parse event to main thread: rv[%x]", rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::LoadNextByteRange()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (!mResource) {
|
||||
LOG1("Error: resource is reported as null!");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate the array of subsegment byte ranges if it's empty.
|
||||
nsresult rv;
|
||||
if (mByteRanges.IsEmpty()) {
|
||||
if (!mReader) {
|
||||
LOG1("Error: mReader should not be null!");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
rv = mReader->GetIndexByteRanges(mByteRanges);
|
||||
// If empty, just fail.
|
||||
if (NS_FAILED(rv) || mByteRanges.IsEmpty()) {
|
||||
LOG1("Error getting list of subsegment byte ranges.");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get byte range for subsegment.
|
||||
if (mSubsegmentIdx < mByteRanges.Length()) {
|
||||
mCurrentByteRange = mByteRanges[mSubsegmentIdx];
|
||||
} else {
|
||||
mCurrentByteRange.Clear();
|
||||
LOG("End of subsegments: index [%d] out of range.", mSubsegmentIdx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open byte range corresponding to subsegment.
|
||||
rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error opening byte range [%d - %d]: rv [%x].",
|
||||
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, rv);
|
||||
NetworkError();
|
||||
return;
|
||||
}
|
||||
// Increment subsegment index for next load.
|
||||
mSubsegmentIdx++;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDASHRepDecoder::GetByteRangeForSeek(int64_t const aOffset,
|
||||
MediaByteRange& aByteRange)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// Check data ranges, if available.
|
||||
for (int i = 0; i < mByteRanges.Length(); i++) {
|
||||
NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
|
||||
if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
|
||||
mCurrentByteRange = aByteRange = mByteRanges[i];
|
||||
mSubsegmentIdx = i;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
// Check metadata ranges; init range.
|
||||
if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
|
||||
mCurrentByteRange = aByteRange = mInitByteRange;
|
||||
mSubsegmentIdx = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
// ... index range.
|
||||
if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) {
|
||||
mCurrentByteRange = aByteRange = mIndexByteRange;
|
||||
mSubsegmentIdx = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
aByteRange.Clear();
|
||||
if (mByteRanges.IsEmpty()) {
|
||||
// Assume mByteRanges will be populated after metadata is read.
|
||||
LOG("Can't get range for offset [%d].", aOffset);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
} else {
|
||||
// Cannot seek to an unknown offset.
|
||||
// XXX Revisit this for dynamic MPD profiles if MPD is regularly updated.
|
||||
LOG("Error! Offset [%d] is in an unknown range!", aOffset);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::NetworkError()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (mMainDecoder) { mMainDecoder->NetworkError(); }
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::SetDuration(double aDuration)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (mMainDecoder) { mMainDecoder->SetDuration(aDuration); }
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::SetInfinite(bool aInfinite)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (mMainDecoder) { mMainDecoder->SetInfinite(aInfinite); }
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::SetSeekable(bool aSeekable)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (mMainDecoder) { mMainDecoder->SetSeekable(aSeekable); }
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::Progress(bool aTimer)
|
||||
{
|
||||
if (mMainDecoder) { mMainDecoder->Progress(aTimer); }
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::NotifyDataArrived(const char* aBuffer,
|
||||
uint32_t aLength,
|
||||
int64_t aOffset)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
LOG("Data bytes [%d - %d] arrived via buffer [%p].",
|
||||
aOffset, aOffset+aLength, aBuffer);
|
||||
// Notify reader directly, since call to |nsBuiltinDecoderStateMachine|::
|
||||
// |NotifyDataArrived| will go to |nsDASHReader|::|NotifyDataArrived|, which
|
||||
// has no way to forward the notification to the correct sub-reader.
|
||||
if (mReader) {
|
||||
mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
|
||||
}
|
||||
// Forward to main decoder which will notify state machine.
|
||||
if (mMainDecoder) {
|
||||
mMainDecoder->NotifyDataArrived(aBuffer, aLength, aOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::NotifyBytesDownloaded()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (mMainDecoder) { mMainDecoder->NotifyBytesDownloaded(); }
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::NotifySuspendedStatusChanged()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (mMainDecoder) { mMainDecoder->NotifySuspendedStatusChanged(); }
|
||||
}
|
||||
|
||||
bool
|
||||
nsDASHRepDecoder::OnStateMachineThread() const
|
||||
{
|
||||
return (mMainDecoder ? mMainDecoder->OnStateMachineThread() : false);
|
||||
}
|
||||
|
||||
bool
|
||||
nsDASHRepDecoder::OnDecodeThread() const
|
||||
{
|
||||
return (mMainDecoder ? mMainDecoder->OnDecodeThread() : false);
|
||||
}
|
||||
|
||||
ReentrantMonitor&
|
||||
nsDASHRepDecoder::GetReentrantMonitor()
|
||||
{
|
||||
return mMainDecoder->GetReentrantMonitor();
|
||||
}
|
||||
|
||||
nsDecoderStateMachine::State
|
||||
nsDASHRepDecoder::GetDecodeState()
|
||||
{
|
||||
// XXX SHUTDOWN might not be an appropriate error.
|
||||
return (mMainDecoder ? mMainDecoder->GetDecodeState()
|
||||
: nsDecoderStateMachine::DECODER_STATE_SHUTDOWN);
|
||||
}
|
||||
|
||||
mozilla::layers::ImageContainer*
|
||||
nsDASHRepDecoder::GetImageContainer()
|
||||
{
|
||||
NS_ASSERTION(mMainDecoder && mMainDecoder->OnDecodeThread(),
|
||||
"Should be on decode thread.");
|
||||
return (mMainDecoder ? mMainDecoder->GetImageContainer() : nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::DecodeError()
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
nsBuiltinDecoder::DecodeError();
|
||||
} else {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &nsBuiltinDecoder::DecodeError);
|
||||
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error dispatching DecodeError event to main thread: rv[%x]", rv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDASHRepDecoder::ReleaseStateMachine()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
|
||||
|
||||
// Since state machine owns mReader, remove reference to it.
|
||||
mReader = nullptr;
|
||||
|
||||
nsBuiltinDecoder::ReleaseStateMachine();
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* DASH - Dynamic Adaptive Streaming over HTTP
|
||||
*
|
||||
* DASH is an adaptive bitrate streaming technology where a multimedia file is
|
||||
* partitioned into one or more segments and delivered to a client using HTTP.
|
||||
*
|
||||
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
|
||||
|
||||
#if !defined(nsDASHRepDecoder_h_)
|
||||
#define nsDASHRepDecoder_h_
|
||||
|
||||
#include "Representation.h"
|
||||
#include "ImageLayers.h"
|
||||
#include "nsDASHDecoder.h"
|
||||
#include "nsWebMDecoder.h"
|
||||
#include "nsWebMReader.h"
|
||||
#include "nsBuiltinDecoder.h"
|
||||
|
||||
class nsDASHDecoder;
|
||||
|
||||
class nsDASHRepDecoder : public nsBuiltinDecoder
|
||||
{
|
||||
public:
|
||||
typedef mozilla::net::Representation Representation;
|
||||
typedef mozilla::net::SegmentBase SegmentBase;
|
||||
typedef mozilla::layers::ImageContainer ImageContainer;
|
||||
|
||||
// Constructor takes a ptr to the main decoder.
|
||||
nsDASHRepDecoder(nsDASHDecoder* aMainDecoder) :
|
||||
mMainDecoder(aMainDecoder),
|
||||
mMPDRepresentation(nullptr),
|
||||
mMetadataChunkCount(0),
|
||||
mCurrentByteRange(),
|
||||
mSubsegmentIdx(0),
|
||||
mReader(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsDASHRepDecoder);
|
||||
}
|
||||
|
||||
~nsDASHRepDecoder()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsDASHRepDecoder);
|
||||
}
|
||||
|
||||
// Clone not supported; just return nullptr.
|
||||
virtual nsMediaDecoder* Clone() { return nullptr; }
|
||||
|
||||
// Called by the main decoder at creation time; points to the main state
|
||||
// machine managed by the main decoder. Called on the main thread only.
|
||||
nsresult SetStateMachine(nsDecoderStateMachine* aSM);
|
||||
|
||||
private:
|
||||
// Overridden to return the ptr set by SetStateMachine. Called on the main
|
||||
// thread only.
|
||||
nsDecoderStateMachine* CreateStateMachine();
|
||||
|
||||
public:
|
||||
// Called by nsDASHDecoder at creation time; points to the media resource
|
||||
// for this decoder's |Representation|. Called on the main thread only.
|
||||
void SetResource(MediaResource* aResource);
|
||||
|
||||
// Sets the |Representation| object for this decoder. Called on the main
|
||||
// thread.
|
||||
void SetMPDRepresentation(Representation const * aRep);
|
||||
|
||||
// Called from nsDASHDecoder on main thread; Starts media stream download.
|
||||
nsresult Load(MediaResource* aResource = nullptr,
|
||||
nsIStreamListener** aListener = nullptr,
|
||||
nsMediaDecoder* aCloneDonor = nullptr);
|
||||
|
||||
// Loads the next byte range (or first one on first call). Called on the main
|
||||
// thread only.
|
||||
void LoadNextByteRange();
|
||||
|
||||
// Calls from nsDASHRepDecoder. Called on the main thread only.
|
||||
void SetReader(nsWebMReader* aReader);
|
||||
|
||||
// Called if the media file encounters a network error. Call on the main
|
||||
// thread only.
|
||||
void NetworkError();
|
||||
|
||||
// Set the duration of the media resource in units of seconds.
|
||||
// This is called via a channel listener if it can pick up the duration
|
||||
// from a content header. Must be called from the main thread only.
|
||||
virtual void SetDuration(double aDuration);
|
||||
|
||||
// Set media stream as infinite. Called on the main thread only.
|
||||
void SetInfinite(bool aInfinite);
|
||||
|
||||
// Sets media stream as seekable. Called on main thread only.
|
||||
void SetSeekable(bool aSeekable);
|
||||
|
||||
// Fire progress events if needed according to the time and byte
|
||||
// constraints outlined in the specification. aTimer is true
|
||||
// if the method is called as a result of the progress timer rather
|
||||
// than the result of downloaded data.
|
||||
void Progress(bool aTimer);
|
||||
|
||||
// Called as data arrives on the stream and is read into the cache. Called
|
||||
// on the main thread only.
|
||||
void NotifyDataArrived(const char* aBuffer,
|
||||
uint32_t aLength,
|
||||
int64_t aOffset);
|
||||
|
||||
// Called by MediaResource when some data has been received.
|
||||
// Call on the main thread only.
|
||||
void NotifyBytesDownloaded();
|
||||
|
||||
// Notify that a byte range request has been completed by the media resource.
|
||||
// Called on the main thread only.
|
||||
void NotifyDownloadEnded(nsresult aStatus);
|
||||
|
||||
// Called by MediaResource when the "cache suspended" status changes.
|
||||
// If MediaResource::IsSuspendedByCache returns true, then the decoder
|
||||
// should stop buffering or otherwise waiting for download progress and
|
||||
// start consuming data, if possible, because the cache is full.
|
||||
void NotifySuspendedStatusChanged();
|
||||
|
||||
// Gets a byte range containing the byte offset. Call on main thread only.
|
||||
nsresult GetByteRangeForSeek(int64_t const aOffset,
|
||||
MediaByteRange& aByteRange);
|
||||
|
||||
// Returns true if the current thread is the state machine thread.
|
||||
bool OnStateMachineThread() const;
|
||||
|
||||
// Returns true if the current thread is the decode thread.
|
||||
bool OnDecodeThread() const;
|
||||
|
||||
// Returns main decoder's monitor for synchronised access.
|
||||
ReentrantMonitor& GetReentrantMonitor();
|
||||
|
||||
// Return the current decode state, according to the main decoder. The
|
||||
// decoder monitor must be obtained before calling this.
|
||||
nsDecoderStateMachine::State GetDecodeState();
|
||||
|
||||
// Called on the decode thread from nsWebMReader.
|
||||
ImageContainer* GetImageContainer();
|
||||
|
||||
// Called when Metadata has been read; notifies that index data is read.
|
||||
// Called on the decode thread only.
|
||||
void OnReadMetadataCompleted();
|
||||
|
||||
// Overridden to cleanup ref to |nsDASHDecoder|. Called on main thread only.
|
||||
void Shutdown() {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
// Call parent class shutdown.
|
||||
nsBuiltinDecoder::Shutdown();
|
||||
NS_ENSURE_TRUE(mShuttingDown, );
|
||||
// Cleanup ref to main decoder.
|
||||
mMainDecoder = nullptr;
|
||||
}
|
||||
|
||||
// Drop reference to state machine and mReader (owned by state machine).
|
||||
// Only called during shutdown dance.
|
||||
void ReleaseStateMachine();
|
||||
|
||||
// Notifies the element that decoding has failed.
|
||||
void DecodeError();
|
||||
|
||||
private:
|
||||
// The main decoder.
|
||||
nsRefPtr<nsDASHDecoder> mMainDecoder;
|
||||
// This decoder's MPD |Representation| object.
|
||||
Representation const * mMPDRepresentation;
|
||||
|
||||
// Countdown var for loading metadata byte ranges.
|
||||
uint16_t mMetadataChunkCount;
|
||||
|
||||
// All the byte ranges for this |Representation|.
|
||||
nsTArray<MediaByteRange> mByteRanges;
|
||||
|
||||
// Byte range for the init and index bytes.
|
||||
MediaByteRange mInitByteRange;
|
||||
MediaByteRange mIndexByteRange;
|
||||
|
||||
// The current byte range being requested.
|
||||
MediaByteRange mCurrentByteRange;
|
||||
// Index of the current byte range.
|
||||
uint64_t mSubsegmentIdx;
|
||||
|
||||
// Ptr to the reader object for this |Representation|. Owned by state
|
||||
// machine.
|
||||
nsBuiltinDecoderReader* mReader;
|
||||
};
|
||||
|
||||
#endif //nsDASHRepDecoder_h_
|
|
@ -711,7 +711,7 @@ public:
|
|||
virtual void NotifyAudioAvailableListener();
|
||||
|
||||
// Notifies the element that decoding has failed.
|
||||
void DecodeError();
|
||||
virtual void DecodeError();
|
||||
|
||||
// Schedules the state machine to run one cycle on the shared state
|
||||
// machine thread. Main thread only.
|
||||
|
|
|
@ -376,24 +376,6 @@ VideoData* nsBuiltinDecoderReader::FindStartTime(int64_t& aOutStartTime)
|
|||
return videoData;
|
||||
}
|
||||
|
||||
/*template<class Data>
|
||||
Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn,
|
||||
MediaQueue<Data>& aQueue)
|
||||
{
|
||||
bool eof = false;
|
||||
while (!eof && aQueue.GetSize() == 0) {
|
||||
{
|
||||
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
|
||||
if (mDecoder->GetDecodeState() == nsDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
eof = !(this->*aDecodeFn)();
|
||||
}
|
||||
Data* d = nullptr;
|
||||
return (d = aQueue.PeekFront()) ? d : nullptr;
|
||||
}*/
|
||||
|
||||
nsresult nsBuiltinDecoderReader::DecodeToTarget(int64_t aTarget)
|
||||
{
|
||||
// Decode forward to the target frame. Start with video, if we have it.
|
||||
|
|
|
@ -530,7 +530,7 @@ public:
|
|||
virtual void SetIndexByteRange(MediaByteRange &aByteRange) { }
|
||||
|
||||
// Returns list of ranges for index frame start/end offsets. Used by DASH.
|
||||
nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges) {
|
||||
virtual nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ class nsITimer;
|
|||
|
||||
namespace mozilla {
|
||||
class MediaResource;
|
||||
class MediaByteRange;
|
||||
}
|
||||
|
||||
// The size to use for audio data frames in MozAudioAvailable events.
|
||||
|
@ -39,6 +40,7 @@ class nsMediaDecoder : public nsIObserver
|
|||
{
|
||||
public:
|
||||
typedef mozilla::MediaResource MediaResource;
|
||||
typedef mozilla::MediaByteRange MediaByteRange;
|
||||
typedef mozilla::ReentrantMonitor ReentrantMonitor;
|
||||
typedef mozilla::SourceMediaStream SourceMediaStream;
|
||||
typedef mozilla::ProcessedMediaStream ProcessedMediaStream;
|
||||
|
@ -73,6 +75,14 @@ public:
|
|||
// Seek to the time position in (seconds) from the start of the video.
|
||||
virtual nsresult Seek(double aTime) = 0;
|
||||
|
||||
// Enables decoders to supply an enclosing byte range for a seek offset.
|
||||
// E.g. used by ChannelMediaResource to download a whole cluster for
|
||||
// DASH-WebM.
|
||||
virtual nsresult GetByteRangeForSeek(int64_t const aOffset,
|
||||
MediaByteRange &aByteRange) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
// Called by the element when the playback rate has been changed.
|
||||
// Adjust the speed of the playback, optionally with pitch correction,
|
||||
// when this is called.
|
||||
|
|
|
@ -32,3 +32,9 @@ include $(topsrcdir)/config/rules.mk
|
|||
LOCAL_INCLUDES = \
|
||||
$(MOZ_LIBVPX_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
ifdef MOZ_DASH
|
||||
LOCAL_INCLUDES += \
|
||||
-I$(srcdir)/../dash \
|
||||
$(NULL)
|
||||
endif
|
||||
|
|
|
@ -438,7 +438,8 @@ WebappsApplication.prototype = {
|
|||
},
|
||||
|
||||
download: function() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
cpmm.sendAsyncMessage("Webapps:Download",
|
||||
{ manifestURL: this.manifestURL });
|
||||
},
|
||||
|
||||
cancelDownload: function() {
|
||||
|
@ -538,6 +539,9 @@ WebappsApplication.prototype = {
|
|||
if (msg.event == "downloadapplied") {
|
||||
Services.DOMRequest.fireSuccess(req, this.manifestURL);
|
||||
this._fireEvent("downloadapplied", this._ondownloadapplied);
|
||||
} else if (msg.event == "downloadavailable") {
|
||||
Services.DOMRequest.fireSuccess(req, this.manifestURL);
|
||||
this._fireEvent("downloadavailable", this._ondownloadavailable);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -579,6 +583,18 @@ WebappsApplication.prototype = {
|
|||
this._downloadError = msg.error;
|
||||
this._fireEvent("downloaderror", this._ondownloaderror);
|
||||
break;
|
||||
case "downloaded":
|
||||
app = msg.app;
|
||||
this.downloading = app.downloading;
|
||||
this.downloadavailable = app.downloadavailable;
|
||||
this.readyToApplyDownload = app.readyToApplyDownload;
|
||||
this._fireEvent("downloaded", this._ondownloaded);
|
||||
break;
|
||||
case "applied":
|
||||
app = msg.app;
|
||||
this.readyToApplyDownload = app.readyToApplyDownload;
|
||||
this._fireEvent("downloadapplied", this._ondownloadapplied);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -638,7 +654,12 @@ WebappsApplicationMgmt.prototype = {
|
|||
},
|
||||
|
||||
applyDownload: function(aApp) {
|
||||
return Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
if (!aApp.readyToApplyDownload) {
|
||||
return;
|
||||
}
|
||||
|
||||
cpmm.sendAsyncMessage("Webapps::ApplyDownload",
|
||||
{ manifestURL: aApp.manifestURL });
|
||||
},
|
||||
|
||||
getAll: function() {
|
||||
|
|
|
@ -160,7 +160,8 @@ let DOMApplicationRegistry = {
|
|||
"Webapps:InstallPackage", "Webapps:GetBasePath",
|
||||
"Webapps:GetList", "Webapps:RegisterForMessages",
|
||||
"Webapps:UnregisterForMessages",
|
||||
"Webapps:CancelDownload", "Webapps:CheckForUpdate"];
|
||||
"Webapps:CancelDownload", "Webapps:CheckForUpdate",
|
||||
"Webapps::Download", "Webapps::ApplyDownload"];
|
||||
|
||||
this.frameMessages = ["Webapps:ClearBrowserData"];
|
||||
|
||||
|
@ -529,6 +530,18 @@ let DOMApplicationRegistry = {
|
|||
// the pref instead of first checking if it is false.
|
||||
Services.prefs.setBoolPref("dom.mozApps.used", true);
|
||||
|
||||
// We need to check permissions for calls coming from mozApps.mgmt.
|
||||
// These are: getAll(), getNotInstalled() and applyDownload()
|
||||
if (["Webapps:GetAll",
|
||||
"Webapps:GetNotInstalled",
|
||||
"Webapps::ApplyDownload"].indexOf(aMessage.name) != -1) {
|
||||
if (!aMessage.target.assertPermission("webapps-manage")) {
|
||||
debug("mozApps message " + aMessage.name +
|
||||
" from a content process with no 'webapps-manage' privileges.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let msg = aMessage.json;
|
||||
let mm = aMessage.target;
|
||||
msg.mm = mm;
|
||||
|
@ -578,12 +591,18 @@ let DOMApplicationRegistry = {
|
|||
case "Webapps:GetList":
|
||||
this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], mm);
|
||||
return this.webapps;
|
||||
case "Webapps:Download":
|
||||
this.startDownload(msg.manifestURL);
|
||||
break;
|
||||
case "Webapps:CancelDownload":
|
||||
this.cancelDowload(msg.manifestURL);
|
||||
this.cancelDownload(msg.manifestURL);
|
||||
break;
|
||||
case "Webapps:CheckForUpdate":
|
||||
this.checkForUpdate(msg, mm);
|
||||
break;
|
||||
case "Webapps::ApplyDownload":
|
||||
this.ApplyDownload(msg.manifestURL);
|
||||
break;
|
||||
case "Activities:Register:OK":
|
||||
this.activitiesRegistered++;
|
||||
if (this.allActivitiesSent &&
|
||||
|
@ -658,20 +677,20 @@ let DOMApplicationRegistry = {
|
|||
Services.obs.notifyObservers(aMm, "webapps-launch", JSON.stringify(aData));
|
||||
},
|
||||
|
||||
cancelDownload: function cancelDowload(aManifestURL) {
|
||||
// We can't cancel appcache dowloads for now.
|
||||
cancelDownload: function cancelDownload(aManifestURL) {
|
||||
// We can't cancel appcache downloads for now.
|
||||
if (!this.downloads[aManifestURL]) {
|
||||
return;
|
||||
}
|
||||
// This is a HTTP channel.
|
||||
let download = this.downloads[aManifestURL]
|
||||
download.channel.cancel(Cr.NS_BINDING_ABORTED);
|
||||
let app = this.webapps[dowload.appId];
|
||||
let app = this.webapps[download.appId];
|
||||
|
||||
app.progress = 0;
|
||||
app.installState = app.previousState;
|
||||
app.dowloading = false;
|
||||
app.dowloadavailable = false;
|
||||
app.downloading = false;
|
||||
app.downloadavailable = false;
|
||||
app.downloadSize = 0;
|
||||
this._saveApps((function() {
|
||||
this.broadcastMessage("Webapps:PackageEvent",
|
||||
|
@ -682,6 +701,87 @@ let DOMApplicationRegistry = {
|
|||
}).bind(this));
|
||||
},
|
||||
|
||||
startDownload: function cancelDownload(aManifestURL) {
|
||||
let app = this.getAppByManifestURL(manifestURL);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._appIdForManifestURL(manifestURL);
|
||||
|
||||
// We need to get the update manifest here, not the webapp manifest.
|
||||
let file = FileUtils.getFile(DIRECTORY_NAME,
|
||||
["webapps", id, "update.webapp"], true);
|
||||
|
||||
this._loadJSONAsync(file, (function(aJSON) {
|
||||
if (!aJSON) {
|
||||
return;
|
||||
}
|
||||
|
||||
let manifest = new DOMApplicationManifest(aJSON, app.origin);
|
||||
this.downloadPackage(manifest, { manifestURL: aManifestURL,
|
||||
origin: app.origin }, true,
|
||||
function(aId, aManifest) {
|
||||
// Success! Keep the zip in of TmpD, we'll move it out when
|
||||
// applyDownload() will be called.
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
|
||||
|
||||
// Save the manifest in TmpD also
|
||||
let manFile = tmpDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
DOMApplicationRegistry._writeFile(manFile,
|
||||
JSON.stringify(aManifest),
|
||||
function() { });
|
||||
// Set state and fire events.
|
||||
app.downloading = false;
|
||||
app.downloadavailable = false;
|
||||
app.readyToApplyDownload = true;
|
||||
DOMApplicationRegistry._saveApps(function() {
|
||||
debug("About to fire Webapps:PackageEvent");
|
||||
DOMApplicationRegistry.broadcastMessage("Webapps:PackageEvent",
|
||||
{ type: "downloaded",
|
||||
manifestURL: manifestURL,
|
||||
app: app,
|
||||
manifest: aManifest });
|
||||
});
|
||||
});
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
applyDownload: function applyDownload(aManifestURL) {
|
||||
let app = this.getAppByManifestURL(manifestURL);
|
||||
if (!app || (app && !app.readyToApplyDownload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._appIdForManifestURL(aApp.manifestURL);
|
||||
|
||||
// Move the application.zip and manifest.webapp files out of TmpD
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
|
||||
let manFile = tmpDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
let appFile = tmpDir.clone();
|
||||
appFile.append("application.zip");
|
||||
|
||||
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
|
||||
appFile.moveTo(dir, "application.zip");
|
||||
manFile.moveTo(dir, "manifest.webapp");
|
||||
|
||||
try {
|
||||
tmpDir.remove(true);
|
||||
} catch(e) { }
|
||||
|
||||
// Get the manifest, and set properties.
|
||||
this.getManifestFor(app.origin, function(aData) {
|
||||
app.readyToApplyDownload = false;
|
||||
this.broadcastMessage("Webapps:PackageEvent",
|
||||
{ type: "applied",
|
||||
manifestURL: aApp.manifestURL,
|
||||
app: app,
|
||||
manifest: aData });
|
||||
});
|
||||
},
|
||||
|
||||
startOfflineCacheDownload: function startOfflineCacheDownload(aManifest, aApp, aProfileDir) {
|
||||
// if the manifest has an appcache_path property, use it to populate the appcache
|
||||
if (aManifest.appcache_path) {
|
||||
|
@ -814,6 +914,19 @@ let DOMApplicationRegistry = {
|
|||
|
||||
function updatePackagedApp(aManifest) {
|
||||
debug("updatePackagedApp");
|
||||
let manifest = new DOMApplicationManifest(aManifest, app.manifestURL);
|
||||
// A package is available: set downloadAvailable to fire the matching
|
||||
// event.
|
||||
app.downloadAvailable = true;
|
||||
app.downloadSize = manifest.size;
|
||||
aData.event = "downloadavailable";
|
||||
aData.app = {
|
||||
downloadAvailable: true,
|
||||
downloadSize: manifest.size
|
||||
}
|
||||
DOMApplicationRegistry._saveApps(function() {
|
||||
aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:OK", aData);
|
||||
});
|
||||
}
|
||||
|
||||
function updateHostedApp(aManifest) {
|
||||
|
@ -885,7 +998,7 @@ let DOMApplicationRegistry = {
|
|||
} else {
|
||||
app.etag = xhr.getResponseHeader("Etag");
|
||||
app.lastCheckedUpdate = Date.now();
|
||||
if (package_path in manifest) {
|
||||
if (manifest.package_path) {
|
||||
updatePackagedApp(manifest);
|
||||
} else {
|
||||
updateHostedApp(manifest);
|
||||
|
@ -1022,7 +1135,39 @@ let DOMApplicationRegistry = {
|
|||
|
||||
this.startOfflineCacheDownload(manifest, appObject, aProfileDir);
|
||||
if (manifest.package_path) {
|
||||
this.downloadPackage(manifest, appObject);
|
||||
// origin for install apps is meaningless here, since it's app:// and this
|
||||
// can't be used to resolve package paths.
|
||||
manifest = new DOMApplicationManifest(jsonManifest, app.manifestURL);
|
||||
this.downloadPackage(manifest, appObject, false, function(aId, aManifest) {
|
||||
// Success! Move the zip out of TmpD.
|
||||
let app = DOMApplicationRegistry.webapps[id];
|
||||
let zipFile = FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
|
||||
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
|
||||
zipFile.moveTo(dir, "application.zip");
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
|
||||
try {
|
||||
tmpDir.remove(true);
|
||||
} catch(e) { }
|
||||
|
||||
// Save the manifest
|
||||
let manFile = dir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
DOMApplicationRegistry._writeFile(manFile,
|
||||
JSON.stringify(aManifest),
|
||||
function() { });
|
||||
// Set state and fire events.
|
||||
app.installState = "installed";
|
||||
app.downloading = false;
|
||||
app.downloadavailable = false;
|
||||
DOMApplicationRegistry._saveApps(function() {
|
||||
debug("About to fire Webapps:PackageEvent");
|
||||
DOMApplicationRegistry.broadcastMessage("Webapps:PackageEvent",
|
||||
{ type: "installed",
|
||||
manifestURL: appObject.manifestURL,
|
||||
app: app,
|
||||
manifest: aManifest });
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1091,7 +1236,7 @@ let DOMApplicationRegistry = {
|
|||
}).bind(this));
|
||||
},
|
||||
|
||||
downloadPackage: function(aManifest, aApp) {
|
||||
downloadPackage: function(aManifest, aApp, aIsUpdate, aOnSuccess) {
|
||||
// Here are the steps when installing a package:
|
||||
// - create a temp directory where to store the app.
|
||||
// - download the zip in this directory.
|
||||
|
@ -1147,7 +1292,7 @@ let DOMApplicationRegistry = {
|
|||
this.downloads[aApp.manifestURL] =
|
||||
{ channel:requestChannel,
|
||||
appId: id,
|
||||
previousState: "pending"
|
||||
previousState: aIsUpdate ? "installed" : "pending"
|
||||
};
|
||||
requestChannel.notificationCallbacks = {
|
||||
QueryInterface: function notifQI(aIID) {
|
||||
|
@ -1216,33 +1361,10 @@ let DOMApplicationRegistry = {
|
|||
throw "INVALID_SECURITY_LEVEL";
|
||||
}
|
||||
|
||||
// Success! Move the zip out of TmpD.
|
||||
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
|
||||
zipFile.moveTo(dir, "application.zip");
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
|
||||
try {
|
||||
tmpDir.remove(true);
|
||||
} catch(e) { }
|
||||
|
||||
// Save the manifest
|
||||
let manFile = dir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
DOMApplicationRegistry._writeFile(manFile,
|
||||
JSON.stringify(manifest),
|
||||
function() { });
|
||||
// Set state and fire events.
|
||||
app.installState = "installed";
|
||||
app.dowloading = false;
|
||||
app.dowloadavailable = false;
|
||||
DOMApplicationRegistry._saveApps(function() {
|
||||
debug("About to fire Webapps:PackageEvent");
|
||||
DOMApplicationRegistry.broadcastMessage("Webapps:PackageEvent",
|
||||
{ type: "installed",
|
||||
manifestURL: aApp.manifestURL,
|
||||
app: app,
|
||||
manifest: manifest });
|
||||
delete DOMApplicationRegistry.downloads[aApp.manifestURL]
|
||||
});
|
||||
if (aOnSuccess) {
|
||||
aOnSuccess(id, manifest);
|
||||
}
|
||||
delete DOMApplicationRegistry.downloads[aApp.manifestURL];
|
||||
} catch (e) {
|
||||
// XXX we may need new error messages.
|
||||
cleanup(e);
|
||||
|
|
|
@ -763,7 +763,7 @@ BluetoothService::Notify(const BluetoothSignal& aData)
|
|||
type.AssignLiteral("bluetooth-cancel");
|
||||
} else if (aData.name().EqualsLiteral("PairedStatusChanged")) {
|
||||
NS_ASSERTION(arr.Length() == 1, "PairedStatusChagned: Wrong length of parameters");
|
||||
type.AssignLiteral("bluetooth-pairingstatuschanged");
|
||||
type.AssignLiteral("bluetooth-pairedstatuschanged");
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
nsCString warningMsg;
|
||||
|
|
|
@ -1264,6 +1264,16 @@ EventFilter(DBusConnection* aConn, DBusMessage* aMsg, void* aData)
|
|||
signalName = NS_LITERAL_STRING("PairedStatusChanged");
|
||||
signalPath = NS_LITERAL_STRING(LOCAL_AGENT_PATH);
|
||||
v.get_ArrayOfBluetoothNamedValue()[0].name() = NS_LITERAL_STRING("paired");
|
||||
} else {
|
||||
/*
|
||||
* This is a workaround for Bug 795458. We avoid sending events whose
|
||||
* signalPath is "device object path" (formatted as "/org/bluez/
|
||||
* [pid]/hci0/dev_xx_xx_xx_xx_xx_xx". It's because those corresponding
|
||||
* BluetoothDevice objects may have been garbage-collected. Since we
|
||||
* don't need to know any propert changed except 'paired', this should
|
||||
* work for now.
|
||||
*/
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
} else if (dbus_message_is_signal(aMsg, DBUS_MANAGER_IFACE, "AdapterAdded")) {
|
||||
const char* str;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "skia/SkCanvas.h"
|
||||
#include "skia/SkDashPathEffect.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include <vector>
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
|
|
@ -29,6 +29,7 @@ EXPORTS_mozilla/gfx = \
|
|||
Point.h \
|
||||
Matrix.h \
|
||||
Rect.h \
|
||||
Scale.h \
|
||||
Types.h \
|
||||
Tools.h \
|
||||
UserData.h \
|
||||
|
@ -46,6 +47,7 @@ CPPSRCS = \
|
|||
RecordedEvent.cpp \
|
||||
DrawEventRecorder.cpp \
|
||||
Blur.cpp \
|
||||
Scale.cpp \
|
||||
ScaledFontBase.cpp \
|
||||
DrawTargetDual.cpp \
|
||||
ImageScaling.cpp \
|
||||
|
@ -76,6 +78,8 @@ CPPSRCS += \
|
|||
SourceSurfaceSkia.cpp \
|
||||
DrawTargetSkia.cpp \
|
||||
PathSkia.cpp \
|
||||
convolver.cpp \
|
||||
image_operations.cpp \
|
||||
$(NULL)
|
||||
|
||||
DEFINES += -DUSE_SKIA
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/* 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 "Scale.h"
|
||||
|
||||
#ifdef USE_SKIA
|
||||
#include "HelpersSkia.h"
|
||||
#include "skia/SkBitmap.h"
|
||||
#include "image_operations.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
bool Scale(uint8_t* srcData, int32_t srcWidth, int32_t srcHeight, int32_t srcStride,
|
||||
uint8_t* dstData, int32_t dstWidth, int32_t dstHeight, int32_t dstStride,
|
||||
SurfaceFormat format)
|
||||
{
|
||||
#ifdef USE_SKIA
|
||||
bool opaque;
|
||||
if (format == FORMAT_B8G8R8A8) {
|
||||
opaque = false;
|
||||
} else {
|
||||
opaque = true;
|
||||
}
|
||||
|
||||
SkBitmap::Config config = GfxFormatToSkiaConfig(format);
|
||||
|
||||
SkBitmap imgSrc;
|
||||
imgSrc.setConfig(config, srcWidth, srcHeight, srcStride);
|
||||
imgSrc.setPixels(srcData);
|
||||
imgSrc.setIsOpaque(opaque);
|
||||
|
||||
// Rescaler is compatible with 32 bpp only. Convert to RGB32 if needed.
|
||||
if (config != SkBitmap::kARGB_8888_Config) {
|
||||
imgSrc.copyTo(&imgSrc, SkBitmap::kARGB_8888_Config);
|
||||
}
|
||||
|
||||
// This returns an SkBitmap backed by dstData; since it also wrote to dstData,
|
||||
// we don't need to look at that SkBitmap.
|
||||
SkBitmap result = skia::ImageOperations::Resize(imgSrc,
|
||||
skia::ImageOperations::RESIZE_BEST,
|
||||
dstWidth, dstHeight,
|
||||
dstData);
|
||||
|
||||
return result.readyToDraw();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* 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_GFX_SCALE_H_
|
||||
#define MOZILLA_GFX_SCALE_H_
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
/**
|
||||
* Scale an image using a high-quality filter.
|
||||
*
|
||||
* Synchronously scales an image and writes the output to the destination in
|
||||
* 32-bit format. The destination must be pre-allocated by the caller.
|
||||
*
|
||||
* Returns true if scaling was successful, and false otherwise. Currently, this
|
||||
* function is implemented using Skia. If Skia is not enabled when building,
|
||||
* calling this function will always return false.
|
||||
*
|
||||
* IMPLEMTATION NOTES:
|
||||
* This API is not currently easily hardware acceleratable. A better API might
|
||||
* take a SourceSurface and return a SourceSurface; the Direct2D backend, for
|
||||
* example, could simply set a status bit on a copy of the image, and use
|
||||
* Direct2D's high-quality scaler at draw time.
|
||||
*/
|
||||
GFX2D_API bool Scale(uint8_t* srcData, int32_t srcWidth, int32_t srcHeight, int32_t srcStride,
|
||||
uint8_t* dstData, int32_t dstWidth, int32_t dstHeight, int32_t dstStride,
|
||||
SurfaceFormat format);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MOZILLA_GFX_BLUR_H_ */
|
|
@ -0,0 +1,357 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASICTYPES_H_
|
||||
#define BASE_BASICTYPES_H_
|
||||
|
||||
// Chromium includes a prtypes.h also, but it has been modified to include
|
||||
// their build_config.h as well. We can therefore test for both to determine
|
||||
// if someone screws up the include order.
|
||||
#if defined(prtypes_h___) && !defined(BUILD_BUILD_CONFIG_H_)
|
||||
#error You_must_include_basictypes.h_before_prtypes.h!
|
||||
#endif
|
||||
|
||||
#ifndef NO_NSPR_10_SUPPORT
|
||||
#define NO_NSPR_10_SUPPORT
|
||||
#define NO_NSPR_10_SUPPORT_SAVE
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef NO_NSPR_10_SUPPORT_SAVE
|
||||
#undef NO_NSPR_10_SUPPORT_SAVE
|
||||
#undef NO_NSPR_10_SUPPORT
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef _WIN32
|
||||
#define _WIN32_SAVE
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _WIN32_SAVE
|
||||
#undef _WIN32_SAVE
|
||||
#define _WIN32
|
||||
#endif
|
||||
|
||||
#include <limits.h> // So we can set the bounds of our types
|
||||
#include <stddef.h> // For size_t
|
||||
#include <string.h> // for memcpy
|
||||
|
||||
//#include "base/port.h" // Types that only need exist on certain systems
|
||||
|
||||
#ifndef COMPILER_MSVC
|
||||
// stdint.h is part of C99 but MSVC doesn't have it.
|
||||
#include <stdint.h> // For intptr_t.
|
||||
#endif
|
||||
typedef uint8_t uint8;
|
||||
typedef int16_t int16;
|
||||
#if 0
|
||||
// A type to represent a Unicode code-point value. As of Unicode 4.0,
|
||||
// such values require up to 21 bits.
|
||||
// (For type-checking on pointers, make this explicitly signed,
|
||||
// and it should always be the signed version of whatever int32 is.)
|
||||
typedef signed int char32;
|
||||
|
||||
const uint8 kuint8max = (( uint8) 0xFF);
|
||||
const uint16 kuint16max = ((uint16) 0xFFFF);
|
||||
const uint32 kuint32max = ((uint32) 0xFFFFFFFF);
|
||||
const uint64 kuint64max = ((uint64) GG_LONGLONG(0xFFFFFFFFFFFFFFFF));
|
||||
const int8 kint8min = (( int8) 0x80);
|
||||
const int8 kint8max = (( int8) 0x7F);
|
||||
const int16 kint16min = (( int16) 0x8000);
|
||||
const int16 kint16max = (( int16) 0x7FFF);
|
||||
const int32 kint32min = (( int32) 0x80000000);
|
||||
const int32 kint32max = (( int32) 0x7FFFFFFF);
|
||||
const int64 kint64min = (( int64) GG_LONGLONG(0x8000000000000000));
|
||||
const int64 kint64max = (( int64) GG_LONGLONG(0x7FFFFFFFFFFFFFFF));
|
||||
#endif
|
||||
// Platform- and hardware-dependent printf specifiers
|
||||
# if defined(OS_POSIX)
|
||||
# define __STDC_FORMAT_MACROS 1
|
||||
# include <inttypes.h> // for 64-bit integer format macros
|
||||
# define PRId64L "I64d"
|
||||
# define PRIu64L "I64u"
|
||||
# define PRIx64L "I64x"
|
||||
# elif defined(OS_WIN)
|
||||
# define PRId64 "I64d"
|
||||
# define PRIu64 "I64u"
|
||||
# define PRIx64 "I64x"
|
||||
# define PRId64L L"I64d"
|
||||
# define PRIu64L L"I64u"
|
||||
# define PRIx64L L"I64x"
|
||||
# endif
|
||||
|
||||
// A macro to disallow the copy constructor and operator= functions
|
||||
// This should be used in the private: declarations for a class
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
// An older, deprecated, politically incorrect name for the above.
|
||||
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
|
||||
// A macro to disallow all the implicit constructors, namely the
|
||||
// default constructor, copy constructor and operator= functions.
|
||||
//
|
||||
// This should be used in the private: declarations for a class
|
||||
// that wants to prevent anyone from instantiating it. This is
|
||||
// especially useful for classes containing only static methods.
|
||||
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
|
||||
TypeName(); \
|
||||
DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
|
||||
// The arraysize(arr) macro returns the # of elements in an array arr.
|
||||
// The expression is a compile-time constant, and therefore can be
|
||||
// used in defining new arrays, for example. If you use arraysize on
|
||||
// a pointer by mistake, you will get a compile-time error.
|
||||
//
|
||||
// One caveat is that arraysize() doesn't accept any array of an
|
||||
// anonymous type or a type defined inside a function. In these rare
|
||||
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
|
||||
// due to a limitation in C++'s template system. The limitation might
|
||||
// eventually be removed, but it hasn't happened yet.
|
||||
|
||||
// This template function declaration is used in defining arraysize.
|
||||
// Note that the function doesn't need an implementation, as we only
|
||||
// use its type.
|
||||
template <typename T, size_t N>
|
||||
char (&ArraySizeHelper(T (&array)[N]))[N];
|
||||
|
||||
// That gcc wants both of these prototypes seems mysterious. VC, for
|
||||
// its part, can't decide which to use (another mystery). Matching of
|
||||
// template overloads: the final frontier.
|
||||
#ifndef _MSC_VER
|
||||
template <typename T, size_t N>
|
||||
char (&ArraySizeHelper(const T (&array)[N]))[N];
|
||||
#endif
|
||||
|
||||
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
|
||||
|
||||
// ARRAYSIZE_UNSAFE performs essentially the same calculation as arraysize,
|
||||
// but can be used on anonymous types or types defined inside
|
||||
// functions. It's less safe than arraysize as it accepts some
|
||||
// (although not all) pointers. Therefore, you should use arraysize
|
||||
// whenever possible.
|
||||
//
|
||||
// The expression ARRAYSIZE_UNSAFE(a) is a compile-time constant of type
|
||||
// size_t.
|
||||
//
|
||||
// ARRAYSIZE_UNSAFE catches a few type errors. If you see a compiler error
|
||||
//
|
||||
// "warning: division by zero in ..."
|
||||
//
|
||||
// when using ARRAYSIZE_UNSAFE, you are (wrongfully) giving it a pointer.
|
||||
// You should only use ARRAYSIZE_UNSAFE on statically allocated arrays.
|
||||
//
|
||||
// The following comments are on the implementation details, and can
|
||||
// be ignored by the users.
|
||||
//
|
||||
// ARRAYSIZE_UNSAFE(arr) works by inspecting sizeof(arr) (the # of bytes in
|
||||
// the array) and sizeof(*(arr)) (the # of bytes in one array
|
||||
// element). If the former is divisible by the latter, perhaps arr is
|
||||
// indeed an array, in which case the division result is the # of
|
||||
// elements in the array. Otherwise, arr cannot possibly be an array,
|
||||
// and we generate a compiler error to prevent the code from
|
||||
// compiling.
|
||||
//
|
||||
// Since the size of bool is implementation-defined, we need to cast
|
||||
// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final
|
||||
// result has type size_t.
|
||||
//
|
||||
// This macro is not perfect as it wrongfully accepts certain
|
||||
// pointers, namely where the pointer size is divisible by the pointee
|
||||
// size. Since all our code has to go through a 32-bit compiler,
|
||||
// where a pointer is 4 bytes, this means all pointers to a type whose
|
||||
// size is 3 or greater than 4 will be (righteously) rejected.
|
||||
|
||||
#define ARRAYSIZE_UNSAFE(a) \
|
||||
((sizeof(a) / sizeof(*(a))) / \
|
||||
static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
|
||||
|
||||
|
||||
// Use implicit_cast as a safe version of static_cast or const_cast
|
||||
// for upcasting in the type hierarchy (i.e. casting a pointer to Foo
|
||||
// to a pointer to SuperclassOfFoo or casting a pointer to Foo to
|
||||
// a const pointer to Foo).
|
||||
// When you use implicit_cast, the compiler checks that the cast is safe.
|
||||
// Such explicit implicit_casts are necessary in surprisingly many
|
||||
// situations where C++ demands an exact type match instead of an
|
||||
// argument type convertable to a target type.
|
||||
//
|
||||
// The From type can be inferred, so the preferred syntax for using
|
||||
// implicit_cast is the same as for static_cast etc.:
|
||||
//
|
||||
// implicit_cast<ToType>(expr)
|
||||
//
|
||||
// implicit_cast would have been part of the C++ standard library,
|
||||
// but the proposal was submitted too late. It will probably make
|
||||
// its way into the language in the future.
|
||||
template<typename To, typename From>
|
||||
inline To implicit_cast(From const &f) {
|
||||
return f;
|
||||
}
|
||||
|
||||
// The COMPILE_ASSERT macro can be used to verify that a compile time
|
||||
// expression is true. For example, you could use it to verify the
|
||||
// size of a static array:
|
||||
//
|
||||
// COMPILE_ASSERT(ARRAYSIZE_UNSAFE(content_type_names) == CONTENT_NUM_TYPES,
|
||||
// content_type_names_incorrect_size);
|
||||
//
|
||||
// or to make sure a struct is smaller than a certain size:
|
||||
//
|
||||
// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large);
|
||||
//
|
||||
// The second argument to the macro is the name of the variable. If
|
||||
// the expression is false, most compilers will issue a warning/error
|
||||
// containing the name of the variable.
|
||||
|
||||
template <bool>
|
||||
struct CompileAssert {
|
||||
};
|
||||
|
||||
#undef COMPILE_ASSERT
|
||||
#define COMPILE_ASSERT(expr, msg) \
|
||||
typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
|
||||
|
||||
// Implementation details of COMPILE_ASSERT:
|
||||
//
|
||||
// - COMPILE_ASSERT works by defining an array type that has -1
|
||||
// elements (and thus is invalid) when the expression is false.
|
||||
//
|
||||
// - The simpler definition
|
||||
//
|
||||
// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1]
|
||||
//
|
||||
// does not work, as gcc supports variable-length arrays whose sizes
|
||||
// are determined at run-time (this is gcc's extension and not part
|
||||
// of the C++ standard). As a result, gcc fails to reject the
|
||||
// following code with the simple definition:
|
||||
//
|
||||
// int foo;
|
||||
// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is
|
||||
// // not a compile-time constant.
|
||||
//
|
||||
// - By using the type CompileAssert<(bool(expr))>, we ensures that
|
||||
// expr is a compile-time constant. (Template arguments must be
|
||||
// determined at compile-time.)
|
||||
//
|
||||
// - The outter parentheses in CompileAssert<(bool(expr))> are necessary
|
||||
// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written
|
||||
//
|
||||
// CompileAssert<bool(expr)>
|
||||
//
|
||||
// instead, these compilers will refuse to compile
|
||||
//
|
||||
// COMPILE_ASSERT(5 > 0, some_message);
|
||||
//
|
||||
// (They seem to think the ">" in "5 > 0" marks the end of the
|
||||
// template argument list.)
|
||||
//
|
||||
// - The array size is (bool(expr) ? 1 : -1), instead of simply
|
||||
//
|
||||
// ((expr) ? 1 : -1).
|
||||
//
|
||||
// This is to avoid running into a bug in MS VC 7.1, which
|
||||
// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1.
|
||||
|
||||
|
||||
// MetatagId refers to metatag-id that we assign to
|
||||
// each metatag <name, value> pair..
|
||||
//typedef uint32 MetatagId;
|
||||
|
||||
// Argument type used in interfaces that can optionally take ownership
|
||||
// of a passed in argument. If TAKE_OWNERSHIP is passed, the called
|
||||
// object takes ownership of the argument. Otherwise it does not.
|
||||
enum Ownership {
|
||||
DO_NOT_TAKE_OWNERSHIP,
|
||||
TAKE_OWNERSHIP
|
||||
};
|
||||
|
||||
// bit_cast<Dest,Source> is a template function that implements the
|
||||
// equivalent of "*reinterpret_cast<Dest*>(&source)". We need this in
|
||||
// very low-level functions like the protobuf library and fast math
|
||||
// support.
|
||||
//
|
||||
// float f = 3.14159265358979;
|
||||
// int i = bit_cast<int32>(f);
|
||||
// // i = 0x40490fdb
|
||||
//
|
||||
// The classical address-casting method is:
|
||||
//
|
||||
// // WRONG
|
||||
// float f = 3.14159265358979; // WRONG
|
||||
// int i = * reinterpret_cast<int*>(&f); // WRONG
|
||||
//
|
||||
// The address-casting method actually produces undefined behavior
|
||||
// according to ISO C++ specification section 3.10 -15 -. Roughly, this
|
||||
// section says: if an object in memory has one type, and a program
|
||||
// accesses it with a different type, then the result is undefined
|
||||
// behavior for most values of "different type".
|
||||
//
|
||||
// This is true for any cast syntax, either *(int*)&f or
|
||||
// *reinterpret_cast<int*>(&f). And it is particularly true for
|
||||
// conversions betweeen integral lvalues and floating-point lvalues.
|
||||
//
|
||||
// The purpose of 3.10 -15- is to allow optimizing compilers to assume
|
||||
// that expressions with different types refer to different memory. gcc
|
||||
// 4.0.1 has an optimizer that takes advantage of this. So a
|
||||
// non-conforming program quietly produces wildly incorrect output.
|
||||
//
|
||||
// The problem is not the use of reinterpret_cast. The problem is type
|
||||
// punning: holding an object in memory of one type and reading its bits
|
||||
// back using a different type.
|
||||
//
|
||||
// The C++ standard is more subtle and complex than this, but that
|
||||
// is the basic idea.
|
||||
//
|
||||
// Anyways ...
|
||||
//
|
||||
// bit_cast<> calls memcpy() which is blessed by the standard,
|
||||
// especially by the example in section 3.9 . Also, of course,
|
||||
// bit_cast<> wraps up the nasty logic in one place.
|
||||
//
|
||||
// Fortunately memcpy() is very fast. In optimized mode, with a
|
||||
// constant size, gcc 2.95.3, gcc 4.0.1, and msvc 7.1 produce inline
|
||||
// code with the minimal amount of data movement. On a 32-bit system,
|
||||
// memcpy(d,s,4) compiles to one load and one store, and memcpy(d,s,8)
|
||||
// compiles to two loads and two stores.
|
||||
//
|
||||
// I tested this code with gcc 2.95.3, gcc 4.0.1, icc 8.1, and msvc 7.1.
|
||||
//
|
||||
// WARNING: if Dest or Source is a non-POD type, the result of the memcpy
|
||||
// is likely to surprise you.
|
||||
|
||||
template <class Dest, class Source>
|
||||
inline Dest bit_cast(const Source& source) {
|
||||
// Compile time assertion: sizeof(Dest) == sizeof(Source)
|
||||
// A compile error here means your Dest and Source have different sizes.
|
||||
typedef char VerifySizesAreEqual [sizeof(Dest) == sizeof(Source) ? 1 : -1];
|
||||
|
||||
Dest dest;
|
||||
memcpy(&dest, &source, sizeof(dest));
|
||||
return dest;
|
||||
}
|
||||
|
||||
// The following enum should be used only as a constructor argument to indicate
|
||||
// that the variable has static storage class, and that the constructor should
|
||||
// do nothing to its state. It indicates to the reader that it is legal to
|
||||
// declare a static instance of the class, provided the constructor is given
|
||||
// the base::LINKER_INITIALIZED argument. Normally, it is unsafe to declare a
|
||||
// static variable that has a constructor or a destructor because invocation
|
||||
// order is undefined. However, IF the type can be initialized by filling with
|
||||
// zeroes (which the loader does for static variables), AND the destructor also
|
||||
// does nothing to the storage, AND there are no virtual methods, then a
|
||||
// constructor declared as
|
||||
// explicit MyClass(base::LinkerInitialized x) {}
|
||||
// and invoked as
|
||||
// static MyClass my_variable_name(base::LINKER_INITIALIZED);
|
||||
namespace base {
|
||||
enum LinkerInitialized { LINKER_INITIALIZED };
|
||||
} // base
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // BASE_BASICTYPES_H_
|
|
@ -0,0 +1,864 @@
|
|||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "convolver.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "nsAlgorithm.h"
|
||||
|
||||
#include "skia/SkTypes.h"
|
||||
|
||||
// note: SIMD_SSE2 is not enabled because of bugs, apparently
|
||||
|
||||
#if defined(SIMD_SSE2)
|
||||
#include <emmintrin.h> // ARCH_CPU_X86_FAMILY was defined in build/config.h
|
||||
#endif
|
||||
|
||||
namespace skia {
|
||||
|
||||
namespace {
|
||||
|
||||
// Converts the argument to an 8-bit unsigned value by clamping to the range
|
||||
// 0-255.
|
||||
inline unsigned char ClampTo8(int a) {
|
||||
if (static_cast<unsigned>(a) < 256)
|
||||
return a; // Avoid the extra check in the common case.
|
||||
if (a < 0)
|
||||
return 0;
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Stores a list of rows in a circular buffer. The usage is you write into it
|
||||
// by calling AdvanceRow. It will keep track of which row in the buffer it
|
||||
// should use next, and the total number of rows added.
|
||||
class CircularRowBuffer {
|
||||
public:
|
||||
// The number of pixels in each row is given in |source_row_pixel_width|.
|
||||
// The maximum number of rows needed in the buffer is |max_y_filter_size|
|
||||
// (we only need to store enough rows for the biggest filter).
|
||||
//
|
||||
// We use the |first_input_row| to compute the coordinates of all of the
|
||||
// following rows returned by Advance().
|
||||
CircularRowBuffer(int dest_row_pixel_width, int max_y_filter_size,
|
||||
int first_input_row)
|
||||
: row_byte_width_(dest_row_pixel_width * 4),
|
||||
num_rows_(max_y_filter_size),
|
||||
next_row_(0),
|
||||
next_row_coordinate_(first_input_row) {
|
||||
buffer_.resize(row_byte_width_ * max_y_filter_size);
|
||||
row_addresses_.resize(num_rows_);
|
||||
}
|
||||
|
||||
// Moves to the next row in the buffer, returning a pointer to the beginning
|
||||
// of it.
|
||||
unsigned char* AdvanceRow() {
|
||||
unsigned char* row = &buffer_[next_row_ * row_byte_width_];
|
||||
next_row_coordinate_++;
|
||||
|
||||
// Set the pointer to the next row to use, wrapping around if necessary.
|
||||
next_row_++;
|
||||
if (next_row_ == num_rows_)
|
||||
next_row_ = 0;
|
||||
return row;
|
||||
}
|
||||
|
||||
// Returns a pointer to an "unrolled" array of rows. These rows will start
|
||||
// at the y coordinate placed into |*first_row_index| and will continue in
|
||||
// order for the maximum number of rows in this circular buffer.
|
||||
//
|
||||
// The |first_row_index_| may be negative. This means the circular buffer
|
||||
// starts before the top of the image (it hasn't been filled yet).
|
||||
unsigned char* const* GetRowAddresses(int* first_row_index) {
|
||||
// Example for a 4-element circular buffer holding coords 6-9.
|
||||
// Row 0 Coord 8
|
||||
// Row 1 Coord 9
|
||||
// Row 2 Coord 6 <- next_row_ = 2, next_row_coordinate_ = 10.
|
||||
// Row 3 Coord 7
|
||||
//
|
||||
// The "next" row is also the first (lowest) coordinate. This computation
|
||||
// may yield a negative value, but that's OK, the math will work out
|
||||
// since the user of this buffer will compute the offset relative
|
||||
// to the first_row_index and the negative rows will never be used.
|
||||
*first_row_index = next_row_coordinate_ - num_rows_;
|
||||
|
||||
int cur_row = next_row_;
|
||||
for (int i = 0; i < num_rows_; i++) {
|
||||
row_addresses_[i] = &buffer_[cur_row * row_byte_width_];
|
||||
|
||||
// Advance to the next row, wrapping if necessary.
|
||||
cur_row++;
|
||||
if (cur_row == num_rows_)
|
||||
cur_row = 0;
|
||||
}
|
||||
return &row_addresses_[0];
|
||||
}
|
||||
|
||||
private:
|
||||
// The buffer storing the rows. They are packed, each one row_byte_width_.
|
||||
std::vector<unsigned char> buffer_;
|
||||
|
||||
// Number of bytes per row in the |buffer_|.
|
||||
int row_byte_width_;
|
||||
|
||||
// The number of rows available in the buffer.
|
||||
int num_rows_;
|
||||
|
||||
// The next row index we should write into. This wraps around as the
|
||||
// circular buffer is used.
|
||||
int next_row_;
|
||||
|
||||
// The y coordinate of the |next_row_|. This is incremented each time a
|
||||
// new row is appended and does not wrap.
|
||||
int next_row_coordinate_;
|
||||
|
||||
// Buffer used by GetRowAddresses().
|
||||
std::vector<unsigned char*> row_addresses_;
|
||||
};
|
||||
|
||||
// Convolves horizontally along a single row. The row data is given in
|
||||
// |src_data| and continues for the num_values() of the filter.
|
||||
template<bool has_alpha>
|
||||
void ConvolveHorizontally(const unsigned char* src_data,
|
||||
const ConvolutionFilter1D& filter,
|
||||
unsigned char* out_row) {
|
||||
// Loop over each pixel on this row in the output image.
|
||||
int num_values = filter.num_values();
|
||||
for (int out_x = 0; out_x < num_values; out_x++) {
|
||||
// Get the filter that determines the current output pixel.
|
||||
int filter_offset, filter_length;
|
||||
const ConvolutionFilter1D::Fixed* filter_values =
|
||||
filter.FilterForValue(out_x, &filter_offset, &filter_length);
|
||||
|
||||
// Compute the first pixel in this row that the filter affects. It will
|
||||
// touch |filter_length| pixels (4 bytes each) after this.
|
||||
const unsigned char* row_to_filter = &src_data[filter_offset * 4];
|
||||
|
||||
// Apply the filter to the row to get the destination pixel in |accum|.
|
||||
int accum[4] = {0};
|
||||
for (int filter_x = 0; filter_x < filter_length; filter_x++) {
|
||||
ConvolutionFilter1D::Fixed cur_filter = filter_values[filter_x];
|
||||
accum[0] += cur_filter * row_to_filter[filter_x * 4 + 0];
|
||||
accum[1] += cur_filter * row_to_filter[filter_x * 4 + 1];
|
||||
accum[2] += cur_filter * row_to_filter[filter_x * 4 + 2];
|
||||
if (has_alpha)
|
||||
accum[3] += cur_filter * row_to_filter[filter_x * 4 + 3];
|
||||
}
|
||||
|
||||
// Bring this value back in range. All of the filter scaling factors
|
||||
// are in fixed point with kShiftBits bits of fractional part.
|
||||
accum[0] >>= ConvolutionFilter1D::kShiftBits;
|
||||
accum[1] >>= ConvolutionFilter1D::kShiftBits;
|
||||
accum[2] >>= ConvolutionFilter1D::kShiftBits;
|
||||
if (has_alpha)
|
||||
accum[3] >>= ConvolutionFilter1D::kShiftBits;
|
||||
|
||||
// Store the new pixel.
|
||||
out_row[out_x * 4 + 0] = ClampTo8(accum[0]);
|
||||
out_row[out_x * 4 + 1] = ClampTo8(accum[1]);
|
||||
out_row[out_x * 4 + 2] = ClampTo8(accum[2]);
|
||||
if (has_alpha)
|
||||
out_row[out_x * 4 + 3] = ClampTo8(accum[3]);
|
||||
}
|
||||
}
|
||||
|
||||
// Does vertical convolution to produce one output row. The filter values and
|
||||
// length are given in the first two parameters. These are applied to each
|
||||
// of the rows pointed to in the |source_data_rows| array, with each row
|
||||
// being |pixel_width| wide.
|
||||
//
|
||||
// The output must have room for |pixel_width * 4| bytes.
|
||||
template<bool has_alpha>
|
||||
void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values,
|
||||
int filter_length,
|
||||
unsigned char* const* source_data_rows,
|
||||
int pixel_width,
|
||||
unsigned char* out_row) {
|
||||
// We go through each column in the output and do a vertical convolution,
|
||||
// generating one output pixel each time.
|
||||
for (int out_x = 0; out_x < pixel_width; out_x++) {
|
||||
// Compute the number of bytes over in each row that the current column
|
||||
// we're convolving starts at. The pixel will cover the next 4 bytes.
|
||||
int byte_offset = out_x * 4;
|
||||
|
||||
// Apply the filter to one column of pixels.
|
||||
int accum[4] = {0};
|
||||
for (int filter_y = 0; filter_y < filter_length; filter_y++) {
|
||||
ConvolutionFilter1D::Fixed cur_filter = filter_values[filter_y];
|
||||
accum[0] += cur_filter * source_data_rows[filter_y][byte_offset + 0];
|
||||
accum[1] += cur_filter * source_data_rows[filter_y][byte_offset + 1];
|
||||
accum[2] += cur_filter * source_data_rows[filter_y][byte_offset + 2];
|
||||
if (has_alpha)
|
||||
accum[3] += cur_filter * source_data_rows[filter_y][byte_offset + 3];
|
||||
}
|
||||
|
||||
// Bring this value back in range. All of the filter scaling factors
|
||||
// are in fixed point with kShiftBits bits of precision.
|
||||
accum[0] >>= ConvolutionFilter1D::kShiftBits;
|
||||
accum[1] >>= ConvolutionFilter1D::kShiftBits;
|
||||
accum[2] >>= ConvolutionFilter1D::kShiftBits;
|
||||
if (has_alpha)
|
||||
accum[3] >>= ConvolutionFilter1D::kShiftBits;
|
||||
|
||||
// Store the new pixel.
|
||||
out_row[byte_offset + 0] = ClampTo8(accum[0]);
|
||||
out_row[byte_offset + 1] = ClampTo8(accum[1]);
|
||||
out_row[byte_offset + 2] = ClampTo8(accum[2]);
|
||||
if (has_alpha) {
|
||||
unsigned char alpha = ClampTo8(accum[3]);
|
||||
|
||||
// Make sure the alpha channel doesn't come out smaller than any of the
|
||||
// color channels. We use premultipled alpha channels, so this should
|
||||
// never happen, but rounding errors will cause this from time to time.
|
||||
// These "impossible" colors will cause overflows (and hence random pixel
|
||||
// values) when the resulting bitmap is drawn to the screen.
|
||||
//
|
||||
// We only need to do this when generating the final output row (here).
|
||||
int max_color_channel = NS_MAX(out_row[byte_offset + 0],
|
||||
NS_MAX(out_row[byte_offset + 1], out_row[byte_offset + 2]));
|
||||
if (alpha < max_color_channel)
|
||||
out_row[byte_offset + 3] = max_color_channel;
|
||||
else
|
||||
out_row[byte_offset + 3] = alpha;
|
||||
} else {
|
||||
// No alpha channel, the image is opaque.
|
||||
out_row[byte_offset + 3] = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Convolves horizontally along a single row. The row data is given in
|
||||
// |src_data| and continues for the num_values() of the filter.
|
||||
void ConvolveHorizontally_SSE2(const unsigned char* src_data,
|
||||
const ConvolutionFilter1D& filter,
|
||||
unsigned char* out_row) {
|
||||
#if defined(SIMD_SSE2)
|
||||
int num_values = filter.num_values();
|
||||
|
||||
int filter_offset, filter_length;
|
||||
__m128i zero = _mm_setzero_si128();
|
||||
__m128i mask[4];
|
||||
// |mask| will be used to decimate all extra filter coefficients that are
|
||||
// loaded by SIMD when |filter_length| is not divisible by 4.
|
||||
// mask[0] is not used in following algorithm.
|
||||
mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
|
||||
mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
|
||||
mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
|
||||
|
||||
// Output one pixel each iteration, calculating all channels (RGBA) together.
|
||||
for (int out_x = 0; out_x < num_values; out_x++) {
|
||||
const ConvolutionFilter1D::Fixed* filter_values =
|
||||
filter.FilterForValue(out_x, &filter_offset, &filter_length);
|
||||
|
||||
__m128i accum = _mm_setzero_si128();
|
||||
|
||||
// Compute the first pixel in this row that the filter affects. It will
|
||||
// touch |filter_length| pixels (4 bytes each) after this.
|
||||
const __m128i* row_to_filter =
|
||||
reinterpret_cast<const __m128i*>(&src_data[filter_offset << 2]);
|
||||
|
||||
// We will load and accumulate with four coefficients per iteration.
|
||||
for (int filter_x = 0; filter_x < filter_length >> 2; filter_x++) {
|
||||
|
||||
// Load 4 coefficients => duplicate 1st and 2nd of them for all channels.
|
||||
__m128i coeff, coeff16;
|
||||
// [16] xx xx xx xx c3 c2 c1 c0
|
||||
coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
|
||||
// [16] xx xx xx xx c1 c1 c0 c0
|
||||
coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
|
||||
// [16] c1 c1 c1 c1 c0 c0 c0 c0
|
||||
coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
|
||||
|
||||
// Load four pixels => unpack the first two pixels to 16 bits =>
|
||||
// multiply with coefficients => accumulate the convolution result.
|
||||
// [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
|
||||
__m128i src8 = _mm_loadu_si128(row_to_filter);
|
||||
// [16] a1 b1 g1 r1 a0 b0 g0 r0
|
||||
__m128i src16 = _mm_unpacklo_epi8(src8, zero);
|
||||
__m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
__m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
// [32] a0*c0 b0*c0 g0*c0 r0*c0
|
||||
__m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum = _mm_add_epi32(accum, t);
|
||||
// [32] a1*c1 b1*c1 g1*c1 r1*c1
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi);
|
||||
accum = _mm_add_epi32(accum, t);
|
||||
|
||||
// Duplicate 3rd and 4th coefficients for all channels =>
|
||||
// unpack the 3rd and 4th pixels to 16 bits => multiply with coefficients
|
||||
// => accumulate the convolution results.
|
||||
// [16] xx xx xx xx c3 c3 c2 c2
|
||||
coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
// [16] c3 c3 c3 c3 c2 c2 c2 c2
|
||||
coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
|
||||
// [16] a3 g3 b3 r3 a2 g2 b2 r2
|
||||
src16 = _mm_unpackhi_epi8(src8, zero);
|
||||
mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
// [32] a2*c2 b2*c2 g2*c2 r2*c2
|
||||
t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum = _mm_add_epi32(accum, t);
|
||||
// [32] a3*c3 b3*c3 g3*c3 r3*c3
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi);
|
||||
accum = _mm_add_epi32(accum, t);
|
||||
|
||||
// Advance the pixel and coefficients pointers.
|
||||
row_to_filter += 1;
|
||||
filter_values += 4;
|
||||
}
|
||||
|
||||
// When |filter_length| is not divisible by 4, we need to decimate some of
|
||||
// the filter coefficient that was loaded incorrectly to zero; Other than
|
||||
// that the algorithm is same with above, exceot that the 4th pixel will be
|
||||
// always absent.
|
||||
int r = filter_length&3;
|
||||
if (r) {
|
||||
// Note: filter_values must be padded to align_up(filter_offset, 8).
|
||||
__m128i coeff, coeff16;
|
||||
coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
|
||||
// Mask out extra filter taps.
|
||||
coeff = _mm_and_si128(coeff, mask[r]);
|
||||
coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
|
||||
coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
|
||||
|
||||
// Note: line buffer must be padded to align_up(filter_offset, 16).
|
||||
// We resolve this by use C-version for the last horizontal line.
|
||||
__m128i src8 = _mm_loadu_si128(row_to_filter);
|
||||
__m128i src16 = _mm_unpacklo_epi8(src8, zero);
|
||||
__m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
__m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
__m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum = _mm_add_epi32(accum, t);
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi);
|
||||
accum = _mm_add_epi32(accum, t);
|
||||
|
||||
src16 = _mm_unpackhi_epi8(src8, zero);
|
||||
coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
|
||||
mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum = _mm_add_epi32(accum, t);
|
||||
}
|
||||
|
||||
// Shift right for fixed point implementation.
|
||||
accum = _mm_srai_epi32(accum, ConvolutionFilter1D::kShiftBits);
|
||||
|
||||
// Packing 32 bits |accum| to 16 bits per channel (signed saturation).
|
||||
accum = _mm_packs_epi32(accum, zero);
|
||||
// Packing 16 bits |accum| to 8 bits per channel (unsigned saturation).
|
||||
accum = _mm_packus_epi16(accum, zero);
|
||||
|
||||
// Store the pixel value of 32 bits.
|
||||
*(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum);
|
||||
out_row += 4;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Convolves horizontally along four rows. The row data is given in
|
||||
// |src_data| and continues for the num_values() of the filter.
|
||||
// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please
|
||||
// refer to that function for detailed comments.
|
||||
void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4],
|
||||
const ConvolutionFilter1D& filter,
|
||||
unsigned char* out_row[4]) {
|
||||
#if defined(SIMD_SSE2)
|
||||
int num_values = filter.num_values();
|
||||
|
||||
int filter_offset, filter_length;
|
||||
__m128i zero = _mm_setzero_si128();
|
||||
__m128i mask[4];
|
||||
// |mask| will be used to decimate all extra filter coefficients that are
|
||||
// loaded by SIMD when |filter_length| is not divisible by 4.
|
||||
// mask[0] is not used in following algorithm.
|
||||
mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
|
||||
mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
|
||||
mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
|
||||
|
||||
// Output one pixel each iteration, calculating all channels (RGBA) together.
|
||||
for (int out_x = 0; out_x < num_values; out_x++) {
|
||||
const ConvolutionFilter1D::Fixed* filter_values =
|
||||
filter.FilterForValue(out_x, &filter_offset, &filter_length);
|
||||
|
||||
// four pixels in a column per iteration.
|
||||
__m128i accum0 = _mm_setzero_si128();
|
||||
__m128i accum1 = _mm_setzero_si128();
|
||||
__m128i accum2 = _mm_setzero_si128();
|
||||
__m128i accum3 = _mm_setzero_si128();
|
||||
int start = (filter_offset<<2);
|
||||
// We will load and accumulate with four coefficients per iteration.
|
||||
for (int filter_x = 0; filter_x < (filter_length >> 2); filter_x++) {
|
||||
__m128i coeff, coeff16lo, coeff16hi;
|
||||
// [16] xx xx xx xx c3 c2 c1 c0
|
||||
coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
|
||||
// [16] xx xx xx xx c1 c1 c0 c0
|
||||
coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
|
||||
// [16] c1 c1 c1 c1 c0 c0 c0 c0
|
||||
coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo);
|
||||
// [16] xx xx xx xx c3 c3 c2 c2
|
||||
coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
// [16] c3 c3 c3 c3 c2 c2 c2 c2
|
||||
coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi);
|
||||
|
||||
__m128i src8, src16, mul_hi, mul_lo, t;
|
||||
|
||||
#define ITERATION(src, accum) \
|
||||
src8 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src)); \
|
||||
src16 = _mm_unpacklo_epi8(src8, zero); \
|
||||
mul_hi = _mm_mulhi_epi16(src16, coeff16lo); \
|
||||
mul_lo = _mm_mullo_epi16(src16, coeff16lo); \
|
||||
t = _mm_unpacklo_epi16(mul_lo, mul_hi); \
|
||||
accum = _mm_add_epi32(accum, t); \
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi); \
|
||||
accum = _mm_add_epi32(accum, t); \
|
||||
src16 = _mm_unpackhi_epi8(src8, zero); \
|
||||
mul_hi = _mm_mulhi_epi16(src16, coeff16hi); \
|
||||
mul_lo = _mm_mullo_epi16(src16, coeff16hi); \
|
||||
t = _mm_unpacklo_epi16(mul_lo, mul_hi); \
|
||||
accum = _mm_add_epi32(accum, t); \
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi); \
|
||||
accum = _mm_add_epi32(accum, t)
|
||||
|
||||
ITERATION(src_data[0] + start, accum0);
|
||||
ITERATION(src_data[1] + start, accum1);
|
||||
ITERATION(src_data[2] + start, accum2);
|
||||
ITERATION(src_data[3] + start, accum3);
|
||||
|
||||
start += 16;
|
||||
filter_values += 4;
|
||||
}
|
||||
|
||||
int r = filter_length & 3;
|
||||
if (r) {
|
||||
// Note: filter_values must be padded to align_up(filter_offset, 8);
|
||||
__m128i coeff;
|
||||
coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
|
||||
// Mask out extra filter taps.
|
||||
coeff = _mm_and_si128(coeff, mask[r]);
|
||||
|
||||
__m128i coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
|
||||
/* c1 c1 c1 c1 c0 c0 c0 c0 */
|
||||
coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo);
|
||||
__m128i coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi);
|
||||
|
||||
__m128i src8, src16, mul_hi, mul_lo, t;
|
||||
|
||||
ITERATION(src_data[0] + start, accum0);
|
||||
ITERATION(src_data[1] + start, accum1);
|
||||
ITERATION(src_data[2] + start, accum2);
|
||||
ITERATION(src_data[3] + start, accum3);
|
||||
}
|
||||
|
||||
accum0 = _mm_srai_epi32(accum0, ConvolutionFilter1D::kShiftBits);
|
||||
accum0 = _mm_packs_epi32(accum0, zero);
|
||||
accum0 = _mm_packus_epi16(accum0, zero);
|
||||
accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits);
|
||||
accum1 = _mm_packs_epi32(accum1, zero);
|
||||
accum1 = _mm_packus_epi16(accum1, zero);
|
||||
accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits);
|
||||
accum2 = _mm_packs_epi32(accum2, zero);
|
||||
accum2 = _mm_packus_epi16(accum2, zero);
|
||||
accum3 = _mm_srai_epi32(accum3, ConvolutionFilter1D::kShiftBits);
|
||||
accum3 = _mm_packs_epi32(accum3, zero);
|
||||
accum3 = _mm_packus_epi16(accum3, zero);
|
||||
|
||||
*(reinterpret_cast<int*>(out_row[0])) = _mm_cvtsi128_si32(accum0);
|
||||
*(reinterpret_cast<int*>(out_row[1])) = _mm_cvtsi128_si32(accum1);
|
||||
*(reinterpret_cast<int*>(out_row[2])) = _mm_cvtsi128_si32(accum2);
|
||||
*(reinterpret_cast<int*>(out_row[3])) = _mm_cvtsi128_si32(accum3);
|
||||
|
||||
out_row[0] += 4;
|
||||
out_row[1] += 4;
|
||||
out_row[2] += 4;
|
||||
out_row[3] += 4;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Does vertical convolution to produce one output row. The filter values and
|
||||
// length are given in the first two parameters. These are applied to each
|
||||
// of the rows pointed to in the |source_data_rows| array, with each row
|
||||
// being |pixel_width| wide.
|
||||
//
|
||||
// The output must have room for |pixel_width * 4| bytes.
|
||||
template<bool has_alpha>
|
||||
void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values,
|
||||
int filter_length,
|
||||
unsigned char* const* source_data_rows,
|
||||
int pixel_width,
|
||||
unsigned char* out_row) {
|
||||
#if defined(SIMD_SSE2)
|
||||
int width = pixel_width & ~3;
|
||||
|
||||
__m128i zero = _mm_setzero_si128();
|
||||
__m128i accum0, accum1, accum2, accum3, coeff16;
|
||||
const __m128i* src;
|
||||
// Output four pixels per iteration (16 bytes).
|
||||
for (int out_x = 0; out_x < width; out_x += 4) {
|
||||
|
||||
// Accumulated result for each pixel. 32 bits per RGBA channel.
|
||||
accum0 = _mm_setzero_si128();
|
||||
accum1 = _mm_setzero_si128();
|
||||
accum2 = _mm_setzero_si128();
|
||||
accum3 = _mm_setzero_si128();
|
||||
|
||||
// Convolve with one filter coefficient per iteration.
|
||||
for (int filter_y = 0; filter_y < filter_length; filter_y++) {
|
||||
|
||||
// Duplicate the filter coefficient 8 times.
|
||||
// [16] cj cj cj cj cj cj cj cj
|
||||
coeff16 = _mm_set1_epi16(filter_values[filter_y]);
|
||||
|
||||
// Load four pixels (16 bytes) together.
|
||||
// [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
|
||||
src = reinterpret_cast<const __m128i*>(
|
||||
&source_data_rows[filter_y][out_x << 2]);
|
||||
__m128i src8 = _mm_loadu_si128(src);
|
||||
|
||||
// Unpack 1st and 2nd pixels from 8 bits to 16 bits for each channels =>
|
||||
// multiply with current coefficient => accumulate the result.
|
||||
// [16] a1 b1 g1 r1 a0 b0 g0 r0
|
||||
__m128i src16 = _mm_unpacklo_epi8(src8, zero);
|
||||
__m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
__m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
// [32] a0 b0 g0 r0
|
||||
__m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum0 = _mm_add_epi32(accum0, t);
|
||||
// [32] a1 b1 g1 r1
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi);
|
||||
accum1 = _mm_add_epi32(accum1, t);
|
||||
|
||||
// Unpack 3rd and 4th pixels from 8 bits to 16 bits for each channels =>
|
||||
// multiply with current coefficient => accumulate the result.
|
||||
// [16] a3 b3 g3 r3 a2 b2 g2 r2
|
||||
src16 = _mm_unpackhi_epi8(src8, zero);
|
||||
mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
// [32] a2 b2 g2 r2
|
||||
t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum2 = _mm_add_epi32(accum2, t);
|
||||
// [32] a3 b3 g3 r3
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi);
|
||||
accum3 = _mm_add_epi32(accum3, t);
|
||||
}
|
||||
|
||||
// Shift right for fixed point implementation.
|
||||
accum0 = _mm_srai_epi32(accum0, ConvolutionFilter1D::kShiftBits);
|
||||
accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits);
|
||||
accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits);
|
||||
accum3 = _mm_srai_epi32(accum3, ConvolutionFilter1D::kShiftBits);
|
||||
|
||||
// Packing 32 bits |accum| to 16 bits per channel (signed saturation).
|
||||
// [16] a1 b1 g1 r1 a0 b0 g0 r0
|
||||
accum0 = _mm_packs_epi32(accum0, accum1);
|
||||
// [16] a3 b3 g3 r3 a2 b2 g2 r2
|
||||
accum2 = _mm_packs_epi32(accum2, accum3);
|
||||
|
||||
// Packing 16 bits |accum| to 8 bits per channel (unsigned saturation).
|
||||
// [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
|
||||
accum0 = _mm_packus_epi16(accum0, accum2);
|
||||
|
||||
if (has_alpha) {
|
||||
// Compute the max(ri, gi, bi) for each pixel.
|
||||
// [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
|
||||
__m128i a = _mm_srli_epi32(accum0, 8);
|
||||
// [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
|
||||
__m128i b = _mm_max_epu8(a, accum0); // Max of r and g.
|
||||
// [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
|
||||
a = _mm_srli_epi32(accum0, 16);
|
||||
// [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
|
||||
b = _mm_max_epu8(a, b); // Max of r and g and b.
|
||||
// [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
|
||||
b = _mm_slli_epi32(b, 24);
|
||||
|
||||
// Make sure the value of alpha channel is always larger than maximum
|
||||
// value of color channels.
|
||||
accum0 = _mm_max_epu8(b, accum0);
|
||||
} else {
|
||||
// Set value of alpha channels to 0xFF.
|
||||
__m128i mask = _mm_set1_epi32(0xff000000);
|
||||
accum0 = _mm_or_si128(accum0, mask);
|
||||
}
|
||||
|
||||
// Store the convolution result (16 bytes) and advance the pixel pointers.
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(out_row), accum0);
|
||||
out_row += 16;
|
||||
}
|
||||
|
||||
// When the width of the output is not divisible by 4, We need to save one
|
||||
// pixel (4 bytes) each time. And also the fourth pixel is always absent.
|
||||
if (pixel_width & 3) {
|
||||
accum0 = _mm_setzero_si128();
|
||||
accum1 = _mm_setzero_si128();
|
||||
accum2 = _mm_setzero_si128();
|
||||
for (int filter_y = 0; filter_y < filter_length; ++filter_y) {
|
||||
coeff16 = _mm_set1_epi16(filter_values[filter_y]);
|
||||
// [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
|
||||
src = reinterpret_cast<const __m128i*>(
|
||||
&source_data_rows[filter_y][width<<2]);
|
||||
__m128i src8 = _mm_loadu_si128(src);
|
||||
// [16] a1 b1 g1 r1 a0 b0 g0 r0
|
||||
__m128i src16 = _mm_unpacklo_epi8(src8, zero);
|
||||
__m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
__m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
// [32] a0 b0 g0 r0
|
||||
__m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum0 = _mm_add_epi32(accum0, t);
|
||||
// [32] a1 b1 g1 r1
|
||||
t = _mm_unpackhi_epi16(mul_lo, mul_hi);
|
||||
accum1 = _mm_add_epi32(accum1, t);
|
||||
// [16] a3 b3 g3 r3 a2 b2 g2 r2
|
||||
src16 = _mm_unpackhi_epi8(src8, zero);
|
||||
mul_hi = _mm_mulhi_epi16(src16, coeff16);
|
||||
mul_lo = _mm_mullo_epi16(src16, coeff16);
|
||||
// [32] a2 b2 g2 r2
|
||||
t = _mm_unpacklo_epi16(mul_lo, mul_hi);
|
||||
accum2 = _mm_add_epi32(accum2, t);
|
||||
}
|
||||
|
||||
accum0 = _mm_srai_epi32(accum0, ConvolutionFilter1D::kShiftBits);
|
||||
accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits);
|
||||
accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits);
|
||||
// [16] a1 b1 g1 r1 a0 b0 g0 r0
|
||||
accum0 = _mm_packs_epi32(accum0, accum1);
|
||||
// [16] a3 b3 g3 r3 a2 b2 g2 r2
|
||||
accum2 = _mm_packs_epi32(accum2, zero);
|
||||
// [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
|
||||
accum0 = _mm_packus_epi16(accum0, accum2);
|
||||
if (has_alpha) {
|
||||
// [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
|
||||
__m128i a = _mm_srli_epi32(accum0, 8);
|
||||
// [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
|
||||
__m128i b = _mm_max_epu8(a, accum0); // Max of r and g.
|
||||
// [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
|
||||
a = _mm_srli_epi32(accum0, 16);
|
||||
// [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
|
||||
b = _mm_max_epu8(a, b); // Max of r and g and b.
|
||||
// [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
|
||||
b = _mm_slli_epi32(b, 24);
|
||||
accum0 = _mm_max_epu8(b, accum0);
|
||||
} else {
|
||||
__m128i mask = _mm_set1_epi32(0xff000000);
|
||||
accum0 = _mm_or_si128(accum0, mask);
|
||||
}
|
||||
|
||||
for (int out_x = width; out_x < pixel_width; out_x++) {
|
||||
*(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum0);
|
||||
accum0 = _mm_srli_si128(accum0, 4);
|
||||
out_row += 4;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ConvolutionFilter1D ---------------------------------------------------------
|
||||
|
||||
ConvolutionFilter1D::ConvolutionFilter1D()
|
||||
: max_filter_(0) {
|
||||
}
|
||||
|
||||
ConvolutionFilter1D::~ConvolutionFilter1D() {
|
||||
}
|
||||
|
||||
void ConvolutionFilter1D::AddFilter(int filter_offset,
|
||||
const float* filter_values,
|
||||
int filter_length) {
|
||||
SkASSERT(filter_length > 0);
|
||||
|
||||
std::vector<Fixed> fixed_values;
|
||||
fixed_values.reserve(filter_length);
|
||||
|
||||
for (int i = 0; i < filter_length; ++i)
|
||||
fixed_values.push_back(FloatToFixed(filter_values[i]));
|
||||
|
||||
AddFilter(filter_offset, &fixed_values[0], filter_length);
|
||||
}
|
||||
|
||||
void ConvolutionFilter1D::AddFilter(int filter_offset,
|
||||
const Fixed* filter_values,
|
||||
int filter_length) {
|
||||
// It is common for leading/trailing filter values to be zeros. In such
|
||||
// cases it is beneficial to only store the central factors.
|
||||
// For a scaling to 1/4th in each dimension using a Lanczos-2 filter on
|
||||
// a 1080p image this optimization gives a ~10% speed improvement.
|
||||
int first_non_zero = 0;
|
||||
while (first_non_zero < filter_length && filter_values[first_non_zero] == 0)
|
||||
first_non_zero++;
|
||||
|
||||
if (first_non_zero < filter_length) {
|
||||
// Here we have at least one non-zero factor.
|
||||
int last_non_zero = filter_length - 1;
|
||||
while (last_non_zero >= 0 && filter_values[last_non_zero] == 0)
|
||||
last_non_zero--;
|
||||
|
||||
filter_offset += first_non_zero;
|
||||
filter_length = last_non_zero + 1 - first_non_zero;
|
||||
SkASSERT(filter_length > 0);
|
||||
|
||||
for (int i = first_non_zero; i <= last_non_zero; i++)
|
||||
filter_values_.push_back(filter_values[i]);
|
||||
} else {
|
||||
// Here all the factors were zeroes.
|
||||
filter_length = 0;
|
||||
}
|
||||
|
||||
FilterInstance instance;
|
||||
|
||||
// We pushed filter_length elements onto filter_values_
|
||||
instance.data_location = (static_cast<int>(filter_values_.size()) -
|
||||
filter_length);
|
||||
instance.offset = filter_offset;
|
||||
instance.length = filter_length;
|
||||
filters_.push_back(instance);
|
||||
|
||||
max_filter_ = NS_MAX(max_filter_, filter_length);
|
||||
}
|
||||
|
||||
void BGRAConvolve2D(const unsigned char* source_data,
|
||||
int source_byte_row_stride,
|
||||
bool source_has_alpha,
|
||||
const ConvolutionFilter1D& filter_x,
|
||||
const ConvolutionFilter1D& filter_y,
|
||||
int output_byte_row_stride,
|
||||
unsigned char* output,
|
||||
bool use_sse2) {
|
||||
#if !defined(SIMD_SSE2)
|
||||
// Even we have runtime support for SSE2 instructions, since the binary
|
||||
// was not built with SSE2 support, we had to fallback to C version.
|
||||
use_sse2 = false;
|
||||
#endif
|
||||
|
||||
int max_y_filter_size = filter_y.max_filter();
|
||||
|
||||
// The next row in the input that we will generate a horizontally
|
||||
// convolved row for. If the filter doesn't start at the beginning of the
|
||||
// image (this is the case when we are only resizing a subset), then we
|
||||
// don't want to generate any output rows before that. Compute the starting
|
||||
// row for convolution as the first pixel for the first vertical filter.
|
||||
int filter_offset, filter_length;
|
||||
const ConvolutionFilter1D::Fixed* filter_values =
|
||||
filter_y.FilterForValue(0, &filter_offset, &filter_length);
|
||||
int next_x_row = filter_offset;
|
||||
|
||||
// We loop over each row in the input doing a horizontal convolution. This
|
||||
// will result in a horizontally convolved image. We write the results into
|
||||
// a circular buffer of convolved rows and do vertical convolution as rows
|
||||
// are available. This prevents us from having to store the entire
|
||||
// intermediate image and helps cache coherency.
|
||||
// We will need four extra rows to allow horizontal convolution could be done
|
||||
// simultaneously. We also padding each row in row buffer to be aligned-up to
|
||||
// 16 bytes.
|
||||
// TODO(jiesun): We do not use aligned load from row buffer in vertical
|
||||
// convolution pass yet. Somehow Windows does not like it.
|
||||
int row_buffer_width = (filter_x.num_values() + 15) & ~0xF;
|
||||
int row_buffer_height = max_y_filter_size + (use_sse2 ? 4 : 0);
|
||||
CircularRowBuffer row_buffer(row_buffer_width,
|
||||
row_buffer_height,
|
||||
filter_offset);
|
||||
|
||||
// Loop over every possible output row, processing just enough horizontal
|
||||
// convolutions to run each subsequent vertical convolution.
|
||||
SkASSERT(output_byte_row_stride >= filter_x.num_values() * 4);
|
||||
int num_output_rows = filter_y.num_values();
|
||||
|
||||
// We need to check which is the last line to convolve before we advance 4
|
||||
// lines in one iteration.
|
||||
int last_filter_offset, last_filter_length;
|
||||
filter_y.FilterForValue(num_output_rows - 1, &last_filter_offset,
|
||||
&last_filter_length);
|
||||
|
||||
for (int out_y = 0; out_y < num_output_rows; out_y++) {
|
||||
filter_values = filter_y.FilterForValue(out_y,
|
||||
&filter_offset, &filter_length);
|
||||
|
||||
// Generate output rows until we have enough to run the current filter.
|
||||
if (use_sse2) {
|
||||
while (next_x_row < filter_offset + filter_length) {
|
||||
if (next_x_row + 3 < last_filter_offset + last_filter_length - 1) {
|
||||
const unsigned char* src[4];
|
||||
unsigned char* out_row[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
src[i] = &source_data[(next_x_row + i) * source_byte_row_stride];
|
||||
out_row[i] = row_buffer.AdvanceRow();
|
||||
}
|
||||
ConvolveHorizontally4_SSE2(src, filter_x, out_row);
|
||||
next_x_row += 4;
|
||||
} else {
|
||||
// For the last row, SSE2 load possibly to access data beyond the
|
||||
// image area. therefore we use C version here.
|
||||
if (next_x_row == last_filter_offset + last_filter_length - 1) {
|
||||
if (source_has_alpha) {
|
||||
ConvolveHorizontally<true>(
|
||||
&source_data[next_x_row * source_byte_row_stride],
|
||||
filter_x, row_buffer.AdvanceRow());
|
||||
} else {
|
||||
ConvolveHorizontally<false>(
|
||||
&source_data[next_x_row * source_byte_row_stride],
|
||||
filter_x, row_buffer.AdvanceRow());
|
||||
}
|
||||
} else {
|
||||
ConvolveHorizontally_SSE2(
|
||||
&source_data[next_x_row * source_byte_row_stride],
|
||||
filter_x, row_buffer.AdvanceRow());
|
||||
}
|
||||
next_x_row++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (next_x_row < filter_offset + filter_length) {
|
||||
if (source_has_alpha) {
|
||||
ConvolveHorizontally<true>(
|
||||
&source_data[next_x_row * source_byte_row_stride],
|
||||
filter_x, row_buffer.AdvanceRow());
|
||||
} else {
|
||||
ConvolveHorizontally<false>(
|
||||
&source_data[next_x_row * source_byte_row_stride],
|
||||
filter_x, row_buffer.AdvanceRow());
|
||||
}
|
||||
next_x_row++;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute where in the output image this row of final data will go.
|
||||
unsigned char* cur_output_row = &output[out_y * output_byte_row_stride];
|
||||
|
||||
// Get the list of rows that the circular buffer has, in order.
|
||||
int first_row_in_circular_buffer;
|
||||
unsigned char* const* rows_to_convolve =
|
||||
row_buffer.GetRowAddresses(&first_row_in_circular_buffer);
|
||||
|
||||
// Now compute the start of the subset of those rows that the filter
|
||||
// needs.
|
||||
unsigned char* const* first_row_for_filter =
|
||||
&rows_to_convolve[filter_offset - first_row_in_circular_buffer];
|
||||
|
||||
if (source_has_alpha) {
|
||||
if (use_sse2) {
|
||||
ConvolveVertically_SSE2<true>(filter_values, filter_length,
|
||||
first_row_for_filter,
|
||||
filter_x.num_values(), cur_output_row);
|
||||
} else {
|
||||
ConvolveVertically<true>(filter_values, filter_length,
|
||||
first_row_for_filter,
|
||||
filter_x.num_values(), cur_output_row);
|
||||
}
|
||||
} else {
|
||||
if (use_sse2) {
|
||||
ConvolveVertically_SSE2<false>(filter_values, filter_length,
|
||||
first_row_for_filter,
|
||||
filter_x.num_values(), cur_output_row);
|
||||
} else {
|
||||
ConvolveVertically<false>(filter_values, filter_length,
|
||||
first_row_for_filter,
|
||||
filter_x.num_values(), cur_output_row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace skia
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SKIA_EXT_CONVOLVER_H_
|
||||
#define SKIA_EXT_CONVOLVER_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "basictypes.h"
|
||||
#include "prtypes.h"
|
||||
#include "cpu.h"
|
||||
#include "skia/SkTypes.h"
|
||||
|
||||
// avoid confusion with Mac OS X's math library (Carbon)
|
||||
#if defined(__APPLE__)
|
||||
#undef FloatToFixed
|
||||
#undef FixedToFloat
|
||||
#endif
|
||||
|
||||
namespace skia {
|
||||
|
||||
// Represents a filter in one dimension. Each output pixel has one entry in this
|
||||
// object for the filter values contributing to it. You build up the filter
|
||||
// list by calling AddFilter for each output pixel (in order).
|
||||
//
|
||||
// We do 2-dimensional convolution by first convolving each row by one
|
||||
// ConvolutionFilter1D, then convolving each column by another one.
|
||||
//
|
||||
// Entries are stored in fixed point, shifted left by kShiftBits.
|
||||
class ConvolutionFilter1D {
|
||||
public:
|
||||
typedef short Fixed;
|
||||
|
||||
// The number of bits that fixed point values are shifted by.
|
||||
enum { kShiftBits = 14 };
|
||||
|
||||
ConvolutionFilter1D();
|
||||
~ConvolutionFilter1D();
|
||||
|
||||
// Convert between floating point and our fixed point representation.
|
||||
static Fixed FloatToFixed(float f) {
|
||||
return static_cast<Fixed>(f * (1 << kShiftBits));
|
||||
}
|
||||
static unsigned char FixedToChar(Fixed x) {
|
||||
return static_cast<unsigned char>(x >> kShiftBits);
|
||||
}
|
||||
static float FixedToFloat(Fixed x) {
|
||||
// The cast relies on Fixed being a short, implying that on
|
||||
// the platforms we care about all (16) bits will fit into
|
||||
// the mantissa of a (32-bit) float.
|
||||
COMPILE_ASSERT(sizeof(Fixed) == 2, fixed_type_should_fit_in_float_mantissa);
|
||||
float raw = static_cast<float>(x);
|
||||
return ldexpf(raw, -kShiftBits);
|
||||
}
|
||||
|
||||
// Returns the maximum pixel span of a filter.
|
||||
int max_filter() const { return max_filter_; }
|
||||
|
||||
// Returns the number of filters in this filter. This is the dimension of the
|
||||
// output image.
|
||||
int num_values() const { return static_cast<int>(filters_.size()); }
|
||||
|
||||
// Appends the given list of scaling values for generating a given output
|
||||
// pixel. |filter_offset| is the distance from the edge of the image to where
|
||||
// the scaling factors start. The scaling factors apply to the source pixels
|
||||
// starting from this position, and going for the next |filter_length| pixels.
|
||||
//
|
||||
// You will probably want to make sure your input is normalized (that is,
|
||||
// all entries in |filter_values| sub to one) to prevent affecting the overall
|
||||
// brighness of the image.
|
||||
//
|
||||
// The filter_length must be > 0.
|
||||
//
|
||||
// This version will automatically convert your input to fixed point.
|
||||
void AddFilter(int filter_offset,
|
||||
const float* filter_values,
|
||||
int filter_length);
|
||||
|
||||
// Same as the above version, but the input is already fixed point.
|
||||
void AddFilter(int filter_offset,
|
||||
const Fixed* filter_values,
|
||||
int filter_length);
|
||||
|
||||
// Retrieves a filter for the given |value_offset|, a position in the output
|
||||
// image in the direction we're convolving. The offset and length of the
|
||||
// filter values are put into the corresponding out arguments (see AddFilter
|
||||
// above for what these mean), and a pointer to the first scaling factor is
|
||||
// returned. There will be |filter_length| values in this array.
|
||||
inline const Fixed* FilterForValue(int value_offset,
|
||||
int* filter_offset,
|
||||
int* filter_length) const {
|
||||
const FilterInstance& filter = filters_[value_offset];
|
||||
*filter_offset = filter.offset;
|
||||
*filter_length = filter.length;
|
||||
if (filter.length == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return &filter_values_[filter.data_location];
|
||||
}
|
||||
|
||||
|
||||
inline void PaddingForSIMD(int padding_count) {
|
||||
// Padding |padding_count| of more dummy coefficients after the coefficients
|
||||
// of last filter to prevent SIMD instructions which load 8 or 16 bytes
|
||||
// together to access invalid memory areas. We are not trying to align the
|
||||
// coefficients right now due to the opaqueness of <vector> implementation.
|
||||
// This has to be done after all |AddFilter| calls.
|
||||
for (int i = 0; i < padding_count; ++i)
|
||||
filter_values_.push_back(static_cast<Fixed>(0));
|
||||
}
|
||||
|
||||
private:
|
||||
struct FilterInstance {
|
||||
// Offset within filter_values for this instance of the filter.
|
||||
int data_location;
|
||||
|
||||
// Distance from the left of the filter to the center. IN PIXELS
|
||||
int offset;
|
||||
|
||||
// Number of values in this filter instance.
|
||||
int length;
|
||||
};
|
||||
|
||||
// Stores the information for each filter added to this class.
|
||||
std::vector<FilterInstance> filters_;
|
||||
|
||||
// We store all the filter values in this flat list, indexed by
|
||||
// |FilterInstance.data_location| to avoid the mallocs required for storing
|
||||
// each one separately.
|
||||
std::vector<Fixed> filter_values_;
|
||||
|
||||
// The maximum size of any filter we've added.
|
||||
int max_filter_;
|
||||
};
|
||||
|
||||
// Does a two-dimensional convolution on the given source image.
|
||||
//
|
||||
// It is assumed the source pixel offsets referenced in the input filters
|
||||
// reference only valid pixels, so the source image size is not required. Each
|
||||
// row of the source image starts |source_byte_row_stride| after the previous
|
||||
// one (this allows you to have rows with some padding at the end).
|
||||
//
|
||||
// The result will be put into the given output buffer. The destination image
|
||||
// size will be xfilter.num_values() * yfilter.num_values() pixels. It will be
|
||||
// in rows of exactly xfilter.num_values() * 4 bytes.
|
||||
//
|
||||
// |source_has_alpha| is a hint that allows us to avoid doing computations on
|
||||
// the alpha channel if the image is opaque. If you don't know, set this to
|
||||
// true and it will work properly, but setting this to false will be a few
|
||||
// percent faster if you know the image is opaque.
|
||||
//
|
||||
// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order
|
||||
// (this is ARGB when loaded into 32-bit words on a little-endian machine).
|
||||
void BGRAConvolve2D(const unsigned char* source_data,
|
||||
int source_byte_row_stride,
|
||||
bool source_has_alpha,
|
||||
const ConvolutionFilter1D& xfilter,
|
||||
const ConvolutionFilter1D& yfilter,
|
||||
int output_byte_row_stride,
|
||||
unsigned char* output,
|
||||
bool use_sse2);
|
||||
} // namespace skia
|
||||
|
||||
#endif // SKIA_EXT_CONVOLVER_H_
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_CPU_H_
|
||||
#define BASE_CPU_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace base {
|
||||
|
||||
// Query information about the processor.
|
||||
class CPU {
|
||||
public:
|
||||
// Constructor
|
||||
CPU();
|
||||
|
||||
// Accessors for CPU information.
|
||||
const std::string& vendor_name() const { return cpu_vendor_; }
|
||||
int stepping() const { return stepping_; }
|
||||
int model() const { return model_; }
|
||||
int family() const { return family_; }
|
||||
int type() const { return type_; }
|
||||
int extended_model() const { return ext_model_; }
|
||||
int extended_family() const { return ext_family_; }
|
||||
|
||||
private:
|
||||
// Query the processor for CPUID information.
|
||||
void Initialize();
|
||||
|
||||
int type_; // process type
|
||||
int family_; // family of the processor
|
||||
int model_; // model of processor
|
||||
int stepping_; // processor revision number
|
||||
int ext_model_;
|
||||
int ext_family_;
|
||||
std::string cpu_vendor_;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_CPU_H_
|
|
@ -0,0 +1,536 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "basictypes.h"
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "image_operations.h"
|
||||
|
||||
#include "nsAlgorithm.h"
|
||||
#include "stack_container.h"
|
||||
#include "convolver.h"
|
||||
#include "skia/SkColorPriv.h"
|
||||
#include "skia/SkBitmap.h"
|
||||
#include "skia/SkRect.h"
|
||||
#include "skia/SkFontHost.h"
|
||||
|
||||
namespace skia {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns the ceiling/floor as an integer.
|
||||
inline int CeilInt(float val) {
|
||||
return static_cast<int>(ceil(val));
|
||||
}
|
||||
inline int FloorInt(float val) {
|
||||
return static_cast<int>(floor(val));
|
||||
}
|
||||
|
||||
// Filter function computation -------------------------------------------------
|
||||
|
||||
// Evaluates the box filter, which goes from -0.5 to +0.5.
|
||||
float EvalBox(float x) {
|
||||
return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
// Evaluates the Lanczos filter of the given filter size window for the given
|
||||
// position.
|
||||
//
|
||||
// |filter_size| is the width of the filter (the "window"), outside of which
|
||||
// the value of the function is 0. Inside of the window, the value is the
|
||||
// normalized sinc function:
|
||||
// lanczos(x) = sinc(x) * sinc(x / filter_size);
|
||||
// where
|
||||
// sinc(x) = sin(pi*x) / (pi*x);
|
||||
float EvalLanczos(int filter_size, float x) {
|
||||
if (x <= -filter_size || x >= filter_size)
|
||||
return 0.0f; // Outside of the window.
|
||||
if (x > -std::numeric_limits<float>::epsilon() &&
|
||||
x < std::numeric_limits<float>::epsilon())
|
||||
return 1.0f; // Special case the discontinuity at the origin.
|
||||
float xpi = x * static_cast<float>(M_PI);
|
||||
return (sin(xpi) / xpi) * // sinc(x)
|
||||
sin(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size)
|
||||
}
|
||||
|
||||
// Evaluates the Hamming filter of the given filter size window for the given
|
||||
// position.
|
||||
//
|
||||
// The filter covers [-filter_size, +filter_size]. Outside of this window
|
||||
// the value of the function is 0. Inside of the window, the value is sinus
|
||||
// cardinal multiplied by a recentered Hamming function. The traditional
|
||||
// Hamming formula for a window of size N and n ranging in [0, N-1] is:
|
||||
// hamming(n) = 0.54 - 0.46 * cos(2 * pi * n / (N-1)))
|
||||
// In our case we want the function centered for x == 0 and at its minimum
|
||||
// on both ends of the window (x == +/- filter_size), hence the adjusted
|
||||
// formula:
|
||||
// hamming(x) = (0.54 -
|
||||
// 0.46 * cos(2 * pi * (x - filter_size)/ (2 * filter_size)))
|
||||
// = 0.54 - 0.46 * cos(pi * x / filter_size - pi)
|
||||
// = 0.54 + 0.46 * cos(pi * x / filter_size)
|
||||
float EvalHamming(int filter_size, float x) {
|
||||
if (x <= -filter_size || x >= filter_size)
|
||||
return 0.0f; // Outside of the window.
|
||||
if (x > -std::numeric_limits<float>::epsilon() &&
|
||||
x < std::numeric_limits<float>::epsilon())
|
||||
return 1.0f; // Special case the sinc discontinuity at the origin.
|
||||
const float xpi = x * static_cast<float>(M_PI);
|
||||
|
||||
return ((sin(xpi) / xpi) * // sinc(x)
|
||||
(0.54f + 0.46f * cos(xpi / filter_size))); // hamming(x)
|
||||
}
|
||||
|
||||
// ResizeFilter ----------------------------------------------------------------
|
||||
|
||||
// Encapsulates computation and storage of the filters required for one complete
|
||||
// resize operation.
|
||||
class ResizeFilter {
|
||||
public:
|
||||
ResizeFilter(ImageOperations::ResizeMethod method,
|
||||
int src_full_width, int src_full_height,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset);
|
||||
|
||||
// Returns the filled filter values.
|
||||
const ConvolutionFilter1D& x_filter() { return x_filter_; }
|
||||
const ConvolutionFilter1D& y_filter() { return y_filter_; }
|
||||
|
||||
private:
|
||||
// Returns the number of pixels that the filer spans, in filter space (the
|
||||
// destination image).
|
||||
float GetFilterSupport(float scale) {
|
||||
switch (method_) {
|
||||
case ImageOperations::RESIZE_BOX:
|
||||
// The box filter just scales with the image scaling.
|
||||
return 0.5f; // Only want one side of the filter = /2.
|
||||
case ImageOperations::RESIZE_HAMMING1:
|
||||
// The Hamming filter takes as much space in the source image in
|
||||
// each direction as the size of the window = 1 for Hamming1.
|
||||
return 1.0f;
|
||||
case ImageOperations::RESIZE_LANCZOS2:
|
||||
// The Lanczos filter takes as much space in the source image in
|
||||
// each direction as the size of the window = 2 for Lanczos2.
|
||||
return 2.0f;
|
||||
case ImageOperations::RESIZE_LANCZOS3:
|
||||
// The Lanczos filter takes as much space in the source image in
|
||||
// each direction as the size of the window = 3 for Lanczos3.
|
||||
return 3.0f;
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Computes one set of filters either horizontally or vertically. The caller
|
||||
// will specify the "min" and "max" rather than the bottom/top and
|
||||
// right/bottom so that the same code can be re-used in each dimension.
|
||||
//
|
||||
// |src_depend_lo| and |src_depend_size| gives the range for the source
|
||||
// depend rectangle (horizontally or vertically at the caller's discretion
|
||||
// -- see above for what this means).
|
||||
//
|
||||
// Likewise, the range of destination values to compute and the scale factor
|
||||
// for the transform is also specified.
|
||||
void ComputeFilters(int src_size,
|
||||
int dest_subset_lo, int dest_subset_size,
|
||||
float scale, float src_support,
|
||||
ConvolutionFilter1D* output);
|
||||
|
||||
// Computes the filter value given the coordinate in filter space.
|
||||
inline float ComputeFilter(float pos) {
|
||||
switch (method_) {
|
||||
case ImageOperations::RESIZE_BOX:
|
||||
return EvalBox(pos);
|
||||
case ImageOperations::RESIZE_HAMMING1:
|
||||
return EvalHamming(1, pos);
|
||||
case ImageOperations::RESIZE_LANCZOS2:
|
||||
return EvalLanczos(2, pos);
|
||||
case ImageOperations::RESIZE_LANCZOS3:
|
||||
return EvalLanczos(3, pos);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ImageOperations::ResizeMethod method_;
|
||||
|
||||
// Size of the filter support on one side only in the destination space.
|
||||
// See GetFilterSupport.
|
||||
float x_filter_support_;
|
||||
float y_filter_support_;
|
||||
|
||||
// Subset of scaled destination bitmap to compute.
|
||||
SkIRect out_bounds_;
|
||||
|
||||
ConvolutionFilter1D x_filter_;
|
||||
ConvolutionFilter1D y_filter_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ResizeFilter);
|
||||
};
|
||||
|
||||
ResizeFilter::ResizeFilter(ImageOperations::ResizeMethod method,
|
||||
int src_full_width, int src_full_height,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset)
|
||||
: method_(method),
|
||||
out_bounds_(dest_subset) {
|
||||
// method_ will only ever refer to an "algorithm method".
|
||||
SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
|
||||
(method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD));
|
||||
|
||||
float scale_x = static_cast<float>(dest_width) /
|
||||
static_cast<float>(src_full_width);
|
||||
float scale_y = static_cast<float>(dest_height) /
|
||||
static_cast<float>(src_full_height);
|
||||
|
||||
x_filter_support_ = GetFilterSupport(scale_x);
|
||||
y_filter_support_ = GetFilterSupport(scale_y);
|
||||
|
||||
// Support of the filter in source space.
|
||||
float src_x_support = x_filter_support_ / scale_x;
|
||||
float src_y_support = y_filter_support_ / scale_y;
|
||||
|
||||
ComputeFilters(src_full_width, dest_subset.fLeft, dest_subset.width(),
|
||||
scale_x, src_x_support, &x_filter_);
|
||||
ComputeFilters(src_full_height, dest_subset.fTop, dest_subset.height(),
|
||||
scale_y, src_y_support, &y_filter_);
|
||||
}
|
||||
|
||||
// TODO(egouriou): Take advantage of periods in the convolution.
|
||||
// Practical resizing filters are periodic outside of the border area.
|
||||
// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the
|
||||
// source become p pixels in the destination) will have a period of p.
|
||||
// A nice consequence is a period of 1 when downscaling by an integral
|
||||
// factor. Downscaling from typical display resolutions is also bound
|
||||
// to produce interesting periods as those are chosen to have multiple
|
||||
// small factors.
|
||||
// Small periods reduce computational load and improve cache usage if
|
||||
// the coefficients can be shared. For periods of 1 we can consider
|
||||
// loading the factors only once outside the borders.
|
||||
void ResizeFilter::ComputeFilters(int src_size,
|
||||
int dest_subset_lo, int dest_subset_size,
|
||||
float scale, float src_support,
|
||||
ConvolutionFilter1D* output) {
|
||||
int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi)
|
||||
|
||||
// When we're doing a magnification, the scale will be larger than one. This
|
||||
// means the destination pixels are much smaller than the source pixels, and
|
||||
// that the range covered by the filter won't necessarily cover any source
|
||||
// pixel boundaries. Therefore, we use these clamped values (max of 1) for
|
||||
// some computations.
|
||||
float clamped_scale = NS_MIN(1.0f, scale);
|
||||
|
||||
// Speed up the divisions below by turning them into multiplies.
|
||||
float inv_scale = 1.0f / scale;
|
||||
|
||||
StackVector<float, 64> filter_values;
|
||||
StackVector<int16_t, 64> fixed_filter_values;
|
||||
|
||||
// Loop over all pixels in the output range. We will generate one set of
|
||||
// filter values for each one. Those values will tell us how to blend the
|
||||
// source pixels to compute the destination pixel.
|
||||
for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi;
|
||||
dest_subset_i++) {
|
||||
// Reset the arrays. We don't declare them inside so they can re-use the
|
||||
// same malloc-ed buffer.
|
||||
filter_values->clear();
|
||||
fixed_filter_values->clear();
|
||||
|
||||
// This is the pixel in the source directly under the pixel in the dest.
|
||||
// Note that we base computations on the "center" of the pixels. To see
|
||||
// why, observe that the destination pixel at coordinates (0, 0) in a 5.0x
|
||||
// downscale should "cover" the pixels around the pixel with *its center*
|
||||
// at coordinates (2.5, 2.5) in the source, not those around (0, 0).
|
||||
// Hence we need to scale coordinates (0.5, 0.5), not (0, 0).
|
||||
// TODO(evannier): this code is therefore incorrect and should read:
|
||||
// float src_pixel = (static_cast<float>(dest_subset_i) + 0.5f) * inv_scale;
|
||||
// I leave it incorrect, because changing it would require modifying
|
||||
// the results for the webkit test, which I will do in a subsequent checkin.
|
||||
float src_pixel = dest_subset_i * inv_scale;
|
||||
|
||||
// Compute the (inclusive) range of source pixels the filter covers.
|
||||
int src_begin = NS_MAX(0, FloorInt(src_pixel - src_support));
|
||||
int src_end = NS_MIN(src_size - 1, CeilInt(src_pixel + src_support));
|
||||
|
||||
// Compute the unnormalized filter value at each location of the source
|
||||
// it covers.
|
||||
float filter_sum = 0.0f; // Sub of the filter values for normalizing.
|
||||
for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end;
|
||||
cur_filter_pixel++) {
|
||||
// Distance from the center of the filter, this is the filter coordinate
|
||||
// in source space. We also need to consider the center of the pixel
|
||||
// when comparing distance against 'src_pixel'. In the 5x downscale
|
||||
// example used above the distance from the center of the filter to
|
||||
// the pixel with coordinates (2, 2) should be 0, because its center
|
||||
// is at (2.5, 2.5).
|
||||
// TODO(evannier): as above (in regards to the 0.5 pixel error),
|
||||
// this code is incorrect, but is left it for the same reasons.
|
||||
// float src_filter_dist =
|
||||
// ((static_cast<float>(cur_filter_pixel) + 0.5f) - src_pixel);
|
||||
float src_filter_dist = cur_filter_pixel - src_pixel;
|
||||
|
||||
// Since the filter really exists in dest space, map it there.
|
||||
float dest_filter_dist = src_filter_dist * clamped_scale;
|
||||
|
||||
// Compute the filter value at that location.
|
||||
float filter_value = ComputeFilter(dest_filter_dist);
|
||||
filter_values->push_back(filter_value);
|
||||
|
||||
filter_sum += filter_value;
|
||||
}
|
||||
|
||||
// The filter must be normalized so that we don't affect the brightness of
|
||||
// the image. Convert to normalized fixed point.
|
||||
int16_t fixed_sum = 0;
|
||||
for (size_t i = 0; i < filter_values->size(); i++) {
|
||||
int16_t cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum);
|
||||
fixed_sum += cur_fixed;
|
||||
fixed_filter_values->push_back(cur_fixed);
|
||||
}
|
||||
|
||||
// The conversion to fixed point will leave some rounding errors, which
|
||||
// we add back in to avoid affecting the brightness of the image. We
|
||||
// arbitrarily add this to the center of the filter array (this won't always
|
||||
// be the center of the filter function since it could get clipped on the
|
||||
// edges, but it doesn't matter enough to worry about that case).
|
||||
int16_t leftovers = output->FloatToFixed(1.0f) - fixed_sum;
|
||||
fixed_filter_values[fixed_filter_values->size() / 2] += leftovers;
|
||||
|
||||
// Now it's ready to go.
|
||||
output->AddFilter(src_begin, &fixed_filter_values[0],
|
||||
static_cast<int>(fixed_filter_values->size()));
|
||||
}
|
||||
|
||||
output->PaddingForSIMD(8);
|
||||
}
|
||||
|
||||
ImageOperations::ResizeMethod ResizeMethodToAlgorithmMethod(
|
||||
ImageOperations::ResizeMethod method) {
|
||||
// Convert any "Quality Method" into an "Algorithm Method"
|
||||
if (method >= ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD &&
|
||||
method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD) {
|
||||
return method;
|
||||
}
|
||||
// The call to ImageOperationsGtv::Resize() above took care of
|
||||
// GPU-acceleration in the cases where it is possible. So now we just
|
||||
// pick the appropriate software method for each resize quality.
|
||||
switch (method) {
|
||||
// Users of RESIZE_GOOD are willing to trade a lot of quality to
|
||||
// get speed, allowing the use of linear resampling to get hardware
|
||||
// acceleration (SRB). Hence any of our "good" software filters
|
||||
// will be acceptable, and we use the fastest one, Hamming-1.
|
||||
case ImageOperations::RESIZE_GOOD:
|
||||
// Users of RESIZE_BETTER are willing to trade some quality in order
|
||||
// to improve performance, but are guaranteed not to devolve to a linear
|
||||
// resampling. In visual tests we see that Hamming-1 is not as good as
|
||||
// Lanczos-2, however it is about 40% faster and Lanczos-2 itself is
|
||||
// about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed
|
||||
// an acceptable trade-off between quality and speed.
|
||||
case ImageOperations::RESIZE_BETTER:
|
||||
return ImageOperations::RESIZE_HAMMING1;
|
||||
default:
|
||||
return ImageOperations::RESIZE_LANCZOS3;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Resize ----------------------------------------------------------------------
|
||||
|
||||
// static
|
||||
SkBitmap ImageOperations::Resize(const SkBitmap& source,
|
||||
ResizeMethod method,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset,
|
||||
void* dest_pixels /* = nullptr */) {
|
||||
if (method == ImageOperations::RESIZE_SUBPIXEL)
|
||||
return ResizeSubpixel(source, dest_width, dest_height, dest_subset);
|
||||
else
|
||||
return ResizeBasic(source, method, dest_width, dest_height, dest_subset,
|
||||
dest_pixels);
|
||||
}
|
||||
|
||||
// static
|
||||
SkBitmap ImageOperations::ResizeSubpixel(const SkBitmap& source,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset) {
|
||||
// Currently only works on Linux/BSD because these are the only platforms
|
||||
// where SkFontHost::GetSubpixelOrder is defined.
|
||||
#if defined(XP_UNIX)
|
||||
// Understand the display.
|
||||
const SkFontHost::LCDOrder order = SkFontHost::GetSubpixelOrder();
|
||||
const SkFontHost::LCDOrientation orientation =
|
||||
SkFontHost::GetSubpixelOrientation();
|
||||
|
||||
// Decide on which dimension, if any, to deploy subpixel rendering.
|
||||
int w = 1;
|
||||
int h = 1;
|
||||
switch (orientation) {
|
||||
case SkFontHost::kHorizontal_LCDOrientation:
|
||||
w = dest_width < source.width() ? 3 : 1;
|
||||
break;
|
||||
case SkFontHost::kVertical_LCDOrientation:
|
||||
h = dest_height < source.height() ? 3 : 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Resize the image.
|
||||
const int width = dest_width * w;
|
||||
const int height = dest_height * h;
|
||||
SkIRect subset = { dest_subset.fLeft, dest_subset.fTop,
|
||||
dest_subset.fLeft + dest_subset.width() * w,
|
||||
dest_subset.fTop + dest_subset.height() * h };
|
||||
SkBitmap img = ResizeBasic(source, ImageOperations::RESIZE_LANCZOS3, width,
|
||||
height, subset);
|
||||
const int row_words = img.rowBytes() / 4;
|
||||
if (w == 1 && h == 1)
|
||||
return img;
|
||||
|
||||
// Render into subpixels.
|
||||
SkBitmap result;
|
||||
result.setConfig(SkBitmap::kARGB_8888_Config, dest_subset.width(),
|
||||
dest_subset.height());
|
||||
result.allocPixels();
|
||||
if (!result.readyToDraw())
|
||||
return img;
|
||||
|
||||
SkAutoLockPixels locker(img);
|
||||
if (!img.readyToDraw())
|
||||
return img;
|
||||
|
||||
uint32_t* src_row = img.getAddr32(0, 0);
|
||||
uint32_t* dst_row = result.getAddr32(0, 0);
|
||||
for (int y = 0; y < dest_subset.height(); y++) {
|
||||
uint32_t* src = src_row;
|
||||
uint32_t* dst = dst_row;
|
||||
for (int x = 0; x < dest_subset.width(); x++, src += w, dst++) {
|
||||
uint8_t r = 0, g = 0, b = 0, a = 0;
|
||||
switch (order) {
|
||||
case SkFontHost::kRGB_LCDOrder:
|
||||
switch (orientation) {
|
||||
case SkFontHost::kHorizontal_LCDOrientation:
|
||||
r = SkGetPackedR32(src[0]);
|
||||
g = SkGetPackedG32(src[1]);
|
||||
b = SkGetPackedB32(src[2]);
|
||||
a = SkGetPackedA32(src[1]);
|
||||
break;
|
||||
case SkFontHost::kVertical_LCDOrientation:
|
||||
r = SkGetPackedR32(src[0 * row_words]);
|
||||
g = SkGetPackedG32(src[1 * row_words]);
|
||||
b = SkGetPackedB32(src[2 * row_words]);
|
||||
a = SkGetPackedA32(src[1 * row_words]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SkFontHost::kBGR_LCDOrder:
|
||||
switch (orientation) {
|
||||
case SkFontHost::kHorizontal_LCDOrientation:
|
||||
b = SkGetPackedB32(src[0]);
|
||||
g = SkGetPackedG32(src[1]);
|
||||
r = SkGetPackedR32(src[2]);
|
||||
a = SkGetPackedA32(src[1]);
|
||||
break;
|
||||
case SkFontHost::kVertical_LCDOrientation:
|
||||
b = SkGetPackedB32(src[0 * row_words]);
|
||||
g = SkGetPackedG32(src[1 * row_words]);
|
||||
r = SkGetPackedR32(src[2 * row_words]);
|
||||
a = SkGetPackedA32(src[1 * row_words]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SkFontHost::kNONE_LCDOrder:
|
||||
break;
|
||||
}
|
||||
// Premultiplied alpha is very fragile.
|
||||
a = a > r ? a : r;
|
||||
a = a > g ? a : g;
|
||||
a = a > b ? a : b;
|
||||
*dst = SkPackARGB32(a, r, g, b);
|
||||
}
|
||||
src_row += h * row_words;
|
||||
dst_row += result.rowBytes() / 4;
|
||||
}
|
||||
result.setIsOpaque(img.isOpaque());
|
||||
return result;
|
||||
#else
|
||||
return SkBitmap();
|
||||
#endif // OS_POSIX && !OS_MACOSX && !defined(OS_ANDROID)
|
||||
}
|
||||
|
||||
// static
|
||||
SkBitmap ImageOperations::ResizeBasic(const SkBitmap& source,
|
||||
ResizeMethod method,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset,
|
||||
void* dest_pixels /* = nullptr */) {
|
||||
// Ensure that the ResizeMethod enumeration is sound.
|
||||
SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) &&
|
||||
(method <= RESIZE_LAST_QUALITY_METHOD)) ||
|
||||
((RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
|
||||
(method <= RESIZE_LAST_ALGORITHM_METHOD)));
|
||||
|
||||
// If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just
|
||||
// return empty.
|
||||
if (source.width() < 1 || source.height() < 1 ||
|
||||
dest_width < 1 || dest_height < 1)
|
||||
return SkBitmap();
|
||||
|
||||
method = ResizeMethodToAlgorithmMethod(method);
|
||||
// Check that we deal with an "algorithm methods" from this point onward.
|
||||
SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
|
||||
(method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD));
|
||||
|
||||
SkAutoLockPixels locker(source);
|
||||
if (!source.readyToDraw())
|
||||
return SkBitmap();
|
||||
|
||||
ResizeFilter filter(method, source.width(), source.height(),
|
||||
dest_width, dest_height, dest_subset);
|
||||
|
||||
// Get a source bitmap encompassing this touched area. We construct the
|
||||
// offsets and row strides such that it looks like a new bitmap, while
|
||||
// referring to the old data.
|
||||
const uint8_t* source_subset =
|
||||
reinterpret_cast<const uint8_t*>(source.getPixels());
|
||||
|
||||
// Convolve into the result.
|
||||
SkBitmap result;
|
||||
result.setConfig(SkBitmap::kARGB_8888_Config,
|
||||
dest_subset.width(), dest_subset.height());
|
||||
|
||||
if (dest_pixels) {
|
||||
result.setPixels(dest_pixels);
|
||||
} else {
|
||||
result.allocPixels();
|
||||
}
|
||||
|
||||
if (!result.readyToDraw())
|
||||
return SkBitmap();
|
||||
|
||||
BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()),
|
||||
!source.isOpaque(), filter.x_filter(), filter.y_filter(),
|
||||
static_cast<int>(result.rowBytes()),
|
||||
static_cast<unsigned char*>(result.getPixels()),
|
||||
/* sse = */ false);
|
||||
|
||||
// Preserve the "opaque" flag for use as an optimization later.
|
||||
result.setIsOpaque(source.isOpaque());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// static
|
||||
SkBitmap ImageOperations::Resize(const SkBitmap& source,
|
||||
ResizeMethod method,
|
||||
int dest_width, int dest_height,
|
||||
void* dest_pixels /* = nullptr */) {
|
||||
SkIRect dest_subset = { 0, 0, dest_width, dest_height };
|
||||
return Resize(source, method, dest_width, dest_height, dest_subset,
|
||||
dest_pixels);
|
||||
}
|
||||
|
||||
} // namespace skia
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SKIA_EXT_IMAGE_OPERATIONS_H_
|
||||
#define SKIA_EXT_IMAGE_OPERATIONS_H_
|
||||
|
||||
#include "skia/SkTypes.h"
|
||||
#include "Types.h"
|
||||
|
||||
class SkBitmap;
|
||||
struct SkIRect;
|
||||
|
||||
namespace skia {
|
||||
|
||||
class ImageOperations {
|
||||
public:
|
||||
enum ResizeMethod {
|
||||
//
|
||||
// Quality Methods
|
||||
//
|
||||
// Those enumeration values express a desired quality/speed tradeoff.
|
||||
// They are translated into an algorithm-specific method that depends
|
||||
// on the capabilities (CPU, GPU) of the underlying platform.
|
||||
// It is possible for all three methods to be mapped to the same
|
||||
// algorithm on a given platform.
|
||||
|
||||
// Good quality resizing. Fastest resizing with acceptable visual quality.
|
||||
// This is typically intended for use during interactive layouts
|
||||
// where slower platforms may want to trade image quality for large
|
||||
// increase in resizing performance.
|
||||
//
|
||||
// For example the resizing implementation may devolve to linear
|
||||
// filtering if this enables GPU acceleration to be used.
|
||||
//
|
||||
// Note that the underlying resizing method may be determined
|
||||
// on the fly based on the parameters for a given resize call.
|
||||
// For example an implementation using a GPU-based linear filter
|
||||
// in the common case may still use a higher-quality software-based
|
||||
// filter in cases where using the GPU would actually be slower - due
|
||||
// to too much latency - or impossible - due to image format or size
|
||||
// constraints.
|
||||
RESIZE_GOOD,
|
||||
|
||||
// Medium quality resizing. Close to high quality resizing (better
|
||||
// than linear interpolation) with potentially some quality being
|
||||
// traded-off for additional speed compared to RESIZE_BEST.
|
||||
//
|
||||
// This is intended, for example, for generation of large thumbnails
|
||||
// (hundreds of pixels in each dimension) from large sources, where
|
||||
// a linear filter would produce too many artifacts but where
|
||||
// a RESIZE_HIGH might be too costly time-wise.
|
||||
RESIZE_BETTER,
|
||||
|
||||
// High quality resizing. The algorithm is picked to favor image quality.
|
||||
RESIZE_BEST,
|
||||
|
||||
//
|
||||
// Algorithm-specific enumerations
|
||||
//
|
||||
|
||||
// Box filter. This is a weighted average of all of the pixels touching
|
||||
// the destination pixel. For enlargement, this is nearest neighbor.
|
||||
//
|
||||
// You probably don't want this, it is here for testing since it is easy to
|
||||
// compute. Use RESIZE_LANCZOS3 instead.
|
||||
RESIZE_BOX,
|
||||
|
||||
// 1-cycle Hamming filter. This is tall is the middle and falls off towards
|
||||
// the window edges but without going to 0. This is about 40% faster than
|
||||
// a 2-cycle Lanczos.
|
||||
RESIZE_HAMMING1,
|
||||
|
||||
// 2-cycle Lanczos filter. This is tall in the middle, goes negative on
|
||||
// each side, then returns to zero. Does not provide as good a frequency
|
||||
// response as a 3-cycle Lanczos but is roughly 30% faster.
|
||||
RESIZE_LANCZOS2,
|
||||
|
||||
// 3-cycle Lanczos filter. This is tall in the middle, goes negative on
|
||||
// each side, then oscillates 2 more times. It gives nice sharp edges.
|
||||
RESIZE_LANCZOS3,
|
||||
|
||||
// Lanczos filter + subpixel interpolation. If subpixel rendering is not
|
||||
// appropriate we automatically fall back to Lanczos.
|
||||
RESIZE_SUBPIXEL,
|
||||
|
||||
// enum aliases for first and last methods by algorithm or by quality.
|
||||
RESIZE_FIRST_QUALITY_METHOD = RESIZE_GOOD,
|
||||
RESIZE_LAST_QUALITY_METHOD = RESIZE_BEST,
|
||||
RESIZE_FIRST_ALGORITHM_METHOD = RESIZE_BOX,
|
||||
RESIZE_LAST_ALGORITHM_METHOD = RESIZE_SUBPIXEL,
|
||||
};
|
||||
|
||||
// Resizes the given source bitmap using the specified resize method, so that
|
||||
// the entire image is (dest_size) big. The dest_subset is the rectangle in
|
||||
// this destination image that should actually be returned.
|
||||
//
|
||||
// The output image will be (dest_subset.width(), dest_subset.height()). This
|
||||
// will save work if you do not need the entire bitmap.
|
||||
//
|
||||
// The destination subset must be smaller than the destination image.
|
||||
static SkBitmap Resize(const SkBitmap& source,
|
||||
ResizeMethod method,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset,
|
||||
void* dest_pixels = nullptr);
|
||||
|
||||
// Alternate version for resizing and returning the entire bitmap rather than
|
||||
// a subset.
|
||||
static SkBitmap Resize(const SkBitmap& source,
|
||||
ResizeMethod method,
|
||||
int dest_width, int dest_height,
|
||||
void* dest_pixels = nullptr);
|
||||
|
||||
private:
|
||||
ImageOperations(); // Class for scoping only.
|
||||
|
||||
// Supports all methods except RESIZE_SUBPIXEL.
|
||||
static SkBitmap ResizeBasic(const SkBitmap& source,
|
||||
ResizeMethod method,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset,
|
||||
void* dest_pixels = nullptr);
|
||||
|
||||
// Subpixel renderer.
|
||||
static SkBitmap ResizeSubpixel(const SkBitmap& source,
|
||||
int dest_width, int dest_height,
|
||||
const SkIRect& dest_subset);
|
||||
};
|
||||
|
||||
} // namespace skia
|
||||
|
||||
#endif // SKIA_EXT_IMAGE_OPERATIONS_H_
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_PORT_H_
|
||||
#define BASE_PORT_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "build/build_config.h"
|
||||
|
||||
#ifdef COMPILER_MSVC
|
||||
#define GG_LONGLONG(x) x##I64
|
||||
#define GG_ULONGLONG(x) x##UI64
|
||||
#else
|
||||
#define GG_LONGLONG(x) x##LL
|
||||
#define GG_ULONGLONG(x) x##ULL
|
||||
#endif
|
||||
|
||||
// Per C99 7.8.14, define __STDC_CONSTANT_MACROS before including <stdint.h>
|
||||
// to get the INTn_C and UINTn_C macros for integer constants. It's difficult
|
||||
// to guarantee any specific ordering of header includes, so it's difficult to
|
||||
// guarantee that the INTn_C macros can be defined by including <stdint.h> at
|
||||
// any specific point. Provide GG_INTn_C macros instead.
|
||||
|
||||
#define GG_INT8_C(x) (x)
|
||||
#define GG_INT16_C(x) (x)
|
||||
#define GG_INT32_C(x) (x)
|
||||
#define GG_INT64_C(x) GG_LONGLONG(x)
|
||||
|
||||
#define GG_UINT8_C(x) (x ## U)
|
||||
#define GG_UINT16_C(x) (x ## U)
|
||||
#define GG_UINT32_C(x) (x ## U)
|
||||
#define GG_UINT64_C(x) GG_ULONGLONG(x)
|
||||
|
||||
namespace base {
|
||||
|
||||
// It's possible for functions that use a va_list, such as StringPrintf, to
|
||||
// invalidate the data in it upon use. The fix is to make a copy of the
|
||||
// structure before using it and use that copy instead. va_copy is provided
|
||||
// for this purpose. MSVC does not provide va_copy, so define an
|
||||
// implementation here. It is not guaranteed that assignment is a copy, so the
|
||||
// StringUtil.VariableArgsFunc unit test tests this capability.
|
||||
|
||||
// The C standard says that va_copy is a "macro", not a function. Trying to
|
||||
// use va_list as ref args to a function, as above, breaks some machines.
|
||||
# if defined(COMPILER_GCC)
|
||||
# define base_va_copy(_a, _b) ::va_copy(_a, _b)
|
||||
# elif defined(COMPILER_MSVC)
|
||||
# define base_va_copy(_a, _b) (_a = _b)
|
||||
# else
|
||||
# error No va_copy for your compiler
|
||||
# endif
|
||||
|
||||
} // namespace base
|
||||
|
||||
// Define an OS-neutral wrapper for shared library entry points
|
||||
#if defined(OS_WIN)
|
||||
#define API_CALL __stdcall
|
||||
#elif defined(OS_LINUX) || defined(OS_MACOSX)
|
||||
#define API_CALL
|
||||
#endif
|
||||
|
||||
#endif // BASE_PORT_H_
|
|
@ -0,0 +1,253 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_STACK_CONTAINER_H_
|
||||
#define BASE_STACK_CONTAINER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "basictypes.h"
|
||||
|
||||
// This allocator can be used with STL containers to provide a stack buffer
|
||||
// from which to allocate memory and overflows onto the heap. This stack buffer
|
||||
// would be allocated on the stack and allows us to avoid heap operations in
|
||||
// some situations.
|
||||
//
|
||||
// STL likes to make copies of allocators, so the allocator itself can't hold
|
||||
// the data. Instead, we make the creator responsible for creating a
|
||||
// StackAllocator::Source which contains the data. Copying the allocator
|
||||
// merely copies the pointer to this shared source, so all allocators created
|
||||
// based on our allocator will share the same stack buffer.
|
||||
//
|
||||
// This stack buffer implementation is very simple. The first allocation that
|
||||
// fits in the stack buffer will use the stack buffer. Any subsequent
|
||||
// allocations will not use the stack buffer, even if there is unused room.
|
||||
// This makes it appropriate for array-like containers, but the caller should
|
||||
// be sure to reserve() in the container up to the stack buffer size. Otherwise
|
||||
// the container will allocate a small array which will "use up" the stack
|
||||
// buffer.
|
||||
template<typename T, size_t stack_capacity>
|
||||
class StackAllocator : public std::allocator<T> {
|
||||
public:
|
||||
typedef typename std::allocator<T>::pointer pointer;
|
||||
typedef typename std::allocator<T>::size_type size_type;
|
||||
|
||||
// Backing store for the allocator. The container owner is responsible for
|
||||
// maintaining this for as long as any containers using this allocator are
|
||||
// live.
|
||||
struct Source {
|
||||
Source() : used_stack_buffer_(false) {
|
||||
}
|
||||
|
||||
// Casts the buffer in its right type.
|
||||
T* stack_buffer() { return reinterpret_cast<T*>(stack_buffer_); }
|
||||
const T* stack_buffer() const {
|
||||
return reinterpret_cast<const T*>(stack_buffer_);
|
||||
}
|
||||
|
||||
//
|
||||
// IMPORTANT: Take care to ensure that stack_buffer_ is aligned
|
||||
// since it is used to mimic an array of T.
|
||||
// Be careful while declaring any unaligned types (like bool)
|
||||
// before stack_buffer_.
|
||||
//
|
||||
|
||||
// The buffer itself. It is not of type T because we don't want the
|
||||
// constructors and destructors to be automatically called. Define a POD
|
||||
// buffer of the right size instead.
|
||||
char stack_buffer_[sizeof(T[stack_capacity])];
|
||||
|
||||
// Set when the stack buffer is used for an allocation. We do not track
|
||||
// how much of the buffer is used, only that somebody is using it.
|
||||
bool used_stack_buffer_;
|
||||
};
|
||||
|
||||
// Used by containers when they want to refer to an allocator of type U.
|
||||
template<typename U>
|
||||
struct rebind {
|
||||
typedef StackAllocator<U, stack_capacity> other;
|
||||
};
|
||||
|
||||
// For the straight up copy c-tor, we can share storage.
|
||||
StackAllocator(const StackAllocator<T, stack_capacity>& rhs)
|
||||
: source_(rhs.source_) {
|
||||
}
|
||||
|
||||
// ISO C++ requires the following constructor to be defined,
|
||||
// and std::vector in VC++2008SP1 Release fails with an error
|
||||
// in the class _Container_base_aux_alloc_real (from <xutility>)
|
||||
// if the constructor does not exist.
|
||||
// For this constructor, we cannot share storage; there's
|
||||
// no guarantee that the Source buffer of Ts is large enough
|
||||
// for Us.
|
||||
// TODO: If we were fancy pants, perhaps we could share storage
|
||||
// iff sizeof(T) == sizeof(U).
|
||||
template<typename U, size_t other_capacity>
|
||||
StackAllocator(const StackAllocator<U, other_capacity>& other)
|
||||
: source_(NULL) {
|
||||
}
|
||||
|
||||
explicit StackAllocator(Source* source) : source_(source) {
|
||||
}
|
||||
|
||||
// Actually do the allocation. Use the stack buffer if nobody has used it yet
|
||||
// and the size requested fits. Otherwise, fall through to the standard
|
||||
// allocator.
|
||||
pointer allocate(size_type n, void* hint = 0) {
|
||||
if (source_ != NULL && !source_->used_stack_buffer_
|
||||
&& n <= stack_capacity) {
|
||||
source_->used_stack_buffer_ = true;
|
||||
return source_->stack_buffer();
|
||||
} else {
|
||||
return std::allocator<T>::allocate(n, hint);
|
||||
}
|
||||
}
|
||||
|
||||
// Free: when trying to free the stack buffer, just mark it as free. For
|
||||
// non-stack-buffer pointers, just fall though to the standard allocator.
|
||||
void deallocate(pointer p, size_type n) {
|
||||
if (source_ != NULL && p == source_->stack_buffer())
|
||||
source_->used_stack_buffer_ = false;
|
||||
else
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
private:
|
||||
Source* source_;
|
||||
};
|
||||
|
||||
// A wrapper around STL containers that maintains a stack-sized buffer that the
|
||||
// initial capacity of the vector is based on. Growing the container beyond the
|
||||
// stack capacity will transparently overflow onto the heap. The container must
|
||||
// support reserve().
|
||||
//
|
||||
// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this
|
||||
// type. This object is really intended to be used only internally. You'll want
|
||||
// to use the wrappers below for different types.
|
||||
template<typename TContainerType, int stack_capacity>
|
||||
class StackContainer {
|
||||
public:
|
||||
typedef TContainerType ContainerType;
|
||||
typedef typename ContainerType::value_type ContainedType;
|
||||
typedef StackAllocator<ContainedType, stack_capacity> Allocator;
|
||||
|
||||
// Allocator must be constructed before the container!
|
||||
StackContainer() : allocator_(&stack_data_), container_(allocator_) {
|
||||
// Make the container use the stack allocation by reserving our buffer size
|
||||
// before doing anything else.
|
||||
container_.reserve(stack_capacity);
|
||||
}
|
||||
|
||||
// Getters for the actual container.
|
||||
//
|
||||
// Danger: any copies of this made using the copy constructor must have
|
||||
// shorter lifetimes than the source. The copy will share the same allocator
|
||||
// and therefore the same stack buffer as the original. Use std::copy to
|
||||
// copy into a "real" container for longer-lived objects.
|
||||
ContainerType& container() { return container_; }
|
||||
const ContainerType& container() const { return container_; }
|
||||
|
||||
// Support operator-> to get to the container. This allows nicer syntax like:
|
||||
// StackContainer<...> foo;
|
||||
// std::sort(foo->begin(), foo->end());
|
||||
ContainerType* operator->() { return &container_; }
|
||||
const ContainerType* operator->() const { return &container_; }
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
// Retrieves the stack source so that that unit tests can verify that the
|
||||
// buffer is being used properly.
|
||||
const typename Allocator::Source& stack_data() const {
|
||||
return stack_data_;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
typename Allocator::Source stack_data_;
|
||||
Allocator allocator_;
|
||||
ContainerType container_;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(StackContainer);
|
||||
};
|
||||
|
||||
// StackString
|
||||
template<size_t stack_capacity>
|
||||
class StackString : public StackContainer<
|
||||
std::basic_string<char,
|
||||
std::char_traits<char>,
|
||||
StackAllocator<char, stack_capacity> >,
|
||||
stack_capacity> {
|
||||
public:
|
||||
StackString() : StackContainer<
|
||||
std::basic_string<char,
|
||||
std::char_traits<char>,
|
||||
StackAllocator<char, stack_capacity> >,
|
||||
stack_capacity>() {
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(StackString);
|
||||
};
|
||||
|
||||
// StackWString
|
||||
template<size_t stack_capacity>
|
||||
class StackWString : public StackContainer<
|
||||
std::basic_string<wchar_t,
|
||||
std::char_traits<wchar_t>,
|
||||
StackAllocator<wchar_t, stack_capacity> >,
|
||||
stack_capacity> {
|
||||
public:
|
||||
StackWString() : StackContainer<
|
||||
std::basic_string<wchar_t,
|
||||
std::char_traits<wchar_t>,
|
||||
StackAllocator<wchar_t, stack_capacity> >,
|
||||
stack_capacity>() {
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(StackWString);
|
||||
};
|
||||
|
||||
// StackVector
|
||||
//
|
||||
// Example:
|
||||
// StackVector<int, 16> foo;
|
||||
// foo->push_back(22); // we have overloaded operator->
|
||||
// foo[0] = 10; // as well as operator[]
|
||||
template<typename T, size_t stack_capacity>
|
||||
class StackVector : public StackContainer<
|
||||
std::vector<T, StackAllocator<T, stack_capacity> >,
|
||||
stack_capacity> {
|
||||
public:
|
||||
StackVector() : StackContainer<
|
||||
std::vector<T, StackAllocator<T, stack_capacity> >,
|
||||
stack_capacity>() {
|
||||
}
|
||||
|
||||
// We need to put this in STL containers sometimes, which requires a copy
|
||||
// constructor. We can't call the regular copy constructor because that will
|
||||
// take the stack buffer from the original. Here, we create an empty object
|
||||
// and make a stack buffer of its own.
|
||||
StackVector(const StackVector<T, stack_capacity>& other)
|
||||
: StackContainer<
|
||||
std::vector<T, StackAllocator<T, stack_capacity> >,
|
||||
stack_capacity>() {
|
||||
this->container().assign(other->begin(), other->end());
|
||||
}
|
||||
|
||||
StackVector<T, stack_capacity>& operator=(
|
||||
const StackVector<T, stack_capacity>& other) {
|
||||
this->container().assign(other->begin(), other->end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Vectors are commonly indexed, which isn't very convenient even with
|
||||
// operator-> (using "->at()" does exception stuff we don't want).
|
||||
T& operator[](size_t i) { return this->container().operator[](i); }
|
||||
const T& operator[](size_t i) const {
|
||||
return this->container().operator[](i);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // BASE_STACK_CONTAINER_H_
|
|
@ -196,6 +196,23 @@ inline gfxASurface::gfxImageFormat SurfaceFormatToImageFormat(SurfaceFormat aFor
|
|||
}
|
||||
}
|
||||
|
||||
inline SurfaceFormat ImageFormatToSurfaceFormat(gfxASurface::gfxImageFormat aFormat)
|
||||
{
|
||||
switch (aFormat) {
|
||||
case gfxASurface::ImageFormatARGB32:
|
||||
return FORMAT_B8G8R8A8;
|
||||
case gfxASurface::ImageFormatRGB24:
|
||||
return FORMAT_B8G8R8X8;
|
||||
case gfxASurface::ImageFormatRGB16_565:
|
||||
return FORMAT_R5G6B5;
|
||||
case gfxASurface::ImageFormatA8:
|
||||
return FORMAT_A8;
|
||||
default:
|
||||
case gfxASurface::ImageFormatUnknown:
|
||||
return FORMAT_B8G8R8A8;
|
||||
}
|
||||
}
|
||||
|
||||
inline gfxASurface::gfxContentType ContentForFormat(const SurfaceFormat &aFormat)
|
||||
{
|
||||
switch (aFormat) {
|
||||
|
|
|
@ -736,6 +736,9 @@ public:
|
|||
virtual bool
|
||||
RecvEnableFMRadio(const hal::FMRadioSettings& aSettings)
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
hal::EnableFMRadio(aSettings);
|
||||
return true;
|
||||
}
|
||||
|
@ -743,6 +746,9 @@ public:
|
|||
virtual bool
|
||||
RecvDisableFMRadio()
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
hal::DisableFMRadio();
|
||||
return true;
|
||||
}
|
||||
|
@ -750,6 +756,9 @@ public:
|
|||
virtual bool
|
||||
RecvFMRadioSeek(const hal::FMRadioSeekDirection& aDirection)
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
hal::FMRadioSeek(aDirection);
|
||||
return true;
|
||||
}
|
||||
|
@ -757,6 +766,9 @@ public:
|
|||
virtual bool
|
||||
RecvGetFMRadioSettings(hal::FMRadioSettings* aSettings)
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
hal::GetFMRadioSettings(aSettings);
|
||||
return true;
|
||||
}
|
||||
|
@ -764,6 +776,9 @@ public:
|
|||
virtual bool
|
||||
RecvSetFMRadioFrequency(const uint32_t& aFrequency)
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
hal::SetFMRadioFrequency(aFrequency);
|
||||
return true;
|
||||
}
|
||||
|
@ -771,6 +786,9 @@ public:
|
|||
virtual bool
|
||||
RecvGetFMRadioFrequency(uint32_t* aFrequency)
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
*aFrequency = hal::GetFMRadioFrequency();
|
||||
return true;
|
||||
}
|
||||
|
@ -783,6 +801,9 @@ public:
|
|||
virtual bool
|
||||
RecvIsFMRadioOn(bool* radioOn)
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
*radioOn = hal::IsFMRadioOn();
|
||||
return true;
|
||||
}
|
||||
|
@ -790,6 +811,9 @@ public:
|
|||
virtual bool
|
||||
RecvGetFMRadioSignalStrength(uint32_t* strength)
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
*strength = hal::GetFMRadioSignalStrength();
|
||||
return true;
|
||||
}
|
||||
|
@ -797,6 +821,9 @@ public:
|
|||
virtual bool
|
||||
RecvCancelFMRadioSeek()
|
||||
{
|
||||
if (!AssertAppProcessPermission(this, "fmradio")) {
|
||||
return false;
|
||||
}
|
||||
hal::CancelFMRadioSeek();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ static nsresult
|
|||
imglib_Initialize()
|
||||
{
|
||||
mozilla::image::DiscardTracker::Initialize();
|
||||
mozilla::image::RasterImage::Initialize();
|
||||
imgLoader::GlobalInit();
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ImageLogging.h"
|
||||
#include "EndianMacros.h"
|
||||
#include "nsBMPDecoder.h"
|
||||
|
||||
#include "nsIInputStream.h"
|
||||
#include "RasterImage.h"
|
||||
#include "imgIContainerObserver.h"
|
||||
#include "ImageLogging.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* 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 "nsJPEGDecoder.h"
|
||||
#include "ImageLogging.h"
|
||||
#include "nsJPEGDecoder.h"
|
||||
|
||||
#include "imgIContainerObserver.h"
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* 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 "nsPNGDecoder.h"
|
||||
#include "ImageLogging.h"
|
||||
#include "nsPNGDecoder.h"
|
||||
|
||||
#include "nsMemory.h"
|
||||
#include "nsRect.h"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "base/histogram.h"
|
||||
#include "ImageLogging.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "imgIContainerObserver.h"
|
||||
#include "nsError.h"
|
||||
|
@ -16,7 +17,6 @@
|
|||
#include "nsStringStream.h"
|
||||
#include "prmem.h"
|
||||
#include "prenv.h"
|
||||
#include "ImageLogging.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "Layers.h"
|
||||
|
||||
|
@ -28,12 +28,66 @@
|
|||
#include "nsIconDecoder.h"
|
||||
|
||||
#include "gfxContext.h"
|
||||
#include "gfx2DGlue.h"
|
||||
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/StandardInteger.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/gfx/Scale.h"
|
||||
|
||||
// The high-quality scaler requires Skia.
|
||||
#ifdef MOZ_ENABLE_SKIA
|
||||
|
||||
static bool
|
||||
ScaleFrameImage(imgFrame *aSrcFrame, imgFrame *aDstFrame,
|
||||
const gfxSize &aScaleFactors)
|
||||
{
|
||||
if (aScaleFactors.width <= 0 || aScaleFactors.height <= 0)
|
||||
return false;
|
||||
|
||||
imgFrame *srcFrame = aSrcFrame;
|
||||
nsIntRect srcRect = srcFrame->GetRect();
|
||||
uint32_t dstWidth = NSToIntRoundUp(srcRect.width * aScaleFactors.width);
|
||||
uint32_t dstHeight = NSToIntRoundUp(srcRect.height * aScaleFactors.height);
|
||||
|
||||
// Destination is unconditionally ARGB32 because that's what the scaler
|
||||
// outputs.
|
||||
nsresult rv = aDstFrame->Init(0, 0, dstWidth, dstHeight,
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
if (!NS_FAILED(rv)) {
|
||||
uint8_t* srcData;
|
||||
uint32_t srcDataLength;
|
||||
// Source frame data is locked/unlocked on the main thread.
|
||||
srcFrame->GetImageData(&srcData, &srcDataLength);
|
||||
NS_ASSERTION(srcData != nullptr, "Source data is unavailable! Is it locked?");
|
||||
|
||||
uint8_t* dstData;
|
||||
uint32_t dstDataLength;
|
||||
aDstFrame->LockImageData();
|
||||
aDstFrame->GetImageData(&dstData, &dstDataLength);
|
||||
|
||||
// This returns an SkBitmap backed by dstData; since it wrote to dstData,
|
||||
// we don't need to look at that SkBitmap.
|
||||
mozilla::gfx::Scale(srcData, srcRect.width, srcRect.height, aSrcFrame->GetImageBytesPerRow(),
|
||||
dstData, dstWidth, dstHeight, aDstFrame->GetImageBytesPerRow(),
|
||||
mozilla::gfx::ImageFormatToSurfaceFormat(aSrcFrame->GetFormat()));
|
||||
|
||||
aDstFrame->UnlockImageData();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#else // MOZ_ENABLE_SKIA
|
||||
static bool
|
||||
ScaleFrameImage(imgFrame *aSrcFrame, imgFrame *aDstFrame,
|
||||
const gfxSize &aScaleFactors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif // MOZ_ENABLE_SKIA
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
@ -53,9 +107,11 @@ static PRLogModuleInfo *gCompressedImageAccountingLog = PR_NewLogModule ("Compre
|
|||
// Tweakable progressive decoding parameters. These are initialized to 0 here
|
||||
// because otherwise, we have to initialize them in a static initializer, which
|
||||
// makes us slower to start up.
|
||||
static bool gInitializedPrefCaches = false;
|
||||
static uint32_t gDecodeBytesAtATime = 0;
|
||||
static uint32_t gMaxMSBeforeYield = 0;
|
||||
static bool gHQDownscaling = false;
|
||||
// This is interpreted as a floating-point value / 1000
|
||||
static uint32_t gHQDownscalingMinFactor = 1000;
|
||||
|
||||
static void
|
||||
InitPrefCaches()
|
||||
|
@ -64,7 +120,10 @@ InitPrefCaches()
|
|||
"image.mem.decode_bytes_at_a_time", 200000);
|
||||
Preferences::AddUintVarCache(&gMaxMSBeforeYield,
|
||||
"image.mem.max_ms_before_yield", 400);
|
||||
gInitializedPrefCaches = true;
|
||||
Preferences::AddBoolVarCache(&gHQDownscaling,
|
||||
"image.high_quality_downscaling.enabled", false);
|
||||
Preferences::AddUintVarCache(&gHQDownscalingMinFactor,
|
||||
"image.high_quality_downscaling.min_factor", 1000);
|
||||
}
|
||||
|
||||
/* We define our own error checking macros here for 2 reasons:
|
||||
|
@ -137,6 +196,9 @@ namespace mozilla {
|
|||
namespace image {
|
||||
|
||||
/* static */ StaticRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton;
|
||||
/* static */ nsRefPtr<RasterImage::ScaleWorker> RasterImage::ScaleWorker::sSingleton;
|
||||
/* static */ nsRefPtr<RasterImage::DrawWorker> RasterImage::DrawWorker::sSingleton;
|
||||
static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
|
||||
|
||||
#ifndef DEBUG
|
||||
NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
|
||||
|
@ -172,7 +234,8 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
|
|||
mInDecoder(false),
|
||||
mAnimationFinished(false),
|
||||
mFinishing(false),
|
||||
mInUpdateImageContainer(false)
|
||||
mInUpdateImageContainer(false),
|
||||
mScaleRequest(this)
|
||||
{
|
||||
// Set up the discard tracker node.
|
||||
mDiscardTrackerNode.img = this;
|
||||
|
@ -181,15 +244,13 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
|
|||
// Statistics
|
||||
num_containers++;
|
||||
|
||||
// Register our pref observers if we haven't yet.
|
||||
if (NS_UNLIKELY(!gInitializedPrefCaches)) {
|
||||
InitPrefCaches();
|
||||
}
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
RasterImage::~RasterImage()
|
||||
{
|
||||
ScaleRequest::Stop(mScaleRequest.image);
|
||||
|
||||
delete mAnim;
|
||||
|
||||
for (unsigned int i = 0; i < mFrames.Length(); ++i)
|
||||
|
@ -224,6 +285,18 @@ RasterImage::~RasterImage()
|
|||
total_source_bytes -= mSourceData.Length();
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::Initialize()
|
||||
{
|
||||
InitPrefCaches();
|
||||
|
||||
// Create our singletons now, so we don't have to worry about what thread
|
||||
// they're created on.
|
||||
DecodeWorker::Singleton();
|
||||
DrawWorker::Singleton();
|
||||
ScaleWorker::Singleton();
|
||||
}
|
||||
|
||||
nsresult
|
||||
RasterImage::Init(imgIDecoderObserver *aObserver,
|
||||
const char* aMimeType,
|
||||
|
@ -995,15 +1068,15 @@ RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame,
|
|||
|
||||
nsAutoPtr<imgFrame> frame(aFrame);
|
||||
|
||||
// We are in the middle of decoding. This will be unlocked when we finish the
|
||||
// decoder->Write() call.
|
||||
frame->LockImageData();
|
||||
|
||||
if (paletteData && paletteLength)
|
||||
frame->GetPaletteData(paletteData, paletteLength);
|
||||
|
||||
frame->GetImageData(imageData, imageLength);
|
||||
|
||||
// We are in the middle of decoding. This will be unlocked when we finish the
|
||||
// decoder->Write() call.
|
||||
frame->LockImageData();
|
||||
|
||||
mFrames.InsertElementAt(framenum, frame.forget());
|
||||
|
||||
return NS_OK;
|
||||
|
@ -1183,6 +1256,11 @@ RasterImage::EnsureFrame(uint32_t aFrameNum, int32_t aX, int32_t aY,
|
|||
}
|
||||
|
||||
// Not reusable, so replace the frame directly.
|
||||
|
||||
// We know this frame is already locked, because it's the one we're currently
|
||||
// writing to.
|
||||
frame->UnlockImageData();
|
||||
|
||||
DeleteImgFrame(aFrameNum);
|
||||
mFrames.RemoveElementAt(aFrameNum);
|
||||
nsAutoPtr<imgFrame> newFrame(new imgFrame());
|
||||
|
@ -1995,18 +2073,21 @@ RasterImage::CopyFrameImage(imgFrame *aSrcFrame,
|
|||
if (!aSrcFrame || !aDstFrame)
|
||||
return false;
|
||||
|
||||
if (NS_FAILED(aDstFrame->LockImageData()))
|
||||
AutoFrameLocker dstLock(aDstFrame);
|
||||
AutoFrameLocker srcLock(aSrcFrame);
|
||||
|
||||
if (!srcLock.Succeeded() || !dstLock.Succeeded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy Image Over
|
||||
aSrcFrame->GetImageData(&aDataSrc, &aDataLengthSrc);
|
||||
aDstFrame->GetImageData(&aDataDest, &aDataLengthDest);
|
||||
if (!aDataDest || !aDataSrc || aDataLengthDest != aDataLengthSrc) {
|
||||
aDstFrame->UnlockImageData();
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(aDataDest, aDataSrc, aDataLengthSrc);
|
||||
aDstFrame->UnlockImageData();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2026,6 +2107,9 @@ RasterImage::DrawFrameTo(imgFrame *aSrc,
|
|||
NS_ENSURE_ARG_POINTER(aSrc);
|
||||
NS_ENSURE_ARG_POINTER(aDst);
|
||||
|
||||
AutoFrameLocker srcLock(aSrc);
|
||||
AutoFrameLocker dstLock(aDst);
|
||||
|
||||
nsIntRect dstRect = aDst->GetRect();
|
||||
|
||||
// According to both AGIF and APNG specs, offsets are unsigned
|
||||
|
@ -2053,7 +2137,7 @@ RasterImage::DrawFrameTo(imgFrame *aSrc,
|
|||
NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
|
||||
"RasterImage::DrawFrameTo: source must be smaller than dest");
|
||||
|
||||
if (NS_FAILED(aDst->LockImageData()))
|
||||
if (!srcLock.Succeeded() || !dstLock.Succeeded())
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
// Get pointers to image data
|
||||
|
@ -2066,7 +2150,6 @@ RasterImage::DrawFrameTo(imgFrame *aSrc,
|
|||
aSrc->GetPaletteData(&colormap, &size);
|
||||
aDst->GetImageData((uint8_t **)&dstPixels, &size);
|
||||
if (!srcPixels || !dstPixels || !colormap) {
|
||||
aDst->UnlockImageData();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -2094,14 +2177,12 @@ RasterImage::DrawFrameTo(imgFrame *aSrc,
|
|||
}
|
||||
}
|
||||
|
||||
aDst->UnlockImageData();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<gfxPattern> srcPatt;
|
||||
aSrc->GetPattern(getter_AddRefs(srcPatt));
|
||||
|
||||
aDst->LockImageData();
|
||||
nsRefPtr<gfxASurface> dstSurf;
|
||||
aDst->GetSurface(getter_AddRefs(dstSurf));
|
||||
|
||||
|
@ -2120,8 +2201,6 @@ RasterImage::DrawFrameTo(imgFrame *aSrc,
|
|||
dst.SetPattern(srcPatt);
|
||||
dst.Paint();
|
||||
|
||||
aDst->UnlockImageData();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2574,6 +2653,220 @@ RasterImage::SyncDecode()
|
|||
return mError ? NS_ERROR_FAILURE : NS_OK;
|
||||
}
|
||||
|
||||
/* static */ RasterImage::ScaleWorker*
|
||||
RasterImage::ScaleWorker::Singleton()
|
||||
{
|
||||
if (!sSingleton) {
|
||||
sSingleton = new ScaleWorker();
|
||||
ClearOnShutdown(&sSingleton);
|
||||
}
|
||||
|
||||
return sSingleton;
|
||||
}
|
||||
|
||||
nsresult
|
||||
RasterImage::ScaleWorker::Run()
|
||||
{
|
||||
if (!mInitialized) {
|
||||
PR_SetCurrentThreadName("Image Scaler");
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
ScaleRequest* request;
|
||||
gfxSize scale;
|
||||
imgFrame* frame;
|
||||
{
|
||||
MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
|
||||
request = mScaleRequests.popFirst();
|
||||
if (!request)
|
||||
return NS_OK;
|
||||
|
||||
scale = request->scale;
|
||||
frame = request->srcFrame;
|
||||
}
|
||||
|
||||
nsAutoPtr<imgFrame> scaledFrame(new imgFrame());
|
||||
bool scaled = ScaleFrameImage(frame, scaledFrame, scale);
|
||||
|
||||
// OK, we've got a new scaled image. Let's get the main thread to unlock and
|
||||
// redraw it.
|
||||
{
|
||||
MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
|
||||
if (scaled && scale == request->scale && !request->isInList()) {
|
||||
request->dstFrame = scaledFrame;
|
||||
request->done = true;
|
||||
}
|
||||
|
||||
DrawWorker::Singleton()->RequestDraw(request->image);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Note: you MUST call RequestScale with the ScaleWorker mutex held.
|
||||
void
|
||||
RasterImage::ScaleWorker::RequestScale(RasterImage* aImg)
|
||||
{
|
||||
mRequestsMutex.AssertCurrentThreadOwns();
|
||||
|
||||
ScaleRequest* request = &aImg->mScaleRequest;
|
||||
if (request->isInList())
|
||||
return;
|
||||
|
||||
mScaleRequests.insertBack(request);
|
||||
|
||||
if (!sScaleWorkerThread) {
|
||||
NS_NewThread(getter_AddRefs(sScaleWorkerThread), this, NS_DISPATCH_NORMAL);
|
||||
ClearOnShutdown(&sScaleWorkerThread);
|
||||
}
|
||||
else {
|
||||
sScaleWorkerThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ RasterImage::DrawWorker*
|
||||
RasterImage::DrawWorker::Singleton()
|
||||
{
|
||||
if (!sSingleton) {
|
||||
sSingleton = new DrawWorker();
|
||||
ClearOnShutdown(&sSingleton);
|
||||
}
|
||||
|
||||
return sSingleton;
|
||||
}
|
||||
|
||||
nsresult
|
||||
RasterImage::DrawWorker::Run()
|
||||
{
|
||||
ScaleRequest* request;
|
||||
{
|
||||
MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
|
||||
request = mDrawRequests.popFirst();
|
||||
}
|
||||
if (request) {
|
||||
// ScaleWorker is finished with this request, so we can unlock the data now.
|
||||
request->UnlockSourceData();
|
||||
// We have to reset dstFrame if request was stopped while ScaleWorker was scaling.
|
||||
if (request->stopped) {
|
||||
ScaleRequest::Stop(request->image);
|
||||
}
|
||||
nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(request->image->mObserver));
|
||||
if (request->done && observer) {
|
||||
imgFrame *scaledFrame = request->dstFrame.get();
|
||||
scaledFrame->ImageUpdated(scaledFrame->GetRect());
|
||||
nsIntRect frameRect = request->srcFrame->GetRect();
|
||||
observer->FrameChanged(nullptr, request->image, &frameRect);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::DrawWorker::RequestDraw(RasterImage* aImg)
|
||||
{
|
||||
ScaleRequest* request = &aImg->mScaleRequest;
|
||||
mDrawRequests.insertBack(request);
|
||||
NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::ScaleRequest::Stop(RasterImage* aImg)
|
||||
{
|
||||
ScaleRequest* request = &aImg->mScaleRequest;
|
||||
// It's safe to unlock source image data only if request is in the list.
|
||||
// Otherwise we may be reading from the source while performing scaling
|
||||
// and can't interrupt immediately.
|
||||
if (request->isInList()) {
|
||||
request->remove();
|
||||
request->UnlockSourceData();
|
||||
}
|
||||
// We have to check if request is finished before dropping the destination
|
||||
// frame. Otherwise we may be writing to the dest while performing scaling.
|
||||
if (request->done) {
|
||||
request->done = false;
|
||||
request->dstFrame = nullptr;
|
||||
request->scale.width = 0;
|
||||
request->scale.height = 0;
|
||||
}
|
||||
request->stopped = true;
|
||||
}
|
||||
|
||||
bool
|
||||
RasterImage::CanScale(gfxPattern::GraphicsFilter aFilter,
|
||||
gfxSize aScale)
|
||||
{
|
||||
// The high-quality scaler requires Skia.
|
||||
#ifdef MOZ_ENABLE_SKIA
|
||||
if (gHQDownscaling && aFilter == gfxPattern::FILTER_GOOD &&
|
||||
!mAnim && mDecoded &&
|
||||
(aScale.width <= 1.0 && aScale.height <= 1.0)) {
|
||||
gfxFloat factor = gHQDownscalingMinFactor / 1000.0;
|
||||
return (aScale.width < factor || aScale.height < factor);
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
|
||||
gfxContext *aContext,
|
||||
gfxPattern::GraphicsFilter aFilter,
|
||||
const gfxMatrix &aUserSpaceToImageSpace,
|
||||
const gfxRect &aFill,
|
||||
const nsIntRect &aSubimage)
|
||||
{
|
||||
imgFrame *frame = aFrame;
|
||||
nsIntRect framerect = frame->GetRect();
|
||||
gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
|
||||
gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace;
|
||||
imageSpaceToUserSpace.Invert();
|
||||
gfxSize scale = imageSpaceToUserSpace.ScaleFactors(true);
|
||||
nsIntRect subimage = aSubimage;
|
||||
|
||||
if (CanScale(aFilter, scale)) {
|
||||
MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
|
||||
// If scale factor is still the same that we scaled for and
|
||||
// ScaleWorker has done it's job, then we can use pre-downscaled frame.
|
||||
// If scale factor has changed, order new request.
|
||||
if (mScaleRequest.scale == scale) {
|
||||
if (mScaleRequest.done) {
|
||||
frame = mScaleRequest.dstFrame.get();
|
||||
userSpaceToImageSpace.Multiply(gfxMatrix().Scale(scale.width, scale.height));
|
||||
|
||||
// Since we're switching to a scaled image, we we need to transform the
|
||||
// area of the subimage to draw accordingly, since imgFrame::Draw()
|
||||
// doesn't know about scaled frames.
|
||||
subimage.ScaleRoundOut(scale.width, scale.height);
|
||||
}
|
||||
} else {
|
||||
// FIXME: Current implementation doesn't support pre-downscale
|
||||
// mechanism for multiple images from same src, since we cache
|
||||
// pre-downscaled frame only for the latest requested scale.
|
||||
// The solution is to cache more than one scaled image frame
|
||||
// for each RasterImage.
|
||||
int scaling = mScaleRequest.srcDataLocked ? 1 : 0;
|
||||
if (mLockCount - scaling == 1) {
|
||||
ScaleRequest::Stop(this);
|
||||
mScaleRequest.srcFrame = frame;
|
||||
mScaleRequest.scale = scale;
|
||||
mScaleRequest.stopped = false;
|
||||
|
||||
// We need to make sure that source data is available before asking to scale.
|
||||
if (mScaleRequest.LockSourceData()) {
|
||||
ScaleWorker::Singleton()->RequestScale(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsIntMargin padding(framerect.x, framerect.y,
|
||||
mSize.width - framerect.XMost(),
|
||||
mSize.height - framerect.YMost());
|
||||
|
||||
frame->Draw(aContext, aFilter, userSpaceToImageSpace, aFill, padding, subimage);
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* [noscript] void draw(in gfxContext aContext,
|
||||
* in gfxGraphicsFilter aFilter,
|
||||
|
@ -2645,12 +2938,7 @@ RasterImage::Draw(gfxContext *aContext,
|
|||
return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
|
||||
}
|
||||
|
||||
nsIntRect framerect = frame->GetRect();
|
||||
nsIntMargin padding(framerect.x, framerect.y,
|
||||
mSize.width - framerect.XMost(),
|
||||
mSize.height - framerect.YMost());
|
||||
|
||||
frame->Draw(aContext, aFilter, aUserSpaceToImageSpace, aFill, padding, aSubimage, aFlags);
|
||||
DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter, aUserSpaceToImageSpace, aFill, aSubimage);
|
||||
|
||||
if (mDecoded && !mDrawStartTime.IsNull()) {
|
||||
TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
|
||||
|
@ -2658,6 +2946,7 @@ RasterImage::Draw(gfxContext *aContext,
|
|||
// clear the value of mDrawStartTime
|
||||
mDrawStartTime = TimeStamp();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2706,6 +2995,11 @@ RasterImage::UnlockImage()
|
|||
// Decrement our lock count
|
||||
mLockCount--;
|
||||
|
||||
if (ScaleWorker::sSingleton && mLockCount == 0) {
|
||||
MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
|
||||
ScaleRequest::Stop(this);
|
||||
}
|
||||
|
||||
// If we've decoded this image once before, we're currently decoding again,
|
||||
// and our lock count is now zero (so nothing is forcing us to keep the
|
||||
// decoded data around), try to cancel the decode and throw away whatever
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#ifndef mozilla_imagelib_RasterImage_h_
|
||||
#define mozilla_imagelib_RasterImage_h_
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "Image.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
@ -308,6 +309,9 @@ public:
|
|||
|
||||
const char* GetURIString() { return mURIString.get();}
|
||||
|
||||
// Called from module startup. Sets up RasterImage to be used.
|
||||
static void Initialize();
|
||||
|
||||
private:
|
||||
struct Anim
|
||||
{
|
||||
|
@ -467,6 +471,109 @@ private:
|
|||
bool mPendingInEventLoop;
|
||||
};
|
||||
|
||||
struct ScaleRequest : public LinkedListElement<ScaleRequest>
|
||||
{
|
||||
ScaleRequest(RasterImage* aImage)
|
||||
: image(aImage)
|
||||
, srcFrame(nullptr)
|
||||
, dstFrame(nullptr)
|
||||
, scale(0, 0)
|
||||
, done(false)
|
||||
, stopped(false)
|
||||
, srcDataLocked(false)
|
||||
{};
|
||||
|
||||
bool LockSourceData()
|
||||
{
|
||||
if (!srcDataLocked) {
|
||||
bool success = true;
|
||||
success = success && NS_SUCCEEDED(image->LockImage());
|
||||
success = success && NS_SUCCEEDED(srcFrame->LockImageData());
|
||||
srcDataLocked = success;
|
||||
}
|
||||
return srcDataLocked;
|
||||
}
|
||||
|
||||
bool UnlockSourceData()
|
||||
{
|
||||
bool success = true;
|
||||
if (srcDataLocked) {
|
||||
success = success && NS_SUCCEEDED(image->UnlockImage());
|
||||
success = success && NS_SUCCEEDED(srcFrame->UnlockImageData());
|
||||
|
||||
// If unlocking fails, there's nothing we can do to make it work, so we
|
||||
// claim that we're not locked regardless.
|
||||
srcDataLocked = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static void Stop(RasterImage* aImg);
|
||||
|
||||
RasterImage* const image;
|
||||
imgFrame *srcFrame;
|
||||
nsAutoPtr<imgFrame> dstFrame;
|
||||
gfxSize scale;
|
||||
bool done;
|
||||
bool stopped;
|
||||
bool srcDataLocked;
|
||||
};
|
||||
|
||||
class ScaleWorker : public nsRunnable
|
||||
{
|
||||
public:
|
||||
static ScaleWorker* Singleton();
|
||||
|
||||
NS_IMETHOD Run();
|
||||
|
||||
/* statics */
|
||||
static nsRefPtr<ScaleWorker> sSingleton;
|
||||
|
||||
private: /* methods */
|
||||
ScaleWorker()
|
||||
: mRequestsMutex("RasterImage.ScaleWorker.mRequestsMutex")
|
||||
, mInitialized(false)
|
||||
{};
|
||||
|
||||
// Note: you MUST call RequestScale with the ScaleWorker mutex held.
|
||||
void RequestScale(RasterImage* aImg);
|
||||
|
||||
private: /* members */
|
||||
|
||||
friend class RasterImage;
|
||||
LinkedList<ScaleRequest> mScaleRequests;
|
||||
Mutex mRequestsMutex;
|
||||
bool mInitialized;
|
||||
};
|
||||
|
||||
class DrawWorker : public nsRunnable
|
||||
{
|
||||
public:
|
||||
static DrawWorker* Singleton();
|
||||
|
||||
NS_IMETHOD Run();
|
||||
|
||||
/* statics */
|
||||
static nsRefPtr<DrawWorker> sSingleton;
|
||||
|
||||
private: /* methods */
|
||||
DrawWorker() {};
|
||||
|
||||
void RequestDraw(RasterImage* aImg);
|
||||
|
||||
private: /* members */
|
||||
|
||||
friend class RasterImage;
|
||||
LinkedList<ScaleRequest> mDrawRequests;
|
||||
};
|
||||
|
||||
void DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
|
||||
gfxContext *aContext,
|
||||
gfxPattern::GraphicsFilter aFilter,
|
||||
const gfxMatrix &aUserSpaceToImageSpace,
|
||||
const gfxRect &aFill,
|
||||
const nsIntRect &aSubimage);
|
||||
|
||||
/**
|
||||
* Advances the animation. Typically, this will advance a single frame, but it
|
||||
* may advance multiple frames. This may happen if we have infrequently
|
||||
|
@ -672,6 +779,9 @@ private: // data
|
|||
bool IsDecodeFinished();
|
||||
TimeStamp mDrawStartTime;
|
||||
|
||||
inline bool CanScale(gfxPattern::GraphicsFilter aFilter, gfxSize aScale);
|
||||
ScaleRequest mScaleRequest;
|
||||
|
||||
// Decoder shutdown
|
||||
enum eShutdownIntent {
|
||||
eShutdownIntent_Done = 0,
|
||||
|
|
|
@ -109,6 +109,7 @@ imgFrame::imgFrame() :
|
|||
mSinglePixelColor(0),
|
||||
mTimeout(100),
|
||||
mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
|
||||
mLockCount(0),
|
||||
mBlendMethod(1), /* imgIContainer::kBlendOver */
|
||||
mSinglePixel(false),
|
||||
mNeverUseDeviceSurface(false),
|
||||
|
@ -118,7 +119,6 @@ imgFrame::imgFrame() :
|
|||
#ifdef USE_WIN_SURFACE
|
||||
mIsDDBSurface(false),
|
||||
#endif
|
||||
mLocked(false),
|
||||
mInformedDiscardTracker(false)
|
||||
{
|
||||
static bool hasCheckedOptimize = false;
|
||||
|
@ -578,6 +578,8 @@ uint32_t imgFrame::GetImageDataLength() const
|
|||
|
||||
void imgFrame::GetImageData(uint8_t **aData, uint32_t *length) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetImageData unless frame is locked");
|
||||
|
||||
if (mImageSurface)
|
||||
*aData = mImageSurface->Data();
|
||||
else if (mPalettedImageData)
|
||||
|
@ -600,6 +602,8 @@ bool imgFrame::GetHasAlpha() const
|
|||
|
||||
void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetPaletteData unless frame is locked");
|
||||
|
||||
if (!mPalettedImageData) {
|
||||
*aPalette = nullptr;
|
||||
*length = 0;
|
||||
|
@ -611,14 +615,21 @@ void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const
|
|||
|
||||
nsresult imgFrame::LockImageData()
|
||||
{
|
||||
if (mPalettedImageData)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
NS_ABORT_IF_FALSE(!mLocked, "Trying to lock already locked image data.");
|
||||
if (mLocked) {
|
||||
NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
|
||||
if (mLockCount < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mLocked = true;
|
||||
|
||||
mLockCount++;
|
||||
|
||||
// If we are not the first lock, there's nothing to do.
|
||||
if (mLockCount != 1) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Paletted images don't have surfaces, so there's nothing to do.
|
||||
if (mPalettedImageData)
|
||||
return NS_OK;
|
||||
|
||||
if ((mOptSurface || mSinglePixel) && !mImageSurface) {
|
||||
// Recover the pixels
|
||||
|
@ -659,15 +670,26 @@ nsresult imgFrame::LockImageData()
|
|||
|
||||
nsresult imgFrame::UnlockImageData()
|
||||
{
|
||||
if (mPalettedImageData)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
NS_ABORT_IF_FALSE(mLocked, "Unlocking an unlocked image!");
|
||||
if (!mLocked) {
|
||||
NS_ABORT_IF_FALSE(mLockCount != 0, "Unlocking an unlocked image!");
|
||||
if (mLockCount == 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mLocked = false;
|
||||
mLockCount--;
|
||||
|
||||
NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
|
||||
if (mLockCount < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// If we are not the last lock, there's nothing to do.
|
||||
if (mLockCount != 0) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Paletted images don't have surfaces, so there's nothing to do.
|
||||
if (mPalettedImageData)
|
||||
return NS_OK;
|
||||
|
||||
// Assume we've been written to.
|
||||
if (mImageSurface)
|
||||
|
|
|
@ -157,6 +157,9 @@ private: // data
|
|||
int32_t mTimeout; // -1 means display forever
|
||||
int32_t mDisposalMethod;
|
||||
|
||||
/** Indicates how many readers currently have locked this frame */
|
||||
int32_t mLockCount;
|
||||
|
||||
gfxASurface::gfxImageFormat mFormat;
|
||||
uint8_t mPaletteDepth;
|
||||
int8_t mBlendMethod;
|
||||
|
@ -165,8 +168,6 @@ private: // data
|
|||
bool mFormatChanged;
|
||||
bool mCompositingFailed;
|
||||
bool mNonPremult;
|
||||
/** Indicates if the image data is currently locked */
|
||||
bool mLocked;
|
||||
|
||||
/** Have we called DiscardTracker::InformAllocation()? */
|
||||
bool mInformedDiscardTracker;
|
||||
|
@ -176,4 +177,33 @@ private: // data
|
|||
#endif
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
// An RAII class to ensure it's easy to balance locks and unlocks on
|
||||
// imgFrames.
|
||||
class AutoFrameLocker
|
||||
{
|
||||
public:
|
||||
AutoFrameLocker(imgFrame* frame)
|
||||
: mFrame(frame)
|
||||
, mSucceeded(NS_SUCCEEDED(frame->LockImageData()))
|
||||
{}
|
||||
|
||||
~AutoFrameLocker()
|
||||
{
|
||||
if (mSucceeded) {
|
||||
mFrame->UnlockImageData();
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the lock request succeeded.
|
||||
bool Succeeded() { return mSucceeded; }
|
||||
|
||||
private:
|
||||
imgFrame* mFrame;
|
||||
bool mSucceeded;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* imgFrame_h */
|
||||
|
|
|
@ -191,6 +191,12 @@ SHARED_LIBRARY_LIBS += \
|
|||
$(NULL)
|
||||
endif
|
||||
|
||||
ifdef MOZ_DASH
|
||||
SHARED_LIBRARY_LIBS += \
|
||||
$(DEPTH)/content/media/dash/$(LIB_PREFIX)gkcondash_s.$(LIB_SUFFIX) \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
|
||||
INCLUDES += \
|
||||
-I$(srcdir)/../../base/src \
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -11,6 +11,7 @@
|
|||
#define nsFlexContainerFrame_h___
|
||||
|
||||
#include "nsContainerFrame.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
nsIFrame* NS_NewFlexContainerFrame(nsIPresShell* aPresShell,
|
||||
|
@ -18,6 +19,11 @@ nsIFrame* NS_NewFlexContainerFrame(nsIPresShell* aPresShell,
|
|||
|
||||
typedef nsContainerFrame nsFlexContainerFrameSuper;
|
||||
|
||||
class FlexItem;
|
||||
class FlexboxAxisTracker;
|
||||
class MainAxisPositionTracker;
|
||||
class SingleLineCrossAxisPositionTracker;
|
||||
|
||||
class nsFlexContainerFrame : public nsFlexContainerFrameSuper {
|
||||
NS_DECL_FRAMEARENA_HELPERS
|
||||
NS_DECL_QUERYFRAME_TARGET(nsFlexContainerFrame)
|
||||
|
@ -27,20 +33,89 @@ class nsFlexContainerFrame : public nsFlexContainerFrameSuper {
|
|||
friend nsIFrame* NS_NewFlexContainerFrame(nsIPresShell* aPresShell,
|
||||
nsStyleContext* aContext);
|
||||
|
||||
public:
|
||||
// nsIFrame overrides
|
||||
NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
const nsRect& aDirtyRect,
|
||||
const nsDisplayListSet& aLists) MOZ_OVERRIDE;
|
||||
|
||||
NS_IMETHOD Reflow(nsPresContext* aPresContext,
|
||||
nsHTMLReflowMetrics& aDesiredSize,
|
||||
const nsHTMLReflowState& aReflowState,
|
||||
nsReflowStatus& aStatus) MOZ_OVERRIDE;
|
||||
|
||||
virtual nscoord
|
||||
GetMinWidth(nsRenderingContext* aRenderingContext) MOZ_OVERRIDE;
|
||||
virtual nscoord
|
||||
GetPrefWidth(nsRenderingContext* aRenderingContext) MOZ_OVERRIDE;
|
||||
|
||||
virtual nsIAtom* GetType() const MOZ_OVERRIDE;
|
||||
#ifdef DEBUG
|
||||
NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
|
||||
#endif
|
||||
#endif // DEBUG
|
||||
// Flexbox-specific public methods
|
||||
bool IsHorizontal();
|
||||
|
||||
protected:
|
||||
// Protected constructor & destructor
|
||||
nsFlexContainerFrame(nsStyleContext* aContext) : nsFlexContainerFrameSuper(aContext) {}
|
||||
nsFlexContainerFrame(nsStyleContext* aContext) :
|
||||
nsFlexContainerFrameSuper(aContext),
|
||||
mCachedContentBoxCrossSize(nscoord_MIN),
|
||||
mCachedAscent(nscoord_MIN)
|
||||
{}
|
||||
virtual ~nsFlexContainerFrame();
|
||||
|
||||
// Protected nsIFrame overrides:
|
||||
virtual void DestroyFrom(nsIFrame* aDestructRoot);
|
||||
|
||||
// Protected flex-container-specific methods / member-vars
|
||||
#ifdef DEBUG
|
||||
void SanityCheckAnonymousFlexItems() const;
|
||||
#endif // DEBUG
|
||||
|
||||
|
||||
// Returns nsresult because we might have to reflow aChildFrame (to get its
|
||||
// vertical intrinsic size in a vertical flexbox), and if that reflow fails
|
||||
// (returns a failure nsresult), we want to bail out.
|
||||
nsresult AppendFlexItemForChild(nsPresContext* aPresContext,
|
||||
nsIFrame* aChildFrame,
|
||||
const nsHTMLReflowState& aParentReflowState,
|
||||
const FlexboxAxisTracker& aAxisTracker,
|
||||
nsTArray<FlexItem>& aFlexItems);
|
||||
|
||||
// Runs the "resolve the flexible lengths" algorithm, distributing
|
||||
// |aFlexContainerMainSize| among the |aItems| and freezing them.
|
||||
void ResolveFlexibleLengths(const FlexboxAxisTracker& aAxisTracker,
|
||||
nscoord aFlexContainerMainSize,
|
||||
nsTArray<FlexItem>& aItems);
|
||||
|
||||
nsresult GenerateFlexItems(nsPresContext* aPresContext,
|
||||
const nsHTMLReflowState& aReflowState,
|
||||
const FlexboxAxisTracker& aAxisTracker,
|
||||
nsTArray<FlexItem>& aItems);
|
||||
|
||||
nscoord ComputeFlexContainerMainSize(const nsHTMLReflowState& aReflowState,
|
||||
const FlexboxAxisTracker& aAxisTracker,
|
||||
const nsTArray<FlexItem>& aFlexItems);
|
||||
|
||||
void PositionItemInMainAxis(MainAxisPositionTracker& aMainAxisPosnTracker,
|
||||
FlexItem& aItem);
|
||||
|
||||
nsresult SizeItemInCrossAxis(nsPresContext* aPresContext,
|
||||
const FlexboxAxisTracker& aAxisTracker,
|
||||
const nsHTMLReflowState& aChildReflowState,
|
||||
FlexItem& aItem);
|
||||
|
||||
void PositionItemInCrossAxis(
|
||||
nscoord aLineStartPosition,
|
||||
SingleLineCrossAxisPositionTracker& aLineCrossAxisPosnTracker,
|
||||
FlexItem& aItem);
|
||||
|
||||
// Cached values from running flexbox layout algorithm, used in setting our
|
||||
// reflow metrics w/out actually reflowing all of our children, in any
|
||||
// reflows where we're not dirty:
|
||||
nscoord mCachedContentBoxCrossSize; // cross size of our content-box size
|
||||
nscoord mCachedAscent; // our ascent, in prev. reflow.
|
||||
};
|
||||
|
||||
#endif /* nsFlexContainerFrame_h___ */
|
||||
|
|
|
@ -4004,7 +4004,11 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext,
|
|||
result.width = NS_MAX(minWidth, result.width);
|
||||
|
||||
// Compute height
|
||||
if (!nsLayoutUtils::IsAutoHeight(*heightStyleCoord, aCBSize.height)) {
|
||||
// (but not if we're auto-height or if we recieved the "eUseAutoHeight"
|
||||
// flag -- then, we'll just stick with the height that we already calculated
|
||||
// in the initial ComputeAutoSize() call.)
|
||||
if (!nsLayoutUtils::IsAutoHeight(*heightStyleCoord, aCBSize.height) &&
|
||||
!(aFlags & nsIFrame::eUseAutoHeight)) {
|
||||
result.height =
|
||||
nsLayoutUtils::ComputeHeightValue(aCBSize.height,
|
||||
boxSizingAdjust.height,
|
||||
|
|
|
@ -22,9 +22,7 @@ FRAME_ID(nsFieldSetFrame)
|
|||
FRAME_ID(nsFileControlFrame)
|
||||
FRAME_ID(nsFirstLetterFrame)
|
||||
FRAME_ID(nsFirstLineFrame)
|
||||
#ifdef MOZ_FLEXBOX
|
||||
FRAME_ID(nsFlexContainerFrame)
|
||||
#endif // MOZ_FLEXBOX
|
||||
FRAME_ID(nsFormControlFrame)
|
||||
FRAME_ID(nsFrame)
|
||||
FRAME_ID(nsGfxButtonControlFrame)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "nsFontMetrics.h"
|
||||
#include "nsBlockFrame.h"
|
||||
#include "nsLineBox.h"
|
||||
#include "nsFlexContainerFrame.h"
|
||||
#include "nsImageFrame.h"
|
||||
#include "nsTableFrame.h"
|
||||
#include "nsTableCellFrame.h"
|
||||
|
@ -174,6 +175,7 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext,
|
|||
mFlags.mAssumingHScrollbar = mFlags.mAssumingVScrollbar = false;
|
||||
mFlags.mHasClearance = false;
|
||||
mFlags.mIsColumnBalancing = false;
|
||||
mFlags.mIsFlexContainerMeasuringHeight = false;
|
||||
mFlags.mDummyParentReflowState = false;
|
||||
|
||||
mDiscoveredClearance = nullptr;
|
||||
|
@ -686,6 +688,9 @@ nsHTMLReflowState::InitFrameType(nsIAtom* aFrameType)
|
|||
case NS_STYLE_DISPLAY_LIST_ITEM:
|
||||
case NS_STYLE_DISPLAY_TABLE:
|
||||
case NS_STYLE_DISPLAY_TABLE_CAPTION:
|
||||
#ifdef MOZ_FLEXBOX
|
||||
case NS_STYLE_DISPLAY_FLEX:
|
||||
#endif // MOZ_FLEXBOX
|
||||
frameType = NS_CSS_FRAME_TYPE_BLOCK;
|
||||
break;
|
||||
|
||||
|
@ -695,6 +700,9 @@ nsHTMLReflowState::InitFrameType(nsIAtom* aFrameType)
|
|||
case NS_STYLE_DISPLAY_INLINE_BOX:
|
||||
case NS_STYLE_DISPLAY_INLINE_GRID:
|
||||
case NS_STYLE_DISPLAY_INLINE_STACK:
|
||||
#ifdef MOZ_FLEXBOX
|
||||
case NS_STYLE_DISPLAY_INLINE_FLEX:
|
||||
#endif // MOZ_FLEXBOX
|
||||
frameType = NS_CSS_FRAME_TYPE_INLINE;
|
||||
break;
|
||||
|
||||
|
@ -1798,6 +1806,20 @@ IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay)
|
|||
captionSide == NS_STYLE_CAPTION_SIDE_RIGHT;
|
||||
}
|
||||
|
||||
#ifdef MOZ_FLEXBOX
|
||||
static nsFlexContainerFrame*
|
||||
GetFlexContainer(nsIFrame* aFrame)
|
||||
{
|
||||
nsIFrame* parent = aFrame->GetParent();
|
||||
if (!parent ||
|
||||
parent->GetType() != nsGkAtoms::flexContainerFrame) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<nsFlexContainerFrame*>(parent);
|
||||
}
|
||||
#endif // MOZ_FLEXBOX
|
||||
|
||||
// XXX refactor this code to have methods for each set of properties
|
||||
// we are computing: width,height,line-height; margin; offsets
|
||||
|
||||
|
@ -2009,6 +2031,23 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext,
|
|||
computeSizeFlags |= nsIFrame::eShrinkWrap;
|
||||
}
|
||||
|
||||
#ifdef MOZ_FLEXBOX
|
||||
const nsFlexContainerFrame* flexContainerFrame = GetFlexContainer(frame);
|
||||
if (flexContainerFrame) {
|
||||
computeSizeFlags |= nsIFrame::eShrinkWrap;
|
||||
|
||||
// If we're inside of a flex container that needs to measure our
|
||||
// auto height, pass that information along to ComputeSize().
|
||||
if (mFlags.mIsFlexContainerMeasuringHeight) {
|
||||
computeSizeFlags |= nsIFrame::eUseAutoHeight;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(!mFlags.mIsFlexContainerMeasuringHeight,
|
||||
"We're not in a flex container, so the flag "
|
||||
"'mIsFlexContainerMeasuringHeight' shouldn't be set");
|
||||
}
|
||||
#endif // MOZ_FLEXBOX
|
||||
|
||||
nsSize size =
|
||||
frame->ComputeSize(rendContext,
|
||||
nsSize(aContainingBlockWidth,
|
||||
|
@ -2030,9 +2069,14 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext,
|
|||
NS_ASSERTION(mComputedHeight == NS_UNCONSTRAINEDSIZE ||
|
||||
mComputedHeight >= 0, "Bogus height");
|
||||
|
||||
// Exclude inline tables from the block margin calculations
|
||||
if (isBlock && !IsSideCaption(frame, mStyleDisplay) &&
|
||||
frame->GetStyleDisplay()->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE)
|
||||
// Exclude inline tables and flex items from the block margin calculations
|
||||
if (isBlock &&
|
||||
!IsSideCaption(frame, mStyleDisplay) &&
|
||||
mStyleDisplay->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE
|
||||
#ifdef MOZ_FLEXBOX
|
||||
&& !flexContainerFrame
|
||||
#endif // MOZ_FLEXBOX
|
||||
)
|
||||
CalculateBlockSideMargins(availableWidth, mComputedWidth, aFrameType);
|
||||
}
|
||||
}
|
||||
|
@ -2458,10 +2502,22 @@ nsHTMLReflowState::ComputeMinMaxValues(nscoord aContainingBlockWidth,
|
|||
nscoord aContainingBlockHeight,
|
||||
const nsHTMLReflowState* aContainingBlockRS)
|
||||
{
|
||||
#ifdef MOZ_FLEXBOX
|
||||
nsFlexContainerFrame* flexContainerFrame = GetFlexContainer(frame);
|
||||
#endif // MOZ_FLEXBOX
|
||||
|
||||
// Handle "min-width: auto"
|
||||
if (eStyleUnit_Auto == mStylePosition->mMinWidth.GetUnit()) {
|
||||
// XXXdholbert For flex items, this needs to behave like -moz-min-content.
|
||||
mComputedMinWidth = 0;
|
||||
#ifdef MOZ_FLEXBOX
|
||||
if (flexContainerFrame && flexContainerFrame->IsHorizontal()) {
|
||||
mComputedMinWidth =
|
||||
ComputeWidthValue(aContainingBlockWidth,
|
||||
mStylePosition->mBoxSizing,
|
||||
nsStyleCoord(NS_STYLE_WIDTH_MIN_CONTENT,
|
||||
eStyleUnit_Enumerated));
|
||||
}
|
||||
#endif // MOZ_FLEXBOX
|
||||
} else {
|
||||
mComputedMinWidth = ComputeWidthValue(aContainingBlockWidth,
|
||||
mStylePosition->mBoxSizing,
|
||||
|
@ -2487,6 +2543,9 @@ nsHTMLReflowState::ComputeMinMaxValues(nscoord aContainingBlockWidth,
|
|||
// depends on the content height. Treat them like 'auto'
|
||||
// Likewise, check for calc() on internal table elements; calc() on
|
||||
// such elements is unsupported.
|
||||
// Likewise, if we're a child of a flex container who's measuring our
|
||||
// intrinsic height, then we want to disregard our min-height.
|
||||
|
||||
// NOTE: We treat "min-height:auto" as "0" for the purpose of this code,
|
||||
// since that's what it means in all cases except for on flex items -- and
|
||||
// even there, we're supposed to ignore it (i.e. treat it as 0) until the
|
||||
|
@ -2496,7 +2555,8 @@ nsHTMLReflowState::ComputeMinMaxValues(nscoord aContainingBlockWidth,
|
|||
(NS_AUTOHEIGHT == aContainingBlockHeight &&
|
||||
minHeight.HasPercent()) ||
|
||||
(mFrameType == NS_CSS_FRAME_TYPE_INTERNAL_TABLE &&
|
||||
minHeight.IsCalcUnit())) {
|
||||
minHeight.IsCalcUnit()) ||
|
||||
mFlags.mIsFlexContainerMeasuringHeight) {
|
||||
mComputedMinHeight = 0;
|
||||
} else {
|
||||
mComputedMinHeight = ComputeHeightValue(aContainingBlockHeight,
|
||||
|
@ -2510,13 +2570,16 @@ nsHTMLReflowState::ComputeMinMaxValues(nscoord aContainingBlockWidth,
|
|||
mComputedMaxHeight = NS_UNCONSTRAINEDSIZE; // no limit
|
||||
} else {
|
||||
// Check for percentage based values and a containing block height that
|
||||
// depends on the content height. Treat them like 'auto'
|
||||
// depends on the content height. Treat them like 'none'
|
||||
// Likewise, check for calc() on internal table elements; calc() on
|
||||
// such elements is unsupported.
|
||||
// Likewise, if we're a child of a flex container who's measuring our
|
||||
// intrinsic height, then we want to disregard our max-height.
|
||||
if ((NS_AUTOHEIGHT == aContainingBlockHeight &&
|
||||
maxHeight.HasPercent()) ||
|
||||
(mFrameType == NS_CSS_FRAME_TYPE_INTERNAL_TABLE &&
|
||||
maxHeight.IsCalcUnit())) {
|
||||
maxHeight.IsCalcUnit()) ||
|
||||
mFlags.mIsFlexContainerMeasuringHeight) {
|
||||
mComputedMaxHeight = NS_UNCONSTRAINEDSIZE;
|
||||
} else {
|
||||
mComputedMaxHeight = ComputeHeightValue(aContainingBlockHeight,
|
||||
|
|
|
@ -349,6 +349,9 @@ public:
|
|||
uint16_t mHeightDependsOnAncestorCell:1; // Does frame height depend on
|
||||
// an ancestor table-cell?
|
||||
uint16_t mIsColumnBalancing:1; // nsColumnSetFrame is balancing columns
|
||||
uint16_t mIsFlexContainerMeasuringHeight:1; // nsFlexContainerFrame is
|
||||
// reflowing this child to
|
||||
// measure its intrinsic height.
|
||||
uint16_t mDummyParentReflowState:1; // a "fake" reflow state made
|
||||
// in order to be the parent
|
||||
// of a real one
|
||||
|
|
|
@ -1738,7 +1738,11 @@ public:
|
|||
/* Set if the frame is in a context where non-replaced blocks should
|
||||
* shrink-wrap (e.g., it's floating, absolutely positioned, or
|
||||
* inline-block). */
|
||||
eShrinkWrap = 1 << 0
|
||||
eShrinkWrap = 1 << 0,
|
||||
/* Set if we'd like to compute our 'auto' height, regardless of our actual
|
||||
* computed value of 'height'. (e.g. to get an intrinsic height for flex
|
||||
* items with "min-height: auto" to use during flexbox layout.) */
|
||||
eUseAutoHeight = 1 << 1
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
@font-face {
|
||||
font-family: "Ahem";
|
||||
src: url(../fonts/Ahem.ttf);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for behavior of the 'baseline' value for align-items and
|
||||
align-self.
|
||||
|
||||
NOTE: For multi-line 'display: block' elements in the testcase (and inline
|
||||
content that gets wrapped in an anonymous block), we add an inline-table
|
||||
wrapper here in the reference case, so that we get first-line baseline
|
||||
alignment instead of the last-line baseline-alignment that an inline-block
|
||||
would give us.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
display: block;
|
||||
border: 1px dashed blue;
|
||||
font: 14px sans-serif;
|
||||
}
|
||||
|
||||
div { display: inline-block; }
|
||||
table { display: inline-table; }
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font: 24px sans-serif;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.super {
|
||||
vertical-align: super;
|
||||
font-size: 12px;
|
||||
}
|
||||
.sub {
|
||||
vertical-align: sub;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.lime { background: lime; }
|
||||
.yellow { background: yellow; }
|
||||
.orange { background: orange; }
|
||||
.pink { background: pink; }
|
||||
.aqua { background: aqua; }
|
||||
.violet { background: violet; }
|
||||
.tan { background: tan; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="lime">blk_1line</div
|
||||
><table cellspacing="0" cellpadding="0"
|
||||
class="yellow">blk<br/>2lines</table
|
||||
><div class="orange"><span class="super">super</span></div
|
||||
><div class="pink"><span class="sub">sub</span></div
|
||||
><table cellspacing="0" cellpadding="0"
|
||||
class="aqua big">big<br/>text<br/>3lines</table
|
||||
><table class="violet" border="1">
|
||||
<tr><td>tr1</td></tr>
|
||||
<tr><td>tr2</td></tr></table
|
||||
><table class="tan" cellspacing="0" cellpadding="0">
|
||||
<i>ital<br/>ic</i>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for behavior of the 'baseline' value for align-items (and
|
||||
align-self, implicitly). This test baseline-aligns various types of
|
||||
content, and the flexbox's vertical size depends on the aggregate
|
||||
post-alignment height of its children.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
display: -moz-flex;
|
||||
-moz-align-items: baseline;
|
||||
border: 1px dashed blue;
|
||||
font: 14px sans-serif;
|
||||
}
|
||||
|
||||
i {
|
||||
/* XXXdholbert HACK - REMOVEME after bug 783415 lands (which will
|
||||
make this display:block conversion happen automatically). */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font: 24px sans-serif;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.super {
|
||||
vertical-align: super;
|
||||
font-size: 12px;
|
||||
}
|
||||
.sub {
|
||||
vertical-align: sub;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.lime { background: lime; }
|
||||
.yellow { background: yellow; }
|
||||
.orange { background: orange; }
|
||||
.pink { background: pink; }
|
||||
.aqua { background: aqua; }
|
||||
.violet { background: violet; }
|
||||
.tan { background: tan; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="lime">blk_1line</div>
|
||||
<div class="yellow">blk<br/>2lines</div>
|
||||
<div class="orange"><span class="super">super</span></div>
|
||||
<div class="pink"><span class="sub">sub</span></div>
|
||||
<div class="aqua big">big<br/>text<br/>3lines</div>
|
||||
<table class="violet" border="1">
|
||||
<tr><td>tr1</td></tr>
|
||||
<tr><td>tr2</td></tr></table>
|
||||
<i class="tan">ital<br/>ic</i>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for behavior of the 'baseline' value for align-items and
|
||||
align-self.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
display: block;
|
||||
border: 1px dashed blue;
|
||||
font: 14px sans-serif;
|
||||
}
|
||||
|
||||
input { height: 30px; }
|
||||
|
||||
textarea {
|
||||
width: 30px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
div.multilinebutton {
|
||||
/* For comparison vs. "button.multilinebutton" in the testcase: */
|
||||
-moz-appearance: button;
|
||||
display: block;
|
||||
text-align: center;
|
||||
font: 20px sans-serif;
|
||||
padding-top: 1px;
|
||||
height: 49px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
div { display: inline-block; }
|
||||
table { display: inline-table; }
|
||||
|
||||
.lime { background: lime; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="lime">text</div
|
||||
><input type="text" style="width: 20px; min-width: 0;" value="input"
|
||||
/><textarea style="width: 30px">t e x t a r e a </textarea
|
||||
><table cellspacing="0" cellpadding="0">
|
||||
<div class="multilinebutton">b<br/>t<br/>n</div>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for behavior of the 'baseline' value for align-items (and
|
||||
align-self, implicitly). This test baseline-aligns various types of
|
||||
content, and the flexbox's vertical size depends on the aggregate
|
||||
post-alignment height of its children.
|
||||
|
||||
This test checks baseline-alignment for a text <input> field, a
|
||||
<textarea>, and a <button> with a multi-line label.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
display: -moz-flex;
|
||||
-moz-align-items: baseline;
|
||||
border: 1px dashed blue;
|
||||
font: 14px sans-serif;
|
||||
}
|
||||
|
||||
input { height: 30px; }
|
||||
|
||||
textarea {
|
||||
width: 30px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
button.multilinebutton {
|
||||
font: 20px sans-serif;
|
||||
color: black;
|
||||
padding: 0;
|
||||
height: 50px;
|
||||
width: 20px;
|
||||
-moz-box-sizing: content-box;
|
||||
}
|
||||
|
||||
.lime { background: lime; }
|
||||
.orange { background: orange; }
|
||||
.pink { background: pink; }
|
||||
.aqua { background: aqua; }
|
||||
.violet { background: violet; }
|
||||
.tan { background: tan; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="lime">text</div>
|
||||
<input type="text" style="width: 20px; min-width: 0;" value="input"/>
|
||||
<textarea style="width: 30px">t e x t a r e a </textarea>
|
||||
<button class="multilinebutton">b<br/>t<br/>n</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for behavior of the 'baseline' value for align-items and
|
||||
align-self.
|
||||
|
||||
NOTE: For multi-line 'display: block' flex items in the testcase, we use
|
||||
an inline-table here in the reference case, so that we get first-line
|
||||
baseline alignment. (If we used an inline-block instead, that would give
|
||||
us *last-line* baseline alignment.)
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
display: block;
|
||||
border: 1px dashed blue;
|
||||
font: 14px sans-serif;
|
||||
}
|
||||
|
||||
div { display: inline-block; }
|
||||
table { display: inline-table; }
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font: 24px sans-serif;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.lime { background: lime; }
|
||||
.pink { background: pink; }
|
||||
.aqua { background: aqua; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="lime">text</div
|
||||
><button>btn</button
|
||||
><input type="radio"
|
||||
/><input type="checkbox"
|
||||
/><label class="pink">label</label
|
||||
><table cellspacing="0" cellpadding="0" class="aqua">
|
||||
<label>lab<br/>el</label>
|
||||
</table
|
||||
><table cellspacing="0" cellpadding="0">
|
||||
<fieldset>field<br/>set</fieldset>
|
||||
</table
|
||||
><table cellspacing="0" cellpadding="0">
|
||||
<fieldset><legend>leg</legend>field<br/>set</fieldset>
|
||||
</table
|
||||
><table cellspacing="0" cellpadding="0">
|
||||
<fieldset><legend>leg<br/>end</legend>field<br/>set</fieldset>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for behavior of the 'baseline' value for align-items (and
|
||||
align-self, implicitly). This test has various types of baseline-aligned
|
||||
content, and the flexbox's vertical size depends on the aggregate
|
||||
post-alignment height of its children.
|
||||
|
||||
This test checks baseline-alignment for text <button>, for
|
||||
various <input> fields, for <label>, and for <fieldset>.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
display: -moz-flex;
|
||||
-moz-align-items: baseline;
|
||||
border: 1px dashed blue;
|
||||
font: 14px sans-serif;
|
||||
}
|
||||
|
||||
input, label {
|
||||
/* XXXdholbert HACK - REMOVEME after bug 783415 lands (which will
|
||||
make this display:block conversion happen automatically). */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font: 24px sans-serif;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.lime { background: lime; }
|
||||
.pink { background: pink; }
|
||||
.aqua { background: aqua; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="lime">text</div>
|
||||
<button>btn</button>
|
||||
<input type="radio"/>
|
||||
<input type="checkbox"/>
|
||||
<label class="pink">label</label>
|
||||
<label class="aqua">lab<br/>el</label>
|
||||
<fieldset>field<br/>set</fieldset>
|
||||
<fieldset><legend>leg</legend>field<br/>set</fieldset>
|
||||
<fieldset><legend>leg<br/>end</legend>field<br/>set</fieldset>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using floated divs
|
||||
in place of flex items and using margin-top in place of the align-items /
|
||||
align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
height: 200px;
|
||||
width: 560px;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end" style="margin-top: 190px">end</div>
|
||||
<div class="flex-end big" style="margin-top: 100px">a b c d e f</div>
|
||||
<div class="center" style="margin-top: 95px">center</div>
|
||||
<div class="center big" style="margin-top: 50px">a b c d e f</div>
|
||||
<!-- We use inline-blocks inside of a wrapper-block as references for the
|
||||
baseline-aligned flex items, since inline-blocks get
|
||||
baseline-aligned in block layout. We also need to specify the widths
|
||||
manually here since the "flexbox > div" child-selector doesn't
|
||||
handle these guys (since they're grandchildren).
|
||||
-->
|
||||
<div style="width: 80px">
|
||||
<div class="baseline"
|
||||
style="width: 40px; display: inline-block">base</div
|
||||
><div class="baseline big"
|
||||
style="width: 40px; display: inline-block">abc</div>
|
||||
</div>
|
||||
<div class="stretch" style="height: 100%">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto" style="margin-top: 95px">auto</div>
|
||||
<div class="unspecified" style="margin-top: 95px">unspec</div>
|
||||
<div class="initial" style="margin-top: 95px">initial</div>
|
||||
<div class="inherit" style="margin-top: 190px">inherit</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
height: 200px;
|
||||
width: -moz-fit-content;
|
||||
display: -moz-flex;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
|
||||
/* Any children whose align-self is 'auto' (or unspecified, or
|
||||
-moz-initial) will end up taking this value from us: */
|
||||
-moz-align-items: center;
|
||||
|
||||
/* Any children whose align-self is 'inherit' will end up
|
||||
inheriting this value from us: */
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
-moz-align-self: auto;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
-moz-align-self: -moz-initial;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
-moz-align-self: inherit;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto">auto</div>
|
||||
<div class="unspecified">unspec</div>
|
||||
<div class="initial">initial</div>
|
||||
<div class="inherit">inherit</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using floated divs
|
||||
in place of flex items and using relative positioning in place of the
|
||||
align-items / align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
height: 200px;
|
||||
width: -moz-fit-content;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.flexbox > div { float: left }
|
||||
.flex-start, .flex-end, .center, .baseline, .stretch,
|
||||
.auto, .unspecified, .initial, .inherit {
|
||||
width: 40px;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
position: relative;
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end" style="top: 172px">end</div>
|
||||
<div class="flex-end big" style="top: 82px">a b c d e f</div>
|
||||
<div class="center" style="top: 86px">center</div>
|
||||
<div class="center big" style="top: 41px">a b c d e f</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<!-- We use inline-blocks inside of a wrapper-block as references for the
|
||||
baseline-aligned flex items, since inline-blocks get
|
||||
baseline-aligned in block layout. We also need to specify the widths
|
||||
manually here since the "flexbox > div" child-selector doesn't
|
||||
handle these guys (since they're grandchildren).
|
||||
-->
|
||||
<div style="width: -moz-fit-content;">
|
||||
<div class="baseline"
|
||||
style="display: inline-block">base</div
|
||||
><div class="baseline big"
|
||||
style="display: inline-block">abc</div>
|
||||
</div>
|
||||
<div class="stretch" style="height: 182px">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with
|
||||
margin/border/padding values on each item. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
height: 200px;
|
||||
width: -moz-fit-content;
|
||||
display: -moz-flex;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using floated divs
|
||||
in place of flex items and using relative positioning in place of the
|
||||
align-items / align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
padding: 3px;
|
||||
width: 560px;
|
||||
height: 4px;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end" style="position: relative; top: -6px">end</div>
|
||||
<div class="flex-end big" style="position: relative; top: -96px">a b c d e f</div>
|
||||
<div class="center" style="position: relative; top: -3px">center</div>
|
||||
<div class="center big" style="position: relative; top: -48px">a b c d e f</div>
|
||||
<!-- We use inline-blocks inside of a wrapper-block as references for the
|
||||
baseline-aligned flex items, since inline-blocks get
|
||||
baseline-aligned in block layout. We also need to specify the widths
|
||||
manually here since the "flexbox > div" child-selector doesn't
|
||||
handle these guys (since they're grandchildren).
|
||||
-->
|
||||
<div style="width: 80px">
|
||||
<div class="baseline"
|
||||
style="width: 40px; display: inline-block">base</div
|
||||
><div class="baseline big"
|
||||
style="width: 40px; display: inline-block">abc</div>
|
||||
</div>
|
||||
<div class="stretch" style="height: 100%">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto" style="height: 100%">auto</div>
|
||||
<div class="unspecified" style="height: 100%">unspec</div>
|
||||
<div class="initial" style="height: 100%">initial</div>
|
||||
<div class="inherit" style="height: 100%">inherit</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with the
|
||||
flex container being shorter than its items. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
padding: 3px;
|
||||
height: 4px;
|
||||
width: -moz-fit-content;
|
||||
display: -moz-flex;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
-moz-align-self: auto;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
-moz-align-self: -moz-initial;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
-moz-align-self: inherit;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto">auto</div>
|
||||
<div class="unspecified">unspec</div>
|
||||
<div class="initial">initial</div>
|
||||
<div class="inherit">inherit</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using floated divs
|
||||
in place of flex items and using relative positioning in place of the
|
||||
align-items / align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
padding: 3px;
|
||||
height: 4px;
|
||||
width: -moz-fit-content;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
font-family: sans-serif;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.flexbox > div { float: left }
|
||||
.flex-start, .flex-end, .center, .baseline, .stretch,
|
||||
.auto, .unspecified, .initial, .inherit {
|
||||
width: 40px;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
position: relative;
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end" style="top: -24px">end</div>
|
||||
<div class="flex-end big" style="top: -114px">a b c d e f</div>
|
||||
<div class="center" style="top: -12px">center</div>
|
||||
<div class="center big" style="top: -57px">a b c d e f</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<!-- We use inline-blocks inside of a wrapper-block as references for the
|
||||
baseline-aligned flex items, since inline-blocks get
|
||||
baseline-aligned in block layout. We also need to specify the widths
|
||||
manually here since the "flexbox > div" child-selector doesn't
|
||||
handle these guys (since they're grandchildren).
|
||||
-->
|
||||
<div style="width: -moz-fit-content;">
|
||||
<div class="baseline"
|
||||
style="display: inline-block">base</div
|
||||
><div class="baseline big"
|
||||
style="display: inline-block">abc</div>
|
||||
</div>
|
||||
<div class="stretch" style="height: 2px">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with the
|
||||
flex container being shorter than its items. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
padding: 3px;
|
||||
height: 4px;
|
||||
width: -moz-fit-content;
|
||||
display: -moz-flex;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
font-family: sans-serif;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
.big {
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
min-height: 2px;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior with auto
|
||||
margins in play. This reference case uses fixed margin-top values
|
||||
in place of the testcase's auto margins. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
height: 140px;
|
||||
width: 400px;
|
||||
display: -moz-flex;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.kidsAutoTop > div { margin-top: 130px; }
|
||||
.kidsAutoTop > div.big { margin-top: 60px; }
|
||||
.kidsAutoBoth > div { margin-top: 65px; }
|
||||
.kidsAutoBoth > div.big { margin-top: 30px; }
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.flexbox > div.big {
|
||||
height: 80px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox kidsAutoTop">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">a b c d e f</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
|
||||
<div class="flexbox kidsAutoBottom">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">a b c d e f</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
|
||||
<div class="flexbox kidsAutoBoth">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">a b c d e f</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior with auto margins in
|
||||
play (which should negate the effects of align-items / align-self,
|
||||
because there won't be any available space in which to align the item
|
||||
after the auto margins are resolved). -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
height: 140px;
|
||||
width: 400px;
|
||||
display: -moz-flex;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.kidsAutoTop > div { margin-top: auto; }
|
||||
.kidsAutoBottom > div { margin-bottom: auto; }
|
||||
.kidsAutoBoth > div { margin: auto 0; }
|
||||
|
||||
.flexbox > div {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.flexbox > div.big {
|
||||
height: 80px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox kidsAutoTop">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">a b c d e f</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
|
||||
<div class="flexbox kidsAutoBottom">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">a b c d e f</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
|
||||
<div class="flexbox kidsAutoBoth">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">a b c d e f</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using blocks in
|
||||
place of flex items and using float and width keywords to emulate the
|
||||
align-items / align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.flexbox > * {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
float: right;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
width: 100%;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
float: right;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto">auto</div>
|
||||
<div class="unspecified">unspec</div>
|
||||
<div class="initial">initial</div>
|
||||
<div class="inherit">inherit</div>
|
||||
<!-- Since that last div is floated right, the container doesn't include
|
||||
its height by default. So we add some invisible hacky text (of the
|
||||
same font) to make sure our container is tall enough. -->
|
||||
<span style="visibility:hidden">hacky text</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
font-size: 10px;
|
||||
|
||||
/* Any children whose align-self is 'auto' (or unspecified, or
|
||||
-moz-initial) will end up taking this value from us: */
|
||||
-moz-align-items: center;
|
||||
|
||||
/* Any children whose align-self is 'inherit' will end up
|
||||
inheriting this value from us: */
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
-moz-align-self: auto;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
-moz-align-self: -moz-initial;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
-moz-align-self: inherit;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto">auto</div>
|
||||
<div class="unspecified">unspec</div>
|
||||
<div class="initial">initial</div>
|
||||
<div class="inherit">inherit</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using blocks in
|
||||
place of flex items and using float and width keywords to emulate the
|
||||
align-items / align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
float: left;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.flex-start, .flex-end, .center, .baseline, .stretch {
|
||||
clear: both;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
float: left;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
width: -moz-max-content;
|
||||
float: right;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
width: -moz-max-content;
|
||||
float: left;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center">center</div>
|
||||
</div>
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center big">a b c d e f</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- NOTE: I'm not using floats in this flex container, since floating
|
||||
makes it harder to emulate the "stretch" behavior.
|
||||
(We need to use width: auto - not width:100% - because we need to
|
||||
allow space for our margins/borders/padding. And width:auto doesn't do
|
||||
what we want if we're floated.)
|
||||
|
||||
However, since our children aren't floated, their adjacent vertical
|
||||
margins will collapse by default (which doesn't match what happens in
|
||||
the testcase, since margins don't collapse in a flexbox). So, we
|
||||
compensate for that by bumping up the margin-top value on every
|
||||
collapsed top-margin to "4px", which is the total amount of vertical
|
||||
margin we're expecting between consecutive flex items in the testcase.
|
||||
(4px = 3px margin-bottom + 1px margin-top)
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big" style="margin-top: 4px">abc</div>
|
||||
<div class="stretch" style="margin-top: 4px">stretch</div>
|
||||
<div class="stretch big" style="margin-top: 4px">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with
|
||||
margin/border/padding values on each item. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
float: left;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using blocks in
|
||||
place of flex items and using float and width keywords to emulate the
|
||||
align-items / align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.flexbox > * {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
float: right;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b</div>
|
||||
<!-- We're using auto margins as references for "align-self:center", but
|
||||
auto margins only work if we've got extra space available, and we
|
||||
don't have extra space available in this case. So, we create a
|
||||
wide wrapper-div that _will_ have space available. We position
|
||||
that wrapper-div using a negative margin, so that centering in it
|
||||
is equivalent to centering in our reference for the flex container.
|
||||
-->
|
||||
<div style="width: 100px; margin-left: -48px">
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b</div>
|
||||
</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with the
|
||||
flex container being skinnier than its items. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior, using blocks in
|
||||
place of flex items and using float and width keywords to emulate the
|
||||
align-items / align-self properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
float: left;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.flex-start, .flex-end, .center, .baseline, .stretch {
|
||||
clear: both;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
float: left;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
width: -moz-max-content;
|
||||
float: right;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big" style="margin-bottom: 4px">a b</div>
|
||||
<!-- We're using auto margins as references for "align-self:center", but
|
||||
auto margins only work if we've got extra space available, and we
|
||||
don't have extra space available in this case. So, we create a
|
||||
wide wrapper-div that _will_ have space available. We position
|
||||
that wrapper-div using a negative margin, so that centering in it
|
||||
is equivalent to centering in our reference for the flex container.
|
||||
-->
|
||||
<div style="width: 100px; margin-left: -48px">
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center">center</div>
|
||||
</div>
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center big" style="margin-top: 4px">a b</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- NOTE: I'm not using floats in this flex container, since floating
|
||||
makes it harder to emulate the "stretch" behavior.
|
||||
(We need to use width: auto - not width:100% - because we need to
|
||||
allow space for our margins/borders/padding. And width:auto doesn't do
|
||||
what we want if we're floated.)
|
||||
|
||||
However, since our children aren't floated, their adjacent vertical
|
||||
margins will collapse by default (which doesn't match what happens in
|
||||
the testcase, since margins don't collapse in a flexbox). So, we
|
||||
compensate for that by bumping up the margin-top value on every
|
||||
collapsed top-margin to "4px", which is the total amount of vertical
|
||||
margin we're expecting between consecutive flex items in the testcase.
|
||||
(4px = 3px margin-bottom + 1px margin-top)
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big" style="margin-top: 4px">abc</div>
|
||||
<div class="stretch" style="margin-top: 4px">stretch</div>
|
||||
<div class="stretch big" style="margin-top: 4px">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with the
|
||||
flex container being skinnier than its items. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
float: left;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior in a vertical
|
||||
"direction: rtl" flex container, using blocks in place of flex items and
|
||||
using float and width keywords to emulate the align-items / align-self
|
||||
properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
direction: rtl;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.flexbox > * {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
float: left;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
width: 100%;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
float: left;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto">auto</div>
|
||||
<div class="unspecified">unspec</div>
|
||||
<div class="initial">initial</div>
|
||||
<div class="inherit">inherit</div>
|
||||
<!-- Since that last div is floated right, the container doesn't include
|
||||
its height by default. So we add some invisible hacky text (of the
|
||||
same font) to make sure our container is tall enough. -->
|
||||
<span style="visibility:hidden">hacky text</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with
|
||||
"direction: rtl" to swap the horizontal (cross) axis. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
direction: rtl;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
|
||||
/* Any children whose align-self is 'auto' (or unspecified, or
|
||||
-moz-initial) will end up taking this value from us: */
|
||||
-moz-align-items: center;
|
||||
|
||||
/* Any children whose align-self is 'inherit' will end up
|
||||
inheriting this value from us: */
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
.auto {
|
||||
background: yellow;
|
||||
-moz-align-self: auto;
|
||||
}
|
||||
.unspecified {
|
||||
background: lightgreen;
|
||||
}
|
||||
.initial {
|
||||
background: aqua;
|
||||
-moz-align-self: -moz-initial;
|
||||
}
|
||||
.inherit {
|
||||
background: violet;
|
||||
-moz-align-self: inherit;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
<div class="auto">auto</div>
|
||||
<div class="unspecified">unspec</div>
|
||||
<div class="initial">initial</div>
|
||||
<div class="inherit">inherit</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior in a vertical
|
||||
"direction: rtl" flex container, using blocks in place of flex items and
|
||||
using float and width keywords to emulate the align-items / align-self
|
||||
properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
float: left;
|
||||
direction: rtl;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.flex-start, .flex-end, .center, .baseline, .stretch {
|
||||
clear: both;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
float: right;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
width: -moz-max-content;
|
||||
float: left;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
width: -moz-max-content;
|
||||
float: right;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center">center</div>
|
||||
</div>
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center big">a b c d e f</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- NOTE: I'm not using floats in this flex container, since floating
|
||||
makes it harder to emulate the "stretch" behavior.
|
||||
(We need to use width: auto - not width:100% - because we need to
|
||||
allow space for our margins/borders/padding. And width:auto doesn't do
|
||||
what we want if we're floated.)
|
||||
|
||||
However, since our children aren't floated, their adjacent vertical
|
||||
margins will collapse by default (which doesn't match what happens in
|
||||
the testcase, since margins don't collapse in a flexbox). So, we
|
||||
compensate for that by bumping up the margin-top value on every
|
||||
collapsed top-margin to "4px", which is the total amount of vertical
|
||||
margin we're expecting between consecutive flex items in the testcase.
|
||||
(4px = 3px margin-bottom + 1px margin-top)
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big" style="margin-top: 4px">abc</div>
|
||||
<div class="stretch" style="margin-top: 4px">stretch</div>
|
||||
<div class="stretch big" style="margin-top: 4px">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with
|
||||
margin/border/padding values on each, and with "direction: rtl" to swap
|
||||
the horizontal (cross) axis item. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
direction: rtl;
|
||||
float: left;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b c d e f</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b c d e f</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b c d e f</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b c d e f</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior in a vertical
|
||||
"direction: rtl" flex container, using blocks in place of flex items and
|
||||
using float and width keywords to emulate the align-items / align-self
|
||||
properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
font-family: sans-serif;
|
||||
direction: rtl;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.flexbox > * {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
float: left;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
margin: auto;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b</div>
|
||||
<!-- We're using auto margins as references for "align-self:center", but
|
||||
auto margins only work if we've got extra space available, and we
|
||||
don't have extra space available in this case. So, we create a
|
||||
wide wrapper-div that _will_ have space available. We position
|
||||
that wrapper-div using a negative margin, so that centering in it
|
||||
is equivalent to centering in our reference for the flex container.
|
||||
-->
|
||||
<div style="width: 100px; margin-right: -48px">
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b</div>
|
||||
</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with the
|
||||
flex container being skinnier than its items, and with "direction: rtl" to
|
||||
swap the horizontal (cross) axis. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
direction: rtl;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b</div>
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Reference case for align-items / align-self behavior in a vertical
|
||||
"direction: rtl" flex container, using blocks in place of flex items and
|
||||
using float and width keywords to emulate the align-items / align-self
|
||||
properties. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
float: left;
|
||||
direction: rtl;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.flex-start, .flex-end, .center, .baseline, .stretch {
|
||||
clear: both;
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
width: -moz-max-content;
|
||||
float: left;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
width: -moz-max-content;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- NOTE: I'm not using floats in this flex container, since floating
|
||||
makes it harder to emulate the "stretch" behavior.
|
||||
(We need to use width: auto - not width:100% - because we need to
|
||||
allow space for our margins/borders/padding. And width:auto doesn't do
|
||||
what we want if we're floated.)
|
||||
|
||||
However, since our children aren't floated, their adjacent vertical
|
||||
margins will collapse by default (which doesn't match what happens in
|
||||
the testcase, since margins don't collapse in a flexbox). So, we
|
||||
compensate for that by bumping up the margin-top value on every
|
||||
collapsed top-margin to "4px", which is the total amount of vertical
|
||||
margin we're expecting between consecutive flex items in the testcase.
|
||||
(4px = 3px margin-bottom + 1px margin-top)
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big" style="margin-top: 4px">a b</div>
|
||||
<div class="stretch" style="margin-top: 4px">stretch</div>
|
||||
<div class="stretch big" style="margin-top: 4px">a b</div>
|
||||
<!-- We're using auto margins as references for "align-self:center", but
|
||||
auto margins only work if we've got extra space available, and we
|
||||
don't have extra space available in this case. So, we create a
|
||||
wide wrapper-div that _will_ have space available. We position
|
||||
that wrapper-div using a negative margin, so that centering in it
|
||||
is equivalent to centering in our reference for the flex container.
|
||||
-->
|
||||
<div style="width: 100px; margin-right: -48px">
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center" style="margin-top: 4px">center</div>
|
||||
</div>
|
||||
<div style="margin: auto; width: -moz-max-content">
|
||||
<div class="center big" style="margin-top: 4px">a b</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big" style="margin-top: 4px">abc</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase for align-items / align-self behavior, with all the possible
|
||||
values included on different items within a flex container, and with the
|
||||
flex container being skinnier than its items, and with "direction: rtl" to
|
||||
swap the horizontal (cross) axis. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 4px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
direction: rtl;
|
||||
float: left;
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.flexbox > div {
|
||||
margin: 1px 2px 3px 4px;
|
||||
border-width: 2px 3px 4px 5px;
|
||||
padding: 3px 4px 5px 6px;
|
||||
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
div.big {
|
||||
font-size: 18px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Classes for each of the various align-self values */
|
||||
.flex-start {
|
||||
background: lime;
|
||||
-moz-align-self: flex-start;
|
||||
}
|
||||
.flex-end {
|
||||
background: orange;
|
||||
-moz-align-self: flex-end;
|
||||
}
|
||||
.center {
|
||||
background: lightblue;
|
||||
-moz-align-self: center;
|
||||
}
|
||||
.baseline {
|
||||
background: teal;
|
||||
-moz-align-self: baseline;
|
||||
}
|
||||
.stretch {
|
||||
background: pink;
|
||||
-moz-align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- (NOTE: this test has the "stretch" divs and the "flex-end" divs
|
||||
swapped in the ordering, with respect to the other
|
||||
flexbox-align-self-* testcases. That's because "stretch" and
|
||||
"flex-end" overflow in opposite directions, and in RTL mode (with 2
|
||||
flex containers side by side), they overflow *at* each other and
|
||||
overlap. If we swap them, they float away from each other and we can
|
||||
still see them.) -->
|
||||
<div class="flexbox">
|
||||
<div class="flex-start">start</div>
|
||||
<div class="flex-start big">a b</div>
|
||||
<div class="stretch">stretch</div>
|
||||
<div class="stretch big">a b</div>
|
||||
<div class="center">center</div>
|
||||
<div class="center big">a b</div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="baseline">base</div>
|
||||
<div class="baseline big">abc</div>
|
||||
<div class="flex-end">end</div>
|
||||
<div class="flex-end big">a b</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div { height: 100px; }
|
||||
div.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
}
|
||||
div.a {
|
||||
display: inline-block;
|
||||
background: lightgreen;
|
||||
}
|
||||
div.b {
|
||||
display: inline-block;
|
||||
background: yellow;
|
||||
}
|
||||
div.c {
|
||||
display: inline-block;
|
||||
background: orange;
|
||||
}
|
||||
div.auto {
|
||||
display: inline-block;
|
||||
background: pink;
|
||||
}
|
||||
div.inflex {
|
||||
display: inline-block;
|
||||
background: gray;
|
||||
}
|
||||
div.spacer {
|
||||
height: 15px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="width: 80px"/><div class="b" style="width: 120px"/>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="width: 62.5px"/><div class="c" style="width: 137.5px"/>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="width: 185px"/><div class="auto">
|
||||
<div class="spacer" style="width: 15px"/></div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="b" style="width: 76px"/><div class="c" style="width: 124px"/>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="b" style="width: 170px"/><div class="auto">
|
||||
<div class="spacer" style="width: 30px"/></div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="width: 45px"/><div class="b" style="width: 50px"
|
||||
/><div class="inflex" style="width: 20px"/><div class="c" style="width: 85px"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase with blocks as flex items in a horizontal flex container, with
|
||||
various "flex" values and various combinations of the items. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div { height: 100px; }
|
||||
div.flexbox {
|
||||
border: 1px dashed blue;
|
||||
width: 200px;
|
||||
font-size: 10px;
|
||||
display: -moz-flex;
|
||||
}
|
||||
div.a {
|
||||
-moz-flex: 1 0 30px;
|
||||
background: lightgreen;
|
||||
}
|
||||
div.b {
|
||||
-moz-flex: 2 0 20px;
|
||||
background: yellow;
|
||||
}
|
||||
div.c {
|
||||
-moz-flex: 3 0 40px;
|
||||
background: orange;
|
||||
}
|
||||
div.flexNone {
|
||||
-moz-flex: none;
|
||||
background: pink;
|
||||
}
|
||||
div.flexBasis {
|
||||
-moz-flex: 0 0 20px;
|
||||
background: gray;
|
||||
}
|
||||
div.spacer {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox"><div class="a"></div><div class="b"/></div>
|
||||
<div class="flexbox"><div class="a"/><div class="c"/></div>
|
||||
<div class="flexbox"><div class="a"/>
|
||||
<div class="flexNone"><div class="spacer"/></div>
|
||||
</div>
|
||||
<div class="flexbox"><div class="b"/><div class="c"/></div>
|
||||
<div class="flexbox"><div class="b"/>
|
||||
<div class="flexNone"><div class="spacer"/><div class="spacer"/></div>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<div class="a"/><div class="b"/><div class="flexBasis"/><div class="c"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div { width: 50px; }
|
||||
div.flexbox {
|
||||
border: 1px dashed blue;
|
||||
float: left;
|
||||
}
|
||||
div.a {
|
||||
background: lightgreen;
|
||||
}
|
||||
div.b {
|
||||
background: yellow;
|
||||
}
|
||||
div.c {
|
||||
background: orange;
|
||||
}
|
||||
div.auto {
|
||||
background: pink;
|
||||
}
|
||||
div.inflex {
|
||||
background: gray;
|
||||
}
|
||||
div.spacer {
|
||||
width: 15px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="height: 80px"/><div class="b" style="height: 120px"/>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="height: 62.5px"/><div class="c" style="height: 137.5px"/>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="height: 185px"/><div class="auto">
|
||||
<div class="spacer" style="height: 15px"/></div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="b" style="height: 76px"/><div class="c" style="height: 124px"/>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="b" style="height: 170px"/><div class="auto">
|
||||
<div class="spacer" style="height: 30px"/></div>
|
||||
</div>
|
||||
<div class="flexbox">
|
||||
<div class="a" style="height: 45px"/><div class="b" style="height: 50px"
|
||||
/><div class="inflex" style="height: 20px"/><div class="c" style="height: 85px"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!-- Testcase with blocks as flex items in a vertical flex container, with
|
||||
various "flex" values and various combinations of the items. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div { width: 50px; }
|
||||
div.flexbox {
|
||||
float: left;
|
||||
border: 1px dashed blue;
|
||||
height: 200px;
|
||||
font-size: 10px;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
}
|
||||
div.a {
|
||||
-moz-flex: 1 0 30px;
|
||||
background: lightgreen;
|
||||
}
|
||||
div.b {
|
||||
-moz-flex: 2 0 20px;
|
||||
background: yellow;
|
||||
}
|
||||
div.c {
|
||||
-moz-flex: 3 0 40px;
|
||||
background: orange;
|
||||
}
|
||||
div.flexNone {
|
||||
-moz-flex: none;
|
||||
background: pink;
|
||||
}
|
||||
div.flexBasis {
|
||||
-moz-flex: 0 0 20px;
|
||||
background: gray;
|
||||
}
|
||||
div.spacer {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox"><div class="a"></div><div class="b"/></div>
|
||||
<div class="flexbox"><div class="a"/><div class="c"/></div>
|
||||
<div class="flexbox"><div class="a"/>
|
||||
<div class="flexNone"><div class="spacer"/></div>
|
||||
</div>
|
||||
<div class="flexbox"><div class="b"/><div class="c"/></div>
|
||||
<div class="flexbox"><div class="b"/>
|
||||
<div class="flexNone"><div class="spacer"/><div class="spacer"/></div>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<div class="a"/><div class="b"/><div class="flexBasis"/><div class="c"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
width: 200px;
|
||||
background: lightgreen;
|
||||
margin-bottom: 5px;
|
||||
line-height: 8px;
|
||||
}
|
||||
canvas {
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
border: 1px dotted green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox" style="height: 22px">
|
||||
some words
|
||||
<canvas style="float:right"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 122.5px"
|
||||
/><canvas style="width: 73.5px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 93px"
|
||||
/><canvas style="width: 103px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 114px"
|
||||
/><canvas style="width: 82px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 106px"
|
||||
/><canvas style="width: 90px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 46px"
|
||||
/><canvas style="width: 150px"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!--
|
||||
This test checks that canvas elements behave correctly as flex items.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
width: 200px;
|
||||
background: lightgreen;
|
||||
display: -moz-flex;
|
||||
-moz-justify-content: space-between;
|
||||
margin-bottom: 5px;
|
||||
line-height: 8px;
|
||||
}
|
||||
canvas {
|
||||
min-width: 0;
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
border: 1px dotted green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- A) One flex item -->
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
|
||||
<!-- B) Text and a canvas (ensure they aren't merged into one flex item) -->
|
||||
<div class="flexbox">
|
||||
some words <canvas/>
|
||||
</div>
|
||||
|
||||
<!-- C) Two canvas elements, getting stretched by different ratios, from 0.
|
||||
|
||||
Space-to-be-distributed = 200px - borders = 200 - (1 + 1) - (1 + 1)
|
||||
= 196px
|
||||
|
||||
1st element gets 5/8 of space: 5/8 * 196px = 122.5px
|
||||
1st element gets 3/8 of space: 3/8 * 196px = 73.5px
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="-moz-flex: 5"/>
|
||||
<canvas style="-moz-flex: 3"/>
|
||||
</div>
|
||||
|
||||
<!-- D) Two canvas elements, getting stretched by different ratios, from
|
||||
different flex bases.
|
||||
|
||||
Space-to-be-distributed = 200px - (33 + 1 + 1) - (13 + 1 + 1) = 150px
|
||||
1st element gets 2/5 of space: 33px + 2/5 * 150px = 93px
|
||||
1st element gets 3/5 of space: 13px + 3/5 * 150px = 103px
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 33px; -moz-flex: 2 auto"/>
|
||||
<canvas style="width: 13px; -moz-flex: 3 auto"/>
|
||||
</div>
|
||||
|
||||
<!-- E) Two flex items, getting shrunk by different amounts.
|
||||
|
||||
Space-to-be-distributed = 200px - (150 + 1 + 1) - (100 + 1 + 1) = -54px
|
||||
First element scaled flex ratio = 4 * 150 = 600
|
||||
Second element scaled flex ratio = 3 * 100 = 300
|
||||
* So, total flexibility is 600 + 300 = 900
|
||||
|
||||
1st element gets 600/900 of space: 150 + 600/900 * -54 = 114px
|
||||
2nd element gets 300/900 of space: 100 + 300/900 * -54 = 82px
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 150px; -moz-flex: 1 4 auto"/>
|
||||
<canvas style="width: 100px; -moz-flex: 1 3 auto"/>
|
||||
</div>
|
||||
|
||||
<!-- F) Making sure we don't grow past max-width -->
|
||||
<!-- Same as (D), except we've added a max-width on the second element.
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 33px; -moz-flex: 2 auto"/>
|
||||
<canvas style="width: 13px; max-width: 90px; -moz-flex: 3 auto"/>
|
||||
</div>
|
||||
|
||||
<!-- G) Making sure we grow at least as large as min-width -->
|
||||
<!-- Same as (C), except we've added a min-width on the second element.
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="width: 33px; -moz-flex: 2 auto"/>
|
||||
<canvas style="width: 13px; min-width: 150px; -moz-flex: 3 auto"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
border: 2px dotted black;
|
||||
display: -moz-flex;
|
||||
}
|
||||
canvas {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!--
|
||||
This test checks that canvas elements' default "min-width: auto" property
|
||||
is handled correctly (i.e. isn't influenced by the "width" property).
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
border: 2px dotted black;
|
||||
display: -moz-flex;
|
||||
}
|
||||
canvas {
|
||||
width: 1000px;
|
||||
height: 50px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
height: 200px;
|
||||
width: -moz-min-content;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
background: lightgreen;
|
||||
line-height: 0;
|
||||
}
|
||||
canvas {
|
||||
width: 20px;
|
||||
height: 10px;
|
||||
border: 1px dotted green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<div style="font: 8px monospace; height: 188px">
|
||||
a b
|
||||
</div>
|
||||
<canvas/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 122.5px"
|
||||
/><canvas style="height: 73.5px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 93px"
|
||||
/><canvas style="height: 103px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 114px"
|
||||
/><canvas style="height: 82px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 106px"
|
||||
/><canvas style="height: 90px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 46px"
|
||||
/><canvas style="height: 150px"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!--
|
||||
This test checks that canvas elements behave correctly as flex items.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
height: 200px;
|
||||
background: lightgreen;
|
||||
display: -moz-flex;
|
||||
-moz-justify-content: space-between;
|
||||
-moz-flex-direction: column;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
font: 8px monospace;
|
||||
}
|
||||
canvas {
|
||||
width: 20px;
|
||||
height: 10px;
|
||||
min-height: 0;
|
||||
border: 1px dotted green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- A) One flex item -->
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
|
||||
<!-- B) Text and a canvas (ensure they aren't merged into one flex item) -->
|
||||
<div class="flexbox">
|
||||
a b <canvas/>
|
||||
</div>
|
||||
|
||||
<!-- C) Two canvas elements, getting stretched by different ratios, from 0.
|
||||
|
||||
Space-to-be-distributed = 200px - borders = 200 - (1 + 1) - (1 + 1)
|
||||
= 196px
|
||||
|
||||
1st element gets 5/8 of space: 5/8 * 196px = 122.5px
|
||||
1st element gets 3/8 of space: 3/8 * 196px = 73.5px
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="-moz-flex: 5"/>
|
||||
<canvas style="-moz-flex: 3"/>
|
||||
</div>
|
||||
|
||||
<!-- D) Two canvas elements, getting stretched by different ratios, from
|
||||
different flex bases.
|
||||
|
||||
Space-to-be-distributed = 200px - (33 + 1 + 1) - (13 + 1 + 1) = 150px
|
||||
1st element gets 2/5 of space: 33px + 2/5 * 150px = 93px
|
||||
1st element gets 3/5 of space: 13px + 3/5 * 150px = 103px
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 33px; -moz-flex: 2 auto"/>
|
||||
<canvas style="height: 13px; -moz-flex: 3 auto"/>
|
||||
</div>
|
||||
|
||||
<!-- E) Two flex items, getting shrunk by different amounts.
|
||||
|
||||
Space-to-be-distributed = 200px - (150 + 1 + 1) - (100 + 1 + 1) = -54px
|
||||
First element scaled flex ratio = 4 * 150 = 600
|
||||
Second element scaled flex ratio = 3 * 100 = 300
|
||||
* So, total flexibility is 600 + 300 = 900
|
||||
|
||||
1st element gets 600/900 of space: 150 + 600/900 * -54 = 114px
|
||||
2nd element gets 300/900 of space: 100 + 300/900 * -54 = 82px
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 150px; -moz-flex: 1 4 auto"/>
|
||||
<canvas style="height: 100px; -moz-flex: 1 3 auto"/>
|
||||
</div>
|
||||
|
||||
<!-- F) Making sure we don't grow past max-height -->
|
||||
<!-- Same as (D), except we've added a max-height on the second element.
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 33px; -moz-flex: 2 auto"/>
|
||||
<canvas style="height: 13px; max-height: 90px; -moz-flex: 3 auto"/>
|
||||
</div>
|
||||
|
||||
<!-- G) Making sure we grow at least as large as min-height -->
|
||||
<!-- Same as (C), except we've added a min-height on the second element.
|
||||
-->
|
||||
<div class="flexbox">
|
||||
<canvas style="height: 33px; -moz-flex: 2 auto"/>
|
||||
<canvas style="height: 13px; min-height: 150px; -moz-flex: 3 auto"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
border: 2px dotted black;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
}
|
||||
canvas {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!--
|
||||
This test checks that canvas elements' default "min-height: auto" property
|
||||
is handled correctly (i.e. isn't influenced by the "height" property).
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
border: 2px dotted black;
|
||||
display: -moz-flex;
|
||||
-moz-flex-direction: column;
|
||||
}
|
||||
canvas {
|
||||
width: 50px;
|
||||
height: 1000px;
|
||||
background: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<canvas/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style>
|
||||
div.flexbox {
|
||||
width: 200px;
|
||||
background: lightgreen;
|
||||
margin-bottom: 5px;
|
||||
line-height: 8px;
|
||||
clear: all;
|
||||
height: 22px;
|
||||
}
|
||||
fieldset {
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
border: 1px dotted green;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flexbox">
|
||||
<fieldset/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox" style="height: 22px">
|
||||
some words
|
||||
<fieldset style="float:right"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<fieldset style="width: 122.5px"
|
||||
/><fieldset style="width: 73.5px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<fieldset style="width: 93px"
|
||||
/><fieldset style="width: 103px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<fieldset style="width: 114px"
|
||||
/><fieldset style="width: 82px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<fieldset style="width: 106px"
|
||||
/><fieldset style="width: 90px"/>
|
||||
</div>
|
||||
|
||||
<div class="flexbox">
|
||||
<fieldset style="width: 46px"
|
||||
/><fieldset style="width: 150px"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче