This commit is contained in:
Mounir Lamouri 2012-09-30 13:19:15 +01:00
Родитель 0b91d0029f a3cb31110c
Коммит 38529e6433
289 изменённых файлов: 22640 добавлений и 175 удалений

Просмотреть файл

@ -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()));
},

Просмотреть файл

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

54
gfx/2d/Scale.cpp Normal file
Просмотреть файл

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

36
gfx/2d/Scale.h Normal file
Просмотреть файл

@ -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_ */

357
gfx/2d/basictypes.h Normal file
Просмотреть файл

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

864
gfx/2d/convolver.cpp Normal file
Просмотреть файл

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

166
gfx/2d/convolver.h Normal file
Просмотреть файл

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

42
gfx/2d/cpu.h Normal file
Просмотреть файл

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

536
gfx/2d/image_operations.cpp Normal file
Просмотреть файл

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

133
gfx/2d/image_operations.h Normal file
Просмотреть файл

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

63
gfx/2d/port.h Normal file
Просмотреть файл

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

253
gfx/2d/stack_container.h Normal file
Просмотреть файл

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

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше