From 0b93aaef9b3c02fdb390ab38b01f9d5712ab5381 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Sat, 28 Oct 2023 00:10:42 +0800 Subject: [PATCH 01/12] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1194466. Use the table-outer frame's margin when requesting the margin for table element with getBoxQuads. r=mats (cb73af6b17) - Backed out changeset 3f51676191a6 (bug 1225703) for cpp unittest failures on at least Windows CLOSED TREE (833dc52434) - Bug 1225703 - Update in-tree libcubeb. r=padenot (103b1296c1) - Bug 1236977 - Default initialize IMMDevice temporary to avoid potential garbage CloseHandle on error. r=padenot (fdb076c905) - Bug 1221228 - Work around busted OpenSL causing hangs/reboots on Android r=padenot (a9f8c9e5dd) - Bug 1104643 - Interpolate the audio clock by system clock if the qudio clock is not going forward. r=padenot (180c4ae7c7) - Bug 1241476: Update libcubeb from upstream rs=kinetik (f68d4010e9) - Bug 1242152 - fix sndio build after full-duplex API changes r=kinetik (cd30999c19) - Bug 1243275: Update libcubeb to pick up device-change-notification changes and a pulse fix rs=kinetik (1c5bf75ac2) - Bug 1189197 - enqueue a silent frame to kick off the buffer queue callbacks. r=kinetik. (98a312ec62) - Bug 948267. Part 1 - add the interface DataSource to implement pull model and remove members no longer useful in the pull model. r=kinetik. (8ff0635fca) - Bug 948267. Part 2 - implement AudioStream::DataSource for DecodedAudioDataSink and remove its audio thread. r=kinetik. (00365c71b8) - Bug 948267. Part 3 - remove MDSM::AdjustAudioThresholds() to ensure we have enough data for AudioStream::DataCallback() to consume in the audio-only case. Also increase the amount of prerolling audio to 1s (which is the size of the circular buffer of AudioStream) to ensure a smooth start of playback. r=kinetik. (00f1bfbe02) - Bug 1240417. Part 1 - add a writer class to encapsulate pointer arithmetic. r=kinetik. (a625befc30) - Bug 1240417. Part 2 - remove unused code. r=kinetik. (086c6c461b) - Bug 1240419 - improve logging macros and include |this| in the log message. r=kinetik. (a0242c4e78) - Bug 1240420. Part 1 - move checks of mismatched sample rate or channel numbers to AudioStream. r=kinetik. (41435e9551) - Bug 1240420. Part 2 - fix warnings of signed/unsigned comparison. r=kinetik. (558ce6a918) - Bug 1230902 - initialize mSampleRate and mMicrophoneActive. r=cpearce (a99b661e8b) - Bug 1203585 - Add comments about threading and locking on GraphDriver's members. r=jesup (5dcaa286c3) - Bug 1203585 - Remove some dead code in GraphDriver.cpp. r=jesup (9618d10e70) - Bug 1070216 - Assert main MediaEngine APIs are called on the owning thread. r=jib (6c950d62d0) - Bug 1221587: allow getUserMedia to use full-duplex cubeb streams r=padenot (f67c2219aa) - Bug 1221587: Rename MediaStreamGraphShutdownThreadRunnable2 r=padenot (d9fe2125e7) - Bug 1203585 - Add new methods to GraphDriver to assert that locks are held. r=jesup (a8e2bfbcef) - Bug 1221587: Base update of the MSG API for full-duplex r=padenot (4be37a6184) - Bug 1221587: change audio listeners for full-duplex audio r=padenot (5d94102a32) - Bug 1221587: use cubeb devids to select input devices r=padenot (2f0806756b) - Bug 1221587: Update for API changes in cubeb r=padenot (87c581ce5f) - Bug 1237414: Switch AsyncCubebOperation to a SharedThreadPool r=padenot (965831dac8) - Bug 1203585 - Add a comment block on how MediaStreamGraph switch GraphDrivers. r=jesup (a4839dccb1) - Bug 1240411: P7. Clean up webspeech header declarations. r=rillian (2e91bebdb5) - Bug 1207220: Ensure MediaShutdownManager waits until all MediaDecoder have completed their shutdown. r=cpearce (1782ffb35a) - Bug 1240411: P8. Clean up MediaSource headers. r=cpearce Remove redundant virtual keyword and add missing override if any. (0f99efac9e) - Bug 1240411: P9. Clean up media headers. r=jwwang (cc1165a3f2) - Bug 1203585 - Update the MediaStreamGraph code to lock properly. r=jesup (d523db643a) - Bug 1221587: Implement switching of AudioCallbackDrivers for full-duplex r=padenot (2349ec7734) - Bug 1221587: stall MSG final shutdown until AudioCallbackDriver shutdown has finished r=pehrsons (ad2dfb76dd) - Bug 1203585 - Add threading assertions to GraphDriver switching methods. r=jesup (8c1c2b4d9d) - Bug 1221587: Improve logging of callback driver/switching r=padenot (87253e8f9c) - Bug 1237794: Extend ClearOnShutdown() to allow specifying the shutdown phase r=froyd (cfe5e30311) - Bug 1221587: add per-platform prefs to control full-duplex cubeb input r=jib (0b004cfd10) - Bug 1229240 - test that applyConstraints() rejects on non-Gum track. r=jesup (c65f7ecc4a) - Bug 987186 - remove AudioConfig, send agc/aec/noise from prefs r=jib,smaug (812108e255) - Bug 1245216: plumb preferred sample rate from full_duplex cubeb through NotifyInput/Output r=padenot (9c123866cc) - Bug 1245216: Fix getUserMedia input in full_duplex mode coming from the wrong place r=padenot (8d10177675) - Bug 1227407 - Ensure Cameras is alive before calling through it. r=jesup (e94d7803ee) - Bug 1239384 - Remove static interface for Cameras using forwarding. r=jesup (e5329280c5) - Bug 1239384 - Encapsulate Cameras dispatch, locking and success handling in a class. r=jesup (a0c2391c44) - Bug 1177242 - Verify whether sandboxed Content process has permissions to access the camera/mic. r=jesup (611c4cb04e) - Bug 1247236. r=jesup (fc0286ccb3) - Bug 1239873 - Use AsyncShutdown API to shut down MediaStreamGraph thread. r=jesup (9e45760ab9) - Bug 1247395 - use UniquePtr for control messages in MediaStreamGraphImpl; r=roc (949149234e) - Bug 1221587: Block attempts to open two mics at once until supported in full-duplex r=jib (4bb1412bb1) - Bug 1242061: re-enumerate audio devices in full_duplex via cubeb when getUserMedia is called r=jib (aa98e4dfff) - Bug 1242061 follow up to fix static analysis build bustage. r=me (3ee4f733f1) - Bug 1242061: fix leaked static nsTArrays r=bustage on a CLOSED TREE (6f57c3e0fd) - Bug 1237816: count open input sources for MediaStreams to release inputs on Destroy() r=roc,padenot (e9fd08c6bd) - Bug 1245216: white-list the fake 440Hz audio source used in automation for getUserMedia enumeration r=padenot (5c1632f0bd) - Bug 1242061: remove small strdup() leak of devicename strings in getUserMedia enumeration rs=jib (112163f77c) - Bug 1245216: Avoid reallocating and leaking AudioPacketizer output buffer r=padenot (86c0a5c277) - Bug 1165963 - Fix regression by bug 1104643: Detect "over compensation" and reset the anchor. r=padenot (9393a44d1b) - Bug 1145195 part 1 - Create a helper function for PrependLocalTransformsTo in SVGContentUtils r=dholbert (91bcee16ad) - Bug 1138065 - view elements as fragment identifiers should have normal target matching. r=dholbert (17b6b7af57) - Bug 1145195 part 2 - SVGFragmentIdentifier::ProcessSVGViewSpec() shouldn't actually let #svgView() affect attribute values r=dholbert (e7663e08c2) --- dom/media/AudioSegment.h | 6 +- dom/media/AudioStream.cpp | 321 ++++------ dom/media/AudioStream.h | 174 ++++-- dom/media/AudioStreamTrack.h | 6 +- dom/media/AudioTrack.h | 6 +- dom/media/AudioTrackList.h | 4 +- dom/media/BufferMediaResource.h | 58 +- dom/media/CanvasCaptureMediaStream.cpp | 2 +- dom/media/CanvasCaptureMediaStream.h | 2 +- dom/media/CubebUtils.h | 2 + dom/media/GetUserMediaRequest.h | 3 +- dom/media/GraphDriver.cpp | 392 +++++++----- dom/media/GraphDriver.h | 226 ++++--- dom/media/MediaDecoder.cpp | 22 +- dom/media/MediaDecoder.h | 4 +- dom/media/MediaDecoderStateMachine.cpp | 39 +- dom/media/MediaDecoderStateMachine.h | 14 +- dom/media/MediaInfo.h | 16 +- dom/media/MediaManager.cpp | 185 +++--- dom/media/MediaManager.h | 6 +- dom/media/MediaRecorder.h | 2 +- dom/media/MediaResource.cpp | 54 +- dom/media/MediaResource.h | 76 ++- dom/media/MediaSegment.h | 24 +- dom/media/MediaShutdownManager.cpp | 38 +- dom/media/MediaShutdownManager.h | 5 + dom/media/MediaStreamError.h | 2 +- dom/media/MediaStreamGraph.cpp | 447 ++++++++++---- dom/media/MediaStreamGraph.h | 84 ++- dom/media/MediaStreamGraphImpl.h | 74 ++- dom/media/RtspMediaResource.h | 57 +- dom/media/SharedBuffer.h | 2 +- dom/media/TextTrack.h | 2 +- dom/media/TextTrackCue.h | 2 +- dom/media/TextTrackCueList.h | 2 +- dom/media/TextTrackList.h | 2 +- dom/media/TextTrackRegion.h | 2 +- dom/media/TrackUnionStream.h | 4 +- dom/media/VideoSegment.h | 4 +- dom/media/VideoStreamTrack.h | 6 +- dom/media/VideoTrack.h | 6 +- dom/media/VideoTrackList.h | 6 +- dom/media/encoder/MediaEncoder.h | 16 +- dom/media/encoder/TrackEncoder.h | 24 +- dom/media/gtest/MockMediaDecoderOwner.h | 48 +- dom/media/gtest/MockMediaResource.h | 55 +- dom/media/gtest/TestAudioCompactor.cpp | 2 +- dom/media/gtest/TestGMPCrossOrigin.cpp | 60 +- dom/media/imagecapture/CaptureTask.h | 18 +- dom/media/imagecapture/ImageCapture.h | 2 +- dom/media/mediasink/AudioSink.h | 6 +- dom/media/mediasink/AudioSinkWrapper.cpp | 3 +- dom/media/mediasink/DecodedAudioDataSink.cpp | 536 +++++------------ dom/media/mediasink/DecodedAudioDataSink.h | 127 +--- dom/media/mediasource/MediaSourceDecoder.cpp | 4 +- dom/media/mediasource/MediaSourceDecoder.h | 12 +- dom/media/mediasource/MediaSourceResource.h | 56 +- dom/media/mediasource/ResourceQueue.cpp | 2 +- .../mediasource/SourceBufferContentManager.h | 2 +- dom/media/mediasource/SourceBufferResource.h | 54 +- dom/media/systemservices/CamerasChild.cpp | 363 +++++------- dom/media/systemservices/CamerasChild.h | 131 +++- dom/media/systemservices/CamerasParent.cpp | 142 ++++- dom/media/systemservices/CamerasParent.h | 2 +- dom/media/systemservices/LoadManager.h | 10 +- dom/media/systemservices/PCameras.ipdl | 2 +- .../test_getUserMedia_constraints.html | 19 +- dom/media/webaudio/AudioNodeStream.cpp | 30 +- dom/media/webrtc/MediaEngine.h | 40 +- .../webrtc/MediaEngineCameraVideoSource.h | 7 - dom/media/webrtc/MediaEngineDefault.cpp | 6 +- dom/media/webrtc/MediaEngineDefault.h | 22 +- .../webrtc/MediaEngineRemoteVideoSource.cpp | 79 ++- .../webrtc/MediaEngineRemoteVideoSource.h | 3 +- .../webrtc/MediaEngineTabVideoSource.cpp | 9 +- dom/media/webrtc/MediaEngineTabVideoSource.h | 4 +- dom/media/webrtc/MediaEngineWebRTC.cpp | 73 ++- dom/media/webrtc/MediaEngineWebRTC.h | 333 ++++++++++- dom/media/webrtc/MediaEngineWebRTCAudio.cpp | 217 ++++--- dom/media/webrtc/MediaTrackConstraints.cpp | 31 + dom/media/webrtc/MediaTrackConstraints.h | 12 +- .../webspeech/recognition/SpeechGrammar.h | 2 +- .../webspeech/recognition/SpeechGrammarList.h | 2 +- .../webspeech/recognition/SpeechRecognition.h | 2 +- .../SpeechRecognitionAlternative.h | 2 +- .../recognition/SpeechRecognitionResult.h | 2 +- .../recognition/SpeechRecognitionResultList.h | 2 +- .../recognition/SpeechStreamListener.h | 16 +- dom/media/webspeech/synth/SpeechSynthesis.h | 2 +- .../synth/SpeechSynthesisUtterance.h | 2 +- .../webspeech/synth/SpeechSynthesisVoice.h | 2 +- .../synth/ipc/SpeechSynthesisChild.h | 30 +- .../synth/ipc/SpeechSynthesisParent.h | 34 +- dom/media/webspeech/synth/nsSpeechTask.cpp | 6 +- dom/svg/SVGContentUtils.cpp | 37 +- dom/svg/SVGContentUtils.h | 41 ++ dom/svg/SVGForeignObjectElement.cpp | 4 +- dom/svg/SVGForeignObjectElement.h | 5 +- dom/svg/SVGFragmentIdentifier.cpp | 295 ++++----- dom/svg/SVGFragmentIdentifier.h | 15 +- dom/svg/SVGSVGElement.cpp | 151 ++--- dom/svg/SVGSVGElement.h | 40 +- dom/svg/SVGTransformableElement.cpp | 32 +- dom/svg/SVGTransformableElement.h | 5 +- dom/svg/SVGUseElement.cpp | 4 +- dom/svg/SVGUseElement.h | 5 +- dom/svg/moz.build | 1 + dom/svg/nsSVGElement.cpp | 4 +- dom/svg/nsSVGElement.h | 10 +- dom/svg/test/test_fragments.html | 103 ++-- dom/webidl/MediaStreamTrack.webidl | 3 + .../MediaTrackSupportedConstraints.webidl | 4 +- layout/base/GeometryUtils.cpp | 11 + layout/base/nsLayoutUtils.cpp | 8 +- layout/base/nsLayoutUtils.h | 2 + ...test_getBoxQuads_convertPointRectQuad.html | 6 +- layout/media/symbols.def.in | 2 + .../reftests/svg/fragmentIdentifier-01.xhtml | 1 + .../svg/fragmentIdentifier-rect-01.svg | 10 + layout/svg/nsSVGClipPathFrame.cpp | 8 +- layout/svg/nsSVGContainerFrame.cpp | 11 +- layout/svg/nsSVGForeignObjectFrame.cpp | 5 +- layout/svg/nsSVGOuterSVGFrame.cpp | 2 +- layout/svg/nsSVGPathGeometryFrame.cpp | 6 +- layout/svg/nsSVGPatternFrame.cpp | 2 +- layout/svg/nsSVGSwitchFrame.cpp | 6 +- layout/svg/nsSVGUtils.cpp | 14 +- media/libcubeb/AUTHORS | 7 + media/libcubeb/README_MOZILLA | 2 +- media/libcubeb/include/cubeb.h | 121 ++-- media/libcubeb/src/cubeb-internal.h | 14 +- media/libcubeb/src/cubeb.c | 57 +- media/libcubeb/src/cubeb_alsa.c | 27 +- media/libcubeb/src/cubeb_audiotrack.c | 26 +- media/libcubeb/src/cubeb_audiounit.c | 23 +- media/libcubeb/src/cubeb_opensl.c | 82 ++- media/libcubeb/src/cubeb_pulse.c | 558 +++++++++++++++--- media/libcubeb/src/cubeb_resampler.cpp | 25 +- media/libcubeb/src/cubeb_resampler.h | 3 +- media/libcubeb/src/cubeb_sndio.c | 31 +- media/libcubeb/src/cubeb_wasapi.cpp | 29 +- media/libcubeb/src/cubeb_winmm.c | 29 +- media/libcubeb/tests/common.h | 1 + media/libcubeb/tests/moz.build | 2 + media/libcubeb/tests/test_audio.cpp | 42 +- media/libcubeb/tests/test_latency.cpp | 5 + media/libcubeb/tests/test_sanity.cpp | 148 ++--- media/libcubeb/tests/test_tone.cpp | 45 +- media/libcubeb/update.sh | 2 + .../src/mediapipeline/MediaPipeline.cpp | 4 +- modules/libpref/init/all.js | 10 +- xpcom/base/ClearOnShutdown.cpp | 33 +- xpcom/base/ClearOnShutdown.h | 64 +- xpcom/build/XPCOMInit.cpp | 6 +- 154 files changed, 4321 insertions(+), 2983 deletions(-) diff --git a/dom/media/AudioSegment.h b/dom/media/AudioSegment.h index 295c235ebf..e2880dadc3 100644 --- a/dom/media/AudioSegment.h +++ b/dom/media/AudioSegment.h @@ -26,7 +26,7 @@ public: mBuffers.SwapElements(*aBuffers); } - virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { size_t amount = 0; amount += mBuffers.ShallowSizeOfExcludingThis(aMallocSizeOf); @@ -37,7 +37,7 @@ public: return amount; } - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } @@ -384,7 +384,7 @@ public: static Type StaticType() { return AUDIO; } - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/AudioStream.cpp b/dom/media/AudioStream.cpp index 3794473c07..b69bca3f36 100644 --- a/dom/media/AudioStream.cpp +++ b/dom/media/AudioStream.cpp @@ -20,13 +20,13 @@ namespace mozilla { -#ifdef LOG #undef LOG -#endif +#undef LOGW LazyLogModule gAudioStreamLog("AudioStream"); // For simple logs -#define LOG(x) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, x) +#define LOG(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__)) +#define LOGW(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, ("%p " x, this, ##__VA_ARGS__)) /** * When MOZ_DUMP_AUDIO is set in the environment (to anything), @@ -46,7 +46,7 @@ class FrameHistory { struct Chunk { uint32_t servicedFrames; uint32_t totalFrames; - int rate; + uint32_t rate; }; template @@ -57,7 +57,7 @@ public: FrameHistory() : mBaseOffset(0), mBasePosition(0) {} - void Append(uint32_t aServiced, uint32_t aUnderrun, int aRate) { + void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) { /* In most case where playback rate stays the same and we don't underrun * frames, we are able to merge chunks to avoid lose of precision to add up * in compressing chunks into |mBaseOffset| and |mBasePosition|. @@ -117,25 +117,24 @@ private: double mBasePosition; }; -AudioStream::AudioStream() +AudioStream::AudioStream(DataSource& aSource) : mMonitor("AudioStream") , mInRate(0) , mOutRate(0) , mChannels(0) , mOutChannels(0) - , mWritten(0) , mAudioClock(this) , mTimeStretcher(nullptr) , mDumpFile(nullptr) - , mBytesPerFrame(0) , mState(INITIALIZED) , mIsMonoAudioEnabled(gfxPrefs::MonoAudio()) + , mDataSource(aSource) { } AudioStream::~AudioStream() { - LOG(("AudioStream: delete %p, state %d", this, mState)); + LOG("deleted, state %d", mState); MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream, "Should've called Shutdown() before deleting an AudioStream"); if (mDumpFile) { @@ -155,8 +154,6 @@ AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const // - mTimeStretcher // - mCubebStream - amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); - return amount; } @@ -231,12 +228,6 @@ nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch) return NS_OK; } -int64_t AudioStream::GetWritten() -{ - MonitorAutoLock mon(mMonitor); - return mWritten; -} - static void SetUint16LE(uint8_t* aDest, uint16_t aValue) { aDest[0] = aValue & 0xFF; @@ -307,13 +298,13 @@ WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames, } nsresult -AudioStream::Init(int32_t aNumChannels, int32_t aRate, +AudioStream::Init(uint32_t aNumChannels, uint32_t aRate, const dom::AudioChannel aAudioChannel) { mStartTime = TimeStamp::Now(); mIsFirst = CubebUtils::GetFirstStream(); - if (!CubebUtils::GetCubebContext() || aNumChannels < 0 || aRate < 0) { + if (!CubebUtils::GetCubebContext()) { return NS_ERROR_FAILURE; } @@ -346,17 +337,9 @@ AudioStream::Init(int32_t aNumChannels, int32_t aRate, } else { params.format = CUBEB_SAMPLE_FLOAT32NE; } - mBytesPerFrame = sizeof(AudioDataValue) * mOutChannels; mAudioClock.Init(); - // Size mBuffer for one second of audio. This value is arbitrary, and was - // selected based on the observed behaviour of the existing AudioStream - // implementations. - uint32_t bufferLimit = FramesToBytes(aRate); - MOZ_ASSERT(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames"); - mBuffer.SetCapacity(bufferLimit); - return OpenCubeb(params); } @@ -380,7 +363,8 @@ AudioStream::OpenCubeb(cubeb_stream_params &aParams) { cubeb_stream* stream; - if (cubeb_stream_init(cubebContext, &stream, "AudioStream", aParams, + if (cubeb_stream_init(cubebContext, &stream, "AudioStream", + nullptr, nullptr, nullptr, &aParams, latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) { MonitorAutoLock mon(mMonitor); MOZ_ASSERT(mState != SHUTDOWN); @@ -397,77 +381,13 @@ AudioStream::OpenCubeb(cubeb_stream_params &aParams) if (!mStartTime.IsNull()) { TimeDuration timeDelta = TimeStamp::Now() - mStartTime; - LOG(("AudioStream creation time %sfirst: %u ms", mIsFirst ? "" : "not ", - (uint32_t) timeDelta.ToMilliseconds())); + LOG("creation time %sfirst: %u ms", mIsFirst ? "" : "not ", + (uint32_t) timeDelta.ToMilliseconds()); } return NS_OK; } -// aTime is the time in ms the samples were inserted into MediaStreamGraph -nsresult -AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames) -{ - MonitorAutoLock mon(mMonitor); - - if (mState == ERRORED) { - return NS_ERROR_FAILURE; - } - NS_ASSERTION(mState == INITIALIZED || mState == STARTED || mState == RUNNING, - "Stream write in unexpected state."); - - // Downmix to Stereo. - if (mChannels > 2 && mChannels <= 8) { - DownmixAudioToStereo(const_cast (aBuf), mChannels, aFrames); - } else if (mChannels > 8) { - return NS_ERROR_FAILURE; - } - - if (mChannels >= 2 && mIsMonoAudioEnabled) { - DownmixStereoToMono(const_cast (aBuf), aFrames); - } - - const uint8_t* src = reinterpret_cast(aBuf); - uint32_t bytesToCopy = FramesToBytes(aFrames); - - while (bytesToCopy > 0) { - uint32_t available = std::min(bytesToCopy, mBuffer.Available()); - MOZ_ASSERT(available % mBytesPerFrame == 0, - "Must copy complete frames."); - - mBuffer.AppendElements(src, available); - src += available; - bytesToCopy -= available; - - if (bytesToCopy > 0) { - // If we are not playing, but our buffer is full, start playing to make - // room for soon-to-be-decoded data. - if (mState != STARTED && mState != RUNNING) { - MOZ_LOG(gAudioStreamLog, LogLevel::Warning, ("Starting stream %p in Write (%u waiting)", - this, bytesToCopy)); - StartUnlocked(); - if (mState == ERRORED) { - return NS_ERROR_FAILURE; - } - } - MOZ_LOG(gAudioStreamLog, LogLevel::Warning, ("Stream %p waiting in Write() (%u waiting)", - this, bytesToCopy)); - mon.Wait(); - } - } - - mWritten += aFrames; - return NS_OK; -} - -uint32_t -AudioStream::Available() -{ - MonitorAutoLock mon(mMonitor); - MOZ_ASSERT(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated."); - return BytesToFrames(mBuffer.Available()); -} - void AudioStream::SetVolume(double aVolume) { @@ -478,29 +398,6 @@ AudioStream::SetVolume(double aVolume) } } -void -AudioStream::Cancel() -{ - MonitorAutoLock mon(mMonitor); - mState = ERRORED; - mon.NotifyAll(); -} - -void -AudioStream::Drain() -{ - MonitorAutoLock mon(mMonitor); - LOG(("AudioStream::Drain() for %p, state %d, avail %u", this, mState, mBuffer.Available())); - if (mState != STARTED && mState != RUNNING) { - NS_ASSERTION(mState == ERRORED || mBuffer.Available() == 0, "Draining without full buffer of unplayed audio"); - return; - } - mState = DRAINING; - while (mState == DRAINING) { - mon.Wait(); - } -} - void AudioStream::Start() { @@ -517,13 +414,19 @@ AudioStream::StartUnlocked() } if (mState == INITIALIZED) { + mState = STARTED; int r; { MonitorAutoUnlock mon(mMonitor); r = cubeb_stream_start(mCubebStream.get()); + // DataCallback might be called before we exit this scope + // if cubeb_stream_start() succeeds. mState must be set to STARTED + // beforehand. } - mState = r == CUBEB_OK ? STARTED : ERRORED; - LOG(("AudioStream: started %p, state %s", this, mState == STARTED ? "STARTED" : "ERRORED")); + if (r != CUBEB_OK) { + mState = ERRORED; + } + LOG("started, state %s", mState == STARTED ? "STARTED" : "ERRORED"); } } @@ -573,7 +476,7 @@ void AudioStream::Shutdown() { MonitorAutoLock mon(mMonitor); - LOG(("AudioStream: Shutdown %p, state %d", this, mState)); + LOG("Shutdown, state %d", mState); if (mCubebStream) { MonitorAutoUnlock mon(mMonitor); @@ -635,69 +538,99 @@ AudioStream::IsPaused() return mState == STOPPED; } -long -AudioStream::GetUnprocessed(void* aBuffer, long aFrames) +bool +AudioStream::Downmix(Chunk* aChunk) +{ + if (aChunk->Rate() != mInRate) { + LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), mInRate); + return false; + } + + if (aChunk->Channels() > 8) { + return false; + } + + if (aChunk->Channels() > 2 && aChunk->Channels() <= 8) { + DownmixAudioToStereo(aChunk->GetWritable(), + aChunk->Channels(), + aChunk->Frames()); + } + + if (aChunk->Channels() >= 2 && mIsMonoAudioEnabled) { + DownmixStereoToMono(aChunk->GetWritable(), aChunk->Frames()); + } + + return true; +} + +void +AudioStream::GetUnprocessed(AudioBufferWriter& aWriter) { mMonitor.AssertCurrentThreadOwns(); - uint8_t* wpos = reinterpret_cast(aBuffer); // Flush the timestretcher pipeline, if we were playing using a playback rate // other than 1.0. - uint32_t flushedFrames = 0; if (mTimeStretcher && mTimeStretcher->numSamples()) { - flushedFrames = mTimeStretcher->receiveSamples(reinterpret_cast(wpos), aFrames); - wpos += FramesToBytes(flushedFrames); + auto timeStretcher = mTimeStretcher; + aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) { + return timeStretcher->receiveSamples(aPtr, aFrames); + }, aWriter.Available()); + + // TODO: There might be still unprocessed samples in the stretcher. + // We should either remove or flush them so they won't be in the output + // next time we switch a playback rate other than 1.0. + NS_WARN_IF(mTimeStretcher->numUnprocessedSamples() > 0); } - uint32_t toPopBytes = FramesToBytes(aFrames - flushedFrames); - uint32_t available = std::min(toPopBytes, mBuffer.Length()); - void* input[2]; - uint32_t input_size[2]; - mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]); - memcpy(wpos, input[0], input_size[0]); - wpos += input_size[0]; - memcpy(wpos, input[1], input_size[1]); - - return BytesToFrames(available) + flushedFrames; + while (aWriter.Available() > 0) { + UniquePtr c = mDataSource.PopFrames(aWriter.Available()); + if (c->Frames() == 0) { + break; + } + MOZ_ASSERT(c->Frames() <= aWriter.Available()); + if (Downmix(c.get())) { + aWriter.Write(c->Data(), c->Frames()); + } else { + // Write silence if downmixing fails. + aWriter.WriteZeros(c->Frames()); + } + } } -long -AudioStream::GetTimeStretched(void* aBuffer, long aFrames) +void +AudioStream::GetTimeStretched(AudioBufferWriter& aWriter) { mMonitor.AssertCurrentThreadOwns(); - long processedFrames = 0; // We need to call the non-locking version, because we already have the lock. if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) { - return 0; + return; } - uint8_t* wpos = reinterpret_cast(aBuffer); double playbackRate = static_cast(mInRate) / mOutRate; - uint32_t toPopBytes = FramesToBytes(ceil(aFrames * playbackRate)); - uint32_t available = 0; - bool lowOnBufferedData = false; - do { - // Check if we already have enough data in the time stretcher pipeline. - if (mTimeStretcher->numSamples() <= static_cast(aFrames)) { - void* input[2]; - uint32_t input_size[2]; - available = std::min(mBuffer.Length(), toPopBytes); - if (available != toPopBytes) { - lowOnBufferedData = true; - } - mBuffer.PopElements(available, &input[0], &input_size[0], - &input[1], &input_size[1]); - for(uint32_t i = 0; i < 2; i++) { - mTimeStretcher->putSamples(reinterpret_cast(input[i]), BytesToFrames(input_size[i])); - } - } - uint32_t receivedFrames = mTimeStretcher->receiveSamples(reinterpret_cast(wpos), aFrames - processedFrames); - wpos += FramesToBytes(receivedFrames); - processedFrames += receivedFrames; - } while (processedFrames < aFrames && !lowOnBufferedData); + uint32_t toPopFrames = ceil(aWriter.Available() * playbackRate); - return processedFrames; + while (mTimeStretcher->numSamples() < aWriter.Available()) { + UniquePtr c = mDataSource.PopFrames(toPopFrames); + if (c->Frames() == 0) { + break; + } + MOZ_ASSERT(c->Frames() <= toPopFrames); + if (Downmix(c.get())) { + mTimeStretcher->putSamples(c->Data(), c->Frames()); + } else { + // Write silence if downmixing fails. + nsAutoTArray buf; + buf.SetLength(mOutChannels * c->Frames()); + memset(buf.Elements(), 0, buf.Length() * sizeof(AudioDataValue)); + mTimeStretcher->putSamples(buf.Elements(), c->Frames()); + } + } + + auto timeStretcher = mTimeStretcher; + aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) { + return timeStretcher->receiveSamples(aPtr, aFrames); + }, aWriter.Available()); } long @@ -705,11 +638,17 @@ AudioStream::DataCallback(void* aBuffer, long aFrames) { MonitorAutoLock mon(mMonitor); MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown"); - uint32_t available = std::min(static_cast(FramesToBytes(aFrames)), mBuffer.Length()); - MOZ_ASSERT(available % mBytesPerFrame == 0, "Must copy complete frames"); - AudioDataValue* output = reinterpret_cast(aBuffer); - uint32_t underrunFrames = 0; - uint32_t servicedFrames = 0; + + auto writer = AudioBufferWriter( + reinterpret_cast(aBuffer), mOutChannels, aFrames); + + // FIXME: cubeb_pulse sometimes calls us before cubeb_stream_start() is called. + // We don't want to consume audio data until Start() is called by the client. + if (mState == INITIALIZED) { + NS_WARNING("data callback fires before cubeb_stream_start() is called"); + mAudioClock.UpdateFrameHistory(0, aFrames); + return writer.WriteZeros(aFrames); + } // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN) // Bug 996162 @@ -719,39 +658,29 @@ AudioStream::DataCallback(void* aBuffer, long aFrames) mState = RUNNING; } - if (available) { - if (mInRate == mOutRate) { - servicedFrames = GetUnprocessed(output, aFrames); - } else { - servicedFrames = GetTimeStretched(output, aFrames); - } - - MOZ_ASSERT(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames"); - - // Notify any blocked Write() call that more space is available in mBuffer. - mon.NotifyAll(); + if (mInRate == mOutRate) { + GetUnprocessed(writer); + } else { + GetTimeStretched(writer); } - underrunFrames = aFrames - servicedFrames; - // Always send audible frames first, and silent frames later. // Otherwise it will break the assumption of FrameHistory. - if (mState != DRAINING) { - mAudioClock.UpdateFrameHistory(servicedFrames, underrunFrames); - uint8_t* rpos = static_cast(aBuffer) + FramesToBytes(aFrames - underrunFrames); - memset(rpos, 0, FramesToBytes(underrunFrames)); - if (underrunFrames) { - MOZ_LOG(gAudioStreamLog, LogLevel::Warning, - ("AudioStream %p lost %d frames", this, underrunFrames)); + if (!mDataSource.Ended()) { + mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), writer.Available()); + if (writer.Available() > 0) { + LOGW("lost %d frames", writer.Available()); + writer.WriteZeros(writer.Available()); } - servicedFrames += underrunFrames; } else { - mAudioClock.UpdateFrameHistory(servicedFrames, 0); + // No more new data in the data source. Don't send silent frames so the + // cubeb stream can start draining. + mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0); } WriteDumpFile(mDumpFile, this, aFrames, aBuffer); - return servicedFrames; + return aFrames - writer.Available(); } void @@ -759,14 +688,14 @@ AudioStream::StateCallback(cubeb_state aState) { MonitorAutoLock mon(mMonitor); MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown"); - LOG(("AudioStream: StateCallback %p, mState=%d cubeb_state=%d", this, mState, aState)); + LOG("StateCallback, mState=%d cubeb_state=%d", mState, aState); if (aState == CUBEB_STATE_DRAINED) { mState = DRAINED; + mDataSource.Drained(); } else if (aState == CUBEB_STATE_ERROR) { - LOG(("AudioStream::StateCallback() state %d cubeb error", mState)); + LOG("StateCallback() state %d cubeb error", mState); mState = ERRORED; } - mon.NotifyAll(); } AudioClock::AudioClock(AudioStream* aStream) @@ -803,7 +732,7 @@ int64_t AudioClock::GetPositionInFrames() const void AudioClock::SetPlaybackRateUnlocked(double aPlaybackRate) { - mOutRate = static_cast(mInRate / aPlaybackRate); + mOutRate = static_cast(mInRate / aPlaybackRate); } double AudioClock::GetPlaybackRate() const diff --git a/dom/media/AudioStream.h b/dom/media/AudioStream.h index f9dce5fb6a..3b0381eea9 100644 --- a/dom/media/AudioStream.h +++ b/dom/media/AudioStream.h @@ -67,9 +67,9 @@ private: // pointer is garanteed to always be valid. AudioStream* const mAudioStream; // Output rate in Hz (characteristic of the playback rate) - int mOutRate; + uint32_t mOutRate; // Input rate in Hz (characteristic of the media being played) - int mInRate; + uint32_t mInRate; // True if the we are timestretching, false if we are resampling. bool mPreservesPitch; // The history of frames sent to the audio engine in each DataCallback. @@ -148,6 +148,66 @@ private: uint32_t mCount; }; +/* + * A bookkeeping class to track the read/write position of an audio buffer. + */ +class AudioBufferCursor { +public: + AudioBufferCursor(AudioDataValue* aPtr, uint32_t aChannels, uint32_t aFrames) + : mPtr(aPtr), mChannels(aChannels), mFrames(aFrames) {} + + // Advance the cursor to account for frames that are consumed. + uint32_t Advance(uint32_t aFrames) { + MOZ_ASSERT(mFrames >= aFrames); + mFrames -= aFrames; + mPtr += mChannels * aFrames; + return aFrames; + } + + // The number of frames available for read/write in this buffer. + uint32_t Available() const { return mFrames; } + + // Return a pointer where read/write should begin. + AudioDataValue* Ptr() const { return mPtr; } + +protected: + AudioDataValue* mPtr; + const uint32_t mChannels; + uint32_t mFrames; +}; + +/* + * A helper class to encapsulate pointer arithmetic and provide means to modify + * the underlying audio buffer. + */ +class AudioBufferWriter : private AudioBufferCursor { +public: + AudioBufferWriter(AudioDataValue* aPtr, uint32_t aChannels, uint32_t aFrames) + : AudioBufferCursor(aPtr, aChannels, aFrames) {} + + uint32_t WriteZeros(uint32_t aFrames) { + memset(mPtr, 0, sizeof(AudioDataValue) * mChannels * aFrames); + return Advance(aFrames); + } + + uint32_t Write(const AudioDataValue* aPtr, uint32_t aFrames) { + memcpy(mPtr, aPtr, sizeof(AudioDataValue) * mChannels * aFrames); + return Advance(aFrames); + } + + // Provide a write fuction to update the audio buffer with the following + // signature: uint32_t(const AudioDataValue* aPtr, uint32_t aFrames) + // aPtr: Pointer to the audio buffer. + // aFrames: The number of frames available in the buffer. + // return: The number of frames actually written by the function. + template + uint32_t Write(const Function& aFunction, uint32_t aFrames) { + return Advance(aFunction(mPtr, aFrames)); + } + + using AudioBufferCursor::Available; +}; + // Access to a single instance of this class must be synchronized by // callers, or made from a single thread. One exception is that access to // GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels}, @@ -158,12 +218,41 @@ class AudioStream final public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioStream) - AudioStream(); + + class Chunk { + public: + // Return a pointer to the audio data. + virtual const AudioDataValue* Data() const = 0; + // Return the number of frames in this chunk. + virtual uint32_t Frames() const = 0; + // Return the number of audio channels. + virtual uint32_t Channels() const = 0; + // Return the sample rate of this chunk. + virtual uint32_t Rate() const = 0; + // Return a writable pointer for downmixing. + virtual AudioDataValue* GetWritable() const = 0; + virtual ~Chunk() {} + }; + + class DataSource { + public: + // Return a chunk which contains at most aFrames frames or zero if no + // frames in the source at all. + virtual UniquePtr PopFrames(uint32_t aFrames) = 0; + // Return true if no more data will be added to the source. + virtual bool Ended() const = 0; + // Notify that all data is drained by the AudioStream. + virtual void Drained() = 0; + protected: + virtual ~DataSource() {} + }; + + explicit AudioStream(DataSource& aSource); // Initialize the audio stream. aNumChannels is the number of audio // channels (1 for mono, 2 for stereo, etc) and aRate is the sample rate // (22050Hz, 44100Hz, etc). - nsresult Init(int32_t aNumChannels, int32_t aRate, + nsresult Init(uint32_t aNumChannels, uint32_t aRate, const dom::AudioChannel aAudioStreamChannel); // Closes the stream. All future use of the stream is an error. @@ -171,32 +260,13 @@ public: void Reset(); - // Write audio data to the audio hardware. aBuf is an array of AudioDataValues - // AudioDataValue of length aFrames*mChannels. If aFrames is larger - // than the result of Available(), the write will block until sufficient - // buffer space is available. - nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames); - - // Return the number of audio frames that can be written without blocking. - uint32_t Available(); - // Set the current volume of the audio playback. This is a value from // 0 (meaning muted) to 1 (meaning full volume). Thread-safe. void SetVolume(double aVolume); - // Block until buffered audio data has been consumed. - void Drain(); - - // Break any blocking operation and set the stream to shutdown. - void Cancel(); - // Start the stream. void Start(); - // Return the number of frames written so far in the stream. This allow the - // caller to check if it is safe to start the stream, if needed. - int64_t GetWritten(); - // Pause audio playback. void Pause(); @@ -214,9 +284,9 @@ public: // Returns true when the audio stream is paused. bool IsPaused(); - int GetRate() { return mOutRate; } - int GetChannels() { return mChannels; } - int GetOutChannels() { return mOutChannels; } + uint32_t GetRate() { return mOutRate; } + uint32_t GetChannels() { return mChannels; } + uint32_t GetOutChannels() { return mOutChannels; } // Set playback rate as a multiple of the intrinsic playback rate. This is to // be called only with aPlaybackRate > 0.0. @@ -238,9 +308,11 @@ protected: private: nsresult OpenCubeb(cubeb_stream_params &aParams); - static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames) + static long DataCallback_S(cubeb_stream*, void* aThis, + const void* /* aInputBuffer */, void* aOutputBuffer, + long aFrames) { - return static_cast(aThis)->DataCallback(aBuffer, aFrames); + return static_cast(aThis)->DataCallback(aOutputBuffer, aFrames); } static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState) @@ -254,28 +326,26 @@ private: nsresult EnsureTimeStretcherInitializedUnlocked(); - long GetUnprocessed(void* aBuffer, long aFrames); - long GetTimeStretched(void* aBuffer, long aFrames); + // Return true if downmixing succeeds otherwise false. + bool Downmix(Chunk* aChunk); + + void GetUnprocessed(AudioBufferWriter& aWriter); + void GetTimeStretched(AudioBufferWriter& aWriter); void StartUnlocked(); - // The monitor is held to protect all access to member variables. Write() - // waits while mBuffer is full; DataCallback() notifies as it consumes - // data from mBuffer. Drain() waits while mState is DRAINING; - // StateCallback() notifies when mState is DRAINED. + // The monitor is held to protect all access to member variables. Monitor mMonitor; // Input rate in Hz (characteristic of the media being played) - int mInRate; + uint32_t mInRate; // Output rate in Hz (characteristic of the playback rate) - int mOutRate; - int mChannels; - int mOutChannels; + uint32_t mOutRate; + uint32_t mChannels; + uint32_t mOutChannels; #if defined(__ANDROID__) dom::AudioChannel mAudioChannel; #endif - // Number of frames written to the buffers. - int64_t mWritten; AudioClock mAudioClock; soundtouch::SoundTouch* mTimeStretcher; @@ -285,36 +355,14 @@ private: // Output file for dumping audio FILE* mDumpFile; - // Temporary audio buffer. Filled by Write() and consumed by - // DataCallback(). Once mBuffer is full, Write() blocks until sufficient - // space becomes available in mBuffer. mBuffer is sized in bytes, not - // frames. - CircularByteBuffer mBuffer; - // Owning reference to a cubeb_stream. UniquePtr mCubebStream; - uint32_t mBytesPerFrame; - - uint32_t BytesToFrames(uint32_t aBytes) { - NS_ASSERTION(aBytes % mBytesPerFrame == 0, - "Byte count not aligned on frames size."); - return aBytes / mBytesPerFrame; - } - - uint32_t FramesToBytes(uint32_t aFrames) { - return aFrames * mBytesPerFrame; - } - enum StreamState { INITIALIZED, // Initialized, playback has not begun. STARTED, // cubeb started, but callbacks haven't started RUNNING, // DataCallbacks have started after STARTED, or after Resume(). STOPPED, // Stopped by a call to Pause(). - DRAINING, // Drain requested. DataCallback will indicate end of stream - // once the remaining contents of mBuffer are requested by - // cubeb, after which StateCallback will indicate drain - // completion. DRAINED, // StateCallback has indicated that the drain is complete. ERRORED, // Stream disabled due to an internal error. SHUTDOWN // Shutdown has been called @@ -324,6 +372,8 @@ private: bool mIsFirst; // Get this value from the preferece, if true, we would downmix the stereo. bool mIsMonoAudioEnabled; + + DataSource& mDataSource; }; } // namespace mozilla diff --git a/dom/media/AudioStreamTrack.h b/dom/media/AudioStreamTrack.h index ce42746330..cca21b5f42 100644 --- a/dom/media/AudioStreamTrack.h +++ b/dom/media/AudioStreamTrack.h @@ -17,12 +17,12 @@ public: AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel) : MediaStreamTrack(aStream, aTrackID, aLabel) {} - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - virtual AudioStreamTrack* AsAudioStreamTrack() override { return this; } + AudioStreamTrack* AsAudioStreamTrack() override { return this; } // WebIDL - virtual void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); } + void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); } }; } // namespace dom diff --git a/dom/media/AudioTrack.h b/dom/media/AudioTrack.h index ae8389e432..4047c34f86 100644 --- a/dom/media/AudioTrack.h +++ b/dom/media/AudioTrack.h @@ -21,14 +21,14 @@ public: const nsAString& aLanguage, bool aEnabled); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - virtual AudioTrack* AsAudioTrack() override + AudioTrack* AsAudioTrack() override { return this; } - virtual void SetEnabledInternal(bool aEnabled, int aFlags) override; + void SetEnabledInternal(bool aEnabled, int aFlags) override; // WebIDL bool Enabled() const diff --git a/dom/media/AudioTrackList.h b/dom/media/AudioTrackList.h index 5785f6c990..9ce21e1fff 100644 --- a/dom/media/AudioTrackList.h +++ b/dom/media/AudioTrackList.h @@ -21,7 +21,7 @@ public: AudioTrackList(nsPIDOMWindow* aOwnerWindow, HTMLMediaElement* aMediaElement) : MediaTrackList(aOwnerWindow, aMediaElement) {} - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; AudioTrack* operator[](uint32_t aIndex); @@ -31,7 +31,7 @@ public: AudioTrack* GetTrackById(const nsAString& aId); protected: - virtual AudioTrackList* AsAudioTrackList() override { return this; } + AudioTrackList* AsAudioTrackList() override { return this; } }; } // namespace dom diff --git a/dom/media/BufferMediaResource.h b/dom/media/BufferMediaResource.h index 755d1da9b9..5633ddf9c7 100644 --- a/dom/media/BufferMediaResource.h +++ b/dom/media/BufferMediaResource.h @@ -40,27 +40,27 @@ protected: } private: - virtual nsresult Close() override { return NS_OK; } - virtual void Suspend(bool aCloseImmediately) override {} - virtual void Resume() override {} + nsresult Close() override { return NS_OK; } + void Suspend(bool aCloseImmediately) override {} + void Resume() override {} // Get the current principal for the channel - virtual already_AddRefed GetCurrentPrincipal() override + already_AddRefed GetCurrentPrincipal() override { nsCOMPtr principal = mPrincipal; return principal.forget(); } - virtual bool CanClone() override { return false; } - virtual already_AddRefed CloneData(MediaResourceCallback*) override + bool CanClone() override { return false; } + already_AddRefed CloneData(MediaResourceCallback*) override { return nullptr; } // These methods are called off the main thread. // The mode is initially MODE_PLAYBACK. - virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {} - virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {} - virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, - uint32_t aCount, uint32_t* aBytes) override + void SetReadMode(MediaCacheStream::ReadMode aMode) override {} + void SetPlaybackRate(uint32_t aBytesPerSecond) override {} + nsresult ReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) override { if (aOffset < 0 || aOffset > mLength) { return NS_ERROR_FAILURE; @@ -70,20 +70,20 @@ private: mOffset = aOffset + *aBytes; return NS_OK; } - virtual int64_t Tell() override { return mOffset; } + int64_t Tell() override { return mOffset; } - virtual void Pin() override {} - virtual void Unpin() override {} - virtual double GetDownloadRate(bool* aIsReliable) override { *aIsReliable = false; return 0.; } - virtual int64_t GetLength() override { return mLength; } - virtual int64_t GetNextCachedData(int64_t aOffset) override { return aOffset; } - virtual int64_t GetCachedDataEnd(int64_t aOffset) override { return mLength; } - virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override { return true; } - virtual bool IsSuspendedByCache() override { return false; } - virtual bool IsSuspended() override { return false; } - virtual nsresult ReadFromCache(char* aBuffer, - int64_t aOffset, - uint32_t aCount) override + void Pin() override {} + void Unpin() override {} + double GetDownloadRate(bool* aIsReliable) override { *aIsReliable = false; return 0.; } + int64_t GetLength() override { return mLength; } + int64_t GetNextCachedData(int64_t aOffset) override { return aOffset; } + int64_t GetCachedDataEnd(int64_t aOffset) override { return mLength; } + bool IsDataCachedToEndOfResource(int64_t aOffset) override { return true; } + bool IsSuspendedByCache() override { return false; } + bool IsSuspended() override { return false; } + nsresult ReadFromCache(char* aBuffer, + int64_t aOffset, + uint32_t aCount) override { if (aOffset < 0) { return NS_ERROR_FAILURE; @@ -94,12 +94,12 @@ private: return NS_OK; } - virtual nsresult Open(nsIStreamListener** aStreamListener) override + nsresult Open(nsIStreamListener** aStreamListener) override { return NS_ERROR_FAILURE; } - virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override + nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override { aRanges += MediaByteRange(0, int64_t(mLength)); return NS_OK; @@ -107,13 +107,12 @@ private: bool IsTransportSeekable() override { return true; } - virtual const nsCString& GetContentType() const override + const nsCString& GetContentType() const override { return mContentType; } - virtual size_t SizeOfExcludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { // Not owned: // - mBuffer @@ -124,8 +123,7 @@ private: return size; } - virtual size_t SizeOfIncludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/CanvasCaptureMediaStream.cpp b/dom/media/CanvasCaptureMediaStream.cpp index aa136284d9..96322742b6 100644 --- a/dom/media/CanvasCaptureMediaStream.cpp +++ b/dom/media/CanvasCaptureMediaStream.cpp @@ -44,7 +44,7 @@ public: mImage = aImage; } - virtual void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override + void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override { // Called on the MediaStreamGraph thread. StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId); diff --git a/dom/media/CanvasCaptureMediaStream.h b/dom/media/CanvasCaptureMediaStream.h index 47b722e70d..8ec00e1d1f 100644 --- a/dom/media/CanvasCaptureMediaStream.h +++ b/dom/media/CanvasCaptureMediaStream.h @@ -103,7 +103,7 @@ public: nsresult Init(const dom::Optional& aFPS, const TrackID& aTrackId); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; // WebIDL HTMLCanvasElement* Canvas() const { return mCanvas; } diff --git a/dom/media/CubebUtils.h b/dom/media/CubebUtils.h index fe2aff2788..fb22e1125c 100644 --- a/dom/media/CubebUtils.h +++ b/dom/media/CubebUtils.h @@ -13,6 +13,8 @@ namespace mozilla { namespace CubebUtils { +typedef cubeb_devid AudioDeviceID; + // Initialize Audio Library. Some Audio backends require initializing the // library before using it. void InitLibrary(); diff --git a/dom/media/GetUserMediaRequest.h b/dom/media/GetUserMediaRequest.h index e7b0118e67..b88f98b480 100644 --- a/dom/media/GetUserMediaRequest.h +++ b/dom/media/GetUserMediaRequest.h @@ -28,8 +28,7 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(GetUserMediaRequest) - virtual JSObject* WrapObject(JSContext* cx, JS::Handle aGivenProto) - override; + JSObject* WrapObject(JSContext* cx, JS::Handle aGivenProto) override; nsISupports* GetParentObject(); uint64_t WindowID(); diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp index aad2adbe9b..9e12899bfa 100644 --- a/dom/media/GraphDriver.cpp +++ b/dom/media/GraphDriver.cpp @@ -5,6 +5,8 @@ #include #include "mozilla/dom/AudioContext.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/ClearOnShutdown.h" #include "CubebUtils.h" #include "webaudio/AudioContext.h" @@ -31,6 +33,8 @@ extern mozilla::LazyLogModule gMediaStreamGraphLog; namespace mozilla { +StaticRefPtr AsyncCubebTask::sThreadPool; + struct AutoProfilerUnregisterThread { // The empty ctor is used to silence a pre-4.8.0 GCC unused variable warning. @@ -58,6 +62,7 @@ void GraphDriver::SetGraphTime(GraphDriver* aPreviousDriver, GraphTime aLastSwitchNextIterationStart, GraphTime aLastSwitchNextIterationEnd) { + GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); // We set mIterationEnd here, because the first thing a driver do when it // does an iteration is to update graph times, so we are in fact setting // mIterationStart of the next iteration by setting the end of the previous @@ -65,13 +70,20 @@ void GraphDriver::SetGraphTime(GraphDriver* aPreviousDriver, mIterationStart = aLastSwitchNextIterationStart; mIterationEnd = aLastSwitchNextIterationEnd; - STREAM_LOG(LogLevel::Debug, ("Setting previous driver: %p (%s)", aPreviousDriver, aPreviousDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" : "SystemClockDriver")); - MOZ_ASSERT(!mPreviousDriver); - mPreviousDriver = aPreviousDriver; + MOZ_ASSERT(!PreviousDriver()); + MOZ_ASSERT(aPreviousDriver); + + STREAM_LOG(LogLevel::Debug, ("Setting previous driver: %p (%s)", + aPreviousDriver, + aPreviousDriver->AsAudioCallbackDriver() + ? "AudioCallbackDriver" + : "SystemClockDriver")); + SetPreviousDriver(aPreviousDriver); } void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver) { + GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); // This is the situation where `mPreviousDriver` is an AudioCallbackDriver // that is switching device, and the graph has found the current driver is not // an AudioCallbackDriver, but tries to switch to a _new_ AudioCallbackDriver @@ -79,23 +91,21 @@ void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver) // request to switch, since we know we will switch back to the old // AudioCallbackDriver when it has recovered from the device switching. if (aNextDriver->AsAudioCallbackDriver() && - mPreviousDriver && - mPreviousDriver->AsAudioCallbackDriver()->IsSwitchingDevice() && - mPreviousDriver != aNextDriver) { + PreviousDriver() && + PreviousDriver()->AsAudioCallbackDriver()->IsSwitchingDevice() && + PreviousDriver() != aNextDriver) { return; } LIFECYCLE_LOG("Switching to new driver: %p (%s)", aNextDriver, aNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" : "SystemClockDriver"); - mNextDriver = aNextDriver; -} - -void GraphDriver::EnsureImmediateWakeUpLocked() -{ - mGraphImpl->GetMonitor().AssertCurrentThreadOwns(); - mWaitState = WAITSTATE_WAKING_UP; - mGraphImpl->mGraphDriverAsleep = false; // atomic - mGraphImpl->GetMonitor().Notify(); + if (mNextDriver && + mNextDriver != GraphImpl()->CurrentDriver()) { + LIFECYCLE_LOG("Discarding previous next driver: %p (%s)", + mNextDriver.get(), mNextDriver->AsAudioCallbackDriver() ? + "AudioCallbackDriver" : "SystemClockDriver"); + } + SetNextDriver(aNextDriver); } GraphTime @@ -109,49 +119,48 @@ void GraphDriver::EnsureNextIteration() mGraphImpl->EnsureNextIteration(); } -class MediaStreamGraphShutdownThreadRunnable : public nsRunnable { -public: - explicit MediaStreamGraphShutdownThreadRunnable(GraphDriver* aDriver) - : mDriver(aDriver) - { - } - NS_IMETHOD Run() - { - MOZ_ASSERT(NS_IsMainThread()); - - LIFECYCLE_LOG("MediaStreamGraphShutdownThreadRunnable for graph %p", - mDriver->GraphImpl()); - // We can't release an audio driver on the main thread, because it can be - // blocking. - if (mDriver->AsAudioCallbackDriver()) { - LIFECYCLE_LOG("Releasing audio driver off main thread."); - RefPtr releaseEvent = - new AsyncCubebTask(mDriver->AsAudioCallbackDriver(), - AsyncCubebOperation::SHUTDOWN); - mDriver = nullptr; - releaseEvent->Dispatch(); - } else { - LIFECYCLE_LOG("Dropping driver reference for SystemClockDriver."); - mDriver = nullptr; - } - return NS_OK; - } -private: - RefPtr mDriver; -}; - void GraphDriver::Shutdown() { if (AsAudioCallbackDriver()) { LIFECYCLE_LOG("Releasing audio driver off main thread (GraphDriver::Shutdown).\n"); RefPtr releaseEvent = new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); - releaseEvent->Dispatch(); + releaseEvent->Dispatch(NS_DISPATCH_SYNC); } else { Stop(); } } +bool GraphDriver::Switching() +{ + GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); + return mNextDriver || mPreviousDriver; +} + +GraphDriver* GraphDriver::NextDriver() +{ + GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); + return mNextDriver; +} + +GraphDriver* GraphDriver::PreviousDriver() +{ + GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); + return mPreviousDriver; +} + +void GraphDriver::SetNextDriver(GraphDriver* aNextDriver) +{ + GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); + mNextDriver = aNextDriver; +} + +void GraphDriver::SetPreviousDriver(GraphDriver* aPreviousDriver) +{ + GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); + mPreviousDriver = aPreviousDriver; +} + ThreadedDriver::ThreadedDriver(MediaStreamGraphImpl* aGraphImpl) : GraphDriver(aGraphImpl) { } @@ -175,20 +184,28 @@ public: profiler_register_thread("MediaStreamGraph", &aLocal); LIFECYCLE_LOG("Starting a new system driver for graph %p\n", mDriver->mGraphImpl); - if (mDriver->mPreviousDriver) { + + GraphDriver* previousDriver = nullptr; + { + MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); + previousDriver = mDriver->PreviousDriver(); + } + if (previousDriver) { LIFECYCLE_LOG("%p releasing an AudioCallbackDriver(%p), for graph %p\n", mDriver, - mDriver->mPreviousDriver.get(), + previousDriver, mDriver->GraphImpl()); MOZ_ASSERT(!mDriver->AsAudioCallbackDriver()); // Stop and release the previous driver off-main-thread, but only if we're // not in the situation where we've fallen back to a system clock driver // because the osx audio stack is currently switching output device. - if (!mDriver->mPreviousDriver->AsAudioCallbackDriver()->IsSwitchingDevice()) { + if (!previousDriver->AsAudioCallbackDriver()->IsSwitchingDevice()) { RefPtr releaseEvent = - new AsyncCubebTask(mDriver->mPreviousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); - mDriver->mPreviousDriver = nullptr; + new AsyncCubebTask(previousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); releaseEvent->Dispatch(); + + MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); + mDriver->SetPreviousDriver(nullptr); } } else { MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); @@ -229,16 +246,21 @@ ThreadedDriver::Revive() // If we were switching, switch now. Otherwise, tell thread to run the main // loop again. MonitorAutoLock mon(mGraphImpl->GetMonitor()); - if (mNextDriver) { - mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd); - mGraphImpl->SetCurrentDriver(mNextDriver); - mNextDriver->Start(); + if (NextDriver()) { + NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd); + mGraphImpl->SetCurrentDriver(NextDriver()); + NextDriver()->Start(); } else { nsCOMPtr event = new MediaStreamGraphInitThreadRunnable(this); mThread->Dispatch(event, NS_DISPATCH_NORMAL); } } +void +ThreadedDriver::RemoveCallback() +{ +} + void ThreadedDriver::Stop() { @@ -303,11 +325,13 @@ ThreadedDriver::RunThread() stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime); - if (mNextDriver && stillProcessing) { + MonitorAutoLock lock(GraphImpl()->GetMonitor()); + if (NextDriver() && stillProcessing) { STREAM_LOG(LogLevel::Debug, ("Switching to AudioCallbackDriver")); - mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd); - mGraphImpl->SetCurrentDriver(mNextDriver); - mNextDriver->Start(); + RemoveCallback(); + NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd); + mGraphImpl->SetCurrentDriver(NextDriver()); + NextDriver()->Start(); return; } } @@ -374,8 +398,7 @@ SystemClockDriver::WaitForNextIteration() mGraphImpl->mNeedAnotherIteration = false; } -void -SystemClockDriver::WakeUp() +void SystemClockDriver::WakeUp() { mGraphImpl->GetMonitor().AssertCurrentThreadOwns(); mWaitState = WAITSTATE_WAKING_UP; @@ -390,9 +413,9 @@ OfflineClockDriver::OfflineClockDriver(MediaStreamGraphImpl* aGraphImpl, GraphTi } -class MediaStreamGraphShutdownThreadRunnable2 : public nsRunnable { +class MediaStreamGraphShutdownThreadRunnable : public nsRunnable { public: - explicit MediaStreamGraphShutdownThreadRunnable2(nsIThread* aThread) + explicit MediaStreamGraphShutdownThreadRunnable(nsIThread* aThread) : mThread(aThread) { } @@ -414,7 +437,7 @@ OfflineClockDriver::~OfflineClockDriver() // transfer the ownership of mThread to the event // XXX should use .forget()/etc if (mThread) { - nsCOMPtr event = new MediaStreamGraphShutdownThreadRunnable2(mThread); + nsCOMPtr event = new MediaStreamGraphShutdownThreadRunnable(mThread); mThread = nullptr; NS_DispatchToMainThread(event); } @@ -450,31 +473,50 @@ AsyncCubebTask::~AsyncCubebTask() { } +/* static */ +nsresult +AsyncCubebTask::EnsureThread() +{ + if (!sThreadPool) { + nsCOMPtr threadPool = + SharedThreadPool::Get(NS_LITERAL_CSTRING("CubebOperation"), 1); + sThreadPool = threadPool; + // Need to null this out before xpcom-shutdown-threads Observers run + // since we don't know the order that the shutdown-threads observers + // will run. ClearOnShutdown guarantees it runs first. + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void { + ClearOnShutdown(&sThreadPool, ShutdownPhase::ShutdownThreads); + })); + } else { + ClearOnShutdown(&sThreadPool, ShutdownPhase::ShutdownThreads); + } + + const uint32_t kIdleThreadTimeoutMs = 2000; + + nsresult rv = sThreadPool->SetIdleThreadTimeout(PR_MillisecondsToInterval(kIdleThreadTimeoutMs)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + NS_IMETHODIMP AsyncCubebTask::Run() { - MOZ_ASSERT(mThread); - if (NS_IsMainThread()) { - mThread->Shutdown(); // can't shutdown from the thread itself, darn - // don't null out mThread! - // See bug 999104. we must hold a ref to the thread across Dispatch() - // since the internal mthread ref could be released while processing - // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it - // assumes the caller does. - return NS_OK; - } - MOZ_ASSERT(mDriver); switch(mOperation) { case AsyncCubebOperation::INIT: { - LIFECYCLE_LOG("AsyncCubebOperation::INIT\n"); + LIFECYCLE_LOG("AsyncCubebOperation::INIT driver=%p\n", mDriver.get()); mDriver->Init(); mDriver->CompleteAudioContextOperations(mOperation); break; } case AsyncCubebOperation::SHUTDOWN: { - LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN\n"); + LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN driver=%p\n", mDriver.get()); mDriver->Stop(); mDriver->CompleteAudioContextOperations(mOperation); @@ -487,9 +529,7 @@ AsyncCubebTask::Run() MOZ_CRASH("Operation not implemented."); } - // and now kill this thread - NS_DispatchToMainThread(this); - + // The thread will kill itself after a bit return NS_OK; } @@ -505,11 +545,15 @@ StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream, AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl) : GraphDriver(aGraphImpl) + , mSampleRate(0) + , mInputChannels(1) , mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS) , mStarted(false) + , mAudioInput(nullptr) , mAudioChannel(aGraphImpl->AudioChannel()) + , mAddedMixer(false) , mInCallback(false) - , mPauseRequested(false) + , mMicrophoneActive(false) #ifdef XP_MACOSX , mCallbackReceivedWhileSwitching(0) #endif @@ -525,21 +569,22 @@ AudioCallbackDriver::~AudioCallbackDriver() void AudioCallbackDriver::Init() { - cubeb_stream_params params; + cubeb_stream_params output; + cubeb_stream_params input; uint32_t latency; MOZ_ASSERT(!NS_IsMainThread(), "This is blocking and should never run on the main thread."); - mSampleRate = params.rate = CubebUtils::PreferredSampleRate(); + mSampleRate = output.rate = CubebUtils::PreferredSampleRate(); #if defined(__ANDROID__) #if defined(MOZ_B2G) - params.stream_type = CubebUtils::ConvertChannelToCubebType(mAudioChannel); + output.stream_type = CubebUtils::ConvertChannelToCubebType(mAudioChannel); #else - params.stream_type = CUBEB_STREAM_TYPE_MUSIC; + output.stream_type = CUBEB_STREAM_TYPE_MUSIC; #endif - if (params.stream_type == CUBEB_STREAM_TYPE_MAX) { + if (output.stream_type == CUBEB_STREAM_TYPE_MAX) { NS_WARNING("Bad stream type"); return; } @@ -547,32 +592,41 @@ AudioCallbackDriver::Init() (void)mAudioChannel; #endif - params.channels = mGraphImpl->AudioChannelCount(); + output.channels = mGraphImpl->AudioChannelCount(); if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { - params.format = CUBEB_SAMPLE_S16NE; + output.format = CUBEB_SAMPLE_S16NE; } else { - params.format = CUBEB_SAMPLE_FLOAT32NE; + output.format = CUBEB_SAMPLE_FLOAT32NE; } - if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), params, &latency) != CUBEB_OK) { + if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), output, &latency) != CUBEB_OK) { NS_WARNING("Could not get minimal latency from cubeb."); return; } + input = output; + input.channels = mInputChannels; // change to support optional stereo capture + cubeb_stream* stream; + // XXX Only pass input input if we have an input listener. Always + // set up output because it's easier, and it will just get silence. + // XXX Add support for adding/removing an input listener later. if (cubeb_stream_init(CubebUtils::GetCubebContext(), &stream, - "AudioCallbackDriver", params, latency, + "AudioCallbackDriver", + mGraphImpl->mInputDeviceID, + mGraphImpl->mInputWanted ? &input : nullptr, + mGraphImpl->mOutputDeviceID, + mGraphImpl->mOutputWanted ? &output : nullptr, latency, DataCallback_s, StateCallback_s, this) == CUBEB_OK) { mAudioStream.own(stream); } else { NS_WARNING("Could not create a cubeb stream for MediaStreamGraph, falling back to a SystemClockDriver"); // Fall back to a driver using a normal thread. - mNextDriver = new SystemClockDriver(GraphImpl()); - mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd); - mGraphImpl->SetCurrentDriver(mNextDriver); - DebugOnly found = mGraphImpl->RemoveMixerCallback(this); - NS_WARN_IF_FALSE(!found, "Mixer callback not added when switching?"); - mNextDriver->Start(); + MonitorAutoLock lock(GraphImpl()->GetMonitor()); + SetNextDriver(new SystemClockDriver(GraphImpl())); + NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd); + mGraphImpl->SetCurrentDriver(NextDriver()); + NextDriver()->Start(); return; } @@ -604,30 +658,25 @@ AudioCallbackDriver::Resume() void AudioCallbackDriver::Start() { - // If this is running on the main thread, we can't open the stream directly, - // because it is a blocking operation. - if (NS_IsMainThread()) { - STREAM_LOG(LogLevel::Debug, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl)); - RefPtr initEvent = - new AsyncCubebTask(this, AsyncCubebOperation::INIT); - initEvent->Dispatch(); - } else { - STREAM_LOG(LogLevel::Debug, ("Starting audio threads for MediaStreamGraph %p from the previous driver's thread", mGraphImpl)); - Init(); - - // Check if we need to resolve promises because the driver just got switched - // because of a resuming AudioContext - if (!mPromisesForOperation.IsEmpty()) { - CompleteAudioContextOperations(AsyncCubebOperation::INIT); - } - - if (mPreviousDriver) { - nsCOMPtr event = - new MediaStreamGraphShutdownThreadRunnable(mPreviousDriver); + if (mPreviousDriver) { + if (mPreviousDriver->AsAudioCallbackDriver()) { + LIFECYCLE_LOG("Releasing audio driver off main thread."); + RefPtr releaseEvent = + new AsyncCubebTask(mPreviousDriver->AsAudioCallbackDriver(), + AsyncCubebOperation::SHUTDOWN); + releaseEvent->Dispatch(); + mPreviousDriver = nullptr; + } else { + LIFECYCLE_LOG("Dropping driver reference for SystemClockDriver."); mPreviousDriver = nullptr; - NS_DispatchToMainThread(event); } } + + LIFECYCLE_LOG("Starting new audio driver off main thread, " + "to ensure it runs after previous shutdown."); + RefPtr initEvent = + new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::INIT); + initEvent->Dispatch(); } void @@ -660,10 +709,11 @@ AudioCallbackDriver::Revive() STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver reviving.")); // If we were switching, switch now. Otherwise, start the audio thread again. MonitorAutoLock mon(mGraphImpl->GetMonitor()); - if (mNextDriver) { - mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd); - mGraphImpl->SetCurrentDriver(mNextDriver); - mNextDriver->Start(); + if (NextDriver()) { + RemoveCallback(); + NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd); + mGraphImpl->SetCurrentDriver(NextDriver()); + NextDriver()->Start(); } else { STREAM_LOG(LogLevel::Debug, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl)); RefPtr initEvent = @@ -672,7 +722,17 @@ AudioCallbackDriver::Revive() } } -void AudioCallbackDriver::WaitForNextIteration() +void +AudioCallbackDriver::RemoveCallback() +{ + if (mAddedMixer) { + mGraphImpl->mMixer.RemoveCallback(this); + mAddedMixer = false; + } +} + +void +AudioCallbackDriver::WaitForNextIteration() { } @@ -685,11 +745,14 @@ AudioCallbackDriver::WakeUp() /* static */ long AudioCallbackDriver::DataCallback_s(cubeb_stream* aStream, - void* aUser, void* aBuffer, + void* aUser, + const void* aInputBuffer, + void* aOutputBuffer, long aFrames) { AudioCallbackDriver* driver = reinterpret_cast(aUser); - return driver->DataCallback(static_cast(aBuffer), aFrames); + return driver->DataCallback(static_cast(aInputBuffer), + static_cast(aOutputBuffer), aFrames); } /* static */ void @@ -742,7 +805,7 @@ AudioCallbackDriver::OSXDeviceSwitchingWorkaround() // the self reference and unref the SystemClockDriver we fallen back on. if (GraphImpl()->CurrentDriver() == this) { mSelfReference.Drop(this); - mNextDriver = nullptr; + SetNextDriver(nullptr); } else { GraphImpl()->CurrentDriver()->SwitchAtNextIteration(this); } @@ -756,18 +819,20 @@ AudioCallbackDriver::OSXDeviceSwitchingWorkaround() #endif // XP_MACOSX long -AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames) +AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer, + AudioDataValue* aOutputBuffer, long aFrames) { bool stillProcessing; - if (mPauseRequested) { - PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); - return aFrames; + // Don't add the callback until we're inited and ready + if (!mAddedMixer) { + mGraphImpl->mMixer.AddCallback(this); + mAddedMixer = true; } #ifdef XP_MACOSX if (OSXDeviceSwitchingWorkaround()) { - PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); + PodZero(aOutputBuffer, aFrames * mGraphImpl->AudioChannelCount()); return aFrames; } #endif @@ -787,7 +852,7 @@ AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames) // driver is the first one for this graph), and the graph would exit. Simply // return here until we have messages. if (!mGraphImpl->MessagesQueued()) { - PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); + PodZero(aOutputBuffer, aFrames * mGraphImpl->AudioChannelCount()); return aFrames; } mGraphImpl->SwapMessageQueues(); @@ -804,7 +869,7 @@ AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames) mIterationDurationMS /= 4; } - mBuffer.SetBuffer(aBuffer, aFrames); + mBuffer.SetBuffer(aOutputBuffer, aFrames); // fill part or all with leftover data from last iteration (since we // align to Audio blocks) mScratchBuffer.Empty(mBuffer); @@ -831,11 +896,11 @@ AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames) // reclock the current time against the state time, here. mIterationEnd = mIterationStart + 0.8 * inGraph; - STREAM_LOG(LogLevel::Debug, ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) (duration ticks: %ld)\n", - (long)mIterationStart, (long)mIterationEnd, - (long)stateComputedTime, (long)nextStateComputedTime, - (long)aFrames, (uint32_t)durationMS, - (long)(nextStateComputedTime - stateComputedTime))); + STREAM_LOG(LogLevel::Verbose, ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) (duration ticks: %ld)\n", + (long)mIterationStart, (long)mIterationEnd, + (long)stateComputedTime, (long)nextStateComputedTime, + (long)aFrames, (uint32_t)durationMS, + (long)(nextStateComputedTime - stateComputedTime))); mCurrentTimeStamp = TimeStamp::Now(); @@ -846,25 +911,47 @@ AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames) stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime); } else { - NS_WARNING("DataCallback buffer filled entirely from scratch buffer, skipping iteration."); + STREAM_LOG(LogLevel::Verbose, ("DataCallback buffer filled entirely from scratch buffer, skipping iteration.")); stillProcessing = true; } mBuffer.BufferFilled(); - if (mNextDriver && stillProcessing) { - { - // If the audio stream has not been started by the previous driver or - // the graph itself, keep it alive. - MonitorAutoLock mon(mGraphImpl->GetMonitor()); - if (!IsStarted()) { - return aFrames; - } + // Callback any observers for the AEC speaker data. Note that one + // (maybe) of these will be full-duplex, the others will get their input + // data off separate cubeb callbacks. Take care with how stuff is + // removed/added to this list and TSAN issues, but input and output will + // use separate callback methods. + mGraphImpl->NotifyOutputData(aOutputBuffer, static_cast(aFrames), + mSampleRate, ChannelCount); + + // Process mic data if any/needed -- after inserting far-end data for AEC! + if (aInputBuffer) { + if (mAudioInput) { // for this specific input-only or full-duplex stream + mAudioInput->NotifyInputData(mGraphImpl, aInputBuffer, + static_cast(aFrames), + mSampleRate, mInputChannels); + } + } + + bool switching = false; + { + MonitorAutoLock mon(mGraphImpl->GetMonitor()); + switching = !!NextDriver(); + } + + if (switching && stillProcessing) { + // If the audio stream has not been started by the previous driver or + // the graph itself, keep it alive. + MonitorAutoLock mon(mGraphImpl->GetMonitor()); + if (!IsStarted()) { + return aFrames; } STREAM_LOG(LogLevel::Debug, ("Switching to system driver.")); - mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd); - mGraphImpl->SetCurrentDriver(mNextDriver); - mNextDriver->Start(); + RemoveCallback(); + NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd); + mGraphImpl->SetCurrentDriver(NextDriver()); + NextDriver()->Start(); // Returning less than aFrames starts the draining and eventually stops the // audio thread. This function will never get called again. return aFrames - 1; @@ -967,7 +1054,8 @@ AudioCallbackDriver::DeviceChangedCallback() { STREAM_LOG(LogLevel::Error, ("Switching to SystemClockDriver during output switch")); mSelfReference.Take(this); mCallbackReceivedWhileSwitching = 0; - mNextDriver = new SystemClockDriver(GraphImpl()); + SetNextDriver(new SystemClockDriver(GraphImpl())); + RemoveCallback(); mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd); mGraphImpl->SetCurrentDriver(mNextDriver); mNextDriver->Start(); diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h index 5973276635..9a1c19f30f 100644 --- a/dom/media/GraphDriver.h +++ b/dom/media/GraphDriver.h @@ -13,6 +13,8 @@ #include "AudioSegment.h" #include "SelfRef.h" #include "mozilla/Atomics.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/StaticPtr.h" struct cubeb_stream; @@ -61,6 +63,46 @@ class OfflineClockDriver; * OfflineClockDriver, if the graph is offline, or a SystemClockDriver, if the * graph is real time. * A MediaStreamGraph holds an owning reference to its driver. + * + * The lifetime of drivers is a complicated affair. Here are the different + * scenarii that can happen: + * + * Starting a MediaStreamGraph with an AudioCallbackDriver + * - A new thread T is created, from the main thread. + * - On this thread T, cubeb is initialized if needed, and a cubeb_stream is + * created and started + * - The thread T posts a message to the main thread to terminate itself. + * - The graph runs off the audio thread + * + * Starting a MediaStreamGraph with a SystemClockDriver: + * - A new thread T is created from the main thread. + * - The graph runs off this thread. + * + * Switching from a SystemClockDriver to an AudioCallbackDriver: + * - A new AudioCallabackDriver is created and initialized on the graph thread + * - At the end of the MSG iteration, the SystemClockDriver transfers its timing + * info and a reference to itself to the AudioCallbackDriver. It then starts + * the AudioCallbackDriver. + * - When the AudioCallbackDriver starts, it checks if it has been switched from + * a SystemClockDriver, and if that is the case, sends a message to the main + * thread to shut the SystemClockDriver thread down. + * - The graph now runs off an audio callback + * + * Switching from an AudioCallbackDriver to a SystemClockDriver: + * - A new SystemClockDriver is created, and set as mNextDriver. + * - At the end of the MSG iteration, the AudioCallbackDriver transfers its + * timing info and a reference to itself to the SystemClockDriver. A new + * SystemClockDriver is started from the current audio thread. + * - When starting, the SystemClockDriver checks if it has been switched from an + * AudioCallbackDriver. If yes, it creates a new temporary thread to release + * the cubeb_streams. This temporary thread closes the cubeb_stream, and + * then dispatches a message to the main thread to be terminated. + * - The graph now runs off a normal thread. + * + * Two drivers cannot run at the same time for the same graph. The thread safety + * of the different attributes of drivers, and they access pattern is documented + * next to the members themselves. + * */ class GraphDriver { @@ -82,6 +124,9 @@ public: virtual void Resume() = 0; /* Revive this driver, as more messages just arrived. */ virtual void Revive() = 0; + /* Remove Mixer callbacks when switching */ + virtual void RemoveCallback() = 0; + /* Shutdown GraphDriver (synchronously) */ void Shutdown(); /* Rate at which the GraphDriver runs, in ms. This can either be user * controlled (because we are using a {System,Offline}ClockDriver, and decide @@ -92,14 +137,14 @@ public: virtual uint32_t IterationDuration() = 0; /* Return whether we are switching or not. */ - bool Switching() { - return mNextDriver || mPreviousDriver; - } + bool Switching(); - GraphDriver* NextDriver() - { - return mNextDriver; - } + // Those are simply or setting the associated pointer, but assert that the + // lock is held. + GraphDriver* NextDriver(); + GraphDriver* PreviousDriver(); + void SetNextDriver(GraphDriver* aNextDriver); + void SetPreviousDriver(GraphDriver* aPreviousDriver); /** * If we are running a real time graph, get the current time stamp to schedule @@ -109,27 +154,10 @@ public: return mCurrentTimeStamp; } - bool IsWaiting() { - return mWaitState == WAITSTATE_WAITING_INDEFINITELY || - mWaitState == WAITSTATE_WAITING_FOR_NEXT_ITERATION; - } - - bool IsWaitingIndefinitly() { - return mWaitState == WAITSTATE_WAITING_INDEFINITELY; - } - - GraphTime IterationStart() { - return mIterationStart; - } - GraphTime IterationEnd() { return mIterationEnd; } - virtual void GetAudioBuffer(float** aBuffer, long& aFrames) { - MOZ_CRASH("This is not an Audio GraphDriver!"); - } - virtual AudioCallbackDriver* AsAudioCallbackDriver() { return nullptr; } @@ -151,13 +179,6 @@ public: void SetGraphTime(GraphDriver* aPreviousDriver, GraphTime aLastSwitchNextIterationStart, GraphTime aLastSwitchNextIterationEnd); - - /** - * Call this to indicate that another iteration of the control loop is - * required immediately. The monitor must already be held. - */ - void EnsureImmediateWakeUpLocked(); - /** * Call this to indicate that another iteration of the control loop is * required on its regular schedule. The monitor must not be held. @@ -179,12 +200,15 @@ public: protected: GraphTime StateComputedTime() const; - // Time of the start of this graph iteration. + // Time of the start of this graph iteration. This must be accessed while + // having the monitor. GraphTime mIterationStart; - // Time of the end of this graph iteration. + // Time of the end of this graph iteration. This must be accessed while having + // the monitor. GraphTime mIterationEnd; // The MediaStreamGraphImpl that owns this driver. This has a lifetime longer - // than the driver, and will never be null. + // than the driver, and will never be null. Hence, it can be accesed without + // monitor. MediaStreamGraphImpl* mGraphImpl; // This enum specifies the wait state of the driver. @@ -200,15 +224,26 @@ protected: // but it hasn't done so yet WAITSTATE_WAKING_UP }; + // This must be access with the monitor. WaitState mWaitState; + // This is used on the main thread (during initialization), and the graph + // thread. No monitor needed because we know the graph thread does not run + // during the initialization. TimeStamp mCurrentTimeStamp; // This is non-null only when this driver has recently switched from an other // driver, and has not cleaned it up yet (for example because the audio stream // is currently calling the callback during initialization). + // + // This is written to when changing driver, from the previous driver's thread, + // or a thread created for the occasion. This is read each time we need to + // check whether we're changing driver (in Switching()), from the graph + // thread. + // This must be accessed using the {Set,Get}PreviousDriver methods. RefPtr mPreviousDriver; // This is non-null only when this driver is going to switch to an other // driver at the end of this iteration. + // This must be accessed using the {Set,Get}NextDriver methods. RefPtr mNextDriver; virtual ~GraphDriver() { } @@ -224,21 +259,22 @@ class ThreadedDriver : public GraphDriver public: explicit ThreadedDriver(MediaStreamGraphImpl* aGraphImpl); virtual ~ThreadedDriver(); - virtual void Start() override; - virtual void Stop() override; - virtual void Resume() override; - virtual void Revive() override; + void Start() override; + void Stop() override; + void Resume() override; + void Revive() override; + void RemoveCallback() override; /** * Runs main control loop on the graph thread. Normally a single invocation * of this runs for the entire lifetime of the graph thread. */ void RunThread(); friend class MediaStreamGraphInitThreadRunnable; - virtual uint32_t IterationDuration() override { + uint32_t IterationDuration() override { return MEDIA_GRAPH_TARGET_PERIOD_MS; } - virtual bool OnThread() override { return !mThread || NS_GetCurrentThread() == mThread; } + bool OnThread() override { return !mThread || NS_GetCurrentThread() == mThread; } /* When the graph wakes up to do an iteration, implementations return the * range of time that will be processed. This is called only once per @@ -258,12 +294,14 @@ class SystemClockDriver : public ThreadedDriver public: explicit SystemClockDriver(MediaStreamGraphImpl* aGraphImpl); virtual ~SystemClockDriver(); - virtual MediaTime GetIntervalForIteration() override; - virtual void WaitForNextIteration() override; - virtual void WakeUp() override; + MediaTime GetIntervalForIteration() override; + void WaitForNextIteration() override; + void WakeUp() override; private: + // Those are only modified (after initialization) on the graph thread. The + // graph thread does not run during the initialization. TimeStamp mInitialTimeStamp; TimeStamp mLastTimeStamp; }; @@ -277,11 +315,11 @@ class OfflineClockDriver : public ThreadedDriver public: OfflineClockDriver(MediaStreamGraphImpl* aGraphImpl, GraphTime aSlice); virtual ~OfflineClockDriver(); - virtual MediaTime GetIntervalForIteration() override; - virtual void WaitForNextIteration() override; - virtual void WakeUp() override; - virtual TimeStamp GetCurrentTimeStamp() override; - virtual OfflineClockDriver* AsOfflineClockDriver() override { + MediaTime GetIntervalForIteration() override; + void WaitForNextIteration() override; + void WakeUp() override; + TimeStamp GetCurrentTimeStamp() override; + OfflineClockDriver* AsOfflineClockDriver() override { return this; } @@ -332,17 +370,20 @@ public: explicit AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl); virtual ~AudioCallbackDriver(); - virtual void Destroy() override; - virtual void Start() override; - virtual void Stop() override; - virtual void Resume() override; - virtual void Revive() override; - virtual void WaitForNextIteration() override; - virtual void WakeUp() override; + void Destroy() override; + void Start() override; + void Stop() override; + void Resume() override; + void Revive() override; + void RemoveCallback() override; + void WaitForNextIteration() override; + void WakeUp() override; /* Static wrapper function cubeb calls back. */ static long DataCallback_s(cubeb_stream * aStream, - void * aUser, void * aBuffer, + void * aUser, + const void * aInputBuffer, + void * aOutputBuffer, long aFrames); static void StateCallback_s(cubeb_stream* aStream, void * aUser, cubeb_state aState); @@ -352,23 +393,34 @@ public: * audio. If the return value is exactly aFrames, this function will get * called again. If it is less than aFrames, the stream will go in draining * mode, and this function will not be called again. */ - long DataCallback(AudioDataValue* aBuffer, long aFrames); + long DataCallback(const AudioDataValue* aInputBuffer, AudioDataValue* aOutputBuffer, long aFrames); /* This function is called by the underlying audio backend, but is only used * for informational purposes at the moment. */ void StateCallback(cubeb_state aState); /* This is an approximation of the number of millisecond there are between two * iterations of the graph. */ - virtual uint32_t IterationDuration() override; + uint32_t IterationDuration() override; /* This function gets called when the graph has produced the audio frames for * this iteration. */ - virtual void MixerCallback(AudioDataValue* aMixedBuffer, - AudioSampleFormat aFormat, - uint32_t aChannels, - uint32_t aFrames, - uint32_t aSampleRate) override; + void MixerCallback(AudioDataValue* aMixedBuffer, + AudioSampleFormat aFormat, + uint32_t aChannels, + uint32_t aFrames, + uint32_t aSampleRate) override; - virtual AudioCallbackDriver* AsAudioCallbackDriver() override { + // These are invoked on the MSG thread (we don't call this if not LIFECYCLE_RUNNING) + virtual void SetInputListener(AudioDataListener *aListener) { + MOZ_ASSERT(OnThread()); + mAudioInput = aListener; + } + // XXX do we need the param? probably no + virtual void RemoveInputListener(AudioDataListener *aListener) { + MOZ_ASSERT(OnThread()); + mAudioInput = nullptr; + } + + AudioCallbackDriver* AsAudioCallbackDriver() override { return this; } @@ -391,7 +443,7 @@ public: */ bool InCallback(); - virtual bool OnThread() override { return !mStarted || InCallback(); } + bool OnThread() override { return !mStarted || InCallback(); } /* Whether the underlying cubeb stream has been started. See comment for * mStarted for details. */ @@ -420,18 +472,25 @@ private: /* The size of this buffer comes from the fact that some audio backends can * call back with a number of frames lower than one block (128 frames), so we * need to keep at most two block in the SpillBuffer, because we always round - * up to block boundaries during an iteration. */ + * up to block boundaries during an iteration. + * This is only ever accessed on the audio callback thread. */ SpillBuffer mScratchBuffer; /* Wrapper to ensure we write exactly the number of frames we need in the - * audio buffer cubeb passes us. */ + * audio buffer cubeb passes us. This is only ever accessed on the audio + * callback thread. */ AudioCallbackBufferWrapper mBuffer; /* cubeb stream for this graph. This is guaranteed to be non-null after Init() - * has been called. */ + * has been called, and is synchronized internaly. */ nsAutoRef mAudioStream; - /* The sample rate for the aforementionned cubeb stream. */ + /* The sample rate for the aforementionned cubeb stream. This is set on + * initialization and can be read safely afterwards. */ uint32_t mSampleRate; + /* The number of input channels from cubeb. Should be set before opening cubeb + * and then be static. */ + uint32_t mInputChannels; /* Approximation of the time between two callbacks. This is used to schedule - * video frames. This is in milliseconds. */ + * video frames. This is in milliseconds. Only even used (after + * inizatialization) on the audio callback thread. */ uint32_t mIterationDurationMS; /* cubeb_stream_init calls the audio callback to prefill the buffers. The * previous driver has to be kept alive until the audio stream has been @@ -448,6 +507,8 @@ private: * This is synchronized by the Graph's monitor. * */ bool mStarted; + /* Listener for mic input, if any. */ + RefPtr mAudioInput; struct AutoInCallback { @@ -459,13 +520,16 @@ private: /* Thread for off-main-thread initialization and * shutdown of the audio stream. */ nsCOMPtr mInitShutdownThread; + /* This must be accessed with the graph monitor held. */ nsAutoTArray mPromisesForOperation; + /* This is set during initialization, and can be read safely afterwards. */ dom::AudioChannel mAudioChannel; + /* Used to queue us to add the mixer callback on first run. */ + bool mAddedMixer; + + /* This is atomic and is set by the audio callback thread. It can be read by + * any thread safely. */ Atomic mInCallback; - /* A thread has been created to be able to pause and restart the audio thread, - * but has not done so yet. This indicates that the callback should return - * early */ - bool mPauseRequested; /** * True if microphone is being used by this process. This is synchronized by * the graph's monitor. */ @@ -490,13 +554,11 @@ public: AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation); - nsresult Dispatch() + nsresult Dispatch(uint32_t aFlags = NS_DISPATCH_NORMAL) { - // Can't add 'this' as the event to run, since mThread may not be set yet - nsresult rv = NS_NewNamedThread("CubebOperation", getter_AddRefs(mThread)); - if (NS_SUCCEEDED(rv)) { - // Note: event must not null out mThread! - rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL); + nsresult rv = EnsureThread(); + if (!NS_FAILED(rv)) { + rv = sThreadPool->Dispatch(this, aFlags); } return rv; } @@ -505,8 +567,10 @@ protected: virtual ~AsyncCubebTask(); private: + static nsresult EnsureThread(); + NS_IMETHOD Run() override final; - nsCOMPtr mThread; + static StaticRefPtr sThreadPool; RefPtr mDriver; AsyncCubebOperation mOperation; RefPtr mShutdownGrip; diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 40e6f8f0b8..bdb27d9382 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -592,14 +592,14 @@ MediaDecoder::MediaDecoder(MediaDecoderOwner* aOwner) MediaShutdownManager::Instance().Register(this); } -void +RefPtr MediaDecoder::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); - if (mShuttingDown) - return; - + if (mShuttingDown) { + return ShutdownPromise::CreateAndResolve(true, __func__); + } mShuttingDown = true; mResourceCallback->Disconnect(); @@ -611,6 +611,7 @@ MediaDecoder::Shutdown() // This changes the decoder state to SHUTDOWN and does other things // necessary to unblock the state machine thread if it's blocked, so // the asynchronous shutdown in nsDestroyStateMachine won't deadlock. + RefPtr shutdown; if (mDecoderStateMachine) { mTimedMetadataListener.Disconnect(); mMetadataLoadedListener.Disconnect(); @@ -619,9 +620,11 @@ MediaDecoder::Shutdown() mOnSeekingStart.Disconnect(); mOnMediaNotSeekable.Disconnect(); - mDecoderStateMachine->BeginShutdown()->Then( - AbstractThread::MainThread(), __func__, this, - &MediaDecoder::FinishShutdown, &MediaDecoder::FinishShutdown); + shutdown = mDecoderStateMachine->BeginShutdown() + ->Then(AbstractThread::MainThread(), __func__, this, + &MediaDecoder::FinishShutdown, + &MediaDecoder::FinishShutdown) + ->CompletionPromise(); } // Force any outstanding seek and byterange requests to complete @@ -635,6 +638,8 @@ MediaDecoder::Shutdown() ChangeState(PLAY_STATE_SHUTDOWN); MediaShutdownManager::Instance().Unregister(this); + + return shutdown ? shutdown : ShutdownPromise::CreateAndResolve(true, __func__); } MediaDecoder::~MediaDecoder() @@ -668,12 +673,13 @@ MediaDecoder::OnPlaybackEvent(MediaEventType aEvent) } } -void +RefPtr MediaDecoder::FinishShutdown() { MOZ_ASSERT(NS_IsMainThread()); mDecoderStateMachine->BreakCycles(); SetStateMachine(nullptr); + return ShutdownPromise::CreateAndResolve(true, __func__); } MediaResourceCallback* diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 744de79186..2b93e454e3 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -302,7 +302,7 @@ public: // Cleanup internal data structures. Must be called on the main // thread by the owning object before that object disposes of this object. - virtual void Shutdown(); + virtual RefPtr Shutdown(); // Start downloading the media. Decode the downloaded data up to the // point of the first frame of data. @@ -763,7 +763,7 @@ private: SetMediaSeekable(false); } - void FinishShutdown(); + RefPtr FinishShutdown(); MediaEventProducer mDataArrivedEvent; diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 3f89135157..e7b7975769 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -96,13 +96,6 @@ const int64_t AMPLE_AUDIO_USECS = 1000000; } // namespace detail -// When we're only playing audio and we don't have a video stream, we divide -// AMPLE_AUDIO_USECS and LOW_AUDIO_USECS by the following value. This reduces -// the amount of decoded audio we buffer, reducing our memory usage. We only -// need to decode far ahead when we're decoding video using software decoding, -// as otherwise a long video decode could cause an audio underrun. -const int64_t NO_VIDEO_AMPLE_AUDIO_DIVISOR = 8; - // If we have fewer than LOW_VIDEO_FRAMES decoded frames, and // we're not "prerolling video", we'll skip the video up to the next keyframe // which is at or after the current playback position. @@ -223,7 +216,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mQuickBufferingLowDataThresholdUsecs(detail::QUICK_BUFFERING_LOW_DATA_USECS), mIsAudioPrerolling(false), mIsVideoPrerolling(false), - mAudioCaptured(false, "MediaDecoderStateMachine::mAudioCaptured"), + mAudioCaptured(false), mAudioCompleted(false, "MediaDecoderStateMachine::mAudioCompleted"), mVideoCompleted(false, "MediaDecoderStateMachine::mVideoCompleted"), mNotifyMetadataBeforeFirstFrame(false), @@ -238,7 +231,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mCorruptFrames(60), mDecodingFirstFrame(true), mSentLoadedMetadataEvent(false), - mSentFirstFrameLoadedEvent(false, "MediaDecoderStateMachine::mSentFirstFrameLoadedEvent"), + mSentFirstFrameLoadedEvent(false), mSentPlaybackEndedEvent(false), mOutputStreamManager(new OutputStreamManager()), mResource(aDecoder->GetResource()), @@ -369,8 +362,6 @@ MediaDecoderStateMachine::InitializationTask(MediaDecoder* aDecoder) mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration); mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged); mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::LogicallySeekingChanged); - mWatchManager.Watch(mSentFirstFrameLoadedEvent, &MediaDecoderStateMachine::AdjustAudioThresholds); - mWatchManager.Watch(mAudioCaptured, &MediaDecoderStateMachine::AdjustAudioThresholds); } media::MediaSink* @@ -2036,32 +2027,6 @@ MediaDecoderStateMachine::IsDecodingFirstFrame() return mState == DECODER_STATE_DECODING && mDecodingFirstFrame; } -void -MediaDecoderStateMachine::AdjustAudioThresholds() -{ - MOZ_ASSERT(OnTaskQueue()); - - // Experiments show that we need to buffer more if audio is captured to avoid - // audio glitch. See bug 1188643 comment 16 for the details. - int64_t divisor = mAudioCaptured ? NO_VIDEO_AMPLE_AUDIO_DIVISOR / 2 - : NO_VIDEO_AMPLE_AUDIO_DIVISOR; - - // We're playing audio only. We don't need to worry about slow video - // decodes causing audio underruns, so don't buffer so much audio in - // order to reduce memory usage. - if (HasAudio() && !HasVideo() && mSentFirstFrameLoadedEvent) { - mAmpleAudioThresholdUsecs = detail::AMPLE_AUDIO_USECS / divisor; - mLowAudioThresholdUsecs = detail::LOW_AUDIO_USECS / divisor; - mQuickBufferingLowDataThresholdUsecs = - detail::QUICK_BUFFERING_LOW_DATA_USECS / divisor; - - // Check if we need to stop audio prerolling for thresholds changed. - if (mIsAudioPrerolling && DonePrerollingAudio()) { - StopPrerollingAudio(); - } - } -} - void MediaDecoderStateMachine::FinishDecodeFirstFrame() { diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index 36a9b509af..584e2e95e3 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -661,8 +661,6 @@ private: // play time. bool NeedToSkipToNextKeyframe(); - void AdjustAudioThresholds(); - void* const mDecoderID; const RefPtr mFrameStats; const RefPtr mVideoFrameContainer; @@ -983,13 +981,7 @@ private: uint32_t AudioPrerollUsecs() const { MOZ_ASSERT(OnTaskQueue()); - if (IsRealTime()) { - return 0; - } - - uint32_t result = mLowAudioThresholdUsecs * 2; - MOZ_ASSERT(result <= mAmpleAudioThresholdUsecs, "Prerolling will never finish"); - return result; + return IsRealTime() ? 0 : mAmpleAudioThresholdUsecs; } uint32_t VideoPrerollFrames() const @@ -1088,7 +1080,7 @@ private: // True if we shouldn't play our audio (but still write it to any capturing // streams). When this is true, the audio thread will never start again after // it has stopped. - Watchable mAudioCaptured; + bool mAudioCaptured; // True if the audio playback thread has finished. It is finished // when either all the audio frames have completed playing, or we've moved @@ -1186,7 +1178,7 @@ private: // SetStartTime because the mStartTime already set before. Also we don't need // to decode any audio/video since the MediaDecoder will trigger a seek // operation soon. - Watchable mSentFirstFrameLoadedEvent; + bool mSentFirstFrameLoadedEvent; bool mSentPlaybackEndedEvent; diff --git a/dom/media/MediaInfo.h b/dom/media/MediaInfo.h index eac3c6e2ab..6170ca16ce 100644 --- a/dom/media/MediaInfo.h +++ b/dom/media/MediaInfo.h @@ -194,22 +194,22 @@ public: { } - virtual bool IsValid() const override + bool IsValid() const override { return mDisplay.width > 0 && mDisplay.height > 0; } - virtual VideoInfo* GetAsVideoInfo() override + VideoInfo* GetAsVideoInfo() override { return this; } - virtual const VideoInfo* GetAsVideoInfo() const override + const VideoInfo* GetAsVideoInfo() const override { return this; } - virtual UniquePtr Clone() const override + UniquePtr Clone() const override { return MakeUnique(*this); } @@ -273,22 +273,22 @@ public: { } - virtual bool IsValid() const override + bool IsValid() const override { return mChannels > 0 && mRate > 0; } - virtual AudioInfo* GetAsAudioInfo() override + AudioInfo* GetAsAudioInfo() override { return this; } - virtual const AudioInfo* GetAsAudioInfo() const override + const AudioInfo* GetAsAudioInfo() const override { return this; } - virtual UniquePtr Clone() const override + UniquePtr Clone() const override { return MakeUnique(*this); } diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index b5af415bba..611efbb853 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -613,13 +613,15 @@ AudioDevice::GetSource() } nsresult VideoDevice::Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) { - return GetSource()->Allocate(aConstraints, aPrefs, mID); + const MediaEnginePrefs &aPrefs, + const nsACString& aOrigin) { + return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin); } nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) { - return GetSource()->Allocate(aConstraints, aPrefs, mID); + const MediaEnginePrefs &aPrefs, + const nsACString& aOrigin) { + return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin); } nsresult VideoDevice::Restart(const dom::MediaTrackConstraints &aConstraints, @@ -658,20 +660,7 @@ public: VideoDevice *aVideoDevice) : mListener(aListener), mAudioDevice(aAudioDevice), - mVideoDevice(aVideoDevice), - mEchoOn(true), - mAgcOn(false), - mNoiseOn(true), -#ifdef MOZ_WEBRTC - mEcho(webrtc::kEcDefault), - mAgc(webrtc::kAgcDefault), - mNoise(webrtc::kNsDefault), -#else - mEcho(0), - mAgc(0), - mNoise(0), -#endif - mPlayoutDelay(20) + mVideoDevice(aVideoDevice) {} virtual ~nsDOMUserMediaStream() @@ -687,7 +676,7 @@ public: // single-source trackunion like we have here, the TrackUnion will assign trackids // that match the source's trackids, so we can avoid needing a mapping function. // XXX This will not handle more complex cases well. - virtual void StopTrack(TrackID aTrackID) override + void StopTrack(TrackID aTrackID) override { if (GetSourceStream()) { GetSourceStream()->EndTrack(aTrackID); @@ -703,7 +692,7 @@ public: } } - virtual already_AddRefed + already_AddRefed ApplyConstraintsToTrack(TrackID aTrackID, const MediaTrackConstraints& aConstraints, ErrorResult &aRv) override @@ -765,7 +754,7 @@ public: #endif // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline - virtual bool AddDirectListener(MediaStreamDirectListener *aListener) override + bool AddDirectListener(MediaStreamDirectListener *aListener) override { if (GetSourceStream()) { GetSourceStream()->AddDirectListener(aListener); @@ -774,34 +763,19 @@ public: return false; } - virtual void - AudioConfig(bool aEchoOn, uint32_t aEcho, - bool aAgcOn, uint32_t aAgc, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) - { - mEchoOn = aEchoOn; - mEcho = aEcho; - mAgcOn = aAgcOn; - mAgc = aAgc; - mNoiseOn = aNoiseOn; - mNoise = aNoise; - mPlayoutDelay = aPlayoutDelay; - } - - virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) override + void RemoveDirectListener(MediaStreamDirectListener *aListener) override { if (GetSourceStream()) { GetSourceStream()->RemoveDirectListener(aListener); } } - virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override + DOMLocalMediaStream* AsDOMLocalMediaStream() override { return this; } - virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) override + MediaEngineSource* GetMediaEngine(TrackID aTrackID) override { // MediaEngine supports only one video and on video track now and TrackID is // fixed in MediaEngine. @@ -826,13 +800,6 @@ public: RefPtr mListener; RefPtr mAudioDevice; // so we can turn on AEC RefPtr mVideoDevice; - bool mEchoOn; - bool mAgcOn; - bool mNoiseOn; - uint32_t mEcho; - uint32_t mAgc; - uint32_t mNoise; - uint32_t mPlayoutDelay; }; @@ -904,7 +871,7 @@ public: DOMMediaStream* aStream) : mWindowID(aWindowID), mOnSuccess(aSuccess), mManager(aManager), mStream(aStream) {} - virtual void NotifyTracksAvailable(DOMMediaStream* aStream) override + void NotifyTracksAvailable(DOMMediaStream* aStream) override { // We're in the main thread, so no worries here. if (!(mManager->IsWindowStillActive(mWindowID))) { @@ -937,16 +904,6 @@ public: NS_IMETHOD Run() { -#ifdef MOZ_WEBRTC - int32_t aec = (int32_t) webrtc::kEcUnchanged; - int32_t agc = (int32_t) webrtc::kAgcUnchanged; - int32_t noise = (int32_t) webrtc::kNsUnchanged; -#else - int32_t aec = 0, agc = 0, noise = 0; -#endif - bool aec_on = false, agc_on = false, noise_on = false; - int32_t playout_delay = 0; - MOZ_ASSERT(NS_IsMainThread()); nsPIDOMWindow *window = static_cast (nsGlobalWindow::GetInnerWindowWithId(mWindowID)); @@ -959,25 +916,6 @@ public: return NS_OK; } -#ifdef MOZ_WEBRTC - // Right now these configs are only of use if webrtc is available - nsresult rv; - nsCOMPtr prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); - if (NS_SUCCEEDED(rv)) { - nsCOMPtr branch = do_QueryInterface(prefs); - - if (branch) { - branch->GetBoolPref("media.getusermedia.aec_enabled", &aec_on); - branch->GetIntPref("media.getusermedia.aec", &aec); - branch->GetBoolPref("media.getusermedia.agc_enabled", &agc_on); - branch->GetIntPref("media.getusermedia.agc", &agc); - branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on); - branch->GetIntPref("media.getusermedia.noise", &noise); - branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay); - } - } -#endif - MediaStreamGraph::GraphDriverType graphDriverType = mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER : MediaStreamGraph::SYSTEM_THREAD_DRIVER; @@ -1056,11 +994,6 @@ public: TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(mManager, mOnSuccess, mWindowID, domStream); - mListener->AudioConfig(aec_on, (uint32_t) aec, - agc_on, (uint32_t) agc, - noise_on, (uint32_t) noise, - playout_delay); - // Dispatch to the media thread to ask it to start the sources, // because that can take a while. // Pass ownership of domStream to the MediaOperationTask @@ -1273,7 +1206,8 @@ public: nsresult rv; if (mAudioDevice) { - rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio), mPrefs); + rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio), + mPrefs, mOrigin); if (NS_FAILED(rv)) { LOG(("Failed to allocate audiosource %d",rv)); Fail(NS_LITERAL_STRING("SourceUnavailableError"), @@ -1282,7 +1216,8 @@ public: } } if (mVideoDevice) { - rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo), mPrefs); + rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo), + mPrefs, mOrigin); if (NS_FAILED(rv)) { LOG(("Failed to allocate videosource %d\n",rv)); if (mAudioDevice) { @@ -1487,11 +1422,25 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId, MediaManager::MediaManager() : mMediaThread(nullptr) , mBackend(nullptr) { - mPrefs.mFreq = 1000; // 1KHz test tone - mPrefs.mWidth = 0; // adaptive default - mPrefs.mHeight = 0; // adaptive default - mPrefs.mFPS = MediaEngine::DEFAULT_VIDEO_FPS; - mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS; + mPrefs.mFreq = 1000; // 1KHz test tone + mPrefs.mWidth = 0; // adaptive default + mPrefs.mHeight = 0; // adaptive default + mPrefs.mFPS = MediaEngine::DEFAULT_VIDEO_FPS; + mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS; + mPrefs.mAecOn = false; + mPrefs.mAgcOn = false; + mPrefs.mNoiseOn = false; +#ifdef MOZ_WEBRTC + mPrefs.mAec = webrtc::kEcUnchanged; + mPrefs.mAgc = webrtc::kAgcUnchanged; + mPrefs.mNoise = webrtc::kNsUnchanged; +#else + mPrefs.mAec = 0; + mPrefs.mAgc = 0; + mPrefs.mNoise = 0; +#endif + mPrefs.mPlayoutDelay = 0; + mPrefs.mFullDuplex = false; nsresult rv; nsCOMPtr prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); if (NS_SUCCEEDED(rv)) { @@ -1500,8 +1449,12 @@ MediaManager::MediaManager() GetPrefs(branch, nullptr); } } - LOG(("%s: default prefs: %dx%d @%dfps (min %d), %dHz test tones", __FUNCTION__, - mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS, mPrefs.mFreq)); + LOG(("%s: default prefs: %dx%d @%dfps (min %d), %dHz test tones, aec: %s," + "agc: %s, noise: %s, aec level: %d, agc level: %d, noise level: %d," + "playout delay: %d, %sfull_duplex", __FUNCTION__, mPrefs.mWidth, mPrefs.mHeight, + mPrefs.mFPS, mPrefs.mMinFPS, mPrefs.mFreq, mPrefs.mAecOn ? "on" : "off", + mPrefs.mAgcOn ? "on": "off", mPrefs.mNoiseOn ? "on": "off", mPrefs.mAec, + mPrefs.mAgc, mPrefs.mNoise, mPrefs.mPlayoutDelay, mPrefs.mFullDuplex ? "" : "not ")); } NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver) @@ -1564,6 +1517,17 @@ MediaManager::Get() { prefs->AddObserver("media.navigator.video.default_height", sSingleton, false); prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false); prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false); + prefs->AddObserver("media.navigator.audio.fake_frequency", sSingleton, false); + prefs->AddObserver("media.navigator.audio.full_duplex", sSingleton, false); +#ifdef MOZ_WEBRTC + prefs->AddObserver("media.getusermedia.aec_enabled", sSingleton, false); + prefs->AddObserver("media.getusermedia.aec", sSingleton, false); + prefs->AddObserver("media.getusermedia.agc_enabled", sSingleton, false); + prefs->AddObserver("media.getusermedia.agc", sSingleton, false); + prefs->AddObserver("media.getusermedia.noise_enabled", sSingleton, false); + prefs->AddObserver("media.getusermedia.noise", sSingleton, false); + prefs->AddObserver("media.getusermedia.playout_delay", sSingleton, false); +#endif } // Prepare async shutdown @@ -2544,6 +2508,16 @@ MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData) GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS); GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS); GetPref(aBranch, "media.navigator.audio.fake_frequency", aData, &mPrefs.mFreq); +#ifdef MOZ_WEBRTC + GetPrefBool(aBranch, "media.getusermedia.aec_enabled", aData, &mPrefs.mAecOn); + GetPrefBool(aBranch, "media.getusermedia.agc_enabled", aData, &mPrefs.mAgcOn); + GetPrefBool(aBranch, "media.getusermedia.noise_enabled", aData, &mPrefs.mNoiseOn); + GetPref(aBranch, "media.getusermedia.aec", aData, &mPrefs.mAec); + GetPref(aBranch, "media.getusermedia.agc", aData, &mPrefs.mAgc); + GetPref(aBranch, "media.getusermedia.noise", aData, &mPrefs.mNoise); + GetPref(aBranch, "media.getusermedia.playout_delay", aData, &mPrefs.mPlayoutDelay); +#endif + GetPrefBool(aBranch, "media.navigator.audio.full_duplex", aData, &mPrefs.mFullDuplex); } void @@ -2571,6 +2545,16 @@ MediaManager::Shutdown() prefs->RemoveObserver("media.navigator.video.default_fps", this); prefs->RemoveObserver("media.navigator.video.default_minfps", this); prefs->RemoveObserver("media.navigator.audio.fake_frequency", this); +#ifdef MOZ_WEBRTC + prefs->RemoveObserver("media.getusermedia.aec_enabled", this); + prefs->RemoveObserver("media.getusermedia.aec", this); + prefs->RemoveObserver("media.getusermedia.agc_enabled", this); + prefs->RemoveObserver("media.getusermedia.agc", this); + prefs->RemoveObserver("media.getusermedia.noise_enabled", this); + prefs->RemoveObserver("media.getusermedia.noise", this); + prefs->RemoveObserver("media.getusermedia.playout_delay", this); +#endif + prefs->RemoveObserver("media.navigator.audio.full_duplex", this); } // Close off any remaining active windows. @@ -2589,8 +2573,8 @@ MediaManager::Shutdown() : mManager(aManager) , mReply(aReply) {} private: - virtual void - Run() + void + Run() override { LOG(("MediaManager Thread Shutdown")); MOZ_ASSERT(MediaManager::IsInMediaThread()); @@ -3051,23 +3035,6 @@ MediaManager::IsActivelyCapturingOrHasAPermission(uint64_t aWindowId) video == nsIPermissionManager::ALLOW_ACTION; } -void -GetUserMediaCallbackMediaStreamListener::AudioConfig(bool aEchoOn, - uint32_t aEcho, - bool aAgcOn, uint32_t aAGC, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) -{ - if (mAudioDevice) { -#ifdef MOZ_WEBRTC - MediaManager::PostTask(FROM_HERE, - NewRunnableMethod(mAudioDevice->GetSource(), &MediaEngineSource::Config, - aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn, - aNoise, aPlayoutDelay)); -#endif - } -} - void GetUserMediaCallbackMediaStreamListener::Invalidate() { diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h index 961d82b481..1c413ea186 100644 --- a/dom/media/MediaManager.h +++ b/dom/media/MediaManager.h @@ -94,7 +94,8 @@ public: NS_IMETHOD GetType(nsAString& aType); Source* GetSource(); nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs); + const MediaEnginePrefs &aPrefs, + const nsACString& aOrigin); nsresult Restart(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs); }; @@ -108,7 +109,8 @@ public: NS_IMETHOD GetType(nsAString& aType); Source* GetSource(); nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs); + const MediaEnginePrefs &aPrefs, + const nsACString& aOrigin); nsresult Restart(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs); }; diff --git a/dom/media/MediaRecorder.h b/dom/media/MediaRecorder.h index b4aabcc013..964237579a 100644 --- a/dom/media/MediaRecorder.h +++ b/dom/media/MediaRecorder.h @@ -48,7 +48,7 @@ public: MediaRecorder(AudioNode& aSrcAudioNode, uint32_t aSrcOutput, nsPIDOMWindow* aOwnerWindow); // nsWrapperCache - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; nsPIDOMWindow* GetParentObject() { return GetOwner(); } diff --git a/dom/media/MediaResource.cpp b/dom/media/MediaResource.cpp index 3bb68b08ea..36e6ff533a 100644 --- a/dom/media/MediaResource.cpp +++ b/dom/media/MediaResource.cpp @@ -1150,70 +1150,68 @@ public: } // Main thread - virtual nsresult Open(nsIStreamListener** aStreamListener) override; - virtual nsresult Close() override; - virtual void Suspend(bool aCloseImmediately) override {} - virtual void Resume() override {} - virtual already_AddRefed GetCurrentPrincipal() override; - virtual bool CanClone() override; - virtual already_AddRefed CloneData(MediaResourceCallback* aCallback) override; - virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override; + nsresult Open(nsIStreamListener** aStreamListener) override; + nsresult Close() override; + void Suspend(bool aCloseImmediately) override {} + void Resume() override {} + already_AddRefed GetCurrentPrincipal() override; + bool CanClone() override; + already_AddRefed CloneData(MediaResourceCallback* aCallback) override; + nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override; // These methods are called off the main thread. // Other thread - virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {} - virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {} - virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, - uint32_t aCount, uint32_t* aBytes) override; - virtual already_AddRefed MediaReadAt(int64_t aOffset, uint32_t aCount) override; - virtual int64_t Tell() override; + void SetReadMode(MediaCacheStream::ReadMode aMode) override {} + void SetPlaybackRate(uint32_t aBytesPerSecond) override {} + nsresult ReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) override; + already_AddRefed MediaReadAt(int64_t aOffset, uint32_t aCount) override; + int64_t Tell() override; // Any thread - virtual void Pin() override {} - virtual void Unpin() override {} - virtual double GetDownloadRate(bool* aIsReliable) override + void Pin() override {} + void Unpin() override {} + double GetDownloadRate(bool* aIsReliable) override { // The data's all already here *aIsReliable = true; return 100*1024*1024; // arbitray, use 100MB/s } - virtual int64_t GetLength() override { + int64_t GetLength() override { MutexAutoLock lock(mLock); EnsureSizeInitialized(); return mSizeInitialized ? mSize : 0; } - virtual int64_t GetNextCachedData(int64_t aOffset) override + int64_t GetNextCachedData(int64_t aOffset) override { MutexAutoLock lock(mLock); EnsureSizeInitialized(); return (aOffset < mSize) ? aOffset : -1; } - virtual int64_t GetCachedDataEnd(int64_t aOffset) override { + int64_t GetCachedDataEnd(int64_t aOffset) override { MutexAutoLock lock(mLock); EnsureSizeInitialized(); return std::max(aOffset, mSize); } - virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override { return true; } - virtual bool IsSuspendedByCache() override { return true; } - virtual bool IsSuspended() override { return true; } - virtual bool IsTransportSeekable() override { return true; } + bool IsDataCachedToEndOfResource(int64_t aOffset) override { return true; } + bool IsSuspendedByCache() override { return true; } + bool IsSuspended() override { return true; } + bool IsTransportSeekable() override { return true; } nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override; - virtual size_t SizeOfExcludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { // Might be useful to track in the future: // - mInput return BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf); } - virtual size_t SizeOfIncludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/MediaResource.h b/dom/media/MediaResource.h index cd2f4e6a14..9e93c43139 100644 --- a/dom/media/MediaResource.h +++ b/dom/media/MediaResource.h @@ -380,11 +380,10 @@ private: class BaseMediaResource : public MediaResource { public: - virtual nsIURI* URI() const override { return mURI; } - virtual void SetLoadInBackground(bool aLoadInBackground) override; + nsIURI* URI() const override { return mURI; } + void SetLoadInBackground(bool aLoadInBackground) override; - virtual size_t SizeOfExcludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { // Might be useful to track in the future: // - mChannel @@ -397,8 +396,7 @@ public: return size; } - virtual size_t SizeOfIncludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } @@ -429,7 +427,7 @@ protected: MOZ_COUNT_DTOR(BaseMediaResource); } - virtual const nsCString& GetContentType() const override + const nsCString& GetContentType() const override { return mContentType; } @@ -556,21 +554,21 @@ public: // Ensure that the media cache writes any data held in its partial block. // Called on the main thread. - virtual void FlushCache() override; + void FlushCache() override; // Notify that the last data byte range was loaded. - virtual void NotifyLastByteRange() override; + void NotifyLastByteRange() override; // Main thread - virtual nsresult Open(nsIStreamListener** aStreamListener) override; - virtual nsresult Close() override; - virtual void Suspend(bool aCloseImmediately) override; - virtual void Resume() override; - virtual already_AddRefed GetCurrentPrincipal() override; + nsresult Open(nsIStreamListener** aStreamListener) override; + nsresult Close() override; + void Suspend(bool aCloseImmediately) override; + void Resume() override; + already_AddRefed GetCurrentPrincipal() override; // Return true if the stream has been closed. - bool IsClosed() const { return mCacheStream.IsClosed(); } - virtual bool CanClone() override; - virtual already_AddRefed CloneData(MediaResourceCallback* aDecoder) override; + bool IsClosed() const { return mCacheStream.IsClosed(); } + bool CanClone() override; + already_AddRefed CloneData(MediaResourceCallback* aDecoder) override; // Set statistics to be recorded to the object passed in. If not called, // |ChannelMediaResource| will create it's own statistics objects in |Open|. void RecordStatisticsTo(MediaChannelStatistics *aStatistics) override { @@ -580,31 +578,30 @@ public: mChannelStatistics = aStatistics; } } - virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override; - virtual void EnsureCacheUpToDate() override; + nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override; + void EnsureCacheUpToDate() override; // Other thread - virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override; - virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override; - virtual nsresult ReadAt(int64_t offset, char* aBuffer, - uint32_t aCount, uint32_t* aBytes) override; - virtual already_AddRefed MediaReadAt(int64_t aOffset, uint32_t aCount) override; - virtual int64_t Tell() override; + void SetReadMode(MediaCacheStream::ReadMode aMode) override; + void SetPlaybackRate(uint32_t aBytesPerSecond) override; + nsresult ReadAt(int64_t offset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) override; + already_AddRefed MediaReadAt(int64_t aOffset, uint32_t aCount) override; + int64_t Tell() override; // Any thread - virtual void Pin() override; - virtual void Unpin() override; - virtual double GetDownloadRate(bool* aIsReliable) override; - virtual int64_t GetLength() override; - virtual int64_t GetNextCachedData(int64_t aOffset) override; - virtual int64_t GetCachedDataEnd(int64_t aOffset) override; - virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override; - virtual bool IsSuspendedByCache() override; - virtual bool IsSuspended() override; - virtual bool IsTransportSeekable() override; + void Pin() override; + void Unpin() override; + double GetDownloadRate(bool* aIsReliable) override; + int64_t GetLength() override; + int64_t GetNextCachedData(int64_t aOffset) override; + int64_t GetCachedDataEnd(int64_t aOffset) override; + bool IsDataCachedToEndOfResource(int64_t aOffset) override; + bool IsSuspendedByCache() override; + bool IsSuspended() override; + bool IsTransportSeekable() override; - virtual size_t SizeOfExcludingThis( - MallocSizeOf aMallocSizeOf) const override { + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { // Might be useful to track in the future: // - mListener (seems minor) // - mChannelStatistics (seems minor) @@ -616,8 +613,7 @@ public: return size; } - virtual size_t SizeOfIncludingThis( - MallocSizeOf aMallocSizeOf) const override { + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } @@ -642,7 +638,7 @@ public: }; friend class Listener; - virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override; + nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override; protected: // These are called on the main thread by Listener. diff --git a/dom/media/MediaSegment.h b/dom/media/MediaSegment.h index 423ad91b1a..25546797ed 100644 --- a/dom/media/MediaSegment.h +++ b/dom/media/MediaSegment.h @@ -149,11 +149,11 @@ protected: */ template class MediaSegmentBase : public MediaSegment { public: - virtual MediaSegment* CreateEmptyClone() const override + MediaSegment* CreateEmptyClone() const override { return new C(); } - virtual void AppendFrom(MediaSegment* aSource) override + void AppendFrom(MediaSegment* aSource) override { NS_ASSERTION(aSource->GetType() == C::StaticType(), "Wrong type"); AppendFromInternal(static_cast(aSource)); @@ -162,8 +162,8 @@ public: { AppendFromInternal(aSource); } - virtual void AppendSlice(const MediaSegment& aSource, - StreamTime aStart, StreamTime aEnd) override + void AppendSlice(const MediaSegment& aSource, + StreamTime aStart, StreamTime aEnd) override { NS_ASSERTION(aSource.GetType() == C::StaticType(), "Wrong type"); AppendSliceInternal(static_cast(aSource), aStart, aEnd); @@ -176,7 +176,7 @@ public: * Replace the first aDuration ticks with null media data, because the data * will not be required again. */ - virtual void ForgetUpTo(StreamTime aDuration) override + void ForgetUpTo(StreamTime aDuration) override { if (mChunks.IsEmpty() || aDuration <= 0) { return; @@ -194,7 +194,7 @@ public: mChunks.InsertElementAt(0)->SetNull(aDuration); mDuration += aDuration; } - virtual void FlushAfter(StreamTime aNewEnd) override + void FlushAfter(StreamTime aNewEnd) override { if (mChunks.IsEmpty()) { return; @@ -217,7 +217,7 @@ public: } mDuration = aNewEnd; } - virtual void InsertNullDataAtStart(StreamTime aDuration) override + void InsertNullDataAtStart(StreamTime aDuration) override { if (aDuration <= 0) { return; @@ -232,7 +232,7 @@ public: #endif mDuration += aDuration; } - virtual void AppendNullData(StreamTime aDuration) override + void AppendNullData(StreamTime aDuration) override { if (aDuration <= 0) { return; @@ -244,7 +244,7 @@ public: } mDuration += aDuration; } - virtual void ReplaceWithDisabled() override + void ReplaceWithDisabled() override { if (GetType() != AUDIO) { MOZ_CRASH("Disabling unknown segment type"); @@ -253,7 +253,7 @@ public: Clear(); AppendNullData(duration); } - virtual void Clear() override + void Clear() override { mDuration = 0; mChunks.Clear(); @@ -315,7 +315,7 @@ public: } #endif - virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { size_t amount = mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf); for (size_t i = 0; i < mChunks.Length(); i++) { @@ -324,7 +324,7 @@ public: return amount; } - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/MediaShutdownManager.cpp b/dom/media/MediaShutdownManager.cpp index b55954b664..615e623bc2 100644 --- a/dom/media/MediaShutdownManager.cpp +++ b/dom/media/MediaShutdownManager.cpp @@ -18,8 +18,9 @@ extern LazyLogModule gMediaDecoderLog; NS_IMPL_ISUPPORTS(MediaShutdownManager, nsIObserver) MediaShutdownManager::MediaShutdownManager() - : mIsObservingShutdown(false), - mIsDoingXPCOMShutDown(false) + : mIsObservingShutdown(false) + , mIsDoingXPCOMShutDown(false) + , mCompletedShutdown(false) { MOZ_ASSERT(NS_IsMainThread()); MOZ_COUNT_CTOR(MediaShutdownManager); @@ -114,11 +115,35 @@ MediaShutdownManager::Shutdown() // Iterate over the decoders and shut them down, and remove them from the // hashtable. + nsTArray> promises; for (auto iter = mDecoders.Iter(); !iter.Done(); iter.Next()) { - iter.Get()->GetKey()->Shutdown(); + promises.AppendElement(iter.Get()->GetKey()->Shutdown()->Then( + // We want to ensure that all shutdowns have completed, regardless + // of the ShutdownPromise being resolved or rejected. At this stage, + // a MediaDecoder's ShutdownPromise is only ever resolved, but as this may + // change in the future we want to avoid nasty surprises, so we wrap the + // ShutdownPromise into our own that will only ever be resolved. + AbstractThread::MainThread(), __func__, + []() -> RefPtr { + return ShutdownPromise::CreateAndResolve(true, __func__); + }, + []() -> RefPtr { + return ShutdownPromise::CreateAndResolve(true, __func__); + })->CompletionPromise()); iter.Remove(); } + if (!promises.IsEmpty()) { + ShutdownPromise::All(AbstractThread::MainThread(), promises) + ->Then(AbstractThread::MainThread(), __func__, this, + &MediaShutdownManager::FinishShutdown, + &MediaShutdownManager::FinishShutdown); + // Wait for all decoders to complete their async shutdown... + while (!mCompletedShutdown) { + NS_ProcessNextEvent(NS_GetCurrentThread(), true); + } + } + // Remove the MediaShutdownManager instance from the shutdown observer // list. nsContentUtils::UnregisterShutdownObserver(this); @@ -132,4 +157,11 @@ MediaShutdownManager::Shutdown() DECODER_LOG(LogLevel::Debug, ("MediaShutdownManager::Shutdown() end.")); } +void +MediaShutdownManager::FinishShutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + mCompletedShutdown = true; +} + } // namespace mozilla diff --git a/dom/media/MediaShutdownManager.h b/dom/media/MediaShutdownManager.h index aa2e2ff1f0..161aeb1c35 100644 --- a/dom/media/MediaShutdownManager.h +++ b/dom/media/MediaShutdownManager.h @@ -81,6 +81,7 @@ private: virtual ~MediaShutdownManager(); void Shutdown(); + void FinishShutdown(); // Ensures we have a shutdown listener if we need one, and removes the // listener and destroys the singleton if we don't. @@ -97,6 +98,10 @@ private: bool mIsObservingShutdown; bool mIsDoingXPCOMShutDown; + + // Will be set to true once all registered MediaDecoders have completed their + // shutdown. + bool mCompletedShutdown; }; } // namespace mozilla diff --git a/dom/media/MediaStreamError.h b/dom/media/MediaStreamError.h index 4004c2f642..6f6bf09a90 100644 --- a/dom/media/MediaStreamError.h +++ b/dom/media/MediaStreamError.h @@ -75,7 +75,7 @@ public: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaStreamError) NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIASTREAMERROR_IMPLEMENTATION_IID) - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; nsPIDOMWindow* GetParentObject() const { diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index 19ed973f4f..df17b48768 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -23,6 +23,7 @@ #include "AudioNodeStream.h" #include "AudioNodeExternalInputStream.h" #include "mozilla/dom/AudioContextBinding.h" +#include "mozilla/media/MediaUtils.h" #include #include "DOMMediaStream.h" #include "GeckoProfiler.h" @@ -30,6 +31,7 @@ #ifdef MOZ_WEBRTC #include "AudioOutputObserver.h" #endif +#include "mtransport/runnable_utils.h" #include "webaudio/blink/HRTFDatabaseLoader.h" @@ -368,6 +370,8 @@ MediaStreamGraphImpl::UpdateStreamOrder() } } } + // Note that this looks for any audio streams, input or output, and switches to a + // SystemClockDriver if there are none if (!audioTrackPresent && mRealtime && CurrentDriver()->AsAudioCallbackDriver()) { @@ -375,19 +379,23 @@ MediaStreamGraphImpl::UpdateStreamOrder() if (CurrentDriver()->AsAudioCallbackDriver()->IsStarted()) { if (mLifecycleState == LIFECYCLE_RUNNING) { SystemClockDriver* driver = new SystemClockDriver(this); - mMixer.RemoveCallback(CurrentDriver()->AsAudioCallbackDriver()); CurrentDriver()->SwitchAtNextIteration(driver); } } } + bool switching = false; + { + MonitorAutoLock mon(mMonitor); + switching = CurrentDriver()->Switching(); + } + if (audioTrackPresent && mRealtime && !CurrentDriver()->AsAudioCallbackDriver() && - !CurrentDriver()->Switching()) { + !switching) { MonitorAutoLock mon(mMonitor); if (mLifecycleState == LIFECYCLE_RUNNING) { AudioCallbackDriver* driver = new AudioCallbackDriver(this); - mMixer.AddCallback(driver); CurrentDriver()->SwitchAtNextIteration(driver); } } @@ -641,12 +649,18 @@ MediaStreamGraphImpl::CreateOrDestroyAudioStreams(MediaStream* aStream) audioOutputStream->mLastTickWritten = 0; audioOutputStream->mTrackID = tracks->GetID(); + bool switching = false; + + { + MonitorAutoLock lock(mMonitor); + switching = CurrentDriver()->Switching(); + } + if (!CurrentDriver()->AsAudioCallbackDriver() && - !CurrentDriver()->Switching()) { + !switching) { MonitorAutoLock mon(mMonitor); if (mLifecycleState == LIFECYCLE_RUNNING) { AudioCallbackDriver* driver = new AudioCallbackDriver(this); - mMixer.AddCallback(driver); CurrentDriver()->SwitchAtNextIteration(driver); } } @@ -929,6 +943,164 @@ MediaStreamGraphImpl::PlayVideo(MediaStream* aStream) } } +void +MediaStreamGraphImpl::OpenAudioInputImpl(CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener) +{ + // Bug 1238038 Need support for multiple mics at once + if (mInputDeviceUsers.Count() > 0 && + !mInputDeviceUsers.Get(aListener, nullptr)) { + NS_ASSERTION(false, "Input from multiple mics not yet supported; bug 1238038"); + // Need to support separate input-only AudioCallback drivers; they'll + // call us back on "other" threads. We will need to echo-cancel them, though. + return; + } + mInputWanted = true; + + // Add to count of users for this ID. + // XXX Since we can't rely on IDs staying valid (ugh), use the listener as + // a stand-in for the ID. Fix as part of support for multiple-captures + // (Bug 1238038) + uint32_t count = 0; + mInputDeviceUsers.Get(aListener, &count); // ok if this fails + count++; + mInputDeviceUsers.Put(aListener, count); // creates a new entry in the hash if needed + + // aID is a cubeb_devid, and we assume that opaque ptr is valid until + // we close cubeb. + mInputDeviceID = aID; + if (count == 1) { // first open for this listener + mAudioInputs.AppendElement(aListener); // always monitor speaker data + } + + // Switch Drivers since we're adding input (to input-only or full-duplex) + MonitorAutoLock mon(mMonitor); + if (mLifecycleState == LIFECYCLE_RUNNING) { + AudioCallbackDriver* driver = new AudioCallbackDriver(this); + driver->SetInputListener(aListener); + CurrentDriver()->SwitchAtNextIteration(driver); + } +} + +nsresult +MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener) +{ + // So, so, so annoying. Can't AppendMessage except on Mainthread + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(WrapRunnable(this, + &MediaStreamGraphImpl::OpenAudioInput, + aID, aListener)); + return NS_OK; + } + class Message : public ControlMessage { + public: + Message(MediaStreamGraphImpl *aGraph, CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener) : + ControlMessage(nullptr), mGraph(aGraph), mID(aID), mListener(aListener) {} + virtual void Run() + { + mGraph->OpenAudioInputImpl(mID, mListener); + } + MediaStreamGraphImpl *mGraph; + // aID is a cubeb_devid, and we assume that opaque ptr is valid until + // we close cubeb. + CubebUtils::AudioDeviceID mID; + RefPtr mListener; + }; + // XXX Check not destroyed! + this->AppendMessage(MakeUnique(this, aID, aListener)); + return NS_OK; +} + +void +MediaStreamGraphImpl::CloseAudioInputImpl(AudioDataListener *aListener) +{ + uint32_t count; + DebugOnly result = mInputDeviceUsers.Get(aListener, &count); + MOZ_ASSERT(result); + if (--count > 0) { + mInputDeviceUsers.Put(aListener, count); + return; // still in use + } + mInputDeviceUsers.Remove(aListener); + mInputDeviceID = nullptr; + mInputWanted = false; + AudioCallbackDriver *driver = CurrentDriver()->AsAudioCallbackDriver(); + if (driver) { + driver->RemoveInputListener(aListener); + } + mAudioInputs.RemoveElement(aListener); + + // Switch Drivers since we're adding or removing an input (to nothing/system or output only) + bool audioTrackPresent = false; + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + MediaStream* stream = mStreams[i]; + // If this is a AudioNodeStream, force a AudioCallbackDriver. + if (stream->AsAudioNodeStream()) { + audioTrackPresent = true; + } else if (CurrentDriver()->AsAudioCallbackDriver()) { + // only if there's a real switch! + for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO); + !tracks.IsEnded(); tracks.Next()) { + audioTrackPresent = true; + } + } + } + + MonitorAutoLock mon(mMonitor); + if (mLifecycleState == LIFECYCLE_RUNNING) { + GraphDriver* driver; + if (audioTrackPresent) { + // We still have audio output + STREAM_LOG(LogLevel::Debug, ("CloseInput: output present (AudioCallback)")); + + driver = new AudioCallbackDriver(this); + CurrentDriver()->SwitchAtNextIteration(driver); + } else if (CurrentDriver()->AsAudioCallbackDriver()) { + STREAM_LOG(LogLevel::Debug, ("CloseInput: no output present (SystemClockCallback)")); + + driver = new SystemClockDriver(this); + CurrentDriver()->SwitchAtNextIteration(driver); + } // else SystemClockDriver->SystemClockDriver, no switch + } +} + +void +MediaStreamGraphImpl::CloseAudioInput(AudioDataListener *aListener) +{ + // So, so, so annoying. Can't AppendMessage except on Mainthread + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(WrapRunnable(this, + &MediaStreamGraphImpl::CloseAudioInput, + aListener)); + return; + } + class Message : public ControlMessage { + public: + Message(MediaStreamGraphImpl *aGraph, AudioDataListener *aListener) : + ControlMessage(nullptr), mGraph(aGraph), mListener(aListener) {} + virtual void Run() + { + mGraph->CloseAudioInputImpl(mListener); + } + MediaStreamGraphImpl *mGraph; + RefPtr mListener; + }; + this->AppendMessage(MakeUnique(this, aListener)); +} + + +// All AudioInput listeners get the same speaker data (at least for now). +void +MediaStreamGraph::NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) +{ + for (auto& listener : mAudioInputs) { + listener->NotifyOutputData(this, aBuffer, aFrames, aRate, aChannels); + } +} + bool MediaStreamGraphImpl::ShouldUpdateMainThread() { @@ -1029,7 +1201,7 @@ MediaStreamGraphImpl::AllFinishedStreamsNotified() } void -MediaStreamGraphImpl::RunMessageAfterProcessing(nsAutoPtr aMessage) +MediaStreamGraphImpl::RunMessageAfterProcessing(UniquePtr aMessage) { MOZ_ASSERT(CurrentDriver()->OnThread()); @@ -1049,7 +1221,7 @@ MediaStreamGraphImpl::RunMessagesInQueue() // batch corresponding to an event loop task). This isolates the performance // of different scripts to some extent. for (uint32_t i = 0; i < mFrontMessageQueue.Length(); ++i) { - nsTArray >& messages = mFrontMessageQueue[i].mMessages; + nsTArray>& messages = mFrontMessageQueue[i].mMessages; for (uint32_t j = 0; j < messages.Length(); ++j) { messages[j]->Run(); @@ -1182,20 +1354,6 @@ MediaStreamGraphImpl::Process() mMixer.FinishMixing(); } - // If we are switching away from an AudioCallbackDriver, we don't need the - // mixer anymore. - if (CurrentDriver()->AsAudioCallbackDriver() && - CurrentDriver()->Switching()) { - bool isStarted; - { - MonitorAutoLock mon(mMonitor); - isStarted = CurrentDriver()->AsAudioCallbackDriver()->IsStarted(); - } - if (isStarted) { - mMixer.RemoveCallback(CurrentDriver()->AsAudioCallbackDriver()); - } - } - if (!allBlockedForever) { EnsureNextIteration(); } @@ -1294,17 +1452,20 @@ MediaStreamGraphImpl::ApplyStreamUpdate(StreamUpdate* aUpdate) } void -MediaStreamGraphImpl::ForceShutDown() +MediaStreamGraphImpl::ForceShutDown(ShutdownTicket* aShutdownTicket) { NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread"); STREAM_LOG(LogLevel::Debug, ("MediaStreamGraph %p ForceShutdown", this)); { MonitorAutoLock lock(mMonitor); mForceShutDown = true; + mForceShutdownTicket = aShutdownTicket; EnsureNextIterationLocked(); } } +/* static */ StaticRefPtr gMediaStreamGraphShutdownBlocker; + namespace { class MediaStreamGraphShutDownRunnable : public nsRunnable { @@ -1330,7 +1491,15 @@ public: } #endif - mGraph->mDriver->Shutdown(); + mGraph->mDriver->Shutdown(); // This will wait until it's shutdown since + // we'll start tearing down the graph after this + + // We may be one of several graphs. Drop ticket to eventually unblock shutdown. + mGraph->mForceShutdownTicket = nullptr; + + // We can't block past the final LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION + // stage, since completion of that stage requires all streams to be freed, + // which requires shutdown to proceed. // mGraph's thread is not running so it's OK to do whatever here if (mGraph->IsEmpty()) { @@ -1385,11 +1554,11 @@ private: class CreateMessage : public ControlMessage { public: explicit CreateMessage(MediaStream* aStream) : ControlMessage(aStream) {} - virtual void Run() override + void Run() override { mStream->GraphImpl()->AddStreamGraphThread(mStream); } - virtual void RunDuringShutdown() override + void RunDuringShutdown() override { // Make sure to run this message during shutdown too, to make sure // that we balance the number of streams registered with the graph @@ -1398,14 +1567,6 @@ public: } }; -class MediaStreamGraphShutdownObserver final : public nsIObserver -{ - ~MediaStreamGraphShutdownObserver() {} -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER -}; - } // namespace void @@ -1417,7 +1578,7 @@ MediaStreamGraphImpl::RunInStableState(bool aSourceIsMSG) // When we're doing a forced shutdown, pending control messages may be // run on the main thread via RunDuringShutdown. Those messages must // run without the graph monitor being held. So, we collect them here. - nsTArray > controlMessagesToRunDuringShutdown; + nsTArray> controlMessagesToRunDuringShutdown; { MonitorAutoLock lock(mMonitor); @@ -1601,7 +1762,7 @@ MediaStreamGraphImpl::EnsureStableStateEventPosted() } void -MediaStreamGraphImpl::AppendMessage(ControlMessage* aMessage) +MediaStreamGraphImpl::AppendMessage(UniquePtr aMessage) { MOZ_ASSERT(NS_IsMainThread(), "main thread only"); MOZ_ASSERT(!aMessage->GetStream() || @@ -1624,7 +1785,6 @@ MediaStreamGraphImpl::AppendMessage(ControlMessage* aMessage) #ifdef DEBUG mCanRunMessagesSynchronously = true; #endif - delete aMessage; if (IsEmpty() && mLifecycleState >= LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION) { @@ -1638,7 +1798,7 @@ MediaStreamGraphImpl::AppendMessage(ControlMessage* aMessage) return; } - mCurrentTaskMessageQueue.AppendElement(aMessage); + mCurrentTaskMessageQueue.AppendElement(Move(aMessage)); EnsureRunInStableState(); } @@ -1805,18 +1965,18 @@ MediaStream::Destroy() class Message : public ControlMessage { public: explicit Message(MediaStream* aStream) : ControlMessage(aStream) {} - virtual void Run() + void Run() override { mStream->RemoveAllListenersImpl(); auto graph = mStream->GraphImpl(); mStream->DestroyImpl(); graph->RemoveStreamGraphThread(mStream); } - virtual void RunDuringShutdown() + void RunDuringShutdown() override { Run(); } }; mWrapper = nullptr; - GraphImpl()->AppendMessage(new Message(this)); + GraphImpl()->AppendMessage(MakeUnique(this)); // Message::RunDuringShutdown may have removed this stream from the graph, // but our kungFuDeathGrip above will have kept this stream alive if // necessary. @@ -1829,13 +1989,13 @@ MediaStream::AddAudioOutput(void* aKey) class Message : public ControlMessage { public: Message(MediaStream* aStream, void* aKey) : ControlMessage(aStream), mKey(aKey) {} - virtual void Run() + void Run() override { mStream->AddAudioOutputImpl(mKey); } void* mKey; }; - GraphImpl()->AppendMessage(new Message(this, aKey)); + GraphImpl()->AppendMessage(MakeUnique(this, aKey)); } void @@ -1857,14 +2017,14 @@ MediaStream::SetAudioOutputVolume(void* aKey, float aVolume) public: Message(MediaStream* aStream, void* aKey, float aVolume) : ControlMessage(aStream), mKey(aKey), mVolume(aVolume) {} - virtual void Run() + void Run() override { mStream->SetAudioOutputVolumeImpl(mKey, mVolume); } void* mKey; float mVolume; }; - GraphImpl()->AppendMessage(new Message(this, aKey, aVolume)); + GraphImpl()->AppendMessage(MakeUnique(this, aKey, aVolume)); } void @@ -1896,13 +2056,13 @@ MediaStream::RemoveAudioOutput(void* aKey) public: Message(MediaStream* aStream, void* aKey) : ControlMessage(aStream), mKey(aKey) {} - virtual void Run() + void Run() override { mStream->RemoveAudioOutputImpl(mKey); } void* mKey; }; - GraphImpl()->AppendMessage(new Message(this, aKey)); + GraphImpl()->AppendMessage(MakeUnique(this, aKey)); } void @@ -1932,13 +2092,13 @@ MediaStream::AddVideoOutput(VideoFrameContainer* aContainer) public: Message(MediaStream* aStream, VideoFrameContainer* aContainer) : ControlMessage(aStream), mContainer(aContainer) {} - virtual void Run() + void Run() override { mStream->AddVideoOutputImpl(mContainer.forget()); } RefPtr mContainer; }; - GraphImpl()->AppendMessage(new Message(this, aContainer)); + GraphImpl()->AppendMessage(MakeUnique(this, aContainer)); } void @@ -1948,13 +2108,13 @@ MediaStream::RemoveVideoOutput(VideoFrameContainer* aContainer) public: Message(MediaStream* aStream, VideoFrameContainer* aContainer) : ControlMessage(aStream), mContainer(aContainer) {} - virtual void Run() + void Run() override { mStream->RemoveVideoOutputImpl(mContainer); } RefPtr mContainer; }; - GraphImpl()->AppendMessage(new Message(this, aContainer)); + GraphImpl()->AppendMessage(MakeUnique(this, aContainer)); } void @@ -1964,7 +2124,7 @@ MediaStream::Suspend() public: explicit Message(MediaStream* aStream) : ControlMessage(aStream) {} - virtual void Run() + void Run() override { mStream->GraphImpl()->IncrementSuspendCount(mStream); } @@ -1975,7 +2135,7 @@ MediaStream::Suspend() if (mMainThreadDestroyed) { return; } - GraphImpl()->AppendMessage(new Message(this)); + GraphImpl()->AppendMessage(MakeUnique(this)); } void @@ -1985,7 +2145,7 @@ MediaStream::Resume() public: explicit Message(MediaStream* aStream) : ControlMessage(aStream) {} - virtual void Run() + void Run() override { mStream->GraphImpl()->DecrementSuspendCount(mStream); } @@ -1996,7 +2156,7 @@ MediaStream::Resume() if (mMainThreadDestroyed) { return; } - GraphImpl()->AppendMessage(new Message(this)); + GraphImpl()->AppendMessage(MakeUnique(this)); } void @@ -2020,13 +2180,13 @@ MediaStream::AddListener(MediaStreamListener* aListener) public: Message(MediaStream* aStream, MediaStreamListener* aListener) : ControlMessage(aStream), mListener(aListener) {} - virtual void Run() + void Run() override { mStream->AddListenerImpl(mListener.forget()); } RefPtr mListener; }; - GraphImpl()->AppendMessage(new Message(this, aListener)); + GraphImpl()->AppendMessage(MakeUnique(this, aListener)); } void @@ -2045,7 +2205,7 @@ MediaStream::RemoveListener(MediaStreamListener* aListener) public: Message(MediaStream* aStream, MediaStreamListener* aListener) : ControlMessage(aStream), mListener(aListener) {} - virtual void Run() + void Run() override { mStream->RemoveListenerImpl(mListener); } @@ -2054,7 +2214,7 @@ MediaStream::RemoveListener(MediaStreamListener* aListener) // If the stream is destroyed the Listeners have or will be // removed. if (!IsDestroyed()) { - GraphImpl()->AppendMessage(new Message(this, aListener)); + GraphImpl()->AppendMessage(MakeUnique(this, aListener)); } } @@ -2078,12 +2238,12 @@ MediaStream::RunAfterPendingUpdates(already_AddRefed aRunnable) already_AddRefed aRunnable) : ControlMessage(aStream) , mRunnable(aRunnable) {} - virtual void Run() override + void Run() override { mStream->Graph()-> DispatchToMainThreadAfterStreamStateUpdate(mRunnable.forget()); } - virtual void RunDuringShutdown() override + void RunDuringShutdown() override { // Don't run mRunnable now as it may call AppendMessage() which would // assume that there are no remaining controlMessagesToRunDuringShutdown. @@ -2094,7 +2254,7 @@ MediaStream::RunAfterPendingUpdates(already_AddRefed aRunnable) nsCOMPtr mRunnable; }; - graph->AppendMessage(new Message(this, runnable.forget())); + graph->AppendMessage(MakeUnique(this, runnable.forget())); } void @@ -2116,14 +2276,14 @@ MediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled) public: Message(MediaStream* aStream, TrackID aTrackID, bool aEnabled) : ControlMessage(aStream), mTrackID(aTrackID), mEnabled(aEnabled) {} - virtual void Run() + void Run() override { mStream->SetTrackEnabledImpl(mTrackID, mEnabled); } TrackID mTrackID; bool mEnabled; }; - GraphImpl()->AppendMessage(new Message(this, aTrackID, aEnabled)); + GraphImpl()->AppendMessage(MakeUnique(this, aTrackID, aEnabled)); } void @@ -2176,9 +2336,32 @@ MediaStream::AddMainThreadListener(MainThreadMediaStreamListener* aListener) NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(runnable.forget()))); } +nsresult +SourceMediaStream::OpenAudioInput(CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener) +{ + if (GraphImpl()) { + mInputListener = aListener; + return GraphImpl()->OpenAudioInput(aID, aListener); + } + return NS_ERROR_FAILURE; +} + +void +SourceMediaStream::CloseAudioInput() +{ + // Destroy() may have run already and cleared this + if (GraphImpl() && mInputListener) { + GraphImpl()->CloseAudioInput(mInputListener); + } + mInputListener = nullptr; +} + void SourceMediaStream::DestroyImpl() { + CloseAudioInput(); + // Hold mMutex while mGraph is reset so that other threads holding mMutex // can null-check know that the graph will not destroyed. MutexAutoLock lock(mMutex); @@ -2324,13 +2507,13 @@ SourceMediaStream::NotifyListenersEvent(MediaStreamListener::MediaStreamGraphEve public: Message(SourceMediaStream* aStream, MediaStreamListener::MediaStreamGraphEvent aEvent) : ControlMessage(aStream), mEvent(aEvent) {} - virtual void Run() + void Run() override { mStream->AsSourceStream()->NotifyListenersEventImpl(mEvent); } MediaStreamListener::MediaStreamGraphEvent mEvent; }; - GraphImpl()->AppendMessage(new Message(this, aNewEvent)); + GraphImpl()->AppendMessage(MakeUnique(this, aNewEvent)); } void @@ -2489,20 +2672,20 @@ MediaInputPort::Destroy() public: explicit Message(MediaInputPort* aPort) : ControlMessage(nullptr), mPort(aPort) {} - virtual void Run() + void Run() override { mPort->Disconnect(); --mPort->GraphImpl()->mPortCount; mPort->SetGraphImpl(nullptr); NS_RELEASE(mPort); } - virtual void RunDuringShutdown() + void RunDuringShutdown() override { Run(); } MediaInputPort* mPort; }; - GraphImpl()->AppendMessage(new Message(this)); + GraphImpl()->AppendMessage(MakeUnique(this)); } MediaStreamGraphImpl* @@ -2538,11 +2721,11 @@ MediaInputPort::BlockTrackId(TrackID aTrackId) explicit Message(MediaInputPort* aPort, TrackID aTrackId) : ControlMessage(aPort->GetDestination()), mPort(aPort), mTrackId(aTrackId) {} - virtual void Run() + void Run() override { mPort->BlockTrackIdImpl(mTrackId); } - virtual void RunDuringShutdown() + void RunDuringShutdown() override { Run(); } @@ -2552,7 +2735,7 @@ MediaInputPort::BlockTrackId(TrackID aTrackId) MOZ_ASSERT(aTrackId != TRACK_NONE && aTrackId != TRACK_INVALID && aTrackId != TRACK_ANY, "Only explicit TrackID is allowed"); - GraphImpl()->AppendMessage(new Message(this, aTrackId)); + GraphImpl()->AppendMessage(MakeUnique(this, aTrackId)); } already_AddRefed @@ -2566,14 +2749,14 @@ ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, TrackID aTrackID, explicit Message(MediaInputPort* aPort) : ControlMessage(aPort->GetDestination()), mPort(aPort) {} - virtual void Run() + void Run() override { mPort->Init(); // The graph holds its reference implicitly mPort->GraphImpl()->SetStreamOrderDirty(); Unused << mPort.forget(); } - virtual void RunDuringShutdown() + void RunDuringShutdown() override { Run(); } @@ -2585,7 +2768,7 @@ ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, TrackID aTrackID, RefPtr port = new MediaInputPort(aStream, aTrackID, this, aInputNumber, aOutputNumber); port->SetGraphImpl(GraphImpl()); - GraphImpl()->AppendMessage(new Message(port)); + GraphImpl()->AppendMessage(MakeUnique(port)); return port.forget(); } @@ -2596,12 +2779,12 @@ ProcessedMediaStream::Finish() public: explicit Message(ProcessedMediaStream* aStream) : ControlMessage(aStream) {} - virtual void Run() + void Run() override { mStream->GraphImpl()->FinishStream(mStream); } }; - GraphImpl()->AppendMessage(new Message(this)); + GraphImpl()->AppendMessage(MakeUnique(this)); } void @@ -2611,13 +2794,13 @@ ProcessedMediaStream::SetAutofinish(bool aAutofinish) public: Message(ProcessedMediaStream* aStream, bool aAutofinish) : ControlMessage(aStream), mAutofinish(aAutofinish) {} - virtual void Run() + void Run() override { static_cast(mStream)->SetAutofinishImpl(mAutofinish); } bool mAutofinish; }; - GraphImpl()->AppendMessage(new Message(this, aAutofinish)); + GraphImpl()->AppendMessage(MakeUnique(this, aAutofinish)); } void @@ -2638,6 +2821,10 @@ MediaStreamGraphImpl::MediaStreamGraphImpl(GraphDriverType aDriverRequested, dom::AudioChannel aChannel) : MediaStreamGraph(aSampleRate) , mPortCount(0) + , mInputWanted(false) + , mInputDeviceID(nullptr) + , mOutputWanted(true) + , mOutputDeviceID(nullptr) , mNeedAnotherIteration(false) , mGraphDriverAsleep(false) , mMonitor("MediaStreamGraphImpl") @@ -2667,7 +2854,6 @@ MediaStreamGraphImpl::MediaStreamGraphImpl(GraphDriverType aDriverRequested, if (aDriverRequested == AUDIO_THREAD_DRIVER) { AudioCallbackDriver* driver = new AudioCallbackDriver(this); mDriver = driver; - mMixer.AddCallback(driver); } else { mDriver = new SystemClockDriver(this); } @@ -2690,26 +2876,6 @@ MediaStreamGraphImpl::Destroy() mSelfRef = nullptr; } -NS_IMPL_ISUPPORTS(MediaStreamGraphShutdownObserver, nsIObserver) - -static bool gShutdownObserverRegistered = false; - -NS_IMETHODIMP -MediaStreamGraphShutdownObserver::Observe(nsISupports *aSubject, - const char *aTopic, - const char16_t *aData) -{ - if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { - for (auto iter = gGraphs.Iter(); !iter.Done(); iter.Next()) { - MediaStreamGraphImpl* graph = iter.UserData(); - graph->ForceShutDown(); - } - nsContentUtils::UnregisterShutdownObserver(this); - gShutdownObserverRegistered = false; - } - return NS_OK; -} - MediaStreamGraph* MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequested, dom::AudioChannel aChannel) @@ -2720,9 +2886,38 @@ MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequ MediaStreamGraphImpl* graph = nullptr; if (!gGraphs.Get(channel, &graph)) { - if (!gShutdownObserverRegistered) { - gShutdownObserverRegistered = true; - nsContentUtils::RegisterShutdownObserver(new MediaStreamGraphShutdownObserver()); + if (!gMediaStreamGraphShutdownBlocker) { + + class Blocker : public media::ShutdownBlocker + { + public: + Blocker() + : media::ShutdownBlocker(NS_LITERAL_STRING( + "MediaStreamGraph shutdown: blocking on msg thread")) {} + + NS_IMETHOD + BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override + { + // Distribute the global async shutdown blocker in a ticket. If there + // are zero graphs then shutdown is unblocked when we go out of scope. + RefPtr ticket = + new MediaStreamGraphImpl::ShutdownTicket(gMediaStreamGraphShutdownBlocker.get()); + gMediaStreamGraphShutdownBlocker = nullptr; + + for (auto iter = gGraphs.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->ForceShutDown(ticket); + } + return NS_OK; + } + }; + + gMediaStreamGraphShutdownBlocker = new Blocker(); + nsCOMPtr barrier = MediaStreamGraphImpl::GetShutdownBarrier(); + nsresult rv = barrier-> + AddBlocker(gMediaStreamGraphShutdownBlocker, + NS_LITERAL_STRING(__FILE__), __LINE__, + NS_LITERAL_STRING("MediaStreamGraph shutdown")); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); } CubebUtils::InitPreferredSampleRate(); @@ -2770,7 +2965,7 @@ MediaStreamGraph::DestroyNonRealtimeInstance(MediaStreamGraph* aGraph) // Start the graph, but don't produce anything graph->StartNonRealtimeProcessing(0); } - graph->ForceShutDown(); + graph->ForceShutDown(nullptr); } NS_IMPL_ISUPPORTS(MediaStreamGraphImpl, nsIMemoryReporter) @@ -2893,7 +3088,7 @@ MediaStreamGraph::AddStream(MediaStream* aStream) NS_ADDREF(aStream); MediaStreamGraphImpl* graph = static_cast(this); aStream->SetGraphImpl(graph); - graph->AppendMessage(new CreateMessage(aStream)); + graph->AppendMessage(MakeUnique(aStream)); } class GraphStartedRunnable final : public nsRunnable @@ -2926,7 +3121,7 @@ MediaStreamGraph::NotifyWhenGraphStarted(AudioNodeStream* aStream) : ControlMessage(aStream) { } - virtual void Run() + void Run() override { // This runs on the graph thread, so when this runs, and the current // driver is an AudioCallbackDriver, we know the audio hardware is @@ -2943,14 +3138,14 @@ MediaStreamGraph::NotifyWhenGraphStarted(AudioNodeStream* aStream) NS_DispatchToMainThread(event.forget()); } } - virtual void RunDuringShutdown() + void RunDuringShutdown() override { } }; if (!aStream->IsDestroyed()) { MediaStreamGraphImpl* graphImpl = static_cast(this); - graphImpl->AppendMessage(new GraphStartedNotificationControlMessage(aStream)); + graphImpl->AppendMessage(MakeUnique(aStream)); } } @@ -3049,6 +3244,16 @@ MediaStreamGraphImpl::ApplyAudioContextOperationImpl( SuspendOrResumeStreams(aOperation, aStreams); + bool switching = false; + GraphDriver* nextDriver = nullptr; + { + MonitorAutoLock lock(mMonitor); + switching = CurrentDriver()->Switching(); + if (switching) { + nextDriver = CurrentDriver()->NextDriver(); + } + } + // If we have suspended the last AudioContext, and we don't have other // streams that have audio, this graph will automatically switch to a // SystemCallbackDriver, because it can't find a MediaStream that has an audio @@ -3058,12 +3263,12 @@ MediaStreamGraphImpl::ApplyAudioContextOperationImpl( if (aOperation == AudioContextOperation::Resume) { if (!CurrentDriver()->AsAudioCallbackDriver()) { AudioCallbackDriver* driver; - if (CurrentDriver()->Switching()) { - MOZ_ASSERT(CurrentDriver()->NextDriver()->AsAudioCallbackDriver()); - driver = CurrentDriver()->NextDriver()->AsAudioCallbackDriver(); + if (switching) { + MOZ_ASSERT(nextDriver->AsAudioCallbackDriver()); + driver = nextDriver->AsAudioCallbackDriver(); } else { driver = new AudioCallbackDriver(this); - mMixer.AddCallback(driver); + MonitorAutoLock lock(mMonitor); CurrentDriver()->SwitchAtNextIteration(driver); } driver->EnqueueStreamAndPromiseForOperation(aDestinationStream, @@ -3097,19 +3302,19 @@ MediaStreamGraphImpl::ApplyAudioContextOperationImpl( aOperation); SystemClockDriver* driver; - if (CurrentDriver()->NextDriver()) { - MOZ_ASSERT(!CurrentDriver()->NextDriver()->AsAudioCallbackDriver()); + if (nextDriver) { + MOZ_ASSERT(!nextDriver->AsAudioCallbackDriver()); } else { driver = new SystemClockDriver(this); - mMixer.RemoveCallback(CurrentDriver()->AsAudioCallbackDriver()); + MonitorAutoLock lock(mMonitor); CurrentDriver()->SwitchAtNextIteration(driver); } // We are closing or suspending an AudioContext, but we just got resumed. // Queue the operation on the next driver so that the ordering is // preserved. - } else if (!audioTrackPresent && CurrentDriver()->Switching()) { - MOZ_ASSERT(CurrentDriver()->NextDriver()->AsAudioCallbackDriver()); - CurrentDriver()->NextDriver()->AsAudioCallbackDriver()-> + } else if (!audioTrackPresent && switching) { + MOZ_ASSERT(nextDriver->AsAudioCallbackDriver()); + nextDriver->AsAudioCallbackDriver()-> EnqueueStreamAndPromiseForOperation(aDestinationStream, aPromise, aOperation); } else { @@ -3139,12 +3344,12 @@ MediaStreamGraph::ApplyAudioContextOperation(MediaStream* aDestinationStream, , mPromise(aPromise) { } - virtual void Run() + void Run() override { mStream->GraphImpl()->ApplyAudioContextOperationImpl(mStream, mStreams, mAudioContextOperation, mPromise); } - virtual void RunDuringShutdown() + void RunDuringShutdown() override { MOZ_ASSERT(false, "We should be reviving the graph?"); } @@ -3159,8 +3364,8 @@ MediaStreamGraph::ApplyAudioContextOperation(MediaStream* aDestinationStream, MediaStreamGraphImpl* graphImpl = static_cast(this); graphImpl->AppendMessage( - new AudioContextOperationControlMessage(aDestinationStream, aStreams, - aOperation, aPromise)); + MakeUnique(aDestinationStream, aStreams, + aOperation, aPromise)); } bool diff --git a/dom/media/MediaStreamGraph.h b/dom/media/MediaStreamGraph.h index 0ea27ff2b0..052fe6e58e 100644 --- a/dom/media/MediaStreamGraph.h +++ b/dom/media/MediaStreamGraph.h @@ -183,6 +183,39 @@ public: virtual void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) {} }; +class AudioDataListenerInterface { +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AudioDataListenerInterface() {} + +public: + /* These are for cubeb audio input & output streams: */ + /** + * Output data to speakers, for use as the "far-end" data for echo + * cancellation. This is not guaranteed to be in any particular size + * chunks. + */ + virtual void NotifyOutputData(MediaStreamGraph* aGraph, + AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) = 0; + /** + * Input data from a microphone (or other audio source. This is not + * guaranteed to be in any particular size chunks. + */ + virtual void NotifyInputData(MediaStreamGraph* aGraph, + const AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) = 0; +}; + +class AudioDataListener : public AudioDataListenerInterface { +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AudioDataListener() {} + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioDataListener) +}; + /** * This is a base class for media graph thread listener direct callbacks * from within AppendToTrack(). Note that your regular listener will @@ -698,10 +731,20 @@ public: mNeedsMixing(false) {} - virtual SourceMediaStream* AsSourceStream() override { return this; } + SourceMediaStream* AsSourceStream() override { return this; } // Media graph thread only - virtual void DestroyImpl() override; + + // Users of audio inputs go through the stream so it can track when the + // last stream referencing an input goes away, so it can close the cubeb + // input. Also note: callable on any thread (though it bounces through + // MainThread to set the command if needed). + nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener); + // Note: also implied when Destroy() happens + void CloseAudioInput(); + + void DestroyImpl() override; // Call these on any thread. /** @@ -796,14 +839,14 @@ public: } // Overriding allows us to hold the mMutex lock while changing the track enable status - virtual void + void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override { MutexAutoLock lock(mMutex); MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled); } // Overriding allows us to ensure mMutex is locked while changing the track enable status - virtual void + void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment = nullptr) override { mMutex.AssertCurrentThreadOwns(); @@ -887,6 +930,12 @@ protected: void NotifyDirectConsumers(TrackData *aTrack, MediaSegment *aSegment); + // Only accessed on the MSG thread. Used so to ask the MSGImpl to usecount + // users of a specific input. + // XXX Should really be a CubebUtils::AudioDeviceID, but they aren't + // copyable (opaque pointers) + RefPtr mInputListener; + // This must be acquired *before* MediaStreamGraphImpl's lock, if they are // held together. Mutex mMutex; @@ -1071,7 +1120,7 @@ public: */ void SetAutofinish(bool aAutofinish); - virtual ProcessedMediaStream* AsProcessedStream() override { return this; } + ProcessedMediaStream* AsProcessedStream() override { return this; } friend class MediaStreamGraphImpl; @@ -1089,7 +1138,7 @@ public: { return mInputs.Length(); } - virtual void DestroyImpl() override; + void DestroyImpl() override; /** * This gets called after we've computed the blocking states for all * streams (mBlocked is up to date up to mStateComputedTime). @@ -1117,7 +1166,7 @@ public: // true for echo loops, only for muted cycles. bool InMutedCycle() const { return mCycleMarker; } - virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { size_t amount = MediaStream::SizeOfExcludingThis(aMallocSizeOf); // Not owned: @@ -1126,7 +1175,7 @@ public: return amount; } - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } @@ -1175,6 +1224,12 @@ public: // Idempotent static void DestroyNonRealtimeInstance(MediaStreamGraph* aGraph); + virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener) { + return NS_ERROR_FAILURE; + } + virtual void CloseAudioInput(AudioDataListener *aListener) {} + // Control API. /** * Create a stream that a media decoder (or some other source of @@ -1254,6 +1309,13 @@ public: already_AddRefed ConnectToCaptureStream( uint64_t aWindowId, MediaStream* aMediaStream); + /** + * Data going to the speakers from the GraphDriver's DataCallback + * to notify any listeners (for echo cancellation). + */ + void NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels); + protected: explicit MediaStreamGraph(TrackRate aSampleRate) : mSampleRate(aSampleRate) @@ -1274,6 +1336,12 @@ protected: * at construction. */ TrackRate mSampleRate; + + /** + * Lifetime is controlled by OpenAudioInput/CloseAudioInput. Destroying the listener + * without removing it is an error; callers should assert on that. + */ + nsTArray mAudioInputs; }; } // namespace mozilla diff --git a/dom/media/MediaStreamGraphImpl.h b/dom/media/MediaStreamGraphImpl.h index c5e850ca6b..5076daddcd 100644 --- a/dom/media/MediaStreamGraphImpl.h +++ b/dom/media/MediaStreamGraphImpl.h @@ -8,12 +8,16 @@ #include "MediaStreamGraph.h" +#include "nsDataHashtable.h" + #include "mozilla/Monitor.h" #include "mozilla/TimeStamp.h" #include "nsIMemoryReporter.h" #include "nsIThread.h" #include "nsIRunnable.h" +#include "nsIAsyncShutdown.h" #include "Latency.h" +#include "mozilla/UniquePtr.h" #include "mozilla/WeakPtr.h" #include "GraphDriver.h" #include "AudioMixer.h" @@ -79,7 +83,7 @@ protected: class MessageBlock { public: - nsTArray > mMessages; + nsTArray> mMessages; }; /** @@ -138,14 +142,49 @@ public: * Append a ControlMessage to the message queue. This queue is drained * during RunInStableState; the messages will run on the graph thread. */ - void AppendMessage(ControlMessage* aMessage); + void AppendMessage(UniquePtr aMessage); + + // Shutdown helpers. + + static already_AddRefed + GetShutdownBarrier() + { + nsCOMPtr svc = services::GetAsyncShutdown(); + MOZ_RELEASE_ASSERT(svc); + + nsCOMPtr barrier; + nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier)); + if (!barrier) { + // We are probably in a content process. + rv = svc->GetContentChildShutdown(getter_AddRefs(barrier)); + } + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + MOZ_RELEASE_ASSERT(barrier); + return barrier.forget(); + } + + class ShutdownTicket final + { + public: + explicit ShutdownTicket(nsIAsyncShutdownBlocker* aBlocker) : mBlocker(aBlocker) {} + NS_INLINE_DECL_REFCOUNTING(ShutdownTicket) + private: + ~ShutdownTicket() + { + nsCOMPtr barrier = GetShutdownBarrier(); + barrier->RemoveBlocker(mBlocker); + } + + nsCOMPtr mBlocker; + }; + /** * Make this MediaStreamGraph enter forced-shutdown state. This state * will be noticed by the media graph thread, which will shut down all streams * and other state controlled by the media graph thread. * This is called during application shutdown. */ - void ForceShutDown(); + void ForceShutDown(ShutdownTicket* aShutdownTicket); /** * Shutdown() this MediaStreamGraph's threads and return when they've shut down. */ @@ -257,7 +296,7 @@ public: * Schedules |aMessage| to run after processing, at a time when graph state * can be changed. Graph thread. */ - void RunMessageAfterProcessing(nsAutoPtr aMessage); + void RunMessageAfterProcessing(UniquePtr aMessage); /** * Called when a suspend/resume/close operation has been completed, on the @@ -350,6 +389,13 @@ public: * at the current buffer end point. The StreamBuffer's tracks must be * explicitly set to finished by the caller. */ + void OpenAudioInputImpl(CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener); + virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID, + AudioDataListener *aListener) override; + void CloseAudioInputImpl(AudioDataListener *aListener); + virtual void CloseAudioInput(AudioDataListener *aListener) override; + void FinishStream(MediaStream* aStream); /** * Compute how much stream data we would like to buffer for aStream. @@ -577,6 +623,18 @@ public: */ int32_t mPortCount; + /** + * Devices to use for cubeb input & output, or NULL for no input (void*), + * and boolean to control if we want input/output + */ + bool mInputWanted; + CubebUtils::AudioDeviceID mInputDeviceID; + bool mOutputWanted; + CubebUtils::AudioDeviceID mOutputDeviceID; + // Maps AudioDataListeners to a usecount of streams using the listener + // so we can know when it's no longer in use. + nsDataHashtable, uint32_t> mInputDeviceUsers; + // True if the graph needs another iteration after the current iteration. Atomic mNeedAnotherIteration; // GraphDriver may need a WakeUp() if something changes @@ -676,6 +734,12 @@ public: * True when we need to do a forced shutdown during application shutdown. */ bool mForceShutDown; + + /** + * Drop this reference during shutdown to unblock shutdown. + **/ + RefPtr mForceShutdownTicket; + /** * True when we have posted an event to the main thread to run * RunInStableState() and the event hasn't run yet. @@ -690,7 +754,7 @@ public: * immediately because we want all messages between stable states to be * processed as an atomic batch. */ - nsTArray > mCurrentTaskMessageQueue; + nsTArray> mCurrentTaskMessageQueue; /** * True when RunInStableState has determined that mLifecycleState is > * LIFECYCLE_RUNNING. Since only the main thread can reset mLifecycleState to diff --git a/dom/media/RtspMediaResource.h b/dom/media/RtspMediaResource.h index 04ed36c00f..9207e69b62 100644 --- a/dom/media/RtspMediaResource.h +++ b/dom/media/RtspMediaResource.h @@ -79,7 +79,7 @@ public: // Get the RtspMediaResource pointer if this MediaResource is a // RtspMediaResource. For calling Rtsp specific functions. - virtual RtspMediaResource* GetRtspPointer() override final { + RtspMediaResource* GetRtspPointer() override final { return this; } @@ -94,7 +94,7 @@ public: // Even it is a live stream, as long as it provides valid timestamps, // we tell state machine it's not a live stream. - virtual bool IsRealTime() override { + bool IsRealTime() override { return !mHasTimestamp; } @@ -124,29 +124,29 @@ public: void DisablePlayoutDelay(); // dummy - virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, - uint32_t aCount, uint32_t* aBytes) override{ + nsresult ReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) override{ return NS_ERROR_FAILURE; } // dummy - virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {} + void SetReadMode(MediaCacheStream::ReadMode aMode) override {} // dummy - virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {} + void SetPlaybackRate(uint32_t aBytesPerSecond) override {} // dummy - virtual int64_t Tell() override { return 0; } + int64_t Tell() override { return 0; } // Any thread - virtual void Pin() override {} - virtual void Unpin() override {} + void Pin() override {} + void Unpin() override {} - virtual bool IsSuspendedByCache() override { return mIsSuspend; } + bool IsSuspendedByCache() override { return mIsSuspend; } - virtual bool IsSuspended() override { return false; } - virtual bool IsTransportSeekable() override { return true; } + bool IsSuspended() override { return false; } + bool IsTransportSeekable() override { return true; } // dummy - virtual double GetDownloadRate(bool* aIsReliable) override { *aIsReliable = false; return 0; } + double GetDownloadRate(bool* aIsReliable) override { *aIsReliable = false; return 0; } - virtual int64_t GetLength() override { + int64_t GetLength() override { if (mIsLiveStream) { return -1; } @@ -154,11 +154,11 @@ public: } // dummy - virtual int64_t GetNextCachedData(int64_t aOffset) override { return 0; } + int64_t GetNextCachedData(int64_t aOffset) override { return 0; } // dummy - virtual int64_t GetCachedDataEnd(int64_t aOffset) override { return 0; } + int64_t GetCachedDataEnd(int64_t aOffset) override { return 0; } // dummy - virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override { + bool IsDataCachedToEndOfResource(int64_t aOffset) override { return false; } // dummy @@ -168,29 +168,26 @@ public: // The following methods can be called on main thread only. - virtual nsresult Open(nsIStreamListener** aStreamListener) override; - virtual nsresult Close() override; - virtual void Suspend(bool aCloseImmediately) override; - virtual void Resume() override; - virtual already_AddRefed GetCurrentPrincipal() override; - virtual bool CanClone() override { + nsresult Open(nsIStreamListener** aStreamListener) override; + nsresult Close() override; + void Suspend(bool aCloseImmediately) override; + void Resume() override; + already_AddRefed GetCurrentPrincipal() override; + bool CanClone() override { return false; } - virtual already_AddRefed CloneData(MediaResourceCallback*) - override { + already_AddRefed CloneData(MediaResourceCallback*) override { return nullptr; } // dummy - virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, + nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { return NS_ERROR_FAILURE; } - virtual size_t SizeOfExcludingThis( - MallocSizeOf aMallocSizeOf) const override; + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; - virtual size_t SizeOfIncludingThis( - MallocSizeOf aMallocSizeOf) const override { + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/SharedBuffer.h b/dom/media/SharedBuffer.h index d7a0320fb0..cc710c2ab4 100644 --- a/dom/media/SharedBuffer.h +++ b/dom/media/SharedBuffer.h @@ -67,7 +67,7 @@ public: return p.forget(); } - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/TextTrack.h b/dom/media/TextTrack.h index 6b9795cbc3..3ba86ab4bc 100644 --- a/dom/media/TextTrack.h +++ b/dom/media/TextTrack.h @@ -59,7 +59,7 @@ public: void SetDefaultSettings(); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; TextTrackKind Kind() const { diff --git a/dom/media/TextTrackCue.h b/dom/media/TextTrackCue.h index 7d5d0f0d0b..0f5ed06764 100644 --- a/dom/media/TextTrackCue.h +++ b/dom/media/TextTrackCue.h @@ -50,7 +50,7 @@ public: const nsAString& aText, HTMLTrackElement* aTrackElement, ErrorResult& aRv); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; TextTrack* GetTrack() const { diff --git a/dom/media/TextTrackCueList.h b/dom/media/TextTrackCueList.h index 3c30d84cf1..0fe435bb98 100644 --- a/dom/media/TextTrackCueList.h +++ b/dom/media/TextTrackCueList.h @@ -28,7 +28,7 @@ public: // TextTrackCueList WebIDL explicit TextTrackCueList(nsISupports* aParent); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; nsISupports* GetParentObject() const { diff --git a/dom/media/TextTrackList.h b/dom/media/TextTrackList.h index 5d44259879..8fda45ecae 100644 --- a/dom/media/TextTrackList.h +++ b/dom/media/TextTrackList.h @@ -29,7 +29,7 @@ public: explicit TextTrackList(nsPIDOMWindow* aOwnerWindow); TextTrackList(nsPIDOMWindow* aOwnerWindow, TextTrackManager* aTextTrackManager); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; uint32_t Length() const { diff --git a/dom/media/TextTrackRegion.h b/dom/media/TextTrackRegion.h index c6f5c7cce9..a4e6069f76 100644 --- a/dom/media/TextTrackRegion.h +++ b/dom/media/TextTrackRegion.h @@ -35,7 +35,7 @@ public: Preferences::GetBool("media.webvtt.regions.enabled"); } - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; nsISupports* GetParentObject() const { diff --git a/dom/media/TrackUnionStream.h b/dom/media/TrackUnionStream.h index 009a3fac1e..d7fb9efa32 100644 --- a/dom/media/TrackUnionStream.h +++ b/dom/media/TrackUnionStream.h @@ -18,8 +18,8 @@ class TrackUnionStream : public ProcessedMediaStream { public: explicit TrackUnionStream(DOMMediaStream* aWrapper); - virtual void RemoveInput(MediaInputPort* aPort) override; - virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override; + void RemoveInput(MediaInputPort* aPort) override; + void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override; protected: // Only non-ended tracks are allowed to persist in this map. diff --git a/dom/media/VideoSegment.h b/dom/media/VideoSegment.h index a37fb3eda5..9f90bd9c49 100644 --- a/dom/media/VideoSegment.h +++ b/dom/media/VideoSegment.h @@ -129,7 +129,7 @@ public: return &c->mFrame; } // Override default impl - virtual void ReplaceWithDisabled() override { + void ReplaceWithDisabled() override { for (ChunkIterator i(*this); !i.IsEnded(); i.Next()) { VideoChunk& chunk = *i; @@ -140,7 +140,7 @@ public: // Segment-generic methods not in MediaSegmentBase static Type StaticType() { return VIDEO; } - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/VideoStreamTrack.h b/dom/media/VideoStreamTrack.h index 89cb677a35..5d76bb6b2b 100644 --- a/dom/media/VideoStreamTrack.h +++ b/dom/media/VideoStreamTrack.h @@ -17,12 +17,12 @@ public: VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel) : MediaStreamTrack(aStream, aTrackID, aLabel) {} - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - virtual VideoStreamTrack* AsVideoStreamTrack() override { return this; } + VideoStreamTrack* AsVideoStreamTrack() override { return this; } // WebIDL - virtual void GetKind(nsAString& aKind) override { aKind.AssignLiteral("video"); } + void GetKind(nsAString& aKind) override { aKind.AssignLiteral("video"); } }; } // namespace dom diff --git a/dom/media/VideoTrack.h b/dom/media/VideoTrack.h index 933c8a8f7c..94f5b362f1 100644 --- a/dom/media/VideoTrack.h +++ b/dom/media/VideoTrack.h @@ -22,9 +22,9 @@ public: const nsAString& aLabel, const nsAString& aLanguage); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - virtual VideoTrack* AsVideoTrack() override + VideoTrack* AsVideoTrack() override { return this; } @@ -34,7 +34,7 @@ public: // default. If multiple video tracks are selected by its media resource at // fetching phase, then the first enabled video track is set selected. // aFlags contains FIRE_NO_EVENTS because no events are fired in such cases. - virtual void SetEnabledInternal(bool aEnabled, int aFlags) override; + void SetEnabledInternal(bool aEnabled, int aFlags) override; // WebIDL bool Selected() const diff --git a/dom/media/VideoTrackList.h b/dom/media/VideoTrackList.h index 38f95cc9cd..971ba7d35b 100644 --- a/dom/media/VideoTrackList.h +++ b/dom/media/VideoTrackList.h @@ -23,11 +23,11 @@ public: , mSelectedIndex(-1) {} - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; VideoTrack* operator[](uint32_t aIndex); - virtual void EmptyTracks() override; + void EmptyTracks() override; // WebIDL int32_t SelectedIndex() const @@ -42,7 +42,7 @@ public: friend class VideoTrack; protected: - virtual VideoTrackList* AsVideoTrackList() override { return this; } + VideoTrackList* AsVideoTrackList() override { return this; } private: int32_t mSelectedIndex; diff --git a/dom/media/encoder/MediaEncoder.h b/dom/media/encoder/MediaEncoder.h index 2922a3ad86..6e632250f4 100644 --- a/dom/media/encoder/MediaEncoder.h +++ b/dom/media/encoder/MediaEncoder.h @@ -82,18 +82,18 @@ public : * Notified by the control loop of MediaStreamGraph; aQueueMedia is the raw * track data in form of MediaSegment. */ - virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, - StreamTime aTrackOffset, - uint32_t aTrackEvents, - const MediaSegment& aQueuedMedia, - MediaStream* aInputStream, - TrackID aInputTrackID) override; + void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, + StreamTime aTrackOffset, + uint32_t aTrackEvents, + const MediaSegment& aQueuedMedia, + MediaStream* aInputStream, + TrackID aInputTrackID) override; /** * Notified the stream is being removed. */ - virtual void NotifyEvent(MediaStreamGraph* aGraph, - MediaStreamListener::MediaStreamGraphEvent event) override; + void NotifyEvent(MediaStreamGraph* aGraph, + MediaStreamListener::MediaStreamGraphEvent event) override; /** * Creates an encoder with a given MIME type. Returns null if we are unable diff --git a/dom/media/encoder/TrackEncoder.h b/dom/media/encoder/TrackEncoder.h index ac01a8ce61..2c25ceb90e 100644 --- a/dom/media/encoder/TrackEncoder.h +++ b/dom/media/encoder/TrackEncoder.h @@ -146,10 +146,10 @@ public: , mAudioBitrate(0) {} - virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, - StreamTime aTrackOffset, - uint32_t aTrackEvents, - const MediaSegment& aQueuedMedia) override; + void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, + StreamTime aTrackOffset, + uint32_t aTrackEvents, + const MediaSegment& aQueuedMedia) override; template static @@ -194,7 +194,7 @@ public: */ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - virtual void SetBitrate(const uint32_t aBitrate) override + void SetBitrate(const uint32_t aBitrate) override { mAudioBitrate = aBitrate; } @@ -227,7 +227,7 @@ protected: * Notifies the audio encoder that we have reached the end of source stream, * and wakes up mReentrantMonitor if encoder is waiting for more track data. */ - virtual void NotifyEndOfStream() override; + void NotifyEndOfStream() override; /** * The number of channels are used for processing PCM data in the audio encoder. @@ -268,16 +268,16 @@ public: * Notified by the same callback of MediaEncoder when it has received a track * change from MediaStreamGraph. Called on the MediaStreamGraph thread. */ - virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, - StreamTime aTrackOffset, - uint32_t aTrackEvents, - const MediaSegment& aQueuedMedia) override; + void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, + StreamTime aTrackOffset, + uint32_t aTrackEvents, + const MediaSegment& aQueuedMedia) override; /** * Measure size of mRawSegment */ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - virtual void SetBitrate(const uint32_t aBitrate) override + void SetBitrate(const uint32_t aBitrate) override { mVideoBitrate = aBitrate; } @@ -303,7 +303,7 @@ protected: * and wakes up mReentrantMonitor if encoder is waiting for more track data. * Called on the MediaStreamGraph thread. */ - virtual void NotifyEndOfStream() override; + void NotifyEndOfStream() override; /** * The width of source video frame, ceiled if the source width is odd. diff --git a/dom/media/gtest/MockMediaDecoderOwner.h b/dom/media/gtest/MockMediaDecoderOwner.h index 148dfe879e..fafd088dfe 100644 --- a/dom/media/gtest/MockMediaDecoderOwner.h +++ b/dom/media/gtest/MockMediaDecoderOwner.h @@ -13,40 +13,40 @@ namespace mozilla class MockMediaDecoderOwner : public MediaDecoderOwner { public: - virtual nsresult DispatchAsyncEvent(const nsAString& aName) override + nsresult DispatchAsyncEvent(const nsAString& aName) override { return NS_OK; } - virtual void FireTimeUpdate(bool aPeriodic) override {} - virtual bool GetPaused() override { return false; } - virtual void MetadataLoaded(const MediaInfo* aInfo, - nsAutoPtr aTags) override + void FireTimeUpdate(bool aPeriodic) override {} + bool GetPaused() override { return false; } + void MetadataLoaded(const MediaInfo* aInfo, + nsAutoPtr aTags) override { } - virtual void NetworkError() override {} - virtual void DecodeError() override {} - virtual void LoadAborted() override {} - virtual void PlaybackEnded() override {} - virtual void SeekStarted() override {} - virtual void SeekCompleted() override {} - virtual void DownloadProgressed() override {} - virtual void UpdateReadyState() override {} - virtual void FirstFrameLoaded() override {} + void NetworkError() override {} + void DecodeError() override {} + void LoadAborted() override {} + void PlaybackEnded() override {} + void SeekStarted() override {} + void SeekCompleted() override {} + void DownloadProgressed() override {} + void UpdateReadyState() override {} + void FirstFrameLoaded() override {} #ifdef MOZ_EME - virtual void DispatchEncrypted(const nsTArray& aInitData, - const nsAString& aInitDataType) override {} + void DispatchEncrypted(const nsTArray& aInitData, + const nsAString& aInitDataType) override {} #endif // MOZ_EME - virtual bool IsActive() const override { return true; } - virtual bool IsHidden() const override { return false; } - virtual void DownloadSuspended() override {} - virtual void DownloadResumed(bool aForceNetworkLoading) override {} - virtual void NotifySuspendedByCache(bool aIsSuspended) override {} - virtual void NotifyDecoderPrincipalChanged() override {} - virtual VideoFrameContainer* GetVideoFrameContainer() override + bool IsActive() const override { return true; } + bool IsHidden() const override { return false; } + void DownloadSuspended() override {} + void DownloadResumed(bool aForceNetworkLoading) override {} + void NotifySuspendedByCache(bool aIsSuspended) override {} + void NotifyDecoderPrincipalChanged() override {} + VideoFrameContainer* GetVideoFrameContainer() override { return nullptr; } - virtual void ResetConnectionState() override {} + void ResetConnectionState() override {} }; } diff --git a/dom/media/gtest/MockMediaResource.h b/dom/media/gtest/MockMediaResource.h index 331e32640f..65048a1e2f 100644 --- a/dom/media/gtest/MockMediaResource.h +++ b/dom/media/gtest/MockMediaResource.h @@ -16,39 +16,39 @@ class MockMediaResource : public MediaResource { public: explicit MockMediaResource(const char* aFileName); - virtual nsIURI* URI() const override { return nullptr; } - virtual nsresult Close() override { return NS_OK; } - virtual void Suspend(bool aCloseImmediately) override {} - virtual void Resume() override {} - virtual already_AddRefed GetCurrentPrincipal() override + nsIURI* URI() const override { return nullptr; } + nsresult Close() override { return NS_OK; } + void Suspend(bool aCloseImmediately) override {} + void Resume() override {} + already_AddRefed GetCurrentPrincipal() override { return nullptr; } - virtual bool CanClone() override { return false; } - virtual already_AddRefed CloneData(MediaResourceCallback*) + bool CanClone() override { return false; } + already_AddRefed CloneData(MediaResourceCallback*) override { return nullptr; } - virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {} - virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {} - virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, - uint32_t* aBytes) override; - virtual int64_t Tell() override { return 0; } - virtual void Pin() override {} - virtual void Unpin() override {} - virtual double GetDownloadRate(bool* aIsReliable) override { return 0; } - virtual int64_t GetLength() override; - virtual int64_t GetNextCachedData(int64_t aOffset) override; - virtual int64_t GetCachedDataEnd(int64_t aOffset) override; - virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override + void SetReadMode(MediaCacheStream::ReadMode aMode) override {} + void SetPlaybackRate(uint32_t aBytesPerSecond) override {} + nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, + uint32_t* aBytes) override; + int64_t Tell() override { return 0; } + void Pin() override {} + void Unpin() override {} + double GetDownloadRate(bool* aIsReliable) override { return 0; } + int64_t GetLength() override; + int64_t GetNextCachedData(int64_t aOffset) override; + int64_t GetCachedDataEnd(int64_t aOffset) override; + bool IsDataCachedToEndOfResource(int64_t aOffset) override { return false; } - virtual bool IsSuspendedByCache() override { return false; } - virtual bool IsSuspended() override { return false; } - virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, - uint32_t aCount) override + bool IsSuspendedByCache() override { return false; } + bool IsSuspended() override { return false; } + nsresult ReadFromCache(char* aBuffer, int64_t aOffset, + uint32_t aCount) override { uint32_t bytesRead = 0; nsresult rv = ReadAt(aOffset, aBuffer, aCount, &bytesRead); @@ -56,11 +56,10 @@ public: return bytesRead == aCount ? NS_OK : NS_ERROR_FAILURE; } - virtual bool IsTransportSeekable() override { return true; } - virtual nsresult Open(nsIStreamListener** aStreamListener) override; - virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) - override; - virtual const nsCString& GetContentType() const override + bool IsTransportSeekable() override { return true; } + nsresult Open(nsIStreamListener** aStreamListener) override; + nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override; + const nsCString& GetContentType() const override { return mContentType; } diff --git a/dom/media/gtest/TestAudioCompactor.cpp b/dom/media/gtest/TestAudioCompactor.cpp index 5b25339f33..9a28254b35 100644 --- a/dom/media/gtest/TestAudioCompactor.cpp +++ b/dom/media/gtest/TestAudioCompactor.cpp @@ -18,7 +18,7 @@ public: MemoryFunctor() : mSize(0) {} MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); - virtual void* operator()(void* aObject) { + void* operator()(void* aObject) override { const AudioData* audioData = static_cast(aObject); mSize += audioData->SizeOfIncludingThis(MallocSizeOf); return nullptr; diff --git a/dom/media/gtest/TestGMPCrossOrigin.cpp b/dom/media/gtest/TestGMPCrossOrigin.cpp index f82a07c5a7..8fc36728eb 100644 --- a/dom/media/gtest/TestGMPCrossOrigin.cpp +++ b/dom/media/gtest/TestGMPCrossOrigin.cpp @@ -53,7 +53,7 @@ template class RunTestGMPCrossOrigin : public Base { public: - virtual void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) + void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override { EXPECT_TRUE(aGMP); @@ -170,7 +170,7 @@ private: mShouldBeEqual(aShouldBeEqual) { } - virtual void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) + void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override { EXPECT_TRUE(aGMP); if (aGMP) { @@ -595,7 +595,7 @@ class GMPStorageTest : public GMPDecryptorProxyCallback { } - virtual void Done(GMPDecryptorProxy* aDecryptor) override + void Done(GMPDecryptorProxy* aDecryptor) override { mRunner->mDecryptor = aDecryptor; EXPECT_TRUE(!!mRunner->mDecryptor); @@ -1312,9 +1312,9 @@ class GMPStorageTest : public GMPDecryptorProxyCallback NS_DispatchToMainThread(NS_NewRunnableMethod(this, &GMPStorageTest::Dummy)); } - virtual void SessionMessage(const nsCString& aSessionId, - GMPSessionMessageType aMessageType, - const nsTArray& aMessage) override + void SessionMessage(const nsCString& aSessionId, + GMPSessionMessageType aMessageType, + const nsTArray& aMessage) override { MonitorAutoLock mon(mMonitor); @@ -1331,29 +1331,29 @@ class GMPStorageTest : public GMPDecryptorProxyCallback } } - virtual void SetSessionId(uint32_t aCreateSessionToken, - const nsCString& aSessionId) override { } - virtual void ResolveLoadSessionPromise(uint32_t aPromiseId, - bool aSuccess) override {} - virtual void ResolvePromise(uint32_t aPromiseId) override {} - virtual void RejectPromise(uint32_t aPromiseId, - nsresult aException, - const nsCString& aSessionId) override { } - virtual void ExpirationChange(const nsCString& aSessionId, - GMPTimestamp aExpiryTime) override {} - virtual void SessionClosed(const nsCString& aSessionId) override {} - virtual void SessionError(const nsCString& aSessionId, - nsresult aException, - uint32_t aSystemCode, - const nsCString& aMessage) override {} - virtual void KeyStatusChanged(const nsCString& aSessionId, - const nsTArray& aKeyId, - GMPMediaKeyStatus aStatus) override { } - virtual void SetCaps(uint64_t aCaps) override {} - virtual void Decrypted(uint32_t aId, - GMPErr aResult, - const nsTArray& aDecryptedData) override { } - virtual void Terminated() override { + void SetSessionId(uint32_t aCreateSessionToken, + const nsCString& aSessionId) override { } + void ResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess) override {} + void ResolvePromise(uint32_t aPromiseId) override {} + void RejectPromise(uint32_t aPromiseId, + nsresult aException, + const nsCString& aSessionId) override { } + void ExpirationChange(const nsCString& aSessionId, + GMPTimestamp aExpiryTime) override {} + void SessionClosed(const nsCString& aSessionId) override {} + void SessionError(const nsCString& aSessionId, + nsresult aException, + uint32_t aSystemCode, + const nsCString& aMessage) override {} + void KeyStatusChanged(const nsCString& aSessionId, + const nsTArray& aKeyId, + GMPMediaKeyStatus aStatus) override { } + void SetCaps(uint64_t aCaps) override {} + void Decrypted(uint32_t aId, + GMPErr aResult, + const nsTArray& aDecryptedData) override { } + void Terminated() override { if (mDecryptor) { mDecryptor->Close(); mDecryptor = nullptr; diff --git a/dom/media/imagecapture/CaptureTask.h b/dom/media/imagecapture/CaptureTask.h index d8957aed83..396ff53296 100644 --- a/dom/media/imagecapture/CaptureTask.h +++ b/dom/media/imagecapture/CaptureTask.h @@ -32,18 +32,18 @@ class CaptureTask : public MediaStreamListener, { public: // MediaStreamListener methods. - virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, - StreamTime aTrackOffset, - uint32_t aTrackEvents, - const MediaSegment& aQueuedMedia, - MediaStream* aInputStream, - TrackID aInputTrackID) override; + void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, + StreamTime aTrackOffset, + uint32_t aTrackEvents, + const MediaSegment& aQueuedMedia, + MediaStream* aInputStream, + TrackID aInputTrackID) override; - virtual void NotifyEvent(MediaStreamGraph* aGraph, - MediaStreamGraphEvent aEvent) override; + void NotifyEvent(MediaStreamGraph* aGraph, + MediaStreamGraphEvent aEvent) override; // DOMMediaStream::PrincipalChangeObserver method. - virtual void PrincipalChanged(DOMMediaStream* aMediaStream) override; + void PrincipalChanged(DOMMediaStream* aMediaStream) override; // CaptureTask methods. diff --git a/dom/media/imagecapture/ImageCapture.h b/dom/media/imagecapture/ImageCapture.h index fc8f5385ac..246b1560ee 100644 --- a/dom/media/imagecapture/ImageCapture.h +++ b/dom/media/imagecapture/ImageCapture.h @@ -53,7 +53,7 @@ public: VideoStreamTrack* GetVideoStreamTrack() const; // nsWrapperCache member - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override { return ImageCaptureBinding::Wrap(aCx, this, aGivenProto); } diff --git a/dom/media/mediasink/AudioSink.h b/dom/media/mediasink/AudioSink.h index b4f9175911..4f124d31fb 100644 --- a/dom/media/mediasink/AudioSink.h +++ b/dom/media/mediasink/AudioSink.h @@ -10,6 +10,8 @@ #include "mozilla/RefPtr.h" #include "nsISupportsImpl.h" +#include "MediaSink.h" + namespace mozilla { class MediaData; @@ -28,9 +30,11 @@ public: : mAudioQueue(aAudioQueue) {} + typedef MediaSink::PlaybackParams PlaybackParams; + // Return a promise which will be resolved when AudioSink finishes playing, // or rejected if any error. - virtual RefPtr Init() = 0; + virtual RefPtr Init(const PlaybackParams& aParams) = 0; virtual int64_t GetEndTime() const = 0; virtual int64_t GetPosition() = 0; diff --git a/dom/media/mediasink/AudioSinkWrapper.cpp b/dom/media/mediasink/AudioSinkWrapper.cpp index e41d07b1b7..dd5643d0ab 100644 --- a/dom/media/mediasink/AudioSinkWrapper.cpp +++ b/dom/media/mediasink/AudioSinkWrapper.cpp @@ -188,8 +188,7 @@ AudioSinkWrapper::Start(int64_t aStartTime, const MediaInfo& aInfo) if (aInfo.HasAudio()) { mAudioSink = mCreator->Create(); - mEndPromise = mAudioSink->Init(); - SetPlaybackParams(mParams); + mEndPromise = mAudioSink->Init(mParams); mAudioSinkPromise.Begin(mEndPromise->Then( mOwnerThread.get(), __func__, this, diff --git a/dom/media/mediasink/DecodedAudioDataSink.cpp b/dom/media/mediasink/DecodedAudioDataSink.cpp index 8a8eaaf622..e2e87dafa7 100644 --- a/dom/media/mediasink/DecodedAudioDataSink.cpp +++ b/dom/media/mediasink/DecodedAudioDataSink.cpp @@ -4,7 +4,6 @@ * 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 "AudioStream.h" #include "MediaQueue.h" #include "DecodedAudioDataSink.h" #include "VideoUtils.h" @@ -32,15 +31,11 @@ DecodedAudioDataSink::DecodedAudioDataSink(MediaQueue& aAudioQueue, const AudioInfo& aInfo, dom::AudioChannel aChannel) : AudioSink(aAudioQueue) - , mMonitor("DecodedAudioDataSink::mMonitor") - , mState(AUDIOSINK_STATE_INIT) - , mAudioLoopScheduled(false) , mStartTime(aStartTime) , mWritten(0) , mLastGoodPosition(0) , mInfo(aInfo) , mChannel(aChannel) - , mStopAudioThread(false) , mPlaying(true) { } @@ -49,97 +44,20 @@ DecodedAudioDataSink::~DecodedAudioDataSink() { } -void -DecodedAudioDataSink::SetState(State aState) -{ - AssertOnAudioThread(); - mPendingState = Some(aState); -} - -void -DecodedAudioDataSink::DispatchTask(already_AddRefed&& event) -{ - DebugOnly rv = mThread->Dispatch(Move(event), NS_DISPATCH_NORMAL); - // There isn't much we can do if Dispatch() fails. - // Just assert it to keep things simple. - MOZ_ASSERT(NS_SUCCEEDED(rv)); -} - -void -DecodedAudioDataSink::OnAudioQueueEvent() -{ - AssertOnAudioThread(); - if (!mAudioLoopScheduled) { - AudioLoop(); - } -} - -void -DecodedAudioDataSink::ConnectListener() -{ - AssertOnAudioThread(); - mPushListener = AudioQueue().PushEvent().Connect( - mThread, this, &DecodedAudioDataSink::OnAudioQueueEvent); - mFinishListener = AudioQueue().FinishEvent().Connect( - mThread, this, &DecodedAudioDataSink::OnAudioQueueEvent); -} - -void -DecodedAudioDataSink::DisconnectListener() -{ - AssertOnAudioThread(); - mPushListener.Disconnect(); - mFinishListener.Disconnect(); -} - -void -DecodedAudioDataSink::ScheduleNextLoop() -{ - AssertOnAudioThread(); - if (mAudioLoopScheduled) { - return; - } - mAudioLoopScheduled = true; - nsCOMPtr r = NS_NewRunnableMethod(this, &DecodedAudioDataSink::AudioLoop); - DispatchTask(r.forget()); -} - -void -DecodedAudioDataSink::ScheduleNextLoopCrossThread() -{ - AssertNotOnAudioThread(); - RefPtr self = this; - nsCOMPtr r = NS_NewRunnableFunction([self] () { - // Do nothing if there is already a pending task waiting for its turn. - if (!self->mAudioLoopScheduled) { - self->AudioLoop(); - } - }); - DispatchTask(r.forget()); -} - RefPtr -DecodedAudioDataSink::Init() +DecodedAudioDataSink::Init(const PlaybackParams& aParams) { RefPtr p = mEndPromise.Ensure(__func__); - nsresult rv = NS_NewNamedThread("Media Audio", - getter_AddRefs(mThread), - nullptr, - SharedThreadPool::kStackSize); + nsresult rv = InitializeAudioStream(aParams); if (NS_FAILED(rv)) { mEndPromise.Reject(rv, __func__); - return p; } - - ScheduleNextLoopCrossThread(); return p; } int64_t DecodedAudioDataSink::GetPosition() { - ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - int64_t pos; if (mAudioStream && (pos = mAudioStream->GetPosition()) >= 0) { @@ -157,7 +75,6 @@ DecodedAudioDataSink::GetPosition() bool DecodedAudioDataSink::HasUnplayedFrames() { - ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); // Experimentation suggests that GetPositionInFrames() is zero-indexed, // so we need to add 1 here before comparing it to mWritten. return mAudioStream && mAudioStream->GetPositionInFrames() + 1 < mWritten; @@ -166,350 +83,74 @@ DecodedAudioDataSink::HasUnplayedFrames() void DecodedAudioDataSink::Shutdown() { - { - ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - if (mAudioStream) { - mAudioStream->Cancel(); - } - } - RefPtr self = this; - nsCOMPtr r = NS_NewRunnableFunction([=] () { - self->mStopAudioThread = true; - if (!self->mAudioLoopScheduled) { - self->AudioLoop(); - } - }); - DispatchTask(r.forget()); - - mThread->Shutdown(); - mThread = nullptr; if (mAudioStream) { mAudioStream->Shutdown(); mAudioStream = nullptr; } - - // Should've reached the final state after shutdown. - MOZ_ASSERT(mState == AUDIOSINK_STATE_SHUTDOWN || - mState == AUDIOSINK_STATE_ERROR); - // Should have no pending state change. - MOZ_ASSERT(mPendingState.isNothing()); + mEndPromise.ResolveIfExists(true, __func__); } void DecodedAudioDataSink::SetVolume(double aVolume) { - AssertNotOnAudioThread(); - RefPtr self = this; - nsCOMPtr r = NS_NewRunnableFunction([=] () { - if (self->mState == AUDIOSINK_STATE_PLAYING) { - self->mAudioStream->SetVolume(aVolume); - } - }); - DispatchTask(r.forget()); + if (mAudioStream) { + mAudioStream->SetVolume(aVolume); + } } void DecodedAudioDataSink::SetPlaybackRate(double aPlaybackRate) { - AssertNotOnAudioThread(); MOZ_ASSERT(aPlaybackRate != 0, "Don't set the playbackRate to 0 on AudioStream"); - RefPtr self = this; - nsCOMPtr r = NS_NewRunnableFunction([=] () { - if (self->mState == AUDIOSINK_STATE_PLAYING) { - self->mAudioStream->SetPlaybackRate(aPlaybackRate); - } - }); - DispatchTask(r.forget()); + if (mAudioStream) { + mAudioStream->SetPlaybackRate(aPlaybackRate); + } } void DecodedAudioDataSink::SetPreservesPitch(bool aPreservesPitch) { - AssertNotOnAudioThread(); - RefPtr self = this; - nsCOMPtr r = NS_NewRunnableFunction([=] () { - if (self->mState == AUDIOSINK_STATE_PLAYING) { - self->mAudioStream->SetPreservesPitch(aPreservesPitch); - } - }); - DispatchTask(r.forget()); + if (mAudioStream) { + mAudioStream->SetPreservesPitch(aPreservesPitch); + } } void DecodedAudioDataSink::SetPlaying(bool aPlaying) { - AssertNotOnAudioThread(); - RefPtr self = this; - nsCOMPtr r = NS_NewRunnableFunction([=] () { - if (self->mState != AUDIOSINK_STATE_PLAYING || - self->mPlaying == aPlaying) { - return; - } - self->mPlaying = aPlaying; - // pause/resume AudioStream as necessary. - if (!aPlaying && !self->mAudioStream->IsPaused()) { - self->mAudioStream->Pause(); - } else if (aPlaying && self->mAudioStream->IsPaused()) { - self->mAudioStream->Resume(); - } - // Wake up the audio loop to play next sample. - if (aPlaying && !self->mAudioLoopScheduled) { - self->AudioLoop(); - } - }); - DispatchTask(r.forget()); + if (!mAudioStream || mPlaying == aPlaying) { + return; + } + // pause/resume AudioStream as necessary. + if (!aPlaying && !mAudioStream->IsPaused()) { + mAudioStream->Pause(); + } else if (aPlaying && mAudioStream->IsPaused()) { + mAudioStream->Resume(); + } + mPlaying = aPlaying; } nsresult -DecodedAudioDataSink::InitializeAudioStream() +DecodedAudioDataSink::InitializeAudioStream(const PlaybackParams& aParams) { - // AudioStream initialization can block for extended periods in unusual - // circumstances, so we take care to drop the decoder monitor while - // initializing. - RefPtr audioStream(new AudioStream()); - nsresult rv = audioStream->Init(mInfo.mChannels, mInfo.mRate, mChannel); + mAudioStream = new AudioStream(*this); + nsresult rv = mAudioStream->Init(mInfo.mChannels, mInfo.mRate, mChannel); if (NS_FAILED(rv)) { - audioStream->Shutdown(); + mAudioStream->Shutdown(); + mAudioStream = nullptr; return rv; } - ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - mAudioStream = audioStream; + // Set playback params before calling Start() so they can take effect + // as soon as the 1st DataCallback of the AudioStream fires. + mAudioStream->SetVolume(aParams.mVolume); + mAudioStream->SetPlaybackRate(aParams.mPlaybackRate); + mAudioStream->SetPreservesPitch(aParams.mPreservesPitch); + mAudioStream->Start(); return NS_OK; } -void -DecodedAudioDataSink::Drain() -{ - AssertOnAudioThread(); - MOZ_ASSERT(mPlaying && !mAudioStream->IsPaused()); - // If the media was too short to trigger the start of the audio stream, - // start it now. - mAudioStream->Start(); - mAudioStream->Drain(); -} - -void -DecodedAudioDataSink::Cleanup() -{ - AssertOnAudioThread(); - mEndPromise.Resolve(true, __func__); - // Since the promise if resolved asynchronously, we don't shutdown - // AudioStream here so MDSM::ResyncAudioClock can get the correct - // audio position. -} - -bool -DecodedAudioDataSink::ExpectMoreAudioData() -{ - return AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished(); -} - -bool -DecodedAudioDataSink::WaitingForAudioToPlay() -{ - AssertOnAudioThread(); - // Return true if we're not playing, and we're not shutting down, or we're - // playing and we've got no audio to play. - if (!mStopAudioThread && (!mPlaying || ExpectMoreAudioData())) { - return true; - } - return false; -} - -bool -DecodedAudioDataSink::IsPlaybackContinuing() -{ - AssertOnAudioThread(); - // If we're shutting down, captured, or at EOS, break out and exit the audio - // thread. - if (mStopAudioThread || AudioQueue().AtEndOfStream()) { - return false; - } - - return true; -} - -void -DecodedAudioDataSink::AudioLoop() -{ - AssertOnAudioThread(); - mAudioLoopScheduled = false; - - switch (mState) { - case AUDIOSINK_STATE_INIT: { - SINK_LOG("AudioLoop started"); - nsresult rv = InitializeAudioStream(); - if (NS_FAILED(rv)) { - NS_WARNING("Initializing AudioStream failed."); - mEndPromise.Reject(rv, __func__); - SetState(AUDIOSINK_STATE_ERROR); - break; - } - SetState(AUDIOSINK_STATE_PLAYING); - ConnectListener(); - break; - } - - case AUDIOSINK_STATE_PLAYING: { - if (WaitingForAudioToPlay()) { - // OnAudioQueueEvent() will schedule next loop. - break; - } - if (!IsPlaybackContinuing()) { - SetState(AUDIOSINK_STATE_COMPLETE); - break; - } - if (!PlayAudio()) { - SetState(AUDIOSINK_STATE_COMPLETE); - break; - } - // Schedule next loop to play next sample. - ScheduleNextLoop(); - break; - } - - case AUDIOSINK_STATE_COMPLETE: { - DisconnectListener(); - FinishAudioLoop(); - SetState(AUDIOSINK_STATE_SHUTDOWN); - break; - } - - case AUDIOSINK_STATE_SHUTDOWN: - break; - - case AUDIOSINK_STATE_ERROR: - break; - } // end of switch - - // We want mState to stay stable during AudioLoop to keep things simple. - // Therefore, we only do state transition at the end of AudioLoop. - if (mPendingState.isSome()) { - MOZ_ASSERT(mState != mPendingState.ref()); - SINK_LOG("change mState, %d -> %d", mState, mPendingState.ref()); - mState = mPendingState.ref(); - mPendingState.reset(); - // Schedule next loop when state changes. - ScheduleNextLoop(); - } -} - -bool -DecodedAudioDataSink::PlayAudio() -{ - // See if there's a gap in the audio. If there is, push silence into the - // audio hardware, so we can play across the gap. - // Calculate the timestamp of the next chunk of audio in numbers of - // samples. - NS_ASSERTION(AudioQueue().GetSize() > 0, "Should have data to play"); - CheckedInt64 sampleTime = UsecsToFrames(AudioQueue().PeekFront()->mTime, mInfo.mRate); - - // Calculate the number of frames that have been pushed onto the audio hardware. - CheckedInt64 playedFrames = UsecsToFrames(mStartTime, mInfo.mRate) + - static_cast(mWritten); - - CheckedInt64 missingFrames = sampleTime - playedFrames; - if (!missingFrames.isValid() || !sampleTime.isValid()) { - NS_WARNING("Int overflow adding in AudioLoop"); - return false; - } - - if (missingFrames.value() > AUDIO_FUZZ_FRAMES) { - // The next audio chunk begins some time after the end of the last chunk - // we pushed to the audio hardware. We must push silence into the audio - // hardware so that the next audio chunk begins playback at the correct - // time. - missingFrames = std::min(UINT32_MAX, missingFrames.value()); - mWritten += PlaySilence(static_cast(missingFrames.value())); - } else { - mWritten += PlayFromAudioQueue(); - } - - return true; -} - -void -DecodedAudioDataSink::FinishAudioLoop() -{ - AssertOnAudioThread(); - MOZ_ASSERT(mStopAudioThread || AudioQueue().AtEndOfStream()); - if (!mStopAudioThread && mPlaying) { - Drain(); - } - SINK_LOG("AudioLoop complete"); - Cleanup(); - SINK_LOG("AudioLoop exit"); -} - -uint32_t -DecodedAudioDataSink::PlaySilence(uint32_t aFrames) -{ - // Maximum number of bytes we'll allocate and write at once to the audio - // hardware when the audio stream contains missing frames and we're - // writing silence in order to fill the gap. We limit our silence-writes - // to 32KB in order to avoid allocating an impossibly large chunk of - // memory if we encounter a large chunk of silence. - const uint32_t SILENCE_BYTES_CHUNK = 32 * 1024; - - AssertOnAudioThread(); - NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused"); - uint32_t maxFrames = SILENCE_BYTES_CHUNK / mInfo.mChannels / sizeof(AudioDataValue); - uint32_t frames = std::min(aFrames, maxFrames); - SINK_LOG_V("playing %u frames of silence", aFrames); - WriteSilence(frames); - return frames; -} - -uint32_t -DecodedAudioDataSink::PlayFromAudioQueue() -{ - AssertOnAudioThread(); - NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused"); - RefPtr audio = - dont_AddRef(AudioQueue().PopFront().take()->As()); - - SINK_LOG_V("playing %u frames of audio at time %lld", - audio->mFrames, audio->mTime); - if (audio->mRate == mInfo.mRate && audio->mChannels == mInfo.mChannels) { - mAudioStream->Write(audio->mAudioData.get(), audio->mFrames); - } else { - SINK_LOG_V("mismatched sample format mInfo=[%uHz/%u channels] audio=[%uHz/%u channels]", - mInfo.mRate, mInfo.mChannels, audio->mRate, audio->mChannels); - PlaySilence(audio->mFrames); - } - - StartAudioStreamPlaybackIfNeeded(); - - return audio->mFrames; -} - -void -DecodedAudioDataSink::StartAudioStreamPlaybackIfNeeded() -{ - // This value has been chosen empirically. - const uint32_t MIN_WRITE_BEFORE_START_USECS = 200000; - - // We want to have enough data in the buffer to start the stream. - if (static_cast(mAudioStream->GetWritten()) / mAudioStream->GetRate() >= - static_cast(MIN_WRITE_BEFORE_START_USECS) / USECS_PER_S) { - mAudioStream->Start(); - } -} - -void -DecodedAudioDataSink::WriteSilence(uint32_t aFrames) -{ - uint32_t numSamples = aFrames * mInfo.mChannels; - nsAutoTArray buf; - buf.SetLength(numSamples); - memset(buf.Elements(), 0, numSamples * sizeof(AudioDataValue)); - mAudioStream->Write(buf.Elements(), aFrames); - - StartAudioStreamPlaybackIfNeeded(); -} - int64_t DecodedAudioDataSink::GetEndTime() const { @@ -521,16 +162,117 @@ DecodedAudioDataSink::GetEndTime() const return playedUsecs.value(); } -void -DecodedAudioDataSink::AssertOnAudioThread() +UniquePtr +DecodedAudioDataSink::PopFrames(uint32_t aFrames) { - MOZ_ASSERT(NS_GetCurrentThread() == mThread); + class Chunk : public AudioStream::Chunk { + public: + Chunk(AudioData* aBuffer, uint32_t aFrames, AudioDataValue* aData) + : mBuffer(aBuffer), mFrames(aFrames), mData(aData) {} + Chunk() : mFrames(0), mData(nullptr) {} + const AudioDataValue* Data() const { return mData; } + uint32_t Frames() const { return mFrames; } + uint32_t Channels() const { return mBuffer ? mBuffer->mChannels: 0; } + uint32_t Rate() const { return mBuffer ? mBuffer->mRate : 0; } + AudioDataValue* GetWritable() const { return mData; } + private: + const RefPtr mBuffer; + const uint32_t mFrames; + AudioDataValue* const mData; + }; + + class SilentChunk : public AudioStream::Chunk { + public: + SilentChunk(uint32_t aFrames, uint32_t aChannels, uint32_t aRate) + : mFrames(aFrames) + , mChannels(aChannels) + , mRate(aRate) + , mData(MakeUnique(aChannels * aFrames)) { + memset(mData.get(), 0, aChannels * aFrames * sizeof(AudioDataValue)); + } + const AudioDataValue* Data() const { return mData.get(); } + uint32_t Frames() const { return mFrames; } + uint32_t Channels() const { return mChannels; } + uint32_t Rate() const { return mRate; } + AudioDataValue* GetWritable() const { return mData.get(); } + private: + const uint32_t mFrames; + const uint32_t mChannels; + const uint32_t mRate; + UniquePtr mData; + }; + + if (!mCurrentData) { + // No data in the queue. Return an empty chunk. + if (AudioQueue().GetSize() == 0) { + return MakeUnique(); + } + + // See if there's a gap in the audio. If there is, push silence into the + // audio hardware, so we can play across the gap. + // Calculate the timestamp of the next chunk of audio in numbers of + // samples. + CheckedInt64 sampleTime = UsecsToFrames(AudioQueue().PeekFront()->mTime, mInfo.mRate); + // Calculate the number of frames that have been pushed onto the audio hardware. + CheckedInt64 playedFrames = UsecsToFrames(mStartTime, mInfo.mRate) + + static_cast(mWritten); + CheckedInt64 missingFrames = sampleTime - playedFrames; + + if (!missingFrames.isValid() || !sampleTime.isValid()) { + NS_WARNING("Int overflow in DecodedAudioDataSink"); + mErrored = true; + return MakeUnique(); + } + + if (missingFrames.value() > AUDIO_FUZZ_FRAMES) { + // The next audio chunk begins some time after the end of the last chunk + // we pushed to the audio hardware. We must push silence into the audio + // hardware so that the next audio chunk begins playback at the correct + // time. + missingFrames = std::min(UINT32_MAX, missingFrames.value()); + auto framesToPop = std::min(missingFrames.value(), aFrames); + mWritten += framesToPop; + return MakeUnique(framesToPop, mInfo.mChannels, mInfo.mRate); + } + + mCurrentData = dont_AddRef(AudioQueue().PopFront().take()->As()); + mCursor = MakeUnique(mCurrentData->mAudioData.get(), + mCurrentData->mChannels, + mCurrentData->mFrames); + } + + auto framesToPop = std::min(aFrames, mCursor->Available()); + + SINK_LOG_V("playing audio at time=%lld offset=%u length=%u", + mCurrentData->mTime, mCurrentData->mFrames - mCursor->Available(), framesToPop); + + UniquePtr chunk = + MakeUnique(mCurrentData, framesToPop, mCursor->Ptr()); + + mWritten += framesToPop; + mCursor->Advance(framesToPop); + + // All frames are popped. Reset mCurrentData so we can pop new elements from + // the audio queue in next calls to PopFrames(). + if (mCursor->Available() == 0) { + mCurrentData = nullptr; + } + + return chunk; +} + +bool +DecodedAudioDataSink::Ended() const +{ + // Return true when error encountered so AudioStream can start draining. + return AudioQueue().IsFinished() || mErrored; } void -DecodedAudioDataSink::AssertNotOnAudioThread() +DecodedAudioDataSink::Drained() { - MOZ_ASSERT(NS_GetCurrentThread() != mThread); + SINK_LOG("Drained"); + mEndPromise.Resolve(true, __func__); } } // namespace media diff --git a/dom/media/mediasink/DecodedAudioDataSink.h b/dom/media/mediasink/DecodedAudioDataSink.h index 17eb259431..1134068e14 100644 --- a/dom/media/mediasink/DecodedAudioDataSink.h +++ b/dom/media/mediasink/DecodedAudioDataSink.h @@ -7,6 +7,7 @@ #define DecodedAudioDataSink_h__ #include "AudioSink.h" +#include "AudioStream.h" #include "MediaEventSource.h" #include "MediaInfo.h" #include "mozilla/RefPtr.h" @@ -20,13 +21,11 @@ namespace mozilla { -class AudioStream; - namespace media { -class DecodedAudioDataSink : public AudioSink { +class DecodedAudioDataSink : public AudioSink, + private AudioStream::DataSource { public: - DecodedAudioDataSink(MediaQueue& aAudioQueue, int64_t aStartTime, const AudioInfo& aInfo, @@ -34,10 +33,11 @@ public: // Return a promise which will be resolved when DecodedAudioDataSink // finishes playing, or rejected if any error. - RefPtr Init() override; + RefPtr Init(const PlaybackParams& aParams) override; /* - * All public functions below are thread-safe. + * All public functions are not thread-safe. + * Called on the task queue of MDSM only. */ int64_t GetPosition() override; int64_t GetEndTime() const override; @@ -55,103 +55,23 @@ public: void SetPlaying(bool aPlaying) override; private: - enum State { - AUDIOSINK_STATE_INIT, - AUDIOSINK_STATE_PLAYING, - AUDIOSINK_STATE_COMPLETE, - AUDIOSINK_STATE_SHUTDOWN, - AUDIOSINK_STATE_ERROR - }; - virtual ~DecodedAudioDataSink(); - void DispatchTask(already_AddRefed&& event); - void SetState(State aState); - void ScheduleNextLoop(); - void ScheduleNextLoopCrossThread(); + // Allocate and initialize mAudioStream. Returns NS_OK on success. + nsresult InitializeAudioStream(const PlaybackParams& aParams); - void OnAudioQueueEvent(); - void ConnectListener(); - void DisconnectListener(); + // Interface of AudioStream::DataSource. + // Called on the callback thread of cubeb. + UniquePtr PopFrames(uint32_t aFrames) override; + bool Ended() const override; + void Drained() override; - // The main loop for the audio thread. Sent to the thread as - // an nsRunnableMethod. This continually does blocking writes to - // to audio stream to play audio data. - void AudioLoop(); - - // Allocate and initialize mAudioStream. Returns NS_OK on success. - nsresult InitializeAudioStream(); - - void Drain(); - - void Cleanup(); - - bool ExpectMoreAudioData(); - - // Return true if playback is not ready and the sink is not told to shut down. - bool WaitingForAudioToPlay(); - - // Check if the sink has been told to shut down, resuming mAudioStream if - // not. Returns true if processing should continue, false if AudioLoop - // should shutdown. - bool IsPlaybackContinuing(); - - // Write audio samples or silence to the audio hardware. - // Return false if any error. Called on the audio thread. - bool PlayAudio(); - - void FinishAudioLoop(); - - // Write aFrames of audio frames of silence to the audio hardware. Returns - // the number of frames actually written. The write size is capped at - // SILENCE_BYTES_CHUNK (32kB), so must be called in a loop to write the - // desired number of frames. This ensures that the playback position - // advances smoothly, and guarantees that we don't try to allocate an - // impossibly large chunk of memory in order to play back silence. Called - // on the audio thread. - uint32_t PlaySilence(uint32_t aFrames); - - // Pops an audio chunk from the front of the audio queue, and pushes its - // audio data to the audio hardware. Called on the audio thread. - uint32_t PlayFromAudioQueue(); - - // If we have already written enough frames to the AudioStream, start the - // playback. - void StartAudioStreamPlaybackIfNeeded(); - void WriteSilence(uint32_t aFrames); - - ReentrantMonitor& GetReentrantMonitor() const { - return mMonitor; - } - - void AssertCurrentThreadInMonitor() const { - GetReentrantMonitor().AssertCurrentThreadIn(); - } - - void AssertOnAudioThread(); - void AssertNotOnAudioThread(); - - mutable ReentrantMonitor mMonitor; - - // There members are accessed on the audio thread only. - State mState; - Maybe mPendingState; - bool mAudioLoopScheduled; - - // Thread for pushing audio onto the audio hardware. - // The "audio push thread". - nsCOMPtr mThread; - - // The audio stream resource. Used on the state machine, and audio threads. - // This is created and destroyed on the audio thread, while holding the - // decoder monitor, so if this is used off the audio thread, you must - // first acquire the decoder monitor and check that it is non-null. + // The audio stream resource. Used on the task queue of MDSM only. RefPtr mAudioStream; // The presentation time of the first audio frame that was played in // microseconds. We can add this to the audio stream position to determine - // the current audio time. Accessed on audio and state machine thread. - // Synchronized by decoder monitor. + // the current audio time. const int64_t mStartTime; // PCM frames written to the stream so far. @@ -159,21 +79,28 @@ private: // Keep the last good position returned from the audio stream. Used to ensure // position returned by GetPosition() is mono-increasing in spite of audio - // stream error. + // stream error. Used on the task queue of MDSM only. int64_t mLastGoodPosition; const AudioInfo mInfo; const dom::AudioChannel mChannel; - bool mStopAudioThread; - + // Used on the task queue of MDSM only. bool mPlaying; MozPromiseHolder mEndPromise; - MediaEventListener mPushListener; - MediaEventListener mFinishListener; + /* + * Members to implement AudioStream::DataSource. + * Used on the callback thread of cubeb. + */ + // The AudioData at which AudioStream::DataSource is reading. + RefPtr mCurrentData; + // Keep track of the read positoin of mCurrentData. + UniquePtr mCursor; + // True if there is any error in processing audio data like overflow. + bool mErrored = false; }; } // namespace media diff --git a/dom/media/mediasource/MediaSourceDecoder.cpp b/dom/media/mediasource/MediaSourceDecoder.cpp index 9cc4688426..881dc939d4 100644 --- a/dom/media/mediasource/MediaSourceDecoder.cpp +++ b/dom/media/mediasource/MediaSourceDecoder.cpp @@ -152,7 +152,7 @@ MediaSourceDecoder::GetBuffered() return buffered; } -void +RefPtr MediaSourceDecoder::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); @@ -164,7 +164,7 @@ MediaSourceDecoder::Shutdown() } mDemuxer = nullptr; - MediaDecoder::Shutdown(); + return MediaDecoder::Shutdown(); } /*static*/ diff --git a/dom/media/mediasource/MediaSourceDecoder.h b/dom/media/mediasource/MediaSourceDecoder.h index 9972822ffa..ff3110eda3 100644 --- a/dom/media/mediasource/MediaSourceDecoder.h +++ b/dom/media/mediasource/MediaSourceDecoder.h @@ -37,10 +37,10 @@ class MediaSourceDecoder : public MediaDecoder public: explicit MediaSourceDecoder(dom::HTMLMediaElement* aElement); - virtual MediaDecoder* Clone(MediaDecoderOwner* aOwner) override; - virtual MediaDecoderStateMachine* CreateStateMachine() override; - virtual nsresult Load(nsIStreamListener**) override; - virtual media::TimeIntervals GetSeekable() override; + MediaDecoder* Clone(MediaDecoderOwner* aOwner) override; + MediaDecoderStateMachine* CreateStateMachine() override; + nsresult Load(nsIStreamListener**) override; + media::TimeIntervals GetSeekable() override; media::TimeIntervals GetBuffered() override; // We can't do this in the constructor because we don't know what type of @@ -51,7 +51,7 @@ public: mDormantSupported = aSupported; } - virtual void Shutdown() override; + RefPtr Shutdown() override; static already_AddRefed CreateResource(nsIPrincipal* aPrincipal = nullptr); @@ -61,7 +61,7 @@ public: void Ended(bool aEnded); // Return the duration of the video in seconds. - virtual double GetDuration() override; + double GetDuration() override; void SetInitialDuration(int64_t aDuration); void SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction); diff --git a/dom/media/mediasource/MediaSourceResource.h b/dom/media/mediasource/MediaSourceResource.h index 6349f81d4a..3ce4ebb520 100644 --- a/dom/media/mediasource/MediaSourceResource.h +++ b/dom/media/mediasource/MediaSourceResource.h @@ -28,43 +28,43 @@ public: , mEnded(false) {} - virtual nsresult Close() override { return NS_OK; } - virtual void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); } - virtual void Resume() override { UNIMPLEMENTED(); } - virtual bool CanClone() override { UNIMPLEMENTED(); return false; } - virtual already_AddRefed CloneData(MediaResourceCallback*) override { UNIMPLEMENTED(); return nullptr; } - virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); } - virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); } - virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } - virtual int64_t Tell() override { UNIMPLEMENTED(); return -1; } - virtual void Pin() override { UNIMPLEMENTED(); } - virtual void Unpin() override { UNIMPLEMENTED(); } - virtual double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; } - virtual int64_t GetLength() override { UNIMPLEMENTED(); return -1; } - virtual int64_t GetNextCachedData(int64_t aOffset) override { UNIMPLEMENTED(); return -1; } - virtual int64_t GetCachedDataEnd(int64_t aOffset) override { UNIMPLEMENTED(); return -1; } - virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; } - virtual bool IsSuspendedByCache() override { UNIMPLEMENTED(); return false; } - virtual bool IsSuspended() override { UNIMPLEMENTED(); return false; } - virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } - virtual nsresult Open(nsIStreamListener** aStreamListener) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } + nsresult Close() override { return NS_OK; } + void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); } + void Resume() override { UNIMPLEMENTED(); } + bool CanClone() override { UNIMPLEMENTED(); return false; } + already_AddRefed CloneData(MediaResourceCallback*) override { UNIMPLEMENTED(); return nullptr; } + void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); } + void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); } + nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } + int64_t Tell() override { UNIMPLEMENTED(); return -1; } + void Pin() override { UNIMPLEMENTED(); } + void Unpin() override { UNIMPLEMENTED(); } + double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; } + int64_t GetLength() override { UNIMPLEMENTED(); return -1; } + int64_t GetNextCachedData(int64_t aOffset) override { UNIMPLEMENTED(); return -1; } + int64_t GetCachedDataEnd(int64_t aOffset) override { UNIMPLEMENTED(); return -1; } + bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; } + bool IsSuspendedByCache() override { UNIMPLEMENTED(); return false; } + bool IsSuspended() override { UNIMPLEMENTED(); return false; } + nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } + nsresult Open(nsIStreamListener** aStreamListener) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } - virtual already_AddRefed GetCurrentPrincipal() override + already_AddRefed GetCurrentPrincipal() override { return RefPtr(mPrincipal).forget(); } - virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override + nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override { UNIMPLEMENTED(); aRanges += MediaByteRange(0, GetLength()); return NS_OK; } - virtual bool IsTransportSeekable() override { return true; } - virtual const nsCString& GetContentType() const override { return mType; } + bool IsTransportSeekable() override { return true; } + const nsCString& GetContentType() const override { return mType; } - virtual bool IsLiveStream() override + bool IsLiveStream() override { MonitorAutoLock mon(mMonitor); return !mEnded; @@ -75,14 +75,14 @@ public: mEnded = aEnded; } - virtual bool IsExpectingMoreData() override + bool IsExpectingMoreData() override { MonitorAutoLock mon(mMonitor); return !mEnded; } private: - virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf); size += mType.SizeOfExcludingThisIfUnshared(aMallocSizeOf); @@ -90,7 +90,7 @@ private: return size; } - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/mediasource/ResourceQueue.cpp b/dom/media/mediasource/ResourceQueue.cpp index 58cc0a2fb5..95b0ee3c41 100644 --- a/dom/media/mediasource/ResourceQueue.cpp +++ b/dom/media/mediasource/ResourceQueue.cpp @@ -35,7 +35,7 @@ ResourceItem::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const } class ResourceQueueDeallocator : public nsDequeFunctor { - virtual void* operator() (void* aObject) { + void* operator() (void* aObject) override { delete static_cast(aObject); return nullptr; } diff --git a/dom/media/mediasource/SourceBufferContentManager.h b/dom/media/mediasource/SourceBufferContentManager.h index 3e74c5bd22..563b1e1d8a 100644 --- a/dom/media/mediasource/SourceBufferContentManager.h +++ b/dom/media/mediasource/SourceBufferContentManager.h @@ -57,7 +57,7 @@ public: // Runs MSE range removal algorithm. // http://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal virtual RefPtr RangeRemoval(media::TimeUnit aStart, - media::TimeUnit aEnd) = 0; + media::TimeUnit aEnd) = 0; enum class EvictDataResult : int8_t { diff --git a/dom/media/mediasource/SourceBufferResource.h b/dom/media/mediasource/SourceBufferResource.h index 0faea0ad2a..32253c1405 100644 --- a/dom/media/mediasource/SourceBufferResource.h +++ b/dom/media/mediasource/SourceBufferResource.h @@ -39,20 +39,20 @@ class SourceBufferResource final : public MediaResource { public: explicit SourceBufferResource(const nsACString& aType); - virtual nsresult Close() override; - virtual void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); } - virtual void Resume() override { UNIMPLEMENTED(); } - virtual already_AddRefed GetCurrentPrincipal() override { UNIMPLEMENTED(); return nullptr; } - virtual already_AddRefed CloneData(MediaResourceCallback*) override { UNIMPLEMENTED(); return nullptr; } - virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); } - virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); } - virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override; - virtual int64_t Tell() override { return mOffset; } - virtual void Pin() override { UNIMPLEMENTED(); } - virtual void Unpin() override { UNIMPLEMENTED(); } - virtual double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; } - virtual int64_t GetLength() override { return mInputBuffer.GetLength(); } - virtual int64_t GetNextCachedData(int64_t aOffset) override { + nsresult Close() override; + void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); } + void Resume() override { UNIMPLEMENTED(); } + already_AddRefed GetCurrentPrincipal() override { UNIMPLEMENTED(); return nullptr; } + already_AddRefed CloneData(MediaResourceCallback*) override { UNIMPLEMENTED(); return nullptr; } + void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); } + void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); } + nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override; + int64_t Tell() override { return mOffset; } + void Pin() override { UNIMPLEMENTED(); } + void Unpin() override { UNIMPLEMENTED(); } + double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; } + int64_t GetLength() override { return mInputBuffer.GetLength(); } + int64_t GetNextCachedData(int64_t aOffset) override { ReentrantMonitorAutoEnter mon(mMonitor); MOZ_ASSERT(aOffset >= 0); if (uint64_t(aOffset) < mInputBuffer.GetOffset()) { @@ -62,15 +62,15 @@ public: } return aOffset; } - virtual int64_t GetCachedDataEnd(int64_t aOffset) override { UNIMPLEMENTED(); return -1; } - virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override { return false; } - virtual bool IsSuspendedByCache() override { UNIMPLEMENTED(); return false; } - virtual bool IsSuspended() override { UNIMPLEMENTED(); return false; } - virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override; - virtual bool IsTransportSeekable() override { UNIMPLEMENTED(); return true; } - virtual nsresult Open(nsIStreamListener** aStreamListener) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } + int64_t GetCachedDataEnd(int64_t aOffset) override { UNIMPLEMENTED(); return -1; } + bool IsDataCachedToEndOfResource(int64_t aOffset) override { return false; } + bool IsSuspendedByCache() override { UNIMPLEMENTED(); return false; } + bool IsSuspended() override { UNIMPLEMENTED(); return false; } + nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override; + bool IsTransportSeekable() override { UNIMPLEMENTED(); return true; } + nsresult Open(nsIStreamListener** aStreamListener) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } - virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override + nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override { ReentrantMonitorAutoEnter mon(mMonitor); if (mInputBuffer.GetLength()) { @@ -80,10 +80,9 @@ public: return NS_OK; } - virtual const nsCString& GetContentType() const override { return mType; } + const nsCString& GetContentType() const override { return mType; } - virtual size_t SizeOfExcludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { ReentrantMonitorAutoEnter mon(mMonitor); @@ -94,13 +93,12 @@ public: return size; } - virtual size_t SizeOfIncludingThis( - MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } - virtual bool IsExpectingMoreData() override + bool IsExpectingMoreData() override { return false; } diff --git a/dom/media/systemservices/CamerasChild.cpp b/dom/media/systemservices/CamerasChild.cpp index 094887d21e..4d6a54f9d8 100644 --- a/dom/media/systemservices/CamerasChild.cpp +++ b/dom/media/systemservices/CamerasChild.cpp @@ -29,74 +29,16 @@ mozilla::LazyLogModule gCamerasChildLog("CamerasChild"); namespace mozilla { namespace camera { -// We emulate the sync webrtc.org API with the help of singleton -// CamerasSingleton, which manages a pointer to an IPC object, a thread -// where IPC operations should run on, and a mutex. -// The static function Cameras() will use that Singleton to set up, -// if needed, both the thread and the associated IPC objects and return -// a pointer to the IPC object. Users can then do IPC calls on that object -// after dispatching them to aforementioned thread. +CamerasSingleton::CamerasSingleton() + : mCamerasMutex("CamerasSingleton::mCamerasMutex"), + mCameras(nullptr), + mCamerasChildThread(nullptr) { + LOG(("CamerasSingleton: %p", this)); +} -// 2 Threads are involved in this code: -// - the MediaManager thread, which will call the (static, sync API) functions -// through MediaEngineRemoteVideoSource -// - the Cameras IPC thread, which will be doing our IPC to the parent process -// via PBackground - -// Our main complication is that we emulate a sync API while (having to do) -// async messaging. We dispatch the messages to another thread to send them -// async and hold a Monitor to wait for the result to be asynchronously received -// again. The requirement for async messaging originates on the parent side: -// it's not reasonable to block all PBackground IPC there while waiting for -// something like device enumeration to complete. - -class CamerasSingleton { -public: - CamerasSingleton() - : mCamerasMutex("CamerasSingleton::mCamerasMutex"), - mCameras(nullptr), - mCamerasChildThread(nullptr) { - LOG(("CamerasSingleton: %p", this)); - } - - ~CamerasSingleton() { - LOG(("~CamerasSingleton: %p", this)); - } - - static CamerasSingleton& GetInstance() { - static CamerasSingleton instance; - return instance; - } - - static OffTheBooksMutex& Mutex() { - return GetInstance().mCamerasMutex; - } - - static CamerasChild*& Child() { - GetInstance().Mutex().AssertCurrentThreadOwns(); - return GetInstance().mCameras; - } - - static nsCOMPtr& Thread() { - GetInstance().Mutex().AssertCurrentThreadOwns(); - return GetInstance().mCamerasChildThread; - } - -private: - // Reinitializing CamerasChild will change the pointers below. - // We don't want this to happen in the middle of preparing IPC. - // We will be alive on destruction, so this needs to be off the books. - mozilla::OffTheBooksMutex mCamerasMutex; - - // This is owned by the IPC code, and the same code controls the lifetime. - // It will set and clear this pointer as appropriate in setup/teardown. - // We'd normally make this a WeakPtr but unfortunately the IPC code already - // uses the WeakPtr mixin in a protected base class of CamerasChild, and in - // any case the object becomes unusable as soon as IPC is tearing down, which - // will be before actual destruction. - CamerasChild* mCameras; - nsCOMPtr mCamerasChildThread; -}; +CamerasSingleton::~CamerasSingleton() { + LOG(("~CamerasSingleton: %p", this)); +} class InitializeIPCThread : public nsRunnable { @@ -128,7 +70,6 @@ public: } CamerasChild* GetCamerasChild() { - MOZ_ASSERT(mCamerasChild); return mCamerasChild; } @@ -136,9 +77,9 @@ private: CamerasChild* mCamerasChild; }; -static CamerasChild* -Cameras() { - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); +CamerasChild* +GetCamerasChild() { + CamerasSingleton::Mutex().AssertCurrentThreadOwns(); if (!CamerasSingleton::Child()) { MOZ_ASSERT(!NS_IsMainThread(), "Should not be on the main Thread"); MOZ_ASSERT(!CamerasSingleton::Thread()); @@ -162,7 +103,9 @@ Cameras() { sr->DispatchToThread(CamerasSingleton::Thread()); CamerasSingleton::Child() = runnable->GetCamerasChild(); } - MOZ_ASSERT(CamerasSingleton::Child()); + if (!CamerasSingleton::Child()) { + LOG(("Failed to set up CamerasChild, are we in shutdown?")); + } return CamerasSingleton::Child(); } @@ -188,11 +131,6 @@ CamerasChild::RecvReplySuccess(void) return true; } -int NumberOfCapabilities(CaptureEngine aCapEngine, const char* deviceUniqueIdUTF8) -{ - return Cameras()->NumberOfCapabilities(aCapEngine, deviceUniqueIdUTF8); -} - bool CamerasChild::RecvReplyNumberOfCapabilities(const int& numdev) { @@ -205,14 +143,68 @@ CamerasChild::RecvReplyNumberOfCapabilities(const int& numdev) return true; } +// Helper function to dispatch calls to the IPC Thread and +// CamerasChild object. Takes the needed locks and dispatches. +// Takes a "failed" value and a reference to the output variable +// as parameters, will return the right one depending on whether +// dispatching succeeded. +template +class LockAndDispatch +{ +public: + LockAndDispatch(CamerasChild* aCamerasChild, + const char* aRequestingFunc, + nsIRunnable *aRunnable, + const T& aFailureValue = T(-1), const T& aSuccessValue = T(0)) + : mCamerasChild(aCamerasChild), mRequestingFunc(aRequestingFunc), + mRunnable(aRunnable), + mReplyLock(aCamerasChild->mReplyMonitor), + mRequestLock(aCamerasChild->mRequestMutex), + mSuccess(true), + mFailureValue(aFailureValue), mSuccessValue(aSuccessValue) + { + Dispatch(); + } + + const T& ReturnValue() const { + if (mSuccess) { + return mSuccessValue; + } else { + return mFailureValue; + } + } + + const bool& Success() const { + return mSuccess; + } + +private: + void Dispatch() { + if (!mCamerasChild->DispatchToParent(mRunnable, mReplyLock)) { + LOG(("Cameras dispatch for IPC failed in %s", mRequestingFunc)); + mSuccess = false; + } + } + + CamerasChild* mCamerasChild; + const char* mRequestingFunc; + nsIRunnable* mRunnable; + // Prevent concurrent use of the reply variables by holding + // the mReplyMonitor. Note that this is unlocked while waiting for + // the reply to be filled in, necessitating the additional mRequestLock/Mutex; + MonitorAutoLock mReplyLock; + MutexAutoLock mRequestLock; + bool mSuccess; + const T& mFailureValue; + const T& mSuccessValue; +}; + bool CamerasChild::DispatchToParent(nsIRunnable* aRunnable, MonitorAutoLock& aMonitor) { - { - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasSingleton::Thread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL); - } + CamerasSingleton::Mutex().AssertCurrentThreadOwns(); + CamerasSingleton::Thread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL); // We can't see if the send worked, so we need to be able to bail // out on shutdown (when it failed and we won't get a reply). if (!mIPCIsAlive) { @@ -234,8 +226,6 @@ int CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine, const char* deviceUniqueIdUTF8) { - // Prevents multiple outstanding requests from happening. - MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); LOG(("NumberOfCapabilities for %s", deviceUniqueIdUTF8)); nsCString unique_id(deviceUniqueIdUTF8); @@ -246,27 +236,14 @@ CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine, } return NS_ERROR_FAILURE; }); - // Prevent concurrent use of the reply variables. Note - // that this is unlocked while waiting for the reply to be - // filled in, necessitating the first Mutex above. - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - LOG(("Get capture capability count failed")); - return 0; - } - LOG(("Capture capability count: %d", mReplyInteger)); - return mReplyInteger; -} - -int NumberOfCaptureDevices(CaptureEngine aCapEngine) -{ - return Cameras()->NumberOfCaptureDevices(aCapEngine); + LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); + LOG(("Capture capability count: %d", dispatcher.ReturnValue())); + return dispatcher.ReturnValue(); } int CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine) { - MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCOMPtr runnable = media::NewRunnableFrom([this, aCapEngine]() -> nsresult { @@ -275,13 +252,9 @@ CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine) } return NS_ERROR_FAILURE; }); - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - LOG(("Get NumberOfCaptureDevices failed")); - return 0; - } - LOG(("Capture Devices: %d", mReplyInteger)); - return mReplyInteger; + LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); + LOG(("Capture Devices: %d", dispatcher.ReturnValue())); + return dispatcher.ReturnValue(); } bool @@ -296,23 +269,12 @@ CamerasChild::RecvReplyNumberOfCaptureDevices(const int& numdev) return true; } -int GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8, - const unsigned int capability_number, - webrtc::CaptureCapability& capability) -{ - return Cameras()->GetCaptureCapability(aCapEngine, - unique_idUTF8, - capability_number, - capability); -} - int CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int capability_number, webrtc::CaptureCapability& capability) { - MutexAutoLock requestLock(mRequestMutex); LOG(("GetCaptureCapability: %s %d", unique_idUTF8, capability_number)); nsCString unique_id(unique_idUTF8); nsCOMPtr runnable = @@ -322,12 +284,11 @@ CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine, } return NS_ERROR_FAILURE; }); - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - return -1; + LockAndDispatch<> dispatcher(this, __func__, runnable); + if (dispatcher.Success()) { + capability = mReplyCapability; } - capability = mReplyCapability; - return 0; + return dispatcher.ReturnValue(); } bool @@ -348,21 +309,6 @@ CamerasChild::RecvReplyGetCaptureCapability(const CaptureCapability& ipcCapabili return true; } - -int GetCaptureDevice(CaptureEngine aCapEngine, - unsigned int list_number, char* device_nameUTF8, - const unsigned int device_nameUTF8Length, - char* unique_idUTF8, - const unsigned int unique_idUTF8Length) -{ - return Cameras()->GetCaptureDevice(aCapEngine, - list_number, - device_nameUTF8, - device_nameUTF8Length, - unique_idUTF8, - unique_idUTF8Length); -} - int CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine, unsigned int list_number, char* device_nameUTF8, @@ -370,7 +316,6 @@ CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine, char* unique_idUTF8, const unsigned int unique_idUTF8Length) { - MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCOMPtr runnable = media::NewRunnableFrom([this, aCapEngine, list_number]() -> nsresult { @@ -379,15 +324,13 @@ CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine, } return NS_ERROR_FAILURE; }); - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - LOG(("GetCaptureDevice failed")); - return -1; + LockAndDispatch<> dispatcher(this, __func__, runnable); + if (dispatcher.Success()) { + base::strlcpy(device_nameUTF8, mReplyDeviceName.get(), device_nameUTF8Length); + base::strlcpy(unique_idUTF8, mReplyDeviceID.get(), unique_idUTF8Length); + LOG(("Got %s name %s id", device_nameUTF8, unique_idUTF8)); } - base::strlcpy(device_nameUTF8, mReplyDeviceName.get(), device_nameUTF8Length); - base::strlcpy(unique_idUTF8, mReplyDeviceID.get(), unique_idUTF8Length); - LOG(("Got %s name %s id", device_nameUTF8, unique_idUTF8)); - return 0; + return dispatcher.ReturnValue(); } bool @@ -404,41 +347,29 @@ CamerasChild::RecvReplyGetCaptureDevice(const nsCString& device_name, return true; } -int AllocateCaptureDevice(CaptureEngine aCapEngine, - const char* unique_idUTF8, - const unsigned int unique_idUTF8Length, - int& capture_id) -{ - return Cameras()->AllocateCaptureDevice(aCapEngine, - unique_idUTF8, - unique_idUTF8Length, - capture_id); -} - int CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int unique_idUTF8Length, - int& capture_id) + int& capture_id, + const nsACString& aOrigin) { - MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCString unique_id(unique_idUTF8); + nsCString origin(aOrigin); nsCOMPtr runnable = - media::NewRunnableFrom([this, aCapEngine, unique_id]() -> nsresult { - if (this->SendAllocateCaptureDevice(aCapEngine, unique_id)) { + media::NewRunnableFrom([this, aCapEngine, unique_id, origin]() -> nsresult { + if (this->SendAllocateCaptureDevice(aCapEngine, unique_id, origin)) { return NS_OK; } return NS_ERROR_FAILURE; }); - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - LOG(("AllocateCaptureDevice failed")); - return -1; + LockAndDispatch<> dispatcher(this, __func__, runnable); + if (dispatcher.Success()) { + LOG(("Capture Device allocated: %d", mReplyInteger)); + capture_id = mReplyInteger; } - LOG(("Capture Device allocated: %d", mReplyInteger)); - capture_id = mReplyInteger; - return 0; + return dispatcher.ReturnValue(); } @@ -454,16 +385,10 @@ CamerasChild::RecvReplyAllocateCaptureDevice(const int& numdev) return true; } -int ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id) -{ - return Cameras()->ReleaseCaptureDevice(aCapEngine, capture_id); -} - int CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id) { - MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCOMPtr runnable = media::NewRunnableFrom([this, aCapEngine, capture_id]() -> nsresult { @@ -472,11 +397,8 @@ CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine, } return NS_ERROR_FAILURE; }); - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - return -1; - } - return 0; + LockAndDispatch<> dispatcher(this, __func__, runnable); + return dispatcher.ReturnValue(); } void @@ -504,24 +426,12 @@ CamerasChild::RemoveCallback(const CaptureEngine aCapEngine, const int capture_i } } -int StartCapture(CaptureEngine aCapEngine, - const int capture_id, - webrtc::CaptureCapability& webrtcCaps, - webrtc::ExternalRenderer* cb) -{ - return Cameras()->StartCapture(aCapEngine, - capture_id, - webrtcCaps, - cb); -} - int CamerasChild::StartCapture(CaptureEngine aCapEngine, const int capture_id, webrtc::CaptureCapability& webrtcCaps, webrtc::ExternalRenderer* cb) { - MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); AddCallback(aCapEngine, capture_id, cb); CaptureCapability capCap(webrtcCaps.width, @@ -538,22 +448,13 @@ CamerasChild::StartCapture(CaptureEngine aCapEngine, } return NS_ERROR_FAILURE; }); - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - return -1; - } - return 0; -} - -int StopCapture(CaptureEngine aCapEngine, const int capture_id) -{ - return Cameras()->StopCapture(aCapEngine, capture_id); + LockAndDispatch<> dispatcher(this, __func__, runnable); + return dispatcher.ReturnValue(); } int CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id) { - MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCOMPtr runnable = media::NewRunnableFrom([this, aCapEngine, capture_id]() -> nsresult { @@ -562,27 +463,25 @@ CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id) } return NS_ERROR_FAILURE; }); - MonitorAutoLock monitor(mReplyMonitor); - if (!DispatchToParent(runnable, monitor)) { - return -1; + LockAndDispatch<> dispatcher(this, __func__, runnable); + if (dispatcher.Success()) { + RemoveCallback(aCapEngine, capture_id); } - RemoveCallback(aCapEngine, capture_id); - return 0; + return dispatcher.ReturnValue(); } void Shutdown(void) { - { - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - if (!CamerasSingleton::Child()) { - // We don't want to cause everything to get fired up if we're - // really already shut down. - LOG(("Shutdown when already shut down")); - return; - } + OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); + CamerasChild* child = CamerasSingleton::Child(); + if (!child) { + // We don't want to cause everything to get fired up if we're + // really already shut down. + LOG(("Shutdown when already shut down")); + return; } - Cameras()->Shutdown(); + child->ShutdownAll(); } class ShutdownRunnable : public nsRunnable { @@ -607,15 +506,22 @@ private: }; void -CamerasChild::Shutdown() +CamerasChild::ShutdownAll() { + // Called with CamerasSingleton::Mutex() held + ShutdownParent(); + ShutdownChild(); +} + +void +CamerasChild::ShutdownParent() +{ + // Called with CamerasSingleton::Mutex() held { MonitorAutoLock monitor(mReplyMonitor); mIPCIsAlive = false; monitor.NotifyAll(); } - - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); if (CamerasSingleton::Thread()) { LOG(("Dispatching actor deletion")); // Delete the parent actor. @@ -627,6 +533,16 @@ CamerasChild::Shutdown() return NS_OK; }); CamerasSingleton::Thread()->Dispatch(deleteRunnable, NS_DISPATCH_NORMAL); + } else { + LOG(("ShutdownParent called without PBackground thread")); + } +} + +void +CamerasChild::ShutdownChild() +{ + // Called with CamerasSingleton::Mutex() held + if (CamerasSingleton::Thread()) { LOG(("PBackground thread exists, dispatching close")); // Dispatch closing the IPC thread back to us when the // BackgroundChild is closed. @@ -708,7 +624,14 @@ CamerasChild::~CamerasChild() { LOG(("~CamerasChild: %p", this)); - Shutdown(); + { + OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); + // In normal circumstances we've already shut down and the + // following does nothing. But on fatal IPC errors we will + // get destructed immediately, and should not try to reach + // the parent. + ShutdownChild(); + } MOZ_COUNT_DTOR(CamerasChild); } diff --git a/dom/media/systemservices/CamerasChild.h b/dom/media/systemservices/CamerasChild.h index 16202adc7b..02b59b473d 100644 --- a/dom/media/systemservices/CamerasChild.h +++ b/dom/media/systemservices/CamerasChild.h @@ -7,6 +7,7 @@ #ifndef mozilla_CamerasChild_h #define mozilla_CamerasChild_h +#include "mozilla/Move.h" #include "mozilla/Pair.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/camera/PCamerasChild.h" @@ -46,36 +47,102 @@ struct CapturerElement { webrtc::ExternalRenderer* callback; }; -// statically mirror webrtc.org ViECapture API -// these are called via MediaManager->MediaEngineRemoteVideoSource -// on the MediaManager thread -int NumberOfCapabilities(CaptureEngine aCapEngine, - const char* deviceUniqueIdUTF8); -int GetCaptureCapability(CaptureEngine aCapEngine, - const char* unique_idUTF8, - const unsigned int capability_number, - webrtc::CaptureCapability& capability); -int NumberOfCaptureDevices(CaptureEngine aCapEngine); -int GetCaptureDevice(CaptureEngine aCapEngine, - unsigned int list_number, char* device_nameUTF8, - const unsigned int device_nameUTF8Length, - char* unique_idUTF8, - const unsigned int unique_idUTF8Length); -int AllocateCaptureDevice(CaptureEngine aCapEngine, - const char* unique_idUTF8, - const unsigned int unique_idUTF8Length, - int& capture_id); -int ReleaseCaptureDevice(CaptureEngine aCapEngine, - const int capture_id); -int StartCapture(CaptureEngine aCapEngine, - const int capture_id, webrtc::CaptureCapability& capability, - webrtc::ExternalRenderer* func); -int StopCapture(CaptureEngine aCapEngine, const int capture_id); +// Forward declaration so we can work with pointers to it. +class CamerasChild; +// Helper class in impl that we friend. +template class LockAndDispatch; + +// We emulate the sync webrtc.org API with the help of singleton +// CamerasSingleton, which manages a pointer to an IPC object, a thread +// where IPC operations should run on, and a mutex. +// The static function Cameras() will use that Singleton to set up, +// if needed, both the thread and the associated IPC objects and return +// a pointer to the IPC object. Users can then do IPC calls on that object +// after dispatching them to aforementioned thread. + +// 2 Threads are involved in this code: +// - the MediaManager thread, which will call the (static, sync API) functions +// through MediaEngineRemoteVideoSource +// - the Cameras IPC thread, which will be doing our IPC to the parent process +// via PBackground + +// Our main complication is that we emulate a sync API while (having to do) +// async messaging. We dispatch the messages to another thread to send them +// async and hold a Monitor to wait for the result to be asynchronously received +// again. The requirement for async messaging originates on the parent side: +// it's not reasonable to block all PBackground IPC there while waiting for +// something like device enumeration to complete. + +class CamerasSingleton { +public: + CamerasSingleton(); + ~CamerasSingleton(); + + static OffTheBooksMutex& Mutex() { + return GetInstance().mCamerasMutex; + } + + static CamerasChild*& Child() { + Mutex().AssertCurrentThreadOwns(); + return GetInstance().mCameras; + } + + static nsCOMPtr& Thread() { + Mutex().AssertCurrentThreadOwns(); + return GetInstance().mCamerasChildThread; + } + +private: + static CamerasSingleton& GetInstance() { + static CamerasSingleton instance; + return instance; + } + + // Reinitializing CamerasChild will change the pointers below. + // We don't want this to happen in the middle of preparing IPC. + // We will be alive on destruction, so this needs to be off the books. + mozilla::OffTheBooksMutex mCamerasMutex; + + // This is owned by the IPC code, and the same code controls the lifetime. + // It will set and clear this pointer as appropriate in setup/teardown. + // We'd normally make this a WeakPtr but unfortunately the IPC code already + // uses the WeakPtr mixin in a protected base class of CamerasChild, and in + // any case the object becomes unusable as soon as IPC is tearing down, which + // will be before actual destruction. + CamerasChild* mCameras; + nsCOMPtr mCamerasChildThread; +}; + +// Get a pointer to a CamerasChild object we can use to do IPC with. +// This does everything needed to set up, including starting the IPC +// channel with PBackground, blocking until thats done, and starting the +// thread to do IPC on. This will fail if we're in shutdown. On success +// it will set up the CamerasSingleton. +CamerasChild* GetCamerasChild(); + +// Shut down the IPC channel and everything associated, like WebRTC. +// This is a static call because the CamerasChild object may not even +// be alive when we're called. void Shutdown(void); +// Obtain the CamerasChild object (if possible, i.e. not shutting down), +// and maintain a grip on the object for the duration of the call. +template +int GetChildAndCall(MEM_FUN&& f, ARGS&&... args) +{ + OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); + CamerasChild* child = GetCamerasChild(); + if (child) { + return (child->*f)(mozilla::Forward(args)...); + } else { + return -1; + } +} + class CamerasChild final : public PCamerasChild { friend class mozilla::ipc::BackgroundChildImpl; + template friend class mozilla::camera::LockAndDispatch; public: // We are owned by the PBackground thread only. CamerasSingleton @@ -116,7 +183,8 @@ public: int AllocateCaptureDevice(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int unique_idUTF8Length, - int& capture_id); + int& capture_id, + const nsACString& aOrigin); int GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int capability_number, @@ -126,13 +194,9 @@ public: const unsigned int device_nameUTF8Length, char* unique_idUTF8, const unsigned int unique_idUTF8Length); - void Shutdown(); + void ShutdownAll(); webrtc::ExternalRenderer* Callback(CaptureEngine aCapEngine, int capture_id); - void AddCallback(const CaptureEngine aCapEngine, const int capture_id, - webrtc::ExternalRenderer* render); - void RemoveCallback(const CaptureEngine aCapEngine, const int capture_id); - private: CamerasChild(); @@ -141,6 +205,11 @@ private: // decidecated Cameras IPC/PBackground thread. bool DispatchToParent(nsIRunnable* aRunnable, MonitorAutoLock& aMonitor); + void AddCallback(const CaptureEngine aCapEngine, const int capture_id, + webrtc::ExternalRenderer* render); + void RemoveCallback(const CaptureEngine aCapEngine, const int capture_id); + void ShutdownParent(); + void ShutdownChild(); nsTArray mCallbacks; // Protects the callback arrays diff --git a/dom/media/systemservices/CamerasParent.cpp b/dom/media/systemservices/CamerasParent.cpp index 9bbc112ad3..d48c12938b 100644 --- a/dom/media/systemservices/CamerasParent.cpp +++ b/dom/media/systemservices/CamerasParent.cpp @@ -11,10 +11,14 @@ #include "mozilla/Assertions.h" #include "mozilla/unused.h" +#include "mozilla/Services.h" #include "mozilla/Logging.h" #include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/Preferences.h" +#include "nsIPermissionManager.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" +#include "nsNetUtil.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" @@ -640,39 +644,127 @@ CamerasParent::RecvGetCaptureDevice(const int& aCapEngine, return true; } +static nsresult +GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal) +{ + nsAutoCString originNoSuffix; + mozilla::OriginAttributes attrs; + if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs); + principal.forget(aPrincipal); + return NS_OK; +} + +// Find out whether the given origin has permission to use the +// camera. If the permission is not persistent, we'll make it +// a one-shot by removing the (session) permission. +static bool +HasCameraPermission(const nsCString& aOrigin) +{ + // Name used with nsIPermissionManager + static const char* cameraPermission = "camera"; + bool allowed = false; + bool permanent = false; + nsresult rv; + nsCOMPtr mgr = + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr ioServ(do_GetIOService()); + nsCOMPtr uri; + rv = ioServ->NewURI(aOrigin, nullptr, nullptr, getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv)) { + // Permanent permissions are only retrievable via principal, not uri + nsCOMPtr principal; + rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal)); + if (NS_SUCCEEDED(rv)) { + uint32_t video = nsIPermissionManager::UNKNOWN_ACTION; + rv = mgr->TestExactPermissionFromPrincipal(principal, + cameraPermission, + &video); + if (NS_SUCCEEDED(rv)) { + allowed = (video == nsIPermissionManager::ALLOW_ACTION); + // Was allowed, now see if this is a persistent permission + // or a session one. + if (allowed) { + rv = mgr->TestExactPermanentPermission(principal, + cameraPermission, + &video); + if (NS_SUCCEEDED(rv)) { + permanent = (video == nsIPermissionManager::ALLOW_ACTION); + } + } + } + // Session permissions are removed after one use. + if (allowed && !permanent) { + mgr->RemoveFromPrincipal(principal, cameraPermission); + } + } + } + } + return allowed; +} + bool CamerasParent::RecvAllocateCaptureDevice(const int& aCapEngine, - const nsCString& unique_id) + const nsCString& unique_id, + const nsCString& aOrigin) { - LOG((__PRETTY_FUNCTION__)); - + LOG(("%s: Verifying permissions for %s", __PRETTY_FUNCTION__, aOrigin.get())); RefPtr self(this); - RefPtr webrtc_runnable = - media::NewRunnableFrom([self, aCapEngine, unique_id]() -> nsresult { - int numdev = -1; - int error = -1; - if (self->EnsureInitialized(aCapEngine)) { - error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice( - unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev); + RefPtr mainthread_runnable = + media::NewRunnableFrom([self, aCapEngine, unique_id, aOrigin]() -> nsresult { + // Verify whether the claimed origin has received permission + // to use the camera, either persistently or this session (one shot). + bool allowed = HasCameraPermission(aOrigin); + if (!allowed) { + // Developer preference for turning off permission check. + if (Preferences::GetBool("media.navigator.permission.disabled", false) + || Preferences::GetBool("media.navigator.permission.fake")) { + allowed = true; + LOG(("No permission but checks are disabled or fake sources active")); + } else { + LOG(("No camera permission for this origin")); + } } - RefPtr ipc_runnable = - media::NewRunnableFrom([self, numdev, error]() -> nsresult { - if (self->IsShuttingDown()) { - return NS_ERROR_FAILURE; - } - if (error) { - Unused << self->SendReplyFailure(); - return NS_ERROR_FAILURE; - } else { - LOG(("Allocated device nr %d", numdev)); - Unused << self->SendReplyAllocateCaptureDevice(numdev); - return NS_OK; - } + // After retrieving the permission (or not) on the main thread, + // bounce to the WebRTC thread to allocate the device (or not), + // then bounce back to the IPC thread for the reply to content. + RefPtr webrtc_runnable = + media::NewRunnableFrom([self, allowed, aCapEngine, unique_id]() -> nsresult { + int numdev = -1; + int error = -1; + if (allowed && self->EnsureInitialized(aCapEngine)) { + error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice( + unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev); + } + RefPtr ipc_runnable = + media::NewRunnableFrom([self, numdev, error]() -> nsresult { + if (self->IsShuttingDown()) { + return NS_ERROR_FAILURE; + } + if (error) { + Unused << self->SendReplyFailure(); + return NS_ERROR_FAILURE; + } else { + LOG(("Allocated device nr %d", numdev)); + Unused << self->SendReplyAllocateCaptureDevice(numdev); + return NS_OK; + } + }); + self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); + return NS_OK; }); - self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); + self->DispatchToVideoCaptureThread(webrtc_runnable); return NS_OK; }); - DispatchToVideoCaptureThread(webrtc_runnable); + NS_DispatchToMainThread(mainthread_runnable); return true; } diff --git a/dom/media/systemservices/CamerasParent.h b/dom/media/systemservices/CamerasParent.h index e3c95e9d2c..3d60007038 100644 --- a/dom/media/systemservices/CamerasParent.h +++ b/dom/media/systemservices/CamerasParent.h @@ -86,7 +86,7 @@ public: static already_AddRefed Create(); // Messages received form the child. These run on the IPC/PBackground thread. - virtual bool RecvAllocateCaptureDevice(const int&, const nsCString&) override; + virtual bool RecvAllocateCaptureDevice(const int&, const nsCString&, const nsCString&) override; virtual bool RecvReleaseCaptureDevice(const int&, const int &) override; virtual bool RecvNumberOfCaptureDevices(const int&) override; virtual bool RecvNumberOfCapabilities(const int&, const nsCString&) override; diff --git a/dom/media/systemservices/LoadManager.h b/dom/media/systemservices/LoadManager.h index 2ede35dbb2..6918200284 100644 --- a/dom/media/systemservices/LoadManager.h +++ b/dom/media/systemservices/LoadManager.h @@ -33,15 +33,15 @@ public: NS_DECL_NSIOBSERVER // LoadNotificationCallback interface - virtual void LoadChanged(float aSystemLoad, float aProcessLoad) override; + void LoadChanged(float aSystemLoad, float aProcessLoad) override; // CpuOveruseObserver interface // Called as soon as an overuse is detected. - virtual void OveruseDetected() override; + void OveruseDetected() override; // Called periodically when the system is not overused any longer. - virtual void NormalUsage() override; + void NormalUsage() override; // CPULoadStateCallbackInvoker interface - virtual void AddObserver(webrtc::CPULoadStateObserver * aObserver) override; - virtual void RemoveObserver(webrtc::CPULoadStateObserver * aObserver) override; + void AddObserver(webrtc::CPULoadStateObserver * aObserver) override; + void RemoveObserver(webrtc::CPULoadStateObserver * aObserver) override; private: LoadManagerSingleton(int aLoadMeasurementInterval, diff --git a/dom/media/systemservices/PCameras.ipdl b/dom/media/systemservices/PCameras.ipdl index a71e054cb1..133e861a82 100644 --- a/dom/media/systemservices/PCameras.ipdl +++ b/dom/media/systemservices/PCameras.ipdl @@ -45,7 +45,7 @@ parent: async GetCaptureCapability(int engine, nsCString unique_idUTF8, int capability_number); async GetCaptureDevice(int engine, int num); - async AllocateCaptureDevice(int engine, nsCString unique_idUTF8); + async AllocateCaptureDevice(int engine, nsCString unique_idUTF8, nsCString origin); async ReleaseCaptureDevice(int engine, int numdev); async StartCapture(int engine, int numdev, CaptureCapability capability); async StopCapture(int engine, int numdev); diff --git a/dom/media/tests/mochitest/test_getUserMedia_constraints.html b/dom/media/tests/mochitest/test_getUserMedia_constraints.html index 9acb61a253..978733bf8a 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html +++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html @@ -70,7 +70,7 @@ var mustSupport = [ 'width', 'height', 'frameRate', 'facingMode', 'deviceId', // Yet to add: // 'aspectRatio', 'frameRate', 'volume', 'sampleRate', 'sampleSize', - // 'echoCancellation', 'latency', 'groupId' + // 'latency', 'groupId' // http://fluffy.github.io/w3c-screen-share/#screen-based-video-constraints // OBE by http://w3c.github.io/mediacapture-screen-share @@ -78,9 +78,19 @@ var mustSupport = [ // Experimental https://bugzilla.mozilla.org/show_bug.cgi?id=1131568#c3 'browserWindow', 'scrollWithPage', - 'viewportOffsetX', 'viewportOffsetY', 'viewportWidth', 'viewportHeight' + 'viewportOffsetX', 'viewportOffsetY', 'viewportWidth', 'viewportHeight', + + 'echoCancellation', 'mozNoiseSuppression', 'mozAutoGainControl' ]; +var mustFailWith = (msg, reason, constraint, f) => + f().then(() => ok(false, msg + " must fail"), e => { + is(e.name, reason, msg + " must fail: " + e.message); + if (constraint !== undefined) { + is(e.constraint, constraint, msg + " must fail w/correct constraint."); + } + }); + /** * Starts the test run by running through each constraint * test by verifying that the right resolution and rejection is fired. @@ -120,6 +130,11 @@ runTest(function() { .then(() => stream.getAudioTracks()[0].applyConstraints({ }))) .then(() => ok(true, "applyConstraints code exercised")) // TODO: Test outcome once fake devices support constraints (Bug 1088621) + .then(() => mustFailWith("applyConstraints fails on non-Gum tracks", + "OverconstrainedError", "", + () => (new AudioContext()) + .createMediaStreamDestination().stream + .getAudioTracks()[0].applyConstraints())) }); diff --git a/dom/media/webaudio/AudioNodeStream.cpp b/dom/media/webaudio/AudioNodeStream.cpp index d20a12c20b..0fa1e8ea3b 100644 --- a/dom/media/webaudio/AudioNodeStream.cpp +++ b/dom/media/webaudio/AudioNodeStream.cpp @@ -148,9 +148,9 @@ AudioNodeStream::SetStreamTimeParameter(uint32_t aIndex, AudioContext* aContext, uint32_t mIndex; }; - GraphImpl()->AppendMessage(new Message(this, aIndex, - aContext->DestinationStream(), - aStreamTime)); + GraphImpl()->AppendMessage(MakeUnique(this, aIndex, + aContext->DestinationStream(), + aStreamTime)); } void @@ -179,7 +179,7 @@ AudioNodeStream::SetDoubleParameter(uint32_t aIndex, double aValue) uint32_t mIndex; }; - GraphImpl()->AppendMessage(new Message(this, aIndex, aValue)); + GraphImpl()->AppendMessage(MakeUnique(this, aIndex, aValue)); } void @@ -200,7 +200,7 @@ AudioNodeStream::SetInt32Parameter(uint32_t aIndex, int32_t aValue) uint32_t mIndex; }; - GraphImpl()->AppendMessage(new Message(this, aIndex, aValue)); + GraphImpl()->AppendMessage(MakeUnique(this, aIndex, aValue)); } void @@ -226,7 +226,7 @@ AudioNodeStream::SendTimelineEvent(uint32_t aIndex, TrackRate mSampleRate; uint32_t mIndex; }; - GraphImpl()->AppendMessage(new Message(this, aIndex, aEvent)); + GraphImpl()->AppendMessage(MakeUnique(this, aIndex, aEvent)); } void @@ -247,7 +247,7 @@ AudioNodeStream::SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aVa uint32_t mIndex; }; - GraphImpl()->AppendMessage(new Message(this, aIndex, aValue)); + GraphImpl()->AppendMessage(MakeUnique(this, aIndex, aValue)); } void @@ -268,7 +268,7 @@ AudioNodeStream::SetBuffer(already_AddRefed&& RefPtr mBuffer; }; - GraphImpl()->AppendMessage(new Message(this, aBuffer)); + GraphImpl()->AppendMessage(MakeUnique(this, aBuffer)); } void @@ -290,7 +290,7 @@ AudioNodeStream::SetRawArrayData(nsTArray& aData) nsTArray mData; }; - GraphImpl()->AppendMessage(new Message(this, aData)); + GraphImpl()->AppendMessage(MakeUnique(this, aData)); } void @@ -321,9 +321,9 @@ AudioNodeStream::SetChannelMixingParameters(uint32_t aNumberOfChannels, ChannelInterpretation mChannelInterpretation; }; - GraphImpl()->AppendMessage(new Message(this, aNumberOfChannels, - aChannelCountMode, - aChannelInterpretation)); + GraphImpl()->AppendMessage(MakeUnique(this, aNumberOfChannels, + aChannelCountMode, + aChannelInterpretation)); } void @@ -342,7 +342,7 @@ AudioNodeStream::SetPassThrough(bool aPassThrough) bool mPassThrough; }; - GraphImpl()->AppendMessage(new Message(this, aPassThrough)); + GraphImpl()->AppendMessage(MakeUnique(this, aPassThrough)); } void @@ -400,7 +400,7 @@ void AudioNodeStream::AdvanceAndResume(StreamTime aAdvance) { mMainThreadCurrentTime += aAdvance; - GraphImpl()->AppendMessage(new AdvanceAndResumeMessage(this, aAdvance)); + GraphImpl()->AppendMessage(MakeUnique(this, aAdvance)); } void @@ -732,7 +732,7 @@ AudioNodeStream::ScheduleCheckForInactive() return; } - nsAutoPtr message(new CheckForInactiveMessage(this)); + auto message = MakeUnique(this); GraphImpl()->RunMessageAfterProcessing(Move(message)); } diff --git a/dom/media/webrtc/MediaEngine.h b/dom/media/webrtc/MediaEngine.h index 210e983f38..f7a3bc3125 100644 --- a/dom/media/webrtc/MediaEngine.h +++ b/dom/media/webrtc/MediaEngine.h @@ -121,12 +121,6 @@ public: const MediaEnginePrefs &aPrefs, const nsString& aDeviceId) = 0; - /* Change device configuration. */ - virtual nsresult Config(bool aEchoOn, uint32_t aEcho, - bool aAgcOn, uint32_t aAGC, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) = 0; - /* Returns true if a source represents a fake capture device and * false otherwise */ @@ -177,7 +171,8 @@ public: /* This call reserves but does not start the device. */ virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs, - const nsString& aDeviceId) = 0; + const nsString& aDeviceId, + const nsACString& aOrigin) = 0; virtual uint32_t GetBestFitnessDistance( const nsTArray& aConstraintSets, @@ -187,9 +182,21 @@ protected: // Only class' own members can be initialized in constructor initializer list. explicit MediaEngineSource(MediaEngineState aState) : mState(aState) +#ifdef DEBUG + , mOwningThread(PR_GetCurrentThread()) +#endif , mHasFakeTracks(false) {} + + void AssertIsOnOwningThread() + { + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + } + MediaEngineState mState; +#ifdef DEBUG + PRThread* mOwningThread; +#endif bool mHasFakeTracks; }; @@ -204,6 +211,14 @@ public: , mFPS(0) , mMinFPS(0) , mFreq(0) + , mAecOn(false) + , mAgcOn(false) + , mNoiseOn(false) + , mAec(0) + , mAgc(0) + , mNoise(0) + , mPlayoutDelay(0) + , mFullDuplex(false) {} int32_t mWidth; @@ -211,6 +226,14 @@ public: int32_t mFPS; int32_t mMinFPS; int32_t mFreq; // for test tones (fake:true) + bool mAecOn; + bool mAgcOn; + bool mNoiseOn; + int32_t mAec; + int32_t mAgc; + int32_t mNoise; + int32_t mPlayoutDelay; + bool mFullDuplex; // mWidth and/or mHeight may be zero (=adaptive default), so use functions. @@ -262,7 +285,8 @@ protected: /** * Audio source and friends. */ -class MediaEngineAudioSource : public MediaEngineSource +class MediaEngineAudioSource : public MediaEngineSource, + public AudioDataListenerInterface { public: virtual ~MediaEngineAudioSource() {} diff --git a/dom/media/webrtc/MediaEngineCameraVideoSource.h b/dom/media/webrtc/MediaEngineCameraVideoSource.h index 7fbfbd45f8..4461d2b2d5 100644 --- a/dom/media/webrtc/MediaEngineCameraVideoSource.h +++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h @@ -37,13 +37,6 @@ public: void GetName(nsAString& aName) override; void GetUUID(nsACString& aUUID) override; void SetDirectListeners(bool aHasListeners) override; - nsresult Config(bool aEchoOn, uint32_t aEcho, - bool aAgcOn, uint32_t aAGC, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) override - { - return NS_OK; - }; bool IsFake() override { diff --git a/dom/media/webrtc/MediaEngineDefault.cpp b/dom/media/webrtc/MediaEngineDefault.cpp index 6e129fcc3b..e9c25a505a 100644 --- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -87,7 +87,8 @@ MediaEngineDefaultVideoSource::GetBestFitnessDistance( nsresult MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs, - const nsString& aDeviceId) + const nsString& aDeviceId, + const nsACString& aOrigin) { if (mState != kReleased) { return NS_ERROR_FAILURE; @@ -398,7 +399,8 @@ MediaEngineDefaultAudioSource::GetBestFitnessDistance( nsresult MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs, - const nsString& aDeviceId) + const nsString& aDeviceId, + const nsACString& aOrigin) { if (mState != kReleased) { return NS_ERROR_FAILURE; diff --git a/dom/media/webrtc/MediaEngineDefault.h b/dom/media/webrtc/MediaEngineDefault.h index 8829796f04..cc3a25d339 100644 --- a/dom/media/webrtc/MediaEngineDefault.h +++ b/dom/media/webrtc/MediaEngineDefault.h @@ -45,7 +45,8 @@ public: nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs, - const nsString& aDeviceId) override; + const nsString& aDeviceId, + const nsACString& aOrigin) override; nsresult Deallocate() override; nsresult Start(SourceMediaStream*, TrackID) override; nsresult Stop(SourceMediaStream*, TrackID) override; @@ -53,10 +54,6 @@ public: const MediaEnginePrefs &aPrefs, const nsString& aDeviceId) override; void SetDirectListeners(bool aHasDirectListeners) override {}; - nsresult Config(bool aEchoOn, uint32_t aEcho, - bool aAgcOn, uint32_t aAGC, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) override { return NS_OK; }; void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream *aSource, TrackID aId, @@ -118,7 +115,8 @@ public: nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs, - const nsString& aDeviceId) override; + const nsString& aDeviceId, + const nsACString& aOrigin) override; nsresult Deallocate() override; nsresult Start(SourceMediaStream*, TrackID) override; nsresult Stop(SourceMediaStream*, TrackID) override; @@ -126,10 +124,6 @@ public: const MediaEnginePrefs &aPrefs, const nsString& aDeviceId) override; void SetDirectListeners(bool aHasDirectListeners) override {}; - nsresult Config(bool aEchoOn, uint32_t aEcho, - bool aAgcOn, uint32_t aAGC, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) override { return NS_OK; }; void AppendToSegment(AudioSegment& aSegment, TrackTicks aSamples); void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream *aSource, @@ -144,6 +138,14 @@ public: #endif } + void NotifyOutputData(MediaStreamGraph* aGraph, + AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override + {} + void NotifyInputData(MediaStreamGraph* aGraph, + const AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override + {} bool IsFake() override { return true; } diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp index c527ac03a2..54a3710f09 100644 --- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -17,6 +17,12 @@ extern mozilla::LogModule* GetMediaManagerLog(); namespace mozilla { +// These need a definition somewhere because template +// code is allowed to take their address, and they aren't +// guaranteed to have one without this. +const unsigned int MediaEngineSource::kMaxDeviceNameLength; +const unsigned int MediaEngineSource::kMaxUniqueIdLength;; + using dom::ConstrainLongRange; NS_IMPL_ISUPPORTS0(MediaEngineRemoteVideoSource) @@ -38,10 +44,11 @@ MediaEngineRemoteVideoSource::Init() LOG((__PRETTY_FUNCTION__)); char deviceName[kMaxDeviceNameLength]; char uniqueId[kMaxUniqueIdLength]; - if (mozilla::camera::GetCaptureDevice(mCapEngine, - mCaptureIndex, - deviceName, kMaxDeviceNameLength, - uniqueId, kMaxUniqueIdLength)) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureDevice, + mCapEngine, mCaptureIndex, + deviceName, kMaxDeviceNameLength, + uniqueId, kMaxUniqueIdLength)) { LOG(("Error initializing RemoteVideoSource (GetCaptureDevice)")); return; } @@ -93,9 +100,11 @@ MediaEngineRemoteVideoSource::Shutdown() nsresult MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, - const nsString& aDeviceId) + const nsString& aDeviceId, + const nsACString& aOrigin) { LOG((__PRETTY_FUNCTION__)); + AssertIsOnOwningThread(); if (!mInitDone) { LOG(("Init not done")); @@ -110,13 +119,14 @@ MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstr return NS_ERROR_UNEXPECTED; } - if (mozilla::camera::AllocateCaptureDevice(mCapEngine, - GetUUID().get(), - kMaxUniqueIdLength, mCaptureIndex)) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::AllocateCaptureDevice, + mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex, aOrigin)) { return NS_ERROR_FAILURE; } mState = kAllocated; - LOG(("Video device %d allocated", mCaptureIndex)); + LOG(("Video device %d allocated for %s", mCaptureIndex, + PromiseFlatCString(aOrigin).get())); } else if (MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) { MonitorAutoLock lock(mMonitor); if (mSources.IsEmpty()) { @@ -135,6 +145,7 @@ nsresult MediaEngineRemoteVideoSource::Deallocate() { LOG((__PRETTY_FUNCTION__)); + AssertIsOnOwningThread(); --mNrAllocations; MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited"); @@ -143,7 +154,9 @@ MediaEngineRemoteVideoSource::Deallocate() if (mState != kStopped && mState != kAllocated) { return NS_ERROR_FAILURE; } - mozilla::camera::ReleaseCaptureDevice(mCapEngine, mCaptureIndex); + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::ReleaseCaptureDevice, + mCapEngine, mCaptureIndex); mState = kReleased; LOG(("Video device %d deallocated", mCaptureIndex)); } else { @@ -156,6 +169,7 @@ nsresult MediaEngineRemoteVideoSource::Start(SourceMediaStream* aStream, TrackID aID) { LOG((__PRETTY_FUNCTION__)); + AssertIsOnOwningThread(); if (!mInitDone || !aStream) { LOG(("No stream or init not done")); return NS_ERROR_FAILURE; @@ -176,8 +190,9 @@ MediaEngineRemoteVideoSource::Start(SourceMediaStream* aStream, TrackID aID) mState = kStarted; mTrackID = aID; - if (mozilla::camera::StartCapture(mCapEngine, - mCaptureIndex, mCapability, this)) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StartCapture, + mCapEngine, mCaptureIndex, mCapability, this)) { LOG(("StartCapture failed")); return NS_ERROR_FAILURE; } @@ -190,6 +205,7 @@ MediaEngineRemoteVideoSource::Stop(mozilla::SourceMediaStream* aSource, mozilla::TrackID aID) { LOG((__PRETTY_FUNCTION__)); + AssertIsOnOwningThread(); { MonitorAutoLock lock(mMonitor); @@ -213,7 +229,9 @@ MediaEngineRemoteVideoSource::Stop(mozilla::SourceMediaStream* aSource, mImage = nullptr; } - mozilla::camera::StopCapture(mCapEngine, mCaptureIndex); + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StopCapture, + mCapEngine, mCaptureIndex); return NS_OK; } @@ -223,6 +241,7 @@ MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstra const MediaEnginePrefs& aPrefs, const nsString& aDeviceId) { + AssertIsOnOwningThread(); if (!mInitDone) { LOG(("Init not done")); return NS_ERROR_FAILURE; @@ -234,9 +253,12 @@ MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstra return NS_OK; } - mozilla::camera::StopCapture(mCapEngine, mCaptureIndex); - if (mozilla::camera::StartCapture(mCapEngine, - mCaptureIndex, mCapability, this)) { + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StopCapture, + mCapEngine, mCaptureIndex); + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StartCapture, + mCapEngine, mCaptureIndex, mCapability, this)) { LOG(("StartCapture failed")); return NS_ERROR_FAILURE; } @@ -349,7 +371,10 @@ MediaEngineRemoteVideoSource::DeliverFrame(unsigned char* buffer, size_t MediaEngineRemoteVideoSource::NumCapabilities() { - int num = mozilla::camera::NumberOfCapabilities(mCapEngine, GetUUID().get()); + int num = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::NumberOfCapabilities, + mCapEngine, + GetUUID().get()); if (num > 0) { return num; } @@ -400,6 +425,7 @@ MediaEngineRemoteVideoSource::ChooseCapability(const MediaTrackConstraints &aCon const MediaEnginePrefs &aPrefs, const nsString& aDeviceId) { + AssertIsOnOwningThread(); switch(mMediaSource) { case dom::MediaSourceEnum::Screen: @@ -427,10 +453,12 @@ MediaEngineRemoteVideoSource::GetCapability(size_t aIndex, if (!mHardcodedCapabilities.IsEmpty()) { MediaEngineCameraVideoSource::GetCapability(aIndex, aOut); } - mozilla::camera::GetCaptureCapability(mCapEngine, - GetUUID().get(), - aIndex, - aOut); + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureCapability, + mCapEngine, + GetUUID().get(), + aIndex, + aOut); } void MediaEngineRemoteVideoSource::Refresh(int aIndex) { @@ -440,10 +468,11 @@ void MediaEngineRemoteVideoSource::Refresh(int aIndex) { char deviceName[kMaxDeviceNameLength]; char uniqueId[kMaxUniqueIdLength]; - if (mozilla::camera::GetCaptureDevice(mCapEngine, - aIndex, - deviceName, sizeof(deviceName), - uniqueId, sizeof(uniqueId))) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureDevice, + mCapEngine, aIndex, + deviceName, sizeof(deviceName), + uniqueId, sizeof(uniqueId))) { return; } diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.h b/dom/media/webrtc/MediaEngineRemoteVideoSource.h index 766fc8a63f..a0f5a9b6fa 100644 --- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h @@ -73,7 +73,8 @@ public: nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, - const nsString& aDeviceId) override; + const nsString& aDeviceId, + const nsACString& aOrigin) override; nsresult Deallocate() override;; nsresult Start(SourceMediaStream*, TrackID) override; nsresult Stop(SourceMediaStream*, TrackID) override; diff --git a/dom/media/webrtc/MediaEngineTabVideoSource.cpp b/dom/media/webrtc/MediaEngineTabVideoSource.cpp index 13a608e465..d31e9f8cd8 100644 --- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp @@ -129,7 +129,8 @@ MediaEngineTabVideoSource::GetUUID(nsACString_internal& aUuid) nsresult MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, - const nsString& aDeviceId) + const nsString& aDeviceId, + const nsACString& aOrigin) { // windowId is not a proper constraint, so just read it. // It has no well-defined behavior in advanced, so ignore it there. @@ -314,12 +315,6 @@ MediaEngineTabVideoSource::Stop(mozilla::SourceMediaStream*, mozilla::TrackID) return NS_OK; } -nsresult -MediaEngineTabVideoSource::Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) -{ - return NS_OK; -} - bool MediaEngineTabVideoSource::IsFake() { diff --git a/dom/media/webrtc/MediaEngineTabVideoSource.h b/dom/media/webrtc/MediaEngineTabVideoSource.h index 8c120d3858..b9ad22de8d 100644 --- a/dom/media/webrtc/MediaEngineTabVideoSource.h +++ b/dom/media/webrtc/MediaEngineTabVideoSource.h @@ -23,7 +23,8 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList void GetUUID(nsACString_internal&) override; nsresult Allocate(const dom::MediaTrackConstraints &, const mozilla::MediaEnginePrefs&, - const nsString& aDeviceId) override; + const nsString& aDeviceId, + const nsACString& aOrigin) override; nsresult Deallocate() override; nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) override; void SetDirectListeners(bool aHasDirectListeners) override {}; @@ -32,7 +33,6 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList nsresult Restart(const dom::MediaTrackConstraints& aConstraints, const mozilla::MediaEnginePrefs& aPrefs, const nsString& aDeviceId) override; - nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) override; bool IsFake() override; dom::MediaSourceEnum GetMediaSource() const override { return dom::MediaSourceEnum::Browser; diff --git a/dom/media/webrtc/MediaEngineWebRTC.cpp b/dom/media/webrtc/MediaEngineWebRTC.cpp index 23040d2182..5de97a9b1b 100644 --- a/dom/media/webrtc/MediaEngineWebRTC.cpp +++ b/dom/media/webrtc/MediaEngineWebRTC.cpp @@ -44,10 +44,18 @@ GetUserMediaLog() namespace mozilla { +// statics from AudioInputCubeb +nsTArray* AudioInputCubeb::mDeviceIndexes; +nsTArray* AudioInputCubeb::mDeviceNames; +cubeb_device_collection* AudioInputCubeb::mDevices = nullptr; +bool AudioInputCubeb::mAnyInUse = false; + MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs) : mMutex("mozilla::MediaEngineWebRTC"), mVoiceEngine(nullptr), - mAudioEngineInit(false) + mAudioInput(nullptr), + mAudioEngineInit(false), + mFullDuplex(aPrefs.mFullDuplex) { #ifndef MOZ_B2G_CAMERA nsCOMPtr compMgr; @@ -161,7 +169,9 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource, * mVideoSources must be updated. */ int num; - num = mozilla::camera::NumberOfCaptureDevices(capEngine); + num = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::NumberOfCaptureDevices, + capEngine); if (num <= 0) { return; } @@ -175,11 +185,12 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource, uniqueId[0] = '\0'; int error; - error = mozilla::camera::GetCaptureDevice(capEngine, - i, deviceName, - sizeof(deviceName), uniqueId, - sizeof(uniqueId)); - + error = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureDevice, + capEngine, + i, deviceName, + sizeof(deviceName), uniqueId, + sizeof(uniqueId)); if (error) { LOG(("camera:GetCaptureDevice: Failed %d", error )); continue; @@ -188,13 +199,17 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource, LOG((" Capture Device Index %d, Name %s", i, deviceName)); webrtc::CaptureCapability cap; - int numCaps = mozilla::camera::NumberOfCapabilities(capEngine, - uniqueId); + int numCaps = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::NumberOfCapabilities, + capEngine, + uniqueId); LOG(("Number of Capabilities %d", numCaps)); for (int j = 0; j < numCaps; j++) { - if (mozilla::camera::GetCaptureCapability(capEngine, - uniqueId, - j, cap ) != 0 ) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureCapability, + capEngine, + uniqueId, + j, cap) != 0) { break; } LOG(("type=%d width=%d height=%d maxFPS=%d", @@ -232,7 +247,6 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource, nsTArray >* aASources) { ScopedCustomReleasePtr ptrVoEBase; - ScopedCustomReleasePtr ptrVoEHw; // We spawn threads to handle gUM runnables, so we must protect the member vars MutexAutoLock lock(mMutex); @@ -276,13 +290,17 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource, mAudioEngineInit = true; } - ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine); - if (!ptrVoEHw) { - return; + if (!mAudioInput) { + if (mFullDuplex) { + // The platform_supports_full_duplex. + mAudioInput = new mozilla::AudioInputCubeb(mVoiceEngine); + } else { + mAudioInput = new mozilla::AudioInputWebRTC(mVoiceEngine); + } } int nDevices = 0; - ptrVoEHw->GetNumOfRecordingDevices(nDevices); + mAudioInput->GetNumOfRecordingDevices(nDevices); int i; #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) i = 0; // Bug 1037025 - let the OS handle defaulting for now on android/b2g @@ -298,17 +316,16 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource, deviceName[0] = '\0'; uniqueId[0] = '\0'; - int error = ptrVoEHw->GetRecordingDeviceName(i, deviceName, uniqueId); + int error = mAudioInput->GetRecordingDeviceName(i, deviceName, uniqueId); if (error) { - LOG((" VoEHardware:GetRecordingDeviceName: Failed %d", - ptrVoEBase->LastError() )); + LOG((" VoEHardware:GetRecordingDeviceName: Failed %d", error)); continue; } if (uniqueId[0] == '\0') { // Mac and Linux don't set uniqueId! MOZ_ASSERT(sizeof(deviceName) == sizeof(uniqueId)); // total paranoia - strcpy(uniqueId,deviceName); // safe given assert and initialization/error-check + strcpy(uniqueId, deviceName); // safe given assert and initialization/error-check } RefPtr aSource; @@ -317,8 +334,17 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource, // We've already seen this device, just append. aASources->AppendElement(aSource.get()); } else { - aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, i, - deviceName, uniqueId); + AudioInput* audioinput = mAudioInput; + if (mFullDuplex) { + // The platform_supports_full_duplex. + + // For cubeb, it has state (the selected ID) + // XXX just use the uniqueID for cubeb and support it everywhere, and get rid of this + // XXX Small window where the device list/index could change! + audioinput = new mozilla::AudioInputCubeb(mVoiceEngine, i); + } + aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, audioinput, + i, deviceName, uniqueId); mAudioSources.Put(uuid, aSource); // Hashtable takes ownership. aASources->AppendElement(aSource); } @@ -357,6 +383,7 @@ MediaEngineWebRTC::Shutdown() mVoiceEngine = nullptr; mozilla::camera::Shutdown(); + AudioInputCubeb::CleanupGlobalData(); if (mThread) { mThread->Shutdown(); diff --git a/dom/media/webrtc/MediaEngineWebRTC.h b/dom/media/webrtc/MediaEngineWebRTC.h index e30cdf39da..ffc603012f 100644 --- a/dom/media/webrtc/MediaEngineWebRTC.h +++ b/dom/media/webrtc/MediaEngineWebRTC.h @@ -7,12 +7,14 @@ #include "prcvar.h" #include "prthread.h" +#include "prprf.h" #include "nsIThread.h" #include "nsIRunnable.h" #include "mozilla/dom/File.h" #include "mozilla/Mutex.h" #include "mozilla/Monitor.h" +#include "mozilla/UniquePtr.h" #include "nsCOMPtr.h" #include "nsThreadUtils.h" #include "DOMMediaStream.h" @@ -26,6 +28,9 @@ #include "AudioSegment.h" #include "StreamBuffer.h" #include "MediaStreamGraph.h" +#include "cubeb/cubeb.h" +#include "CubebUtils.h" +#include "AudioPacketizer.h" #include "MediaEngineWrapper.h" #include "mozilla/dom/MediaStreamTrackBinding.h" @@ -68,7 +73,8 @@ public: void GetUUID(nsACString& aUUID) override; nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, - const nsString& aDeviceId) override + const nsString& aDeviceId, + const nsACString& aOrigin) override { // Nothing to do here, everything is managed in MediaManager.cpp return NS_OK; @@ -89,12 +95,14 @@ public: const nsString& aDeviceId) override; void SetDirectListeners(bool aDirect) override {} - nsresult Config(bool aEchoOn, uint32_t aEcho, bool aAgcOn, - uint32_t aAGC, bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) override - { - return NS_OK; - } + void NotifyOutputData(MediaStreamGraph* aGraph, + AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override + {} + void NotifyInputData(MediaStreamGraph* aGraph, + const AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override + {} void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream* aSource, TrackID aID, StreamTime aDesiredTime) override {} @@ -119,6 +127,283 @@ protected: nsCString mUUID; }; +// Small subset of VoEHardware +class AudioInput +{ +public: + explicit AudioInput(webrtc::VoiceEngine* aVoiceEngine) : mVoiceEngine(aVoiceEngine) {}; + // Threadsafe because it's referenced from an MicrophoneSource, which can + // had references to it on other threads. + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioInput) + + virtual int GetNumOfRecordingDevices(int& aDevices) = 0; + virtual int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128], + char aStrGuidUTF8[128]) = 0; + virtual int GetRecordingDeviceStatus(bool& aIsAvailable) = 0; + virtual void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener) = 0; + virtual void StopRecording(SourceMediaStream *aStream) = 0; + virtual int SetRecordingDevice(int aIndex) = 0; + +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AudioInput() {} + + webrtc::VoiceEngine* mVoiceEngine; +}; + +class AudioInputCubeb final : public AudioInput +{ +public: + explicit AudioInputCubeb(webrtc::VoiceEngine* aVoiceEngine, int aIndex = 0) : + AudioInput(aVoiceEngine), mSelectedDevice(aIndex), mInUseCount(0) + { + if (!mDeviceIndexes) { + mDeviceIndexes = new nsTArray; + mDeviceNames = new nsTArray; + } + } + + static void CleanupGlobalData() + { + if (mDevices) { + // This doesn't require anything more than support for free() + cubeb_device_collection_destroy(mDevices); + mDevices = nullptr; + } + delete mDeviceIndexes; + mDeviceIndexes = nullptr; + delete mDeviceNames; + mDeviceNames = nullptr; + } + + int GetNumOfRecordingDevices(int& aDevices) + { + UpdateDeviceList(); + aDevices = mDeviceIndexes->Length(); + return 0; + } + + int32_t DeviceIndex(int aIndex) + { + if (aIndex == -1) { + aIndex = 0; // -1 = system default + } + if (aIndex >= (int) mDeviceIndexes->Length()) { + return -1; + } + // Note: if the device is gone, this will be -1 + return (*mDeviceIndexes)[aIndex]; // translate to mDevices index + } + + int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128], + char aStrGuidUTF8[128]) + { + int32_t devindex = DeviceIndex(aIndex); + if (!mDevices || devindex < 0) { + return 1; + } + PR_snprintf(aStrNameUTF8, 128, "%s%s", aIndex == -1 ? "default: " : "", + mDevices->device[devindex]->friendly_name); + aStrGuidUTF8[0] = '\0'; + return 0; + } + + int GetRecordingDeviceStatus(bool& aIsAvailable) + { + // With cubeb, we only expose devices of type CUBEB_DEVICE_TYPE_INPUT, + // so unless it was removed, say it's available + aIsAvailable = true; + return 0; + } + + void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener) + { + MOZ_ASSERT(mDevices); + + if (mInUseCount == 0) { + ScopedCustomReleasePtr ptrVoERender; + ptrVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine); + if (ptrVoERender) { + ptrVoERender->SetExternalRecordingStatus(true); + } + mAnyInUse = true; + } + mInUseCount++; + // Always tell the stream we're using it for input + aStream->OpenAudioInput(mDevices->device[mSelectedDevice]->devid, aListener); + } + + void StopRecording(SourceMediaStream *aStream) + { + aStream->CloseAudioInput(); + if (--mInUseCount == 0) { + mAnyInUse = false; + } + } + + int SetRecordingDevice(int aIndex) + { + int32_t devindex = DeviceIndex(aIndex); + if (!mDevices || devindex < 0) { + return 1; + } + mSelectedDevice = devindex; + return 0; + } + +protected: + ~AudioInputCubeb() { + MOZ_RELEASE_ASSERT(mInUseCount == 0); + } + +private: + // It would be better to watch for device-change notifications + void UpdateDeviceList() + { + cubeb_device_collection *devices = nullptr; + + if (CUBEB_OK != cubeb_enumerate_devices(CubebUtils::GetCubebContext(), + CUBEB_DEVICE_TYPE_INPUT, + &devices)) { + return; + } + + for (auto& device_index : (*mDeviceIndexes)) { + device_index = -1; // unmapped + } + // We keep all the device names, but wipe the mappings and rebuild them + + // Calculate translation from existing mDevices to new devices. Note we + // never end up with less devices than before, since people have + // stashed indexes. + // For some reason the "fake" device for automation is marked as DISABLED, + // so white-list it. + for (uint32_t i = 0; i < devices->count; i++) { + if (devices->device[i]->type == CUBEB_DEVICE_TYPE_INPUT && // paranoia + (devices->device[i]->state == CUBEB_DEVICE_STATE_ENABLED || + devices->device[i]->state == CUBEB_DEVICE_STATE_UNPLUGGED || + (devices->device[i]->state == CUBEB_DEVICE_STATE_DISABLED && + strcmp(devices->device[i]->friendly_name, "Sine source at 440 Hz") == 0))) + { + auto j = mDeviceNames->IndexOf(devices->device[i]->device_id); + if (j != nsTArray::NoIndex) { + // match! update the mapping + (*mDeviceIndexes)[j] = i; + } else { + // new device, add to the array + mDeviceIndexes->AppendElement(i); + mDeviceNames->AppendElement(devices->device[i]->device_id); + } + } + } + // swap state + if (mDevices) { + cubeb_device_collection_destroy(mDevices); + } + mDevices = devices; + } + + // We have an array, which consists of indexes to the current mDevices + // list. This is updated on mDevices updates. Many devices in mDevices + // won't be included in the array (wrong type, etc), or if a device is + // removed it will map to -1 (and opens of this device will need to check + // for this - and be careful of threading access. The mappings need to + // updated on each re-enumeration. + int mSelectedDevice; + uint32_t mInUseCount; + + // pointers to avoid static constructors + static nsTArray* mDeviceIndexes; + static nsTArray* mDeviceNames; + static cubeb_device_collection *mDevices; + static bool mAnyInUse; +}; + +class AudioInputWebRTC final : public AudioInput +{ +public: + explicit AudioInputWebRTC(webrtc::VoiceEngine* aVoiceEngine) : AudioInput(aVoiceEngine) {} + + int GetNumOfRecordingDevices(int& aDevices) + { + ScopedCustomReleasePtr ptrVoEHw; + ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine); + if (!ptrVoEHw) { + return 1; + } + return ptrVoEHw->GetNumOfRecordingDevices(aDevices); + } + + int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128], + char aStrGuidUTF8[128]) + { + ScopedCustomReleasePtr ptrVoEHw; + ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine); + if (!ptrVoEHw) { + return 1; + } + return ptrVoEHw->GetRecordingDeviceName(aIndex, aStrNameUTF8, + aStrGuidUTF8); + } + + int GetRecordingDeviceStatus(bool& aIsAvailable) + { + ScopedCustomReleasePtr ptrVoEHw; + ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine); + if (!ptrVoEHw) { + return 1; + } + ptrVoEHw->GetRecordingDeviceStatus(aIsAvailable); + return 0; + } + + void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener) {} + void StopRecording(SourceMediaStream *aStream) {} + + int SetRecordingDevice(int aIndex) + { + ScopedCustomReleasePtr ptrVoEHw; + ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine); + if (!ptrVoEHw) { + return 1; + } + return ptrVoEHw->SetRecordingDevice(aIndex); + } + +protected: + // Protected destructor, to discourage deletion outside of Release(): + ~AudioInputWebRTC() {} +}; + +class WebRTCAudioDataListener : public AudioDataListener +{ +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~WebRTCAudioDataListener() {} + +public: + explicit WebRTCAudioDataListener(MediaEngineAudioSource* aAudioSource) : + mAudioSource(aAudioSource) + {} + + // AudioDataListenerInterface methods + virtual void NotifyOutputData(MediaStreamGraph* aGraph, + AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override + { + mAudioSource->NotifyOutputData(aGraph, aBuffer, aFrames, aRate, aChannels); + } + virtual void NotifyInputData(MediaStreamGraph* aGraph, + const AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override + { + mAudioSource->NotifyInputData(aGraph, aBuffer, aFrames, aRate, aChannels); + } + +private: + RefPtr mAudioSource; +}; + class MediaEngineWebRTCMicrophoneSource : public MediaEngineAudioSource, public webrtc::VoEMediaProcess, private MediaConstraintsHelper @@ -126,11 +411,13 @@ class MediaEngineWebRTCMicrophoneSource : public MediaEngineAudioSource, public: MediaEngineWebRTCMicrophoneSource(nsIThread* aThread, webrtc::VoiceEngine* aVoiceEnginePtr, + mozilla::AudioInput* aAudioInput, int aIndex, const char* name, const char* uuid) : MediaEngineAudioSource(kReleased) , mVoiceEngine(aVoiceEnginePtr) + , mAudioInput(aAudioInput) , mMonitor("WebRTCMic.Monitor") , mThread(aThread) , mCapIndex(aIndex) @@ -144,10 +431,13 @@ public: , mAGC(webrtc::kAgcDefault) , mNoiseSuppress(webrtc::kNsDefault) , mPlayoutDelay(0) - , mNullTransport(nullptr) { + , mNullTransport(nullptr) + , mInputBufferLen(0) { MOZ_ASSERT(aVoiceEnginePtr); + MOZ_ASSERT(aAudioInput); mDeviceName.Assign(NS_ConvertUTF8toUTF16(name)); mDeviceUUID.Assign(uuid); + mListener = new mozilla::WebRTCAudioDataListener(this); Init(); } @@ -156,7 +446,8 @@ public: nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, - const nsString& aDeviceId) override; + const nsString& aDeviceId, + const nsACString& aOrigin) override; nsresult Deallocate() override; nsresult Start(SourceMediaStream* aStream, TrackID aID) override; nsresult Stop(SourceMediaStream* aSource, TrackID aID) override; @@ -164,16 +455,20 @@ public: const MediaEnginePrefs &aPrefs, const nsString& aDeviceId) override; void SetDirectListeners(bool aHasDirectListeners) override {}; - nsresult Config(bool aEchoOn, uint32_t aEcho, - bool aAgcOn, uint32_t aAGC, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) override; void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream* aSource, TrackID aId, StreamTime aDesiredTime) override; + // AudioDataListenerInterface methods + void NotifyOutputData(MediaStreamGraph* aGraph, + AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override; + void NotifyInputData(MediaStreamGraph* aGraph, + const AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) override; + bool IsFake() override { return false; } @@ -207,11 +502,16 @@ private: void Init(); webrtc::VoiceEngine* mVoiceEngine; + RefPtr mAudioInput; + RefPtr mListener; + ScopedCustomReleasePtr mVoEBase; ScopedCustomReleasePtr mVoERender; ScopedCustomReleasePtr mVoENetwork; ScopedCustomReleasePtr mVoEProcessing; + nsAutoPtr> mPacketizer; + // mMonitor protects mSources[] access/changes, and transitions of mState // from kStarted to kStopped (which are combined with EndTrack()). // mSources[] is accessed from webrtc threads. @@ -236,6 +536,10 @@ private: int32_t mPlayoutDelay; NullTransport *mNullTransport; + + // For full_duplex packetizer output + size_t mInputBufferLen; + UniquePtr mInputBuffer; }; class MediaEngineWebRTC : public MediaEngine @@ -265,8 +569,9 @@ private: // gUM runnables can e.g. Enumerate from multiple threads Mutex mMutex; webrtc::VoiceEngine* mVoiceEngine; + RefPtr mAudioInput; bool mAudioEngineInit; - + bool mFullDuplex; bool mHasTabVideoSource; // Store devices we've already seen in a hashtable for quick return. diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp index cacc0b3590..4f44acec89 100644 --- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp +++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp @@ -196,69 +196,6 @@ MediaEngineWebRTCMicrophoneSource::GetUUID(nsACString& aUUID) return; } -nsresult -MediaEngineWebRTCMicrophoneSource::Config(bool aEchoOn, uint32_t aEcho, - bool aAgcOn, uint32_t aAGC, - bool aNoiseOn, uint32_t aNoise, - int32_t aPlayoutDelay) -{ - LOG(("Audio config: aec: %d, agc: %d, noise: %d", - aEchoOn ? aEcho : -1, - aAgcOn ? aAGC : -1, - aNoiseOn ? aNoise : -1)); - - bool update_echo = (mEchoOn != aEchoOn); - bool update_agc = (mAgcOn != aAgcOn); - bool update_noise = (mNoiseOn != aNoiseOn); - mEchoOn = aEchoOn; - mAgcOn = aAgcOn; - mNoiseOn = aNoiseOn; - - if ((webrtc::EcModes) aEcho != webrtc::kEcUnchanged) { - if (mEchoCancel != (webrtc::EcModes) aEcho) { - update_echo = true; - mEchoCancel = (webrtc::EcModes) aEcho; - } - } - if ((webrtc::AgcModes) aAGC != webrtc::kAgcUnchanged) { - if (mAGC != (webrtc::AgcModes) aAGC) { - update_agc = true; - mAGC = (webrtc::AgcModes) aAGC; - } - } - if ((webrtc::NsModes) aNoise != webrtc::kNsUnchanged) { - if (mNoiseSuppress != (webrtc::NsModes) aNoise) { - update_noise = true; - mNoiseSuppress = (webrtc::NsModes) aNoise; - } - } - mPlayoutDelay = aPlayoutDelay; - - if (mInitDone) { - int error; - - if (update_echo && - 0 != (error = mVoEProcessing->SetEcStatus(mEchoOn, (webrtc::EcModes) aEcho))) { - LOG(("%s Error setting Echo Status: %d ",__FUNCTION__, error)); - // Overhead of capturing all the time is very low (<0.1% of an audio only call) - if (mEchoOn) { - if (0 != (error = mVoEProcessing->SetEcMetricsStatus(true))) { - LOG(("%s Error setting Echo Metrics: %d ",__FUNCTION__, error)); - } - } - } - if (update_agc && - 0 != (error = mVoEProcessing->SetAgcStatus(mAgcOn, (webrtc::AgcModes) aAGC))) { - LOG(("%s Error setting AGC Status: %d ",__FUNCTION__, error)); - } - if (update_noise && - 0 != (error = mVoEProcessing->SetNsStatus(mNoiseOn, (webrtc::NsModes) aNoise))) { - LOG(("%s Error setting NoiseSuppression Status: %d ",__FUNCTION__, error)); - } - } - return NS_OK; -} - // GetBestFitnessDistance returns the best distance the capture device can offer // as a whole, given an accumulated number of ConstraintSets. // Ideal values are considered in the first ConstraintSet only. @@ -283,12 +220,13 @@ uint32_t MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance( nsresult MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs, - const nsString& aDeviceId) + const nsString& aDeviceId, + const nsACString& aOrigin) { + AssertIsOnOwningThread(); if (mState == kReleased) { if (mInitDone) { - ScopedCustomReleasePtr ptrVoEHw(webrtc::VoEHardware::GetInterface(mVoiceEngine)); - if (!ptrVoEHw || ptrVoEHw->SetRecordingDevice(mCapIndex)) { + if (mAudioInput->SetRecordingDevice(mCapIndex)) { return NS_ERROR_FAILURE; } mState = kAllocated; @@ -306,12 +244,82 @@ MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aC } } ++mNrAllocations; + return Restart(aConstraints, aPrefs, aDeviceId); +} + +nsresult +MediaEngineWebRTCMicrophoneSource::Restart(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) +{ + FlattenedConstraints c(aConstraints); + + bool aec_on = c.mEchoCancellation.Get(aPrefs.mAecOn); + bool agc_on = c.mMozAutoGainControl.Get(aPrefs.mAgcOn); + bool noise_on = c.mMozNoiseSuppression.Get(aPrefs.mNoiseOn); + + LOG(("Audio config: aec: %d, agc: %d, noise: %d, delay: %d", + aec_on ? aPrefs.mAec : -1, + agc_on ? aPrefs.mAgc : -1, + noise_on ? aPrefs.mNoise : -1, + aPrefs.mPlayoutDelay)); + + bool update_echo = (mEchoOn != aec_on); + bool update_agc = (mAgcOn != agc_on); + bool update_noise = (mNoiseOn != noise_on); + mEchoOn = aec_on; + mAgcOn = agc_on; + mNoiseOn = noise_on; + + mPlayoutDelay = aPrefs.mPlayoutDelay; + if ((webrtc::EcModes) aPrefs.mAec != webrtc::kEcUnchanged) { + if (mEchoCancel != (webrtc::EcModes) aPrefs.mAec) { + update_echo = true; + mEchoCancel = (webrtc::EcModes) aPrefs.mAec; + } + } + if ((webrtc::AgcModes) aPrefs.mAgc != webrtc::kAgcUnchanged) { + if (mAGC != (webrtc::AgcModes) aPrefs.mAgc) { + update_agc = true; + mAGC = (webrtc::AgcModes) aPrefs.mAgc; + } + } + if ((webrtc::NsModes) aPrefs.mNoise != webrtc::kNsUnchanged) { + if (mNoiseSuppress != (webrtc::NsModes) aPrefs.mNoise) { + update_noise = true; + mNoiseSuppress = (webrtc::NsModes) aPrefs.mNoise; + } + } + + if (mInitDone) { + int error; + + if (update_echo && + 0 != (error = mVoEProcessing->SetEcStatus(mEchoOn, (webrtc::EcModes) aPrefs.mAec))) { + LOG(("%s Error setting Echo Status: %d ",__FUNCTION__, error)); + // Overhead of capturing all the time is very low (<0.1% of an audio only call) + if (mEchoOn) { + if (0 != (error = mVoEProcessing->SetEcMetricsStatus(true))) { + LOG(("%s Error setting Echo Metrics: %d ",__FUNCTION__, error)); + } + } + } + if (update_agc && + 0 != (error = mVoEProcessing->SetAgcStatus(mAgcOn, (webrtc::AgcModes) aPrefs.mAgc))) { + LOG(("%s Error setting AGC Status: %d ",__FUNCTION__, error)); + } + if (update_noise && + 0 != (error = mVoEProcessing->SetNsStatus(mNoiseOn, (webrtc::NsModes) aPrefs.mNoise))) { + LOG(("%s Error setting NoiseSuppression Status: %d ",__FUNCTION__, error)); + } + } return NS_OK; } nsresult MediaEngineWebRTCMicrophoneSource::Deallocate() { + AssertIsOnOwningThread(); --mNrAllocations; MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited"); if (mNrAllocations == 0) { @@ -332,6 +340,7 @@ nsresult MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream, TrackID aID) { + AssertIsOnOwningThread(); if (!mInitDone || !aStream) { return NS_ERROR_FAILURE; } @@ -350,6 +359,8 @@ MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream, if (mState == kStarted) { MOZ_ASSERT(aID == mTrackID); + // Make sure we're associated with this stream + mAudioInput->StartRecording(aStream, mListener); return NS_OK; } mState = kStarted; @@ -363,15 +374,13 @@ MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream, MOZ_ASSERT(gFarendObserver); gFarendObserver->Clear(); - // Configure audio processing in webrtc code - Config(mEchoOn, webrtc::kEcUnchanged, - mAgcOn, webrtc::kAgcUnchanged, - mNoiseOn, webrtc::kNsUnchanged, - mPlayoutDelay); - if (mVoEBase->StartReceive(mChannel)) { return NS_ERROR_FAILURE; } + + // Must be *before* StartSend() so it will notice we selected external input (full_duplex) + mAudioInput->StartRecording(aStream, mListener); + if (mVoEBase->StartSend(mChannel)) { return NS_ERROR_FAILURE; } @@ -385,6 +394,7 @@ MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream, nsresult MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID) { + AssertIsOnOwningThread(); { MonitorAutoLock lock(mMonitor); @@ -396,6 +406,7 @@ MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID) aSource->EndTrack(aID); if (!mSources.IsEmpty()) { + mAudioInput->StopRecording(aSource); return NS_OK; } if (mState != kStarted) { @@ -408,6 +419,8 @@ MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID) mState = kStopped; } + mAudioInput->StopRecording(aSource); + mVoERender->DeRegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel); if (mVoEBase->StopSend(mChannel)) { @@ -419,14 +432,6 @@ MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID) return NS_OK; } -nsresult -MediaEngineWebRTCMicrophoneSource::Restart(const dom::MediaTrackConstraints& aConstraints, - const MediaEnginePrefs &aPrefs, - const nsString& aDeviceId) -{ - return NS_OK; -} - void MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph, SourceMediaStream *aSource, @@ -437,6 +442,46 @@ MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph, LOG_FRAMES(("NotifyPull, desired = %ld", (int64_t) aDesiredTime)); } +void +MediaEngineWebRTCMicrophoneSource::NotifyOutputData(MediaStreamGraph* aGraph, + AudioDataValue* aBuffer, + size_t aFrames, + TrackRate aRate, + uint32_t aChannels) +{ +} + +// Called back on GraphDriver thread +void +MediaEngineWebRTCMicrophoneSource::NotifyInputData(MediaStreamGraph* aGraph, + const AudioDataValue* aBuffer, + size_t aFrames, + TrackRate aRate, + uint32_t aChannels) +{ + // This will call Process() with data coming out of the AEC/NS/AGC/etc chain + if (!mPacketizer || + mPacketizer->PacketSize() != aRate/100u || + mPacketizer->Channels() != aChannels) { + // It's ok to drop the audio still in the packetizer here. + mPacketizer = new AudioPacketizer(aRate/100, aChannels); + } + + mPacketizer->Input(aBuffer, static_cast(aFrames)); + + while (mPacketizer->PacketsAvailable()) { + uint32_t samplesPerPacket = mPacketizer->PacketSize() * + mPacketizer->Channels(); + if (mInputBufferLen < samplesPerPacket) { + mInputBuffer = MakeUnique(samplesPerPacket); + } + int16_t *packet = mInputBuffer.get(); + mPacketizer->Output(packet); + + mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0); + } +} + void MediaEngineWebRTCMicrophoneSource::Init() { @@ -471,8 +516,7 @@ MediaEngineWebRTCMicrophoneSource::Init() LOG(("%s: sampling rate %u", __FUNCTION__, mSampleFrequency)); // Check for availability. - ScopedCustomReleasePtr ptrVoEHw(webrtc::VoEHardware::GetInterface(mVoiceEngine)); - if (!ptrVoEHw || ptrVoEHw->SetRecordingDevice(mCapIndex)) { + if (mAudioInput->SetRecordingDevice(mCapIndex)) { return; } @@ -480,7 +524,7 @@ MediaEngineWebRTCMicrophoneSource::Init() // Because of the permission mechanism of B2G, we need to skip the status // check here. bool avail = false; - ptrVoEHw->GetRecordingDeviceStatus(avail); + mAudioInput->GetRecordingDeviceStatus(avail); if (!avail) { return; } @@ -555,6 +599,9 @@ MediaEngineWebRTCMicrophoneSource::Shutdown() mVoERender = nullptr; mVoEBase = nullptr; + mAudioInput = nullptr; + mListener = nullptr; // breaks a cycle, since the WebRTCAudioDataListener has a RefPtr to us + mState = kReleased; mInitDone = false; } @@ -635,6 +682,7 @@ MediaEngineWebRTCAudioCaptureSource::GetName(nsAString &aName) { aName.AssignLiteral("AudioCapture"); } + void MediaEngineWebRTCAudioCaptureSource::GetUUID(nsACString &aUUID) { @@ -661,6 +709,7 @@ nsresult MediaEngineWebRTCAudioCaptureSource::Start(SourceMediaStream *aMediaStream, TrackID aId) { + AssertIsOnOwningThread(); aMediaStream->AddTrack(aId, 0, new AudioSegment()); return NS_OK; } @@ -669,6 +718,7 @@ nsresult MediaEngineWebRTCAudioCaptureSource::Stop(SourceMediaStream *aMediaStream, TrackID aId) { + AssertIsOnOwningThread(); aMediaStream->EndAllTrackAndFinish(); return NS_OK; } @@ -690,4 +740,5 @@ MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance( // There is only one way of capturing audio for now, and it's always adequate. return 0; } + } diff --git a/dom/media/webrtc/MediaTrackConstraints.cpp b/dom/media/webrtc/MediaTrackConstraints.cpp index 6989f38b30..99e36d60fc 100644 --- a/dom/media/webrtc/MediaTrackConstraints.cpp +++ b/dom/media/webrtc/MediaTrackConstraints.cpp @@ -61,6 +61,28 @@ NormalizedConstraintSet::DoubleRange::DoubleRange( } } +NormalizedConstraintSet::BooleanRange::BooleanRange( + const dom::OwningBooleanOrConstrainBooleanParameters& aOther, bool advanced) +: Range(false, true) +{ + if (aOther.IsBoolean()) { + if (advanced) { + mMin = mMax = aOther.GetAsBoolean(); + } else { + mIdeal.Construct(aOther.GetAsBoolean()); + } + } else { + const ConstrainBooleanParameters& r = aOther.GetAsConstrainBooleanParameters(); + if (r.mIdeal.WasPassed()) { + mIdeal.Construct(r.mIdeal.Value()); + } + if (r.mExact.WasPassed()) { + mMin = r.mExact.Value(); + mMax = r.mExact.Value(); + } + } +} + FlattenedConstraints::FlattenedConstraints(const dom::MediaTrackConstraints& aOther) : NormalizedConstraintSet(aOther, false) { @@ -77,6 +99,15 @@ FlattenedConstraints::FlattenedConstraints(const dom::MediaTrackConstraints& aOt mHeight.Intersect(set.mHeight); mFrameRate.Intersect(set.mFrameRate); } + if (mEchoCancellation.Intersects(set.mEchoCancellation)) { + mEchoCancellation.Intersect(set.mEchoCancellation); + } + if (mMozNoiseSuppression.Intersects(set.mMozNoiseSuppression)) { + mMozNoiseSuppression.Intersect(set.mMozNoiseSuppression); + } + if (mMozAutoGainControl.Intersects(set.mMozAutoGainControl)) { + mMozAutoGainControl.Intersect(set.mMozAutoGainControl); + } } } } diff --git a/dom/media/webrtc/MediaTrackConstraints.h b/dom/media/webrtc/MediaTrackConstraints.h index d00ee3a722..ec26a032e7 100644 --- a/dom/media/webrtc/MediaTrackConstraints.h +++ b/dom/media/webrtc/MediaTrackConstraints.h @@ -72,10 +72,17 @@ struct NormalizedConstraintSet bool advanced); }; + struct BooleanRange : public Range + { + BooleanRange(const dom::OwningBooleanOrConstrainBooleanParameters& aOther, + bool advanced); + }; + // Do you need to add your constraint here? Only if your code uses flattening LongRange mWidth, mHeight; DoubleRange mFrameRate; LongRange mViewportOffsetX, mViewportOffsetY, mViewportWidth, mViewportHeight; + BooleanRange mEchoCancellation, mMozNoiseSuppression, mMozAutoGainControl; NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther, bool advanced) @@ -85,7 +92,10 @@ struct NormalizedConstraintSet , mViewportOffsetX(aOther.mViewportOffsetX, advanced) , mViewportOffsetY(aOther.mViewportOffsetY, advanced) , mViewportWidth(aOther.mViewportWidth, advanced) - , mViewportHeight(aOther.mViewportHeight, advanced) {} + , mViewportHeight(aOther.mViewportHeight, advanced) + , mEchoCancellation(aOther.mEchoCancellation, advanced) + , mMozNoiseSuppression(aOther.mMozNoiseSuppression, advanced) + , mMozAutoGainControl(aOther.mMozAutoGainControl, advanced) {} }; struct FlattenedConstraints : public NormalizedConstraintSet diff --git a/dom/media/webspeech/recognition/SpeechGrammar.h b/dom/media/webspeech/recognition/SpeechGrammar.h index 5acc41410e..8a6a14eacb 100644 --- a/dom/media/webspeech/recognition/SpeechGrammar.h +++ b/dom/media/webspeech/recognition/SpeechGrammar.h @@ -32,7 +32,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; static already_AddRefed Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); diff --git a/dom/media/webspeech/recognition/SpeechGrammarList.h b/dom/media/webspeech/recognition/SpeechGrammarList.h index 208f8d775e..dbe7117fee 100644 --- a/dom/media/webspeech/recognition/SpeechGrammarList.h +++ b/dom/media/webspeech/recognition/SpeechGrammarList.h @@ -37,7 +37,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; uint32_t Length() const; diff --git a/dom/media/webspeech/recognition/SpeechRecognition.h b/dom/media/webspeech/recognition/SpeechRecognition.h index 6fe4dc3bd9..d4605599f3 100644 --- a/dom/media/webspeech/recognition/SpeechRecognition.h +++ b/dom/media/webspeech/recognition/SpeechRecognition.h @@ -64,7 +64,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; static bool IsAuthorized(JSContext* aCx, JSObject* aGlobal); diff --git a/dom/media/webspeech/recognition/SpeechRecognitionAlternative.h b/dom/media/webspeech/recognition/SpeechRecognitionAlternative.h index efe5571ac1..790d8d28fa 100644 --- a/dom/media/webspeech/recognition/SpeechRecognitionAlternative.h +++ b/dom/media/webspeech/recognition/SpeechRecognitionAlternative.h @@ -31,7 +31,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; void GetTranscript(nsString& aRetVal) const; diff --git a/dom/media/webspeech/recognition/SpeechRecognitionResult.h b/dom/media/webspeech/recognition/SpeechRecognitionResult.h index 76c0b62a05..be0997bf6a 100644 --- a/dom/media/webspeech/recognition/SpeechRecognitionResult.h +++ b/dom/media/webspeech/recognition/SpeechRecognitionResult.h @@ -32,7 +32,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; uint32_t Length() const; diff --git a/dom/media/webspeech/recognition/SpeechRecognitionResultList.h b/dom/media/webspeech/recognition/SpeechRecognitionResultList.h index 85fdf81483..9725d06ce5 100644 --- a/dom/media/webspeech/recognition/SpeechRecognitionResultList.h +++ b/dom/media/webspeech/recognition/SpeechRecognitionResultList.h @@ -33,7 +33,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; uint32_t Length() const; diff --git a/dom/media/webspeech/recognition/SpeechStreamListener.h b/dom/media/webspeech/recognition/SpeechStreamListener.h index 55d51a4e97..7dd596118e 100644 --- a/dom/media/webspeech/recognition/SpeechStreamListener.h +++ b/dom/media/webspeech/recognition/SpeechStreamListener.h @@ -24,15 +24,15 @@ public: explicit SpeechStreamListener(SpeechRecognition* aRecognition); ~SpeechStreamListener(); - virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, - StreamTime aTrackOffset, - uint32_t aTrackEvents, - const MediaSegment& aQueuedMedia, - MediaStream* aInputStream, - TrackID aInputTrackID) override; + void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, + StreamTime aTrackOffset, + uint32_t aTrackEvents, + const MediaSegment& aQueuedMedia, + MediaStream* aInputStream, + TrackID aInputTrackID) override; - virtual void NotifyEvent(MediaStreamGraph* aGraph, - MediaStreamListener::MediaStreamGraphEvent event) override; + void NotifyEvent(MediaStreamGraph* aGraph, + MediaStreamListener::MediaStreamGraphEvent event) override; private: template diff --git a/dom/media/webspeech/synth/SpeechSynthesis.h b/dom/media/webspeech/synth/SpeechSynthesis.h index c8b013069a..340fe574f5 100644 --- a/dom/media/webspeech/synth/SpeechSynthesis.h +++ b/dom/media/webspeech/synth/SpeechSynthesis.h @@ -39,7 +39,7 @@ public: nsIDOMWindow* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; bool Pending() const; diff --git a/dom/media/webspeech/synth/SpeechSynthesisUtterance.h b/dom/media/webspeech/synth/SpeechSynthesisUtterance.h index 7b2d8c7527..ee15070841 100644 --- a/dom/media/webspeech/synth/SpeechSynthesisUtterance.h +++ b/dom/media/webspeech/synth/SpeechSynthesisUtterance.h @@ -37,7 +37,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; static already_AddRefed Constructor(GlobalObject& aGlobal, diff --git a/dom/media/webspeech/synth/SpeechSynthesisVoice.h b/dom/media/webspeech/synth/SpeechSynthesisVoice.h index f55695d55b..5c3c651e2f 100644 --- a/dom/media/webspeech/synth/SpeechSynthesisVoice.h +++ b/dom/media/webspeech/synth/SpeechSynthesisVoice.h @@ -34,7 +34,7 @@ public: nsISupports* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; void GetVoiceURI(nsString& aRetval) const; diff --git a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h index 37e8da3e58..8b40319e39 100644 --- a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h +++ b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h @@ -50,21 +50,21 @@ public: virtual ~SpeechSynthesisRequestChild(); protected: - virtual bool RecvOnStart(const nsString& aUri) override; + bool RecvOnStart(const nsString& aUri) override; - virtual bool RecvOnEnd(const bool& aIsError, - const float& aElapsedTime, - const uint32_t& aCharIndex) override; + bool RecvOnEnd(const bool& aIsError, + const float& aElapsedTime, + const uint32_t& aCharIndex) override; - virtual bool RecvOnPause(const float& aElapsedTime, const uint32_t& aCharIndex) override; + bool RecvOnPause(const float& aElapsedTime, const uint32_t& aCharIndex) override; - virtual bool RecvOnResume(const float& aElapsedTime, const uint32_t& aCharIndex) override; + bool RecvOnResume(const float& aElapsedTime, const uint32_t& aCharIndex) override; - virtual bool RecvOnBoundary(const nsString& aName, const float& aElapsedTime, - const uint32_t& aCharIndex) override; + bool RecvOnBoundary(const nsString& aName, const float& aElapsedTime, + const uint32_t& aCharIndex) override; - virtual bool RecvOnMark(const nsString& aName, const float& aElapsedTime, - const uint32_t& aCharIndex) override; + bool RecvOnMark(const nsString& aName, const float& aElapsedTime, + const uint32_t& aCharIndex) override; RefPtr mTask; }; @@ -84,15 +84,15 @@ public: NS_IMETHOD SendAudioNative(int16_t* aData, uint32_t aDataLen) override; - virtual void Pause() override; + void Pause() override; - virtual void Resume() override; + void Resume() override; - virtual void Cancel() override; + void Cancel() override; - virtual void ForceEnd() override; + void ForceEnd() override; - virtual void SetAudioOutputVolume(float aVolume) override; + void SetAudioOutputVolume(float aVolume) override; private: SpeechSynthesisRequestChild* mActor; diff --git a/dom/media/webspeech/synth/ipc/SpeechSynthesisParent.h b/dom/media/webspeech/synth/ipc/SpeechSynthesisParent.h index 53379def19..2edd7e28e3 100644 --- a/dom/media/webspeech/synth/ipc/SpeechSynthesisParent.h +++ b/dom/media/webspeech/synth/ipc/SpeechSynthesisParent.h @@ -22,7 +22,7 @@ class SpeechSynthesisParent : public PSpeechSynthesisParent friend class SpeechSynthesisRequestParent; public: - virtual void ActorDestroy(ActorDestroyReason aWhy) override; + void ActorDestroy(ActorDestroyReason aWhy) override; bool RecvReadVoicesAndState(InfallibleTArray* aVoices, InfallibleTArray* aDefaults, @@ -60,19 +60,19 @@ public: protected: - virtual void ActorDestroy(ActorDestroyReason aWhy) override; + void ActorDestroy(ActorDestroyReason aWhy) override; - virtual bool RecvPause() override; + bool RecvPause() override; - virtual bool RecvResume() override; + bool RecvResume() override; - virtual bool RecvCancel() override; + bool RecvCancel() override; - virtual bool RecvForceEnd() override; + bool RecvForceEnd() override; - virtual bool RecvSetAudioOutputVolume(const float& aVolume) override; + bool RecvSetAudioOutputVolume(const float& aVolume) override; - virtual bool Recv__delete__() override; + bool Recv__delete__() override; }; class SpeechTaskParent : public nsSpeechTask @@ -82,21 +82,21 @@ public: SpeechTaskParent(float aVolume, const nsAString& aUtterance) : nsSpeechTask(aVolume, aUtterance) {} - virtual nsresult DispatchStartImpl(const nsAString& aUri); + nsresult DispatchStartImpl(const nsAString& aUri); - virtual nsresult DispatchEndImpl(float aElapsedTime, uint32_t aCharIndex); + nsresult DispatchEndImpl(float aElapsedTime, uint32_t aCharIndex); - virtual nsresult DispatchPauseImpl(float aElapsedTime, uint32_t aCharIndex); + nsresult DispatchPauseImpl(float aElapsedTime, uint32_t aCharIndex); - virtual nsresult DispatchResumeImpl(float aElapsedTime, uint32_t aCharIndex); + nsresult DispatchResumeImpl(float aElapsedTime, uint32_t aCharIndex); - virtual nsresult DispatchErrorImpl(float aElapsedTime, uint32_t aCharIndex); + nsresult DispatchErrorImpl(float aElapsedTime, uint32_t aCharIndex); - virtual nsresult DispatchBoundaryImpl(const nsAString& aName, - float aElapsedTime, uint32_t aCharIndex); + nsresult DispatchBoundaryImpl(const nsAString& aName, + float aElapsedTime, uint32_t aCharIndex); - virtual nsresult DispatchMarkImpl(const nsAString& aName, - float aElapsedTime, uint32_t aCharIndex); + nsresult DispatchMarkImpl(const nsAString& aName, + float aElapsedTime, uint32_t aCharIndex); private: SpeechSynthesisRequestParent* mActor; diff --git a/dom/media/webspeech/synth/nsSpeechTask.cpp b/dom/media/webspeech/synth/nsSpeechTask.cpp index b43f0e6581..a8cc2d13cd 100644 --- a/dom/media/webspeech/synth/nsSpeechTask.cpp +++ b/dom/media/webspeech/synth/nsSpeechTask.cpp @@ -52,8 +52,8 @@ public: } } - virtual void NotifyEvent(MediaStreamGraph* aGraph, - MediaStreamListener::MediaStreamGraphEvent event) override + void NotifyEvent(MediaStreamGraph* aGraph, + MediaStreamListener::MediaStreamGraphEvent event) override { switch (event) { case EVENT_FINISHED: @@ -73,7 +73,7 @@ public: } } - virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) override + void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) override { if (aBlocked == MediaStreamListener::UNBLOCKED && !mStarted) { mStarted = true; diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp index 6fab226870..5a9b9f86ad 100644 --- a/dom/svg/SVGContentUtils.cpp +++ b/dom/svg/SVGContentUtils.cpp @@ -394,7 +394,7 @@ static gfx::Matrix GetCTMInternal(nsSVGElement *aElement, bool aScreenCTM, bool aHaveRecursed) { gfxMatrix matrix = aElement->PrependLocalTransformsTo(gfxMatrix(), - aHaveRecursed ? nsSVGElement::eAllTransforms : nsSVGElement::eUserSpaceToParent); + aHaveRecursed ? eAllTransforms : eUserSpaceToParent); nsSVGElement *element = aElement; nsIContent *ancestor = aElement->GetFlattenedTreeParent(); @@ -857,3 +857,38 @@ SVGContentUtils::ShapeTypeHasNoCorners(const nsIContent* aContent) { return aContent && aContent->IsAnyOfSVGElements(nsGkAtoms::circle, nsGkAtoms::ellipse); } + +gfxMatrix +SVGContentUtils::PrependLocalTransformsTo( + const gfxMatrix &aMatrix, + SVGTransformTypes aWhich, + const gfx::Matrix* aAnimateMotionTransform, + const nsSVGAnimatedTransformList* aTransforms) +{ + gfxMatrix result(aMatrix); + + if (aWhich == eChildToUserSpace) { + // We don't have anything to prepend. + // eChildToUserSpace is not the common case, which is why we return + // 'result' to benefit from NRVO rather than returning aMatrix before + // creating 'result'. + return result; + } + + MOZ_ASSERT(aWhich == eAllTransforms || aWhich == eUserSpaceToParent, + "Unknown TransformTypes"); + + // animateMotion's resulting transform is supposed to apply *on top of* + // any transformations from the |transform| attribute. So since we're + // PRE-multiplying, we need to apply the animateMotion transform *first*. + if (aAnimateMotionTransform) { + result.PreMultiply(ThebesMatrix(*aAnimateMotionTransform)); + } + + if (aTransforms) { + result.PreMultiply( + aTransforms->GetAnimValue().GetConsolidationMatrix()); + } + + return result; +} diff --git a/dom/svg/SVGContentUtils.h b/dom/svg/SVGContentUtils.h index f050e7d468..41f3e93cf0 100644 --- a/dom/svg/SVGContentUtils.h +++ b/dom/svg/SVGContentUtils.h @@ -26,6 +26,7 @@ class nsStyleCoord; class nsSVGElement; namespace mozilla { +class nsSVGAnimatedTransformList; class SVGAnimatedPreserveAspectRatio; class SVGPreserveAspectRatio; namespace dom { @@ -40,6 +41,35 @@ class Matrix; #define SVG_ZERO_LENGTH_PATH_FIX_FACTOR 512 +/** + * SVGTransformTypes controls the transforms that PrependLocalTransformsTo + * applies. + * + * If aWhich is eAllTransforms, then all the transforms from the coordinate + * space established by this element for its children to the coordinate + * space established by this element's parent element for this element, are + * included. + * + * If aWhich is eUserSpaceToParent, then only the transforms from this + * element's userspace to the coordinate space established by its parent is + * included. This includes any transforms introduced by the 'transform' + * attribute, transform animations and animateMotion, but not any offsets + * due to e.g. 'x'/'y' attributes, or any transform due to a 'viewBox' + * attribute. (SVG userspace is defined to be the coordinate space in which + * coordinates on an element apply.) + * + * If aWhich is eChildToUserSpace, then only the transforms from the + * coordinate space established by this element for its childre to this + * elements userspace are included. This includes any offsets due to e.g. + * 'x'/'y' attributes, and any transform due to a 'viewBox' attribute, but + * does not include any transforms due to the 'transform' attribute. + */ +enum SVGTransformTypes { + eAllTransforms, + eUserSpaceToParent, + eChildToUserSpace +}; + inline bool IsSVGWhitespace(char aChar) { @@ -348,6 +378,17 @@ public: * to have no corners: circle or ellipse */ static bool ShapeTypeHasNoCorners(const nsIContent* aContent); + + /** + * Prepends an element's local transforms to the transform matrix. + * This is a helper for nsSVGElement::PrependLocalTransformsTo. + * Any callers probably really want to call that method instead of this one. + */ + static gfxMatrix PrependLocalTransformsTo( + const gfxMatrix &aMatrix, + SVGTransformTypes aWhich, + const Matrix* aAnimateMotionTransform, + const mozilla::nsSVGAnimatedTransformList* aTransforms); }; #endif diff --git a/dom/svg/SVGForeignObjectElement.cpp b/dom/svg/SVGForeignObjectElement.cpp index 90bf03e819..8a643d79ac 100644 --- a/dom/svg/SVGForeignObjectElement.cpp +++ b/dom/svg/SVGForeignObjectElement.cpp @@ -73,8 +73,8 @@ SVGForeignObjectElement::Height() // nsSVGElement methods /* virtual */ gfxMatrix -SVGForeignObjectElement::PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich) const +SVGForeignObjectElement::PrependLocalTransformsTo( + const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const { // 'transform' attribute: gfxMatrix fromUserSpace = diff --git a/dom/svg/SVGForeignObjectElement.h b/dom/svg/SVGForeignObjectElement.h index 1c6ba35999..095bcd56ca 100644 --- a/dom/svg/SVGForeignObjectElement.h +++ b/dom/svg/SVGForeignObjectElement.h @@ -30,8 +30,9 @@ protected: public: // nsSVGElement specializations: - virtual gfxMatrix PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich = eAllTransforms) const override; + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix &aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; virtual bool HasValidDimensions() const override; // nsIContent interface diff --git a/dom/svg/SVGFragmentIdentifier.cpp b/dom/svg/SVGFragmentIdentifier.cpp index 4235b6b152..3adad54447 100644 --- a/dom/svg/SVGFragmentIdentifier.cpp +++ b/dom/svg/SVGFragmentIdentifier.cpp @@ -12,7 +12,9 @@ #include "nsSVGAnimatedTransformList.h" #include "nsCharSeparatedTokenizer.h" -using namespace mozilla; +namespace mozilla { + +using namespace dom; static bool IsMatchingParameter(const nsAString& aString, const nsAString& aParameterName) @@ -30,117 +32,112 @@ IgnoreWhitespace(char16_t aChar) return false; } -static dom::SVGViewElement* +static SVGViewElement* GetViewElement(nsIDocument* aDocument, const nsAString& aId) { - dom::Element* element = aDocument->GetElementById(aId); + Element* element = aDocument->GetElementById(aId); return (element && element->IsSVGElement(nsGkAtoms::view)) ? - static_cast(element) : nullptr; + static_cast(element) : nullptr; } -void -SVGFragmentIdentifier::SaveOldPreserveAspectRatio(dom::SVGSVGElement* root) +// Handles setting/clearing the root's mSVGView pointer. +class MOZ_RAII AutoSVGViewHandler { - if (root->mPreserveAspectRatio.IsExplicitlySet()) { - root->SetPreserveAspectRatioProperty(root->mPreserveAspectRatio.GetBaseValue()); +public: + explicit AutoSVGViewHandler(SVGSVGElement* aRoot + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mRoot(aRoot), mValid(false) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + mWasOverridden = mRoot->UseCurrentView(); + mRoot->mSVGView = nullptr; + mRoot->mCurrentViewID = nullptr; } -} -void -SVGFragmentIdentifier::RestoreOldPreserveAspectRatio(dom::SVGSVGElement* root) -{ - const SVGPreserveAspectRatio* oldPARPtr = root->GetPreserveAspectRatioProperty(); - if (oldPARPtr) { - root->mPreserveAspectRatio.SetBaseValue(*oldPARPtr, root); - } else if (root->mPreserveAspectRatio.IsExplicitlySet()) { - mozilla::ErrorResult error; - root->RemoveAttribute(NS_LITERAL_STRING("preserveAspectRatio"), error); - } -} - -void -SVGFragmentIdentifier::SaveOldViewBox(dom::SVGSVGElement* root) -{ - if (root->mViewBox.IsExplicitlySet()) { - root->SetViewBoxProperty(root->mViewBox.GetBaseValue()); - } -} - -void -SVGFragmentIdentifier::RestoreOldViewBox(dom::SVGSVGElement* root) -{ - const nsSVGViewBoxRect* oldViewBoxPtr = root->GetViewBoxProperty(); - if (oldViewBoxPtr) { - root->mViewBox.SetBaseValue(*oldViewBoxPtr, root); - } else if (root->mViewBox.IsExplicitlySet()) { - mozilla::ErrorResult error; - root->RemoveAttribute(NS_LITERAL_STRING("viewBox"), error); - } -} - -void -SVGFragmentIdentifier::SaveOldZoomAndPan(dom::SVGSVGElement* root) -{ - if (root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].IsExplicitlySet()) { - root->SetZoomAndPanProperty(root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].GetBaseValue()); - } -} - -void -SVGFragmentIdentifier::RestoreOldZoomAndPan(dom::SVGSVGElement* root) -{ - uint16_t oldZoomAndPan = root->GetZoomAndPanProperty(); - if (oldZoomAndPan != SVG_ZOOMANDPAN_UNKNOWN) { - root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].SetBaseValue(oldZoomAndPan, root); - } else if (root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].IsExplicitlySet()) { - mozilla::ErrorResult error; - root->RemoveAttribute(NS_LITERAL_STRING("zoomAndPan"), error); - } -} - -void -SVGFragmentIdentifier::SaveOldTransform(dom::SVGSVGElement* root) -{ - nsSVGAnimatedTransformList* transformList = root->GetAnimatedTransformList(); - - if (transformList && transformList->IsExplicitlySet()) { - root->SetTransformProperty(transformList->GetBaseValue()); - } -} - -void -SVGFragmentIdentifier::RestoreOldTransform(dom::SVGSVGElement* root) -{ - const SVGTransformList* oldTransformPtr = root->GetTransformProperty(); - if (oldTransformPtr) { - root->GetAnimatedTransformList(nsSVGElement::DO_ALLOCATE)->SetBaseValue(*oldTransformPtr); - } else { - nsSVGAnimatedTransformList* transformList = root->GetAnimatedTransformList(); - if (transformList && transformList->IsExplicitlySet()) { - mozilla::ErrorResult error; - root->RemoveAttribute(NS_LITERAL_STRING("transform"), error); + ~AutoSVGViewHandler() { + if (!mWasOverridden && !mValid) { + // we weren't overridden before and we aren't + // overridden now so nothing has changed. + return; } + if (mValid) { + mRoot->mSVGView = mSVGView; + } + mRoot->InvalidateTransformNotifyFrame(); } -} + + void CreateSVGView() { + MOZ_ASSERT(!mSVGView, "CreateSVGView should not be called multiple times"); + mSVGView = new SVGView(); + } + + bool ProcessAttr(const nsAString& aToken, const nsAString &aParams) { + + MOZ_ASSERT(mSVGView, "CreateSVGView should have been called"); + + // SVGViewAttributes may occur in any order, but each type may only occur + // at most one time in a correctly formed SVGViewSpec. + // If we encounter any attribute more than once or get any syntax errors + // we're going to return false and cancel any changes. + + if (IsMatchingParameter(aToken, NS_LITERAL_STRING("viewBox"))) { + if (mSVGView->mViewBox.IsExplicitlySet() || + NS_FAILED(mSVGView->mViewBox.SetBaseValueString( + aParams, mRoot, false))) { + return false; + } + } else if (IsMatchingParameter(aToken, NS_LITERAL_STRING("preserveAspectRatio"))) { + if (mSVGView->mPreserveAspectRatio.IsExplicitlySet() || + NS_FAILED(mSVGView->mPreserveAspectRatio.SetBaseValueString( + aParams, mRoot, false))) { + return false; + } + } else if (IsMatchingParameter(aToken, NS_LITERAL_STRING("transform"))) { + if (mSVGView->mTransforms) { + return false; + } + mSVGView->mTransforms = new nsSVGAnimatedTransformList(); + if (NS_FAILED(mSVGView->mTransforms->SetBaseValueString(aParams))) { + return false; + } + } else if (IsMatchingParameter(aToken, NS_LITERAL_STRING("zoomAndPan"))) { + if (mSVGView->mZoomAndPan.IsExplicitlySet()) { + return false; + } + nsIAtom* valAtom = NS_GetStaticAtom(aParams); + if (!valAtom || + NS_FAILED(mSVGView->mZoomAndPan.SetBaseValueAtom( + valAtom, mRoot))) { + return false; + } + } else { + // We don't support viewTarget currently + return false; + } + return true; + } + + void SetValid() { + mValid = true; + } + +private: + SVGSVGElement* mRoot; + nsAutoPtr mSVGView; + bool mValid; + bool mWasOverridden; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec, - dom::SVGSVGElement* root) + SVGSVGElement* aRoot) { + AutoSVGViewHandler viewHandler(aRoot); + if (!IsMatchingParameter(aViewSpec, NS_LITERAL_STRING("svgView"))) { return false; } - // SVGViewAttributes may occur in any order, but each type may only occur - // at most one time in a correctly formed SVGViewSpec. - // If we encounter any attribute more than once or get any syntax errors - // we're going to return false and cancel any changes. - - bool viewBoxFound = false; - bool preserveAspectRatioFound = false; - bool transformFound = false; - bool zoomAndPanFound = false; - // Each token is a SVGViewAttribute int32_t bracketPos = aViewSpec.FindChar('('); uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2; @@ -150,6 +147,8 @@ SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec, if (!tokenizer.hasMoreTokens()) { return false; } + viewHandler.CreateSVGView(); + do { nsAutoString token(tokenizer.nextToken()); @@ -163,75 +162,13 @@ SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec, const nsAString ¶ms = Substring(token, bracketPos + 1, token.Length() - bracketPos - 2); - if (IsMatchingParameter(token, NS_LITERAL_STRING("viewBox"))) { - if (viewBoxFound || - NS_FAILED(root->mViewBox.SetBaseValueString( - params, root, true))) { - return false; - } - viewBoxFound = true; - } else if (IsMatchingParameter(token, NS_LITERAL_STRING("preserveAspectRatio"))) { - if (preserveAspectRatioFound || - NS_FAILED(root->mPreserveAspectRatio.SetBaseValueString( - params, root, true))) { - return false; - } - preserveAspectRatioFound = true; - } else if (IsMatchingParameter(token, NS_LITERAL_STRING("transform"))) { - if (transformFound || - NS_FAILED(root->GetAnimatedTransformList(nsSVGElement::DO_ALLOCATE)-> - SetBaseValueString(params))) { - return false; - } - transformFound = true; - } else if (IsMatchingParameter(token, NS_LITERAL_STRING("zoomAndPan"))) { - if (zoomAndPanFound) { - return false; - } - nsIAtom* valAtom = NS_GetStaticAtom(params); - if (!valAtom) { - return false; - } - const nsSVGEnumMapping* mapping = dom::SVGSVGElement::sZoomAndPanMap; - while (mapping->mKey) { - if (valAtom == *(mapping->mKey)) { - // If we've got a valid zoomAndPan value, then set it on our root element. - if (NS_FAILED(root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].SetBaseValue( - mapping->mVal, root))) { - return false; - } - break; - } - mapping++; - } - if (!mapping->mKey) { - // Unrecognised zoomAndPan value - return false; - } - zoomAndPanFound = true; - } else { - // We don't support viewTarget currently + if (!viewHandler.ProcessAttr(token, params)) { return false; } + } while (tokenizer.hasMoreTokens()); - if (root->mUseCurrentView) { - // A previous SVGViewSpec may have overridden some attributes. - // If they are no longer overridden we need to restore the old values. - if (!transformFound) { - RestoreOldTransform(root); - } - if (!viewBoxFound) { - RestoreOldViewBox(root); - } - if (!preserveAspectRatioFound) { - RestoreOldPreserveAspectRatio(root); - } - if (!zoomAndPanFound) { - RestoreOldZoomAndPan(root); - } - } - + viewHandler.SetValid(); return true; } @@ -242,44 +179,24 @@ SVGFragmentIdentifier::ProcessFragmentIdentifier(nsIDocument* aDocument, MOZ_ASSERT(aDocument->GetRootElement()->IsSVGElement(nsGkAtoms::svg), "expecting an SVG root element"); - dom::SVGSVGElement* rootElement = - static_cast(aDocument->GetRootElement()); + SVGSVGElement* rootElement = + static_cast(aDocument->GetRootElement()); - if (!rootElement->mUseCurrentView) { - SaveOldViewBox(rootElement); - SaveOldPreserveAspectRatio(rootElement); - SaveOldZoomAndPan(rootElement); - } - - const dom::SVGViewElement* viewElement = GetViewElement(aDocument, aAnchorName); + const SVGViewElement* viewElement = GetViewElement(aDocument, aAnchorName); if (viewElement) { if (!rootElement->mCurrentViewID) { rootElement->mCurrentViewID = new nsString(); } *rootElement->mCurrentViewID = aAnchorName; - rootElement->mUseCurrentView = true; + rootElement->mSVGView = nullptr; rootElement->InvalidateTransformNotifyFrame(); - return true; + // not an svgView()-style fragment identifier, return false so the caller + // continues processing to match any :target pseudo elements + return false; } - bool wasOverridden = !!rootElement->mCurrentViewID; - rootElement->mCurrentViewID = nullptr; - - rootElement->mUseCurrentView = ProcessSVGViewSpec(aAnchorName, rootElement); - if (rootElement->mUseCurrentView) { - return true; - } - RestoreOldViewBox(rootElement); - rootElement->ClearViewBoxProperty(); - RestoreOldPreserveAspectRatio(rootElement); - rootElement->ClearPreserveAspectRatioProperty(); - RestoreOldZoomAndPan(rootElement); - rootElement->ClearZoomAndPanProperty(); - RestoreOldTransform(rootElement); - rootElement->ClearTransformProperty(); - if (wasOverridden) { - rootElement->InvalidateTransformNotifyFrame(); - } - return false; + return ProcessSVGViewSpec(aAnchorName, rootElement); } + +} // namespace mozilla diff --git a/dom/svg/SVGFragmentIdentifier.h b/dom/svg/SVGFragmentIdentifier.h index 8bf1aeccbd..0fb6fb1a78 100644 --- a/dom/svg/SVGFragmentIdentifier.h +++ b/dom/svg/SVGFragmentIdentifier.h @@ -29,7 +29,9 @@ class SVGFragmentIdentifier public: /** * Process the SVG fragment identifier, if there is one. - * @return true if we found something we recognised + * @return true if we found a valid svgView()-style fragment identifier, + * in which case further processing by the caller can stop. Otherwise return + * false as we may have an ordinary anchor which needs to be :target matched. */ static bool ProcessFragmentIdentifier(nsIDocument *aDocument, const nsAString &aAnchorName); @@ -40,17 +42,6 @@ private: * @return true if there is a valid ViewSpec */ static bool ProcessSVGViewSpec(const nsAString &aViewSpec, dom::SVGSVGElement *root); - - // Save and restore things we override in case we want to go back e.g. the - // user presses the back button - static void SaveOldPreserveAspectRatio(dom::SVGSVGElement *root); - static void RestoreOldPreserveAspectRatio(dom::SVGSVGElement *root); - static void SaveOldViewBox(dom::SVGSVGElement *root); - static void RestoreOldViewBox(dom::SVGSVGElement *root); - static void SaveOldZoomAndPan(dom::SVGSVGElement *root); - static void RestoreOldZoomAndPan(dom::SVGSVGElement *root); - static void SaveOldTransform(dom::SVGSVGElement *root); - static void RestoreOldTransform(dom::SVGSVGElement *root); }; } // namespace mozilla diff --git a/dom/svg/SVGSVGElement.cpp b/dom/svg/SVGSVGElement.cpp index 12fcf06562..0bf009865e 100644 --- a/dom/svg/SVGSVGElement.cpp +++ b/dom/svg/SVGSVGElement.cpp @@ -111,6 +111,14 @@ DOMSVGTranslatePoint::MatrixTransform(SVGMatrix& matrix) return point.forget(); } +SVGView::SVGView() +{ + mZoomAndPan.Init(SVGSVGElement::ZOOMANDPAN, + SVG_ZOOMANDPAN_MAGNIFY); + mViewBox.Init(); + mPreserveAspectRatio.Init(); +} + nsSVGElement::LengthInfo SVGSVGElement::sLengthInfo[4] = { { &nsGkAtoms::x, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X }, @@ -176,8 +184,7 @@ SVGSVGElement::SVGSVGElement(already_AddRefed& aNodeInfo aFromParser == FROM_PARSER_XSLT), mImageNeedsTransformInvalidation(false), mIsPaintingSVGImageElement(false), - mHasChildrenOnlyTransform(false), - mUseCurrentView(false) + mHasChildrenOnlyTransform(false) { } @@ -257,7 +264,7 @@ SVGSVGElement::ScreenPixelToMillimeterY() bool SVGSVGElement::UseCurrentView() { - return mUseCurrentView; + return mSVGView || mCurrentViewID; } float @@ -452,12 +459,6 @@ SVGSVGElement::PreserveAspectRatio() uint16_t SVGSVGElement::ZoomAndPan() { - SVGViewElement* viewElement = GetCurrentViewElement(); - if (viewElement && viewElement->mEnumAttributes[ - SVGViewElement::ZOOMANDPAN].IsExplicitlySet()) { - return viewElement->mEnumAttributes[ - SVGViewElement::ZOOMANDPAN].GetAnimValue(); - } return mEnumAttributes[ZOOMANDPAN].GetAnimValue(); } @@ -841,6 +842,9 @@ SVGSVGElement::GetViewBoxWithSynthesis( if (viewElement && viewElement->mViewBox.HasRect()) { return viewElement->mViewBox.GetAnimValue(); } + if (mSVGView && mSVGView->mViewBox.HasRect()) { + return mSVGView->mViewBox.GetAnimValue(); + } if (mViewBox.HasRect()) { return mViewBox.GetAnimValue(); } @@ -878,6 +882,7 @@ SVGSVGElement::GetPreserveAspectRatioWithOverride() const // We're just holding onto the viewElement that HasViewBoxRect() would look up, // so that we don't have to look it up again later. if (!((viewElement && viewElement->mViewBox.HasRect()) || + (mSVGView && mSVGView->mViewBox.HasRect()) || mViewBox.HasRect()) && ShouldSynthesizeViewBox()) { // If we're synthesizing a viewBox, use preserveAspectRatio="none"; @@ -887,6 +892,9 @@ SVGSVGElement::GetPreserveAspectRatioWithOverride() const if (viewElement && viewElement->mPreserveAspectRatio.IsExplicitlySet()) { return viewElement->mPreserveAspectRatio.GetAnimValue(); } + if (mSVGView && mSVGView->mPreserveAspectRatio.IsExplicitlySet()) { + return mSVGView->mPreserveAspectRatio.GetAnimValue(); + } return mPreserveAspectRatio.GetAnimValue(); } @@ -904,6 +912,8 @@ SVGSVGElement::GetLength(uint8_t aCtxType) // The logic here should match HasViewBoxRect(). if (viewElement && viewElement->mViewBox.HasRect()) { viewbox = &viewElement->mViewBox.GetAnimValue(); + } else if (mSVGView && mSVGView->mViewBox.HasRect()) { + viewbox = &mSVGView->mViewBox.GetAnimValue(); } else if (mViewBox.HasRect()) { viewbox = &mViewBox.GetAnimValue(); } @@ -943,12 +953,15 @@ SVGSVGElement::GetLength(uint8_t aCtxType) // nsSVGElement methods /* virtual */ gfxMatrix -SVGSVGElement::PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich) const +SVGSVGElement::PrependLocalTransformsTo( + const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const { - // 'transform' attribute: + // 'transform' attribute (or an override from a fragment identifier): gfxMatrix fromUserSpace = - SVGSVGElementBase::PrependLocalTransformsTo(aMatrix, aWhich); + SVGContentUtils::PrependLocalTransformsTo( + aMatrix, aWhich, mAnimateMotionTransform, + mSVGView && mSVGView->mTransforms ? mSVGView->mTransforms : mTransforms); + if (aWhich == eUserSpaceToParent) { return fromUserSpace; } @@ -975,6 +988,15 @@ SVGSVGElement::PrependLocalTransformsTo(const gfxMatrix &aMatrix, return ThebesMatrix(GetViewBoxTransform()) * fromUserSpace; } +nsSVGAnimatedTransformList* +SVGSVGElement::GetAnimatedTransformList(uint32_t aFlags) +{ + if (!(aFlags & DO_ALLOCATE) && mSVGView && mSVGView->mTransforms) { + return mSVGView->mTransforms; + } + return SVGSVGElementBase::GetAnimatedTransformList(aFlags); +} + /* virtual */ bool SVGSVGElement::HasValidDimensions() const { @@ -1015,7 +1037,8 @@ bool SVGSVGElement::HasViewBoxRect() const { SVGViewElement* viewElement = GetCurrentViewElement(); - if (viewElement && viewElement->mViewBox.HasRect()) { + if ((viewElement && viewElement->mViewBox.HasRect()) || + (mSVGView && mSVGView->mViewBox.HasRect())) { return true; } return mViewBox.HasRect(); @@ -1136,106 +1159,6 @@ SVGSVGElement::FlushImageTransformInvalidation() } } -bool -SVGSVGElement::SetViewBoxProperty(const nsSVGViewBoxRect& aViewBox) -{ - nsSVGViewBoxRect* pViewBoxOverridePtr = new nsSVGViewBoxRect(aViewBox); - nsresult rv = SetProperty(nsGkAtoms::viewBox, - pViewBoxOverridePtr, - nsINode::DeleteProperty, - true); - MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN, - "Setting override value when it's already set...?"); - - if (MOZ_UNLIKELY(NS_FAILED(rv))) { - // property-insertion failed (e.g. OOM in property-table code) - delete pViewBoxOverridePtr; - return false; - } - return true; -} - -const nsSVGViewBoxRect* -SVGSVGElement::GetViewBoxProperty() const -{ - void* valPtr = GetProperty(nsGkAtoms::viewBox); - if (valPtr) { - return static_cast(valPtr); - } - return nullptr; -} - -bool -SVGSVGElement::ClearViewBoxProperty() -{ - void* valPtr = UnsetProperty(nsGkAtoms::viewBox); - delete static_cast(valPtr); - return valPtr; -} - -bool -SVGSVGElement::SetZoomAndPanProperty(uint16_t aValue) -{ - nsresult rv = SetProperty(nsGkAtoms::zoomAndPan, - reinterpret_cast(aValue), - nullptr, true); - MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN, - "Setting override value when it's already set...?"); - - return NS_SUCCEEDED(rv); -} - -uint16_t -SVGSVGElement::GetZoomAndPanProperty() const -{ - void* valPtr = GetProperty(nsGkAtoms::zoomAndPan); - if (valPtr) { - return reinterpret_cast(valPtr); - } - return SVG_ZOOMANDPAN_UNKNOWN; -} - -bool -SVGSVGElement::ClearZoomAndPanProperty() -{ - return UnsetProperty(nsGkAtoms::zoomAndPan); -} - -bool -SVGSVGElement::SetTransformProperty(const SVGTransformList& aTransform) -{ - SVGTransformList* pTransformOverridePtr = new SVGTransformList(aTransform); - nsresult rv = SetProperty(nsGkAtoms::transform, - pTransformOverridePtr, - nsINode::DeleteProperty, - true); - MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN, - "Setting override value when it's already set...?"); - - if (MOZ_UNLIKELY(NS_FAILED(rv))) { - // property-insertion failed (e.g. OOM in property-table code) - delete pTransformOverridePtr; - return false; - } - return true; -} - -const SVGTransformList* -SVGSVGElement::GetTransformProperty() const -{ - void* valPtr = GetProperty(nsGkAtoms::transform); - if (valPtr) { - return static_cast(valPtr); - } - return nullptr; -} - -bool -SVGSVGElement::ClearTransformProperty() -{ - return UnsetProperty(nsGkAtoms::transform); -} - int32_t SVGSVGElement::GetIntrinsicWidth() { diff --git a/dom/svg/SVGSVGElement.h b/dom/svg/SVGSVGElement.h index 59d37387e3..0a011ffd61 100644 --- a/dom/svg/SVGSVGElement.h +++ b/dom/svg/SVGSVGElement.h @@ -33,6 +33,7 @@ class DOMSVGLength; class DOMSVGNumber; class EventChainPreVisitor; class SVGFragmentIdentifier; +class AutoSVGViewHandler; namespace dom { class SVGAngle; @@ -85,13 +86,29 @@ public: float height; }; +// Stores svgView arguments of SVG fragment identifiers. +class SVGView { + friend class mozilla::AutoSVGViewHandler; + friend class mozilla::dom::SVGSVGElement; +public: + SVGView(); + +private: + nsSVGEnum mZoomAndPan; + nsSVGViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + nsAutoPtr mTransforms; +}; + typedef SVGGraphicsElement SVGSVGElementBase; class SVGSVGElement final : public SVGSVGElementBase { friend class ::nsSVGOuterSVGFrame; friend class ::nsSVGInnerSVGFrame; + friend class mozilla::dom::SVGView; friend class mozilla::SVGFragmentIdentifier; + friend class mozilla::AutoSVGViewHandler; friend class mozilla::AutoSVGRenderingState; SVGSVGElement(already_AddRefed& aNodeInfo, @@ -138,8 +155,11 @@ public: virtual bool IsEventAttributeName(nsIAtom* aName) override; // nsSVGElement specializations: - virtual gfxMatrix PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich = eAllTransforms) const override; + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix &aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; + virtual nsSVGAnimatedTransformList* + GetAnimatedTransformList(uint32_t aFlags = 0) override; virtual bool HasValidDimensions() const override; // SVGSVGElement methods: @@ -285,19 +305,11 @@ private: void SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio& aPAR); void ClearImageOverridePreserveAspectRatio(); - // Set/Clear properties to hold old or override versions of attributes + // Set/Clear properties to hold old version of preserveAspectRatio + // when it's being overridden by an element that we are inside of. bool SetPreserveAspectRatioProperty(const SVGPreserveAspectRatio& aPAR); const SVGPreserveAspectRatio* GetPreserveAspectRatioProperty() const; bool ClearPreserveAspectRatioProperty(); - bool SetViewBoxProperty(const nsSVGViewBoxRect& aViewBox); - const nsSVGViewBoxRect* GetViewBoxProperty() const; - bool ClearViewBoxProperty(); - bool SetZoomAndPanProperty(uint16_t aValue); - uint16_t GetZoomAndPanProperty() const; - bool ClearZoomAndPanProperty(); - bool SetTransformProperty(const SVGTransformList& aValue); - const SVGTransformList* GetTransformProperty() const; - bool ClearTransformProperty(); bool IsRoot() const { NS_ASSERTION((IsInDoc() && !GetParent()) == @@ -368,7 +380,10 @@ private: nsSVGViewBox mViewBox; SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + // mCurrentViewID and mSVGView are mutually exclusive; we can have + // at most one non-null. nsAutoPtr mCurrentViewID; + nsAutoPtr mSVGView; // The size of the rectangular SVG viewport into which we render. This is // not (necessarily) the same as the content area. See: @@ -400,7 +415,6 @@ private: bool mImageNeedsTransformInvalidation; bool mIsPaintingSVGImageElement; bool mHasChildrenOnlyTransform; - bool mUseCurrentView; }; } // namespace dom diff --git a/dom/svg/SVGTransformableElement.cpp b/dom/svg/SVGTransformableElement.cpp index 1903392952..ac594fcd5f 100644 --- a/dom/svg/SVGTransformableElement.cpp +++ b/dom/svg/SVGTransformableElement.cpp @@ -90,34 +90,12 @@ SVGTransformableElement::IsEventAttributeName(nsIAtom* aName) // nsSVGElement overrides gfxMatrix -SVGTransformableElement::PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich) const +SVGTransformableElement::PrependLocalTransformsTo( + const gfxMatrix &aMatrix, + SVGTransformTypes aWhich) const { - gfxMatrix result(aMatrix); - - if (aWhich == eChildToUserSpace) { - // We don't have anything to prepend. - // eChildToUserSpace is not the common case, which is why we return - // 'result' to benefit from NRVO rather than returning aMatrix before - // creating 'result'. - return result; - } - - MOZ_ASSERT(aWhich == eAllTransforms || aWhich == eUserSpaceToParent, - "Unknown TransformTypes"); - - // animateMotion's resulting transform is supposed to apply *on top of* - // any transformations from the |transform| attribute. So since we're - // PRE-multiplying, we need to apply the animateMotion transform *first*. - if (mAnimateMotionTransform) { - result.PreMultiply(ThebesMatrix(*mAnimateMotionTransform)); - } - - if (mTransforms) { - result.PreMultiply(mTransforms->GetAnimValue().GetConsolidationMatrix()); - } - - return result; + return SVGContentUtils::PrependLocalTransformsTo( + aMatrix, aWhich, mAnimateMotionTransform, mTransforms); } const gfx::Matrix* diff --git a/dom/svg/SVGTransformableElement.h b/dom/svg/SVGTransformableElement.h index a8e541a76a..7e0a681a47 100644 --- a/dom/svg/SVGTransformableElement.h +++ b/dom/svg/SVGTransformableElement.h @@ -53,8 +53,9 @@ public: virtual bool IsEventAttributeName(nsIAtom* aName) override; - virtual gfxMatrix PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich = eAllTransforms) const override; + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix &aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; virtual const gfx::Matrix* GetAnimateMotionTransform() const override; virtual void SetAnimateMotionTransform(const gfx::Matrix* aMatrix) override; diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp index 262383939c..f82bbe2954 100644 --- a/dom/svg/SVGUseElement.cpp +++ b/dom/svg/SVGUseElement.cpp @@ -431,8 +431,8 @@ SVGUseElement::UnlinkSource() // nsSVGElement methods /* virtual */ gfxMatrix -SVGUseElement::PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich) const +SVGUseElement::PrependLocalTransformsTo( + const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const { // 'transform' attribute: gfxMatrix fromUserSpace = diff --git a/dom/svg/SVGUseElement.h b/dom/svg/SVGUseElement.h index 7309aadff4..36f2be50eb 100644 --- a/dom/svg/SVGUseElement.h +++ b/dom/svg/SVGUseElement.h @@ -60,8 +60,9 @@ public: void DestroyAnonymousContent(); // nsSVGElement specializations: - virtual gfxMatrix PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich = eAllTransforms) const override; + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix &aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; virtual bool HasValidDimensions() const override; // nsIContent interface diff --git a/dom/svg/moz.build b/dom/svg/moz.build index 09480600c8..1ced78ad14 100644 --- a/dom/svg/moz.build +++ b/dom/svg/moz.build @@ -11,6 +11,7 @@ EXPORTS += [ 'nsSVGElement.h', 'nsSVGFeatures.h', 'SVGAttrValueWrapper.h', + 'SVGContentUtils.h', 'SVGPreserveAspectRatio.h', 'SVGStringList.h', ] diff --git a/dom/svg/nsSVGElement.cpp b/dom/svg/nsSVGElement.cpp index 371088f77c..758f3532d0 100644 --- a/dom/svg/nsSVGElement.cpp +++ b/dom/svg/nsSVGElement.cpp @@ -1554,8 +1554,8 @@ nsSVGElement::GetCtx() const } /* virtual */ gfxMatrix -nsSVGElement::PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich) const +nsSVGElement::PrependLocalTransformsTo( + const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const { return aMatrix; } diff --git a/dom/svg/nsSVGElement.h b/dom/svg/nsSVGElement.h index 02afab9cf8..ad7547bdda 100644 --- a/dom/svg/nsSVGElement.h +++ b/dom/svg/nsSVGElement.h @@ -25,6 +25,7 @@ #include "nsStyledElement.h" #include "nsSVGClass.h" #include "nsIDOMSVGElement.h" +#include "SVGContentUtils.h" class nsSVGAngle; class nsSVGBoolean; @@ -139,11 +140,6 @@ public: // nullptr for outer or SVG without an parent (invalid SVG). mozilla::dom::SVGSVGElement* GetCtx() const; - enum TransformTypes { - eAllTransforms - ,eUserSpaceToParent - ,eChildToUserSpace - }; /** * Returns aMatrix pre-multiplied by (explicit or implicit) transforms that * are introduced by attributes on this element. @@ -167,8 +163,8 @@ public: * 'x'/'y' attributes, and any transform due to a 'viewBox' attribute, but * does not include any transforms due to the 'transform' attribute. */ - virtual gfxMatrix PrependLocalTransformsTo(const gfxMatrix &aMatrix, - TransformTypes aWhich = eAllTransforms) const; + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix &aMatrix, SVGTransformTypes aWhich = eAllTransforms) const; // Setter for to set the current transformation // Only visible for nsSVGGraphicElement, so it's a no-op here, and that diff --git a/dom/svg/test/test_fragments.html b/dom/svg/test/test_fragments.html index 970e7a0b95..eba83332de 100644 --- a/dom/svg/test/test_fragments.html +++ b/dom/svg/test/test_fragments.html @@ -24,67 +24,92 @@ function Test(svgFragmentIdentifier, valid, viewBoxString, { this.svgFragmentIdentifier = svgFragmentIdentifier; this.valid = valid; - this.viewBoxString = viewBoxString; - this.preserveAspectRatioString = preserveAspectRatioString; - this.zoomAndPanString = zoomAndPanString; } function runTests() { var svg = $("svg"); var doc = svg.contentWindow.document; - + var rootElement = doc.rootElement; + var tests = [ - new Test("unknown", false, null, null, null), - new Test("svgView(viewBox(0,0,200,200))", true, "0 0 200 200", null, null), - new Test("svgView(preserveAspectRatio(xMaxYMin slice))", true, null, "xMaxYMin slice", null), - new Test("svgView(viewBox(1,2,3,4);preserveAspectRatio(xMinYMax))", true, "1 2 3 4", "xMinYMax meet", null), - new Test("svgView(viewBox(none))", true, "none", null, null), - new Test("svgView(zoomAndPan(disable))", true, null, null, "disable"), - new Test("svgView(transform(translate(-10,-20) scale(2) rotate(45) translate(5,10)))", true, null, null, null), + new Test("unknown", false), + new Test("svgView(viewBox(0,0,200,200))", true), + new Test("svgView(preserveAspectRatio(xMaxYMin slice))", true), + new Test("svgView(viewBox(1,2,3,4);preserveAspectRatio(xMinYMax))", true), + new Test("svgView(viewBox(none))", true), + new Test("svgView(zoomAndPan(disable))", true), + new Test("svgView(transform(translate(-10,-20) scale(2) rotate(45) translate(5,10)))", true), // No duplicates allowed - new Test("svgView(zoomAndPan(disable);zoomAndPan(disable))", false, null, null, null), - new Test("svgView(viewBox(0,0,200,200);viewBox(0,0,200,200))", false, null, null, null), - new Test("svgView(preserveAspectRatio(xMaxYMin);preserveAspectRatio(xMaxYMin))", false, null, null, null), - new Test("svgView(transform(translate(0,200));transform(translate(0,200)))", false, null, null, null), + new Test("svgView(zoomAndPan(disable);zoomAndPan(disable))", false), + new Test("svgView(viewBox(0,0,200,200);viewBox(0,0,200,200))", false), + new Test("svgView(preserveAspectRatio(xMaxYMin);preserveAspectRatio(xMaxYMin))", false), + new Test("svgView(transform(translate(0,200));transform(translate(0,200)))", false), // No invalid values allowed - new Test("svgView(viewBox(bad)", false, null, null, null), - new Test("svgView(preserveAspectRatio(bad))", false, null, null, null), - new Test("svgView(zoomAndPan(bad))", false, null, null, null), - new Test("svgView(transform(bad))", false, null, null, null), - new Test("svgView", false, null, null, null), - new Test("svgView(", false, null, null, null), - new Test("svgView()", false, null, null, null), + new Test("svgView(viewBox(bad)", false), + new Test("svgView(preserveAspectRatio(bad))", false), + new Test("svgView(zoomAndPan(bad))", false), + new Test("svgView(transform(bad))", false), + new Test("svgView", false), + new Test("svgView(", false), + new Test("svgView()", false), // Be sure we verify that there's a closing paren for svgView() // (and not too many closing parens) - new Test("svgView(zoomAndPan(disable)", false, null, null, null), - new Test("svgView(zoomAndPan(disable) ", false, null, null, null), - new Test("svgView(zoomAndPan(disable)]", false, null, null, null), - new Test("svgView(zoomAndPan(disable)))", false, null, null, null) + new Test("svgView(zoomAndPan(disable)", false), + new Test("svgView(zoomAndPan(disable) ", false), + new Test("svgView(zoomAndPan(disable)]", false), + new Test("svgView(zoomAndPan(disable)))", false) ]; var src = svg.getAttribute("src"); - for (var i = 0; i < tests.length; i++) { - var test = tests[i]; - svg.setAttribute("src", src + "#" + test.svgFragmentIdentifier); - is(doc.rootElement.useCurrentView, test.valid, - "Expected " + test.svgFragmentIdentifier + " to be " + - (test.valid ? "valid" : "invalid")); - is(doc.rootElement.getAttribute("viewBox"), - test.viewBoxString, "unexpected viewBox"); + is(false, rootElement.hasAttribute("viewBox"), + "expecting to start without a viewBox set"); + is(false, rootElement.hasAttribute("preserveAspectRatio"), + "expecting to start without preserveAspectRatio set"); + is(false, rootElement.hasAttribute("zoomAndPan"), + "expecting to start without zoomAndPan set"); - is(doc.rootElement.getAttribute("preserveAspectRatio"), - test.preserveAspectRatioString, "unexpected preserveAspectRatio"); + for (var j = 0; j < 2; j++) { + var initialViewBox = rootElement.getAttribute("viewBox"); + var initialPreserveAspectRatio = + rootElement.getAttribute("preserveAspectRatio"); + var initialZoomAndPan = rootElement.getAttribute("zoomAndPan"); + var initialTransform = rootElement.getAttribute("transform"); - is(doc.rootElement.getAttribute("zoomAndPan"), - test.zoomAndPanString, "unexpected zoomAndPan"); + for (var i = 0; i < tests.length; i++) { + var test = tests[i]; + svg.setAttribute("src", src + "#" + test.svgFragmentIdentifier); + is(rootElement.useCurrentView, test.valid, + "Expected " + test.svgFragmentIdentifier + " to be " + + (test.valid ? "valid" : "invalid")); + + // check that assigning a viewSpec does not modify the underlying + // attribute values. + is(rootElement.getAttribute("viewBox"), + initialViewBox, "unexpected viewBox"); + + is(rootElement.getAttribute("preserveAspectRatio"), + initialPreserveAspectRatio, "unexpected preserveAspectRatio"); + + is(rootElement.getAttribute("zoomAndPan"), + initialZoomAndPan, "unexpected zoomAndPan"); + + is(rootElement.getAttribute("transform"), + initialTransform, "unexpected transform"); + } + + // repeat tests with underlying attributes set to values + rootElement.setAttribute("viewBox", "0 0 100 100"); + rootElement.setAttribute("preserveAspectRatio", "none"); + rootElement.setAttribute("zoomAndPan", "disable"); + rootElement.setAttribute("transform", "translate(10,10)"); } SimpleTest.finish(); } -window.addEventListener("load", runTests, false); +$(svg).addEventListener("load", runTests, false); diff --git a/dom/webidl/MediaStreamTrack.webidl b/dom/webidl/MediaStreamTrack.webidl index b61c0a17c0..fea064e5db 100644 --- a/dom/webidl/MediaStreamTrack.webidl +++ b/dom/webidl/MediaStreamTrack.webidl @@ -54,6 +54,9 @@ dictionary MediaTrackConstraintSet { ConstrainLong viewportOffsetY; ConstrainLong viewportWidth; ConstrainLong viewportHeight; + ConstrainBoolean echoCancellation; + ConstrainBoolean mozNoiseSuppression; + ConstrainBoolean mozAutoGainControl; }; dictionary MediaTrackConstraints : MediaTrackConstraintSet { diff --git a/dom/webidl/MediaTrackSupportedConstraints.webidl b/dom/webidl/MediaTrackSupportedConstraints.webidl index dd193ce46e..31616ada72 100644 --- a/dom/webidl/MediaTrackSupportedConstraints.webidl +++ b/dom/webidl/MediaTrackSupportedConstraints.webidl @@ -16,7 +16,9 @@ dictionary MediaTrackSupportedConstraints { boolean volume; // to be supported boolean sampleRate; // to be supported boolean sampleSize; // to be supported - boolean echoCancellation; // to be supported + boolean echoCancellation = true; + boolean mozNoiseSuppression = true; + boolean mozAutoGainControl = true; boolean latency; // to be supported boolean deviceId = true; boolean groupId; // to be supported diff --git a/layout/base/GeometryUtils.cpp b/layout/base/GeometryUtils.cpp index 470a7c266c..74776eeb56 100644 --- a/layout/base/GeometryUtils.cpp +++ b/layout/base/GeometryUtils.cpp @@ -175,11 +175,22 @@ public: , mRelativeToBoxTopLeft(aRelativeToBoxTopLeft) , mBoxType(aBoxType) { + if (mBoxType == CSSBoxType::Margin) { + // Don't include the caption margin when computing margins for a + // table + mIncludeCaptionBoxForTable = false; + } } virtual void AddBox(nsIFrame* aFrame) override { nsIFrame* f = aFrame; + if (mBoxType == CSSBoxType::Margin && + f->GetType() == nsGkAtoms::tableFrame) { + // Margin boxes for table frames should be taken from the outer table + // frame, since that has the margin. + f = f->GetParent(); + } nsRect box = GetBoxRectForFrame(&f, mBoxType); nsPoint appUnits[4] = { box.TopLeft(), box.TopRight(), box.BottomRight(), box.BottomLeft() }; diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 25437904c6..501c07b8e1 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3561,9 +3561,11 @@ AddBoxesForFrame(nsIFrame* aFrame, if (pseudoType == nsCSSAnonBoxes::tableOuter) { AddBoxesForFrame(aFrame->PrincipalChildList().FirstChild(), aCallback); - nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild(); - if (kid) { - AddBoxesForFrame(kid, aCallback); + if (aCallback->mIncludeCaptionBoxForTable) { + nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild(); + if (kid) { + AddBoxesForFrame(kid, aCallback); + } } } else if (pseudoType == nsCSSAnonBoxes::mozAnonymousBlock || pseudoType == nsCSSAnonBoxes::mozAnonymousPositionedBlock || diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index fd57581027..7e79e6ea42 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1081,7 +1081,9 @@ public: class BoxCallback { public: + BoxCallback() : mIncludeCaptionBoxForTable(true) {} virtual void AddBox(nsIFrame* aFrame) = 0; + bool mIncludeCaptionBoxForTable; }; /** * Collect all CSS boxes associated with aFrame and its diff --git a/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html b/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html index 27bf1fd1cb..295c0b392f 100644 --- a/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html +++ b/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html @@ -245,7 +245,7 @@ That ensures the first quad's X position is not equal to the anonymous block's X >
- +
@@ -508,6 +508,10 @@ function runTest() { isEval("text.getBoxQuads({box:'margin'})[0].bounds.left", textX); isEval("text.getBoxQuads({box:'margin'})[1].bounds.left", textX - 150); + // Test table margins + isEval("table.getBoxQuads({box:'margin'}).length", 1); + isEval("table.getBoxQuads({box:'margin'})[0].bounds.height", 106); + // Check that a text node whose layout might have been optimized away gives // correct results. var suppressedTextContainerX = suppressedTextContainer.getBoundingClientRect().left; diff --git a/layout/media/symbols.def.in b/layout/media/symbols.def.in index 23314c390c..c7774d9aa0 100644 --- a/layout/media/symbols.def.in +++ b/layout/media/symbols.def.in @@ -127,6 +127,8 @@ moz_speex_resampler_skip_zeros moz_speex_resampler_set_skip_frac_num moz_speex_resampler_reset_mem cubeb_destroy +cubeb_device_collection_destroy +cubeb_enumerate_devices cubeb_init cubeb_get_backend_id cubeb_get_max_channel_count diff --git a/layout/reftests/svg/fragmentIdentifier-01.xhtml b/layout/reftests/svg/fragmentIdentifier-01.xhtml index f9ac51b988..9173f62fd5 100644 --- a/layout/reftests/svg/fragmentIdentifier-01.xhtml +++ b/layout/reftests/svg/fragmentIdentifier-01.xhtml @@ -6,6 +6,7 @@
+
diff --git a/layout/reftests/svg/fragmentIdentifier-rect-01.svg b/layout/reftests/svg/fragmentIdentifier-rect-01.svg index ec05f543ab..29edaa4917 100644 --- a/layout/reftests/svg/fragmentIdentifier-rect-01.svg +++ b/layout/reftests/svg/fragmentIdentifier-rect-01.svg @@ -1,9 +1,19 @@ + + + + + + diff --git a/layout/svg/nsSVGClipPathFrame.cpp b/layout/svg/nsSVGClipPathFrame.cpp index de306a6e70..5648657d96 100644 --- a/layout/svg/nsSVGClipPathFrame.cpp +++ b/layout/svg/nsSVGClipPathFrame.cpp @@ -59,8 +59,7 @@ nsSVGClipPathFrame::ApplyClipOrPaintClipMask(gfxContext& aContext, nsSVGPathGeometryElement* pathElement = static_cast(pathFrame->GetContent()); gfxMatrix toChildsUserSpace = pathElement-> - PrependLocalTransformsTo(mMatrixForChildren, - nsSVGElement::eUserSpaceToParent); + PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent); gfxMatrix newMatrix = aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers(); if (!newMatrix.IsSingular()) { @@ -137,8 +136,7 @@ nsSVGClipPathFrame::ApplyClipOrPaintClipMask(gfxContext& aContext, if (childContent->IsSVGElement()) { toChildsUserSpace = static_cast(childContent)-> - PrependLocalTransformsTo(mMatrixForChildren, - nsSVGElement::eUserSpaceToParent); + PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent); } SVGFrame->PaintSVG(aContext, toChildsUserSpace); @@ -242,7 +240,7 @@ nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame, if (SVGFrame) { gfxPoint pointForChild = point; gfxMatrix m = static_cast(kid->GetContent())-> - PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eUserSpaceToParent); + PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent); if (!m.IsIdentity()) { if (!m.Invert()) { return false; diff --git a/layout/svg/nsSVGContainerFrame.cpp b/layout/svg/nsSVGContainerFrame.cpp index 9f15f89ffb..bc3f2ebc0f 100644 --- a/layout/svg/nsSVGContainerFrame.cpp +++ b/layout/svg/nsSVGContainerFrame.cpp @@ -234,8 +234,9 @@ nsSVGDisplayContainerFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, if ((transformList && transformList->HasTransform()) || content->GetAnimateMotionTransform()) { if (aOwnTransform) { - *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), - nsSVGElement::eUserSpaceToParent)); + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo( + gfxMatrix(), eUserSpaceToParent)); } foundTransform = true; } @@ -265,8 +266,7 @@ nsSVGDisplayContainerFrame::PaintSVG(gfxContext& aContext, gfxMatrix matrix = aTransform; if (GetContent()->IsSVGElement()) { // must check before cast matrix = static_cast(GetContent())-> - PrependLocalTransformsTo(matrix, - nsSVGElement::eChildToUserSpace); + PrependLocalTransformsTo(matrix, eChildToUserSpace); if (matrix.IsSingular()) { return NS_OK; } @@ -283,8 +283,7 @@ nsSVGDisplayContainerFrame::PaintSVG(gfxContext& aContext, if (!element->HasValidDimensions()) { continue; // nothing to paint for kid } - m = element-> - PrependLocalTransformsTo(m, nsSVGElement::eUserSpaceToParent); + m = element->PrependLocalTransformsTo(m, eUserSpaceToParent); if (m.IsSingular()) { continue; } diff --git a/layout/svg/nsSVGForeignObjectFrame.cpp b/layout/svg/nsSVGForeignObjectFrame.cpp index cc4262cf19..7897967e3e 100644 --- a/layout/svg/nsSVGForeignObjectFrame.cpp +++ b/layout/svg/nsSVGForeignObjectFrame.cpp @@ -189,8 +189,9 @@ nsSVGForeignObjectFrame::IsSVGTransformed(Matrix *aOwnTransform, if ((transformList && transformList->HasTransform()) || content->GetAnimateMotionTransform()) { if (aOwnTransform) { - *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), - nsSVGElement::eUserSpaceToParent)); + *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo( + gfxMatrix(), + eUserSpaceToParent)); } foundTransform = true; } diff --git a/layout/svg/nsSVGOuterSVGFrame.cpp b/layout/svg/nsSVGOuterSVGFrame.cpp index 34f8295745..5b6089f481 100644 --- a/layout/svg/nsSVGOuterSVGFrame.cpp +++ b/layout/svg/nsSVGOuterSVGFrame.cpp @@ -967,7 +967,7 @@ nsSVGOuterSVGAnonChildFrame::IsSVGTransformed(Matrix* aOwnTransform, // Outer- doesn't use x/y, so we can pass eChildToUserSpace here. gfxMatrix ownMatrix = - content->PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eChildToUserSpace); + content->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); if (ownMatrix.IsIdentity()) { return false; diff --git a/layout/svg/nsSVGPathGeometryFrame.cpp b/layout/svg/nsSVGPathGeometryFrame.cpp index 82cbf7f9f0..8613c1fcfb 100644 --- a/layout/svg/nsSVGPathGeometryFrame.cpp +++ b/layout/svg/nsSVGPathGeometryFrame.cpp @@ -216,8 +216,10 @@ nsSVGPathGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, if ((transformList && transformList->HasTransform()) || content->GetAnimateMotionTransform()) { if (aOwnTransform) { - *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), - nsSVGElement::eUserSpaceToParent)); + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo( + gfxMatrix(), + eUserSpaceToParent)); } foundTransform = true; } diff --git a/layout/svg/nsSVGPatternFrame.cpp b/layout/svg/nsSVGPatternFrame.cpp index ce86115954..591c177475 100644 --- a/layout/svg/nsSVGPatternFrame.cpp +++ b/layout/svg/nsSVGPatternFrame.cpp @@ -407,7 +407,7 @@ nsSVGPatternFrame::PaintPattern(const DrawTarget* aDrawTarget, gfxMatrix tm = *(patternWithChildren->mCTM); if (kid->GetContent()->IsSVGElement()) { tm = static_cast(kid->GetContent())-> - PrependLocalTransformsTo(tm, nsSVGElement::eUserSpaceToParent); + PrependLocalTransformsTo(tm, eUserSpaceToParent); } nsSVGUtils::PaintFrameWithEffects(kid, *gfx, tm); } diff --git a/layout/svg/nsSVGSwitchFrame.cpp b/layout/svg/nsSVGSwitchFrame.cpp index 22309291e9..7b5fc87ea2 100644 --- a/layout/svg/nsSVGSwitchFrame.cpp +++ b/layout/svg/nsSVGSwitchFrame.cpp @@ -122,7 +122,7 @@ nsSVGSwitchFrame::PaintSVG(gfxContext& aContext, gfxMatrix tm = aTransform; if (kid->GetContent()->IsSVGElement()) { tm = static_cast(kid->GetContent())-> - PrependLocalTransformsTo(tm, nsSVGElement::eUserSpaceToParent); + PrependLocalTransformsTo(tm, eUserSpaceToParent); } nsSVGUtils::PaintFrameWithEffects(kid, aContext, tm, aDirtyRect); } @@ -145,9 +145,9 @@ nsSVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint) gfxPoint point = aPoint; gfxMatrix m = static_cast(mContent)-> - PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eChildToUserSpace); + PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); m = static_cast(kid->GetContent())-> - PrependLocalTransformsTo(m, nsSVGElement::eUserSpaceToParent); + PrependLocalTransformsTo(m, eUserSpaceToParent); if (!m.IsIdentity()) { if (!m.Invert()) { return nullptr; diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp index 6a43530dd0..5ed2457bb9 100644 --- a/layout/svg/nsSVGUtils.cpp +++ b/layout/svg/nsSVGUtils.cpp @@ -424,7 +424,7 @@ nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame) nsSVGElement *content = static_cast(aFrame->GetContent()); tm = content->PrependLocalTransformsTo( GetCanvasTM(aFrame->GetParent()), - nsSVGElement::eUserSpaceToParent); + eUserSpaceToParent); } return tm; } @@ -753,7 +753,7 @@ nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame, if (aFrame->GetContent()->IsSVGElement()) { // must check before cast gfxMatrix m = static_cast(aFrame->GetContent())-> PrependLocalTransformsTo(gfxMatrix(), - nsSVGElement::eChildToUserSpace); + eChildToUserSpace); if (!m.IsIdentity()) { if (!m.Invert()) { return nullptr; @@ -781,7 +781,7 @@ nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame, if (content->IsSVGElement()) { // must check before cast gfxMatrix m = static_cast(content)-> PrependLocalTransformsTo(gfxMatrix(), - nsSVGElement::eUserSpaceToParent); + eUserSpaceToParent); if (!m.IsIdentity()) { if (!m.Invert()) { continue; @@ -968,8 +968,7 @@ nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags) // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. MOZ_ASSERT(content->IsSVGElement(), "bad cast"); nsSVGElement *element = static_cast(content); - matrix = element->PrependLocalTransformsTo(matrix, - nsSVGElement::eChildToUserSpace); + matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace); } bbox = svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); // Account for 'clipped'. @@ -1058,8 +1057,7 @@ nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame) if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame || aFrame->GetType() == nsGkAtoms::svgUseFrame) { gfxMatrix transform = static_cast(aFrame->GetContent())-> - PrependLocalTransformsTo(gfxMatrix(), - nsSVGElement::eChildToUserSpace); + PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); return transform.GetTranslation(); } @@ -1660,7 +1658,7 @@ nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext, // PaintSVG() expects the passed transform to be the transform to its own // SVG user space, so we need to account for any 'transform' attribute: m = static_cast(frame->GetContent())-> - PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eUserSpaceToParent); + PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent); } nsresult rv = svgFrame->PaintSVG(*aContext, m); return NS_SUCCEEDED(rv); diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS index 394e9ae676..0fde65baad 100644 --- a/media/libcubeb/AUTHORS +++ b/media/libcubeb/AUTHORS @@ -6,3 +6,10 @@ David Richards Sebastien Alaiwan KO Myung-Hun Haakon Sporsheim +Alex Chronopoulos +Jan Beich +Vito Caputo +Landry Breuil +Jacek Caban +Paul Hancock +Ted Mielczarek diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA index 2578a03f51..5bda9ae428 100644 --- a/media/libcubeb/README_MOZILLA +++ b/media/libcubeb/README_MOZILLA @@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system. The cubeb git repository is: git://github.com/kinetiknz/cubeb.git -The git commit ID used was 77745f635240a5a85a2464bd3758c231045ac3d7. +The git commit ID used was c438f775a69cdc8ba6d7d543073c3ccf982050b8. diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index 4c016e0d23..8b17790bf9 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -118,6 +118,10 @@ typedef enum { } cubeb_stream_type; #endif +/** An opaque handle used to refer a particular input or output device + * across calls. */ +typedef void * cubeb_devid; + /** Stream format initialization parameters. */ typedef struct { cubeb_sample_format format; /**< Requested sample format. One of @@ -149,41 +153,61 @@ enum { CUBEB_ERROR = -1, /**< Unclassified error. */ CUBEB_ERROR_INVALID_FORMAT = -2, /**< Unsupported #cubeb_stream_params requested. */ CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */ - CUBEB_ERROR_NOT_SUPPORTED = -4 /**< Optional function not implemented in current backend. */ + CUBEB_ERROR_NOT_SUPPORTED = -4, /**< Optional function not implemented in current backend. */ + CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */ }; +/** + * Whether a particular device is an input device (e.g. a microphone), or an + * output device (e.g. headphones). */ typedef enum { CUBEB_DEVICE_TYPE_UNKNOWN, CUBEB_DEVICE_TYPE_INPUT, CUBEB_DEVICE_TYPE_OUTPUT } cubeb_device_type; +/** + * The state of a device. + */ typedef enum { - CUBEB_DEVICE_STATE_DISABLED, - CUBEB_DEVICE_STATE_UNPLUGGED, - CUBEB_DEVICE_STATE_ENABLED + CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system level. */ + CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is plugged into it. */ + CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */ } cubeb_device_state; -typedef void * cubeb_devid; - +/** + * Architecture specific sample type. + */ typedef enum { - CUBEB_DEVICE_FMT_S16LE = 0x0010, - CUBEB_DEVICE_FMT_S16BE = 0x0020, - CUBEB_DEVICE_FMT_F32LE = 0x1000, - CUBEB_DEVICE_FMT_F32BE = 0x2000 + CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */ + CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */ + CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */ + CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */ } cubeb_device_fmt; #if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) +/** 16-bit integers, native endianess, when on a Big Endian environment. */ #define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE +/** 32-bit floating points, native endianess, when on a Big Endian environment. */ #define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE #else +/** 16-bit integers, native endianess, when on a Little Endian environment. */ #define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE +/** 32-bit floating points, native endianess, when on a Little Endian + * environment. */ #define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE #endif +/** All the 16-bit integers types. */ #define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE) +/** All the 32-bit floating points types. */ #define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE) +/** All the device formats types. */ #define CUBEB_DEVICE_FMT_ALL (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK) +/** Channel type for a `cubeb_stream`. Depending on the backend and platform + * used, this can control inter-stream interruption, ducking, and volume + * control. + */ typedef enum { CUBEB_DEVICE_PREF_NONE = 0x00, CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01, @@ -192,26 +216,30 @@ typedef enum { CUBEB_DEVICE_PREF_ALL = 0x0F } cubeb_device_pref; +/** This structure holds the characteristics + * of an input or output audio device. It can be obtained using + * `cubeb_enumerate_devices`, and must be destroyed using + * `cubeb_device_info_destroy`. */ typedef struct { - cubeb_devid devid; /* Device identifier handle */ - char * device_id; /* Device identifier which might be presented in a UI */ - char * friendly_name; /* Friendly device name which might be presented in a UI */ - char * group_id; /* Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */ - char * vendor_name; /* Optional vendor name, may be NULL */ + cubeb_devid devid; /**< Device identifier handle. */ + char * device_id; /**< Device identifier which might be presented in a UI. */ + char * friendly_name; /**< Friendly device name which might be presented in a UI. */ + char * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */ + char * vendor_name; /**< Optional vendor name, may be NULL. */ - cubeb_device_type type; /* Type of device (Input/Output) */ - cubeb_device_state state; /* State of device disabled/enabled/unplugged */ - cubeb_device_pref preferred;/* Preferred device */ + cubeb_device_type type; /**< Type of device (Input/Output). */ + cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */ + cubeb_device_pref preferred;/**< Preferred device. */ - cubeb_device_fmt format; /* Sample format supported */ - cubeb_device_fmt default_format; - unsigned int max_channels; /* Channels */ - unsigned int default_rate; /* Default/Preferred sample rate */ - unsigned int max_rate; /* Maximum sample rate supported */ - unsigned int min_rate; /* Minimum sample rate supported */ + cubeb_device_fmt format; /**< Sample format supported. */ + cubeb_device_fmt default_format; /**< The default sample format for this device. */ + unsigned int max_channels; /**< Channels. */ + unsigned int default_rate; /**< Default/Preferred sample rate. */ + unsigned int max_rate; /**< Maximum sample rate supported. */ + unsigned int min_rate; /**< Minimum sample rate supported. */ - unsigned int latency_lo_ms; /* Lowest possible latency in milliseconds */ - unsigned int latency_hi_ms; /* Higest possible latency in milliseconds */ + unsigned int latency_lo_ms; /**< Lowest possible latency in milliseconds. */ + unsigned int latency_hi_ms; /**< Higest possible latency in milliseconds. */ } cubeb_device_info; /** Device collection. */ @@ -221,17 +249,21 @@ typedef struct { } cubeb_device_collection; /** User supplied data callback. - @param stream - @param user_ptr - @param buffer - @param nframes - @retval Number of frames written to buffer, which must equal nframes except - at end of stream. + @param stream The stream for which this callback fired + @param user_ptr The pointer passed to cubeb_stream_create + @param input_buffer A pointer containing the input data, or nullptr + if this is an output-only stream. + @param output_buffer A pointer containing the output data, or nullptr + if this is an input -only stream. + @param nframes The number of frames of the two buffer. + @retval Number of frames written to the output buffer, which must equal + nframes except at end of stream. @retval CUBEB_ERROR on error, in which case the data callback will stop and the stream will enter a shutdown state. */ typedef long (* cubeb_data_callback)(cubeb_stream * stream, void * user_ptr, - void * buffer, + const void * input_buffer, + void * output_buffer, long nframes); /** User supplied state callback. @@ -306,7 +338,14 @@ void cubeb_destroy(cubeb * context); @param context @param stream @param stream_name - @param stream_params + @param input_device Device for the input side of the stream. If NULL + default input device is used. + @param input_stream_params Parameters for the input side of the stream, or + NULL if this stream is output only. + @param output_device Device for the output side of the stream. If NULL + default output device is used. + @param output_stream_params Parameters for the output side of the stream, or + NULL if this stream is input only. @param latency Approximate stream latency in milliseconds. Valid range is [1, 2000]. @param data_callback Will be called to preroll data before playback is @@ -315,11 +354,15 @@ void cubeb_destroy(cubeb * context); @param user_ptr @retval CUBEB_OK @retval CUBEB_ERROR - @retval CUBEB_ERROR_INVALID_FORMAT */ + @retval CUBEB_ERROR_INVALID_FORMAT + @retval CUBEB_ERROR_DEVICE_UNAVAILABLE */ int cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, @@ -409,7 +452,7 @@ int cubeb_stream_device_destroy(cubeb_stream * stream, device_changed_callback are invalid pointers. @retval CUBEB_ERROR_NOT_SUPPORTED */ int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, - cubeb_device_changed_callback device_changed_callback); + cubeb_device_changed_callback device_changed_callback); /** Returns enumerated devices. @param context @@ -422,7 +465,7 @@ int cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype, cubeb_device_collection ** collection); -/** Destroy a cubeb_device_collection. +/** Destroy a cubeb_device_collection, and its `cubeb_device_info`. @param collection collection to destroy @retval CUBEB_OK @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */ @@ -437,12 +480,14 @@ int cubeb_device_info_destroy(cubeb_device_info * info); /** Registers a callback which is called when the system detects a new device or a device is removed. @param context + @param devtype device type to include @param callback a function called whenever the system device list changes. Passing NULL allow to unregister a function @param user_ptr pointer to user specified data which will be present in subsequent callbacks. @retval CUBEB_ERROR_NOT_SUPPORTED */ int cubeb_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, cubeb_device_collection_changed_callback callback, void * user_ptr); diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h index 323510712b..15acbd21e4 100644 --- a/media/libcubeb/src/cubeb-internal.h +++ b/media/libcubeb/src/cubeb-internal.h @@ -22,8 +22,14 @@ struct cubeb_ops { int (* enumerate_devices)(cubeb * context, cubeb_device_type type, cubeb_device_collection ** collection); void (* destroy)(cubeb * context); - int (* stream_init)(cubeb * context, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, + int (* stream_init)(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr); @@ -40,6 +46,10 @@ struct cubeb_ops { cubeb_device * device); int (* stream_register_device_changed_callback)(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback); + int (* register_device_collection_changed)(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback callback, + void * user_ptr); }; #define XASSERT(expr) do { \ diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index f22e8a9018..5f5e92bb11 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -61,15 +61,36 @@ int audiotrack_init(cubeb ** context, char const * context_name); int kai_init(cubeb ** context, char const * context_name); #endif + int -validate_stream_params(cubeb_stream_params stream_params) +validate_stream_params(cubeb_stream_params * input_stream_params, + cubeb_stream_params * output_stream_params) { - if (stream_params.rate < 1000 || stream_params.rate > 192000 || - stream_params.channels < 1 || stream_params.channels > 8) { - return CUBEB_ERROR_INVALID_FORMAT; + if (output_stream_params) { + if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 || + output_stream_params->channels < 1 || output_stream_params->channels > 8) { + return CUBEB_ERROR_INVALID_FORMAT; + } + } + if (input_stream_params) { + if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 || + input_stream_params->channels < 1 || input_stream_params->channels > 8) { + return CUBEB_ERROR_INVALID_FORMAT; + } + } + // Rate and sample format must be the same for input and output, if using a + // duplex stream + if (input_stream_params && output_stream_params) { + if (input_stream_params->rate != output_stream_params->rate || + input_stream_params->format != output_stream_params->format) { + return CUBEB_ERROR_INVALID_FORMAT; + } } - switch (stream_params.format) { + cubeb_stream_params * params = input_stream_params ? + input_stream_params : output_stream_params; + + switch (params->format) { case CUBEB_SAMPLE_S16LE: case CUBEB_SAMPLE_S16BE: case CUBEB_SAMPLE_FLOAT32LE: @@ -80,6 +101,8 @@ validate_stream_params(cubeb_stream_params stream_params) return CUBEB_ERROR_INVALID_FORMAT; } + + int validate_latency(int latency) { @@ -218,7 +241,11 @@ cubeb_destroy(cubeb * context) int cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) @@ -229,13 +256,17 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return CUBEB_ERROR_INVALID_PARAMETER; } - if ((r = validate_stream_params(stream_params)) != CUBEB_OK || + if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK || (r = validate_latency(latency)) != CUBEB_OK) { return r; } return context->ops->stream_init(context, stream, stream_name, - stream_params, latency, + input_device, + input_stream_params, + output_device, + output_stream_params, + latency, data_callback, state_callback, user_ptr); @@ -404,9 +435,17 @@ int cubeb_device_info_destroy(cubeb_device_info * info) } int cubeb_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, cubeb_device_collection_changed_callback callback, void * user_ptr) { - return CUBEB_ERROR_NOT_SUPPORTED; + if (context == NULL || (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) + return CUBEB_ERROR_INVALID_PARAMETER; + + if (!context->ops->register_device_collection_changed) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return context->ops->register_device_collection_changed(context, devtype, callback, user_ptr); } diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index 408fc6333d..f71dea6afe 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -85,7 +85,6 @@ struct cubeb_stream { snd_pcm_uframes_t write_position; snd_pcm_uframes_t last_position; snd_pcm_uframes_t buffer_size; - snd_pcm_uframes_t period_size; cubeb_stream_params params; /* Every member after this comment is protected by the owning context's @@ -307,7 +306,7 @@ alsa_refill_stream(cubeb_stream * stm) assert(p); pthread_mutex_unlock(&stm->mutex); - got = stm->data_callback(stm, stm->user_ptr, p, avail); + got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail); pthread_mutex_lock(&stm->mutex); if (got < 0) { pthread_mutex_unlock(&stm->mutex); @@ -787,19 +786,30 @@ static void alsa_stream_destroy(cubeb_stream * stm); static int alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { cubeb_stream * stm; int r; snd_pcm_format_t format; + snd_pcm_uframes_t period_size; assert(ctx && stream); + assert(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + *stream = NULL; - switch (stream_params.format) { + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: format = SND_PCM_FORMAT_S16_LE; break; @@ -831,7 +841,7 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->params = stream_params; + stm->params = *output_stream_params; stm->state = INACTIVE; stm->volume = 1.0; @@ -862,7 +872,7 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, return CUBEB_ERROR_INVALID_FORMAT; } - r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &stm->period_size); + r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size); assert(r == 0); stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); @@ -938,7 +948,7 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) assert(ctx); - r = alsa_stream_init(ctx, &stm, "", params, 100, NULL, NULL, NULL); + r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, ¶ms, 100, NULL, NULL, NULL); if (r != CUBEB_OK) { return CUBEB_ERROR; } @@ -1133,5 +1143,6 @@ static struct cubeb_ops const alsa_ops = { .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL }; diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c index c1ce93c696..31422a160d 100644 --- a/media/libcubeb/src/cubeb_audiotrack.c +++ b/media/libcubeb/src/cubeb_audiotrack.c @@ -105,12 +105,11 @@ audiotrack_refill(int event, void* user, void* info) return; } - got = stream->data_callback(stream, stream->user_ptr, b->raw, b->frameCount); + got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, b->frameCount); stream->written += got; if (got != (long)b->frameCount) { - uint32_t p; stream->draining = 1; /* set a marker so we are notified when the are done draining, that is, * when every frame has been played by android. */ @@ -332,7 +331,11 @@ audiotrack_destroy(cubeb * context) int audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) @@ -343,12 +346,18 @@ audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_ assert(ctx && stream); - if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE || - stream_params.format == CUBEB_SAMPLE_FLOAT32BE) { + assert(!input_stream_params && "not supported"); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + + if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE || + output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) { return CUBEB_ERROR_INVALID_FORMAT; } - if (audiotrack_get_min_frame_count(ctx, &stream_params, (int *)&min_frame_count)) { + if (audiotrack_get_min_frame_count(ctx, output_stream_params, (int *)&min_frame_count)) { return CUBEB_ERROR; } @@ -359,7 +368,7 @@ audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_ stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->params = stream_params; + stm->params = *output_stream_params; stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1); (*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad; @@ -501,5 +510,6 @@ static struct cubeb_ops const audiotrack_ops = { .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL }; diff --git a/media/libcubeb/src/cubeb_audiounit.c b/media/libcubeb/src/cubeb_audiounit.c index 7506107792..ed7c2ed286 100644 --- a/media/libcubeb/src/cubeb_audiounit.c +++ b/media/libcubeb/src/cubeb_audiounit.c @@ -136,7 +136,7 @@ audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, } pthread_mutex_unlock(&stm->mutex); - got = stm->data_callback(stm, stm->user_ptr, buf, nframes); + got = stm->data_callback(stm, stm->user_ptr, NULL, buf, nframes); pthread_mutex_lock(&stm->mutex); if (got < 0) { /* XXX handle this case. */ @@ -539,7 +539,11 @@ static void audiounit_stream_destroy(cubeb_stream * stm); static int audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { @@ -558,13 +562,19 @@ audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stre UInt32 size; AudioValueRange latency_range; + assert(!input_stream_params && "not supported"); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + assert(context); *stream = NULL; memset(&ss, 0, sizeof(ss)); ss.mFormatFlags = 0; - switch (stream_params.format) { + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: ss.mBitsPerChannel = 16; ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger; @@ -589,8 +599,8 @@ audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stre ss.mFormatID = kAudioFormatLinearPCM; ss.mFormatFlags |= kLinearPCMFormatFlagIsPacked; - ss.mSampleRate = stream_params.rate; - ss.mChannelsPerFrame = stream_params.channels; + ss.mSampleRate = output_stream_params->rate; + ss.mChannelsPerFrame = output_stream_params->channels; ss.mBytesPerFrame = (ss.mBitsPerChannel / 8) * ss.mChannelsPerFrame; ss.mFramesPerPacket = 1; @@ -1307,5 +1317,6 @@ static struct cubeb_ops const audiounit_ops = { .stream_set_panning = audiounit_stream_set_panning, .stream_get_current_device = audiounit_stream_get_current_device, .stream_device_destroy = audiounit_stream_device_destroy, - .stream_register_device_changed_callback = audiounit_stream_register_device_changed_callback + .stream_register_device_changed_callback = audiounit_stream_register_device_changed_callback, + .register_device_collection_changed = NULL }; diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index 37d6af71d7..cd98126206 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -11,6 +11,7 @@ #include #include #include +#include #if defined(__ANDROID__) #include #include "android/sles_definitions.h" @@ -70,6 +71,9 @@ struct cubeb_stream { unsigned int inputrate; unsigned int outputrate; unsigned int latency; + int64_t lastPosition; + int64_t lastPositionTimeStamp; + int64_t lastCompensativePosition; }; static void @@ -116,7 +120,7 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) pthread_mutex_unlock(&stm->mutex); if (!draining) { - written = cubeb_resampler_fill(stm->resampler, buf, + written = cubeb_resampler_fill(stm->resampler, NULL, buf, stm->queuebuf_len / stm->framesize); if (written < 0 || written * stm->framesize > stm->queuebuf_len) { (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); @@ -461,17 +465,26 @@ static void opensl_stream_destroy(cubeb_stream * stm); static int opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { cubeb_stream * stm; assert(ctx); + assert(!input_stream_params && "not supported"); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } *stream = NULL; - if (stream_params.channels < 1 || stream_params.channels > 32 || + if (output_stream_params->channels < 1 || output_stream_params->channels > 32 || latency < 1 || latency > 2000) { return CUBEB_ERROR_INVALID_FORMAT; } @@ -479,16 +492,16 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name SLDataFormat_PCM format; format.formatType = SL_DATAFORMAT_PCM; - format.numChannels = stream_params.channels; + format.numChannels = output_stream_params->channels; // samplesPerSec is in milliHertz - format.samplesPerSec = stream_params.rate * 1000; + format.samplesPerSec = output_stream_params->rate * 1000; format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format.channelMask = stream_params.channels == 1 ? + format.channelMask = output_stream_params->channels == 1 ? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - switch (stream_params.format) { + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: format.endianness = SL_BYTEORDER_LITTLEENDIAN; break; @@ -507,10 +520,13 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->inputrate = stream_params.rate; + stm->inputrate = output_stream_params->rate; stm->latency = latency; - stm->stream_type = stream_params.stream_type; - stm->framesize = stream_params.channels * sizeof(int16_t); + stm->stream_type = output_stream_params->stream_type; + stm->framesize = output_stream_params->channels * sizeof(int16_t); + stm->lastPosition = -1; + stm->lastPositionTimeStamp = 0; + stm->lastCompensativePosition = -1; int r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); @@ -568,7 +584,7 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize); } - stm->resampler = cubeb_resampler_create(stm, stream_params, + stm->resampler = cubeb_resampler_create(stm, *output_stream_params, preferred_sampling_rate, data_callback, stm->queuebuf_len / stm->framesize, @@ -587,7 +603,7 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name } #if defined(__ANDROID__) - SLuint32 stream_type = convert_stream_type_to_sl_stream(stream_params.stream_type); + SLuint32 stream_type = convert_stream_type_to_sl_stream(output_stream_params->stream_type); if (stream_type != 0xFFFFFFFF) { SLAndroidConfigurationItf playerConfig; res = (*stm->playerObj)->GetInterface(stm->playerObj, @@ -634,6 +650,9 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name return CUBEB_ERROR; } + // Work around wilhelm/AudioTrack badness, bug 1221228 + (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0); + res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER); if (res != SL_RESULT_SUCCESS) { opensl_stream_destroy(stm); @@ -646,6 +665,17 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name return CUBEB_ERROR; } + { + // Enqueue a silent frame so once the player becomes playing, the frame + // will be consumed and kick off the buffer queue callback. + // Note the duration of a single frame is less than 1ms. We don't bother + // adjusting the playback position. + uint8_t *buf = stm->queuebuf[stm->queuebuf_idx++]; + memset(buf, 0, stm->framesize); + res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize); + assert(res == SL_RESULT_SUCCESS); + } + *stream = stm; return CUBEB_OK; } @@ -669,9 +699,6 @@ opensl_stream_destroy(cubeb_stream * stm) static int opensl_stream_start(cubeb_stream * stm) { - /* To refill the queues before starting playback in order to avoid racing - * with refills started by SetPlayState on OpenSLES ndk threads. */ - bufferqueue_callback(NULL, stm); SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); if (res != SL_RESULT_SUCCESS) return CUBEB_ERROR; @@ -697,11 +724,22 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) SLresult res; int r; uint32_t mixer_latency; + uint32_t compensation_msec = 0; res = (*stm->play)->GetPosition(stm->play, &msec); if (res != SL_RESULT_SUCCESS) return CUBEB_ERROR; + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + if(stm->lastPosition == msec) { + compensation_msec = + (t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000; + } else { + stm->lastPositionTimeStamp = t.tv_sec*1000000000LL + t.tv_nsec; + stm->lastPosition = msec; + } + samplerate = stm->inputrate; r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); @@ -715,7 +753,16 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) assert(maximum_position >= 0); if (msec > mixer_latency) { - int64_t unadjusted_position = samplerate * (msec - mixer_latency) / 1000; + int64_t unadjusted_position; + if (stm->lastCompensativePosition > msec + compensation_msec) { + // Over compensation, use lastCompensativePosition. + unadjusted_position = + samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000; + } else { + unadjusted_position = + samplerate * (msec - mixer_latency + compensation_msec) / 1000; + stm->lastCompensativePosition = msec + compensation_msec; + } *position = unadjusted_position < maximum_position ? unadjusted_position : maximum_position; } else { @@ -791,5 +838,6 @@ static struct cubeb_ops const opensl_ops = { .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL }; diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index ef35661757..f523befd3e 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -70,12 +70,30 @@ X(pa_threaded_mainloop_unlock) \ X(pa_threaded_mainloop_wait) \ X(pa_usec_to_bytes) \ + X(pa_stream_set_read_callback) \ + X(pa_stream_connect_record) \ + X(pa_stream_readable_size) \ + X(pa_stream_peek) \ + X(pa_stream_drop) \ + X(pa_stream_get_buffer_attr) \ + X(pa_stream_get_device_name) \ + X(pa_context_set_subscribe_callback) \ + X(pa_context_subscribe) \ #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; LIBPULSE_API_VISIT(MAKE_TYPEDEF); #undef MAKE_TYPEDEF #endif +//#define LOGGING_ENABLED +#ifdef LOGGING_ENABLED +#define LOG(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + } while(0) +#else +#define LOG(...) +#endif + static struct cubeb_ops const pulse_ops; struct cubeb { @@ -86,16 +104,20 @@ struct cubeb { pa_sink_info * default_sink_info; char * context_name; int error; + cubeb_device_collection_changed_callback collection_changed_callback; + void * collection_changed_user_ptr; }; struct cubeb_stream { cubeb * context; - pa_stream * stream; + pa_stream * output_stream; + pa_stream * input_stream; cubeb_data_callback data_callback; cubeb_state_callback state_callback; void * user_ptr; pa_time_event * drain_timer; - pa_sample_spec sample_spec; + pa_sample_spec output_sample_spec; + pa_sample_spec input_sample_spec; int shutdown; float volume; }; @@ -113,6 +135,7 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi { cubeb * ctx = u; if (!eol) { + free(ctx->default_sink_info); ctx->default_sink_info = malloc(sizeof(pa_sink_info)); memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info)); } @@ -170,46 +193,46 @@ stream_state_callback(pa_stream * s, void * u) } static void -stream_request_callback(pa_stream * s, size_t nbytes, void * u) +trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cubeb_stream * stm) { - cubeb_stream * stm; void * buffer; size_t size; int r; long got; - size_t towrite; + size_t towrite, read_offset; size_t frame_size; - stm = u; - - if (stm->shutdown) - return; - - frame_size = WRAP(pa_frame_size)(&stm->sample_spec); - + frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec); assert(nbytes % frame_size == 0); towrite = nbytes; - + read_offset = 0; while (towrite) { size = towrite; r = WRAP(pa_stream_begin_write)(s, &buffer, &size); + // Note: this has failed running under rr on occassion - needs investigation. assert(r == 0); assert(size > 0); assert(size % frame_size == 0); - got = stm->data_callback(stm, stm->user_ptr, buffer, size / frame_size); + LOG("Trigger user callback with output buffer size=%zd, read_offset=%zd\n", size, read_offset); + got = stm->data_callback(stm, stm->user_ptr, (uint8_t const *)input_data + read_offset, buffer, size / frame_size); if (got < 0) { WRAP(pa_stream_cancel_write)(s); stm->shutdown = 1; return; } + // If more iterations move offset of read buffer + if (input_data) { + size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec); + read_offset += (size / frame_size) * in_frame_size; + } if (stm->volume != PULSE_NO_GAIN) { - uint32_t samples = size * stm->sample_spec.channels / frame_size ; + uint32_t samples = size * stm->output_sample_spec.channels / frame_size ; - if (stm->sample_spec.format == PA_SAMPLE_S16BE || - stm->sample_spec.format == PA_SAMPLE_S16LE) { + if (stm->output_sample_spec.format == PA_SAMPLE_S16BE || + stm->output_sample_spec.format == PA_SAMPLE_S16LE) { short * b = buffer; for (uint32_t i = 0; i < samples; i++) { b[i] *= stm->volume; @@ -246,6 +269,78 @@ stream_request_callback(pa_stream * s, size_t nbytes, void * u) assert(towrite == 0); } +static int +read_from_input(pa_stream * s, void const ** buffer, size_t * size) +{ + size_t readable_size = WRAP(pa_stream_readable_size)(s); + if (readable_size > 0) { + if (WRAP(pa_stream_peek)(s, buffer, size) < 0) { + return -1; + } + } + return readable_size; +} + +static void +stream_write_callback(pa_stream * s, size_t nbytes, void * u) +{ + LOG("Output callback to be written buffer size %zd\n", nbytes); + cubeb_stream * stm = u; + if (stm->shutdown) { + return; + } + + if (!stm->input_stream){ + // Output/playback only operation. + // Write directly to output + assert(!stm->input_stream && stm->output_stream); + trigger_user_callback(s, NULL, nbytes, stm); + } +} + +static void +stream_read_callback(pa_stream * s, size_t nbytes, void * u) +{ + LOG("Input callback buffer size %zd\n", nbytes); + cubeb_stream * stm = u; + if (stm->shutdown) { + return; + } + + void const * read_data = NULL; + size_t read_size; + while (read_from_input(s, &read_data, &read_size) > 0) { + /* read_data can be NULL in case of a hole. */ + if (read_data) { + size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec); + size_t read_frames = read_size / in_frame_size; + + if (stm->output_stream) { + // input/capture + output/playback operation + size_t out_frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec); + size_t write_size = read_frames * out_frame_size; + // Offer full duplex data for writing + trigger_user_callback(stm->output_stream, read_data, write_size, stm); + } else { + // input/capture only operation. Call callback directly + long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL, read_frames); + if (got < 0 || (size_t) got != read_frames) { + WRAP(pa_stream_cancel_write)(s); + stm->shutdown = 1; + break; + } + } + } + if (read_size > 0) { + WRAP(pa_stream_drop)(s); + } + + if (stm->shutdown) { + return; + } + } +} + static int wait_until_context_ready(cubeb * ctx) { @@ -261,15 +356,32 @@ wait_until_context_ready(cubeb * ctx) } static int -wait_until_stream_ready(cubeb_stream * stm) +wait_until_io_stream_ready(pa_stream * stream, pa_threaded_mainloop * mainloop) { + if (!stream || !mainloop){ + return -1; + } for (;;) { - pa_stream_state_t state = WRAP(pa_stream_get_state)(stm->stream); + pa_stream_state_t state = WRAP(pa_stream_get_state)(stream); if (!PA_STREAM_IS_GOOD(state)) return -1; if (state == PA_STREAM_READY) break; - WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop); + WRAP(pa_threaded_mainloop_wait)(mainloop); + } + return 0; +} + +static int +wait_until_stream_ready(cubeb_stream * stm) +{ + if (stm->output_stream && + wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) == -1) { + return -1; + } + if(stm->input_stream && + wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) == -1) { + return -1; } return 0; } @@ -290,16 +402,25 @@ operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o) } static void -stream_cork(cubeb_stream * stm, enum cork_state state) +cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state) { pa_operation * o; - - WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); - o = WRAP(pa_stream_cork)(stm->stream, state & CORK, stream_success_callback, stm); + if (!io_stream) { + return; + } + o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback, stm); if (o) { - operation_wait(stm->context, stm->stream, o); + operation_wait(stm->context, io_stream, o); WRAP(pa_operation_unref)(o); } +} + +static void +stream_cork(cubeb_stream * stm, enum cork_state state) +{ + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + cork_io_stream(stm, stm->output_stream, state); + cork_io_stream(stm, stm->input_stream, state); WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); if (state & NOTIFY) { @@ -308,6 +429,33 @@ stream_cork(cubeb_stream * stm, enum cork_state state) } } +static int +stream_update_timing_info(cubeb_stream * stm) +{ + int r = -1; + pa_operation * o = NULL; + if (stm->output_stream) { + o = WRAP(pa_stream_update_timing_info)(stm->output_stream, stream_success_callback, stm); + if (o) { + r = operation_wait(stm->context, stm->output_stream, o); + WRAP(pa_operation_unref)(o); + } + if (r != 0) { + return r; + } + } + + if (stm->input_stream) { + o = WRAP(pa_stream_update_timing_info)(stm->input_stream, stream_success_callback, stm); + if (o) { + r = operation_wait(stm->context, stm->input_stream, o); + WRAP(pa_operation_unref)(o); + } + } + + return r; +} + static void pulse_context_destroy(cubeb * ctx); static void pulse_destroy(cubeb * ctx); @@ -485,88 +633,145 @@ pulse_destroy(cubeb * ctx) static void pulse_stream_destroy(cubeb_stream * stm); +pa_sample_format_t +cubeb_to_pulse_format(cubeb_sample_format format) +{ + switch (format) { + case CUBEB_SAMPLE_S16LE: + return PA_SAMPLE_S16LE; + case CUBEB_SAMPLE_S16BE: + return PA_SAMPLE_S16BE; + case CUBEB_SAMPLE_FLOAT32LE: + return PA_SAMPLE_FLOAT32LE; + case CUBEB_SAMPLE_FLOAT32BE: + return PA_SAMPLE_FLOAT32BE; + default: + return PA_SAMPLE_INVALID; + } +} + static int -pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, - cubeb_data_callback data_callback, cubeb_state_callback state_callback, +create_pa_stream(cubeb_stream * stm, + pa_stream ** pa_stm, + cubeb_stream_params * stream_params, + char const * stream_name) +{ + assert(stm && stream_params); + *pa_stm = NULL; + pa_sample_spec ss; + ss.format = cubeb_to_pulse_format(stream_params->format); + if (ss.format == PA_SAMPLE_INVALID) + return CUBEB_ERROR_INVALID_FORMAT; + ss.rate = stream_params->rate; + ss.channels = stream_params->channels; + + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); + return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK; +} + +static pa_buffer_attr +set_buffering_attribute(unsigned int latency, pa_sample_spec * sample_spec) +{ + pa_buffer_attr battr; + battr.maxlength = -1; + battr.prebuf = -1; + battr.tlength = WRAP(pa_usec_to_bytes)(latency * PA_USEC_PER_MSEC, sample_spec); + battr.minreq = battr.tlength / 4; + battr.fragsize = battr.minreq; + + LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u\n", + battr.maxlength, battr.tlength, battr.prebuf, battr.minreq, battr.fragsize); + + return battr; +} + +static int +pulse_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, void * user_ptr) { - pa_sample_spec ss; cubeb_stream * stm; - pa_operation * o; pa_buffer_attr battr; int r; assert(context); - *stream = NULL; - - switch (stream_params.format) { - case CUBEB_SAMPLE_S16LE: - ss.format = PA_SAMPLE_S16LE; - break; - case CUBEB_SAMPLE_S16BE: - ss.format = PA_SAMPLE_S16BE; - break; - case CUBEB_SAMPLE_FLOAT32LE: - ss.format = PA_SAMPLE_FLOAT32LE; - break; - case CUBEB_SAMPLE_FLOAT32BE: - ss.format = PA_SAMPLE_FLOAT32BE; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - // If the connection failed for some reason, try to reconnect if (context->error == 1 && pulse_context_init(context) != 0) { return CUBEB_ERROR; } - ss.rate = stream_params.rate; - ss.channels = stream_params.channels; + *stream = NULL; stm = calloc(1, sizeof(*stm)); assert(stm); stm->context = context; - stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - - stm->sample_spec = ss; stm->volume = PULSE_NO_GAIN; - battr.maxlength = -1; - battr.tlength = WRAP(pa_usec_to_bytes)(latency * PA_USEC_PER_MSEC, &stm->sample_spec); - battr.prebuf = -1; - battr.minreq = battr.tlength / 4; - battr.fragsize = -1; - WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); - stm->stream = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); - if (!stm->stream) { - pulse_stream_destroy(stm); - return CUBEB_ERROR; + if (output_stream_params) { + r = create_pa_stream(stm, &stm->output_stream, output_stream_params, stream_name); + if (r != CUBEB_OK) { + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + pulse_stream_destroy(stm); + return r; + } + + stm->output_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->output_stream)); + + WRAP(pa_stream_set_state_callback)(stm->output_stream, stream_state_callback, stm); + WRAP(pa_stream_set_write_callback)(stm->output_stream, stream_write_callback, stm); + + battr = set_buffering_attribute(latency, &stm->output_sample_spec); + WRAP(pa_stream_connect_playback)(stm->output_stream, + output_device, + &battr, + PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY, + NULL, NULL); } - WRAP(pa_stream_set_state_callback)(stm->stream, stream_state_callback, stm); - WRAP(pa_stream_set_write_callback)(stm->stream, stream_request_callback, stm); - WRAP(pa_stream_connect_playback)(stm->stream, NULL, &battr, + + // Set up input stream + if (input_stream_params) { + r = create_pa_stream(stm, &stm->input_stream, input_stream_params, stream_name); + if (r != CUBEB_OK) { + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + pulse_stream_destroy(stm); + return r; + } + + stm->input_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->input_stream)); + + WRAP(pa_stream_set_state_callback)(stm->input_stream, stream_state_callback, stm); + WRAP(pa_stream_set_read_callback)(stm->input_stream, stream_read_callback, stm); + + battr = set_buffering_attribute(latency, &stm->input_sample_spec); + WRAP(pa_stream_connect_record)(stm->input_stream, + input_device, + &battr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | - PA_STREAM_START_CORKED, - NULL, NULL); + PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY); + } r = wait_until_stream_ready(stm); if (r == 0) { /* force a timing update now, otherwise timing info does not become valid until some point after initialization has completed. */ - o = WRAP(pa_stream_update_timing_info)(stm->stream, stream_success_callback, stm); - if (o) { - r = operation_wait(stm->context, stm->stream, o); - WRAP(pa_operation_unref)(o); - } + r = stream_update_timing_info(stm); } + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); if (r != 0) { @@ -574,6 +779,22 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return CUBEB_ERROR; } +#ifdef LOGGING_ENABLED + if (output_stream_params){ + const pa_buffer_attr * output_att; + output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream); + LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u\n",output_att->maxlength, output_att->tlength, + output_att->prebuf, output_att->minreq, output_att->fragsize); + } + + if (input_stream_params){ + const pa_buffer_attr * input_att; + input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream); + LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u\n",input_att->maxlength, input_att->tlength, + input_att->prebuf, input_att->minreq, input_att->fragsize); + } +#endif + *stream = stm; return CUBEB_OK; @@ -582,22 +803,30 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n static void pulse_stream_destroy(cubeb_stream * stm) { - if (stm->stream) { - stream_cork(stm, CORK); + stream_cork(stm, CORK); - WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + if (stm->output_stream) { if (stm->drain_timer) { /* there's no pa_rttime_free, so use this instead. */ WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer); } - WRAP(pa_stream_set_state_callback)(stm->stream, NULL, NULL); - WRAP(pa_stream_disconnect)(stm->stream); - WRAP(pa_stream_unref)(stm->stream); - WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL); + WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL); + WRAP(pa_stream_disconnect)(stm->output_stream); + WRAP(pa_stream_unref)(stm->output_stream); } + if (stm->input_stream) { + WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL); + WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL); + WRAP(pa_stream_disconnect)(stm->input_stream); + WRAP(pa_stream_unref)(stm->input_stream); + } + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + free(stm); } @@ -622,12 +851,16 @@ pulse_stream_get_position(cubeb_stream * stm, uint64_t * position) pa_usec_t r_usec; uint64_t bytes; + if (!stm || !stm->output_stream) { + return CUBEB_ERROR; + } + in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop); if (!in_thread) { WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); } - r = WRAP(pa_stream_get_time)(stm->stream, &r_usec); + r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec); if (!in_thread) { WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); } @@ -636,8 +869,8 @@ pulse_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_ERROR; } - bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->sample_spec); - *position = bytes / WRAP(pa_frame_size)(&stm->sample_spec); + bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec); + *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec); return CUBEB_OK; } @@ -648,17 +881,17 @@ pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency) pa_usec_t r_usec; int negative, r; - if (!stm) { + if (!stm || !stm->output_stream) { return CUBEB_ERROR; } - r = WRAP(pa_stream_get_latency)(stm->stream, &r_usec, &negative); + r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative); assert(!negative); if (r) { return CUBEB_ERROR; } - *latency = r_usec * stm->sample_spec.rate / PA_USEC_PER_SEC; + *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC; return CUBEB_OK; } @@ -678,6 +911,10 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) pa_cvolume cvol; const pa_sample_spec * ss; + if (!stm->output_stream) { + return CUBEB_ERROR; + } + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); while (!stm->context->default_sink_info) { @@ -689,18 +926,18 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) { stm->volume = volume; } else { - ss = WRAP(pa_stream_get_sample_spec)(stm->stream); + ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream); vol = WRAP(pa_sw_volume_from_linear)(volume); WRAP(pa_cvolume_set)(&cvol, ss->channels, vol); - index = WRAP(pa_stream_get_index)(stm->stream); + index = WRAP(pa_stream_get_index)(stm->output_stream); op = WRAP(pa_context_set_sink_input_volume)(stm->context->context, index, &cvol, volume_success, stm); if (op) { - operation_wait(stm->context, stm->stream, op); + operation_wait(stm->context, stm->output_stream, op); WRAP(pa_operation_unref)(op); } } @@ -716,7 +953,11 @@ pulse_stream_set_panning(cubeb_stream * stream, float panning) const pa_channel_map * map; pa_cvolume vol; - map = WRAP(pa_stream_get_channel_map)(stream->stream); + if (!stream->output_stream) { + return CUBEB_ERROR; + } + + map = WRAP(pa_stream_get_channel_map)(stream->output_stream); if (!WRAP(pa_channel_map_can_balance)(map)) { return CUBEB_ERROR; @@ -734,6 +975,7 @@ typedef struct { cubeb_device_info ** devinfo; uint32_t max; uint32_t count; + cubeb * context; } pulse_dev_list_data; static cubeb_device_fmt @@ -819,6 +1061,8 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, pulse_ensure_dev_list_data_list_size (list_data); list_data->devinfo[list_data->count++] = devinfo; + + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static cubeb_device_state @@ -877,6 +1121,8 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, pulse_ensure_dev_list_data_list_size (list_data); list_data->devinfo[list_data->count++] = devinfo; + + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static void @@ -890,16 +1136,20 @@ pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata) free(list_data->default_source_name); list_data->default_sink_name = strdup(i->default_sink_name); list_data->default_source_name = strdup(i->default_source_name); + + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static int pulse_enumerate_devices(cubeb * context, cubeb_device_type type, cubeb_device_collection ** collection) { - pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0 }; + pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context }; pa_operation * o; uint32_t i; + WRAP(pa_threaded_mainloop_lock)(context->mainloop); + o = WRAP(pa_context_get_server_info)(context->context, pulse_server_info_cb, &user_data); if (o) { @@ -925,16 +1175,131 @@ pulse_enumerate_devices(cubeb * context, cubeb_device_type type, } } + WRAP(pa_threaded_mainloop_unlock)(context->mainloop); + *collection = malloc(sizeof(cubeb_device_collection) + - sizeof(cubeb_device_info*) * (user_data.count > 0 ? user_data.count - 1 : 0)); + sizeof(cubeb_device_info *) * (user_data.count > 0 ? user_data.count - 1 : 0)); (*collection)->count = user_data.count; for (i = 0; i < user_data.count; i++) (*collection)->device[i] = user_data.devinfo[i]; + free(user_data.default_sink_name); + free(user_data.default_source_name); free(user_data.devinfo); return CUBEB_OK; } +int pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) +{ +#if PA_CHECK_VERSION(0, 9, 8) + *device = calloc(1, sizeof(cubeb_device)); + if (*device == NULL) + return CUBEB_ERROR; + + if (stm->input_stream) { + const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream); + (*device)->input_name = (name == NULL) ? NULL : strdup(name); + } + + if (stm->output_stream) { + const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream); + (*device)->output_name = (name == NULL) ? NULL : strdup(name); + } + + return CUBEB_OK; +#else + return CUBEB_ERROR_NOT_SUPPORTED; +#endif +} + +int pulse_stream_device_destroy(cubeb_stream * stream, + cubeb_device * device) +{ + free(device->input_name); + free(device->output_name); + free(device); + return CUBEB_OK; +} + +void pulse_subscribe_callback(pa_context * ctx, + pa_subscription_event_type_t t, + uint32_t index, void * userdata) +{ + cubeb * context = userdata; + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SOURCE: + case PA_SUBSCRIPTION_EVENT_SINK: + +#ifdef LOGGING_ENABLED + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + LOG("Removing sink index %d\n", index); + else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + LOG("Adding sink index %d\n", index); + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + LOG("Removing source index %d\n", index); + else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + LOG("Adding source index %d\n", index); +#endif + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE || + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + context->collection_changed_callback(context, context->collection_changed_user_ptr); + } + break; + } +} + +void subscribe_success(pa_context *c, int success, void *userdata) +{ + cubeb * context = userdata; + assert(success); + WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0); +} + +int pulse_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + context->collection_changed_callback = collection_changed_callback; + context->collection_changed_user_ptr = user_ptr; + + WRAP(pa_threaded_mainloop_lock)(context->mainloop); + + pa_subscription_mask_t mask; + if (context->collection_changed_callback == NULL) { + // Unregister subscription + WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL); + mask = PA_SUBSCRIPTION_MASK_NULL; + } else { + WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); + if (devtype == CUBEB_DEVICE_TYPE_INPUT) + mask = PA_SUBSCRIPTION_MASK_SOURCE; + else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT) + mask = PA_SUBSCRIPTION_MASK_SINK; + else + mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; + } + + pa_operation * o; + o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context); + if (o == NULL) { + LOG("Context subscribe failed\n"); + return CUBEB_ERROR; + } + operation_wait(context, NULL, o); + WRAP(pa_operation_unref)(o); + + WRAP(pa_threaded_mainloop_unlock)(context->mainloop); + + return CUBEB_OK; +} + static struct cubeb_ops const pulse_ops = { .init = pulse_init, .get_backend_id = pulse_get_backend_id, @@ -951,7 +1316,8 @@ static struct cubeb_ops const pulse_ops = { .stream_get_latency = pulse_stream_get_latency, .stream_set_volume = pulse_stream_set_volume, .stream_set_panning = pulse_stream_set_panning, - .stream_get_current_device = NULL, - .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL + .stream_get_current_device = pulse_stream_get_current_device, + .stream_device_destroy = pulse_stream_device_destroy, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = pulse_register_device_collection_changed }; diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp index 21922e5b19..c41b29ec44 100644 --- a/media/libcubeb/src/cubeb_resampler.cpp +++ b/media/libcubeb/src/cubeb_resampler.cpp @@ -9,6 +9,9 @@ #include #include #include +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif #include "cubeb_resampler.h" #include "cubeb-speex-resampler.h" @@ -69,7 +72,7 @@ to_speex_quality(cubeb_resampler_quality q) } // end of anonymous namespace struct cubeb_resampler { - virtual long fill(void * buffer, long frames_needed) = 0; + virtual long fill(void * input_buffer, void * output_buffer, long frames_needed) = 0; virtual ~cubeb_resampler() {} }; @@ -84,9 +87,9 @@ public: { } - virtual long fill(void * buffer, long frames_needed) + virtual long fill(void * input_buffer, void * output_buffer, long frames_needed) { - long got = data_callback(stream, user_ptr, buffer, frames_needed); + long got = data_callback(stream, user_ptr, input_buffer, output_buffer, frames_needed); assert(got <= frames_needed); return got; } @@ -106,7 +109,7 @@ public: virtual ~cubeb_resampler_speex(); - virtual long fill(void * buffer, long frames_needed); + virtual long fill(void * input_buffer, void * output_buffer, long frames_needed); private: SpeexResamplerState * const speex_resampler; @@ -161,7 +164,7 @@ cubeb_resampler_speex::~cubeb_resampler_speex() } long -cubeb_resampler_speex::fill(void * buffer, long frames_needed) +cubeb_resampler_speex::fill(void * input_buffer, void * output_buffer, long frames_needed) { // Use more input frames than strictly necessary, so in the worst case, // we have leftover unresampled frames at the end, that we can use @@ -175,7 +178,7 @@ cubeb_resampler_speex::fill(void * buffer, long frames_needed) memcpy(resampling_src_buffer.get(), leftover_frames_buffer.get(), leftover_bytes); uint8_t * buffer_start = resampling_src_buffer.get() + leftover_bytes; - long got = data_callback(stream, user_ptr, buffer_start, frames_requested); + long got = data_callback(stream, user_ptr, NULL, buffer_start, frames_requested); assert(got <= frames_requested); if (got < 0) { @@ -188,12 +191,12 @@ cubeb_resampler_speex::fill(void * buffer, long frames_needed) if (stream_params.format == CUBEB_SAMPLE_FLOAT32NE) { float * in_buffer = reinterpret_cast(resampling_src_buffer.get()); - float * out_buffer = reinterpret_cast(buffer); + float * out_buffer = reinterpret_cast(output_buffer); speex_resampler_process_interleaved_float(speex_resampler, in_buffer, &in_frames, out_buffer, &out_frames); } else { short * in_buffer = reinterpret_cast(resampling_src_buffer.get()); - short * out_buffer = reinterpret_cast(buffer); + short * out_buffer = reinterpret_cast(output_buffer); speex_resampler_process_interleaved_int(speex_resampler, in_buffer, &in_frames, out_buffer, &out_frames); } @@ -239,9 +242,11 @@ cubeb_resampler_create(cubeb_stream * stream, long cubeb_resampler_fill(cubeb_resampler * resampler, - void * buffer, long frames_needed) + void * input_buffer, + void * output_buffer, + long frames_needed) { - return resampler->fill(buffer, frames_needed); + return resampler->fill(input_buffer, output_buffer, frames_needed); } void diff --git a/media/libcubeb/src/cubeb_resampler.h b/media/libcubeb/src/cubeb_resampler.h index e63c2c18aa..64f5d58db5 100644 --- a/media/libcubeb/src/cubeb_resampler.h +++ b/media/libcubeb/src/cubeb_resampler.h @@ -53,7 +53,8 @@ cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream, * @retval CUBEB_ERROR on error. */ long cubeb_resampler_fill(cubeb_resampler * resampler, - void * buffer, long frames_needed); + void * input_buffer, + void * output_buffer, long frames_needed); /** * Destroy a cubeb_resampler. diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index 94f9961735..d13b8f4f02 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -103,7 +103,7 @@ sndio_mainloop(void *arg) break; } pthread_mutex_unlock(&s->mtx); - nfr = s->data_cb(s, s->arg, s->buf, s->nfr); + nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr); pthread_mutex_lock(&s->mtx); if (nfr < 0) { DPR("sndio_mainloop() cb err\n"); @@ -170,10 +170,14 @@ sndio_destroy(cubeb *context) } static int -sndio_stream_init(cubeb *context, - cubeb_stream **stream, - char const *stream_name, - cubeb_stream_params stream_params, unsigned int latency, +sndio_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void *user_ptr) @@ -183,6 +187,12 @@ sndio_stream_init(cubeb *context, DPR("sndio_stream_init(%s)\n", stream_name); size_t size; + assert(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + s = malloc(sizeof(cubeb_stream)); if (s == NULL) return CUBEB_ERROR; @@ -196,7 +206,7 @@ sndio_stream_init(cubeb *context, sio_initpar(&wpar); wpar.sig = 1; wpar.bits = 16; - switch (stream_params.format) { + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: wpar.le = 1; break; @@ -210,8 +220,8 @@ sndio_stream_init(cubeb *context, DPR("sndio_stream_init() unsupported format\n"); return CUBEB_ERROR_INVALID_FORMAT; } - wpar.rate = stream_params.rate; - wpar.pchan = stream_params.channels; + wpar.rate = output_stream_params->rate; + wpar.pchan = output_stream_params->channels; wpar.appbufsz = latency * wpar.rate / 1000; if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) { sio_close(s->hdl); @@ -237,7 +247,7 @@ sndio_stream_init(cubeb *context, s->arg = user_ptr; s->mtx = PTHREAD_MUTEX_INITIALIZER; s->rdpos = s->wrpos = 0; - if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) { + if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { s->conv = 1; size = rpar.round * rpar.pchan * sizeof(float); } else { @@ -368,5 +378,6 @@ static struct cubeb_ops const sndio_ops = { .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL }; diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index c7b0e9530f..2a4dd63ef4 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2013 Mozilla Foundation + * Copyright © 2013 Mozilla Foundation * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. @@ -472,7 +472,7 @@ refill(cubeb_stream * stm, float * data, long frames_needed) dest = data; } - long out_frames = cubeb_resampler_fill(stm->resampler, dest, frames_needed); + long out_frames = cubeb_resampler_fill(stm->resampler, NULL, dest, frames_needed); /* TODO: Report out_frames < 0 as an error via the API. */ XASSERT(out_frames >= 0); @@ -925,6 +925,10 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten return CUBEB_ERROR; } + if (params.format != CUBEB_SAMPLE_FLOAT32NE) { + return CUBEB_ERROR_INVALID_FORMAT; + } + IMMDevice * device; hr = get_default_endpoint(&device); if (FAILED(hr)) { @@ -1197,7 +1201,11 @@ int setup_wasapi_stream(cubeb_stream * stm) int wasapi_stream_init(cubeb * context, cubeb_stream ** stream, - char const * stream_name, cubeb_stream_params stream_params, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { @@ -1208,9 +1216,15 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, return CUBEB_ERROR; } + XASSERT(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + XASSERT(context && stream); - if (stream_params.format != CUBEB_SAMPLE_FLOAT32NE) { + if (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE) { return CUBEB_ERROR_INVALID_FORMAT; } @@ -1222,7 +1236,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->stream_params = stream_params; + stm->stream_params = *output_stream_params; stm->draining = false; stm->latency = latency; stm->volume = 1.0; @@ -1516,7 +1530,7 @@ static cubeb_device_info * wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev) { IMMEndpoint * endpoint = NULL; - IMMDevice * devnode; + IMMDevice * devnode = NULL; IAudioClient * client = NULL; cubeb_device_info * ret = NULL; EDataFlow flow; @@ -1704,6 +1718,7 @@ cubeb_ops const wasapi_ops = { /*.stream_set_panning =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, - /*.stream_register_device_changed_callback =*/ NULL + /*.stream_register_device_changed_callback =*/ NULL, + /*.register_device_collection_changed =*/ NULL }; } // namespace anonymous diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index 7f1c7ee11d..f8d0d0251c 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -183,7 +183,7 @@ winmm_refill_stream(cubeb_stream * stm) /* It is assumed that the caller is holding this lock. It must be dropped during the callback to avoid deadlocks. */ LeaveCriticalSection(&stm->lock); - got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted); + got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted); EnterCriticalSection(&stm->lock); if (got < 0) { LeaveCriticalSection(&stm->lock); @@ -384,7 +384,11 @@ static void winmm_stream_destroy(cubeb_stream * stm); static int winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, - cubeb_stream_params stream_params, unsigned int latency, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) @@ -398,26 +402,32 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n XASSERT(context); XASSERT(stream); + XASSERT(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + *stream = NULL; memset(&wfx, 0, sizeof(wfx)); - if (stream_params.channels > 2) { + if (output_stream_params->channels > 2) { wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format); } else { wfx.Format.wFormatTag = WAVE_FORMAT_PCM; - if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) { + if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; } wfx.Format.cbSize = 0; } - wfx.Format.nChannels = stream_params.channels; - wfx.Format.nSamplesPerSec = stream_params.rate; + wfx.Format.nChannels = output_stream_params->channels; + wfx.Format.nSamplesPerSec = output_stream_params->rate; /* XXX fix channel mappings */ wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; - switch (stream_params.format) { + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: wfx.Format.wBitsPerSample = 16; wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; @@ -450,7 +460,7 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n stm->context = context; - stm->params = stream_params; + stm->params = *output_stream_params; stm->data_callback = data_callback; stm->state_callback = state_callback; @@ -1078,5 +1088,6 @@ static struct cubeb_ops const winmm_ops = { /*.stream_set_panning =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, - /*.stream_register_device_changed_callback=*/ NULL + /*.stream_register_device_changed_callback=*/ NULL, + /*.register_device_collection_changed =*/ NULL }; diff --git a/media/libcubeb/tests/common.h b/media/libcubeb/tests/common.h index 69445b86c6..47cc28cc7f 100644 --- a/media/libcubeb/tests/common.h +++ b/media/libcubeb/tests/common.h @@ -27,3 +27,4 @@ void delay(unsigned int ms) #if !defined(M_PI) #define M_PI 3.14159265358979323846 #endif + diff --git a/media/libcubeb/tests/moz.build b/media/libcubeb/tests/moz.build index 4088f7c125..e98d8d98d2 100644 --- a/media/libcubeb/tests/moz.build +++ b/media/libcubeb/tests/moz.build @@ -4,6 +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/. +DEFINES['CUBEB_GECKO_BUILD'] = True + GeckoCppUnitTests([ 'test_tone' ]) diff --git a/media/libcubeb/tests/test_audio.cpp b/media/libcubeb/tests/test_audio.cpp index 570c5f1e5d..7a8adf66f6 100644 --- a/media/libcubeb/tests/test_audio.cpp +++ b/media/libcubeb/tests/test_audio.cpp @@ -10,7 +10,7 @@ #ifdef NDEBUG #undef NDEBUG #endif -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 600 #include #include #include @@ -19,7 +19,9 @@ #include "cubeb/cubeb.h" #include "common.h" +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif #define MAX_NUM_CHANNELS 32 @@ -71,10 +73,10 @@ void synth_run_float(synth_state* synth, float* audiobuffer, long nframes) } } -long data_cb_float(cubeb_stream *stream, void *user, void *buffer, long nframes) +long data_cb_float(cubeb_stream *stream, void *user, const void * inputbuffer, void *outputbuffer, long nframes) { synth_state *synth = (synth_state *)user; - synth_run_float(synth, (float*)buffer, nframes); + synth_run_float(synth, (float*)outputbuffer, nframes); return nframes; } @@ -90,10 +92,10 @@ void synth_run_16bit(synth_state* synth, short* audiobuffer, long nframes) } } -long data_cb_short(cubeb_stream *stream, void *user, void *buffer, long nframes) +long data_cb_short(cubeb_stream *stream, void *user, const void * inputbuffer, void *outputbuffer, long nframes) { synth_state *synth = (synth_state *)user; - synth_run_16bit(synth, (short*)buffer, nframes); + synth_run_16bit(synth, (short*)outputbuffer, nframes); return nframes; } @@ -108,6 +110,12 @@ int supports_float32(const char* backend_id) strcmp(backend_id, "audiotrack") != 0); } +/* The WASAPI backend only supports float. */ +int supports_int16(const char* backend_id) +{ + return strcmp(backend_id, "wasapi") != 0; +} + /* Some backends don't have code to deal with more than mono or stereo. */ int supports_channel_count(const char* backend_id, int nchannels) { @@ -133,6 +141,7 @@ int run_test(int num_channels, int sampling_rate, int is_float) backend_id = cubeb_get_backend_id(ctx); if ((is_float && !supports_float32(backend_id)) || + (!is_float && !supports_int16(backend_id)) || !supports_channel_count(backend_id, num_channels)) { /* don't treat this as a test failure. */ goto cleanup; @@ -151,7 +160,7 @@ int run_test(int num_channels, int sampling_rate, int is_float) goto cleanup; } - r = cubeb_stream_init(ctx, &stream, "test tone", params, + r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms, 100, is_float ? data_cb_float : data_cb_short, state_cb, synth); if (r != CUBEB_OK) { fprintf(stderr, "Error initializing cubeb stream: %d\n", r); @@ -170,22 +179,30 @@ cleanup: return r; } -int run_panning_volume_test() +int run_panning_volume_test(int is_float) { int r = CUBEB_OK; cubeb *ctx = NULL; synth_state* synth = NULL; cubeb_stream *stream = NULL; + const char * backend_id = NULL; r = cubeb_init(&ctx, "Cubeb audio test"); if (r != CUBEB_OK) { fprintf(stderr, "Error initializing cubeb library\n"); goto cleanup; } + backend_id = cubeb_get_backend_id(ctx); + + if ((is_float && !supports_float32(backend_id)) || + (!is_float && !supports_int16(backend_id))) { + /* don't treat this as a test failure. */ + goto cleanup; + } cubeb_stream_params params; - params.format = CUBEB_SAMPLE_S16NE; + params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE; params.rate = 44100; params.channels = 2; @@ -195,8 +212,8 @@ int run_panning_volume_test() goto cleanup; } - r = cubeb_stream_init(ctx, &stream, "test tone", params, - 100, data_cb_short, state_cb, synth); + r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms, + 100, is_float ? data_cb_float : data_cb_short, state_cb, synth); if (r != CUBEB_OK) { fprintf(stderr, "Error initializing cubeb stream: %d\n", r); goto cleanup; @@ -264,9 +281,12 @@ void run_channel_rate_test() int main(int argc, char *argv[]) { +#ifdef CUBEB_GECKO_BUILD ScopedXPCOM xpcom("test_audio"); +#endif - assert(run_panning_volume_test() == CUBEB_OK); + assert(run_panning_volume_test(0) == CUBEB_OK); + assert(run_panning_volume_test(1) == CUBEB_OK); run_channel_rate_test(); return CUBEB_OK; diff --git a/media/libcubeb/tests/test_latency.cpp b/media/libcubeb/tests/test_latency.cpp index c1b2ae2da1..5b4da8e7da 100644 --- a/media/libcubeb/tests/test_latency.cpp +++ b/media/libcubeb/tests/test_latency.cpp @@ -5,12 +5,17 @@ #include #include #include +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif + #define LOG(msg) fprintf(stderr, "%s\n", msg); int main(int argc, char * argv[]) { +#ifdef CUBEB_GECKO_BUILD ScopedXPCOM xpcom("test_latency"); +#endif cubeb * ctx = NULL; int r; diff --git a/media/libcubeb/tests/test_sanity.cpp b/media/libcubeb/tests/test_sanity.cpp index 1d7e5562bf..b09a4f9556 100644 --- a/media/libcubeb/tests/test_sanity.cpp +++ b/media/libcubeb/tests/test_sanity.cpp @@ -7,37 +7,49 @@ #ifdef NDEBUG #undef NDEBUG #endif -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 600 #include "cubeb/cubeb.h" #include #include #include #include #include "common.h" +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif #if (defined(_WIN32) || defined(__WIN32__)) #define __func__ __FUNCTION__ #endif #define ARRAY_LENGTH(_x) (sizeof(_x) / sizeof(_x[0])) -#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__); -#define END_TEST fprintf(stderr, "END %s\n", __func__); +#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__) +#define END_TEST fprintf(stderr, "END %s\n", __func__) #define STREAM_LATENCY 100 #define STREAM_RATE 44100 #define STREAM_CHANNELS 1 +#if (defined(_WIN32) || defined(__WIN32__)) +#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE +#else #define STREAM_FORMAT CUBEB_SAMPLE_S16LE +#endif static int dummy; static uint64_t total_frames_written; static int delay_callback; static long -test_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes) +test_data_callback(cubeb_stream * stm, void * user_ptr, const void * inputbuffer, void * outputbuffer, long nframes) { - assert(stm && user_ptr == &dummy && p && nframes > 0); - memset(p, 0, nframes * sizeof(short)); + assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0); + memset(outputbuffer, 0, nframes * sizeof(short)); +#if (defined(_WIN32) || defined(__WIN32__)) + memset(outputbuffer, 0, nframes * sizeof(float)); +#else + memset(outputbuffer, 0, nframes * sizeof(short)); +#endif + total_frames_written += nframes; if (delay_callback) { delay(10); @@ -57,9 +69,9 @@ test_init_destroy_context(void) cubeb * ctx; char const* backend_id; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); @@ -70,8 +82,8 @@ test_init_destroy_context(void) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_destroy_multiple_contexts(void) @@ -82,20 +94,20 @@ test_init_destroy_multiple_contexts(void) int order[4] = {2, 0, 3, 1}; assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order)); - BEGIN_TEST + BEGIN_TEST; - for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { - r = cubeb_init(&ctx[i], NULL); - assert(r == 0 && ctx[i]); - } + for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { + r = cubeb_init(&ctx[i], NULL); + assert(r == 0 && ctx[i]); + } /* destroy in a different order */ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { cubeb_destroy(ctx[order[i]]); } - END_TEST - } + END_TEST; +} static void test_context_variables(void) @@ -105,14 +117,14 @@ test_context_variables(void) uint32_t value; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_context_variables"); + r = cubeb_init(&ctx, "test_context_variables"); assert(r == 0 && ctx); - params.channels = 2; - params.format = CUBEB_SAMPLE_S16LE; - params.rate = 44100; + params.channels = STREAM_CHANNELS; + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; r = cubeb_get_min_latency(ctx, params, &value); assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); if (r == CUBEB_OK) { @@ -127,8 +139,8 @@ test_context_variables(void) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_destroy_stream(void) @@ -138,24 +150,24 @@ test_init_destroy_stream(void) cubeb_stream * stream; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; - r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0 && stream); cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_destroy_multiple_streams(void) @@ -166,9 +178,9 @@ test_init_destroy_multiple_streams(void) cubeb_stream * stream[8]; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; @@ -176,7 +188,7 @@ test_init_destroy_multiple_streams(void) params.channels = STREAM_CHANNELS; for (i = 0; i < ARRAY_LENGTH(stream); ++i) { - r = cubeb_stream_init(ctx, &stream[i], "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0); assert(stream[i]); @@ -188,8 +200,8 @@ test_init_destroy_multiple_streams(void) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_configure_stream(void) @@ -199,16 +211,16 @@ test_configure_stream(void) cubeb_stream * stream; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = 2; // panning - r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0 && stream); @@ -220,8 +232,8 @@ test_configure_stream(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) @@ -232,9 +244,9 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) cubeb_stream * stream[8]; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; @@ -242,7 +254,7 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) params.channels = STREAM_CHANNELS; for (i = 0; i < ARRAY_LENGTH(stream); ++i) { - r = cubeb_stream_init(ctx, &stream[i], "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0); assert(stream[i]); @@ -281,8 +293,8 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_destroy_multiple_contexts_and_streams(void) @@ -295,9 +307,9 @@ test_init_destroy_multiple_contexts_and_streams(void) size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx); assert(ARRAY_LENGTH(ctx) * streams_per_ctx == ARRAY_LENGTH(stream)); - BEGIN_TEST + BEGIN_TEST; - params.format = STREAM_FORMAT; + params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; @@ -306,7 +318,7 @@ test_init_destroy_multiple_contexts_and_streams(void) assert(r == 0 && ctx[i]); for (j = 0; j < streams_per_ctx; ++j) { - r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0); assert(stream[i * streams_per_ctx + j]); @@ -320,8 +332,8 @@ test_init_destroy_multiple_contexts_and_streams(void) cubeb_destroy(ctx[i]); } - END_TEST - } + END_TEST; +} static void test_basic_stream_operations(void) @@ -332,16 +344,16 @@ test_basic_stream_operations(void) cubeb_stream_params params; uint64_t position; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; - r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0 && stream); @@ -366,8 +378,8 @@ test_basic_stream_operations(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_stream_position(void) @@ -379,9 +391,9 @@ test_stream_position(void) cubeb_stream_params params; uint64_t position, last_position; - BEGIN_TEST + BEGIN_TEST; - total_frames_written = 0; + total_frames_written = 0; r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); @@ -390,7 +402,7 @@ test_stream_position(void) params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; - r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0 && stream); @@ -456,23 +468,23 @@ test_stream_position(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static int do_drain; static int got_drain; static long -test_drain_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes) +test_drain_data_callback(cubeb_stream * stm, void * user_ptr, const void * inputbuffer, void * outputbuffer, long nframes) { - assert(stm && user_ptr == &dummy && p && nframes > 0); + assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0); if (do_drain == 1) { do_drain = 2; return 0; } /* once drain has started, callback must never be called again */ assert(do_drain != 2); - memset(p, 0, nframes * sizeof(short)); + memset(outputbuffer, 0, nframes * sizeof(short)); total_frames_written += nframes; return nframes; } @@ -495,9 +507,9 @@ test_drain(void) cubeb_stream_params params; uint64_t position; - BEGIN_TEST + BEGIN_TEST; - total_frames_written = 0; + total_frames_written = 0; r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); @@ -506,7 +518,7 @@ test_drain(void) params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; - r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_drain_data_callback, test_drain_state_callback, &dummy); assert(r == 0 && stream); @@ -539,8 +551,8 @@ test_drain(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} int is_windows_7() { @@ -572,7 +584,9 @@ int is_windows_7() int main(int argc, char * argv[]) { +#ifdef CUBEB_GECKO_BUILD ScopedXPCOM xpcom("test_sanity"); +#endif test_init_destroy_context(); test_init_destroy_multiple_contexts(); diff --git a/media/libcubeb/tests/test_tone.cpp b/media/libcubeb/tests/test_tone.cpp index ae50c69281..82bcb80b9c 100644 --- a/media/libcubeb/tests/test_tone.cpp +++ b/media/libcubeb/tests/test_tone.cpp @@ -9,27 +9,40 @@ #ifdef NDEBUG #undef NDEBUG #endif -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 600 #include #include #include #include +#include #include "cubeb/cubeb.h" #include "common.h" +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif #define SAMPLE_FREQUENCY 48000 +#if (defined(_WIN32) || defined(__WIN32__)) +#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE +#else +#define STREAM_FORMAT CUBEB_SAMPLE_S16LE +#endif /* store the phase of the generated waveform */ struct cb_user_data { long position; }; -long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes) +long data_cb(cubeb_stream *stream, void *user, const void* inputbuffer, void *outputbuffer, long nframes) { struct cb_user_data *u = (struct cb_user_data *)user; - short *b = (short *)buffer; +#if (defined(_WIN32) || defined(__WIN32__)) + float *b = (float *)outputbuffer; +#else + short *b = (short *)outputbuffer; +#endif + float t1, t2; int i; if (stream == NULL || u == NULL) @@ -38,10 +51,24 @@ long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes) /* generate our test tone on the fly */ for (i = 0; i < nframes; i++) { /* North American dial tone */ - b[i] = 16000*sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY); - b[i] += 16000*sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY); + t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY); + t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY); +#if (defined(_WIN32) || defined(__WIN32__)) + b[i] = 0.5 * t1; + b[i] += 0.5 * t2; +#else + b[i] = (SHRT_MAX / 2) * t1; + b[i] += (SHRT_MAX / 2) * t2; +#endif /* European dial tone */ - /*b[i] = 30000*sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);*/ + /* + t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY); +#if (defined(_WIN32) || defined(__WIN32__)) + b[i] = t1; +#else + b[i] = SHRT_MAX * t1; +#endif + */ } /* remember our phase to avoid clicking on buffer transitions */ /* we'll still click if position overflows */ @@ -73,7 +100,9 @@ void state_cb(cubeb_stream *stream, void *user, cubeb_state state) int main(int argc, char *argv[]) { +#ifdef CUBEB_GECKO_BUILD ScopedXPCOM xpcom("test_tone"); +#endif cubeb *ctx; cubeb_stream *stream; @@ -87,7 +116,7 @@ int main(int argc, char *argv[]) return r; } - params.format = CUBEB_SAMPLE_S16NE; + params.format = STREAM_FORMAT; params.rate = SAMPLE_FREQUENCY; params.channels = 1; @@ -98,7 +127,7 @@ int main(int argc, char *argv[]) } user_data->position = 0; - r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", params, + r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, ¶ms, 250, data_cb, state_cb, user_data); if (r != CUBEB_OK) { fprintf(stderr, "Error initializing cubeb stream\n"); diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh index adb4fb2e70..07b0a98fc4 100755 --- a/media/libcubeb/update.sh +++ b/media/libcubeb/update.sh @@ -12,6 +12,8 @@ cp $1/src/cubeb_sndio.c src cp $1/src/cubeb_opensl.c src cp $1/src/cubeb_audiotrack.c src cp $1/src/cubeb_wasapi.cpp src +cp $1/src/cubeb_resampler.h src +cp $1/src/cubeb_resampler.cpp src cp $1/src/cubeb-speex-resampler.h src cp $1/src/cubeb_panner.h src cp $1/src/cubeb_panner.cpp src diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp index 983c1cba51..3d2f288179 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp @@ -854,7 +854,7 @@ UnsetTrackId(MediaStreamGraphImpl* graph) { } RefPtr listener_; }; - graph->AppendMessage(new Message(this)); + graph->AppendMessage(MakeUnique(this)); #else UnsetTrackIdImpl(); #endif @@ -1343,7 +1343,7 @@ static void AddTrackAndListener(MediaStream* source, // atomically and have start time 0. When not queueing we have to add // the track on the MediaStreamGraph thread so it can be added with the // appropriate start time. - source->GraphImpl()->AppendMessage(new Message(source, track_id, track_rate, segment, listener, completed)); + source->GraphImpl()->AppendMessage(MakeUnique(source, track_id, track_rate, segment, listener, completed)); MOZ_MTLOG(ML_INFO, "Dispatched track-add for track id " << track_id << " on stream " << source); return; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index ca792fe874..c7bb97f2be 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -481,27 +481,33 @@ pref("media.getusermedia.aec_delay_agnostic", false); pref("media.getusermedia.noise", 1); pref("media.getusermedia.agc_enabled", false); pref("media.getusermedia.agc", 1); -// Adjustments for OS-specific input delay (lower bound) -// Adjustments for OS-specific AudioStream+cubeb+output delay (lower bound) +// capture_delay: Adjustments for OS-specific input delay (lower bound) +// playout_delay: Adjustments for OS-specific AudioStream+cubeb+output delay (lower bound) +// full_duplex: enable cubeb full-duplex capture/playback #if defined(XP_MACOSX) pref("media.peerconnection.capture_delay", 50); pref("media.getusermedia.playout_delay", 10); +pref("media.navigator.audio.full_duplex", false); #elif defined(XP_WIN) pref("media.peerconnection.capture_delay", 50); pref("media.getusermedia.playout_delay", 40); +pref("media.navigator.audio.full_duplex", false); #elif defined(ANDROID) pref("media.peerconnection.capture_delay", 100); pref("media.getusermedia.playout_delay", 100); +pref("media.navigator.audio.full_duplex", false); // Whether to enable Webrtc Hardware acceleration support pref("media.navigator.hardware.vp8_encode.acceleration_enabled", false); pref("media.navigator.hardware.vp8_decode.acceleration_enabled", false); #elif defined(XP_LINUX) pref("media.peerconnection.capture_delay", 70); pref("media.getusermedia.playout_delay", 50); +pref("media.navigator.audio.full_duplex", false); #else // *BSD, others - merely a guess for now pref("media.peerconnection.capture_delay", 50); pref("media.getusermedia.playout_delay", 50); +pref("media.navigator.audio.full_duplex", false); #endif #endif pref("media.mediasource.webm.audio.enabled", true); diff --git a/xpcom/base/ClearOnShutdown.cpp b/xpcom/base/ClearOnShutdown.cpp index bbbb8c0183..bfb3142bc3 100644 --- a/xpcom/base/ClearOnShutdown.cpp +++ b/xpcom/base/ClearOnShutdown.cpp @@ -9,8 +9,37 @@ namespace mozilla { namespace ClearOnShutdown_Internal { -bool sHasShutDown = false; -StaticAutoPtr> sShutdownObservers; +Array, + static_cast(ShutdownPhase::ShutdownPhase_Length)> sShutdownObservers; +ShutdownPhase sCurrentShutdownPhase = ShutdownPhase::NotInShutdown; } // namespace ClearOnShutdown_Internal + +// Called when XPCOM is shutting down, after all shutdown notifications have +// been sent and after all threads' event loops have been purged. +void +KillClearOnShutdown(ShutdownPhase aPhase) +{ + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + // Shutdown only goes one direction... + MOZ_ASSERT(static_cast(sCurrentShutdownPhase) < static_cast(aPhase)); + + // It's impossible to add an entry for a "past" phase; this is blocked in + // ClearOnShutdown, but clear them out anyways in case there are phases + // that weren't passed to KillClearOnShutdown. + for (size_t phase = static_cast(ShutdownPhase::First); + phase <= static_cast(aPhase); + phase++) { + if (sShutdownObservers[static_cast(phase)]) { + while (ShutdownObserver* observer = sShutdownObservers[static_cast(phase)]->popFirst()) { + observer->Shutdown(); + delete observer; + } + sShutdownObservers[static_cast(phase)] = nullptr; + } + } +} + } // namespace mozilla diff --git a/xpcom/base/ClearOnShutdown.h b/xpcom/base/ClearOnShutdown.h index b3d1d605b2..5c39c281cc 100644 --- a/xpcom/base/ClearOnShutdown.h +++ b/xpcom/base/ClearOnShutdown.h @@ -9,16 +9,21 @@ #include "mozilla/LinkedList.h" #include "mozilla/StaticPtr.h" +#include "mozilla/Array.h" #include "MainThreadUtils.h" /* * This header exports one public method in the mozilla namespace: * * template - * void ClearOnShutdown(SmartPtr *aPtr) + * void ClearOnShutdown(SmartPtr *aPtr, aPhase=ShutdownPhase::ShutdownFinal) * * This function takes a pointer to a smart pointer and nulls the smart pointer - * on shutdown. + * on shutdown (and a particular phase of shutdown as needed). If a phase + * is specified, the ptr will be cleared at the start of that phase. Also, + * if a phase has already occurred when ClearOnShutdown() is called it will + * cause a MOZ_ASSERT. In case a phase is not explicitly cleared we will + * clear it on the next phase that occurs. * * This is useful if you have a global smart pointer object which you don't * want to "leak" on shutdown. @@ -36,6 +41,20 @@ */ namespace mozilla { + +// Must be contiguous starting at 0 +enum class ShutdownPhase { + NotInShutdown = 0, + WillShutdown, + Shutdown, + ShutdownThreads, + ShutdownLoaders, + ShutdownFinal, + ShutdownPhase_Length, // never pass this value + First = WillShutdown, // for iteration + Last = ShutdownFinal +}; + namespace ClearOnShutdown_Internal { class ShutdownObserver : public LinkedListElement @@ -67,45 +86,38 @@ private: SmartPtr* mPtr; }; -extern bool sHasShutDown; -extern StaticAutoPtr> sShutdownObservers; +typedef LinkedList ShutdownList; +extern Array, + static_cast(ShutdownPhase::ShutdownPhase_Length)> sShutdownObservers; +extern ShutdownPhase sCurrentShutdownPhase; } // namespace ClearOnShutdown_Internal template inline void -ClearOnShutdown(SmartPtr* aPtr) +ClearOnShutdown(SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::ShutdownFinal) { using namespace ClearOnShutdown_Internal; MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(!sHasShutDown); + MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length); - if (!sShutdownObservers) { - sShutdownObservers = new LinkedList(); + // Adding a ClearOnShutdown for a "past" phase is an error. + if (!(static_cast(sCurrentShutdownPhase) < static_cast(aPhase))) { + MOZ_ASSERT(false, "ClearOnShutdown for phase that already was cleared"); + *aPtr = nullptr; + return; } - sShutdownObservers->insertBack(new PointerClearer(aPtr)); + + if (!(sShutdownObservers[static_cast(aPhase)])) { + sShutdownObservers[static_cast(aPhase)] = new ShutdownList(); + } + sShutdownObservers[static_cast(aPhase)]->insertBack(new PointerClearer(aPtr)); } // Called when XPCOM is shutting down, after all shutdown notifications have // been sent and after all threads' event loops have been purged. -inline void -KillClearOnShutdown() -{ - using namespace ClearOnShutdown_Internal; - - MOZ_ASSERT(NS_IsMainThread()); - - if (sShutdownObservers) { - while (ShutdownObserver* observer = sShutdownObservers->popFirst()) { - observer->Shutdown(); - delete observer; - } - } - - sShutdownObservers = nullptr; - sHasShutDown = true; -} +void KillClearOnShutdown(ShutdownPhase aPhase); } // namespace mozilla diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp index f23bc835b9..ddc0e152d8 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -832,6 +832,7 @@ ShutdownXPCOM(nsIServiceManager* aServMgr) (nsObserverService**)getter_AddRefs(observerService)); if (observerService) { + mozilla::KillClearOnShutdown(ShutdownPhase::WillShutdown); observerService->NotifyObservers(nullptr, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, nullptr); @@ -839,6 +840,7 @@ ShutdownXPCOM(nsIServiceManager* aServMgr) nsCOMPtr mgr; rv = NS_GetServiceManager(getter_AddRefs(mgr)); if (NS_SUCCEEDED(rv)) { + mozilla::KillClearOnShutdown(ShutdownPhase::Shutdown); observerService->NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID, nullptr); } @@ -851,6 +853,7 @@ ShutdownXPCOM(nsIServiceManager* aServMgr) mozilla::scache::StartupCache::DeleteSingleton(); if (observerService) + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownThreads); observerService->NotifyObservers(nullptr, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, nullptr); @@ -881,6 +884,7 @@ ShutdownXPCOM(nsIServiceManager* aServMgr) // We save the "xpcom-shutdown-loaders" observers to notify after // the observerservice is gone. if (observerService) { + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownLoaders); observerService->EnumerateObservers(NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID, getter_AddRefs(moduleLoaders)); @@ -891,7 +895,7 @@ ShutdownXPCOM(nsIServiceManager* aServMgr) // Free ClearOnShutdown()'ed smart pointers. This needs to happen *after* // we've finished notifying observers of XPCOM shutdown, because shutdown // observers themselves might call ClearOnShutdown(). - mozilla::KillClearOnShutdown(); + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownFinal); // XPCOM is officially in shutdown mode NOW // Set this only after the observers have been notified as this From 19b122873dbeb5c03c0c87773088c6823179f9e7 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 8 Nov 2023 16:38:54 +0800 Subject: [PATCH 02/12] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1240985 - Always enqueue OnMaybeDequeueOne task when receiving a message (r=dvander) (a5056d5942) - Bug 1240985 - Null out mRecvd if message it corresponds to is cancelled (r=dvander) (3eb16e50b4) - Bug 1245649: Merge browser and toolkit eslint rule settings. r=felipe (fb8837edb9) - Bug 1189799 - Make sure that about:performance displays each add-on only once (more tests);r=felipe (67cc74db67) - Bug 1230471 - Basic eslint fixes in places. r=mconley (1460c46edc) - Bug 1142734: Allow unloading the loader sandbox module. r=jsantell (052f483a6d) - Bug 1047595 - make picking colors work in HCM / when author colors are disabled, r=jaws (728163434b) - Bug 1244647 fix typeof check, r=mikedeboer (72da15da21) - Bug 1229240 - make applyConstraints() reject instead of crash on non-gUM tracks. r=jesup (93d2abf43a) - Bug 1239893 - Add gonk SidebandStream handling to DOMHwMediaStream r=roc (86739bc7a4) - Bug 1189162 - Clamp gamepad.timestamp and VideoPlaybackQuality.creationTime. r=bz (daaa3cc0db) - Bug 1232348 - adjust nested-frame checking condition. r=baku. (e4d1930bef) - Bug 1166556 - Don't start geolocation provider if the cached position is being used. r=jdm (3ad2c5dfa9) - Bug 858827 - [Geolocation] Add a handling for getCurrentLocation when timeout is zero. r=jdm (63dd4ccaf3) - Bug 1216148 - Handle how geolocation acts when the app's visibility changes. r=kchen. (dbeb9ef51c) - Bug 1230209 - Add more telemetry for Geolocation usage f=bsmedberg r=tanvi,rbarnes,jdm (c32b195d25) - Bug 1238825: Add "dom.bluetooth.webbluetooth.enabled" preference for WebBluetooth API development. r=btian, r=bz (3298be0b87) - Bug 1223722: Transfer Bluetooth addresses in |BluetoothValue|, r=brsun (1cc507823b) - Bug 1223722: Transfer Bluetooth remote names in |BluetoothValue|, r=brsun (73a008b991) - Bug 789315, notify MutationObservers before running + + Test for Bug 789315 + + + + + + + + + +Mozilla Bug 789315 +

+ +
+
+ + diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index ba0f63d726..bbaa56c8e5 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -92,14 +92,13 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, nsGlobalWindow* win = aIsJSImplementedWebIDL ? nullptr : xpc::WindowGlobalOrNull(realCallback); if (win) { - // Make sure that if this is a window it's the current inner, since the - // nsIScriptContext and hence JSContext are associated with the outer - // window. Which means that if someone holds on to a function from a - // now-unloaded document we'd have the new document as the script entry - // point... + // Make sure that if this is a window it has an active document, since + // the nsIScriptContext and hence JSContext are associated with the + // outer window. Which means that if someone holds on to a function + // from a now-unloaded document we'd have the new document as the + // script entry point... MOZ_ASSERT(win->IsInnerWindow()); - nsPIDOMWindow* outer = win->GetOuterWindow(); - if (!outer || win != outer->GetCurrentInnerWindow()) { + if (!win->HasActiveDocument()) { // Just bail out from here return; } diff --git a/dom/bindings/test/mochitest.ini b/dom/bindings/test/mochitest.ini index a55ca350d4..a3dc8ac954 100644 --- a/dom/bindings/test/mochitest.ini +++ b/dom/bindings/test/mochitest.ini @@ -26,6 +26,7 @@ skip-if = debug == false [test_bug1041646.html] [test_bug1123875.html] [test_barewordGetsWindow.html] +[test_callback_across_document_open.html] [test_callback_default_thisval.html] [test_cloneAndImportNode.html] [test_defineProperty.html] diff --git a/dom/bindings/test/test_callback_across_document_open.html b/dom/bindings/test/test_callback_across_document_open.html new file mode 100644 index 0000000000..2a505cefab --- /dev/null +++ b/dom/bindings/test/test_callback_across_document_open.html @@ -0,0 +1,21 @@ + + +Test for callback invocation for a callback that comes from a + no-longer-current window that still has an active document. + + +
+ + diff --git a/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp index 068e0e2efb..ca7af7dcdf 100644 --- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp @@ -688,7 +688,7 @@ BluetoothA2dpManager::NotifyConnectionStatusChanged() // Dispatch an event of status change DispatchStatusChangedEvent( - NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), deviceAddressStr, mA2dpConnected); + NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected); } void diff --git a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp index fb3e7463dd..f35aadba21 100644 --- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp +++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp @@ -1268,7 +1268,11 @@ PackPDU(const BluetoothProperty& aIn, DaemonSocketPDU& aPDU) switch (aIn.mType) { case PROPERTY_BDNAME: - /* fall through */ + rv = PackPDU(PackConversion(aIn.mRemoteName.mLength), + PackArray(aIn.mRemoteName.mName, + aIn.mRemoteName.mLength), + aPDU); + break; case PROPERTY_REMOTE_FRIENDLY_NAME: { NS_ConvertUTF16toUTF8 stringUTF8(aIn.mString); @@ -1561,8 +1565,16 @@ UnpackPDU(DaemonSocketPDU& aPDU, BluetoothProperty& aOut) } switch (aOut.mType) { - case PROPERTY_BDNAME: - /* fall through */ + case PROPERTY_BDNAME: { + const uint8_t* data = aPDU.Consume(len); + if (MOZ_HAL_IPC_UNPACK_WARN_IF(!data, BluetoothProperty)) { + return NS_ERROR_ILLEGAL_VALUE; + } + // We construct an nsCString here because the string + // returned from the PDU is not 0-terminated. + aOut.mRemoteName.Assign(data, len); + } + break; case PROPERTY_REMOTE_FRIENDLY_NAME: { const uint8_t* data = aPDU.Consume(len); if (MOZ_HAL_IPC_UNPACK_WARN_IF(!data, BluetoothProperty)) { diff --git a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h index 7dfbddd586..ec743c5544 100644 --- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h +++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h @@ -464,6 +464,10 @@ UnpackPDU(DaemonSocketPDU& aPDU, BluetoothRemoteName& aOut) if (!aPDU.Consume(1)) { return NS_ERROR_OUT_OF_MEMORY; } + + auto end = std::find(aOut.mName, aOut.mName + sizeof(aOut.mName), '\0'); + + aOut.mLength = end - aOut.mName; return NS_OK; } diff --git a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp index 18c770f445..10247d86df 100644 --- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp @@ -2616,10 +2616,7 @@ private: InfallibleTArray properties; - nsAutoString addressStr; - AddressToString(mBdAddr, addressStr); - - AppendNamedValue(properties, "Address", addressStr); + AppendNamedValue(properties, "Address", mBdAddr); AppendNamedValue(properties, "Rssi", mRssi); AppendNamedValue(properties, "GattAdv", mAdvData); AppendNamedValue(properties, "Type", static_cast(type)); @@ -3411,13 +3408,10 @@ BluetoothGattManager::ConnectionNotification(int aConnId, server->mConnectionMap.Remove(aBdAddr); } - nsAutoString bdAddrStr; - AddressToString(aBdAddr, bdAddrStr); - // Notify BluetoothGattServer that connection status changed InfallibleTArray props; AppendNamedValue(props, "Connected", aConnected); - AppendNamedValue(props, "Address", bdAddrStr); + AppendNamedValue(props, "Address", aBdAddr); bs->DistributeSignal( NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID), server->mAppUuid, @@ -3725,15 +3719,12 @@ BluetoothGattManager::RequestReadNotification( return; } - nsAutoString bdAddrStr; - AddressToString(aBdAddr, bdAddrStr); - // Distribute a signal to gattServer InfallibleTArray properties; AppendNamedValue(properties, "TransId", aTransId); AppendNamedValue(properties, "AttrHandle", aAttributeHandle); - AppendNamedValue(properties, "Address", bdAddrStr); + AppendNamedValue(properties, "Address", aBdAddr); AppendNamedValue(properties, "NeedResponse", true); AppendNamedValue(properties, "Value", nsTArray()); @@ -3782,15 +3773,12 @@ BluetoothGattManager::RequestWriteNotification( return; } - nsAutoString bdAddrStr; - AddressToString(aBdAddr, bdAddrStr); - // Distribute a signal to gattServer InfallibleTArray properties; AppendNamedValue(properties, "TransId", aTransId); AppendNamedValue(properties, "AttrHandle", aAttributeHandle); - AppendNamedValue(properties, "Address", bdAddrStr); + AppendNamedValue(properties, "Address", aBdAddr); AppendNamedValue(properties, "NeedResponse", aNeedResponse); nsTArray value; diff --git a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp index 3613418162..ac8c85f78f 100644 --- a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp @@ -1420,13 +1420,10 @@ BluetoothOppManager::FileTransferComplete() return; } - nsAutoString deviceAddressStr; - AddressToString(mDeviceAddress, deviceAddressStr); - NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-complete"); InfallibleTArray parameters; - AppendNamedValue(parameters, "address", deviceAddressStr); + AppendNamedValue(parameters, "address", mDeviceAddress); AppendNamedValue(parameters, "success", mSuccessFlag); AppendNamedValue(parameters, "received", mIsServer); AppendNamedValue(parameters, "fileName", mFileName); @@ -1441,13 +1438,10 @@ BluetoothOppManager::FileTransferComplete() void BluetoothOppManager::StartFileTransfer() { - nsAutoString deviceAddressStr; - AddressToString(mDeviceAddress, deviceAddressStr); - NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-transfer-start"); InfallibleTArray parameters; - AppendNamedValue(parameters, "address", deviceAddressStr); + AppendNamedValue(parameters, "address", mDeviceAddress); AppendNamedValue(parameters, "received", mIsServer); AppendNamedValue(parameters, "fileName", mFileName); AppendNamedValue(parameters, "fileLength", mFileLength); @@ -1461,13 +1455,10 @@ BluetoothOppManager::StartFileTransfer() void BluetoothOppManager::UpdateProgress() { - nsAutoString deviceAddressStr; - AddressToString(mDeviceAddress, deviceAddressStr); - NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-update-progress"); InfallibleTArray parameters; - AppendNamedValue(parameters, "address", deviceAddressStr); + AppendNamedValue(parameters, "address", mDeviceAddress); AppendNamedValue(parameters, "received", mIsServer); AppendNamedValue(parameters, "processedLength", mSentFileLength); AppendNamedValue(parameters, "fileLength", mFileLength); @@ -1478,13 +1469,10 @@ BluetoothOppManager::UpdateProgress() void BluetoothOppManager::ReceivingFileConfirmation() { - nsAutoString deviceAddressStr; - AddressToString(mDeviceAddress, deviceAddressStr); - NS_NAMED_LITERAL_STRING(type, "bluetooth-opp-receiving-file-confirmation"); InfallibleTArray parameters; - AppendNamedValue(parameters, "address", deviceAddressStr); + AppendNamedValue(parameters, "address", mDeviceAddress); AppendNamedValue(parameters, "fileName", mFileName); AppendNamedValue(parameters, "fileLength", mFileLength); AppendNamedValue(parameters, "contentType", mContentType); diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp index a94702d497..7abad6a08c 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp @@ -784,26 +784,15 @@ BluetoothServiceBluedroid::GetAdaptersInternal( InfallibleTArray adaptersProperties; uint32_t numAdapters = 1; // Bluedroid supports single adapter only - nsAutoString bdAddressStr; - AddressToString(mBdAddress, bdAddressStr); - - nsTArray bondedAddresses; - - for (uint32_t i = 0; i < mBondedAddresses.Length(); ++i) { - nsAutoString bondedAddressStr; - AddressToString(mBondedAddresses[i], bondedAddressStr); - bondedAddresses.AppendElement(bondedAddressStr); - } - for (uint32_t i = 0; i < numAdapters; i++) { InfallibleTArray properties; AppendNamedValue(properties, "State", mEnabled); - AppendNamedValue(properties, "Address", bdAddressStr); + AppendNamedValue(properties, "Address", mBdAddress); AppendNamedValue(properties, "Name", mBdName); AppendNamedValue(properties, "Discoverable", mDiscoverable); AppendNamedValue(properties, "Discovering", mDiscovering); - AppendNamedValue(properties, "PairedDevices", bondedAddresses); + AppendNamedValue(properties, "PairedDevices", mBondedAddresses); AppendNamedValue(adaptersProperties, "Adapter", BluetoothValue(properties)); @@ -2014,14 +2003,11 @@ BluetoothServiceBluedroid::AdapterStateChangedNotification(bool aState) // Cleanup static adapter properties and notify adapter. mBdAddress.Clear(); - mBdName.Truncate(); - - nsAutoString bdAddressStr; - AddressToString(mBdAddress, bdAddressStr); + mBdName.Clear(); InfallibleTArray props; AppendNamedValue(props, "Name", mBdName); - AppendNamedValue(props, "Address", bdAddressStr); + AppendNamedValue(props, "Address", mBdAddress); if (mDiscoverable) { mDiscoverable = false; AppendNamedValue(props, "Discoverable", false); @@ -2120,13 +2106,10 @@ BluetoothServiceBluedroid::AdapterPropertiesNotification( if (p.mType == PROPERTY_BDADDR) { mBdAddress = p.mBdAddress; - nsAutoString addressStr; - AddressToString(mBdAddress, addressStr); - - AppendNamedValue(propertiesArray, "Address", addressStr); + AppendNamedValue(propertiesArray, "Address", mBdAddress); } else if (p.mType == PROPERTY_BDNAME) { - mBdName = p.mString; + mBdName = p.mRemoteName; AppendNamedValue(propertiesArray, "Name", mBdName); } else if (p.mType == PROPERTY_ADAPTER_SCAN_MODE) { @@ -2149,15 +2132,7 @@ BluetoothServiceBluedroid::AdapterPropertiesNotification( mBondedAddresses.Clear(); mBondedAddresses.AppendElements(p.mBdAddressArray); - nsTArray bondedAddresses; - - for (unsigned int j = 0; j < p.mBdAddressArray.Length(); ++j) { - nsAutoString addressStr; - AddressToString(p.mBdAddressArray[j], addressStr); - bondedAddresses.AppendElement(addressStr); - } - - AppendNamedValue(propertiesArray, "PairedDevices", bondedAddresses); + AppendNamedValue(propertiesArray, "PairedDevices", mBondedAddresses); } else if (p.mType == PROPERTY_UNKNOWN) { /* Bug 1065999: working around unknown properties */ } else { @@ -2197,21 +2172,18 @@ BluetoothServiceBluedroid::RemoteDevicePropertiesNotification( InfallibleTArray propertiesArray; - nsAutoString bdAddrStr; - AddressToString(aBdAddr, bdAddrStr); - - AppendNamedValue(propertiesArray, "Address", bdAddrStr); + AppendNamedValue(propertiesArray, "Address", aBdAddr); for (int i = 0; i < aNumProperties; ++i) { const BluetoothProperty& p = aProperties[i]; if (p.mType == PROPERTY_BDNAME) { - AppendNamedValue(propertiesArray, "Name", p.mString); + AppendNamedValue(propertiesArray, "Name", p.mRemoteName); // Update mapping mDeviceNameMap.Remove(aBdAddr); - mDeviceNameMap.Put(aBdAddr, p.mString); + mDeviceNameMap.Put(aBdAddr, p.mRemoteName); } else if (p.mType == PROPERTY_CLASS_OF_DEVICE) { uint32_t cod = p.mUint32; AppendNamedValue(propertiesArray, "Cod", cod); @@ -2293,6 +2265,8 @@ BluetoothServiceBluedroid::RemoteDevicePropertiesNotification( // changing the order of (1,2) and (3). // Update to registered BluetoothDevice objects + nsAutoString bdAddrStr; + AddressToString(aBdAddr, bdAddrStr); BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"), bdAddrStr, propertiesArray); @@ -2337,20 +2311,18 @@ BluetoothServiceBluedroid::DeviceFoundNotification( InfallibleTArray propertiesArray; BluetoothAddress bdAddr; - nsString bdName; + BluetoothRemoteName bdName; for (int i = 0; i < aNumProperties; i++) { const BluetoothProperty& p = aProperties[i]; if (p.mType == PROPERTY_BDADDR) { - nsAutoString addressStr; - AddressToString(p.mBdAddress, addressStr); - AppendNamedValue(propertiesArray, "Address", addressStr); + AppendNamedValue(propertiesArray, "Address", p.mBdAddress); bdAddr = p.mBdAddress; } else if (p.mType == PROPERTY_BDNAME) { - AppendNamedValue(propertiesArray, "Name", p.mString); - bdName = p.mString; + AppendNamedValue(propertiesArray, "Name", p.mRemoteName); + bdName = p.mRemoteName; } else if (p.mType == PROPERTY_CLASS_OF_DEVICE) { AppendNamedValue(propertiesArray, "Cod", p.mUint32); @@ -2417,24 +2389,20 @@ BluetoothServiceBluedroid::PinRequestNotification( { MOZ_ASSERT(NS_IsMainThread()); + BluetoothRemoteName bdName; InfallibleTArray propertiesArray; // If |aBdName| is empty, get device name from |mDeviceNameMap|; // Otherwise update mapping with |aBdName| - nsAutoString bdAddr; - AddressToString(aRemoteBdAddr, bdAddr); - - nsAutoString bdName; - RemoteNameToString(aBdName, bdName); - - if (bdName.IsEmpty()) { + if (aBdName.IsCleared()) { mDeviceNameMap.Get(aRemoteBdAddr, &bdName); } else { + bdName.Assign(aBdName.mName, aBdName.mLength); mDeviceNameMap.Remove(aRemoteBdAddr); mDeviceNameMap.Put(aRemoteBdAddr, bdName); } - AppendNamedValue(propertiesArray, "address", bdAddr); + AppendNamedValue(propertiesArray, "address", aRemoteBdAddr); AppendNamedValue(propertiesArray, "name", bdName); AppendNamedValue(propertiesArray, "passkey", EmptyString()); AppendNamedValue(propertiesArray, "type", @@ -2452,19 +2420,15 @@ BluetoothServiceBluedroid::SspRequestNotification( { MOZ_ASSERT(NS_IsMainThread()); + BluetoothRemoteName bdName; InfallibleTArray propertiesArray; // If |aBdName| is empty, get device name from |mDeviceNameMap|; // Otherwise update mapping with |aBdName| - nsAutoString bdAddr; - AddressToString(aRemoteBdAddr, bdAddr); - - nsAutoString bdName; - RemoteNameToString(aBdName, bdName); - - if (bdName.IsEmpty()) { + if (aBdName.IsCleared()) { mDeviceNameMap.Get(aRemoteBdAddr, &bdName); } else { + bdName.Assign(aBdName.mName, aBdName.mLength); mDeviceNameMap.Remove(aRemoteBdAddr); mDeviceNameMap.Put(aRemoteBdAddr, bdName); } @@ -2496,7 +2460,7 @@ BluetoothServiceBluedroid::SspRequestNotification( return; } - AppendNamedValue(propertiesArray, "address", bdAddr); + AppendNamedValue(propertiesArray, "address", aRemoteBdAddr); AppendNamedValue(propertiesArray, "name", bdName); AppendNamedValue(propertiesArray, "passkey", passkey); AppendNamedValue(propertiesArray, "type", pairingType); @@ -2544,10 +2508,7 @@ BluetoothServiceBluedroid::BondStateChangedNotification( } // Query pairing device name from hash table - nsAutoString remoteBdAddr; - AddressToString(aRemoteBdAddr, remoteBdAddr); - - nsString remotebdName; + BluetoothRemoteName remotebdName; mDeviceNameMap.Get(aRemoteBdAddr, &remotebdName); // Update bonded address array and append pairing device name @@ -2567,13 +2528,15 @@ BluetoothServiceBluedroid::BondStateChangedNotification( } // Notify device of attribute changed + nsAutoString remoteBdAddr; + AddressToString(aRemoteBdAddr, remoteBdAddr); AppendNamedValue(propertiesArray, "Paired", bonded); DistributeSignal(NS_LITERAL_STRING("PropertyChanged"), remoteBdAddr, BluetoothValue(propertiesArray)); // Notify adapter of device paired/unpaired - InsertNamedValue(propertiesArray, 0, "Address", remoteBdAddr); + InsertNamedValue(propertiesArray, 0, "Address", aRemoteBdAddr); DistributeSignal(bonded ? NS_LITERAL_STRING(DEVICE_PAIRED_ID) : NS_LITERAL_STRING(DEVICE_UNPAIRED_ID), NS_LITERAL_STRING(KEY_ADAPTER), diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h index 2d9af32070..2f4ba58572 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h @@ -497,7 +497,7 @@ protected: // Adapter properties BluetoothAddress mBdAddress; - nsString mBdName; + BluetoothRemoteName mBdName; bool mEnabled; bool mDiscoverable; bool mDiscovering; @@ -522,7 +522,7 @@ protected: nsTArray mGetDeviceRequests; // mapping table for remote devices - nsDataHashtable mDeviceNameMap; + nsDataHashtable mDeviceNameMap; // Arrays for SDP operations nsTArray mGetRemoteServiceRecordArray; diff --git a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp index 4c80ebe1d9..ddc202b4db 100644 --- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp +++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp @@ -624,7 +624,7 @@ BluetoothHfpManager::NotifyConnectionStateChanged(const nsAString& aType) return; } - DispatchStatusChangedEvent(eventName, deviceAddressStr, status); + DispatchStatusChangedEvent(eventName, mDeviceAddress, status); // Notify profile controller if (aType.EqualsLiteral(BLUETOOTH_HFP_STATUS_CHANGED_ID)) { diff --git a/dom/bluetooth/bluez/BluetoothA2dpManager.cpp b/dom/bluetooth/bluez/BluetoothA2dpManager.cpp index bf0d1b7542..807dabb60d 100644 --- a/dom/bluetooth/bluez/BluetoothA2dpManager.cpp +++ b/dom/bluetooth/bluez/BluetoothA2dpManager.cpp @@ -366,7 +366,7 @@ BluetoothA2dpManager::NotifyConnectionStatusChanged() // Dispatch an event of status change DispatchStatusChangedEvent( - NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), deviceAddressStr, mA2dpConnected); + NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected); } void diff --git a/dom/bluetooth/bluez/BluetoothHfpManager.cpp b/dom/bluetooth/bluez/BluetoothHfpManager.cpp index 095137d0c6..e548f6cf07 100644 --- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp +++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp @@ -547,7 +547,7 @@ BluetoothHfpManager::NotifyConnectionStatusChanged(const nsAString& aType) return; } - DispatchStatusChangedEvent(eventName, deviceAddressStr, status); + DispatchStatusChangedEvent(eventName, mDeviceAddress, status); } #ifdef MOZ_B2G_RIL diff --git a/dom/bluetooth/bluez/BluetoothOppManager.cpp b/dom/bluetooth/bluez/BluetoothOppManager.cpp index 1f2de0b080..d09f66e51f 100644 --- a/dom/bluetooth/bluez/BluetoothOppManager.cpp +++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp @@ -1383,16 +1383,13 @@ BluetoothOppManager::FileTransferComplete() return; } - nsAutoString connectedDeviceAddressStr; - AddressToString(mConnectedDeviceAddress, connectedDeviceAddressStr); - nsString type, name; BluetoothValue v; InfallibleTArray parameters; type.AssignLiteral("bluetooth-opp-transfer-complete"); name.AssignLiteral("address"); - v = connectedDeviceAddressStr; + v = mConnectedDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("success"); @@ -1426,16 +1423,13 @@ BluetoothOppManager::FileTransferComplete() void BluetoothOppManager::StartFileTransfer() { - nsAutoString connectedDeviceAddressStr; - AddressToString(mConnectedDeviceAddress, connectedDeviceAddressStr); - nsString type, name; BluetoothValue v; InfallibleTArray parameters; type.AssignLiteral("bluetooth-opp-transfer-start"); name.AssignLiteral("address"); - v = connectedDeviceAddressStr; + v = mConnectedDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("received"); @@ -1465,16 +1459,13 @@ BluetoothOppManager::StartFileTransfer() void BluetoothOppManager::UpdateProgress() { - nsAutoString connectedDeviceAddressStr; - AddressToString(mConnectedDeviceAddress, connectedDeviceAddressStr); - nsString type, name; BluetoothValue v; InfallibleTArray parameters; type.AssignLiteral("bluetooth-opp-update-progress"); name.AssignLiteral("address"); - v = connectedDeviceAddressStr; + v = mConnectedDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("received"); @@ -1498,16 +1489,13 @@ BluetoothOppManager::UpdateProgress() void BluetoothOppManager::ReceivingFileConfirmation() { - nsAutoString connectedDeviceAddressStr; - AddressToString(mConnectedDeviceAddress, connectedDeviceAddressStr); - nsString type, name; BluetoothValue v; InfallibleTArray parameters; type.AssignLiteral("bluetooth-opp-receiving-file-confirmation"); name.AssignLiteral("address"); - v = connectedDeviceAddressStr; + v = mConnectedDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("fileName"); diff --git a/dom/bluetooth/common/BluetoothCommon.h b/dom/bluetooth/common/BluetoothCommon.h index ecf5dd56df..966300cfe8 100644 --- a/dom/bluetooth/common/BluetoothCommon.h +++ b/dom/bluetooth/common/BluetoothCommon.h @@ -713,6 +713,52 @@ struct BluetoothRemoteInfo { struct BluetoothRemoteName { uint8_t mName[248]; /* not \0-terminated */ + uint8_t mLength; + + BluetoothRemoteName() + : mLength(0) + { } + + explicit BluetoothRemoteName(const nsACString_internal& aString) + : mLength(0) + { + MOZ_ASSERT(aString.Length() <= MOZ_ARRAY_LENGTH(mName)); + memcpy(mName, aString.Data(), aString.Length()); + mLength = aString.Length(); + } + + BluetoothRemoteName(const BluetoothRemoteName&) = default; + + BluetoothRemoteName& operator=(const BluetoothRemoteName&) = default; + + bool operator==(const BluetoothRemoteName& aRhs) const + { + MOZ_ASSERT(mLength <= MOZ_ARRAY_LENGTH(mName)); + return (mLength == aRhs.mLength) && + std::equal(aRhs.mName, aRhs.mName + aRhs.mLength, mName); + } + + bool operator!=(const BluetoothRemoteName& aRhs) const + { + return !operator==(aRhs); + } + + void Assign(const uint8_t* aName, size_t aLength) + { + MOZ_ASSERT(aLength <= MOZ_ARRAY_LENGTH(mName)); + memcpy(mName, aName, aLength); + mLength = aLength; + } + + void Clear() + { + mLength = 0; + } + + bool IsCleared() const + { + return !mLength; + } }; struct BluetoothProperty { @@ -725,8 +771,10 @@ struct BluetoothProperty { /* PROPERTY_BDADDR */ BluetoothAddress mBdAddress; - /* PROPERTY_BDNAME - PROPERTY_REMOTE_FRIENDLY_NAME */ + /* PROPERTY_BDNAME */ + BluetoothRemoteName mRemoteName; + + /* PROPERTY_REMOTE_FRIENDLY_NAME */ nsString mString; /* PROPERTY_UUIDS */ @@ -764,6 +812,12 @@ struct BluetoothProperty { , mBdAddress(aBdAddress) { } + explicit BluetoothProperty(BluetoothPropertyType aType, + const BluetoothRemoteName& aRemoteName) + : mType(aType) + , mRemoteName(aRemoteName) + { } + explicit BluetoothProperty(BluetoothPropertyType aType, const nsAString& aString) : mType(aType) diff --git a/dom/bluetooth/common/BluetoothHidManager.cpp b/dom/bluetooth/common/BluetoothHidManager.cpp index 9cddb0040b..5dc2056ccb 100644 --- a/dom/bluetooth/common/BluetoothHidManager.cpp +++ b/dom/bluetooth/common/BluetoothHidManager.cpp @@ -247,14 +247,11 @@ BluetoothHidManager::NotifyStatusChanged() { MOZ_ASSERT(NS_IsMainThread()); - nsAutoString deviceAddressStr; - AddressToString(mDeviceAddress, deviceAddressStr); - NS_NAMED_LITERAL_STRING(type, BLUETOOTH_HID_STATUS_CHANGED_ID); InfallibleTArray parameters; AppendNamedValue(parameters, "connected", mConnected); - AppendNamedValue(parameters, "address", deviceAddressStr); + AppendNamedValue(parameters, "address", mDeviceAddress); BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); } diff --git a/dom/bluetooth/common/BluetoothUtils.cpp b/dom/bluetooth/common/BluetoothUtils.cpp index 3dfb289437..98b7efad71 100644 --- a/dom/bluetooth/common/BluetoothUtils.cpp +++ b/dom/bluetooth/common/BluetoothUtils.cpp @@ -150,12 +150,12 @@ NamedValueToProperty(const BluetoothNamedValue& aValue, switch (aProperty.mType) { case PROPERTY_BDNAME: - if (aValue.value().type() != BluetoothValue::TnsString) { - BT_LOGR("Bluetooth property value is not a string"); + if (aValue.value().type() != BluetoothValue::TBluetoothRemoteName) { + BT_LOGR("Bluetooth property value is not a remote name"); return NS_ERROR_ILLEGAL_VALUE; } // Set name - aProperty.mString = aValue.value().get_nsString(); + aProperty.mRemoteName = aValue.value().get_BluetoothRemoteName(); break; case PROPERTY_ADAPTER_SCAN_MODE: @@ -191,13 +191,14 @@ NamedValueToProperty(const BluetoothNamedValue& aValue, void RemoteNameToString(const BluetoothRemoteName& aRemoteName, nsAString& aString) { + MOZ_ASSERT(aRemoteName.mLength <= sizeof(aRemoteName.mName)); + auto name = reinterpret_cast(aRemoteName.mName); /* The content in |BluetoothRemoteName| is not a C string and not - * terminated by \0. We use |strnlen| to limit its length. + * terminated by \0. We use |mLength| to limit its length. */ - aString = - NS_ConvertUTF8toUTF16(name, strnlen(name, sizeof(aRemoteName.mName))); + aString = NS_ConvertUTF8toUTF16(name, aRemoteName.mLength); } nsresult @@ -517,7 +518,18 @@ SetJsObject(JSContext* aContext, const BluetoothValue& v = arr[i].value(); switch(v.type()) { - case BluetoothValue::TnsString: { + case BluetoothValue::TBluetoothAddress: { + nsAutoString addressStr; + AddressToString(v.get_BluetoothAddress(), addressStr); + + JSString* jsData = JS_NewUCStringCopyN(aContext, + addressStr.BeginReading(), + addressStr.Length()); + NS_ENSURE_TRUE(jsData, false); + val.setString(jsData); + break; + } + case BluetoothValue::TnsString: { JSString* jsData = JS_NewUCStringCopyN(aContext, v.get_nsString().BeginReading(), v.get_nsString().Length()); @@ -673,13 +685,13 @@ DispatchReplyError(BluetoothReplyRunnable* aRunnable, void DispatchStatusChangedEvent(const nsAString& aType, - const nsAString& aAddress, + const BluetoothAddress& aAddress, bool aStatus) { MOZ_ASSERT(NS_IsMainThread()); InfallibleTArray data; - AppendNamedValue(data, "address", nsString(aAddress)); + AppendNamedValue(data, "address", aAddress); AppendNamedValue(data, "status", aStatus); BluetoothService* bs = BluetoothService::Get(); diff --git a/dom/bluetooth/common/BluetoothUtils.h b/dom/bluetooth/common/BluetoothUtils.h index f188d27fba..8cc6ec4e57 100644 --- a/dom/bluetooth/common/BluetoothUtils.h +++ b/dom/bluetooth/common/BluetoothUtils.h @@ -313,7 +313,7 @@ DispatchReplyError(BluetoothReplyRunnable* aRunnable, void DispatchStatusChangedEvent(const nsAString& aType, - const nsAString& aDeviceAddress, + const BluetoothAddress& aDeviceAddress, bool aStatus); // diff --git a/dom/bluetooth/common/webapi/BluetoothAdapter.cpp b/dom/bluetooth/common/webapi/BluetoothAdapter.cpp index 613f80d86c..3aeed12f01 100644 --- a/dom/bluetooth/common/webapi/BluetoothAdapter.cpp +++ b/dom/bluetooth/common/webapi/BluetoothAdapter.cpp @@ -406,9 +406,6 @@ void BluetoothAdapter::GetPairedDeviceProperties( const nsTArray& aDeviceAddresses) { - BluetoothService* bs = BluetoothService::Get(); - NS_ENSURE_TRUE_VOID(bs); - nsTArray deviceAddresses; deviceAddresses.SetLength(aDeviceAddresses.Length()); @@ -420,10 +417,20 @@ BluetoothAdapter::GetPairedDeviceProperties( } } + GetPairedDeviceProperties(deviceAddresses); +} + +void +BluetoothAdapter::GetPairedDeviceProperties( + const nsTArray& aDeviceAddresses) +{ + BluetoothService* bs = BluetoothService::Get(); + NS_ENSURE_TRUE_VOID(bs); + RefPtr results = new BluetoothVoidReplyRunnable(nullptr); - auto rv = bs->GetPairedDevicePropertiesInternal(deviceAddresses, results); + auto rv = bs->GetPairedDevicePropertiesInternal(aDeviceAddresses, results); if (NS_FAILED(rv)) { BT_WARNING("GetPairedDeviceProperties failed"); return; @@ -449,9 +456,9 @@ BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue) } } } else if (name.EqualsLiteral("Name")) { - mName = value.get_nsString(); + RemoteNameToString(value.get_BluetoothRemoteName(), mName); } else if (name.EqualsLiteral("Address")) { - mAddress = value.get_nsString(); + AddressToString(value.get_BluetoothAddress(), mAddress); } else if (name.EqualsLiteral("Discoverable")) { mDiscoverable = value.get_bool(); } else if (name.EqualsLiteral("Discovering")) { @@ -461,11 +468,14 @@ BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue) SetDiscoveryHandleInUse(nullptr); } } else if (name.EqualsLiteral("PairedDevices")) { - const InfallibleTArray& pairedDeviceAddresses - = value.get_ArrayOfnsString(); + const InfallibleTArray& pairedDeviceAddresses + = value.get_ArrayOfBluetoothAddress(); for (uint32_t i = 0; i < pairedDeviceAddresses.Length(); i++) { - if (mDevices.Contains(pairedDeviceAddresses[i])) { + nsString pairedDeviceAddressStr; + AddressToString(pairedDeviceAddresses[i], pairedDeviceAddressStr); + + if (mDevices.Contains(pairedDeviceAddressStr)) { // Existing paired devices handle 'PropertyChanged' signal // in BluetoothDevice::Notify() continue; @@ -535,15 +545,15 @@ BluetoothAdapter::Notify(const BluetoothSignal& aData) v.get_ArrayOfBluetoothNamedValue(); MOZ_ASSERT(arr.Length() == 2 && - arr[0].value().type() == BluetoothValue::TnsString && + arr[0].value().type() == BluetoothValue::TBluetoothAddress && arr[1].value().type() == BluetoothValue::Tbool); - nsString address = arr[0].value().get_nsString(); + BluetoothAddress address = arr[0].value().get_BluetoothAddress(); bool status = arr[1].value().get_bool(); BluetoothStatusChangedEventInit init; init.mBubbles = false; init.mCancelable = false; - init.mAddress = address; + AddressToString(address, init.mAddress); init.mStatus = status; RefPtr event = BluetoothStatusChangedEvent::Constructor(this, aData.name(), init); @@ -778,9 +788,10 @@ BluetoothAdapter::SetName(const nsAString& aName, ErrorResult& aRv) BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE); // Wrap property to set and runnable to handle result - nsString name(aName); BluetoothNamedValue property(NS_LITERAL_STRING("Name"), - BluetoothValue(name)); + BluetoothValue( + BluetoothRemoteName( + NS_ConvertUTF16toUTF8(aName)))); BT_ENSURE_SUCCESS_REJECT( bs->SetProperty(BluetoothObjectType::TYPE_ADAPTER, property, new BluetoothVoidReplyRunnable(nullptr, promise)), @@ -1025,12 +1036,18 @@ BluetoothAdapter::IsAdapterAttributeChanged(BluetoothAdapterAttribute aType, MOZ_ASSERT(aValue.type() == BluetoothValue::Tbool); return aValue.get_bool() ? mState != BluetoothAdapterState::Enabled : mState != BluetoothAdapterState::Disabled; - case BluetoothAdapterAttribute::Name: - MOZ_ASSERT(aValue.type() == BluetoothValue::TnsString); - return !mName.Equals(aValue.get_nsString()); - case BluetoothAdapterAttribute::Address: - MOZ_ASSERT(aValue.type() == BluetoothValue::TnsString); - return !mAddress.Equals(aValue.get_nsString()); + case BluetoothAdapterAttribute::Name: { + MOZ_ASSERT(aValue.type() == BluetoothValue::TBluetoothRemoteName); + nsAutoString name; + RemoteNameToString(aValue.get_BluetoothRemoteName(), name); + return !name.Equals(mName); + } + case BluetoothAdapterAttribute::Address: { + MOZ_ASSERT(aValue.type() == BluetoothValue::TBluetoothAddress); + BluetoothAddress address; + StringToAddress(mAddress, address); + return address != aValue.get_BluetoothAddress(); + } case BluetoothAdapterAttribute::Discoverable: MOZ_ASSERT(aValue.type() == BluetoothValue::Tbool); return mDiscoverable != aValue.get_bool(); @@ -1207,14 +1224,16 @@ BluetoothAdapter::HandleDevicePaired(const BluetoothValue& aValue) aValue.get_ArrayOfBluetoothNamedValue(); MOZ_ASSERT(arr.Length() == 3 && - arr[0].value().type() == BluetoothValue::TnsString && // Address - arr[1].value().type() == BluetoothValue::TnsString && // Name + arr[0].value().type() == BluetoothValue::TBluetoothAddress && // Address + arr[1].value().type() == BluetoothValue::TBluetoothRemoteName && // Name arr[2].value().type() == BluetoothValue::Tbool); // Paired - MOZ_ASSERT(!arr[0].value().get_nsString().IsEmpty() && + MOZ_ASSERT(!arr[0].value().get_BluetoothAddress().IsCleared() && arr[2].value().get_bool()); // Append the paired device if it doesn't exist in adapter's devices array - size_t index = mDevices.IndexOf(arr[0].value().get_nsString()); + nsString addressStr; + AddressToString(arr[0].value().get_BluetoothAddress(), addressStr); + size_t index = mDevices.IndexOf(addressStr); if (index == mDevices.NoIndex) { index = mDevices.Length(); // the new device's index mDevices.AppendElement( @@ -1240,13 +1259,14 @@ BluetoothAdapter::HandleDeviceUnpaired(const BluetoothValue& aValue) aValue.get_ArrayOfBluetoothNamedValue(); MOZ_ASSERT(arr.Length() == 2 && - arr[0].value().type() == BluetoothValue::TnsString && // Address - arr[1].value().type() == BluetoothValue::Tbool); // Paired - MOZ_ASSERT(!arr[0].value().get_nsString().IsEmpty() && + arr[0].value().type() == BluetoothValue::TBluetoothAddress && // Address + arr[1].value().type() == BluetoothValue::Tbool); // Paired + MOZ_ASSERT(!arr[0].value().get_BluetoothAddress().IsCleared() && !arr[1].value().get_bool()); // Remove the device with the same address - nsString deviceAddress = arr[0].value().get_nsString(); + nsString deviceAddress; + AddressToString(arr[0].value().get_BluetoothAddress(), deviceAddress); mDevices.RemoveElement(deviceAddress); // Notify application of unpaired device diff --git a/dom/bluetooth/common/webapi/BluetoothAdapter.h b/dom/bluetooth/common/webapi/BluetoothAdapter.h index 137f8d616f..2a90e0e8f2 100644 --- a/dom/bluetooth/common/webapi/BluetoothAdapter.h +++ b/dom/bluetooth/common/webapi/BluetoothAdapter.h @@ -193,6 +193,9 @@ public: JS::Handle aGivenProto) override; virtual void DisconnectFromOwner() override; + void GetPairedDeviceProperties( + const nsTArray& aDeviceAddresses); + /** * Set this adapter's discovery handle in use (mDiscoveryHandleInUse). * diff --git a/dom/bluetooth/common/webapi/BluetoothDevice.cpp b/dom/bluetooth/common/webapi/BluetoothDevice.cpp index 7ca29cd5c8..28277041de 100644 --- a/dom/bluetooth/common/webapi/BluetoothDevice.cpp +++ b/dom/bluetooth/common/webapi/BluetoothDevice.cpp @@ -146,9 +146,9 @@ BluetoothDevice::SetPropertyByValue(const BluetoothNamedValue& aValue) const nsString& name = aValue.name(); const BluetoothValue& value = aValue.value(); if (name.EqualsLiteral("Name")) { - mName = value.get_nsString(); + RemoteNameToString(value.get_BluetoothRemoteName(), mName); } else if (name.EqualsLiteral("Address")) { - mAddress = value.get_nsString(); + AddressToString(value.get_BluetoothAddress(), mAddress); } else if (name.EqualsLiteral("Cod")) { mCod->Update(value.get_uint32_t()); } else if (name.EqualsLiteral("Paired")) { @@ -250,9 +250,12 @@ BluetoothDevice::IsDeviceAttributeChanged(BluetoothDeviceAttribute aType, case BluetoothDeviceAttribute::Cod: MOZ_ASSERT(aValue.type() == BluetoothValue::Tuint32_t); return !mCod->Equals(aValue.get_uint32_t()); - case BluetoothDeviceAttribute::Name: - MOZ_ASSERT(aValue.type() == BluetoothValue::TnsString); - return !mName.Equals(aValue.get_nsString()); + case BluetoothDeviceAttribute::Name: { + MOZ_ASSERT(aValue.type() == BluetoothValue::TBluetoothRemoteName); + nsAutoString remoteNameStr; + RemoteNameToString(aValue.get_BluetoothRemoteName(), remoteNameStr); + return !mName.Equals(remoteNameStr); + } case BluetoothDeviceAttribute::Paired: MOZ_ASSERT(aValue.type() == BluetoothValue::Tbool); return mPaired != aValue.get_bool(); diff --git a/dom/bluetooth/common/webapi/BluetoothGattServer.cpp b/dom/bluetooth/common/webapi/BluetoothGattServer.cpp index aa022e34c7..ce46d64929 100644 --- a/dom/bluetooth/common/webapi/BluetoothGattServer.cpp +++ b/dom/bluetooth/common/webapi/BluetoothGattServer.cpp @@ -89,11 +89,11 @@ void BluetoothGattServer::HandleConnectionStateChanged( MOZ_ASSERT(arr.Length() == 2 && arr[0].value().type() == BluetoothValue::Tbool && - arr[1].value().type() == BluetoothValue::TnsString); + arr[1].value().type() == BluetoothValue::TBluetoothAddress); BluetoothStatusChangedEventInit init; init.mStatus = arr[0].value().get_bool(); - init.mAddress = arr[1].value().get_nsString(); + AddressToString(arr[1].value().get_BluetoothAddress(), init.mAddress); RefPtr event = BluetoothStatusChangedEvent::Constructor( @@ -190,14 +190,15 @@ BluetoothGattServer::HandleReadWriteRequest(const BluetoothValue& aValue, MOZ_ASSERT(arr.Length() == 5 && arr[0].value().type() == BluetoothValue::Tint32_t && arr[1].value().type() == BluetoothValue::TBluetoothAttributeHandle && - arr[2].value().type() == BluetoothValue::TnsString && + arr[2].value().type() == BluetoothValue::TBluetoothAddress && arr[3].value().type() == BluetoothValue::Tbool && arr[4].value().type() == BluetoothValue::TArrayOfuint8_t); int32_t requestId = arr[0].value().get_int32_t(); BluetoothAttributeHandle handle = arr[1].value().get_BluetoothAttributeHandle(); - nsString address = arr[2].value().get_nsString(); + nsString address; + AddressToString(arr[2].value().get_BluetoothAddress(), address); bool needResponse = arr[3].value().get_bool(); nsTArray value; value = arr[4].value().get_ArrayOfuint8_t(); diff --git a/dom/bluetooth/common/webapi/BluetoothManager.cpp b/dom/bluetooth/common/webapi/BluetoothManager.cpp index c1fe672fcf..c9fddf7ed6 100644 --- a/dom/bluetooth/common/webapi/BluetoothManager.cpp +++ b/dom/bluetooth/common/webapi/BluetoothManager.cpp @@ -13,6 +13,7 @@ #include "mozilla/dom/bluetooth/BluetoothManager.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/dom/BluetoothManagerBinding.h" +#include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" @@ -196,11 +197,12 @@ BluetoothManager::HandleAdapterAdded(const BluetoothValue& aValue) void BluetoothManager::HandleAdapterRemoved(const BluetoothValue& aValue) { - MOZ_ASSERT(aValue.type() == BluetoothValue::TnsString); + MOZ_ASSERT(aValue.type() == BluetoothValue::TBluetoothAddress); MOZ_ASSERT(DefaultAdapterExists()); // Remove the adapter of given address from adapters array - nsString addressToRemove = aValue.get_nsString(); + nsString addressToRemove; + AddressToString(aValue.get_BluetoothAddress(), addressToRemove); uint32_t i; for (i = 0; i < mAdapters.Length(); i++) { @@ -283,3 +285,10 @@ BluetoothManager::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return BluetoothManagerBinding::Wrap(aCx, this, aGivenProto); } + +// static +bool +BluetoothManager::B2GGattClientEnabled(JSContext* cx, JSObject* aGlobal) +{ + return !Preferences::GetBool("dom.bluetooth.webbluetooth.enabled"); +} diff --git a/dom/bluetooth/common/webapi/BluetoothManager.h b/dom/bluetooth/common/webapi/BluetoothManager.h index dce208b02d..6595e51e55 100644 --- a/dom/bluetooth/common/webapi/BluetoothManager.h +++ b/dom/bluetooth/common/webapi/BluetoothManager.h @@ -77,6 +77,12 @@ public: */ void AppendAdapter(const BluetoothValue& aValue); + /** + * Check whether B2G only GATT client API is enabled (true) or W3C + * WebBluetooth API is enabled (false). + */ + static bool B2GGattClientEnabled(JSContext* cx, JSObject* aGlobal); + private: BluetoothManager(nsPIDOMWindow* aWindow); ~BluetoothManager(); diff --git a/dom/bluetooth/common/webapi/BluetoothPairingListener.cpp b/dom/bluetooth/common/webapi/BluetoothPairingListener.cpp index 2f2e7e2d31..d398455eae 100644 --- a/dom/bluetooth/common/webapi/BluetoothPairingListener.cpp +++ b/dom/bluetooth/common/webapi/BluetoothPairingListener.cpp @@ -48,21 +48,29 @@ BluetoothPairingListener::~BluetoothPairingListener() } void -BluetoothPairingListener::DispatchPairingEvent(const nsAString& aName, - const nsAString& aAddress, - const nsAString& aPasskey, - const nsAString& aType) +BluetoothPairingListener::DispatchPairingEvent( + const BluetoothRemoteName& aName, + const BluetoothAddress& aAddress, + const nsAString& aPasskey, + const nsAString& aType) { - MOZ_ASSERT(!aName.IsEmpty() && !aAddress.IsEmpty() && !aType.IsEmpty()); + MOZ_ASSERT(!aAddress.IsCleared()); + MOZ_ASSERT(!aName.IsCleared() && !aType.IsEmpty()); + + nsString nameStr; + RemoteNameToString(aName, nameStr); + + nsString addressStr; + AddressToString(aAddress, addressStr); RefPtr handle = BluetoothPairingHandle::Create(GetOwner(), - aAddress, + addressStr, aType, aPasskey); BluetoothPairingEventInit init; - init.mDeviceName = aName; + init.mDeviceName = nameStr; init.mHandle = handle; RefPtr event = @@ -87,13 +95,13 @@ BluetoothPairingListener::Notify(const BluetoothSignal& aData) value.get_ArrayOfBluetoothNamedValue(); MOZ_ASSERT(arr.Length() == 4 && - arr[0].value().type() == BluetoothValue::TnsString && // address - arr[1].value().type() == BluetoothValue::TnsString && // name + arr[0].value().type() == BluetoothValue::TBluetoothAddress && // address + arr[1].value().type() == BluetoothValue::TBluetoothRemoteName && // name arr[2].value().type() == BluetoothValue::TnsString && // passkey arr[3].value().type() == BluetoothValue::TnsString); // type - nsString address = arr[0].value().get_nsString(); - nsString name = arr[1].value().get_nsString(); + BluetoothAddress address = arr[0].value().get_BluetoothAddress(); + const BluetoothRemoteName& name = arr[1].value().get_BluetoothRemoteName(); nsString passkey = arr[2].value().get_nsString(); nsString type = arr[3].value().get_nsString(); diff --git a/dom/bluetooth/common/webapi/BluetoothPairingListener.h b/dom/bluetooth/common/webapi/BluetoothPairingListener.h index 195c931b4e..8407a368e0 100644 --- a/dom/bluetooth/common/webapi/BluetoothPairingListener.h +++ b/dom/bluetooth/common/webapi/BluetoothPairingListener.h @@ -24,8 +24,8 @@ public: static already_AddRefed Create(nsPIDOMWindow* aWindow); - void DispatchPairingEvent(const nsAString& aName, - const nsAString& aAddress, + void DispatchPairingEvent(const BluetoothRemoteName& aName, + const BluetoothAddress& aAddress, const nsAString& aPasskey, const nsAString& aType); diff --git a/dom/bluetooth/ipc/BluetoothMessageUtils.h b/dom/bluetooth/ipc/BluetoothMessageUtils.h index eab1a59e1b..9124b564bb 100644 --- a/dom/bluetooth/ipc/BluetoothMessageUtils.h +++ b/dom/bluetooth/ipc/BluetoothMessageUtils.h @@ -79,6 +79,36 @@ struct ParamTraits } }; +template <> +struct ParamTraits +{ + typedef mozilla::dom::bluetooth::BluetoothRemoteName paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mLength); + for (size_t i = 0; i < aParam.mLength; ++i) { + WriteParam(aMsg, aParam.mName[i]); + } + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + if (!ReadParam(aMsg, aIter, &aResult->mLength)) { + return false; + } + if (aResult->mLength > MOZ_ARRAY_LENGTH(aResult->mName)) { + return false; + } + for (uint8_t i = 0; i < aResult->mLength; ++i) { + if (!ReadParam(aMsg, aIter, aResult->mName + i)) { + return false; + } + } + return true; + } +}; + template <> struct ParamTraits : public ContiguousEnumSerializer< diff --git a/dom/bluetooth/ipc/BluetoothTypes.ipdlh b/dom/bluetooth/ipc/BluetoothTypes.ipdlh index 5c335dc952..e699b1713d 100644 --- a/dom/bluetooth/ipc/BluetoothTypes.ipdlh +++ b/dom/bluetooth/ipc/BluetoothTypes.ipdlh @@ -4,6 +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/. */ +using mozilla::dom::bluetooth::BluetoothAddress + from "mozilla/dom/bluetooth/BluetoothCommon.h"; using mozilla::dom::bluetooth::BluetoothAttributeHandle from "mozilla/dom/bluetooth/BluetoothCommon.h"; using mozilla::dom::bluetooth::BluetoothGattAttrPerm @@ -20,6 +22,8 @@ using mozilla::dom::bluetooth::BluetoothGattServiceId from "mozilla/dom/bluetooth/BluetoothCommon.h"; using mozilla::dom::bluetooth::BluetoothGattWriteType from "mozilla/dom/bluetooth/BluetoothCommon.h"; +using mozilla::dom::bluetooth::BluetoothRemoteName + from "mozilla/dom/bluetooth/BluetoothCommon.h"; using mozilla::dom::bluetooth::BluetoothSspVariant from "mozilla/dom/bluetooth/BluetoothCommon.h"; using mozilla::dom::bluetooth::BluetoothStatus @@ -52,7 +56,10 @@ union BluetoothValue BluetoothGattServiceId; BluetoothGattServiceId[]; BluetoothGattCharAttribute[]; + BluetoothAddress; + BluetoothAddress[]; BluetoothAttributeHandle; + BluetoothRemoteName; BluetoothUuid; }; diff --git a/dom/browser-element/BrowserElementAudioChannel.cpp b/dom/browser-element/BrowserElementAudioChannel.cpp index 08cfa0e19e..c4a2d9715c 100644 --- a/dom/browser-element/BrowserElementAudioChannel.cpp +++ b/dom/browser-element/BrowserElementAudioChannel.cpp @@ -4,6 +4,7 @@ #include "BrowserElementAudioChannel.h" +#include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/dom/BrowserElementAudioChannelBinding.h" #include "mozilla/dom/DOMRequest.h" @@ -13,6 +14,7 @@ #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/ToJSValue.h" #include "AudioChannelService.h" +#include "nsIAppsService.h" #include "nsIBrowserElementAPI.h" #include "nsIDocShell.h" #include "nsIDOMDocument.h" @@ -576,25 +578,14 @@ BrowserElementAudioChannel::Observe(nsISupports* aSubject, const char* aTopic, } nsCOMPtr wrapper = do_QueryInterface(aSubject); - // This can be a nested iframe. if (!wrapper) { - nsCOMPtr iTabParent = do_QueryInterface(aSubject); - if (!iTabParent) { - return NS_ERROR_FAILURE; + bool isNested = false; + nsresult rv = IsFromNestedFrame(aSubject, isNested); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } - RefPtr tabParent = TabParent::GetFrom(iTabParent); - if (!tabParent) { - return NS_ERROR_FAILURE; - } - - Element* element = tabParent->GetOwnerElement(); - if (!element) { - return NS_ERROR_FAILURE; - } - - nsCOMPtr window = element->OwnerDoc()->GetWindow(); - if (window == mFrameWindow) { + if (isNested) { ProcessStateChanged(aData); } @@ -627,5 +618,78 @@ BrowserElementAudioChannel::ProcessStateChanged(const char16_t* aData) DispatchTrustedEvent(NS_LITERAL_STRING("activestatechanged")); } +bool +BrowserElementAudioChannel::IsSystemAppWindow(nsPIDOMWindow* aWindow) const +{ + nsCOMPtr doc = aWindow->GetExtantDoc(); + if (!doc) { + return false; + } + + uint32_t appId; + nsCOMPtr principal = doc->NodePrincipal(); + nsresult rv = principal->GetAppId(&appId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + if (appId == nsIScriptSecurityManager::NO_APP_ID || + appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { + return false; + } + + nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); + if (NS_WARN_IF(!appsService)) { + return false; + } + + nsAdoptingString systemAppManifest = + mozilla::Preferences::GetString("b2g.system_manifest_url"); + if (!systemAppManifest) { + return false; + } + + uint32_t systemAppId; + appsService->GetAppLocalIdByManifestURL(systemAppManifest, &systemAppId); + + if (systemAppId == appId) { + return true; + } + + return false; +} + +nsresult +BrowserElementAudioChannel::IsFromNestedFrame(nsISupports* aSubject, + bool& aIsNested) const +{ + aIsNested = false; + nsCOMPtr iTabParent = do_QueryInterface(aSubject); + if (!iTabParent) { + return NS_ERROR_FAILURE; + } + + RefPtr tabParent = TabParent::GetFrom(iTabParent); + if (!tabParent) { + return NS_ERROR_FAILURE; + } + + Element* element = tabParent->GetOwnerElement(); + if (!element) { + return NS_ERROR_FAILURE; + } + + // Since the normal OOP processes are opened out from b2g process, the owner + // of their tabParent are the same - system app window. Therefore, in order + // to find the case of nested MozFrame, we need to exclude this situation. + nsCOMPtr window = element->OwnerDoc()->GetWindow(); + if (window == mFrameWindow && !IsSystemAppWindow(window)) { + aIsNested = true; + return NS_OK; + } + + return NS_OK; +} + } // dom namespace } // mozilla namespace diff --git a/dom/browser-element/BrowserElementAudioChannel.h b/dom/browser-element/BrowserElementAudioChannel.h index 69ff3fa6ae..f9c751fe5a 100644 --- a/dom/browser-element/BrowserElementAudioChannel.h +++ b/dom/browser-element/BrowserElementAudioChannel.h @@ -70,6 +70,13 @@ private: AudioChannel aAudioChannel, const nsAString& aManifestURL); + bool IsSystemAppWindow(nsPIDOMWindow* aWindow) const; + + // This method is used to check whether we're in the nested-mozbrower-frame + // situation, see bug1214148. + nsresult IsFromNestedFrame(nsISupports* aSubject, + bool& aIsNested) const; + ~BrowserElementAudioChannel(); nsresult Initialize(); diff --git a/dom/gamepad/Gamepad.cpp b/dom/gamepad/Gamepad.cpp index d23859b829..81d3feee7e 100644 --- a/dom/gamepad/Gamepad.cpp +++ b/dom/gamepad/Gamepad.cpp @@ -31,7 +31,7 @@ Gamepad::UpdateTimestamp() if(newWindow) { nsPerformance* perf = newWindow->GetPerformance(); if (perf) { - mTimestamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now()); + mTimestamp = perf->Now(); } } } diff --git a/dom/geolocation/nsGeolocation.cpp b/dom/geolocation/nsGeolocation.cpp index 781c2f9c10..bc7691f9e0 100644 --- a/dom/geolocation/nsGeolocation.cpp +++ b/dom/geolocation/nsGeolocation.cpp @@ -20,15 +20,20 @@ #include "nsContentUtils.h" #include "nsContentPermissionHelper.h" #include "nsIDocument.h" +#include "nsIDOMEvent.h" #include "nsIObserverService.h" #include "nsPIDOMWindow.h" #include "nsThreadUtils.h" +#include "mozilla/HalWakeLock.h" +#include "mozilla/Hal.h" #include "mozilla/Services.h" #include "mozilla/unused.h" #include "mozilla/Preferences.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Event.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/SettingChangeNotificationBinding.h" +#include "mozilla/dom/WakeLock.h" #include "nsJSUtils.h" #include "prdtoa.h" @@ -65,6 +70,7 @@ class nsIPrincipal; using mozilla::Unused; // using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::hal; class nsGeolocationRequest final : public nsIContentPermissionRequest @@ -83,6 +89,7 @@ class nsGeolocationRequest final const GeoPositionCallback& aCallback, const GeoPositionErrorCallback& aErrorCallback, PositionOptions* aOptions, + uint8_t aProtocolType, bool aWatchPositionRequest = false, int32_t aWatchId = 0); void Shutdown(); @@ -113,6 +120,7 @@ class nsGeolocationRequest final int32_t mWatchId; bool mShutdown; nsCOMPtr mRequester; + uint8_t mProtocolType; }; static PositionOptions* @@ -347,6 +355,7 @@ nsGeolocationRequest::nsGeolocationRequest(Geolocation* aLocator, const GeoPositionCallback& aCallback, const GeoPositionErrorCallback& aErrorCallback, PositionOptions* aOptions, + uint8_t aProtocolType, bool aWatchPositionRequest, int32_t aWatchId) : mIsWatchPositionRequest(aWatchPositionRequest), @@ -355,7 +364,8 @@ nsGeolocationRequest::nsGeolocationRequest(Geolocation* aLocator, mOptions(aOptions), mLocator(aLocator), mWatchId(aWatchId), - mShutdown(false) + mShutdown(false), + mProtocolType(aProtocolType) { nsCOMPtr win = do_QueryReferent(mLocator->GetOwner()); if (win) { @@ -446,6 +456,13 @@ nsGeolocationRequest::GetElement(nsIDOMElement * *aRequestingElement) NS_IMETHODIMP nsGeolocationRequest::Cancel() { + if (mRequester) { + // Record the number of denied requests for regular web content. + // This method is only called when the user explicitly denies the request, + // and is not called when the page is simply unloaded, or similar. + Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED, mProtocolType); + } + if (mLocator->ClearPendingRequest(this)) { return NS_OK; } @@ -459,19 +476,16 @@ nsGeolocationRequest::Allow(JS::HandleValue aChoices) { MOZ_ASSERT(aChoices.isUndefined()); + if (mRequester) { + // Record the number of granted requests for regular web content. + Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED, mProtocolType + 10); + } + if (mLocator->ClearPendingRequest(this)) { return NS_OK; } - // Kick off the geo device, if it isn't already running RefPtr gs = nsGeolocationService::GetGeolocationService(); - nsresult rv = gs->StartDevice(GetPrincipal()); - - if (NS_FAILED(rv)) { - // Location provider error - NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE); - return NS_OK; - } bool canUseCache = false; CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition(); @@ -495,6 +509,26 @@ nsGeolocationRequest::Allow(JS::HandleValue aChoices) // getCurrentPosition requests serviced by the cache // will now be owned by the RequestSendLocationEvent Update(lastPosition.position); + } else { + // if it is not a watch request and timeout is 0, + // invoke the errorCallback (if present) with TIMEOUT code + if (mOptions && mOptions->mTimeout == 0 && !mIsWatchPositionRequest) { + NotifyError(nsIDOMGeoPositionError::TIMEOUT); + return NS_OK; + } + + // Kick off the geo device, if it isn't already running + nsresult rv = gs->StartDevice(GetPrincipal()); + + if (NS_FAILED(rv)) { + // Location provider error + NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE); + return NS_OK; + } + } + + if (mLocator->ContainsRequest(this)) { + return NS_OK; } if (mIsWatchPositionRequest || !canUseCache) { @@ -1039,6 +1073,16 @@ nsGeolocationService::StartDevice(nsIPrincipal *aPrincipal) return NS_OK; } +void +nsGeolocationService::StopDisconnectTimer() +{ + if (mDisconnectTimer) { + mDisconnectTimer->Cancel(); + mDisconnectTimer = nullptr; + } +} + + void nsGeolocationService::SetDisconnectTimer() { @@ -1091,10 +1135,7 @@ nsGeolocationService::UpdateAccuracy(bool aForceHigh) void nsGeolocationService::StopDevice() { - if(mDisconnectTimer) { - mDisconnectTimer->Cancel(); - mDisconnectTimer = nullptr; - } + StopDisconnectTimer(); if (XRE_IsContentProcess()) { ContentChild* cpc = ContentChild::GetSingleton(); @@ -1171,7 +1212,8 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation, mPendingRequests) Geolocation::Geolocation() -: mLastWatchId(0) +: mProtocolType(ProtocolType::OTHER) +, mLastWatchId(0) { } @@ -1204,6 +1246,32 @@ Geolocation::Init(nsIDOMWindow* aContentDom) } mPrincipal = doc->NodePrincipal(); + + if (XRE_IsContentProcess()) { + doc->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + /* listener */ this, + /* use capture */ true, + /* wants untrusted */ false); + } + + nsCOMPtr uri; + nsresult rv = mPrincipal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isHttp; + rv = uri->SchemeIs("http", &isHttp); + NS_ENSURE_SUCCESS(rv, rv); + + bool isHttps; + rv = uri->SchemeIs("https", &isHttps); + NS_ENSURE_SUCCESS(rv, rv); + + // Store the protocol to send via telemetry later. + if (isHttp) { + mProtocolType = ProtocolType::HTTP; + } else if (isHttps) { + mProtocolType = ProtocolType::HTTPS; + } } // If no aContentDom was passed into us, we are being used @@ -1216,6 +1284,58 @@ Geolocation::Init(nsIDOMWindow* aContentDom) return NS_OK; } +bool +Geolocation::ContainsRequest(nsGeolocationRequest* aRequest) +{ + if (aRequest->IsWatch()) { + if (mWatchingCallbacks.Contains(aRequest)) { + return true; + } + } else { + if (mPendingCallbacks.Contains(aRequest)) { + return true; + } + } + return false; +} + + +NS_IMETHODIMP +Geolocation::HandleEvent(nsIDOMEvent* aEvent) +{ + + nsAutoString type; + aEvent->GetType(type); + if (!type.EqualsLiteral("visibilitychange")) { + return NS_OK; + } + + nsCOMPtr doc = do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget()); + MOZ_ASSERT(doc); + + if (doc->Hidden()) { + WakeLockInformation info; + GetWakeLockInfo(NS_LITERAL_STRING("gps"), &info); + + MOZ_ASSERT(XRE_IsContentProcess()); + ContentChild* cpc = ContentChild::GetSingleton(); + if (!info.lockingProcesses().Contains(cpc->GetID())) { + cpc->SendRemoveGeolocationListener(); + mService->StopDisconnectTimer(); + } + } else { + mService->SetDisconnectTimer(); + for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) { + mWatchingCallbacks[i]->Allow(JS::UndefinedHandleValue); + } + for (uint32_t i = 0, length = mPendingCallbacks.Length(); i < length; ++i) { + mPendingCallbacks[i]->Allow(JS::UndefinedHandleValue); + } + } + + return NS_OK; +} + void Geolocation::Shutdown() { @@ -1223,6 +1343,18 @@ Geolocation::Shutdown() mPendingCallbacks.Clear(); mWatchingCallbacks.Clear(); + if (XRE_IsContentProcess()) { + nsCOMPtr window = do_QueryReferent(mOwner); + if (window) { + nsCOMPtr doc = window->GetExtantDoc(); + if (doc) { + doc->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + this, + /* useCapture = */ true); + } + } + } + if (mService) { mService->RemoveLocator(this); mService->UpdateAccuracy(); @@ -1400,11 +1532,13 @@ Geolocation::GetCurrentPosition(GeoPositionCallback& callback, return NS_ERROR_NOT_AVAILABLE; } - RefPtr request = new nsGeolocationRequest(this, - callback, - errorCallback, - options, - false); + // Count the number of requests per protocol/scheme. + Telemetry::Accumulate(Telemetry::GEOLOCATION_GETCURRENTPOSITION_SECURE_ORIGIN, + static_cast(mProtocolType)); + + RefPtr request = + new nsGeolocationRequest(this, callback, errorCallback, options, + static_cast(mProtocolType), false); if (!sGeoEnabled) { nsCOMPtr ev = new RequestAllowEvent(false, request); @@ -1489,15 +1623,16 @@ Geolocation::WatchPosition(GeoPositionCallback& aCallback, return NS_ERROR_NOT_AVAILABLE; } + // Count the number of requests per protocol/scheme. + Telemetry::Accumulate(Telemetry::GEOLOCATION_WATCHPOSITION_SECURE_ORIGIN, + static_cast(mProtocolType)); + // The watch ID: *aRv = mLastWatchId++; - RefPtr request = new nsGeolocationRequest(this, - aCallback, - aErrorCallback, - aOptions, - true, - *aRv); + RefPtr request = + new nsGeolocationRequest(this, aCallback, aErrorCallback, aOptions, + static_cast(mProtocolType), true, *aRv); if (!sGeoEnabled) { GPSLOG("request allow event"); diff --git a/dom/geolocation/nsGeolocation.h b/dom/geolocation/nsGeolocation.h index f456f9b4dd..9ee874d89e 100644 --- a/dom/geolocation/nsGeolocation.h +++ b/dom/geolocation/nsGeolocation.h @@ -22,6 +22,7 @@ #include "nsCycleCollectionParticipant.h" #include "nsGeoPosition.h" +#include "nsIDOMEventListener.h" #include "nsIDOMGeoGeolocation.h" #include "nsIDOMGeoPosition.h" #include "nsIDOMGeoPositionError.h" @@ -91,6 +92,7 @@ public: // create, or reinitalize the callback timer void SetDisconnectTimer(); + void StopDisconnectTimer(); // Update the accuracy and notify the provider if changed void UpdateAccuracy(bool aForceHigh = false); @@ -128,7 +130,8 @@ namespace dom { */ class Geolocation final : public nsIDOMGeoGeolocation, public nsIGeolocationUpdate, - public nsWrapperCache + public nsWrapperCache, + public nsIDOMEventListener { public: @@ -138,6 +141,8 @@ public: NS_DECL_NSIGEOLOCATIONUPDATE NS_DECL_NSIDOMGEOGEOLOCATION + NS_DECL_NSIDOMEVENTLISTENER + Geolocation(); nsresult Init(nsIDOMWindow* contentDom=nullptr); @@ -154,6 +159,9 @@ public: // Register an allowed request void NotifyAllowedRequest(nsGeolocationRequest* aRequest); + // Check if callbacks arrays already contain this request + bool ContainsRequest(nsGeolocationRequest* aRequest); + // Remove request from all callbacks arrays void RemoveRequest(nsGeolocationRequest* request); @@ -210,6 +218,12 @@ private: // where the content was loaded from nsCOMPtr mPrincipal; + // the protocols we want to measure + enum class ProtocolType: uint8_t { OTHER, HTTP, HTTPS }; + + // the protocol used to load the content + ProtocolType mProtocolType; + // owning back pointer. RefPtr mService; diff --git a/dom/html/HTMLVideoElement.cpp b/dom/html/HTMLVideoElement.cpp index 5f5fab91fc..722fd7201f 100644 --- a/dom/html/HTMLVideoElement.cpp +++ b/dom/html/HTMLVideoElement.cpp @@ -238,7 +238,7 @@ HTMLVideoElement::GetVideoPlaybackQuality() if (window) { nsPerformance* perf = window->GetPerformance(); if (perf) { - creationTime = perf->GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now()); + creationTime = perf->Now(); } } diff --git a/dom/media/DOMMediaStream.cpp b/dom/media/DOMMediaStream.cpp index 68c63c09ba..60717e14fa 100644 --- a/dom/media/DOMMediaStream.cpp +++ b/dom/media/DOMMediaStream.cpp @@ -18,6 +18,7 @@ #include "mozilla/dom/VideoTrack.h" #include "mozilla/dom/VideoTrackList.h" #include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/MediaStreamError.h" #include "MediaStreamGraph.h" #include "AudioStreamTrack.h" #include "VideoStreamTrack.h" @@ -713,7 +714,16 @@ DOMMediaStream::ApplyConstraintsToTrack(TrackID aTrackID, const MediaTrackConstraints& aConstraints, ErrorResult &aRv) { - return nullptr; + nsCOMPtr go = do_QueryInterface(mWindow); + RefPtr promise = Promise::Create(go, aRv); + MOZ_RELEASE_ASSERT(!aRv.Failed()); + + promise->MaybeReject(new MediaStreamError( + static_cast(mWindow.get()), + NS_LITERAL_STRING("OverconstrainedError"), + NS_LITERAL_STRING(""), + NS_LITERAL_STRING(""))); + return promise.forget(); } bool @@ -1042,13 +1052,6 @@ DOMAudioNodeMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, DOMHwMediaStream::DOMHwMediaStream() { -#ifdef MOZ_WIDGET_GONK - mImageContainer = LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS); - mOverlayImage = mImageContainer->CreateOverlayImage(); - nsAutoTArray images; - images.AppendElement(ImageContainer::NonOwningImage(mOverlayImage)); - mImageContainer->SetCurrentImages(images); -#endif } DOMHwMediaStream::~DOMHwMediaStream() @@ -1056,7 +1059,7 @@ DOMHwMediaStream::~DOMHwMediaStream() } already_AddRefed -DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow) +DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow, OverlayImage* aImage) { RefPtr stream = new DOMHwMediaStream(); @@ -1064,25 +1067,35 @@ DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow) MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER, AudioChannel::Normal); stream->InitSourceStream(aWindow, graph); - stream->Init(stream->GetInputStream()); + stream->Init(stream->GetInputStream(), aImage); return stream.forget(); } void -DOMHwMediaStream::Init(MediaStream* stream) +DOMHwMediaStream::Init(MediaStream* stream, OverlayImage* aImage) { SourceMediaStream* srcStream = stream->AsSourceStream(); +#ifdef MOZ_WIDGET_GONK + if (aImage) { + mOverlayImage = aImage; + } else { + Data imageData; + imageData.mOverlayId = DEFAULT_IMAGE_ID; + imageData.mSize.width = DEFAULT_IMAGE_WIDTH; + imageData.mSize.height = DEFAULT_IMAGE_HEIGHT; + + mOverlayImage = new OverlayImage(); + mOverlayImage->SetData(imageData); + } +#endif + if (srcStream) { VideoSegment segment; #ifdef MOZ_WIDGET_GONK const StreamTime delta = STREAM_TIME_MAX; // Because MediaStreamGraph will run out frames in non-autoplay mode, // we must give it bigger frame length to cover this situation. - mImageData.mOverlayId = DEFAULT_IMAGE_ID; - mImageData.mSize.width = DEFAULT_IMAGE_WIDTH; - mImageData.mSize.height = DEFAULT_IMAGE_HEIGHT; - mOverlayImage->SetData(mImageData); RefPtr image = static_cast(mOverlayImage.get()); mozilla::gfx::IntSize size = image->GetSize(); @@ -1110,11 +1123,17 @@ void DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height) { #ifdef MOZ_WIDGET_GONK - OverlayImage::Data imgData; - - imgData.mOverlayId = mOverlayImage->GetOverlayId(); - imgData.mSize = IntSize(width, height); - mOverlayImage->SetData(imgData); + if (mOverlayImage->GetSidebandStream().IsValid()) { + OverlayImage::SidebandStreamData imgData; + imgData.mStream = mOverlayImage->GetSidebandStream(); + imgData.mSize = IntSize(width, height); + mOverlayImage->SetData(imgData); + } else { + OverlayImage::Data imgData; + imgData.mOverlayId = mOverlayImage->GetOverlayId(); + imgData.mSize = IntSize(width, height); + mOverlayImage->SetData(imgData); + } #endif SourceMediaStream* srcStream = GetInputStream()->AsSourceStream(); @@ -1142,6 +1161,43 @@ DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height) srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment); #endif } + +void +DOMHwMediaStream::SetOverlayImage(OverlayImage* aImage) +{ + if (!aImage) { + return; + } +#ifdef MOZ_WIDGET_GONK + mOverlayImage = aImage; +#endif + + SourceMediaStream* srcStream = GetInputStream()->AsSourceStream(); + StreamBuffer::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY); + + if (!track || !track->GetSegment()) { + return; + } + +#ifdef MOZ_WIDGET_GONK + // Clear the old segment. + // Changing the existing content of segment is a Very BAD thing, and this way will + // confuse consumers of MediaStreams. + // It is only acceptable for DOMHwMediaStream + // because DOMHwMediaStream doesn't have consumers of TV streams currently. + track->GetSegment()->Clear(); + + // Change the image size. + const StreamTime delta = STREAM_TIME_MAX; + RefPtr image = static_cast(mOverlayImage.get()); + mozilla::gfx::IntSize size = image->GetSize(); + VideoSegment segment; + + segment.AppendFrame(image.forget(), delta, size); + srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment); +#endif +} + void DOMHwMediaStream::SetOverlayId(int32_t aOverlayId) { diff --git a/dom/media/DOMMediaStream.h b/dom/media/DOMMediaStream.h index 0837a3b312..07256c13d8 100644 --- a/dom/media/DOMMediaStream.h +++ b/dom/media/DOMMediaStream.h @@ -698,34 +698,32 @@ private: class DOMHwMediaStream : public DOMLocalMediaStream { typedef mozilla::gfx::IntSize IntSize; - typedef layers::ImageContainer ImageContainer; -#ifdef MOZ_WIDGET_GONK typedef layers::OverlayImage OverlayImage; +#ifdef MOZ_WIDGET_GONK typedef layers::OverlayImage::Data Data; #endif public: DOMHwMediaStream(); - static already_AddRefed CreateHwStream(nsIDOMWindow* aWindow); + static already_AddRefed CreateHwStream(nsIDOMWindow* aWindow, OverlayImage* aImage = nullptr); virtual DOMHwMediaStream* AsDOMHwMediaStream() override { return this; } int32_t RequestOverlayId(); void SetOverlayId(int32_t aOverlayId); void SetImageSize(uint32_t width, uint32_t height); + void SetOverlayImage(OverlayImage* aImage); protected: ~DOMHwMediaStream(); private: - void Init(MediaStream* aStream); + void Init(MediaStream* aStream, OverlayImage* aImage); #ifdef MOZ_WIDGET_GONK - RefPtr mImageContainer; const int DEFAULT_IMAGE_ID = 0x01; const int DEFAULT_IMAGE_WIDTH = 400; const int DEFAULT_IMAGE_HEIGHT = 300; RefPtr mOverlayImage; - Data mImageData; #endif }; diff --git a/dom/media/webspeech/synth/moz.build b/dom/media/webspeech/synth/moz.build index cc629267c8..e420172c92 100644 --- a/dom/media/webspeech/synth/moz.build +++ b/dom/media/webspeech/synth/moz.build @@ -42,8 +42,6 @@ if CONFIG['MOZ_WEBSPEECH']: 'test/nsFakeSynthServices.cpp' ] - DIRS = [] - if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': DIRS += ['windows'] diff --git a/dom/tests/browser/browser_webapps_permissions.js b/dom/tests/browser/browser_webapps_permissions.js index 0b7f0dce2b..8be841c742 100644 --- a/dom/tests/browser/browser_webapps_permissions.js +++ b/dom/tests/browser/browser_webapps_permissions.js @@ -15,7 +15,7 @@ function log() } } -let scope = {}; +var scope = {}; Cu.import("resource://gre/modules/PermissionSettings.jsm", scope); var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"] diff --git a/dom/tests/mochitest/gamepad/test_gamepad.html b/dom/tests/mochitest/gamepad/test_gamepad.html index 04c071e600..7a206338d1 100644 --- a/dom/tests/mochitest/gamepad/test_gamepad.html +++ b/dom/tests/mochitest/gamepad/test_gamepad.html @@ -19,7 +19,8 @@ var index = GamepadService.addGamepad("test gamepad", // id 2);// axes GamepadService.newButtonEvent(index, 0, true); function connecthandler(e) { - ok(e.gamepad.timestamp <= performance.now()); + ok(e.gamepad.timestamp <= performance.now(), + "gamepad.timestamp should less than or equal to performance.now()"); is(e.gamepad.index, 0, "correct gamepad index"); is(e.gamepad.id, "test gamepad", "correct gamepad name"); is(e.gamepad.mapping, "standard", "standard mapping"); diff --git a/dom/tests/mochitest/geolocation/mochitest.ini b/dom/tests/mochitest/geolocation/mochitest.ini index ba563f82e7..d31052db0c 100644 --- a/dom/tests/mochitest/geolocation/mochitest.ini +++ b/dom/tests/mochitest/geolocation/mochitest.ini @@ -44,9 +44,10 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #mozSettings is unde skip-if = buildapp == 'b2g' [test_shutdown.html] skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT +[test_timeoutCurrent.html] [test_timerRestartWatch.html] skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT [test_windowClose.html] skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT [test_worseAccuracyDoesNotBlockCallback.html] -skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT \ No newline at end of file +skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT diff --git a/dom/tests/mochitest/geolocation/test_timeoutCurrent.html b/dom/tests/mochitest/geolocation/test_timeoutCurrent.html new file mode 100644 index 0000000000..af6cab328f --- /dev/null +++ b/dom/tests/mochitest/geolocation/test_timeoutCurrent.html @@ -0,0 +1,56 @@ + + + + + Test for timeout option + + + + + + +Mozilla Bug 858827 +

+ +
+
+
+ + + diff --git a/dom/webidl/BluetoothAdapter.webidl b/dom/webidl/BluetoothAdapter.webidl index 34ccac3746..0305e660e9 100644 --- a/dom/webidl/BluetoothAdapter.webidl +++ b/dom/webidl/BluetoothAdapter.webidl @@ -128,10 +128,17 @@ interface BluetoothAdapter : EventTarget { sequence getPairedDevices(); - [NewObject] + /** + * [B2G only GATT client API] + * |startLeScan| and |stopLeScan| methods are exposed only if + * "dom.bluetooth.webbluetooth.enabled" preference is false. + */ + [NewObject, + Func="mozilla::dom::bluetooth::BluetoothManager::B2GGattClientEnabled"] Promise startLeScan(sequence serviceUuids); - [NewObject] + [NewObject, + Func="mozilla::dom::bluetooth::BluetoothManager::B2GGattClientEnabled"] Promise stopLeScan(BluetoothDiscoveryHandle discoveryHandle); [NewObject, Throws, AvailableIn=CertifiedApps] diff --git a/dom/webidl/BluetoothDevice.webidl b/dom/webidl/BluetoothDevice.webidl index cdc8b4e05f..06646784fe 100644 --- a/dom/webidl/BluetoothDevice.webidl +++ b/dom/webidl/BluetoothDevice.webidl @@ -16,7 +16,12 @@ interface BluetoothDevice : EventTarget /** * Retrieve the BluetoothGatt interface to interact with remote BLE devices. * This attribute is null if the device type is not dual or le. + * + * [B2G only GATT client API] + * gatt attribute is exposed only if "dom.bluetooth.webbluetooth.enabled" + * preference is false. */ + [Func="mozilla::dom::bluetooth::BluetoothManager::B2GGattClientEnabled"] readonly attribute BluetoothGatt? gatt; [Cached, Pure] diff --git a/dom/webidl/BluetoothGatt.webidl b/dom/webidl/BluetoothGatt.webidl index e9c48b7b4f..ddc9260d90 100644 --- a/dom/webidl/BluetoothGatt.webidl +++ b/dom/webidl/BluetoothGatt.webidl @@ -4,7 +4,13 @@ * 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/. */ -[CheckAnyPermissions="bluetooth"] +/** + * [B2G only GATT client API] + * BluetoothGatt interface is exposed only if + * "dom.bluetooth.webbluetooth.enabled" preference is false. + */ +[CheckAnyPermissions="bluetooth", + Func="mozilla::dom::bluetooth::BluetoothManager::B2GGattClientEnabled"] interface BluetoothGatt : EventTarget { [Cached, Pure] diff --git a/dom/webidl/BluetoothGattCharacteristicEvent.webidl b/dom/webidl/BluetoothGattCharacteristicEvent.webidl index ce6b87ce09..1b117bc447 100644 --- a/dom/webidl/BluetoothGattCharacteristicEvent.webidl +++ b/dom/webidl/BluetoothGattCharacteristicEvent.webidl @@ -4,7 +4,13 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ +/** + * [B2G only GATT client API] + * BluetoothGattCharacteristicEvent interface is exposed only if + * "dom.bluetooth.webbluetooth.enabled" preference is false. + */ [CheckAnyPermissions="bluetooth", + Func="mozilla::dom::bluetooth::BluetoothManager::B2GGattClientEnabled", Constructor(DOMString type, optional BluetoothGattCharacteristicEventInit eventInitDict)] interface BluetoothGattCharacteristicEvent : Event diff --git a/dom/webidl/BluetoothLeDeviceEvent.webidl b/dom/webidl/BluetoothLeDeviceEvent.webidl index fb70dd2591..a6e272bf6a 100644 --- a/dom/webidl/BluetoothLeDeviceEvent.webidl +++ b/dom/webidl/BluetoothLeDeviceEvent.webidl @@ -4,7 +4,13 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ +/** + * [B2G only GATT client API] + * BluetoothLeDeviceEvent interface is exposed only if + * "dom.bluetooth.webbluetooth.enabled" preference is false. + */ [CheckAnyPermissions="bluetooth", + Func="mozilla::dom::bluetooth::BluetoothManager::B2GGattClientEnabled", Constructor(DOMString type, optional BluetoothLeDeviceEventInit eventInitDict)] interface BluetoothLeDeviceEvent : Event { diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h index 4826ebab13..d20d0369da 100644 --- a/gfx/layers/ImageContainer.h +++ b/gfx/layers/ImageContainer.h @@ -857,12 +857,14 @@ public: { mOverlayId = aData.mOverlayId; mSize = aData.mSize; + mSidebandStream = GonkNativeHandle(); } void SetData(const SidebandStreamData& aData) { mSidebandStream = aData.mStream; mSize = aData.mSize; + mOverlayId = INVALID_OVERLAY; } already_AddRefed GetAsSourceSurface() { return nullptr; } ; diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp index 67bbf344cd..edb88de19b 100644 --- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -715,10 +715,18 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg) } } + bool wakeUpSyncSend = AwaitingSyncReply() && !ShouldDeferMessage(aMsg); + bool shouldWakeUp = AwaitingInterruptReply() || - (AwaitingSyncReply() && !ShouldDeferMessage(aMsg)) || + wakeUpSyncSend || AwaitingIncomingMessage(); + // Although we usually don't need to post an OnMaybeDequeueOne task if + // shouldWakeUp is true, it's easier to post anyway than to have to + // guarantee that every Send call processes everything it's supposed to + // before returning. + bool shouldPostTask = !shouldWakeUp || wakeUpSyncSend; + IPC_LOG("Receive on link thread; seqno=%d, xid=%d, shouldWakeUp=%d", aMsg.seqno(), aMsg.transaction_id(), shouldWakeUp); @@ -748,10 +756,9 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg) if (shouldWakeUp) { NotifyWorkerThread(); - } else { - // Worker thread is either not blocked on a reply, or this is an - // incoming Interrupt that raced with outgoing sync, and needs to be - // deferred to a later event-loop iteration. + } + + if (shouldPostTask) { if (!compress) { // If we compressed away the previous message, we'll re-use // its pending task. @@ -761,9 +768,9 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg) } void -MessageChannel::ProcessPendingRequests(int transaction, int prio) +MessageChannel::ProcessPendingRequests(int seqno, int transaction) { - IPC_LOG("ProcessPendingRequests"); + IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d", seqno, transaction); // Loop until there aren't any more priority messages to process. for (;;) { @@ -802,19 +809,31 @@ MessageChannel::ProcessPendingRequests(int transaction, int prio) // operating with weird state (as if no Send is in progress). That could // cause even normal priority sync messages to be processed (but not // normal priority async messages), which would break message ordering. - if (WasTransactionCanceled(transaction, prio)) { + if (WasTransactionCanceled(transaction)) { return; } } } bool -MessageChannel::WasTransactionCanceled(int transaction, int prio) +MessageChannel::WasTransactionCanceled(int transaction) { - if (transaction == mCurrentTransaction) { - return false; + if (transaction != mCurrentTransaction) { + // Imagine this scenario: + // 1. Child sends high prio sync message H1. + // 2. Parent sends reply to H1. + // 3. Parent sends high prio sync message H2. + // 4. Child's link thread receives H1 reply and H2 before worker wakes up. + // 5. Child dispatches H2 while still waiting for H1 reply. + // 6. Child cancels H2. + // + // In this case H1 will also be considered cancelled. However, its + // reply is still sitting in mRecvd, which can trip up later Sends. So + // we null it out here. + mRecvd = nullptr; + return true; } - return true; + return false; } bool @@ -916,24 +935,23 @@ MessageChannel::Send(Message* aMsg, Message* aReply) int32_t transaction = mCurrentTransaction; msg->set_transaction_id(transaction); - IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction); - - ProcessPendingRequests(transaction, prio); - if (WasTransactionCanceled(transaction, prio)) { - IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction); - return false; - } + IPC_LOG("Send seqno=%d, xid=%d, pending=%d", seqno, transaction, prios); bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg); mLink->SendMessage(msg.forget()); while (true) { - ProcessPendingRequests(transaction, prio); - if (WasTransactionCanceled(transaction, prio)) { + ProcessPendingRequests(seqno, transaction); + if (WasTransactionCanceled(transaction)) { IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction); mLastSendError = SyncSendError::CancelledAfterSend; return false; } + if (!Connected()) { + ReportConnectionError("MessageChannel::Send"); + mLastSendError = SyncSendError::DisconnectedDuringSend; + return false; + } // See if we've received a reply. if (mRecvdErrors) { @@ -959,7 +977,7 @@ MessageChannel::Send(Message* aMsg, Message* aReply) return false; } - if (WasTransactionCanceled(transaction, prio)) { + if (WasTransactionCanceled(transaction)) { IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction); mLastSendError = SyncSendError::CancelledAfterSend; return false; diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h index e9d1d72b40..3a03acd4ae 100644 --- a/ipc/glue/MessageChannel.h +++ b/ipc/glue/MessageChannel.h @@ -263,7 +263,7 @@ class MessageChannel : HasResultCodes bool InterruptEventOccurred(); bool HasPendingEvents(); - void ProcessPendingRequests(int transaction, int prio); + void ProcessPendingRequests(int seqno, int transaction); bool ProcessPendingRequest(const Message &aUrgent); void MaybeUndeferIncall(); @@ -439,7 +439,7 @@ class MessageChannel : HasResultCodes // Tell the IO thread to close the channel and wait for it to ACK. void SynchronouslyClose(); - bool WasTransactionCanceled(int transaction, int prio); + bool WasTransactionCanceled(int transaction); bool ShouldDeferMessage(const Message& aMsg); void OnMessageReceivedFromLink(const Message& aMsg); void OnChannelErrorFromLink(); diff --git a/testing/web-platform/meta/dom/nodes/MutationObserver-document.html.ini b/testing/web-platform/meta/dom/nodes/MutationObserver-document.html.ini deleted file mode 100644 index 1b5555582d..0000000000 --- a/testing/web-platform/meta/dom/nodes/MutationObserver-document.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[MutationObserver-document.html] - type: testharness - [parser insertion mutations] - expected: FAIL - - [parser script insertion mutation] - expected: FAIL - - [removal of parent during parsing] - expected: FAIL - diff --git a/testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/016.html.ini b/testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/016.html.ini deleted file mode 100644 index 1695214e2b..0000000000 --- a/testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/016.html.ini +++ /dev/null @@ -1,15 +0,0 @@ -[016.html] - type: testharness - expected: TIMEOUT - [Timeout on original window, scope] - expected: NOTRUN - - [Timeout on original window, this object] - expected: NOTRUN - - [Timeout on new window, scope] - expected: NOTRUN - - [Timeout on new window, this object] - expected: NOTRUN - diff --git a/testing/web-platform/meta/old-tests/submission/Opera/script_scheduling/094.html.ini b/testing/web-platform/meta/old-tests/submission/Opera/script_scheduling/094.html.ini deleted file mode 100644 index d0da2ca763..0000000000 --- a/testing/web-platform/meta/old-tests/submission/Opera/script_scheduling/094.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[094.html] - type: testharness - expected: TIMEOUT diff --git a/testing/web-platform/meta/old-tests/submission/Opera/script_scheduling/101.html.ini b/testing/web-platform/meta/old-tests/submission/Opera/script_scheduling/101.html.ini deleted file mode 100644 index a1ee8439dc..0000000000 --- a/testing/web-platform/meta/old-tests/submission/Opera/script_scheduling/101.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[101.html] - type: testharness - expected: TIMEOUT - [ scheduler: defer script after initial onload event] - expected: NOTRUN - diff --git a/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/094.html b/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/094.html index 5fe0a00582..44fd7b5590 100644 --- a/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/094.html +++ b/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/094.html @@ -8,14 +8,16 @@
FAILED (This TC requires JavaScript enabled)
+ diff --git a/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/101.html b/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/101.html index 23a9c36c6f..92fe6081ad 100644 --- a/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/101.html +++ b/testing/web-platform/tests/old-tests/submission/Opera/script_scheduling/101.html @@ -8,20 +8,23 @@
FAILED (This TC requires JavaScript enabled)
+ diff --git a/dom/animation/test/css-transitions/file_timeline-get-animations.html b/dom/animation/test/css-transitions/file_document-get-animations.html similarity index 83% rename from dom/animation/test/css-transitions/file_timeline-get-animations.html rename to dom/animation/test/css-transitions/file_document-get-animations.html index d4489a630f..d49b2dd5d5 100644 --- a/dom/animation/test/css-transitions/file_timeline-get-animations.html +++ b/dom/animation/test/css-transitions/file_document-get-animations.html @@ -6,7 +6,7 @@ 'use strict'; test(function(t) { - assert_equals(document.timeline.getAnimations().length, 0, + assert_equals(document.getAnimations().length, 0, 'getAnimations returns an empty sequence for a document' + ' with no animations'); }, 'getAnimations for non-animated content'); @@ -22,12 +22,12 @@ test(function(t) { div.style.transition = 'all 100s'; div.style.left = '100px'; div.style.top = '100px'; - assert_equals(document.timeline.getAnimations().length, 2, + assert_equals(document.getAnimations().length, 2, 'getAnimations returns two running CSS Transitions'); // Remove both div.style.transitionProperty = 'none'; - assert_equals(document.timeline.getAnimations().length, 0, + assert_equals(document.getAnimations().length, 0, 'getAnimations returns no running CSS Transitions'); }, 'getAnimations for CSS Transitions'); @@ -39,7 +39,7 @@ async_test(function(t) { var animations = div.getAnimations(); assert_equals(animations.length, 1, 'Got transition'); animations[0].finished.then(t.step_func(function() { - assert_equals(document.timeline.getAnimations().length, 0, + assert_equals(document.getAnimations().length, 0, 'No animations returned'); t.done(); })); diff --git a/dom/animation/test/css-transitions/test_timeline-get-animations.html b/dom/animation/test/css-transitions/test_document-get-animations.html similarity index 86% rename from dom/animation/test/css-transitions/test_timeline-get-animations.html rename to dom/animation/test/css-transitions/test_document-get-animations.html index 70c15494ec..dc964e62c8 100644 --- a/dom/animation/test/css-transitions/test_timeline-get-animations.html +++ b/dom/animation/test/css-transitions/test_document-get-animations.html @@ -9,7 +9,7 @@ setup({explicit_done: true}); SpecialPowers.pushPrefEnv( { "set": [["dom.animations-api.core.enabled", true]]}, function() { - window.open("file_timeline-get-animations.html"); + window.open("file_document-get-animations.html"); }); diff --git a/dom/animation/test/mochitest.ini b/dom/animation/test/mochitest.ini index a447ff855e..dd64f9321c 100644 --- a/dom/animation/test/mochitest.ini +++ b/dom/animation/test/mochitest.ini @@ -32,15 +32,15 @@ support-files = css-animations/file_animation-reverse.html support-files = css-animations/file_animation-starttime.html [css-animations/test_cssanimation-animationname.html] support-files = css-animations/file_cssanimation-animationname.html -[css-animations/test_keyframeeffect-getframes.html] -support-files = css-animations/file_keyframeeffect-getframes.html +[css-animations/test_document-get-animations.html] +support-files = css-animations/file_document-get-animations.html [css-animations/test_effect-target.html] support-files = css-animations/file_effect-target.html [css-animations/test_element-get-animations.html] skip-if = buildapp == 'mulet' support-files = css-animations/file_element-get-animations.html -[css-animations/test_timeline-get-animations.html] -support-files = css-animations/file_timeline-get-animations.html +[css-animations/test_keyframeeffect-getframes.html] +support-files = css-animations/file_keyframeeffect-getframes.html [css-transitions/test_animation-cancel.html] support-files = css-transitions/file_animation-cancel.html [css-transitions/test_animation-computed-timing.html] @@ -57,15 +57,15 @@ support-files = css-transitions/file_animation-ready.html support-files = css-transitions/file_animation-starttime.html [css-transitions/test_csstransition-transitionproperty.html] support-files = css-transitions/file_csstransition-transitionproperty.html -[css-transitions/test_keyframeeffect-getframes.html] -support-files = css-transitions/file_keyframeeffect-getframes.html +[css-transitions/test_document-get-animations.html] +support-files = css-transitions/file_document-get-animations.html [css-transitions/test_effect-target.html] support-files = css-transitions/file_effect-target.html [css-transitions/test_element-get-animations.html] skip-if = buildapp == 'mulet' support-files = css-transitions/file_element-get-animations.html -[css-transitions/test_timeline-get-animations.html] -support-files = css-transitions/file_timeline-get-animations.html +[css-transitions/test_keyframeeffect-getframes.html] +support-files = css-transitions/file_keyframeeffect-getframes.html [document-timeline/test_document-timeline.html] support-files = document-timeline/file_document-timeline.html [document-timeline/test_request_animation_frame.html] diff --git a/dom/animation/test/testcommon.js b/dom/animation/test/testcommon.js index 54add06692..af537a441f 100644 --- a/dom/animation/test/testcommon.js +++ b/dom/animation/test/testcommon.js @@ -17,18 +17,6 @@ function addDiv(t) { return div; } -/** - * Some tests cause animations to continue to exist even after their target - * element has been removed from the document tree. To ensure that these - * animations do not impact other tests we should cancel them when the test - * is complete. - */ -function cancelAllAnimationsOnEnd(t) { - t.add_cleanup(function() { - document.timeline.getAnimations().forEach(animation => animation.cancel()); - }); -} - /** * Promise wrapper for requestAnimationFrame. */ diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 0ac957ead0..8cca2d5294 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -3378,6 +3378,13 @@ Element::GetAnimations(nsTArray>& aAnimations) doc->FlushPendingNotifications(Flush_Style); } + GetAnimationsUnsorted(aAnimations); + aAnimations.Sort(AnimationPtrComparator>()); +} + +void +Element::GetAnimationsUnsorted(nsTArray>& aAnimations) +{ EffectSet* effects = EffectSet::GetEffectSet(this, nsCSSPseudoElements::ePseudo_NotPseudoElement); if (!effects) { @@ -3395,8 +3402,6 @@ Element::GetAnimations(nsTArray>& aAnimations) "effect set"); aAnimations.AppendElement(animation); } - - aAnimations.Sort(AnimationPtrComparator>()); } NS_IMETHODIMP diff --git a/dom/base/Element.h b/dom/base/Element.h index 084fc6076b..ca056b8da9 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -852,7 +852,9 @@ public: { } + // Note: GetAnimations will flush style while GetAnimationsUnsorted won't. void GetAnimations(nsTArray>& aAnimations); + void GetAnimationsUnsorted(nsTArray>& aAnimations); NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML); virtual void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError); diff --git a/dom/base/FileReader.cpp b/dom/base/FileReader.cpp index 629b14f8e7..e6c255ee49 100644 --- a/dom/base/FileReader.cpp +++ b/dom/base/FileReader.cpp @@ -98,9 +98,9 @@ FileReader::RootResultArrayBuffer() //FileReader constructors/initializers -FileReader::FileReader(nsPIDOMWindow* aWindow, +FileReader::FileReader(nsIGlobalObject* aGlobal, WorkerPrivate* aWorkerPrivate) - : DOMEventTargetHelper(aWindow) + : DOMEventTargetHelper(aGlobal) , mFileData(nullptr) , mDataLen(0) , mDataFormat(FILE_AS_BINARY) @@ -114,8 +114,8 @@ FileReader::FileReader(nsPIDOMWindow* aWindow, , mBusyCount(0) , mWorkerPrivate(aWorkerPrivate) { - MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerPrivate && !aWindow); - MOZ_ASSERT_IF(NS_IsMainThread(), !mWorkerPrivate); + MOZ_ASSERT(aGlobal); + MOZ_ASSERT(NS_IsMainThread() == !mWorkerPrivate); SetDOMStringToNull(mResult); } @@ -128,8 +128,7 @@ FileReader::~FileReader() /* static */ already_AddRefed FileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) { - // The owner can be null when this object is used by chrome code. - nsCOMPtr owner = do_QueryInterface(aGlobal.GetAsSupports()); + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); WorkerPrivate* workerPrivate = nullptr; if (!NS_IsMainThread()) { @@ -138,14 +137,7 @@ FileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) MOZ_ASSERT(workerPrivate); } - RefPtr fileReader = new FileReader(owner, workerPrivate); - - if (!owner && nsContentUtils::ThreadsafeIsCallerChrome()) { - // Instead of grabbing some random global from the context stack, - // let's use the default one (junk scope) for now. - // We should move away from this Init... - fileReader->BindToOwner(xpc::NativeGlobal(xpc::PrivilegedJunkScope())); - } + RefPtr fileReader = new FileReader(global, workerPrivate); return fileReader.forget(); } @@ -242,17 +234,7 @@ FileReader::DoOnLoadEnd(nsresult aStatus, switch (mDataFormat) { case FILE_AS_ARRAYBUFFER: { AutoJSAPI jsapi; - nsCOMPtr globalObject; - - if (NS_IsMainThread()) { - globalObject = do_QueryInterface(GetParentObject()); - } else { - MOZ_ASSERT(mWorkerPrivate); - MOZ_ASSERT(mBusyCount); - globalObject = mWorkerPrivate->GlobalScope(); - } - - if (!globalObject || !jsapi.Init(globalObject)) { + if (!jsapi.Init(GetParentObject())) { FreeFileData(); return NS_ERROR_FAILURE; } diff --git a/dom/base/FileReader.h b/dom/base/FileReader.h index 797dac67b8..af187dddb5 100644 --- a/dom/base/FileReader.h +++ b/dom/base/FileReader.h @@ -46,7 +46,7 @@ class FileReader final : public DOMEventTargetHelper, friend class FileReaderDecreaseBusyCounter; public: - FileReader(nsPIDOMWindow* aWindow, + FileReader(nsIGlobalObject* aGlobal, workers::WorkerPrivate* aWorkerPrivate); NS_DECL_ISUPPORTS_INHERITED diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index f7059ce97f..83da1c102c 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -107,6 +107,7 @@ #include "HTMLImageElement.h" #include "mozilla/css/ImageLoader.h" #include "mozilla/layers/APZCTreeManager.h" // for layers::ZoomToRectBehavior +#include "mozilla/dom/Promise.h" #ifdef XP_WIN #undef GetClassName @@ -2269,28 +2270,30 @@ nsDOMWindowUtils::GetLayerManagerRemote(bool* retval) } NS_IMETHODIMP -nsDOMWindowUtils::GetSupportsHardwareH264Decoding(nsAString& aRetval) +nsDOMWindowUtils::GetSupportsHardwareH264Decoding(JS::MutableHandle aPromise) { - MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + nsCOMPtr window = do_QueryReferent(mWindow); + NS_ENSURE_STATE(window); + nsCOMPtr parentObject = do_QueryInterface(window); + NS_ENSURE_STATE(parentObject); #ifdef MOZ_FMP4 nsCOMPtr widget = GetWidget(); - if (!widget) - return NS_ERROR_FAILURE; - + NS_ENSURE_STATE(widget); LayerManager *mgr = widget->GetLayerManager(); - if (!mgr) - return NS_ERROR_FAILURE; - - nsCString failureReason; - if (MP4Decoder::IsVideoAccelerated(mgr->GetCompositorBackendType(), failureReason)) { - aRetval.AssignLiteral("Yes"); - } else { - aRetval.AssignLiteral("No; "); - AppendUTF8toUTF16(failureReason, aRetval); - } + NS_ENSURE_STATE(mgr); + RefPtr promise = + MP4Decoder::IsVideoAccelerated(mgr->GetCompositorBackendType(), parentObject); + NS_ENSURE_STATE(promise); + aPromise.setObject(*promise->GetWrapper()); #else - aRetval.AssignLiteral("No; Compiled without MP4 support."); + ErrorResult rv; + RefPtr promise = Promise::Create(parentObject, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + promise.MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support.")); + aPromise.setObject(*promise->GetWrapper()); #endif return NS_OK; } diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 52777001c3..f8baf1c067 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -10,10 +10,12 @@ #include "nsDocument.h" #include "nsIDocumentInlines.h" +#include "mozilla/AnimationComparator.h" #include "mozilla/ArrayUtils.h" #include "mozilla/AutoRestore.h" #include "mozilla/BinarySearch.h" #include "mozilla/DebugOnly.h" +#include "mozilla/EffectSet.h" #include "mozilla/IntegerRange.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Likely.h" @@ -3156,6 +3158,32 @@ nsDocument::Timeline() return mDocumentTimeline; } +void +nsDocument::GetAnimations(nsTArray>& aAnimations) +{ + FlushPendingNotifications(Flush_Style); + + // Bug 1174575: Until we implement a suitable PseudoElement interface we + // don't have anything to return for the |target| attribute of + // KeyframeEffect(ReadOnly) objects that refer to pseudo-elements. + // Rather than return some half-baked version of these objects (e.g. + // we a null effect attribute) we simply don't provide access to animations + // whose effect refers to a pseudo-element until we can support them + // properly. + for (nsIContent* node = nsINode::GetFirstChild(); + node; + node = node->GetNextNode(this)) { + if (!node->IsElement()) { + continue; + } + + node->AsElement()->GetAnimationsUnsorted(aAnimations); + } + + // Sort animations by priority + aAnimations.Sort(AnimationPtrComparator>()); +} + /* Return true if the document is in the focused top-level window, and is an * ancestor of the focused DOMWindow. */ NS_IMETHODIMP diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 6339e07602..1ddf12757e 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -759,6 +759,8 @@ public: static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject); virtual mozilla::dom::DocumentTimeline* Timeline() override; + virtual void GetAnimations( + nsTArray>& aAnimations) override; virtual nsresult SetSubDocumentFor(Element* aContent, nsIDocument* aSubDoc) override; diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index f3fc673b09..db824a1ee7 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -104,6 +104,7 @@ class Rule; } // namespace css namespace dom { +class Animation; class AnonymousContent; class Attr; class BoxObject; @@ -2201,6 +2202,9 @@ public: virtual mozilla::dom::DocumentTimeline* Timeline() = 0; + virtual void GetAnimations( + nsTArray>& aAnimations) = 0; + nsresult ScheduleFrameRequestCallback(mozilla::dom::FrameRequestCallback& aCallback, int32_t *aHandle); void CancelFrameRequestCallback(int32_t aHandle); diff --git a/dom/bindings/Makefile.in b/dom/bindings/Makefile.in index 28e1d39f3e..dc114e3041 100644 --- a/dom/bindings/Makefile.in +++ b/dom/bindings/Makefile.in @@ -4,6 +4,8 @@ webidl_base := $(topsrcdir)/dom/webidl +ifdef COMPILE_ENVIRONMENT + # Generated by moz.build include webidlsrcs.mk @@ -55,6 +57,8 @@ codegen.pp: $(codegen_dependencies) compiletests: $(call SUBMAKE,libs,test) +endif + GARBAGE += \ codegen.pp \ codegen.json \ diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 05ec8eb429..2a5858cab1 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -49,7 +49,7 @@ interface nsIJSRAIIHelper; interface nsIContentPermissionRequest; interface nsIObserver; -[scriptable, uuid(ca6a458c-82e7-4979-886e-6d214eac6f0b)] +[scriptable, uuid(46b44e33-13c2-4eb3-bf80-76a4e0857ccc)] interface nsIDOMWindowUtils : nsISupports { /** @@ -1327,11 +1327,12 @@ interface nsIDOMWindowUtils : nsISupports { readonly attribute boolean layerManagerRemote; /** - * True if we can initialize a hardware-backed h264 decoder for a simple - * test video, does not mean that all h264 video decoding will be done + * Returns a Promise that will be resolved with a string once the capabilities + * of the h264 decoder have been determined. + * Success does not mean that all h264 video decoding will be done * in hardware. */ - readonly attribute AString supportsHardwareH264Decoding; + readonly attribute jsval supportsHardwareH264Decoding; /** * Record (and return) frame-intervals for frames which were presented diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp index 0b414afc29..c89c47dff3 100644 --- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -188,23 +188,46 @@ MP4Decoder::IsEnabled() return Preferences::GetBool("media.mp4.enabled"); } +// sTestH264ExtraData represents the content of the avcC atom found in +// an AVC1 h264 video. It contains the H264 SPS and PPS NAL. +// the structure of the avcC atom is as follow: +// write(0x1); // version, always 1 +// write(sps[0].data[1]); // profile +// write(sps[0].data[2]); // compatibility +// write(sps[0].data[3]); // level +// write(0xFC | 3); // reserved (6 bits), NULA length size - 1 (2 bits) +// write(0xE0 | 1); // reserved (3 bits), num of SPS (5 bits) +// write_word(sps[0].size); // 2 bytes for length of SPS +// for(size_t i=0 ; i < sps[0].size ; ++i) +// write(sps[0].data[i]); // data of SPS +// write(&b, pps.size()); // num of PPS +// for(size_t i=0 ; i < pps.size() ; ++i) { +// write_word(pps[i].size); // 2 bytes for length of PPS +// for(size_t j=0 ; j < pps[i].size ; ++j) +// write(pps[i].data[j]); // data of PPS +// } +// } +// here we have a h264 Baseline, 640x360 +// We use a 640x360 extradata, as some video framework (Apple VT) will never +// attempt to use hardware decoding for small videos. static const uint8_t sTestH264ExtraData[] = { - 0x01, 0x64, 0x00, 0x0a, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x64, - 0x00, 0x0a, 0xac, 0xd9, 0x44, 0x26, 0x84, 0x00, 0x00, 0x03, - 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xc8, 0x3c, 0x48, 0x96, - 0x58, 0x01, 0x00, 0x06, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0 + 0x01, 0x42, 0xc0, 0x1e, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x42, + 0xc0, 0x1e, 0xbb, 0x40, 0x50, 0x17, 0xfc, 0xb8, 0x08, 0x80, + 0x00, 0x00, 0x32, 0x00, 0x00, 0x0b, 0xb5, 0x07, 0x8b, 0x17, + 0x50, 0x01, 0x00, 0x04, 0x68, 0xce, 0x32, 0xc8 }; static already_AddRefed CreateTestH264Decoder(layers::LayersBackend aBackend, - VideoInfo& aConfig) + VideoInfo& aConfig, + FlushableTaskQueue* aTaskQueue) { aConfig.mMimeType = "video/avc"; aConfig.mId = 1; aConfig.mDuration = 40000; aConfig.mMediaTime = 0; - aConfig.mDisplay = nsIntSize(64, 64); - aConfig.mImage = nsIntRect(0, 0, 64, 64); + aConfig.mDisplay = nsIntSize(640, 360); + aConfig.mImage = nsIntRect(0, 0, 640, 360); aConfig.mExtraData = new MediaByteBuffer(); aConfig.mExtraData->AppendElements(sTestH264ExtraData, MOZ_ARRAY_LENGTH(sTestH264ExtraData)); @@ -213,23 +236,63 @@ CreateTestH264Decoder(layers::LayersBackend aBackend, RefPtr platform = new PDMFactory(); RefPtr decoder( - platform->CreateDecoder(aConfig, nullptr, nullptr, aBackend, nullptr)); + platform->CreateDecoder(aConfig, aTaskQueue, nullptr, aBackend, nullptr)); return decoder.forget(); } -/* static */ bool -MP4Decoder::IsVideoAccelerated(layers::LayersBackend aBackend, nsACString& aFailureReason) +/* static */ already_AddRefed +MP4Decoder::IsVideoAccelerated(layers::LayersBackend aBackend, nsIGlobalObject* aParent) { - VideoInfo config; - RefPtr decoder(CreateTestH264Decoder(aBackend, config)); - if (!decoder) { - aFailureReason.AssignLiteral("Failed to create H264 decoder"); - return false; + MOZ_ASSERT(NS_IsMainThread()); + + ErrorResult rv; + RefPtr promise; + promise = dom::Promise::Create(aParent, rv); + if (rv.Failed()) { + rv.SuppressException(); + return nullptr; } - bool result = decoder->IsHardwareAccelerated(aFailureReason); - decoder->Shutdown(); - return result; + + RefPtr taskQueue = + new FlushableTaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER)); + VideoInfo config; + RefPtr decoder(CreateTestH264Decoder(aBackend, config, taskQueue)); + if (!decoder) { + taskQueue->BeginShutdown(); + taskQueue->AwaitShutdownAndIdle(); + promise->MaybeResolve(NS_LITERAL_STRING("No; Failed to create H264 decoder")); + return promise.forget(); + } + + decoder->Init() + ->Then(AbstractThread::MainThread(), __func__, + [promise, decoder, taskQueue] (TrackInfo::TrackType aTrack) { + nsCString failureReason; + bool ok = decoder->IsHardwareAccelerated(failureReason); + nsAutoString result; + if (ok) { + result.AssignLiteral("Yes"); + } else { + result.AssignLiteral("No"); + if (failureReason.Length()) { + result.AppendLiteral("; "); + AppendUTF8toUTF16(failureReason, result); + } + } + decoder->Shutdown(); + taskQueue->BeginShutdown(); + taskQueue->AwaitShutdownAndIdle(); + promise->MaybeResolve(result); + }, + [promise, decoder, taskQueue] (MediaDataDecoder::DecoderFailureReason aResult) { + decoder->Shutdown(); + taskQueue->BeginShutdown(); + taskQueue->AwaitShutdownAndIdle(); + promise->MaybeResolve(NS_LITERAL_STRING("No; Failed to initialize H264 decoder")); + }); + + return promise.forget(); } void diff --git a/dom/media/fmp4/MP4Decoder.h b/dom/media/fmp4/MP4Decoder.h index 62578ed234..d99eb8e976 100644 --- a/dom/media/fmp4/MP4Decoder.h +++ b/dom/media/fmp4/MP4Decoder.h @@ -8,6 +8,7 @@ #include "MediaDecoder.h" #include "MediaFormatReader.h" +#include "mozilla/dom/Promise.h" namespace mozilla { @@ -38,7 +39,8 @@ public: // Returns true if the MP4 backend is preffed on. static bool IsEnabled(); - static bool IsVideoAccelerated(layers::LayersBackend aBackend, nsACString& aReason); + static already_AddRefed + IsVideoAccelerated(layers::LayersBackend aBackend, nsIGlobalObject* aParent); void GetMozDebugReaderData(nsAString& aString) override; diff --git a/dom/webidl/AnimationTimeline.webidl b/dom/webidl/AnimationTimeline.webidl index 97b4767f9d..fe3d0fb49a 100644 --- a/dom/webidl/AnimationTimeline.webidl +++ b/dom/webidl/AnimationTimeline.webidl @@ -14,5 +14,4 @@ interface AnimationTimeline { [BinaryName="currentTimeAsDouble"] readonly attribute double? currentTime; - sequence getAnimations (); }; diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl index 4a22e072ea..43a4f7fc26 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -310,6 +310,8 @@ partial interface Document { partial interface Document { [Func="nsDocument::IsWebAnimationsEnabled"] readonly attribute DocumentTimeline timeline; + [Func="nsDocument::IsWebAnimationsEnabled"] + sequence getAnimations(); }; // Mozilla extensions of various sorts diff --git a/dom/workers/test/xpcshell/data/worker_fileReader.js b/dom/workers/test/xpcshell/data/worker_fileReader.js new file mode 100644 index 0000000000..b27366d17b --- /dev/null +++ b/dom/workers/test/xpcshell/data/worker_fileReader.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + + +self.onmessage = function(msg) { + var fr = new FileReader(); + self.postMessage("OK"); +}; diff --git a/dom/workers/test/xpcshell/test_fileReader.js b/dom/workers/test/xpcshell/test_fileReader.js new file mode 100644 index 0000000000..0e283fbe01 --- /dev/null +++ b/dom/workers/test/xpcshell/test_fileReader.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Components.utils.import("resource://gre/modules/Promise.jsm"); + +// Worker must be loaded from a chrome:// uri, not a file:// +// uri, so we first need to load it. +var WORKER_SOURCE_URI = "chrome://workers/content/worker_fileReader.js"; +do_load_manifest("data/chrome.manifest"); + +function run_test() { + run_next_test(); +} + +function talk_with_worker(worker) { + let deferred = Promise.defer(); + worker.onmessage = function(event) { + let success = true; + if (event.data == "OK") { + deferred.resolve(); + } else { + success = false; + deferred.reject(event); + } + do_check_true(success); + worker.terminate(); + }; + worker.onerror = function(event) { + let error = new Error(event.message, event.filename, event.lineno); + worker.terminate(); + deferred.reject(error); + }; + worker.postMessage("START"); + return deferred.promise; +} + + +add_task(function test_chrome_worker() { + return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI)); +}); diff --git a/dom/workers/test/xpcshell/xpcshell.ini b/dom/workers/test/xpcshell/xpcshell.ini index f1437f9ac7..698fca76ee 100644 --- a/dom/workers/test/xpcshell/xpcshell.ini +++ b/dom/workers/test/xpcshell/xpcshell.ini @@ -4,6 +4,8 @@ tail = skip-if = toolkit == 'android' || toolkit == 'gonk' support-files = data/worker.js + data/worker_fileReader.js data/chrome.manifest [test_workers.js] +[test_fileReader.js] diff --git a/toolkit/components/downloads/ApplicationReputation.cpp b/toolkit/components/downloads/ApplicationReputation.cpp index 8f12ed594c..f03eb8a35c 100644 --- a/toolkit/components/downloads/ApplicationReputation.cpp +++ b/toolkit/components/downloads/ApplicationReputation.cpp @@ -593,7 +593,7 @@ PendingLookup::GenerateWhitelistStringsForChain( aChain.element(i).certificate().size(), getter_AddRefs(issuer)); NS_ENSURE_SUCCESS(rv, rv); - nsresult rv = GenerateWhitelistStringsForPair(signer, issuer); + rv = GenerateWhitelistStringsForPair(signer, issuer); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; @@ -703,24 +703,24 @@ PendingLookup::DoLookupInternal() nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); - nsCString spec; - rv = GetStrippedSpec(uri, spec); + nsCString sourceSpec; + rv = GetStrippedSpec(uri, sourceSpec); NS_ENSURE_SUCCESS(rv, rv); - mAnylistSpecs.AppendElement(spec); + mAnylistSpecs.AppendElement(sourceSpec); ClientDownloadRequest_Resource* resource = mRequest.add_resources(); - resource->set_url(spec.get()); + resource->set_url(sourceSpec.get()); resource->set_type(ClientDownloadRequest::DOWNLOAD_URL); nsCOMPtr referrer = nullptr; rv = mQuery->GetReferrerURI(getter_AddRefs(referrer)); if (referrer) { - nsCString spec; - rv = GetStrippedSpec(referrer, spec); + nsCString referrerSpec; + rv = GetStrippedSpec(referrer, referrerSpec); NS_ENSURE_SUCCESS(rv, rv); - mAnylistSpecs.AppendElement(spec); - resource->set_referrer(spec.get()); + mAnylistSpecs.AppendElement(referrerSpec); + resource->set_referrer(referrerSpec.get()); } nsCOMPtr redirects; rv = mQuery->GetRedirects(getter_AddRefs(redirects)); @@ -787,11 +787,11 @@ PendingLookup::ParseCertificates(nsIArray* aSigArray) NS_ENSURE_SUCCESS(rv, rv); while (hasMoreChains) { - nsCOMPtr supports; - rv = chains->GetNext(getter_AddRefs(supports)); + nsCOMPtr chainSupports; + rv = chains->GetNext(getter_AddRefs(chainSupports)); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr certList = do_QueryInterface(supports, &rv); + nsCOMPtr certList = do_QueryInterface(chainSupports, &rv); NS_ENSURE_SUCCESS(rv, rv); safe_browsing::ClientDownloadRequest_CertificateChain* certChain = @@ -804,11 +804,11 @@ PendingLookup::ParseCertificates(nsIArray* aSigArray) bool hasMoreCerts = false; rv = chainElt->HasMoreElements(&hasMoreCerts); while (hasMoreCerts) { - nsCOMPtr supports; - rv = chainElt->GetNext(getter_AddRefs(supports)); + nsCOMPtr certSupports; + rv = chainElt->GetNext(getter_AddRefs(certSupports)); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr cert = do_QueryInterface(supports, &rv); + nsCOMPtr cert = do_QueryInterface(certSupports, &rv); NS_ENSURE_SUCCESS(rv, rv); uint8_t* data = nullptr; diff --git a/toolkit/components/downloads/nsDownloadManager.cpp b/toolkit/components/downloads/nsDownloadManager.cpp index 605eaea99c..de62c84cf4 100644 --- a/toolkit/components/downloads/nsDownloadManager.cpp +++ b/toolkit/components/downloads/nsDownloadManager.cpp @@ -494,6 +494,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to the next upgrade + MOZ_FALLTHROUGH; case 2: // Add referrer column to the database { @@ -508,6 +509,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to the next upgrade + MOZ_FALLTHROUGH; case 3: // This version adds a column to the database (entityID) { @@ -522,6 +524,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to the next upgrade + MOZ_FALLTHROUGH; case 4: // This version adds a column to the database (tempPath) { @@ -536,6 +539,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to the next upgrade + MOZ_FALLTHROUGH; case 5: // This version adds two columns for tracking transfer progress { @@ -555,6 +559,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to the next upgrade + MOZ_FALLTHROUGH; case 6: // This version adds three columns to DB (MIME type related info) { @@ -579,6 +584,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to next upgrade + MOZ_FALLTHROUGH; case 7: // This version adds a column to remember to auto-resume downloads { @@ -593,6 +599,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to the next upgrade + MOZ_FALLTHROUGH; // Warning: schema versions >=8 must take into account that they can // be operating on schemas from unknown, future versions that have @@ -627,6 +634,7 @@ nsDownloadManager::InitFileDB() // Extra sanity checking for developers #ifndef DEBUG + MOZ_FALLTHROUGH; case DM_SCHEMA_VERSION: #endif break; @@ -643,6 +651,7 @@ nsDownloadManager::InitFileDB() NS_ENSURE_SUCCESS(rv, rv); } // Fallthrough to downgrade check + MOZ_FALLTHROUGH; // Downgrading // If columns have been added to the table, we can still use the ones we @@ -1265,17 +1274,17 @@ nsDownloadManager::GetDownloadFromDB(mozIStorageConnection* aDBConn, if (dl->mGUID.IsEmpty()) { rv = GenerateGUID(dl->mGUID); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr stmt; + nsCOMPtr updateStmt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_downloads SET guid = :guid " "WHERE id = :id"), - getter_AddRefs(stmt)); + getter_AddRefs(updateStmt)); NS_ENSURE_SUCCESS(rv, rv); - rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID); + rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID); NS_ENSURE_SUCCESS(rv, rv); - rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID); + rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID); NS_ENSURE_SUCCESS(rv, rv); - rv = stmt->Execute(); + rv = updateStmt->Execute(); NS_ENSURE_SUCCESS(rv, rv); } @@ -1480,9 +1489,6 @@ nsDownloadManager::GetUserDownloadsDirectory(nsIFile **aResult) case 0: // Desktop { nsCOMPtr downloadDir; - nsCOMPtr dirService = - do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); rv = dirService->Get(NS_OS_DESKTOP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(downloadDir)); @@ -1804,7 +1810,6 @@ nsDownloadManager::RetryDownload(const nsACString& aGUID) return RetryDownload(dl); } - NS_IMETHODIMP nsDownloadManager::RetryDownload(uint32_t aID) { From 3e34b8d21b72bdaa77d31b732f6eb750bb06c93c Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 8 Nov 2023 17:17:49 +0800 Subject: [PATCH 04/12] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1213818 - Align document.title for SVG documents with HTML spec; r=bz (fb60e8c048) - Bug 1234170 - WebSocket should check if the channel has been opened before send the 'close' notification to the WebSocketEventService, r=jduell (4bfd6f3f3f) - Bug 1245261 - Use an atomic to safely access gcTriggerBytes; r=jonco (f9c80d47e1) - Bug 1243001 part 1. Remove the dead WrappedWorkerRunnable class from Promise code. r=peterv (9f8c758723) - Bug 1243001 part 2. Make Promise an empty [NoInterfaceObject] interface when SPIDERMONKEY_PROMISE is defined. r=peterv (6be034ee59) - Bug 1243001 part 3. Turn off the IDL bits of PromiseDebugging when SPIDERMONKEY_PROMISE is defined. r=peterv (114241ddd6) - Bug 1243001 part 4. Switch to using MaybeResolve/MaybeReject instead of ResolveInternal/RejectInternal for PromiseWorkerProxy. r=peterv (ca8faf02f8) - Bug 1243114 - Convert PromiseCapability::mPromise to a rooted JSObject* instead of a rooted JS::Value. r=bz (e4c907e5c2) - Bug 1243001 part 5. Get rid of most of the dom::Promise methods when SPIDERMONKEY_PROMISE is defined, and reimplement the rest in terms of SpiderMonkey Promise. r=peterv (693a61e5a2) - Bug 1242054. Get rid of AbortablePromise, so we can move Promise into SpiderMonkey more easily. r=khuey (6a0200e625) - Bug 1243001 part 6. Implement Promise::AppendNativeHandler in the SPIDERMONKEY_PROMISE world. r=peterv (7c3a6390f9) - Bug 1243001 part 7. Stop wrappercaching dom::Promise when SPIDERMONKEY_PROMISE is defined. r=peterv (be1bd9b33f) - Bug 1243001 part 8. Tell SpiderMonkey to put its promise jobs into the CycleCollectedJSRuntime job queue. r=peterv (192e6a551c) - Bug 1156880 - Null check the prescontext in nsDOMWindowUtils::AdvanceTimeAndRefresh; r=mstange (11f1a39f22) - Bug 1191597 part 1 - Add head.js and dummy page for browser chrome test. r=smaug (5257870dd3) - Bug 1191597 part 2 - Convert fullscreen-esc-context-menu to a browser chrome test. r=smaug (e1c0fe84a4) --- dom/base/DOMRequest.cpp | 2 +- dom/base/WebSocket.cpp | 8 +- dom/base/nsDOMWindowUtils.cpp | 17 +- dom/base/nsDocument.cpp | 116 +++-- dom/base/nsDocument.h | 16 +- dom/bindings/BindingUtils.cpp | 28 ++ dom/bindings/BindingUtils.h | 16 +- dom/bindings/Bindings.conf | 8 +- dom/bindings/Codegen.py | 44 +- dom/bindings/Configuration.py | 7 + dom/bindings/ToJSValue.cpp | 13 + dom/bindings/ToJSValue.h | 10 + dom/html/test/browser.ini | 3 + .../browser_fullscreen-contextmenu-esc.js | 105 +++++ dom/html/test/dummy_page.html | 10 + .../file_fullscreen-esc-context-menu.html | 100 ----- dom/html/test/head.js | 54 +++ dom/html/test/mochitest.ini | 1 - dom/html/test/test_fullscreen-api.html | 1 - dom/promise/AbortablePromise.cpp | 118 ----- dom/promise/AbortablePromise.h | 67 --- dom/promise/Promise.cpp | 424 ++++++++++++++++-- dom/promise/Promise.h | 99 +++- dom/promise/PromiseCallback.cpp | 4 + dom/promise/PromiseCallback.h | 3 + dom/promise/PromiseDebugging.cpp | 16 + dom/promise/PromiseDebugging.h | 6 + dom/promise/PromiseNativeAbortCallback.h | 36 -- dom/promise/PromiseNativeHandler.cpp | 26 ++ dom/promise/PromiseNativeHandler.h | 6 + dom/promise/moz.build | 6 +- dom/promise/tests/mochitest.ini | 1 - dom/promise/tests/test_abortable_promise.html | 116 ----- .../mochitest/general/test_interfaces.html | 2 - dom/webidl/AbortablePromise.webidl | 19 - dom/webidl/DummyBinding.webidl | 1 + dom/webidl/Promise.webidl | 21 +- dom/webidl/PromiseDebugging.webidl | 3 + dom/webidl/moz.build | 5 +- js/src/gc/Zone.h | 2 +- js/src/vm/Debugger.cpp | 3 +- js/xpconnect/src/Sandbox.cpp | 7 +- js/xpconnect/src/XPCConvert.cpp | 30 ++ modules/libpref/init/all.js | 3 - .../document.title-09.html.ini | 11 - .../dom-tree-accessors/document.title-09.html | 97 ++++ xpcom/base/CycleCollectedJSRuntime.cpp | 44 ++ xpcom/base/CycleCollectedJSRuntime.h | 3 + 48 files changed, 1118 insertions(+), 620 deletions(-) create mode 100644 dom/html/test/browser_fullscreen-contextmenu-esc.js create mode 100644 dom/html/test/dummy_page.html delete mode 100644 dom/html/test/file_fullscreen-esc-context-menu.html create mode 100644 dom/html/test/head.js delete mode 100644 dom/promise/AbortablePromise.cpp delete mode 100644 dom/promise/AbortablePromise.h delete mode 100644 dom/promise/PromiseNativeAbortCallback.h create mode 100644 dom/promise/PromiseNativeHandler.cpp delete mode 100644 dom/promise/tests/test_abortable_promise.html delete mode 100644 dom/webidl/AbortablePromise.webidl delete mode 100644 testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-09.html.ini create mode 100644 testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-09.html diff --git a/dom/base/DOMRequest.cpp b/dom/base/DOMRequest.cpp index dfc4732a53..8f23c059e1 100644 --- a/dom/base/DOMRequest.cpp +++ b/dom/base/DOMRequest.cpp @@ -232,7 +232,7 @@ DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback, } // Just use the global of the Promise itself as the callee global. - JS::Rooted global(aCx, mPromise->GetWrapper()); + JS::Rooted global(aCx, mPromise->PromiseObj()); global = js::GetGlobalForObjectCrossCompartment(global); mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv); } diff --git a/dom/base/WebSocket.cpp b/dom/base/WebSocket.cpp index b548a90b45..3b5b4a4819 100644 --- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -1912,9 +1912,11 @@ WebSocket::CreateAndDispatchCloseEvent(bool aWasClean, MOZ_ASSERT(mImpl); AssertIsOnTargetThread(); - mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(), - mImpl->mInnerWindowID, - aWasClean, aCode, aReason); + if (mImpl->mChannel) { + mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(), + mImpl->mInnerWindowID, + aWasClean, aCode, aReason); + } nsresult rv = CheckInnerWindowCorrectness(); if (NS_FAILED(rv)) { diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 83da1c102c..28a066616c 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -2285,7 +2285,7 @@ nsDOMWindowUtils::GetSupportsHardwareH264Decoding(JS::MutableHandle a RefPtr promise = MP4Decoder::IsVideoAccelerated(mgr->GetCompositorBackendType(), parentObject); NS_ENSURE_STATE(promise); - aPromise.setObject(*promise->GetWrapper()); + aPromise.setObject(*promise->PromiseObj()); #else ErrorResult rv; RefPtr promise = Promise::Create(parentObject, rv); @@ -2293,7 +2293,7 @@ nsDOMWindowUtils::GetSupportsHardwareH264Decoding(JS::MutableHandle a return rv.StealNSResult(); } promise.MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support.")); - aPromise.setObject(*promise->GetWrapper()); + aPromise.setObject(*promise->PromiseObj()); #endif return NS_OK; } @@ -2416,12 +2416,15 @@ nsDOMWindowUtils::AdvanceTimeAndRefresh(int64_t aMilliseconds) } } - nsRefreshDriver* driver = GetPresContext()->RefreshDriver(); - driver->AdvanceTimeAndRefresh(aMilliseconds); + nsPresContext* presContext = GetPresContext(); + if (presContext) { + nsRefreshDriver* driver = presContext->RefreshDriver(); + driver->AdvanceTimeAndRefresh(aMilliseconds); - RefPtr transaction = GetLayerTransaction(); - if (transaction && transaction->IPCOpen()) { - transaction->SendSetTestSampleTime(driver->MostRecentRefresh()); + RefPtr transaction = GetLayerTransaction(); + if (transaction && transaction->IPCOpen()) { + transaction->SendSetTestSampleTime(driver->MostRecentRefresh()); + } } return NS_OK; diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index f8baf1c067..6b5f746ed6 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -7010,8 +7010,8 @@ nsIDocument::GetHtmlChildElement(nsIAtom* aTag) return nullptr; } -nsIContent* -nsDocument::GetTitleContent(uint32_t aNamespace) +Element* +nsDocument::GetTitleElement() { // mMayHaveTitleElement will have been set to true if any HTML or SVG // element has been bound to this document. So if it's false, @@ -7021,19 +7021,26 @@ nsDocument::GetTitleContent(uint32_t aNamespace) if (!mMayHaveTitleElement) return nullptr; + Element* root = GetRootElement(); + if (root && root->IsSVGElement(nsGkAtoms::svg)) { + // In SVG, the document's title must be a child + for (nsIContent* child = root->GetFirstChild(); + child; child = child->GetNextSibling()) { + if (child->IsSVGElement(nsGkAtoms::title)) { + return child->AsElement(); + } + } + return nullptr; + } + + // We check the HTML namespace even for non-HTML documents, except SVG. This + // matches the spec and the behavior of all tested browsers. RefPtr<nsContentList> list = - NS_GetContentList(this, aNamespace, NS_LITERAL_STRING("title")); + NS_GetContentList(this, kNameSpaceID_XHTML, NS_LITERAL_STRING("title")); - return list->Item(0, false); -} + nsIContent* first = list->Item(0, false); -void -nsDocument::GetTitleFromElement(uint32_t aNamespace, nsAString& aTitle) -{ - nsIContent* title = GetTitleContent(aNamespace); - if (!title) - return; - nsContentUtils::GetNodeTextContent(title, false, aTitle); + return first ? first->AsElement() : nullptr; } NS_IMETHODIMP @@ -7050,26 +7057,24 @@ nsDocument::GetTitle(nsString& aTitle) { aTitle.Truncate(); - nsIContent *rootElement = GetRootElement(); - if (!rootElement) + Element* rootElement = GetRootElement(); + if (!rootElement) { return; + } nsAutoString tmp; - switch (rootElement->GetNameSpaceID()) { #ifdef MOZ_XUL - case kNameSpaceID_XUL: - rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp); - break; + if (rootElement->IsXULElement()) { + rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp); + } else #endif - case kNameSpaceID_SVG: - if (rootElement->IsSVGElement(nsGkAtoms::svg)) { - GetTitleFromElement(kNameSpaceID_SVG, tmp); - break; - } // else fall through - default: - GetTitleFromElement(kNameSpaceID_XHTML, tmp); - break; + { + Element* title = GetTitleElement(); + if (!title) { + return; + } + nsContentUtils::GetNodeTextContent(title, false, tmp); } tmp.CompressWhitespace(); @@ -7079,41 +7084,56 @@ nsDocument::GetTitle(nsString& aTitle) NS_IMETHODIMP nsDocument::SetTitle(const nsAString& aTitle) { - Element *rootElement = GetRootElement(); - if (!rootElement) + Element* rootElement = GetRootElement(); + if (!rootElement) { return NS_OK; - - switch (rootElement->GetNameSpaceID()) { - case kNameSpaceID_SVG: - return NS_OK; // SVG doesn't support setting a title -#ifdef MOZ_XUL - case kNameSpaceID_XUL: - return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, - aTitle, true); -#endif } +#ifdef MOZ_XUL + if (rootElement->IsXULElement()) { + return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, + aTitle, true); + } +#endif + // Batch updates so that mutation events don't change "the title // element" under us mozAutoDocUpdate updateBatch(this, UPDATE_CONTENT_MODEL, true); - nsIContent* title = GetTitleContent(kNameSpaceID_XHTML); - if (!title) { - Element *head = GetHeadElement(); - if (!head) - return NS_OK; + nsCOMPtr<Element> title = GetTitleElement(); + if (rootElement->IsSVGElement(nsGkAtoms::svg)) { + if (!title) { + RefPtr<mozilla::dom::NodeInfo> titleInfo = + mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr, + kNameSpaceID_SVG, + nsIDOMNode::ELEMENT_NODE); + NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(), + NOT_FROM_PARSER); + if (!title) { + return NS_OK; + } + rootElement->InsertChildAt(title, 0, true); + } + } else if (rootElement->IsHTMLElement()) { + if (!title) { + Element* head = GetHeadElement(); + if (!head) { + return NS_OK; + } - { RefPtr<mozilla::dom::NodeInfo> titleInfo; titleInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr, - kNameSpaceID_XHTML, - nsIDOMNode::ELEMENT_NODE); + kNameSpaceID_XHTML, + nsIDOMNode::ELEMENT_NODE); title = NS_NewHTMLTitleElement(titleInfo.forget()); - if (!title) + if (!title) { return NS_OK; - } + } - head->AppendChildTo(title, true); + head->AppendChildTo(title, true); + } + } else { + return NS_OK; } return nsContentUtils::SetNodeTextContent(title, aTitle, false); diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 1ddf12757e..85b1caeb2f 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -1447,13 +1447,15 @@ protected: nsIContent* GetFirstBaseNodeWithHref(); nsresult SetFirstBaseNodeWithHref(nsIContent *node); - // Get the first <title> element with the given IsNodeOfType type, or - // return null if there isn't one - nsIContent* GetTitleContent(uint32_t aNodeType); - // Find the first "title" element in the given IsNodeOfType type and - // append the concatenation of its text node children to aTitle. Do - // nothing if there is no such element. - void GetTitleFromElement(uint32_t aNodeType, nsAString& aTitle); + /** + * Returns the title element of the document as defined by the HTML + * specification, or null if there isn't one. For documents whose root + * element is an <svg:svg>, this is the first <svg:title> element that's a + * child of the root. For other documents, it's the first HTML title element + * in the document. + */ + Element* GetTitleElement(); + public: // Get our title virtual void GetTitle(nsString& aTitle) override; diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 4b6eac00c6..ca7eba41f2 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2795,6 +2795,7 @@ ConvertExceptionToPromise(JSContext* cx, JSObject* promiseScope, JS::MutableHandle<JS::Value> rval) { +#ifndef SPIDERMONKEY_PROMISE GlobalObject global(cx, promiseScope); if (global.Failed()) { return false; @@ -2829,6 +2830,33 @@ ConvertExceptionToPromise(JSContext* cx, } return GetOrCreateDOMReflector(cx, promise, rval); +#else // SPIDERMONKEY_PROMISE + { + JSAutoCompartment ac(cx, promiseScope); + + JS::Rooted<JS::Value> exn(cx); + if (!JS_GetPendingException(cx, &exn)) { + // This is very important: if there is no pending exception here but we're + // ending up in this code, that means the callee threw an uncatchable + // exception. Just propagate that out as-is. + return false; + } + + JS_ClearPendingException(cx); + + JSObject* promise = JS::CallOriginalPromiseReject(cx, exn); + if (!promise) { + // We just give up. Put the exception back. + JS_SetPendingException(cx, exn); + return false; + } + + rval.setObject(*promise); + } + + // Now make sure we rewrap promise back into the compartment we want + return JS_WrapValue(cx, rval); +#endif // SPIDERMONKEY_PROMISE } /* static */ diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 128ef05800..0a04b1a377 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1131,7 +1131,8 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx, // Helper for smart pointers (nsRefPtr/nsCOMPtr). template <template <typename> class SmartPtr, typename T, - typename U=typename EnableIf<IsRefcounted<T>::value, T>::Type> + typename U=typename EnableIf<IsRefcounted<T>::value, T>::Type, + typename V=typename EnableIf<IsSmartPtr<SmartPtr<T>>::value, T>::Type> inline bool WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope, const SmartPtr<T>& value, @@ -1142,6 +1143,19 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope, givenProto); } +// Helper for object references (as opposed to pointers). +template <typename T, + typename U=typename EnableIf<!IsSmartPtr<T>::value, T>::Type> +inline bool +WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope, + T& value, + JS::MutableHandle<JS::Value> rval, + JS::Handle<JSObject*> givenProto = nullptr) +{ + return WrapNewBindingNonWrapperCachedObject(cx, scope, &value, rval, + givenProto); +} + // Only set allowNativeWrapper to false if you really know you need it, if in // doubt use true. Setting it to false disables security wrappers. bool diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 8e82ca7961..d55d5f0170 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -80,10 +80,6 @@ DOMInterfaces = { 'nativeType': 'mozilla::dom::Activity', }, -'MozAbortablePromise': { - 'nativeType': 'mozilla::dom::AbortablePromise', -}, - 'AbstractWorker': { 'concrete': False }, @@ -977,6 +973,10 @@ DOMInterfaces = { 'concrete': False, }, +'PromiseNativeHandler': { + 'wrapperCache': False, +}, + 'PropertyNodeList': { 'headerFile': 'HTMLPropertiesCollection.h', }, diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 91e446972b..0fb885253a 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2982,6 +2982,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): if self.descriptor.name == "Promise": speciesSetup = CGGeneric(fill( """ + #ifndef SPIDERMONKEY_PROMISE JS::Rooted<JSObject*> promiseConstructor(aCx, *interfaceCache); JS::Rooted<jsid> species(aCx, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species))); @@ -2989,6 +2990,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): JSPROP_SHARED, Promise::PromiseSpecies, nullptr)) { $*{failureCode} } + #endif // SPIDERMONKEY_PROMISE """, failureCode=failureCode)) else: @@ -5037,11 +5039,6 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, return JSToNativeConversionInfo(template, declType=declType, dealWithOptional=isOptional) - if descriptor.interface.identifier.name == "AbortablePromise": - raise TypeError("Need to figure out what argument conversion " - "should look like for AbortablePromise: %s" % - sourceDescription) - # This is an interface that we implement as a concrete class # or an XPCOM interface. @@ -5185,7 +5182,26 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if (promiseGlobal.Failed()) { $*{exceptionCode} } + + JS::Rooted<JS::Value> valueToResolve(cx, $${val}); + if (!JS_WrapValue(cx, &valueToResolve)) { + $*{exceptionCode} + } ErrorResult promiseRv; + #ifdef SPIDERMONKEY_PROMISE + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(promiseGlobal.GetAsSupports()); + if (!global) { + promiseRv.Throw(NS_ERROR_UNEXPECTED); + promiseRv.MaybeSetPendingException(cx); + $*{exceptionCode} + } + $${declName} = Promise::Resolve(global, cx, valueToResolve, + promiseRv); + if (promiseRv.MaybeSetPendingException(cx)) { + $*{exceptionCode} + } + #else JS::Handle<JSObject*> promiseCtor = PromiseBinding::GetConstructorObjectHandle(cx, globalObj); if (!promiseCtor) { @@ -5193,10 +5209,6 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, } JS::Rooted<JS::Value> resolveThisv(cx, JS::ObjectValue(*promiseCtor)); JS::Rooted<JS::Value> resolveResult(cx); - JS::Rooted<JS::Value> valueToResolve(cx, $${val}); - if (!JS_WrapValue(cx, &valueToResolve)) { - $*{exceptionCode} - } Promise::Resolve(promiseGlobal, resolveThisv, valueToResolve, &resolveResult, promiseRv); if (promiseRv.MaybeSetPendingException(cx)) { @@ -5208,6 +5220,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, promiseRv.MaybeSetPendingException(cx); $*{exceptionCode} } + #endif // SPIDERMONKEY_PROMISE } """, getPromiseGlobal=getPromiseGlobal, @@ -6328,7 +6341,13 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode, wrapMethod = "GetOrCreateDOMReflector" wrapArgs = "cx, %s, ${jsvalHandle}" % result else: - if not returnsNewObject: + # Hack: the "Promise" interface is OK to return from + # non-newobject things even when it's not wrappercached; that + # happens when using SpiderMonkey promises, and the WrapObject() + # method will just return the existing reflector, which is just + # not stored in a wrappercache. + if (not returnsNewObject and + descriptor.interface.identifier.name != "Promise"): raise MethodNotNewObjectError(descriptor.interface.identifier.name) wrapMethod = "WrapNewBindingNonWrapperCachedObject" wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result @@ -7149,8 +7168,7 @@ class CGPerSignatureCall(CGThing): # Hack for making Promise.prototype.then work well over Xrays. if (not static and - (descriptor.name == "Promise" or - descriptor.name == "MozAbortablePromise") and + descriptor.name == "Promise" and idlNode.isMethod() and idlNode.identifier.name == "then"): cgThings.append(CGGeneric(dedent( @@ -7162,7 +7180,7 @@ class CGPerSignatureCall(CGThing): needsUnwrap = False argsPost = [] if isConstructor: - if descriptor.name == "Promise" or descriptor.name == "MozAbortablePromise": + if descriptor.name == "Promise": # Hack for Promise for now: pass in our desired proto so the # implementation can create the reflector with the right proto. argsPost.append("desiredProto") diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py index ee1be46a50..22a3599360 100644 --- a/dom/bindings/Configuration.py +++ b/dom/bindings/Configuration.py @@ -532,6 +532,13 @@ class Descriptor(DescriptorProvider): self.wrapperCache = (not self.interface.isCallback() and not self.interface.isIteratorInterface() and desc.get('wrapperCache', True)) + # Nasty temporary hack for supporting both DOM and SpiderMonkey promises + # without too much pain + if self.interface.identifier.name == "Promise": + assert self.wrapperCache + # But really, we're only wrappercached if we have an interface + # object (that is, when we're not using SpiderMonkey promises). + self.wrapperCache = self.interface.hasInterfaceObject() def make_name(name): return name + "_workers" if self.workers else name diff --git a/dom/bindings/ToJSValue.cpp b/dom/bindings/ToJSValue.cpp index 8776f55e6d..fb2be1b879 100644 --- a/dom/bindings/ToJSValue.cpp +++ b/dom/bindings/ToJSValue.cpp @@ -7,6 +7,9 @@ #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/Exceptions.h" +#ifdef SPIDERMONKEY_PROMISE +#include "mozilla/dom/Promise.h" +#endif // SPIDERMONKEY_PROMISE #include "nsAString.h" #include "nsContentUtils.h" #include "nsStringBuffer.h" @@ -64,5 +67,15 @@ ToJSValue(JSContext* aCx, return true; } +#ifdef SPIDERMONKEY_PROMISE +bool +ToJSValue(JSContext* aCx, Promise& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + aValue.setObject(*aArgument.PromiseObj()); + return true; +} +#endif // SPIDERMONKEY_PROMISE + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/ToJSValue.h b/dom/bindings/ToJSValue.h index f47fba3124..b5125208af 100644 --- a/dom/bindings/ToJSValue.h +++ b/dom/bindings/ToJSValue.h @@ -19,6 +19,8 @@ namespace mozilla { namespace dom { +class Promise; + // If ToJSValue returns false, it must set an exception on the // JSContext. @@ -304,6 +306,14 @@ ToJSValue(JSContext* aCx, return ToJSValue(aCx, *aArgument, aValue); } +#ifdef SPIDERMONKEY_PROMISE +// Accept Promise objects, which need special handling. +MOZ_WARN_UNUSED_RESULT bool +ToJSValue(JSContext* aCx, + Promise& aArgument, + JS::MutableHandle<JS::Value> aValue); +#endif // SPIDERMONKEY_PROMISE + // Accept arrays of other things we accept template <typename T> MOZ_WARN_UNUSED_RESULT bool diff --git a/dom/html/test/browser.ini b/dom/html/test/browser.ini index 7755e4be58..3e2cbf83b2 100644 --- a/dom/html/test/browser.ini +++ b/dom/html/test/browser.ini @@ -1,8 +1,10 @@ [DEFAULT] support-files = bug592641_img.jpg + dummy_page.html file_bug649778.html file_bug649778.html^headers^ + head.js [browser_bug592641.js] [browser_bug649778.js] @@ -14,3 +16,4 @@ support-files = file_bug1108547-2.html file_bug1108547-3.html [browser_DOMDocElementInserted.js] +[browser_fullscreen-contextmenu-esc.js] diff --git a/dom/html/test/browser_fullscreen-contextmenu-esc.js b/dom/html/test/browser_fullscreen-contextmenu-esc.js new file mode 100644 index 0000000000..a4975145b0 --- /dev/null +++ b/dom/html/test/browser_fullscreen-contextmenu-esc.js @@ -0,0 +1,105 @@ +"use strict"; + +function frameScript() { + addMessageListener("Test:RequestFullscreen", () => { + content.document.body.mozRequestFullScreen(); + }); + content.document.addEventListener("mozfullscreenchange", () => { + sendAsyncMessage("Test:FullscreenChanged", content.document.mozFullScreen); + }); + addMessageListener("Test:QueryFullscreenState", () => { + sendAsyncMessage("Test:FullscreenState", content.document.mozFullScreen); + }); + function waitUntilActive() { + let doc = content.document; + if (doc.docShell.isActive && doc.hasFocus()) { + sendAsyncMessage("Test:Activated"); + } else { + setTimeout(waitUntilActive, 10); + } + } + waitUntilActive(); +} + +var gMessageManager; + +function listenOneMessage(aMsg, aListener) { + function listener({ data }) { + gMessageManager.removeMessageListener(aMsg, listener); + aListener(data); + } + gMessageManager.addMessageListener(aMsg, listener); +} + +function promiseOneMessage(aMsg) { + return new Promise(resolve => listenOneMessage(aMsg, resolve)); +} + +function captureUnexpectedFullscreenChange() { + ok(false, "Caught an unexpected fullscreen change"); +} + +const kPage = "http://example.org/browser/dom/html/test/dummy_page.html"; + +add_task(function* () { + yield pushPrefs( + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"]); + + let tab = gBrowser.addTab(kPage); + registerCleanupFunction(() => gBrowser.removeTab(tab)); + let browser = tab.linkedBrowser; + gBrowser.selectedTab = tab; + yield waitForDocLoadComplete(); + + gMessageManager = browser.messageManager; + gMessageManager.loadFrameScript( + "data:,(" + frameScript.toString() + ")();", false); + + // Wait for the document being activated, so that + // fullscreen request won't be denied. + yield promiseOneMessage("Test:Activated"); + + let contextMenu = document.getElementById("contentAreaContextMenu"); + ok(contextMenu, "Got context menu"); + + let state; + info("Enter DOM fullscreen"); + gMessageManager.sendAsyncMessage("Test:RequestFullscreen"); + state = yield promiseOneMessage("Test:FullscreenChanged"); + ok(state, "The content should have entered fullscreen"); + ok(document.mozFullScreen, "The chrome should also be in fullscreen"); + gMessageManager.addMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + + info("Open context menu"); + is(contextMenu.state, "closed", "Should not have opened context menu"); + let popupShownPromise = promiseWaitForEvent(window, "popupshown"); + EventUtils.synthesizeMouse(browser, screen.width / 2, screen.height / 2, + {type: "contextmenu", button: 2}, window); + yield popupShownPromise; + is(contextMenu.state, "open", "Should have opened context menu"); + + info("Send the first escape"); + let popupHidePromise = promiseWaitForEvent(window, "popuphidden"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield popupHidePromise; + is(contextMenu.state, "closed", "Should have closed context menu"); + + // Wait a small time to confirm that the first ESC key + // does not exit fullscreen. + yield new Promise(resolve => setTimeout(resolve, 1000)); + gMessageManager.sendAsyncMessage("Test:QueryFullscreenState"); + state = yield promiseOneMessage("Test:FullscreenState"); + ok(state, "The content should still be in fullscreen"); + ok(document.mozFullScreen, "The chrome should still be in fullscreen"); + + info("Send the second escape"); + gMessageManager.removeMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + let fullscreenExitPromise = promiseOneMessage("Test:FullscreenChanged"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + state = yield fullscreenExitPromise; + ok(!state, "The content should have exited fullscreen"); + ok(!document.mozFullScreen, "The chrome should have exited fullscreen"); +}); diff --git a/dom/html/test/dummy_page.html b/dom/html/test/dummy_page.html new file mode 100644 index 0000000000..fd238954c6 --- /dev/null +++ b/dom/html/test/dummy_page.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<title>Dummy test page + + + +

Dummy test page

+ + diff --git a/dom/html/test/file_fullscreen-esc-context-menu.html b/dom/html/test/file_fullscreen-esc-context-menu.html deleted file mode 100644 index c7c62bdc30..0000000000 --- a/dom/html/test/file_fullscreen-esc-context-menu.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - Text for bug 910532 - - - - - - - - - diff --git a/dom/html/test/head.js b/dom/html/test/head.js new file mode 100644 index 0000000000..7f5db315e0 --- /dev/null +++ b/dom/html/test/head.js @@ -0,0 +1,54 @@ +function pushPrefs(...aPrefs) { + return new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve); + }); +} + +function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) { + return new Promise((resolve) => { + function listener(event) { + info("Saw " + eventName); + object.removeEventListener(eventName, listener, capturing, chrome); + resolve(event); + } + + info("Waiting for " + eventName); + object.addEventListener(eventName, listener, capturing, chrome); + }); +} + +/** + * Waits for the next load to complete in any browser or the given browser. + * If a is given it waits for a load in any of its browsers. + * + * @return promise + */ +function waitForDocLoadComplete(aBrowser=gBrowser) { + return new Promise(resolve => { + let listener = { + onStateChange: function (webProgress, req, flags, status) { + let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK | + Ci.nsIWebProgressListener.STATE_STOP; + info("Saw state " + flags.toString(16) + " and status " + status.toString(16)); + // When a load needs to be retargetted to a new process it is cancelled + // with NS_BINDING_ABORTED so ignore that case + if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) { + aBrowser.removeProgressListener(this); + waitForDocLoadComplete.listeners.delete(this); + let chan = req.QueryInterface(Ci.nsIChannel); + info("Browser loaded " + chan.originalURI.spec); + resolve(); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]) + }; + aBrowser.addProgressListener(listener); + waitForDocLoadComplete.listeners.add(listener); + info("Waiting for browser load"); + }); +} +// Keep a set of progress listeners for waitForDocLoadComplete() to make sure +// they're not GC'ed before we saw the page load. +waitForDocLoadComplete.listeners = new Set(); +registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear()); diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini index 1a06aa84e1..e4a79ed736 100644 --- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -51,7 +51,6 @@ support-files = file_fullscreen-backdrop.html file_fullscreen-denied-inner.html file_fullscreen-denied.html - file_fullscreen-esc-context-menu.html file_fullscreen-esc-exit-inner.html file_fullscreen-esc-exit.html file_fullscreen-hidden.html diff --git a/dom/html/test/test_fullscreen-api.html b/dom/html/test/test_fullscreen-api.html index 79e62ecee5..3e5480ae91 100644 --- a/dom/html/test/test_fullscreen-api.html +++ b/dom/html/test/test_fullscreen-api.html @@ -30,7 +30,6 @@ SimpleTest.requestFlakyTimeout("untriaged"); var gTestWindows = [ "file_fullscreen-multiple.html", "file_fullscreen-rollback.html", - "file_fullscreen-esc-context-menu.html", "file_fullscreen-esc-exit.html", "file_fullscreen-denied.html", "file_fullscreen-api.html", diff --git a/dom/promise/AbortablePromise.cpp b/dom/promise/AbortablePromise.cpp deleted file mode 100644 index 55d255bc3a..0000000000 --- a/dom/promise/AbortablePromise.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=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/. */ - -#include "mozilla/dom/AbortablePromise.h" - -#include "mozilla/dom/AbortablePromiseBinding.h" -#include "mozilla/dom/PromiseNativeAbortCallback.h" -#include "mozilla/ErrorResult.h" -#include "nsThreadUtils.h" -#include "PromiseCallback.h" - -namespace mozilla { -namespace dom { - -NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeAbortCallback) -NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeAbortCallback) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeAbortCallback) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END -NS_IMPL_CYCLE_COLLECTION_0(PromiseNativeAbortCallback) - -NS_IMPL_ADDREF_INHERITED(AbortablePromise, Promise) -NS_IMPL_RELEASE_INHERITED(AbortablePromise, Promise) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AbortablePromise) -NS_INTERFACE_MAP_END_INHERITING(Promise) -NS_IMPL_CYCLE_COLLECTION_INHERITED(AbortablePromise, Promise, mAbortCallback) - -AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal, - PromiseNativeAbortCallback& aAbortCallback) - : Promise(aGlobal) - , mAbortCallback(&aAbortCallback) -{ -} - -AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal) - : Promise(aGlobal) -{ -} - -AbortablePromise::~AbortablePromise() -{ -} - -/* static */ already_AddRefed -AbortablePromise::Create(nsIGlobalObject* aGlobal, - PromiseNativeAbortCallback& aAbortCallback, - ErrorResult& aRv) -{ - RefPtr p = new AbortablePromise(aGlobal, aAbortCallback); - p->CreateWrapper(nullptr, aRv); - if (aRv.Failed()) { - return nullptr; - } - return p.forget(); -} - -JSObject* -AbortablePromise::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return MozAbortablePromiseBinding::Wrap(aCx, this, aGivenProto); -} - -/* static */ already_AddRefed -AbortablePromise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit, - AbortCallback& aAbortCallback, ErrorResult& aRv, - JS::Handle aDesiredProto) -{ - nsCOMPtr global; - global = do_QueryInterface(aGlobal.GetAsSupports()); - if (!global) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; - } - - RefPtr promise = new AbortablePromise(global); - promise->CreateWrapper(aDesiredProto, aRv); - if (aRv.Failed()) { - return nullptr; - } - - promise->CallInitFunction(aGlobal, aInit, aRv); - if (aRv.Failed()) { - return nullptr; - } - - promise->mAbortCallback = &aAbortCallback; - - return promise.forget(); -} - -void -AbortablePromise::Abort() -{ - if (IsPending()) { - return; - } - MaybeReject(NS_ERROR_ABORT); - - nsCOMPtr runnable = - NS_NewRunnableMethod(this, &AbortablePromise::DoAbort); - Promise::DispatchToMicroTask(runnable); -} - -void -AbortablePromise::DoAbort() -{ - if (mAbortCallback.HasWebIDLCallback()) { - mAbortCallback.GetWebIDLCallback()->Call(); - return; - } - mAbortCallback.GetXPCOMCallback()->Call(); -} - -} // namespace dom -} // namespace mozilla diff --git a/dom/promise/AbortablePromise.h b/dom/promise/AbortablePromise.h deleted file mode 100644 index c6577fb4cd..0000000000 --- a/dom/promise/AbortablePromise.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=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/. */ - -#ifndef mozilla_dom_AbortablePromise_h__ -#define mozilla_dom_AbortablePromise_h__ - -#include "js/TypeDecls.h" -#include "mozilla/dom/Promise.h" -#include "mozilla/dom/CallbackObject.h" - -namespace mozilla { -namespace dom { - -class AbortCallback; -class PromiseNativeAbortCallback; - -class AbortablePromise - : public Promise -{ -public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AbortablePromise, Promise) - -public: - // It is the same as Promise::Create except that this takes an extra - // aAbortCallback parameter to set the abort callback handler. - static already_AddRefed - Create(nsIGlobalObject* aGlobal, PromiseNativeAbortCallback& aAbortCallback, - ErrorResult& aRv); - -protected: - // Constructor used to create native AbortablePromise with C++. - AbortablePromise(nsIGlobalObject* aGlobal, - PromiseNativeAbortCallback& aAbortCallback); - - // Constructor used to create AbortablePromise for JavaScript. It should be - // called by the static AbortablePromise::Constructor. - explicit AbortablePromise(nsIGlobalObject* aGlobal); - - virtual ~AbortablePromise(); - -public: - virtual JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - static already_AddRefed - Constructor(const GlobalObject& aGlobal, PromiseInit& aInit, - AbortCallback& aAbortCallback, ErrorResult& aRv, - JS::Handle aDesiredProto); - - void Abort(); - -private: - void DoAbort(); - - // The callback functions to abort the promise. - CallbackObjectHolder mAbortCallback; -}; - -} // namespace dom -} // namespace mozilla - -#endif // mozilla_dom_AbortablePromise_h__ diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index 2298d99784..b6a8c66000 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -50,6 +50,7 @@ Atomic gIDGenerator(0); using namespace workers; +#ifndef SPIDERMONKEY_PROMISE // This class processes the promise's callbacks with promise's result. class PromiseReactionJob final : public nsRunnable { @@ -317,7 +318,7 @@ struct MOZ_STACK_CLASS Promise::PromiseCapability // purposes it's simpler to store them as JS::Value. // [[Promise]]. - JS::Rooted mPromise; + JS::Rooted mPromise; // [[Resolve]]. Value in the context compartment. JS::Rooted mResolve; // [[Reject]]. Value in the context compartment. @@ -340,7 +341,7 @@ Promise::PromiseCapability::RejectWithException(JSContext* aCx, // or at least the parts of it that happen if we have an abrupt completion. MOZ_ASSERT(!aRv.Failed()); - MOZ_ASSERT(mNativePromise || !mPromise.isUndefined(), + MOZ_ASSERT(mNativePromise || mPromise, "NewPromiseCapability didn't succeed"); JS::Rooted exn(aCx); @@ -370,47 +371,62 @@ Promise::PromiseCapability::RejectWithException(JSContext* aCx, JS::Value Promise::PromiseCapability::PromiseValue() const { - MOZ_ASSERT(mNativePromise || !mPromise.isUndefined(), + MOZ_ASSERT(mNativePromise || mPromise, "NewPromiseCapability didn't succeed"); if (mNativePromise) { return JS::ObjectValue(*mNativePromise->GetWrapper()); } - return mPromise; + return JS::ObjectValue(*mPromise); } +#endif // SPIDERMONKEY_PROMISE + // Promise NS_IMPL_CYCLE_COLLECTION_CLASS(Promise) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise) +#ifndef SPIDERMONKEY_PROMISE #if defined(DOM_PROMISE_DEPRECATED_REPORTING) tmp->MaybeReportRejectedOnce(); #else tmp->mResult = JS::UndefinedValue(); #endif // defined(DOM_PROMISE_DEPRECATED_REPORTING) +#endif // SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) +#ifndef SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks) NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +#else // SPIDERMONKEY_PROMISE + tmp->mPromiseObj = nullptr; +#endif // SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) +#ifndef SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks) +#endif // SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise) +#ifndef SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResult) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAllocationStack) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRejectionStack) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mFullfillmentStack) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +#else // SPIDERMONKEY_PROMISE + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj); +#endif // SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_TRACE_END +#ifndef SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Promise) if (tmp->IsBlack()) { JS::ExposeValueToActiveJS(tmp->mResult); @@ -434,18 +450,22 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Promise) return tmp->IsBlack(); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END +#endif // SPIDERMONKEY_PROMISE NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise) NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise) +#ifndef SPIDERMONKEY_PROMISE NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +#endif // SPIDERMONKEY_PROMISE NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(Promise) NS_INTERFACE_MAP_END Promise::Promise(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) +#ifndef SPIDERMONKEY_PROMISE , mResult(JS::UndefinedValue()) , mAllocationStack(nullptr) , mRejectionStack(nullptr) @@ -459,22 +479,364 @@ Promise::Promise(nsIGlobalObject* aGlobal) , mIsLastInChain(true) , mWasNotifiedAsUncaught(false) , mID(0) +#else // SPIDERMONKEY_PROMISE + , mPromiseObj(nullptr) +#endif // SPIDERMONKEY_PROMISE { MOZ_ASSERT(mGlobal); mozilla::HoldJSObjects(this); +#ifndef SPIDERMONKEY_PROMISE mCreationTimestamp = TimeStamp::Now(); +#endif // SPIDERMONKEY_PROMISE } Promise::~Promise() { +#ifndef SPIDERMONKEY_PROMISE #if defined(DOM_PROMISE_DEPRECATED_REPORTING) MaybeReportRejectedOnce(); #endif // defined(DOM_PROMISE_DEPRECATED_REPORTING) +#endif // SPIDERMONKEY_PROMISE mozilla::DropJSObjects(this); } +#ifdef SPIDERMONKEY_PROMISE + +bool +Promise::WrapObject(JSContext* aCx, JS::Handle aGivenProto, + JS::MutableHandle aWrapper) +{ +#ifdef DEBUG + binding_detail::AssertReflectorHasGivenProto(aCx, mPromiseObj, aGivenProto); +#endif // DEBUG + JS::ExposeObjectToActiveJS(mPromiseObj); + aWrapper.set(mPromiseObj); + return true; +} + +// static +already_AddRefed +Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv) +{ + RefPtr p = new Promise(aGlobal); + p->CreateWrapper(nullptr, aRv); + if (aRv.Failed()) { + return nullptr; + } + return p.forget(); +} + +// static +already_AddRefed +Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, + JS::Handle aValue, ErrorResult& aRv) +{ + JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject()); + JS::Rooted p(aCx, + JS::CallOriginalPromiseResolve(aCx, aValue)); + if (!p) { + aRv.NoteJSContextException(); + return nullptr; + } + + return CreateFromExisting(aGlobal, p); +} + +// static +already_AddRefed +Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx, + JS::Handle aValue, ErrorResult& aRv) +{ + JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject()); + JS::Rooted p(aCx, + JS::CallOriginalPromiseReject(aCx, aValue)); + if (!p) { + aRv.NoteJSContextException(); + return nullptr; + } + + return CreateFromExisting(aGlobal, p); +} + +// static +already_AddRefed +Promise::All(const GlobalObject& aGlobal, + const nsTArray>& aPromiseList, ErrorResult& aRv) +{ + nsCOMPtr global; + global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + JSContext* cx = aGlobal.Context(); + + JS::AutoObjectVector promises(cx); + if (!promises.reserve(aPromiseList.Length())) { + aRv.NoteJSContextException(); + return nullptr; + } + + for (auto& promise : aPromiseList) { + JS::Rooted promiseObj(cx, promise->PromiseObj()); + // Just in case, make sure these are all in the context compartment. + if (!JS_WrapObject(cx, &promiseObj)) { + aRv.NoteJSContextException(); + return nullptr; + } + promises.infallibleAppend(promiseObj); + } + + JS::Rooted result(cx, JS::GetWaitForAllPromise(cx, promises)); + if (!result) { + aRv.NoteJSContextException(); + return nullptr; + } + + return CreateFromExisting(global, result); +} + +void +Promise::Then(JSContext* aCx, + // aCalleeGlobal may not be in the compartment of aCx, when called over + // Xrays. + JS::Handle aCalleeGlobal, + AnyCallback* aResolveCallback, AnyCallback* aRejectCallback, + JS::MutableHandle aRetval, + ErrorResult& aRv) +{ + // Let's hope this does the right thing with Xrays... Ensure everything is + // just in the caller compartment; that ought to do the trick. In theory we + // should consider aCalleeGlobal, but in practice our only caller is + // DOMRequest::Then, which is not working with a Promise subclass, so things + // should be OK. + JS::Rooted promise(aCx, PromiseObj()); + if (!JS_WrapObject(aCx, &promise)) { + aRv.NoteJSContextException(); + return; + } + + JS::Rooted resolveCallback(aCx); + if (aResolveCallback) { + resolveCallback = aResolveCallback->Callback(); + if (!JS_WrapObject(aCx, &resolveCallback)) { + aRv.NoteJSContextException(); + return; + } + } + + JS::Rooted rejectCallback(aCx); + if (aRejectCallback) { + rejectCallback = aRejectCallback->Callback(); + if (!JS_WrapObject(aCx, &rejectCallback)) { + aRv.NoteJSContextException(); + return; + } + } + + JS::Rooted retval(aCx); + retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback, + rejectCallback); + if (!retval) { + aRv.NoteJSContextException(); + return; + } + + aRetval.setObject(*retval); +} + +// We need a dummy function to pass to JS::NewPromiseObject. +static bool +DoNothingPromiseExecutor(JSContext*, unsigned aArgc, JS::Value* aVp) +{ + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); + args.rval().setUndefined(); + return true; +} + +void +Promise::CreateWrapper(JS::Handle aDesiredProto, ErrorResult& aRv) +{ + AutoJSAPI jsapi; + if (!jsapi.Init(mGlobal)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + + JSFunction* doNothingFunc = + JS_NewFunction(cx, DoNothingPromiseExecutor, /* nargs = */ 2, + /* flags = */ 0, nullptr); + if (!doNothingFunc) { + JS_ClearPendingException(cx); + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + JS::Rooted doNothingObj(cx, JS_GetFunctionObject(doNothingFunc)); + mPromiseObj = JS::NewPromiseObject(cx, doNothingObj, aDesiredProto); + if (!mPromiseObj) { + JS_ClearPendingException(cx); + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } +} + +void +Promise::MaybeResolve(JSContext* aCx, + JS::Handle aValue) +{ + JS::Rooted p(aCx, PromiseObj()); + if (!JS::ResolvePromise(aCx, p, aValue)) { + // Now what? There's nothing sane to do here. + JS_ClearPendingException(aCx); + } +} + +void +Promise::MaybeReject(JSContext* aCx, + JS::Handle aValue) +{ + JS::Rooted p(aCx, PromiseObj()); + if (!JS::RejectPromise(aCx, p, aValue)) { + // Now what? There's nothing sane to do here. + JS_ClearPendingException(aCx); + } +} + +#define SLOT_NATIVEHANDLER 0 +#define SLOT_NATIVEHANDLER_TASK 1 + +enum class NativeHandlerTask : int32_t { + Resolve, + Reject +}; + +static bool +NativeHandlerCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp) +{ + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); + + JS::Rooted v(aCx, + js::GetFunctionNativeReserved(&args.callee(), + SLOT_NATIVEHANDLER)); + MOZ_ASSERT(v.isObject()); + + PromiseNativeHandler* handler; + if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &v.toObject(), + handler))) { + return Throw(aCx, NS_ERROR_UNEXPECTED); + } + + v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK); + NativeHandlerTask task = static_cast(v.toInt32()); + + if (task == NativeHandlerTask::Resolve) { + handler->ResolvedCallback(aCx, args.get(0)); + } else { + MOZ_ASSERT(task == NativeHandlerTask::Reject); + handler->RejectedCallback(aCx, args.get(0)); + } + + return true; +} + +static JSObject* +CreateNativeHandlerFunction(JSContext* aCx, JS::Handle aHolder, + NativeHandlerTask aTask) +{ + JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback, + /* nargs = */ 1, + /* flags = */ 0, nullptr); + if (!func) { + return nullptr; + } + + JS::Rooted obj(aCx, JS_GetFunctionObject(func)); + + JS::ExposeObjectToActiveJS(aHolder); + js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER, + JS::ObjectValue(*aHolder)); + js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK, + JS::Int32Value(static_cast(aTask))); + + return obj; +} + +void +Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) +{ + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(mGlobal))) { + // Our API doesn't allow us to return a useful error. Not like this should + // happen anyway. + return; + } + jsapi.TakeOwnershipOfErrorReporting(); + + JSContext* cx = jsapi.cx(); + JS::Rooted handlerWrapper(cx); + // Note: PromiseNativeHandler is NOT wrappercached. So we can't use + // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly. + if (NS_WARN_IF(!aRunnable->WrapObject(cx, nullptr, &handlerWrapper))) { + // Again, no way to report errors. + jsapi.ClearException(); + return; + } + + JS::Rooted resolveFunc(cx); + resolveFunc = + CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Resolve); + if (NS_WARN_IF(!resolveFunc)) { + jsapi.ClearException(); + return; + } + + JS::Rooted rejectFunc(cx); + rejectFunc = + CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Reject); + if (NS_WARN_IF(!rejectFunc)) { + jsapi.ClearException(); + return; + } + + JS::Rooted promiseObj(cx, PromiseObj()); + if (NS_WARN_IF(!JS::AddPromiseReactions(cx, promiseObj, resolveFunc, + rejectFunc))) { + jsapi.ClearException(); + return; + } +} + +void +Promise::HandleException(JSContext* aCx) +{ + JS::Rooted exn(aCx); + if (JS_GetPendingException(aCx, &exn)) { + JS_ClearPendingException(aCx); + // This is only called from MaybeSomething, so it's OK to MaybeReject here, + // unlike in the version that's used when !SPIDERMONKEY_PROMISE. + MaybeReject(aCx, exn); + } +} + +// static +already_AddRefed +Promise::CreateFromExisting(nsIGlobalObject* aGlobal, + JS::Handle aPromiseObj) +{ + MOZ_ASSERT(js::GetObjectCompartment(aGlobal->GetGlobalJSObject()) == + js::GetObjectCompartment(aPromiseObj)); + RefPtr p = new Promise(aGlobal); + p->mPromiseObj = aPromiseObj; + return p.forget(); +} + +#else // SPIDERMONKEY_PROMISE + JSObject* Promise::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { @@ -537,6 +899,8 @@ Promise::MaybeReject(JSContext* aCx, MaybeRejectInternal(aCx, aValue); } +#endif // SPIDERMONKEY_PROMISE + void Promise::MaybeReject(const RefPtr& aArg) { MaybeSomething(aArg, &Promise::MaybeReject); @@ -577,6 +941,8 @@ Promise::PerformMicroTaskCheckpoint() return true; } +#ifndef SPIDERMONKEY_PROMISE + /* static */ bool Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { @@ -959,7 +1325,7 @@ Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal, aCapability.mReject = v; // Step 10. - aCapability.mPromise.setObject(*promiseObj); + aCapability.mPromise = promiseObj; // Step 11 doesn't need anything, since the PromiseCapability was passed in. } @@ -1274,11 +1640,11 @@ Promise::Then(JSContext* aCx, JS::Handle aCalleeGlobal, RefPtr rejectFunc = new AnyCallback(aCx, rejectObj, GetIncumbentGlobal()); - if (!capability.mPromise.isObject()) { + if (!capability.mPromise) { aRv.ThrowTypeError(); return; } - JS::Rooted newPromiseObj(aCx, &capability.mPromise.toObject()); + JS::Rooted newPromiseObj(aCx, capability.mPromise); // We want to store the reflector itself. newPromiseObj = js::CheckedUnwrap(newPromiseObj); if (!newPromiseObj) { @@ -1979,6 +2345,8 @@ Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) AppendCallbacks(resolveCb, rejectCb); } +#endif // SPIDERMONKEY_PROMISE + JSObject* Promise::GlobalJSObject() const { @@ -1991,6 +2359,7 @@ Promise::Compartment() const return js::GetObjectCompartment(GlobalJSObject()); } +#ifndef SPIDERMONKEY_PROMISE void Promise::AppendCallbacks(PromiseCallback* aResolveCallback, PromiseCallback* aRejectCallback) @@ -2027,37 +2396,7 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback, TriggerPromiseReactions(); } } - -class WrappedWorkerRunnable final : public WorkerSameThreadRunnable -{ -public: - WrappedWorkerRunnable(workers::WorkerPrivate* aWorkerPrivate, nsIRunnable* aRunnable) - : WorkerSameThreadRunnable(aWorkerPrivate) - , mRunnable(aRunnable) - { - MOZ_ASSERT(aRunnable); - MOZ_COUNT_CTOR(WrappedWorkerRunnable); - } - - bool - WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override - { - NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable); - mRunnable->Run(); - return true; - } - -private: - virtual - ~WrappedWorkerRunnable() - { - MOZ_COUNT_DTOR(WrappedWorkerRunnable); - NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable); - } - - nsCOMPtr mRunnable; - NS_DECL_OWNINGTHREAD -}; +#endif // SPIDERMONKEY_PROMISE /* static */ void Promise::DispatchToMicroTask(nsIRunnable* aRunnable) @@ -2071,6 +2410,7 @@ Promise::DispatchToMicroTask(nsIRunnable* aRunnable) microtaskQueue.push(aRunnable); } +#ifndef SPIDERMONKEY_PROMISE #if defined(DOM_PROMISE_DEPRECATED_REPORTING) void Promise::MaybeReportRejected() @@ -2363,6 +2703,8 @@ Promise::GetDependentPromises(nsTArray>& aPromises) } } +#endif // SPIDERMONKEY_PROMISE + // A WorkerRunnable to resolve/reject the Promise on the worker thread. // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this. class PromiseWorkerProxyRunnable : public workers::WorkerRunnable @@ -2564,14 +2906,14 @@ void PromiseWorkerProxy::ResolvedCallback(JSContext* aCx, JS::Handle aValue) { - RunCallback(aCx, aValue, &Promise::ResolveInternal); + RunCallback(aCx, aValue, &Promise::MaybeResolve); } void PromiseWorkerProxy::RejectedCallback(JSContext* aCx, JS::Handle aValue) { - RunCallback(aCx, aValue, &Promise::RejectInternal); + RunCallback(aCx, aValue, &Promise::MaybeReject); } bool @@ -2647,6 +2989,7 @@ void Promise::MaybeRejectBrokenly(const nsAString& aArg) { MaybeSomething(aArg, &Promise::MaybeReject); } +#ifndef SPIDERMONKEY_PROMISE uint64_t Promise::GetID() { if (mID != 0) { @@ -2654,6 +2997,7 @@ Promise::GetID() { } return mID = ++gIDGenerator; } +#endif // SPIDERMONKEY_PROMISE } // namespace dom } // namespace mozilla diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index 0090674355..00b6b2494e 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -71,7 +71,12 @@ public: { 0x8a, 0xf9, 0x31, 0x5e, 0x8f, 0xce, 0x75, 0x65 } } class Promise : public nsISupports, +#ifndef SPIDERMONKEY_PROMISE + // Only wrappercached when we're not using SpiderMonkey + // promises, because those don't have a useful object moved + // hook, which wrappercache needs. public nsWrapperCache, +#endif // SPIDERMONKEY_PROMISE public SupportsWeakPtr { friend class NativePromiseCallback; @@ -92,17 +97,27 @@ class Promise : public nsISupports, public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROMISE_IID) NS_DECL_CYCLE_COLLECTING_ISUPPORTS +#ifdef SPIDERMONKEY_PROMISE + // We're not skippable, since we're not owned from JS to start with. + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise) +#else // SPIDERMONKEY_PROMISE NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(Promise) +#endif // SPIDERMONKEY_PROMISE MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Promise) // Promise creation tries to create a JS reflector for the Promise, so is // fallible. Furthermore, we don't want to do JS-wrapping on a 0-refcount // object, so we addref before doing that and return the addrefed pointer // here. +#ifdef SPIDERMONKEY_PROMISE + static already_AddRefed + Create(nsIGlobalObject* aGlobal, ErrorResult& aRv); +#else static already_AddRefed Create(nsIGlobalObject* aGlobal, ErrorResult& aRv, // Passing null for aDesiredProto will use Promise.prototype. JS::Handle aDesiredProto = nullptr); +#endif // SPIDERMONKEY_PROMISE typedef void (Promise::*MaybeFunc)(JSContext* aCx, JS::Handle aValue); @@ -156,6 +171,52 @@ public: return mGlobal; } +#ifdef SPIDERMONKEY_PROMISE + bool + WrapObject(JSContext* aCx, JS::Handle aGivenProto, + JS::MutableHandle aWrapper); + + // Do the equivalent of Promise.resolve in the current compartment of aCx. + // Errorrs are reported on the ErrorResult; if aRv comes back !Failed(), this + // function MUST return a non-null value. + static already_AddRefed + Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, + JS::Handle aValue, ErrorResult& aRv); + + // Do the equivalent of Promise.reject in the current compartment of aCx. + // Errorrs are reported on the ErrorResult; if aRv comes back !Failed(), this + // function MUST return a non-null value. + static already_AddRefed + Reject(nsIGlobalObject* aGlobal, JSContext* aCx, + JS::Handle aValue, ErrorResult& aRv); + + static already_AddRefed + All(const GlobalObject& aGlobal, + const nsTArray>& aPromiseList, ErrorResult& aRv); + + void + Then(JSContext* aCx, + // aCalleeGlobal may not be in the compartment of aCx, when called over + // Xrays. + JS::Handle aCalleeGlobal, + AnyCallback* aResolveCallback, AnyCallback* aRejectCallback, + JS::MutableHandle aRetval, + ErrorResult& aRv); + + JSObject* PromiseObj() const + { + if (mPromiseObj) { + JS::ExposeObjectToActiveJS(mPromiseObj); + } + return mPromiseObj; + } + +#else // SPIDERMONKEY_PROMISE + JSObject* PromiseObj() + { + return GetWrapper(); + } + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -212,6 +273,7 @@ public: static bool PromiseSpecies(JSContext* aCx, unsigned aArgc, JS::Value* aVp); +#endif // SPIDERMONKEY_PROMISE void AppendNativeHandler(PromiseNativeHandler* aRunnable); @@ -219,23 +281,36 @@ public: JSCompartment* Compartment() const; +#ifndef SPIDERMONKEY_PROMISE // Return a unique-to-the-process identifier for this Promise. uint64_t GetID(); +#endif // SPIDERMONKEY_PROMISE // Queue an async microtask to current main or worker thread. static void DispatchToMicroTask(nsIRunnable* aRunnable); +#ifndef SPIDERMONKEY_PROMISE enum JSCallbackSlots { SLOT_PROMISE = 0, SLOT_DATA }; +#endif // SPIDERMONKEY_PROMISE + +#ifdef SPIDERMONKEY_PROMISE + // Create a dom::Promise from a given SpiderMonkey Promise object. + // aPromiseObj MUST be in the compartment of aGlobal's global JS object. + static already_AddRefed + CreateFromExisting(nsIGlobalObject* aGlobal, + JS::Handle aPromiseObj); +#endif // SPIDERMONKEY_PROMISE protected: struct PromiseCapability; - // Do NOT call this unless you're Promise::Create. I wish we could enforce - // that from inside this class too, somehow. + // Do NOT call this unless you're Promise::Create or + // Promise::CreateFromExisting. I wish we could enforce that from inside this + // class too, somehow. explicit Promise(nsIGlobalObject* aGlobal); virtual ~Promise(); @@ -244,6 +319,7 @@ protected: // use the default prototype for the sort of Promise we have. void CreateWrapper(JS::Handle aDesiredProto, ErrorResult& aRv); +#ifndef SPIDERMONKEY_PROMISE // Create the JS resolving functions of resolve() and reject(). And provide // references to the two functions by calling PromiseInit passed from Promise // constructor. @@ -282,16 +358,18 @@ protected: { return mWasNotifiedAsUncaught; } +#endif // SPIDERMONKEY_PROMISE private: - friend class PromiseDebugging; - enum PromiseState { Pending, Resolved, Rejected }; +#ifndef SPIDERMONKEY_PROMISE + friend class PromiseDebugging; + void SetState(PromiseState aState) { MOZ_ASSERT(mState == Pending); @@ -338,11 +416,12 @@ private: JS::Handle aValue); void RejectInternal(JSContext* aCx, JS::Handle aValue); +#endif // SPIDERMONKEY_PROMISE template void MaybeSomething(T& aArgument, MaybeFunc aFunc) { ThreadsafeAutoJSContext cx; - JSObject* wrapper = GetWrapper(); + JSObject* wrapper = PromiseObj(); MOZ_ASSERT(wrapper); // We preserved it! JSAutoCompartment ac(cx, wrapper); @@ -355,6 +434,7 @@ private: (this->*aFunc)(cx, val); } +#ifndef SPIDERMONKEY_PROMISE // Static methods for the PromiseInit functions. static bool JSCallback(JSContext *aCx, unsigned aArgc, JS::Value *aVp); @@ -373,8 +453,6 @@ private: static JSObject* CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask); - void HandleException(JSContext* aCx); - #if defined(DOM_PROMISE_DEPRECATED_REPORTING) void RemoveFeature(); #endif // defined(DOM_PROMISE_DEPRECATED_REPORTING) @@ -382,9 +460,13 @@ private: // Capture the current stack and store it in aTarget. If false is // returned, an exception is presumably pending on aCx. bool CaptureStack(JSContext* aCx, JS::Heap& aTarget); +#endif // SPIDERMONKEY_PROMISE + + void HandleException(JSContext* aCx); RefPtr mGlobal; +#ifndef SPIDERMONKEY_PROMISE nsTArray > mResolveCallbacks; nsTArray > mRejectCallbacks; @@ -435,6 +517,9 @@ private: // Once `GetID()` has been called, a unique-to-the-process identifier for this // promise. Until then, `0`. uint64_t mID; +#else // SPIDERMONKEY_PROMISE + JS::Heap mPromiseObj; +#endif // SPIDERMONKEY_PROMISE }; NS_DEFINE_STATIC_IID_ACCESSOR(Promise, NS_PROMISE_IID) diff --git a/dom/promise/PromiseCallback.cpp b/dom/promise/PromiseCallback.cpp index 8081234b15..13ed1c53fd 100644 --- a/dom/promise/PromiseCallback.cpp +++ b/dom/promise/PromiseCallback.cpp @@ -15,6 +15,8 @@ namespace mozilla { namespace dom { +#ifndef SPIDERMONKEY_PROMISE + NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback) NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback) @@ -566,5 +568,7 @@ PromiseCallback::Factory(Promise* aNextPromise, JS::Handle aGlobal, return nullptr; } +#endif // SPIDERMONKEY_PROMISE + } // namespace dom } // namespace mozilla diff --git a/dom/promise/PromiseCallback.h b/dom/promise/PromiseCallback.h index 3cb98e1543..9f55e03d01 100644 --- a/dom/promise/PromiseCallback.h +++ b/dom/promise/PromiseCallback.h @@ -13,6 +13,7 @@ namespace mozilla { namespace dom { +#ifndef SPIDERMONKEY_PROMISE // This is the base class for any PromiseCallback. // It's a logical step in the promise chain of callbacks. class PromiseCallback : public nsISupports @@ -194,6 +195,8 @@ private: Promise::PromiseState mState; }; +#endif // SPIDERMONKEY_PROMISE + } // namespace dom } // namespace mozilla diff --git a/dom/promise/PromiseDebugging.cpp b/dom/promise/PromiseDebugging.cpp index 6706cd1fd7..92d181f649 100644 --- a/dom/promise/PromiseDebugging.cpp +++ b/dom/promise/PromiseDebugging.cpp @@ -66,6 +66,7 @@ private: /* static */ MOZ_THREAD_LOCAL(bool) FlushRejections::sDispatched; +#ifndef SPIDERMONKEY_PROMISE static Promise* UnwrapPromise(JS::Handle aPromise, ErrorResult& aRv) { @@ -103,6 +104,8 @@ PromiseDebugging::GetState(GlobalObject&, JS::Handle aPromise, } } +#endif // SPIDERMONKEY_PROMISE + /*static */ nsString PromiseDebugging::sIDPrefix; @@ -130,10 +133,15 @@ PromiseDebugging::Shutdown() /* static */ void PromiseDebugging::FlushUncaughtRejections() { + // XXXbz figure out the plan +#ifndef SPIDERMONKEY_PROMISE MOZ_ASSERT(!NS_IsMainThread()); FlushRejections::FlushSync(); +#endif // SPIDERMONKEY_PROMISE } +#ifndef SPIDERMONKEY_PROMISE + /* static */ void PromiseDebugging::GetAllocationStack(GlobalObject&, JS::Handle aPromise, JS::MutableHandle aStack, @@ -210,6 +218,8 @@ PromiseDebugging::GetTimeToSettle(GlobalObject&, JS::Handle aPromise, promise->mCreationTimestamp).ToMilliseconds(); } +#endif // SPIDERMONKEY_PROMISE + /* static */ void PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&, UncaughtRejectionObserver& aObserver) @@ -235,6 +245,8 @@ PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&, return false; } +#ifndef SPIDERMONKEY_PROMISE + /* static */ void PromiseDebugging::AddUncaughtRejection(Promise& aPromise) { @@ -263,10 +275,13 @@ PromiseDebugging::GetPromiseID(GlobalObject&, aID = sIDPrefix; aID.AppendInt(promiseID); } +#endif // SPIDERMONKEY_PROMISE /* static */ void PromiseDebugging::FlushUncaughtRejectionsInternal() { + // XXXbz talk to till about replacement for this stuff. +#ifndef SPIDERMONKEY_PROMISE CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get(); // The Promise that have been left uncaught (rejected and last in @@ -327,6 +342,7 @@ PromiseDebugging::FlushUncaughtRejectionsInternal() obs->OnConsumed(*promise, err); // Ignore errors } } +#endif // SPIDERMONKEY_PROMISE } } // namespace dom diff --git a/dom/promise/PromiseDebugging.h b/dom/promise/PromiseDebugging.h index c76076620f..5eeb65e6d3 100644 --- a/dom/promise/PromiseDebugging.h +++ b/dom/promise/PromiseDebugging.h @@ -32,6 +32,7 @@ public: static void Init(); static void Shutdown(); +#ifndef SPIDERMONKEY_PROMISE static void GetState(GlobalObject&, JS::Handle aPromise, PromiseDebuggingStateHolder& aState, ErrorResult& aRv); @@ -58,20 +59,25 @@ public: static void GetPromiseID(GlobalObject&, JS::Handle, nsString&, ErrorResult&); +#endif // SPIDERMONKEY_PROMISE // Mechanism for watching uncaught instances of Promise. + // XXXbz figure out the plan static void AddUncaughtRejectionObserver(GlobalObject&, UncaughtRejectionObserver& aObserver); static bool RemoveUncaughtRejectionObserver(GlobalObject&, UncaughtRejectionObserver& aObserver); +#ifndef SPIDERMONKEY_PROMISE // Mark a Promise as having been left uncaught at script completion. static void AddUncaughtRejection(Promise&); // Mark a Promise previously added with `AddUncaughtRejection` as // eventually consumed. static void AddConsumedRejection(Promise&); +#endif // SPIDERMONKEY_PROMISE // Propagate the informations from AddUncaughtRejection // and AddConsumedRejection to observers. + // XXXbz figure out the plan. static void FlushUncaughtRejections(); protected: diff --git a/dom/promise/PromiseNativeAbortCallback.h b/dom/promise/PromiseNativeAbortCallback.h deleted file mode 100644 index 28d1925f5b..0000000000 --- a/dom/promise/PromiseNativeAbortCallback.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=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/. */ - -#ifndef mozilla_dom_PromiseNativeAbortCallback_h -#define mozilla_dom_PromiseNativeAbortCallback_h - -#include "nsISupports.h" - -namespace mozilla { -namespace dom { - -/* - * PromiseNativeAbortCallback allows C++ to react to an AbortablePromise being - * aborted. - */ -class PromiseNativeAbortCallback : public nsISupports -{ -protected: - virtual ~PromiseNativeAbortCallback() - { } - -public: - // Implemented in AbortablePromise.cpp. - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeAbortCallback) - - virtual void Call() = 0; -}; - -} // namespace dom -} // namespace mozilla - -#endif // mozilla_dom_PromiseNativeAbortCallback_h diff --git a/dom/promise/PromiseNativeHandler.cpp b/dom/promise/PromiseNativeHandler.cpp new file mode 100644 index 0000000000..50d0e1b96c --- /dev/null +++ b/dom/promise/PromiseNativeHandler.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/dom/PromiseBinding.h" + +namespace mozilla { +namespace dom { + +#ifdef SPIDERMONKEY_PROMISE +bool +PromiseNativeHandler::WrapObject(JSContext* aCx, + JS::Handle aGivenProto, + JS::MutableHandle aWrapper) +{ + return PromiseNativeHandlerBinding::Wrap(aCx, this, aGivenProto, aWrapper); +} +#endif // SPIDERMONKEY_PROMISE + +} // namespace dom +} // namespace mozilla + + diff --git a/dom/promise/PromiseNativeHandler.h b/dom/promise/PromiseNativeHandler.h index 6ba7142aa1..61657361da 100644 --- a/dom/promise/PromiseNativeHandler.h +++ b/dom/promise/PromiseNativeHandler.h @@ -30,6 +30,12 @@ public: virtual void RejectedCallback(JSContext* aCx, JS::Handle aValue) = 0; + +#ifdef SPIDERMONKEY_PROMISE + bool WrapObject(JSContext* aCx, JS::Handle aGivenProto, + JS::MutableHandle aWrapper); +#endif // SPIDERMONKEY_PROMISE + }; } // namespace dom diff --git a/dom/promise/moz.build b/dom/promise/moz.build index 30337a6c9a..8edbb07c7b 100644 --- a/dom/promise/moz.build +++ b/dom/promise/moz.build @@ -5,19 +5,17 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXPORTS.mozilla.dom += [ - 'AbortablePromise.h', 'Promise.h', 'PromiseDebugging.h', - 'PromiseNativeAbortCallback.h', 'PromiseNativeHandler.h', 'PromiseWorkerProxy.h', ] UNIFIED_SOURCES += [ - 'AbortablePromise.cpp', 'Promise.cpp', 'PromiseCallback.cpp', - 'PromiseDebugging.cpp' + 'PromiseDebugging.cpp', + 'PromiseNativeHandler.cpp', ] LOCAL_INCLUDES += [ diff --git a/dom/promise/tests/mochitest.ini b/dom/promise/tests/mochitest.ini index 888bed0a24..1f2c0d5e08 100644 --- a/dom/promise/tests/mochitest.ini +++ b/dom/promise/tests/mochitest.ini @@ -3,7 +3,6 @@ # to go in here, not chrome.ini, apparently. support-files = file_promise_xrays.html -[test_abortable_promise.html] [test_bug883683.html] [test_promise.html] [test_promise_utils.html] diff --git a/dom/promise/tests/test_abortable_promise.html b/dom/promise/tests/test_abortable_promise.html deleted file mode 100644 index d02b521490..0000000000 --- a/dom/promise/tests/test_abortable_promise.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - Promise.resolve(anything) Test - - - - -

- -
-
-
- - - diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 9d3e5d754c..0e7ebb9f7e 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -118,8 +118,6 @@ var legacyMozPrefixedInterfaces = // IMPORTANT: Do not change the list below without review from a DOM peer! var interfaceNamesInGlobalScope = [ -// IMPORTANT: Do not change this list without review from a DOM peer! - {name: "MozAbortablePromise", disabled: true}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "AlarmsManager", b2g: true}, // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/AbortablePromise.webidl b/dom/webidl/AbortablePromise.webidl deleted file mode 100644 index bc06891a9c..0000000000 --- a/dom/webidl/AbortablePromise.webidl +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -callback AbortCallback = void (); - -[Constructor(PromiseInit init, AbortCallback abortCallback), - Pref="dom.abortablepromise.enabled"] -interface MozAbortablePromise : _Promise { - /* - * Aborts the promise. - * If the promise has not been resolved or rejected, it should be rejected - * with an Exception of type abort and then AbortCallback is called - * asynchronously. Otherwise, nothing should be done. - */ - void abort(); -}; diff --git a/dom/webidl/DummyBinding.webidl b/dom/webidl/DummyBinding.webidl index 931c9db99e..01b0a54b21 100644 --- a/dom/webidl/DummyBinding.webidl +++ b/dom/webidl/DummyBinding.webidl @@ -9,6 +9,7 @@ interface DummyInterface { void lifecycleCallbacks(optional LifecycleCallbacks arg); + void promiseJobCallback(PromiseJobCallback arg); }; interface DummyInterfaceWorkers { diff --git a/dom/webidl/Promise.webidl b/dom/webidl/Promise.webidl index 328affcd3b..d449368af8 100644 --- a/dom/webidl/Promise.webidl +++ b/dom/webidl/Promise.webidl @@ -12,10 +12,15 @@ // function"; for now, we just use "object". callback PromiseInit = void (object resolve, object reject); +callback PromiseJobCallback = void(); + [TreatNonCallableAsNull] callback AnyCallback = any (any value); -// REMOVE THE RELEVANT ENTRY FROM test_interfaces.html WHEN THIS IS IMPLEMENTED IN JS. +// When using SpiderMonkey promises, we don't want to define all this stuff; +// just define a tiny interface to make codegen of Promise arguments and return +// values work. +#ifndef SPIDERMONKEY_PROMISE [Constructor(PromiseInit init), Exposed=(Window,Worker,System)] // Need to escape "Promise" so it's treated as an identifier. @@ -56,3 +61,17 @@ interface _Promise { [NewObject, Throws] static any race(optional any iterable); }; +#else // SPIDERMONKEY_PROMISE +[NoInterfaceObject, + Exposed=(Window,Worker,System)] +// Need to escape "Promise" so it's treated as an identifier. +interface _Promise { +}; + +// Hack to allow us to have JS owning and properly tracing/CCing/etc a +// PromiseNativeHandler. +[NoInterfaceObject, + Exposed=(Window,Worker,System)] +interface PromiseNativeHandler { +}; +#endif // SPIDERMONKEY_PROMISE diff --git a/dom/webidl/PromiseDebugging.webidl b/dom/webidl/PromiseDebugging.webidl index 20eed3deb6..a679b3f198 100644 --- a/dom/webidl/PromiseDebugging.webidl +++ b/dom/webidl/PromiseDebugging.webidl @@ -52,6 +52,7 @@ callback interface UncaughtRejectionObserver { [ChromeOnly, Exposed=(Window,System)] interface PromiseDebugging { +#ifndef SPIDERMONKEY_PROMISE /** * The various functions on this interface all expect to take promises but * don't want the WebIDL behavior of assimilating random passed-in objects @@ -130,6 +131,8 @@ interface PromiseDebugging { [Throws] static DOMHighResTimeStamp getTimeToSettle(object p); +#endif // SPIDERMONKEY_PROMISE + /** * Watching uncaught rejections on the current thread. * diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index b38f13c7bf..e2f8155a5b 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -11,12 +11,13 @@ GENERATED_WEBIDL_FILES = [ PREPROCESSED_WEBIDL_FILES = [ 'HTMLMediaElement.webidl', 'Navigator.webidl', + 'Promise.webidl', + 'PromiseDebugging.webidl', 'ServiceWorkerRegistration.webidl', 'Window.webidl', ] WEBIDL_FILES = [ - 'AbortablePromise.webidl', 'AbstractWorker.webidl', 'ActivityRequestHandler.webidl', 'AlarmsManager.webidl', @@ -389,8 +390,6 @@ WEBIDL_FILES = [ 'PresentationSession.webidl', 'ProcessingInstruction.webidl', 'ProfileTimelineMarker.webidl', - 'Promise.webidl', - 'PromiseDebugging.webidl', 'PropertyIndexedKeyframes.webidl', 'RadioNodeList.webidl', 'Range.webidl', diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index eda0933904..8b7b1873cd 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -36,7 +36,7 @@ class ZoneHeapThreshold double gcHeapGrowthFactor_; // GC trigger threshold for allocations on the GC heap. - size_t gcTriggerBytes_; + mozilla::Atomic gcTriggerBytes_; public: ZoneHeapThreshold() diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 3d3f294a9b..7acf84f58d 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -8661,8 +8661,7 @@ AssertIsPromise(JSContext* cx, HandleObject promise) { MOZ_ASSERT(promise); assertSameCompartment(cx, promise); - MOZ_ASSERT(strcmp(promise->getClass()->name, "Promise") == 0 || - strcmp(promise->getClass()->name, "MozAbortablePromise") == 0); + MOZ_ASSERT(strcmp(promise->getClass()->name, "Promise") == 0); } JS_PUBLIC_API(void) diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index 209240ee4d..edbfb6e9e0 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -293,9 +293,8 @@ SandboxFetch(JSContext* cx, JS::HandleObject scope, const CallArgs& args) if (rv.MaybeSetPendingException(cx)) { return false; } - if (!GetOrCreateDOMReflector(cx, response, args.rval())) { - return false; - } + + args.rval().setObject(*response->PromiseObj()); return true; } @@ -1161,10 +1160,12 @@ xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prin if (!options.globalProperties.Define(cx, sandbox)) return NS_ERROR_XPC_UNEXPECTED; +#ifndef SPIDERMONKEY_PROMISE // Promise is supposed to be part of ES, and therefore should appear on // every global. if (!dom::PromiseBinding::GetConstructorObject(cx, sandbox)) return NS_ERROR_XPC_UNEXPECTED; +#endif // SPIDERMONKEY_PROMISE } // We handle the case where the context isn't in a compartment for the diff --git a/js/xpconnect/src/XPCConvert.cpp b/js/xpconnect/src/XPCConvert.cpp index ca0e82490f..a33a11d38b 100644 --- a/js/xpconnect/src/XPCConvert.cpp +++ b/js/xpconnect/src/XPCConvert.cpp @@ -13,6 +13,7 @@ #include "nsIAtom.h" #include "nsWrapperCache.h" #include "nsJSUtils.h" +#include "nsQueryObject.h" #include "WrapperFactory.h" #include "nsWrapperCacheInlines.h" @@ -25,6 +26,7 @@ #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/PrimitiveConversions.h" +#include "mozilla/dom/Promise.h" #include "mozilla/jsipc/CrossProcessObjectWrappers.h" using namespace xpc; @@ -792,6 +794,21 @@ XPCConvert::NativeInterface2JSObject(MutableHandleValue d, return CreateHolderIfNeeded(flat, d, dest); } +#ifdef SPIDERMONKEY_PROMISE + if (iid->Equals(NS_GET_IID(nsISupports))) { + // Check for a Promise being returned via nsISupports. In that + // situation, we want to dig out its underlying JS object and return + // that. + RefPtr promise = do_QueryObject(aHelper.Object()); + if (promise) { + flat = promise->PromiseObj(); + if (!JS_WrapObject(cx, &flat)) + return false; + return CreateHolderIfNeeded(flat, d, dest); + } + } +#endif // SPIDERMONKEY_PROMISE + // Don't double wrap CPOWs. This is a temporary measure for compatibility // with objects that don't provide necessary QIs (such as objects under // the new DOM bindings). We expect the other side of the CPOW to have @@ -925,6 +942,19 @@ XPCConvert::JSObject2NativeInterface(void** dest, HandleObject src, if (GetISupportsFromJSObject(inner ? inner : src, &iface)) { return iface && NS_SUCCEEDED(iface->QueryInterface(*iid, dest)); } + +#ifdef SPIDERMONKEY_PROMISE + // Deal with Promises being passed as nsISupports. In that situation we + // want to create a dom::Promise and use that. + if (iid->Equals(NS_GET_IID(nsISupports))) { + RootedObject innerObj(cx, inner); + if (IsPromiseObject(innerObj)) { + nsIGlobalObject* glob = NativeGlobal(innerObj); + RefPtr p = Promise::CreateFromExisting(glob, innerObj); + return p && NS_SUCCEEDED(p->QueryInterface(*iid, dest)); + } + } +#endif // SPIDERMONKEY_PROMISE } RefPtr wrapper; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index c7bb97f2be..8fa85b554c 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -113,9 +113,6 @@ pref("browser.cache.compression_level", 3); pref("browser.download.saveZoneInformation", 2); #endif -// Whether or not MozAbortablePromise is enabled. -pref("dom.abortablepromise.enabled", false); - // Whether or not testing features are enabled. pref("dom.quotaManager.testing", false); diff --git a/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-09.html.ini b/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-09.html.ini deleted file mode 100644 index 617f8d14ba..0000000000 --- a/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-09.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[document.title-09.html] - type: testharness - [No title element in SVG document] - expected: FAIL - - [Title element in SVG document] - expected: FAIL - - [Title element not child of SVG root] - expected: FAIL - diff --git a/testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-09.html b/testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-09.html new file mode 100644 index 0000000000..a3273f626c --- /dev/null +++ b/testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-09.html @@ -0,0 +1,97 @@ + + + + +
+ diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index 780f36f88c..cecfb20df7 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -68,6 +68,7 @@ #include "mozilla/dom/DOMJSClass.h" #include "mozilla/dom/ProfileTimelineMarkerBinding.h" #include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "jsprf.h" #include "js/Debug.h" @@ -468,6 +469,10 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSRuntime* aParentRuntime, }; SetDOMCallbacks(mJSRuntime, &DOMcallbacks); +#ifdef SPIDERMONKEY_PROMISE + JS::SetEnqueuePromiseJobCallback(mJSRuntime, EnqueuePromiseJobCallback, this); +#endif // SPIDERMONKEY_PROMISE + JS::dbg::SetDebuggerMallocSizeOf(mJSRuntime, moz_malloc_size_of); nsCycleCollector_registerJSRuntime(this); @@ -864,6 +869,45 @@ CycleCollectedJSRuntime::ContextCallback(JSContext* aContext, return self->CustomContextCallback(aContext, aOperation); } +class PromiseJobRunnable final : public nsRunnable +{ +public: + PromiseJobRunnable(JSContext* aCx, JS::HandleObject aCallback) + : mCallback(new PromiseJobCallback(aCx, aCallback, nullptr)) + { + } + + virtual ~PromiseJobRunnable() + { + } + +protected: + NS_IMETHOD + Run() override + { + mCallback->Call(); + return NS_OK; + } + +private: + RefPtr mCallback; +}; + +/* static */ +bool +CycleCollectedJSRuntime::EnqueuePromiseJobCallback(JSContext* aCx, + JS::HandleObject aJob, + void* aData) +{ + CycleCollectedJSRuntime* self = static_cast(aData); + MOZ_ASSERT(JS_GetRuntime(aCx) == self->Runtime()); + MOZ_ASSERT(Get() == self); + + nsCOMPtr runnable = new PromiseJobRunnable(aCx, aJob); + self->GetPromiseMicroTaskQueue().push(runnable); + return true; +} + struct JsGcTracer : public TraceCallbacks { virtual void Trace(JS::Heap* aPtr, const char* aName, diff --git a/xpcom/base/CycleCollectedJSRuntime.h b/xpcom/base/CycleCollectedJSRuntime.h index 3085dfa5ab..8cf8137c25 100644 --- a/xpcom/base/CycleCollectedJSRuntime.h +++ b/xpcom/base/CycleCollectedJSRuntime.h @@ -210,6 +210,9 @@ private: static void LargeAllocationFailureCallback(void* aData); static bool ContextCallback(JSContext* aCx, unsigned aOperation, void* aData); + static bool EnqueuePromiseJobCallback(JSContext* aCx, + JS::HandleObject aJob, + void* aData); virtual void TraceNativeBlackRoots(JSTracer* aTracer) { }; void TraceNativeGrayRoots(JSTracer* aTracer); From 98491d06875a295d8f402bbf1a3082cd7e5f262b Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 9 Nov 2023 09:50:40 +0800 Subject: [PATCH 05/12] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 382721 - Part 1: Fix spacing of simple 2px dotted border without radius. r=jrmuizel (8e83589f45) - Bug 382721 - Part 2: Split constants for border rendering to BorderConsts.h. r=jrmuizel (88ae5aee7f) - Bug 1237805 part 1 - [css-grid] Remove all empty 'auto-fit' tracks, not just those at the end. r=dholbert (1c0530b87b) - Bug 1237805 part 2 - [css-grid] 'auto-fit' reftest removing empty start/middle tracks. (3bfc688e2b) - Bug 1239036 - [css-grid] Deal with implicit tracks when computing grid-template-{columns,rows}. r=dholbert (cf36d9193a) - Bug 1239036 - [css-grid] Tests. (4aaec0499f) - Bug 1238294 part 1 - [css-grid] Make GridLineEdge() a method on the Tracks class rather than a static function (idempotent change). r=dholbert (ab81994ec6) - Bug 1238294 part 2 - [css-grid] Treat any gaps at the grid edges as "line thickness" so they behave the same as gaps between tracks for positioning areas. r=dholbert (fafdc1ceef) - Bug 1230695 - [css-grid] More abs.pos. grid alignment reftests. (330770cf1a) - Bug 1238294 part 3 - [css-grid] Add/tweak reftests for new behavior of gaps at the grid edges. (58ff8670a1) - Bug 1240795 - [css-grid] Refactor GetComputedTemplateColumns/Rows to return a self-contained value. r=dholbert (9c5e68418f) - Bug 1229739 - Use the color of shadow if available for drawing emphasis marks in shadow. r=jfkthame (c19c3deb1c) - Bug 1191597 part 3 - Convert fullscreen-api-keys to a browser chrome test. r=smaug (ad740d4c4c) - Bug 1174575 - Part 1: Define CSSPseudoElement interface. r=birtles, r=smaug (1a304d59c4) - Bug 1214536 - Part 1: Use unrestricted double for iterations. r=birtles (0dbb02e423) - Bug 1214536 - Part 2: Replace mIterationCount in layers::Animation. r=birtles (8a573046f5) - Bug 1214536 - Part 3: Store the original value of fill. r=birtles (df548c244a) - Bug 1214536 - Part 4: Use OwingUnrestrictedDoubleOrString for duration. r=birtles (567cfd1555) - Bug 1214536 - Part 5: Add AnimationEffectTimingReadOnly interface. r=birtles, r=smaug (47138ec7f0) - Bug 1214536 - Part 6: Revise AnimationTiming::operator==. r=birtles (616fc2c711) - Bug 1214536 - Part 7: Rename AnimationTiming as TimingParams. r=birtles, r=smaug (d7de0ec72b) - Bug 1214536 - Part 8: Add an operator=() for TimingParams. r=birtles (bfe22c6501) - Bug 1215406 - Part 6: Test. r=birtles (3f16796304) - Bug 1214536 - Part 9: Test. r=birtles (526419cc1d) - Bug 1147673 - Unadjust clip before intersecting it with the scroll clip. r=botond (85cd06d2d5) - Bug 1147673 - Determine more accurately whether an async transform affects a layer's clip rect. r=kats (8ce7d1e887) - Bug 1096773 part 1 - Make the frames argument to the KeyframeEffectReadOnly constructor NOT optional; r=bz (6e63b08671) - Bug 1096773 part 2 - Add a KeyframeEffectReadOnly constructor that takes a TimingParams argument; r=boris (24971306e6) - Bug 1096773 part 3 - Implement Animatable.animate(); r=bz (9d95ea800e) - Bug 1096773 part 4 - Add tests for Animatable.animate(); r=bz (03b866b2d8) - Bug 1179627 - Part 1: Implement Animation.id. r=smaug, r=birtles (51bbed6e9d) - Bug 1096774 - Part 1: Implement Animation Constructor. r=birtles, r=smaug (e09d7fbd7c) - Bug 1179627 - Part 2: Add animation.id for CSS animations test files. r=bbirtles, r=hiikezoe (329ea31f33) - Bug 1096774 - Part 2: Fix crash if animation has no timeline. r=birtles (d989b44866) - Bug 1096774 - Part 3: Tests for Animation Contructor. r=birtles (23786774bc) - Bug 1240265 - Annotate intentional switch fallthroughs in dom/. r=mrbkap (ffd9da3d5f) - Bug 1227458. Make setAttributeNode be an alias for setAttributeNodeNS and setNamedItem on the attribute map be an alias for setNamedItemNS. r=smaug (f804d28b93) - Bug 1226091 - Use MayHaveAnimations in Element::UnbindFromTree; r=smaug (1ec85f75b3) --- dom/animation/Animation.cpp | 37 +++- dom/animation/Animation.h | 10 +- dom/animation/AnimationEffectReadonly.h | 7 +- .../AnimationEffectTimingReadOnly.cpp | 88 ++++++++ dom/animation/AnimationEffectTimingReadOnly.h | 103 +++++++++ dom/animation/CSSPseudoElement.cpp | 44 ++++ dom/animation/CSSPseudoElement.h | 56 +++++ dom/animation/ComputedTimingFunction.h | 2 +- dom/animation/KeyframeEffect.cpp | 188 ++++++---------- dom/animation/KeyframeEffect.h | 100 +++++---- dom/animation/PendingAnimationTracker.cpp | 1 + dom/animation/moz.build | 4 + dom/animation/test/chrome.ini | 3 + .../test/chrome/file_animate_xrays.html | 19 ++ .../test/chrome/test_animate_xrays.html | 31 +++ .../test/chrome/test_animation_observers.html | 21 ++ .../css-animations/file_animation-id.html | 27 +++ .../css-animations/test_animation-id.html | 15 ++ dom/animation/test/mochitest.ini | 5 + dom/base/Element.cpp | 65 +++++- dom/base/Element.h | 7 + dom/base/nsDOMAttributeMap.cpp | 34 ++- dom/base/nsDOMAttributeMap.h | 17 +- dom/base/nsDocument.cpp | 3 + dom/base/nsGlobalWindow.cpp | 1 + dom/base/nsXMLContentSerializer.cpp | 2 +- dom/base/test/test_bug1075702.html | 2 +- dom/cache/AutoUtils.cpp | 1 + dom/html/HTMLInputElement.cpp | 3 +- dom/html/test/browser.ini | 2 + dom/html/test/browser_fullscreen-api-keys.js | 164 ++++++++++++++ dom/html/test/file_fullscreen-plugins.html | 8 + dom/html/test/mochitest.ini | 1 - dom/html/test/test_fullscreen-api.html | 1 - dom/storage/DOMStorageDBThread.cpp | 2 +- .../mochitest/general/test_interfaces.html | 4 + dom/webidl/Animatable.webidl | 8 + dom/webidl/Animation.webidl | 5 +- dom/webidl/AnimationEffectReadonly.webidl | 5 +- .../AnimationEffectTimingReadOnly.webidl | 23 ++ dom/webidl/CSSPseudoElement.webidl | 25 +++ dom/webidl/KeyframeEffect.webidl | 16 +- dom/webidl/NamedNodeMap.webidl | 2 +- dom/webidl/moz.build | 2 + dom/workers/ScriptLoader.cpp | 2 +- .../composite/AsyncCompositionManager.cpp | 76 ++++--- .../composite/AsyncCompositionManager.h | 7 +- gfx/layers/ipc/LayersMessages.ipdlh | 2 +- layout/base/BorderConsts.h | 25 +++ layout/base/nsCSSRendering.cpp | 1 + layout/base/nsCSSRenderingBorders.cpp | 3 +- layout/base/nsCSSRenderingBorders.h | 16 -- layout/base/nsDisplayList.cpp | 8 +- layout/generic/nsGridContainerFrame.cpp | 155 +++++++------ layout/generic/nsGridContainerFrame.h | 82 +++---- layout/generic/nsTextFrame.cpp | 8 +- layout/generic/nsTextFrame.h | 1 + .../css-grid/grid-abspos-items-013-ref.html | 133 ++++++++++++ .../css-grid/grid-abspos-items-013.html | 120 +++++++++++ ...rid-placement-abspos-implicit-001-ref.html | 6 +- .../grid-placement-abspos-implicit-001.html | 3 +- .../grid-repeat-auto-fill-fit-005-ref.html | 7 +- .../grid-repeat-auto-fill-fit-007-ref.html | 177 +++++++++++++++ .../grid-repeat-auto-fill-fit-007.html | 195 +++++++++++++++++ .../grid-repeat-auto-fill-fit-008-ref.html | 194 +++++++++++++++++ .../grid-repeat-auto-fill-fit-008.html | 204 ++++++++++++++++++ layout/reftests/css-grid/reftest.list | 3 + layout/style/nsAnimationManager.cpp | 23 +- layout/style/nsAnimationManager.h | 4 +- layout/style/nsComputedDOMStyle.cpp | 143 +++++++----- layout/style/nsComputedDOMStyle.h | 3 +- layout/style/nsTransitionManager.cpp | 15 +- layout/style/nsTransitionManager.h | 4 +- .../style/test/test_grid_computed_values.html | 23 ++ modules/libpref/init/all.js | 3 + testing/web-platform/meta/MANIFEST.json | 12 ++ .../animatable/animate.html.ini | 8 + .../animation/constructor.html.ini | 13 ++ .../web-animations/animatable/animate.html | 126 +++++++++++ .../web-animations/animation/constructor.html | 60 ++++++ .../keyframe-effect/constructor.html | 180 ++++++++++++++++ .../keyframe-effect/getComputedTiming.html | 195 +++++++++++++++++ .../tests/web-animations/testcommon.js | 2 +- 83 files changed, 2917 insertions(+), 489 deletions(-) create mode 100644 dom/animation/AnimationEffectTimingReadOnly.cpp create mode 100644 dom/animation/AnimationEffectTimingReadOnly.h create mode 100644 dom/animation/CSSPseudoElement.cpp create mode 100644 dom/animation/CSSPseudoElement.h create mode 100644 dom/animation/test/chrome/file_animate_xrays.html create mode 100644 dom/animation/test/chrome/test_animate_xrays.html create mode 100644 dom/animation/test/css-animations/file_animation-id.html create mode 100644 dom/animation/test/css-animations/test_animation-id.html create mode 100644 dom/html/test/browser_fullscreen-api-keys.js create mode 100644 dom/webidl/AnimationEffectTimingReadOnly.webidl create mode 100644 dom/webidl/CSSPseudoElement.webidl create mode 100644 layout/base/BorderConsts.h create mode 100644 layout/reftests/css-grid/grid-abspos-items-013-ref.html create mode 100644 layout/reftests/css-grid/grid-abspos-items-013.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-007.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-008-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-008.html create mode 100644 testing/web-platform/meta/web-animations/animatable/animate.html.ini create mode 100644 testing/web-platform/meta/web-animations/animation/constructor.html.ini create mode 100644 testing/web-platform/tests/web-animations/animatable/animate.html create mode 100644 testing/web-platform/tests/web-animations/animation/constructor.html create mode 100644 testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html diff --git a/dom/animation/Animation.cpp b/dom/animation/Animation.cpp index d13ff1c3b3..a693117ccf 100644 --- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -81,6 +81,41 @@ namespace { // Animation interface: // // --------------------------------------------------------------------------- +/* static */ already_AddRefed +Animation::Constructor(const GlobalObject& aGlobal, + KeyframeEffectReadOnly* aEffect, + AnimationTimeline* aTimeline, + ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr animation = new Animation(global); + + if (!aTimeline) { + // Bug 1096776: We do not support null timeline yet. + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + if (!aEffect) { + // Bug 1049975: We do not support null effect yet. + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + animation->SetTimeline(aTimeline); + animation->SetEffect(aEffect); + + return animation.forget(); +} + +void +Animation::SetId(const nsAString& aId) +{ + if (mId == aId) { + return; + } + mId = aId; + nsNodeUtils::AnimationChanged(this); +} void Animation::SetEffect(KeyframeEffectReadOnly* aEffect) @@ -1132,7 +1167,7 @@ Animation::EffectEnd() const return StickyTimeDuration(0); } - return mEffect->Timing().mDelay + return mEffect->SpecifiedTiming().mDelay + mEffect->GetComputedTiming().mActiveDuration; } diff --git a/dom/animation/Animation.h b/dom/animation/Animation.h index e1f4995d41..1852bc8c4e 100644 --- a/dom/animation/Animation.h +++ b/dom/animation/Animation.h @@ -90,7 +90,13 @@ public: }; // Animation interface methods - + static already_AddRefed + Constructor(const GlobalObject& aGlobal, + KeyframeEffectReadOnly* aEffect, + AnimationTimeline* aTimeline, + ErrorResult& aRv); + void GetId(nsAString& aResult) const { aResult = mId; } + void SetId(const nsAString& aId); KeyframeEffectReadOnly* GetEffect() const { return mEffect; } void SetEffect(KeyframeEffectReadOnly* aEffect); AnimationTimeline* GetTimeline() const { return mTimeline; } @@ -422,6 +428,8 @@ protected: // in that case mFinished is immediately reset to represent a new current // finished promise. bool mFinishedIsResolved; + + nsString mId; }; } // namespace dom diff --git a/dom/animation/AnimationEffectReadonly.h b/dom/animation/AnimationEffectReadonly.h index 7c38ca8bc3..016e4d979e 100644 --- a/dom/animation/AnimationEffectReadonly.h +++ b/dom/animation/AnimationEffectReadonly.h @@ -14,6 +14,7 @@ namespace mozilla { namespace dom { +class AnimationEffectTimingReadOnly; struct ComputedTimingProperties; class AnimationEffectReadOnly : public nsISupports, @@ -30,9 +31,9 @@ public: nsISupports* GetParentObject() const { return mParent; } - virtual void GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const - { - } + virtual already_AddRefed Timing() const = 0; + + virtual void GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const = 0; protected: virtual ~AnimationEffectReadOnly() = default; diff --git a/dom/animation/AnimationEffectTimingReadOnly.cpp b/dom/animation/AnimationEffectTimingReadOnly.cpp new file mode 100644 index 0000000000..ff574b3bdd --- /dev/null +++ b/dom/animation/AnimationEffectTimingReadOnly.cpp @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +#include "mozilla/dom/AnimationEffectTimingReadOnly.h" + +#include "mozilla/dom/AnimatableBinding.h" +#include "mozilla/dom/AnimationEffectTimingReadOnlyBinding.h" +#include "mozilla/dom/KeyframeEffectBinding.h" + +namespace mozilla { + +TimingParams::TimingParams(const dom::AnimationEffectTimingProperties& aRhs) + : mDuration(aRhs.mDuration) + , mDelay(TimeDuration::FromMilliseconds(aRhs.mDelay)) + , mIterations(aRhs.mIterations) + , mDirection(aRhs.mDirection) + , mFill(aRhs.mFill) +{ +} + +TimingParams::TimingParams(double aDuration) +{ + mDuration.SetAsUnrestrictedDouble() = aDuration; +} + +/* static */ TimingParams +TimingParams::FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) +{ + if (aOptions.IsUnrestrictedDouble()) { + return TimingParams(aOptions.GetAsUnrestrictedDouble()); + } else { + MOZ_ASSERT(aOptions.IsKeyframeEffectOptions()); + return TimingParams(aOptions.GetAsKeyframeEffectOptions()); + } +} + +/* static */ TimingParams +TimingParams::FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) +{ + if (aOptions.IsUnrestrictedDouble()) { + return TimingParams(aOptions.GetAsUnrestrictedDouble()); + } else { + MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions()); + return TimingParams(aOptions.GetAsKeyframeAnimationOptions()); + } +} + +bool +TimingParams::operator==(const TimingParams& aOther) const +{ + bool durationEqual; + if (mDuration.IsUnrestrictedDouble()) { + durationEqual = aOther.mDuration.IsUnrestrictedDouble() && + (mDuration.GetAsUnrestrictedDouble() == + aOther.mDuration.GetAsUnrestrictedDouble()); + } else { + // We consider all string values and uninitialized values as meaning "auto". + // Since mDuration is either a string or uninitialized, we consider it equal + // if aOther.mDuration is also either a string or uninitialized. + durationEqual = !aOther.mDuration.IsUnrestrictedDouble(); + } + return durationEqual && + mDelay == aOther.mDelay && + mIterations == aOther.mIterations && + mDirection == aOther.mDirection && + mFill == aOther.mFill; +} + +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationEffectTimingReadOnly, mParent) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AnimationEffectTimingReadOnly, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AnimationEffectTimingReadOnly, Release) + +JSObject* +AnimationEffectTimingReadOnly::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return AnimationEffectTimingReadOnlyBinding::Wrap(aCx, this, aGivenProto); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/animation/AnimationEffectTimingReadOnly.h b/dom/animation/AnimationEffectTimingReadOnly.h new file mode 100644 index 0000000000..03873d5300 --- /dev/null +++ b/dom/animation/AnimationEffectTimingReadOnly.h @@ -0,0 +1,103 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_AnimationEffectTimingReadOnly_h +#define mozilla_dom_AnimationEffectTimingReadOnly_h + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/UnionTypes.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +// X11 has a #define for None +#ifdef None +#undef None +#endif +#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for FillMode + // and PlaybackDirection + +namespace mozilla { + +namespace dom { +struct AnimationEffectTimingProperties; +class UnrestrictedDoubleOrKeyframeEffectOptions; +class UnrestrictedDoubleOrKeyframeAnimationOptions; +} + +struct TimingParams +{ + TimingParams() = default; + explicit TimingParams( + const dom::AnimationEffectTimingProperties& aTimingProperties); + explicit TimingParams(double aDuration); + + static TimingParams FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions); + static TimingParams FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions); + + // The unitialized state of mDuration represents "auto". + // Bug 1237173: We will replace this with Maybe. + dom::OwningUnrestrictedDoubleOrString mDuration; + TimeDuration mDelay; // Initializes to zero + double mIterations = 1.0; // Can be NaN, negative, +/-Infinity + dom::PlaybackDirection mDirection = dom::PlaybackDirection::Normal; + dom::FillMode mFill = dom::FillMode::Auto; + + bool operator==(const TimingParams& aOther) const; + bool operator!=(const TimingParams& aOther) const + { + return !(*this == aOther); + } +}; + + +namespace dom { + +class AnimationEffectTimingReadOnly : public nsWrapperCache +{ +public: + AnimationEffectTimingReadOnly() = default; + explicit AnimationEffectTimingReadOnly(const TimingParams& aTiming) + : mTiming(aTiming) { } + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationEffectTimingReadOnly) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AnimationEffectTimingReadOnly) + +protected: + virtual ~AnimationEffectTimingReadOnly() = default; + +public: + nsISupports* GetParentObject() const { return mParent; } + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + double Delay() const { return mTiming.mDelay.ToMilliseconds(); } + double EndDelay() const { return 0.0; } + FillMode Fill() const { return mTiming.mFill; } + double IterationStart() const { return 0.0; } + double Iterations() const { return mTiming.mIterations; } + void GetDuration(OwningUnrestrictedDoubleOrString& aRetVal) const + { + aRetVal = mTiming.mDuration; + } + PlaybackDirection Direction() const { return mTiming.mDirection; } + void GetEasing(nsString& aRetVal) const { aRetVal.AssignLiteral("linear"); } + + const TimingParams& AsTimingParams() const { return mTiming; } + void SetTimingParams(const TimingParams& aTiming) { mTiming = aTiming; } + +protected: + nsCOMPtr mParent; + TimingParams mTiming; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_AnimationEffectTimingReadOnly_h diff --git a/dom/animation/CSSPseudoElement.cpp b/dom/animation/CSSPseudoElement.cpp new file mode 100644 index 0000000000..5d67987497 --- /dev/null +++ b/dom/animation/CSSPseudoElement.cpp @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#include "mozilla/dom/CSSPseudoElement.h" +#include "mozilla/dom/CSSPseudoElementBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(CSSPseudoElement) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CSSPseudoElement, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CSSPseudoElement, Release) + +JSObject* +CSSPseudoElement::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return CSSPseudoElementBinding::Wrap(aCx, this, aGivenProto); +} + +void +CSSPseudoElement::GetAnimations(nsTArray>& aRetVal) +{ + // Bug 1234403: Implement this API. + NS_NOTREACHED("CSSPseudoElement::GetAnimations() is not implemented yet."); +} + +already_AddRefed +CSSPseudoElement::Animate( + JSContext* aContext, + JS::Handle aFrames, + const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, + ErrorResult& aError) +{ + // Bug 1241784: Implement this API. + NS_NOTREACHED("CSSPseudoElement::Animate() is not implemented yet."); + return nullptr; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/animation/CSSPseudoElement.h b/dom/animation/CSSPseudoElement.h new file mode 100644 index 0000000000..992c4a8b24 --- /dev/null +++ b/dom/animation/CSSPseudoElement.h @@ -0,0 +1,56 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_CSSPseudoElement_h +#define mozilla_dom_CSSPseudoElement_h + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +class Animation; +class Element; +class UnrestrictedDoubleOrKeyframeAnimationOptions; + +class CSSPseudoElement final : public nsWrapperCache +{ +public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CSSPseudoElement) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CSSPseudoElement) + +protected: + virtual ~CSSPseudoElement() = default; + +public: + ParentObject GetParentObject() const + { + // This will be implemented in later patch. + return ParentObject(nullptr, nullptr); + } + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + void GetType(nsString& aRetVal) const { } + already_AddRefed ParentElement() const { return nullptr; } + + void GetAnimations(nsTArray>& aRetVal); + already_AddRefed + Animate(JSContext* aContext, + JS::Handle aFrames, + const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, + ErrorResult& aError); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_CSSPseudoElement_h diff --git a/dom/animation/ComputedTimingFunction.h b/dom/animation/ComputedTimingFunction.h index fa1b335b3a..6831d65b59 100644 --- a/dom/animation/ComputedTimingFunction.h +++ b/dom/animation/ComputedTimingFunction.h @@ -50,4 +50,4 @@ private: } // namespace mozilla -#endif // mozilla_dom_AnimationEffectReadOnly_h +#endif // mozilla_ComputedTimingFunction_h diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index 843f1ae53f..f6de95ce43 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -6,7 +6,6 @@ #include "mozilla/dom/KeyframeEffect.h" -#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" #include "mozilla/dom/KeyframeEffectBinding.h" #include "mozilla/dom/PropertyIndexedKeyframesBinding.h" #include "mozilla/AnimationUtils.h" @@ -24,32 +23,19 @@ namespace mozilla { -bool -AnimationTiming::FillsForwards() const -{ - return mFillMode == dom::FillMode::Both || - mFillMode == dom::FillMode::Forwards; -} - -bool -AnimationTiming::FillsBackwards() const -{ - return mFillMode == dom::FillMode::Both || - mFillMode == dom::FillMode::Backwards; -} - // Helper functions for generating a ComputedTimingProperties dictionary static void GetComputedTimingDictionary(const ComputedTiming& aComputedTiming, const Nullable& aLocalTime, - const AnimationTiming& aTiming, + const TimingParams& aTiming, dom::ComputedTimingProperties& aRetVal) { // AnimationEffectTimingProperties aRetVal.mDelay = aTiming.mDelay.ToMilliseconds(); - aRetVal.mFill = aTiming.mFillMode; - aRetVal.mIterations = aTiming.mIterationCount; - aRetVal.mDuration.SetAsUnrestrictedDouble() = aTiming.mIterationDuration.ToMilliseconds(); + aRetVal.mFill = aComputedTiming.mFill; + aRetVal.mIterations = aComputedTiming.mIterations; + aRetVal.mDuration.SetAsUnrestrictedDouble() = + aComputedTiming.mDuration.ToMilliseconds(); aRetVal.mDirection = aTiming.mDirection; // ComputedTimingProperties @@ -89,14 +75,15 @@ KeyframeEffectReadOnly::KeyframeEffectReadOnly( nsIDocument* aDocument, Element* aTarget, nsCSSPseudoElements::Type aPseudoType, - const AnimationTiming& aTiming) + const TimingParams& aTiming) : AnimationEffectReadOnly(aDocument) , mTarget(aTarget) - , mTiming(aTiming) , mPseudoType(aPseudoType) , mInEffectOnLastAnimationTimingUpdate(false) { MOZ_ASSERT(aTarget, "null animation target is not yet supported"); + + mTiming = new AnimationEffectTimingReadOnly(aTiming); } JSObject* @@ -118,13 +105,20 @@ KeyframeEffectReadOnly::Composite() const return CompositeOperation::Replace; } -void -KeyframeEffectReadOnly::SetTiming(const AnimationTiming& aTiming) +already_AddRefed +KeyframeEffectReadOnly::Timing() const { - if (mTiming == aTiming) { + RefPtr temp(mTiming); + return temp.forget(); +} + +void +KeyframeEffectReadOnly::SetSpecifiedTiming(const TimingParams& aTiming) +{ + if (mTiming->AsTimingParams() == aTiming) { return; } - mTiming = aTiming; + mTiming->SetTimingParams(aTiming); if (mAnimation) { mAnimation->NotifyEffectTimingUpdated(); } @@ -205,31 +199,36 @@ void KeyframeEffectReadOnly::GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const { const Nullable currentTime = GetLocalTime(); - GetComputedTimingDictionary(GetComputedTimingAt(currentTime, mTiming), + GetComputedTimingDictionary(GetComputedTimingAt(currentTime, + SpecifiedTiming()), currentTime, - mTiming, + SpecifiedTiming(), aRetVal); } ComputedTiming KeyframeEffectReadOnly::GetComputedTimingAt( const Nullable& aLocalTime, - const AnimationTiming& aTiming) + const TimingParams& aTiming) { - const TimeDuration zeroDuration; - - // Currently we expect negative durations to be picked up during CSS - // parsing but when we start receiving timing parameters from other sources - // we will need to clamp negative durations here. - // For now, if we're hitting this it probably means we're overflowing - // integer arithmetic in mozilla::TimeStamp. - MOZ_ASSERT(aTiming.mIterationDuration >= zeroDuration, - "Expecting iteration duration >= 0"); + const StickyTimeDuration zeroDuration; // Always return the same object to benefit from return-value optimization. ComputedTiming result; - result.mActiveDuration = ActiveDuration(aTiming); + if (aTiming.mDuration.IsUnrestrictedDouble()) { + double durationMs = aTiming.mDuration.GetAsUnrestrictedDouble(); + if (!IsNaN(durationMs) && durationMs >= 0.0f) { + result.mDuration = StickyTimeDuration::FromMilliseconds(durationMs); + } + } + result.mIterations = IsNaN(aTiming.mIterations) || aTiming.mIterations < 0.0f ? + 1.0f : + aTiming.mIterations; + result.mActiveDuration = ActiveDuration(result.mDuration, result.mIterations); + result.mFill = aTiming.mFill == dom::FillMode::Auto ? + dom::FillMode::None : + aTiming.mFill; // The default constructor for ComputedTiming sets all other members to // values consistent with an animation that has not been sampled. @@ -247,7 +246,7 @@ KeyframeEffectReadOnly::GetComputedTimingAt( StickyTimeDuration activeTime; if (localTime >= aTiming.mDelay + result.mActiveDuration) { result.mPhase = ComputedTiming::AnimationPhase::After; - if (!aTiming.FillsForwards()) { + if (!result.FillsForwards()) { // The animation isn't active or filling at this time. result.mProgress.SetNull(); return result; @@ -255,12 +254,11 @@ KeyframeEffectReadOnly::GetComputedTimingAt( activeTime = result.mActiveDuration; // Note that infinity == floor(infinity) so this will also be true when we // have finished an infinitely repeating animation of zero duration. - isEndOfFinalIteration = - aTiming.mIterationCount != 0.0 && - aTiming.mIterationCount == floor(aTiming.mIterationCount); + isEndOfFinalIteration = result.mIterations != 0.0 && + result.mIterations == floor(result.mIterations); } else if (localTime < aTiming.mDelay) { result.mPhase = ComputedTiming::AnimationPhase::Before; - if (!aTiming.FillsBackwards()) { + if (!result.FillsBackwards()) { // The animation isn't active or filling at this time. result.mProgress.SetNull(); return result; @@ -275,19 +273,19 @@ KeyframeEffectReadOnly::GetComputedTimingAt( // Get the position within the current iteration. StickyTimeDuration iterationTime; - if (aTiming.mIterationDuration != zeroDuration) { + if (result.mDuration != zeroDuration) { iterationTime = isEndOfFinalIteration - ? StickyTimeDuration(aTiming.mIterationDuration) - : activeTime % aTiming.mIterationDuration; + ? result.mDuration + : activeTime % result.mDuration; } /* else, iterationTime is zero */ // Determine the 0-based index of the current iteration. if (isEndOfFinalIteration) { result.mCurrentIteration = - aTiming.mIterationCount == NS_IEEEPositiveInfinity() + IsInfinite(result.mIterations) // Positive Infinity? ? UINT64_MAX // In GetComputedTimingDictionary(), we will convert this // into Infinity. - : static_cast(aTiming.mIterationCount) - 1; + : static_cast(result.mIterations) - 1; } else if (activeTime == zeroDuration) { // If the active time is zero we're either in the first iteration // (including filling backwards) or we have finished an animation with an @@ -295,11 +293,11 @@ KeyframeEffectReadOnly::GetComputedTimingAt( // the exact end of an iteration since we deal with that above). result.mCurrentIteration = result.mPhase == ComputedTiming::AnimationPhase::After - ? static_cast(aTiming.mIterationCount) // floor + ? static_cast(result.mIterations) // floor : 0; } else { result.mCurrentIteration = - static_cast(activeTime / aTiming.mIterationDuration); // floor + static_cast(activeTime / result.mDuration); // floor } // Normalize the iteration time into a fraction of the iteration duration. @@ -308,15 +306,15 @@ KeyframeEffectReadOnly::GetComputedTimingAt( } else if (result.mPhase == ComputedTiming::AnimationPhase::After) { double progress = isEndOfFinalIteration ? 1.0 - : fmod(aTiming.mIterationCount, 1.0f); + : fmod(result.mIterations, 1.0); result.mProgress.SetValue(progress); } else { // We are in the active phase so the iteration duration can't be zero. - MOZ_ASSERT(aTiming.mIterationDuration != zeroDuration, + MOZ_ASSERT(result.mDuration != zeroDuration, "In the active phase of a zero-duration animation?"); - double progress = aTiming.mIterationDuration == TimeDuration::Forever() + double progress = result.mDuration == StickyTimeDuration::Forever() ? 0.0 - : iterationTime / aTiming.mIterationDuration; + : iterationTime / result.mDuration; result.mProgress.SetValue(progress); } @@ -345,19 +343,19 @@ KeyframeEffectReadOnly::GetComputedTimingAt( } StickyTimeDuration -KeyframeEffectReadOnly::ActiveDuration(const AnimationTiming& aTiming) +KeyframeEffectReadOnly::ActiveDuration(const StickyTimeDuration& aIterationDuration, + double aIterationCount) { - if (aTiming.mIterationCount == mozilla::PositiveInfinity()) { + if (IsInfinite(aIterationCount)) { // An animation that repeats forever has an infinite active duration // unless its iteration duration is zero, in which case it has a zero // active duration. const StickyTimeDuration zeroDuration; - return aTiming.mIterationDuration == zeroDuration - ? zeroDuration - : StickyTimeDuration::Forever(); + return aIterationDuration == zeroDuration ? + zeroDuration : + StickyTimeDuration::Forever(); } - return StickyTimeDuration( - aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount)); + return aIterationDuration.MultDouble(aIterationCount); } // https://w3c.github.io/web-animations/#in-play @@ -641,55 +639,6 @@ DumpAnimationProperties(nsTArray& aAnimationProperties) } #endif -// Extract an iteration duration from an UnrestrictedDoubleOrXXX object. -template -static TimeDuration -GetIterationDuration(const T& aDuration) { - // Always return the same object to benefit from return-value optimization. - TimeDuration result; - if (aDuration.IsUnrestrictedDouble()) { - double durationMs = aDuration.GetAsUnrestrictedDouble(); - if (!IsNaN(durationMs) && durationMs >= 0.0f) { - result = TimeDuration::FromMilliseconds(durationMs); - } - } - // else, aDuration should be zero - return result; -} - -/* static */ AnimationTiming -KeyframeEffectReadOnly::ConvertKeyframeEffectOptions( - const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) -{ - AnimationTiming animationTiming; - - if (aOptions.IsKeyframeEffectOptions()) { - const KeyframeEffectOptions& opt = aOptions.GetAsKeyframeEffectOptions(); - - animationTiming.mIterationDuration = GetIterationDuration(opt.mDuration); - animationTiming.mDelay = TimeDuration::FromMilliseconds(opt.mDelay); - // FIXME: Covert mIterationCount to a valid value. - // Bug 1214536 should revise this and keep the original value, so - // AnimationTimingEffectReadOnly can get the original iterations. - animationTiming.mIterationCount = (IsNaN(opt.mIterations) || - opt.mIterations < 0.0f) ? - 1.0f : - opt.mIterations; - animationTiming.mDirection = opt.mDirection; - // FIXME: We should store original value. - animationTiming.mFillMode = (opt.mFill == FillMode::Auto) ? - FillMode::None : - opt.mFill; - } else { - animationTiming.mIterationDuration = GetIterationDuration(aOptions); - animationTiming.mDelay = TimeDuration(0); - animationTiming.mIterationCount = 1.0f; - animationTiming.mDirection = PlaybackDirection::Normal; - animationTiming.mFillMode = FillMode::None; - } - return animationTiming; -} - /** * A property and StyleAnimationValue pair. */ @@ -1666,7 +1615,7 @@ BuildAnimationPropertyListFromPropertyIndexedKeyframes( KeyframeEffectReadOnly::BuildAnimationPropertyList( JSContext* aCx, Element* aTarget, - const Optional>& aFrames, + JS::Handle aFrames, InfallibleTArray& aResult, ErrorResult& aRv) { @@ -1682,17 +1631,16 @@ KeyframeEffectReadOnly::BuildAnimationPropertyList( // we can look at the open-ended set of properties on a // PropertyIndexedKeyframes or Keyframe. - if (!aFrames.WasPassed() || !aFrames.Value().get()) { - // The argument was omitted, or was explicitly null. In both cases, - // the default dictionary value for PropertyIndexedKeyframes would - // result in no keyframes. + if (!aFrames) { + // The argument was explicitly null. In this case, the default dictionary + // value for PropertyIndexedKeyframes would result in no keyframes. return; } // At this point we know we have an object. We try to convert it to a // sequence first, and if that fails due to not being iterable, // we try to convert it to PropertyIndexedKeyframes. - JS::Rooted objectValue(aCx, JS::ObjectValue(*aFrames.Value())); + JS::Rooted objectValue(aCx, JS::ObjectValue(*aFrames)); JS::ForOfIterator iter(aCx); if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) { aRv.Throw(NS_ERROR_FAILURE); @@ -1713,8 +1661,8 @@ KeyframeEffectReadOnly::BuildAnimationPropertyList( KeyframeEffectReadOnly::Constructor( const GlobalObject& aGlobal, Element* aTarget, - const Optional>& aFrames, - const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, + JS::Handle aFrames, + const TimingParams& aTiming, ErrorResult& aRv) { if (!aTarget) { @@ -1723,8 +1671,6 @@ KeyframeEffectReadOnly::Constructor( return nullptr; } - AnimationTiming timing = ConvertKeyframeEffectOptions(aOptions); - InfallibleTArray animationProperties; BuildAnimationPropertyList(aGlobal.Context(), aTarget, aFrames, animationProperties, aRv); @@ -1736,7 +1682,7 @@ KeyframeEffectReadOnly::Constructor( RefPtr effect = new KeyframeEffectReadOnly(aTarget->OwnerDoc(), aTarget, nsCSSPseudoElements::ePseudo_NotPseudoElement, - timing); + aTiming); effect->mProperties = Move(animationProperties); return effect.forget(); } diff --git a/dom/animation/KeyframeEffect.h b/dom/animation/KeyframeEffect.h index c2978ee5f9..5e27301ac2 100644 --- a/dom/animation/KeyframeEffect.h +++ b/dom/animation/KeyframeEffect.h @@ -15,14 +15,17 @@ #include "mozilla/Attributes.h" #include "mozilla/ComputedTimingFunction.h" // ComputedTimingFunction #include "mozilla/LayerAnimationInfo.h" // LayerAnimations::kRecords +#include "mozilla/OwningNonNull.h" // OwningNonNull<...> #include "mozilla/StickyTimeDuration.h" #include "mozilla/StyleAnimationValue.h" #include "mozilla/TimeStamp.h" #include "mozilla/dom/AnimationEffectReadOnly.h" +#include "mozilla/dom/AnimationEffectTimingReadOnly.h" // TimingParams #include "mozilla/dom/Element.h" #include "mozilla/dom/KeyframeBinding.h" #include "mozilla/dom/Nullable.h" + struct JSContext; class nsCSSPropertySet; class nsIContent; @@ -36,41 +39,11 @@ struct AnimationCollection; class AnimValuesStyleRule; namespace dom { -struct ComputedTimingProperties; class UnrestrictedDoubleOrKeyframeEffectOptions; enum class IterationCompositeOperation : uint32_t; enum class CompositeOperation : uint32_t; } -/** - * Input timing parameters. - * - * Eventually this will represent all the input timing parameters specified - * by content but for now it encapsulates just the subset of those - * parameters passed to GetPositionInIteration. - */ -struct AnimationTiming -{ - TimeDuration mIterationDuration; - TimeDuration mDelay; - float mIterationCount; // mozilla::PositiveInfinity() means infinite - dom::PlaybackDirection mDirection; - dom::FillMode mFillMode; - - bool FillsForwards() const; - bool FillsBackwards() const; - bool operator==(const AnimationTiming& aOther) const { - return mIterationDuration == aOther.mIterationDuration && - mDelay == aOther.mDelay && - mIterationCount == aOther.mIterationCount && - mDirection == aOther.mDirection && - mFillMode == aOther.mFillMode; - } - bool operator!=(const AnimationTiming& aOther) const { - return !(*this == aOther); - } -}; - /** * Stores the results of calculating the timing properties of an animation * at a given sample time. @@ -88,6 +61,25 @@ struct ComputedTiming Nullable mProgress; // Zero-based iteration index (meaningless if mProgress is null). uint64_t mCurrentIteration = 0; + // Unlike TimingParams::mIterations, this value is + // guaranteed to be in the range [0, Infinity]. + double mIterations = 1.0; + StickyTimeDuration mDuration; + + // This is the computed fill mode so it is never auto + dom::FillMode mFill = dom::FillMode::None; + bool FillsForwards() const { + MOZ_ASSERT(mFill != dom::FillMode::Auto, + "mFill should not be Auto in ComputedTiming."); + return mFill == dom::FillMode::Both || + mFill == dom::FillMode::Forwards; + } + bool FillsBackwards() const { + MOZ_ASSERT(mFill != dom::FillMode::Auto, + "mFill should not be Auto in ComputedTiming."); + return mFill == dom::FillMode::Both || + mFill == dom::FillMode::Backwards; + } enum class AnimationPhase { Null, // Not sampled (null sample time) @@ -178,7 +170,7 @@ public: KeyframeEffectReadOnly(nsIDocument* aDocument, Element* aTarget, nsCSSPseudoElements::Type aPseudoType, - const AnimationTiming& aTiming); + const TimingParams& aTiming); NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadOnly, @@ -197,9 +189,23 @@ public: static already_AddRefed Constructor(const GlobalObject& aGlobal, Element* aTarget, - const Optional>& aFrames, + JS::Handle aFrames, const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, + ErrorResult& aRv) + { + return Constructor(aGlobal, aTarget, aFrames, + TimingParams::FromOptionsUnion(aOptions), aRv); + } + + // More generalized version for Animatable.animate. + // Not exposed to content. + static already_AddRefed + Constructor(const GlobalObject& aGlobal, + Element* aTarget, + JS::Handle aFrames, + const TimingParams& aTiming, ErrorResult& aRv); + Element* GetTarget() const { // Currently we never return animations from the API whose effect // targets a pseudo-element so this should never be called when @@ -227,9 +233,13 @@ public: aRetVal.AssignLiteral("distribute"); } - const AnimationTiming& Timing() const { return mTiming; } - AnimationTiming& Timing() { return mTiming; } - void SetTiming(const AnimationTiming& aTiming); + already_AddRefed Timing() const override; + + const TimingParams& SpecifiedTiming() const + { + return mTiming->AsTimingParams(); + } + void SetSpecifiedTiming(const TimingParams& aTiming); void NotifyAnimationTimingUpdated(); Nullable GetLocalTime() const; @@ -246,22 +256,25 @@ public: // (because it is not currently active and is not filling at this time). static ComputedTiming GetComputedTimingAt(const Nullable& aLocalTime, - const AnimationTiming& aTiming); + const TimingParams& aTiming); // Shortcut for that gets the computed timing using the current local time as // calculated from the timeline time. ComputedTiming - GetComputedTiming(const AnimationTiming* aTiming = nullptr) const + GetComputedTiming(const TimingParams* aTiming = nullptr) const { - return GetComputedTimingAt(GetLocalTime(), aTiming ? *aTiming : mTiming); + return GetComputedTimingAt(GetLocalTime(), + aTiming ? *aTiming : SpecifiedTiming()); } void GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const override; - // Return the duration of the active interval for the given timing parameters. + // Return the duration of the active interval for the given duration and + // iteration count. static StickyTimeDuration - ActiveDuration(const AnimationTiming& aTiming); + ActiveDuration(const StickyTimeDuration& aIterationDuration, + double aIterationCount); bool IsInPlay() const; bool IsCurrent() const; @@ -334,20 +347,17 @@ protected: // owning Animation's timing. void UpdateTargetRegistration(); - static AnimationTiming ConvertKeyframeEffectOptions( - const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions); - static void BuildAnimationPropertyList( JSContext* aCx, Element* aTarget, - const Optional>& aFrames, + JS::Handle aFrames, InfallibleTArray& aResult, ErrorResult& aRv); nsCOMPtr mTarget; RefPtr mAnimation; - AnimationTiming mTiming; + OwningNonNull mTiming; nsCSSPseudoElements::Type mPseudoType; InfallibleTArray mProperties; diff --git a/dom/animation/PendingAnimationTracker.cpp b/dom/animation/PendingAnimationTracker.cpp index 75033ec552..a97814a7c0 100644 --- a/dom/animation/PendingAnimationTracker.cpp +++ b/dom/animation/PendingAnimationTracker.cpp @@ -63,6 +63,7 @@ PendingAnimationTracker::TriggerPendingAnimationsOnNextTick(const TimeStamp& // itself on the next tick where it has a timeline. if (!timeline) { iter.Remove(); + continue; } // When the timeline's refresh driver is under test control, its values diff --git a/dom/animation/moz.build b/dom/animation/moz.build index e42a975d5b..f5ae4a93b9 100644 --- a/dom/animation/moz.build +++ b/dom/animation/moz.build @@ -10,7 +10,9 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] EXPORTS.mozilla.dom += [ 'Animation.h', 'AnimationEffectReadOnly.h', + 'AnimationEffectTimingReadOnly.h', 'AnimationTimeline.h', + 'CSSPseudoElement.h', 'DocumentTimeline.h', 'KeyframeEffect.h', ] @@ -29,10 +31,12 @@ EXPORTS.mozilla += [ UNIFIED_SOURCES += [ 'Animation.cpp', 'AnimationEffectReadOnly.cpp', + 'AnimationEffectTimingReadOnly.cpp', 'AnimationTimeline.cpp', 'AnimationUtils.cpp', 'AnimValuesStyleRule.cpp', 'ComputedTimingFunction.cpp', + 'CSSPseudoElement.cpp', 'DocumentTimeline.cpp', 'EffectCompositor.cpp', 'EffectSet.cpp', diff --git a/dom/animation/test/chrome.ini b/dom/animation/test/chrome.ini index 1ae7c74d7a..7bff7243a2 100644 --- a/dom/animation/test/chrome.ini +++ b/dom/animation/test/chrome.ini @@ -3,6 +3,9 @@ support-files = testcommon.js ../../imptests/testharness.js ../../imptests/testharnessreport.js +[chrome/test_animate_xrays.html] +# file_animate_xrays.html needs to go in mochitest.ini since it is served +# over HTTP [chrome/test_animation_observers.html] [chrome/test_restyles.html] [chrome/test_running_on_compositor.html] diff --git a/dom/animation/test/chrome/file_animate_xrays.html b/dom/animation/test/chrome/file_animate_xrays.html new file mode 100644 index 0000000000..8a68fc548f --- /dev/null +++ b/dom/animation/test/chrome/file_animate_xrays.html @@ -0,0 +1,19 @@ + + + + + + +
+ + diff --git a/dom/animation/test/chrome/test_animate_xrays.html b/dom/animation/test/chrome/test_animate_xrays.html new file mode 100644 index 0000000000..e636af4b68 --- /dev/null +++ b/dom/animation/test/chrome/test_animate_xrays.html @@ -0,0 +1,31 @@ + + + + + + + + +Mozilla Bug 1045994 +
+ + + diff --git a/dom/animation/test/chrome/test_animation_observers.html b/dom/animation/test/chrome/test_animation_observers.html index 28a8115fdf..5090609c58 100644 --- a/dom/animation/test/chrome/test_animation_observers.html +++ b/dom/animation/test/chrome/test_animation_observers.html @@ -763,6 +763,27 @@ function assert_records(expected, desc) { }); }); + addAsyncAnimTest("change_id", aOptions, function*() { + e.style.animation = "anim 100s"; + + var animation = div.getAnimations()[0]; + yield await_frame(); + assert_records([{ added: [animation], changed: [], removed: []}], + "records after creation"); + animation.id = "new id"; + yield await_frame(); + assert_records([{ added: [], changed: [animation], removed: []}], + "records after id is changed"); + + animation.id = "new id"; + yield await_frame(); + assert_records([], + "records after assigning same value with id"); + + e.style.animation = ""; + yield await_frame(); + }); + // Test that making a redundant change to currentTime while an Animation // is pause-pending still generates a change MutationRecord since setting // the currentTime to any value in this state aborts the pending pause. diff --git a/dom/animation/test/css-animations/file_animation-id.html b/dom/animation/test/css-animations/file_animation-id.html new file mode 100644 index 0000000000..9f2b117b70 --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-id.html @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/dom/animation/test/css-animations/test_animation-id.html b/dom/animation/test/css-animations/test_animation-id.html new file mode 100644 index 0000000000..c23501b8d6 --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-id.html @@ -0,0 +1,15 @@ + + + + +
+ + diff --git a/dom/animation/test/mochitest.ini b/dom/animation/test/mochitest.ini index dd64f9321c..7abf0b088d 100644 --- a/dom/animation/test/mochitest.ini +++ b/dom/animation/test/mochitest.ini @@ -1,6 +1,9 @@ [DEFAULT] +# Support files for chrome tests that we want to load over HTTP need +# to go in here, not chrome.ini. support-files = testcommon.js + chrome/file_animate_xrays.html [css-animations/test_animations-dynamic-changes.html] support-files = css-animations/file_animations-dynamic-changes.html @@ -14,6 +17,8 @@ support-files = css-animations/file_animation-currenttime.html support-files = css-animations/file_animation-finish.html [css-animations/test_animation-finished.html] support-files = css-animations/file_animation-finished.html +[css-animations/test_animation-id.html] +support-files = css-animations/file_animation-id.html [css-animations/test_animation-oncancel.html] support-files = css-animations/file_animation-oncancel.html [css-animations/test_animation-onfinish.html] diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 8cca2d5294..390c8bc476 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -21,6 +21,7 @@ #include "nsIContentInlines.h" #include "mozilla/dom/NodeInfo.h" #include "nsIDocumentInlines.h" +#include "mozilla/dom/DocumentTimeline.h" #include "nsIDOMNodeList.h" #include "nsIDOMDocument.h" #include "nsIContentIterator.h" @@ -51,6 +52,7 @@ #include "nsDOMString.h" #include "nsIScriptSecurityManager.h" #include "nsIDOMMutationEvent.h" +#include "mozilla/dom/AnimatableBinding.h" #include "mozilla/AnimationComparator.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/ContentEvents.h" @@ -1267,9 +1269,11 @@ Element::GetAttributeNode(const nsAString& aName) already_AddRefed Element::SetAttributeNode(Attr& aNewAttr, ErrorResult& aError) { + // XXXbz can we just remove this warning and the one in setAttributeNodeNS and + // alias setAttributeNode to setAttributeNodeNS? OwnerDoc()->WarnOnceAbout(nsIDocument::eSetAttributeNode); - return Attributes()->SetNamedItem(aNewAttr, aError); + return Attributes()->SetNamedItemNS(aNewAttr, aError); } already_AddRefed @@ -1812,8 +1816,7 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) // We need to delete the properties while we're still in document // (if we were in document). // FIXME (Bug 522599): Need a test for this. - //XXXsmaug this looks slow. - if (HasFlag(NODE_HAS_PROPERTIES)) { + if (MayHaveAnimations()) { DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty); DeleteProperty(nsGkAtoms::transitionsOfAfterProperty); DeleteProperty(nsGkAtoms::transitionsProperty); @@ -2966,7 +2969,7 @@ Element::PreHandleEventForLinks(EventChainPreVisitor& aVisitor) // Set the status bar similarly for mouseover and focus case eMouseOver: aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; - // FALL THROUGH + MOZ_FALLTHROUGH; case eFocus: { InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent(); if (!focusEvent || !focusEvent->isRefocus) { @@ -2981,7 +2984,7 @@ Element::PreHandleEventForLinks(EventChainPreVisitor& aVisitor) } case eMouseOut: aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; - // FALL THROUGH + MOZ_FALLTHROUGH; case eBlur: rv = LeaveLink(aVisitor.mPresContext); if (NS_SUCCEEDED(rv)) { @@ -3370,6 +3373,58 @@ Element::MozRequestPointerLock() OwnerDoc()->RequestPointerLock(this); } +already_AddRefed +Element::Animate(JSContext* aContext, + JS::Handle aFrames, + const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, + ErrorResult& aError) +{ + nsCOMPtr ownerGlobal = GetOwnerGlobal(); + if (!ownerGlobal) { + aError.Throw(NS_ERROR_FAILURE); + return nullptr; + } + GlobalObject global(aContext, ownerGlobal->GetGlobalJSObject()); + MOZ_ASSERT(!global.Failed()); + + // Wrap the aFrames object for the cross-compartment case. + JS::Rooted frames(aContext); + frames = aFrames; + Maybe ac; + if (js::GetContextCompartment(aContext) != + js::GetObjectCompartment(ownerGlobal->GetGlobalJSObject())) { + ac.emplace(aContext, ownerGlobal->GetGlobalJSObject()); + if (!JS_WrapObject(aContext, &frames)) { + return nullptr; + } + } + + // Bug 1211783: Use KeyframeEffect here (instead of KeyframeEffectReadOnly) + RefPtr effect = + KeyframeEffectReadOnly::Constructor(global, this, frames, + TimingParams::FromOptionsUnion(aOptions), aError); + if (aError.Failed()) { + return nullptr; + } + + RefPtr animation = + Animation::Constructor(global, effect, OwnerDoc()->Timeline(), aError); + if (aError.Failed()) { + return nullptr; + } + + if (aOptions.IsKeyframeAnimationOptions()) { + animation->SetId(aOptions.GetAsKeyframeAnimationOptions().mId); + } + + animation->Play(aError, Animation::LimitBehavior::AutoRewind); + if (aError.Failed()) { + return nullptr; + } + + return animation.forget(); +} + void Element::GetAnimations(nsTArray>& aAnimations) { diff --git a/dom/base/Element.h b/dom/base/Element.h index ca056b8da9..536c953a4f 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -58,6 +58,7 @@ namespace mozilla { namespace dom { struct ScrollIntoViewOptions; struct ScrollToOptions; + class UnrestrictedDoubleOrKeyframeAnimationOptions; } // namespace dom } // namespace mozilla @@ -852,6 +853,12 @@ public: { } + already_AddRefed Animate( + JSContext* aContext, + JS::Handle aFrames, + const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, + ErrorResult& aError); + // Note: GetAnimations will flush style while GetAnimationsUnsorted won't. void GetAnimations(nsTArray>& aAnimations); void GetAnimationsUnsorted(nsTArray>& aAnimations); diff --git a/dom/base/nsDOMAttributeMap.cpp b/dom/base/nsDOMAttributeMap.cpp index 346707ffe4..b953496824 100644 --- a/dom/base/nsDOMAttributeMap.cpp +++ b/dom/base/nsDOMAttributeMap.cpp @@ -221,7 +221,7 @@ nsDOMAttributeMap::SetNamedItem(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn) NS_ENSURE_ARG(attribute); ErrorResult rv; - *aReturn = SetNamedItem(*attribute, rv).take(); + *aReturn = SetNamedItemNS(*attribute, rv).take(); return rv.StealNSResult(); } @@ -237,9 +237,7 @@ nsDOMAttributeMap::SetNamedItemNS(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn) } already_AddRefed -nsDOMAttributeMap::SetNamedItemInternal(Attr& aAttr, - bool aWithNS, - ErrorResult& aError) +nsDOMAttributeMap::SetNamedItemNS(Attr& aAttr, ErrorResult& aError) { NS_ENSURE_TRUE(mContent, nullptr); @@ -274,24 +272,18 @@ nsDOMAttributeMap::SetNamedItemInternal(Attr& aAttr, // Get nodeinfo and preexisting attribute (if it exists) RefPtr oldNi; - if (!aWithNS) { - nsAutoString name; - aAttr.GetName(name); - oldNi = mContent->GetExistingAttrNameFromQName(name); - } else { - uint32_t i, count = mContent->GetAttrCount(); - for (i = 0; i < count; ++i) { - const nsAttrName* name = mContent->GetAttrNameAt(i); - int32_t attrNS = name->NamespaceID(); - nsIAtom* nameAtom = name->LocalName(); + uint32_t i, count = mContent->GetAttrCount(); + for (i = 0; i < count; ++i) { + const nsAttrName* name = mContent->GetAttrNameAt(i); + int32_t attrNS = name->NamespaceID(); + nsIAtom* nameAtom = name->LocalName(); - // we're purposefully ignoring the prefix. - if (aAttr.NodeInfo()->Equals(nameAtom, attrNS)) { - oldNi = mContent->NodeInfo()->NodeInfoManager()-> - GetNodeInfo(nameAtom, name->GetPrefix(), aAttr.NodeInfo()->NamespaceID(), - nsIDOMNode::ATTRIBUTE_NODE); - break; - } + // we're purposefully ignoring the prefix. + if (aAttr.NodeInfo()->Equals(nameAtom, attrNS)) { + oldNi = mContent->NodeInfo()->NodeInfoManager()-> + GetNodeInfo(nameAtom, name->GetPrefix(), aAttr.NodeInfo()->NamespaceID(), + nsIDOMNode::ATTRIBUTE_NODE); + break; } } diff --git a/dom/base/nsDOMAttributeMap.h b/dom/base/nsDOMAttributeMap.h index 7ebe61aa13..3abacf8aee 100644 --- a/dom/base/nsDOMAttributeMap.h +++ b/dom/base/nsDOMAttributeMap.h @@ -141,11 +141,6 @@ public: Attr* NamedGetter(const nsAString& aAttrName, bool& aFound); bool NameIsEnumerable(const nsAString& aName); already_AddRefed - SetNamedItem(Attr& aAttr, ErrorResult& aError) - { - return SetNamedItemInternal(aAttr, false, aError); - } - already_AddRefed RemoveNamedItem(mozilla::dom::NodeInfo* aNodeInfo, ErrorResult& aError); already_AddRefed RemoveNamedItem(const nsAString& aName, ErrorResult& aError); @@ -158,10 +153,7 @@ public: GetNamedItemNS(const nsAString& aNamespaceURI, const nsAString& aLocalName); already_AddRefed - SetNamedItemNS(Attr& aNode, ErrorResult& aError) - { - return SetNamedItemInternal(aNode, true, aError); - } + SetNamedItemNS(Attr& aNode, ErrorResult& aError); already_AddRefed RemoveNamedItemNS(const nsAString& aNamespaceURI, const nsAString& aLocalName, ErrorResult& aError); @@ -184,13 +176,6 @@ private: */ AttrCache mAttributeCache; - /** - * SetNamedItem() (aWithNS = false) and SetNamedItemNS() (aWithNS = - * true) implementation. - */ - already_AddRefed - SetNamedItemInternal(Attr& aNode, bool aWithNS, ErrorResult& aError); - already_AddRefed GetAttrNodeInfo(const nsAString& aNamespaceURI, const nsAString& aLocalName); diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 6b5f746ed6..708f8bf03b 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -6671,6 +6671,7 @@ nsIDocument::ImportNode(nsINode& aNode, bool aDeep, ErrorResult& rv) const if (ShadowRoot::FromNode(imported)) { break; } + MOZ_FALLTHROUGH; } case nsIDOMNode::ATTRIBUTE_NODE: case nsIDOMNode::ELEMENT_NODE: @@ -7705,6 +7706,7 @@ nsIDocument::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR); return nullptr; } + MOZ_FALLTHROUGH; } case nsIDOMNode::ELEMENT_NODE: case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: @@ -7992,6 +7994,7 @@ nsDocument::GetViewportInfo(const ScreenIntSize& aDisplaySize) mValidMaxScale = !maxScaleStr.IsEmpty() && NS_SUCCEEDED(scaleMaxErrorCode); mViewportType = Specified; + MOZ_FALLTHROUGH; } case Specified: default: diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 2bffa2018b..2b58a5f0ed 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -8031,6 +8031,7 @@ nsGlobalWindow::RevisePopupAbuseLevel(PopupControlState aControl) case openOverridden: if (PopupWhitelisted()) abuse = PopupControlState(abuse - 1); + break; case openAllowed: break; default: NS_WARNING("Strange PopupControlState!"); diff --git a/dom/base/nsXMLContentSerializer.cpp b/dom/base/nsXMLContentSerializer.cpp index 887a9b78d0..f1f8d76f3e 100644 --- a/dom/base/nsXMLContentSerializer.cpp +++ b/dom/base/nsXMLContentSerializer.cpp @@ -1388,7 +1388,7 @@ nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence( case ' ': case '\t': sawBlankOrTab = true; - // no break + MOZ_FALLTHROUGH; case '\n': ++aPos; // do not increase mColPos, diff --git a/dom/base/test/test_bug1075702.html b/dom/base/test/test_bug1075702.html index 0798e42170..ccee454df5 100644 --- a/dom/base/test/test_bug1075702.html +++ b/dom/base/test/test_bug1075702.html @@ -29,7 +29,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1075702 document.documentElement.setAttributeNode(a1); document.documentElement.setAttributeNode(a2); - is(document.documentElement.getAttributeNS("", "aa"), null, "Should be NULL!"); + is(document.documentElement.getAttributeNS("", "aa"), "lowercase", "Should be lowercase!"); is(document.documentElement.getAttributeNS("", "AA"), "UPPERCASE", "Should be UPPERCASE!"); var a3 = document.createAttribute("AA"); diff --git a/dom/cache/AutoUtils.cpp b/dom/cache/AutoUtils.cpp index 0b7c76b487..1f9c8a52c7 100644 --- a/dom/cache/AutoUtils.cpp +++ b/dom/cache/AutoUtils.cpp @@ -483,6 +483,7 @@ AutoParentOpResult::~AutoParentOpResult() break; } Unused << PCacheParent::Send__delete__(result.actorParent()); + break; } default: // other types do not need clean up diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 00702c0894..bdf4148d62 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -3805,6 +3805,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) break; // If we are submitting, do not send click event } // else fall through and treat Space like click... + MOZ_FALLTHROUGH; } case NS_FORM_INPUT_BUTTON: case NS_FORM_INPUT_RESET: @@ -3826,7 +3827,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) case NS_VK_UP: case NS_VK_LEFT: isMovingBack = true; - // FALLTHROUGH + MOZ_FALLTHROUGH; case NS_VK_DOWN: case NS_VK_RIGHT: // Arrow key pressed, focus+select prev/next radio button diff --git a/dom/html/test/browser.ini b/dom/html/test/browser.ini index 3e2cbf83b2..803b18936e 100644 --- a/dom/html/test/browser.ini +++ b/dom/html/test/browser.ini @@ -4,6 +4,7 @@ support-files = dummy_page.html file_bug649778.html file_bug649778.html^headers^ + file_fullscreen-api-keys.html head.js [browser_bug592641.js] @@ -16,4 +17,5 @@ support-files = file_bug1108547-2.html file_bug1108547-3.html [browser_DOMDocElementInserted.js] +[browser_fullscreen-api-keys.js] [browser_fullscreen-contextmenu-esc.js] diff --git a/dom/html/test/browser_fullscreen-api-keys.js b/dom/html/test/browser_fullscreen-api-keys.js new file mode 100644 index 0000000000..ef55ced6cc --- /dev/null +++ b/dom/html/test/browser_fullscreen-api-keys.js @@ -0,0 +1,164 @@ +"use strict"; + +/** Test for Bug 545812 **/ + +// List of key codes which should exit full-screen mode. +const kKeyList = [ + { code: "VK_ESCAPE", suppressed: true}, + { code: "VK_F11", suppressed: false}, +]; + +function frameScript() { + let doc = content.document; + addMessageListener("Test:RequestFullscreen", () => { + doc.body.mozRequestFullScreen(); + }); + addMessageListener("Test:DispatchUntrustedKeyEvents", msg => { + var evt = new content.CustomEvent("Test:DispatchKeyEvents", { + detail: { code: msg.data } + }); + content.dispatchEvent(evt); + }); + + doc.addEventListener("mozfullscreenchange", () => { + sendAsyncMessage("Test:FullscreenChanged", !!doc.mozFullScreenElement); + }); + + function keyHandler(evt) { + sendAsyncMessage("Test:KeyReceived", { + type: evt.type, + keyCode: evt.keyCode + }); + } + doc.addEventListener("keydown", keyHandler, true); + doc.addEventListener("keyup", keyHandler, true); + doc.addEventListener("keypress", keyHandler, true); + + function waitUntilActive() { + if (doc.docShell.isActive && doc.hasFocus()) { + sendAsyncMessage("Test:Activated"); + } else { + setTimeout(waitUntilActive, 10); + } + } + waitUntilActive(); +} + +var gMessageManager; + +function listenOneMessage(aMsg, aListener) { + function listener({ data }) { + gMessageManager.removeMessageListener(aMsg, listener); + aListener(data); + } + gMessageManager.addMessageListener(aMsg, listener); +} + +function promiseOneMessage(aMsg) { + return new Promise(resolve => listenOneMessage(aMsg, resolve)); +} + +function captureUnexpectedFullscreenChange() { + ok(false, "Caught an unexpected fullscreen change"); +} + +function* temporaryRemoveUnexpectedFullscreenChangeCapture(callback) { + gMessageManager.removeMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + yield* callback(); + gMessageManager.addMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); +} + +function captureUnexpectedKeyEvent(type) { + ok(false, `Caught an unexpected ${type} event`); +} + +function* temporaryRemoveUnexpectedKeyEventCapture(callback) { + gMessageManager.removeMessageListener( + "Test:KeyReceived", captureUnexpectedKeyEvent); + yield* callback(); + gMessageManager.addMessageListener( + "Test:KeyReceived", captureUnexpectedKeyEvent); +} + +function* receiveExpectedKeyEvents(keyCode) { + info("Waiting for key events"); + let events = ["keydown", "keypress", "keyup"]; + while (events.length > 0) { + let evt = yield promiseOneMessage("Test:KeyReceived"); + let expected = events.shift(); + is(evt.type, expected, `Should receive a ${expected} event`); + is(evt.keyCode, keyCode, + `Should receive the event with key code ${keyCode}`); + } +} + +const kPage = "http://example.org/browser/" + + "dom/html/test/file_fullscreen-api-keys.html"; + +add_task(function* () { + yield pushPrefs( + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"]); + + let tab = gBrowser.addTab(kPage); + let browser = tab.linkedBrowser; + gBrowser.selectedTab = tab; + registerCleanupFunction(() => gBrowser.removeTab(tab)); + yield waitForDocLoadComplete(); + + gMessageManager = browser.messageManager; + gMessageManager.loadFrameScript( + "data:,(" + frameScript.toString() + ")();", false); + + // Wait for the document being actived, so that + // fullscreen request won't be denied. + yield promiseOneMessage("Test:Activated"); + + // Register listener to capture unexpected events + gMessageManager.addMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + gMessageManager.addMessageListener( + "Test:KeyReceived", captureUnexpectedKeyEvent); + registerCleanupFunction(() => { + gMessageManager.removeMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + gMessageManager.removeMessageListener( + "Test:KeyReceived", captureUnexpectedKeyEvent); + }); + + for (let {code, suppressed} of kKeyList) { + var keyCode = KeyEvent["DOM_" + code]; + info(`Test keycode ${code} (${keyCode})`); + + info("Enter fullscreen"); + yield* temporaryRemoveUnexpectedFullscreenChangeCapture(function* () { + gMessageManager.sendAsyncMessage("Test:RequestFullscreen"); + let state = yield promiseOneMessage("Test:FullscreenChanged"); + ok(state, "The content should have entered fullscreen"); + ok(document.mozFullScreenElement, + "The chrome should also be in fullscreen"); + }); + + info("Dispatch untrusted key events from content"); + yield* temporaryRemoveUnexpectedKeyEventCapture(function* () { + gMessageManager.sendAsyncMessage("Test:DispatchUntrustedKeyEvents", code); + yield* receiveExpectedKeyEvents(keyCode); + }); + + info("Send trusted key events"); + yield* temporaryRemoveUnexpectedFullscreenChangeCapture(function* () { + yield* temporaryRemoveUnexpectedKeyEventCapture(function* () { + EventUtils.synthesizeKey(code, {}); + if (!suppressed) { + yield* receiveExpectedKeyEvents(keyCode); + } + let state = yield promiseOneMessage("Test:FullscreenChanged"); + ok(!state, "The content should have exited fullscreen"); + ok(!document.mozFullScreenElement, + "The chrome should also have exited fullscreen"); + }); + }); + } +}); diff --git a/dom/html/test/file_fullscreen-plugins.html b/dom/html/test/file_fullscreen-plugins.html index b1a99df2be..4ba485cd06 100644 --- a/dom/html/test/file_fullscreen-plugins.html +++ b/dom/html/test/file_fullscreen-plugins.html @@ -58,6 +58,10 @@ function e(id) { return document.getElementById(id); } +function removeElement(e) { + e.parentNode.removeChild(e); +} + const isMacOs = navigator.appVersion.indexOf("Macintosh") != -1; var windowedPlugin = null; @@ -108,6 +112,10 @@ function nonMacTest2() { function nonMacTest3() { ok(!document.mozFullScreen, "Full-screen should have been revoked when windowed-plugin was focused."); + // Remove windowed plugins before closing the window + // to work around bug 1237853. + removeElement(e("windowed-plugin")); + removeElement(e("subdoc-plugin").contentDocument.getElementById("windowed-plugin")); opener.nextTest(); } diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini index e4a79ed736..936d3a6924 100644 --- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -46,7 +46,6 @@ support-files = file_bug893537.html file_formSubmission_img.jpg file_formSubmission_text.txt - file_fullscreen-api-keys.html file_fullscreen-api.html file_fullscreen-backdrop.html file_fullscreen-denied-inner.html diff --git a/dom/html/test/test_fullscreen-api.html b/dom/html/test/test_fullscreen-api.html index 3e5480ae91..bc47604e96 100644 --- a/dom/html/test/test_fullscreen-api.html +++ b/dom/html/test/test_fullscreen-api.html @@ -33,7 +33,6 @@ var gTestWindows = [ "file_fullscreen-esc-exit.html", "file_fullscreen-denied.html", "file_fullscreen-api.html", - "file_fullscreen-api-keys.html", "file_fullscreen-plugins.html", "file_fullscreen-hidden.html", "file_fullscreen-svg-element.html", diff --git a/dom/storage/DOMStorageDBThread.cpp b/dom/storage/DOMStorageDBThread.cpp index 310ff9e9a4..9d7eda6c48 100644 --- a/dom/storage/DOMStorageDBThread.cpp +++ b/dom/storage/DOMStorageDBThread.cpp @@ -220,7 +220,7 @@ DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation) aOperation->Finalize(NS_OK); return NS_OK; } - // NO BREAK + MOZ_FALLTHROUGH; case DBOperation::opGetUsage: if (aOperation->Type() == DBOperation::opPreloadUrgent) { diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 0e7ebb9f7e..ee0714dcea 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -126,6 +126,8 @@ var interfaceNamesInGlobalScope = {name: "Animation", release: false}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "AnimationEffectReadOnly", release: false}, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "AnimationEffectTimingReadOnly", release: false}, // IMPORTANT: Do not change this list without review from a DOM peer! "AnimationEvent", // IMPORTANT: Do not change this list without review from a DOM peer! @@ -349,6 +351,8 @@ var interfaceNamesInGlobalScope = "CSSPageRule", // IMPORTANT: Do not change this list without review from a DOM peer! "CSSPrimitiveValue", +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "CSSPseudoElement", release: false}, // IMPORTANT: Do not change this list without review from a DOM peer! "CSSRule", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/Animatable.webidl b/dom/webidl/Animatable.webidl index 03985f797b..f1e0a5564f 100644 --- a/dom/webidl/Animatable.webidl +++ b/dom/webidl/Animatable.webidl @@ -10,8 +10,16 @@ * liability, trademark and document use rules apply. */ +dictionary KeyframeAnimationOptions : KeyframeEffectOptions { + DOMString id = ""; +}; + [NoInterfaceObject] interface Animatable { + [Func="nsDocument::IsWebAnimationsEnabled", Throws] + Animation animate(object? frames, + optional (unrestricted double or KeyframeAnimationOptions) + options); [Func="nsDocument::IsWebAnimationsEnabled"] sequence getAnimations(); }; diff --git a/dom/webidl/Animation.webidl b/dom/webidl/Animation.webidl index 6ca9d42535..7264db1549 100644 --- a/dom/webidl/Animation.webidl +++ b/dom/webidl/Animation.webidl @@ -12,8 +12,11 @@ enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" }; -[Func="nsDocument::IsWebAnimationsEnabled"] +[Func="nsDocument::IsWebAnimationsEnabled", + Constructor (optional KeyframeEffectReadOnly? effect = null, + optional AnimationTimeline? timeline = null)] interface Animation : EventTarget { + attribute DOMString id; // Bug 1049975: Make 'effect' writeable [Pure] readonly attribute AnimationEffectReadOnly? effect; diff --git a/dom/webidl/AnimationEffectReadonly.webidl b/dom/webidl/AnimationEffectReadonly.webidl index a71bb6c354..8922f1afd2 100644 --- a/dom/webidl/AnimationEffectReadonly.webidl +++ b/dom/webidl/AnimationEffectReadonly.webidl @@ -46,9 +46,8 @@ dictionary ComputedTimingProperties : AnimationEffectTimingProperties { [Func="nsDocument::IsWebAnimationsEnabled"] interface AnimationEffectReadOnly { - // Not yet implemented: - // readonly attribute AnimationEffectTimingReadOnly timing; - + [Cached, Constant] + readonly attribute AnimationEffectTimingReadOnly timing; [BinaryName="getComputedTimingAsDict"] ComputedTimingProperties getComputedTiming(); }; diff --git a/dom/webidl/AnimationEffectTimingReadOnly.webidl b/dom/webidl/AnimationEffectTimingReadOnly.webidl new file mode 100644 index 0000000000..4aff51e011 --- /dev/null +++ b/dom/webidl/AnimationEffectTimingReadOnly.webidl @@ -0,0 +1,23 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/web-animations/#animationeffecttimingreadonly + * + * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +[Func="nsDocument::IsWebAnimationsEnabled"] +interface AnimationEffectTimingReadOnly { + readonly attribute double delay; + readonly attribute double endDelay; + readonly attribute FillMode fill; + readonly attribute double iterationStart; + readonly attribute unrestricted double iterations; + readonly attribute (unrestricted double or DOMString) duration; + readonly attribute PlaybackDirection direction; + readonly attribute DOMString easing; +}; diff --git a/dom/webidl/CSSPseudoElement.webidl b/dom/webidl/CSSPseudoElement.webidl new file mode 100644 index 0000000000..96d191e3af --- /dev/null +++ b/dom/webidl/CSSPseudoElement.webidl @@ -0,0 +1,25 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://drafts.csswg.org/css-pseudo/#CSSPseudoElement-interface + * https://drafts.csswg.org/cssom/#pseudoelement + * + * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +// Both CSSOM and CSS Pseudo-Elements 4 provide contradictory definitions for +// this interface. +// What we implement here is a minimal subset of the two definitions which we +// ship behind a pref until the specification issues have been resolved. +[Func="nsDocument::IsWebAnimationsEnabled"] +interface CSSPseudoElement { + readonly attribute DOMString type; + readonly attribute Element parentElement; +}; + +// https://w3c.github.io/web-animations/#extensions-to-the-pseudoelement-interface +CSSPseudoElement implements Animatable; diff --git a/dom/webidl/KeyframeEffect.webidl b/dom/webidl/KeyframeEffect.webidl index 9782e00814..9d4317ec05 100644 --- a/dom/webidl/KeyframeEffect.webidl +++ b/dom/webidl/KeyframeEffect.webidl @@ -21,22 +21,12 @@ dictionary KeyframeEffectOptions : AnimationEffectTimingProperties { DOMString spacing = "distribute"; }; -// For the constructor: -// -// 1. We use Element? for the first argument since we don't support Animatable -// for pseudo-elements yet. -// -// 2. We use object? instead of -// -// (PropertyIndexedKeyframes or sequence or SharedKeyframeList) -// -// for the second argument so that we can get the property-value pairs from -// the PropertyIndexedKeyframes or Keyframe objects. We also don't support -// SharedKeyframeList yet. +// For the constructor we use Element? for the first argument since we +// don't support Animatable for pseudo-elements yet. [HeaderFile="mozilla/dom/KeyframeEffect.h", Func="nsDocument::IsWebAnimationsEnabled", Constructor(Element? target, - optional object? frames, + object? frames, optional (unrestricted double or KeyframeEffectOptions) options)] interface KeyframeEffectReadOnly : AnimationEffectReadOnly { readonly attribute Element? target; diff --git a/dom/webidl/NamedNodeMap.webidl b/dom/webidl/NamedNodeMap.webidl index 71d61181ce..82a8730195 100644 --- a/dom/webidl/NamedNodeMap.webidl +++ b/dom/webidl/NamedNodeMap.webidl @@ -5,7 +5,7 @@ interface NamedNodeMap { getter Attr? getNamedItem(DOMString name); - [Throws] + [Throws, BinaryName="setNamedItemNS"] Attr? setNamedItem(Attr arg); [Throws] Attr removeNamedItem(DOMString name); diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index e2f8155a5b..5387a8671d 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -25,6 +25,7 @@ WEBIDL_FILES = [ 'Animatable.webidl', 'Animation.webidl', 'AnimationEffectReadOnly.webidl', + 'AnimationEffectTimingReadOnly.webidl', 'AnimationEvent.webidl', 'AnimationTimeline.webidl', 'AnonymousContent.webidl', @@ -98,6 +99,7 @@ WEBIDL_FILES = [ 'CSSAnimation.webidl', 'CSSLexer.webidl', 'CSSPrimitiveValue.webidl', + 'CSSPseudoElement.webidl', 'CSSRuleList.webidl', 'CSSStyleDeclaration.webidl', 'CSSStyleSheet.webidl', diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index 5f0b1bdde8..43f564cd87 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -1970,7 +1970,7 @@ void ReportLoadError(JSContext* aCx, nsresult aLoadResult) case NS_ERROR_MALFORMED_URI: aLoadResult = NS_ERROR_DOM_SYNTAX_ERR; - // fall through + MOZ_FALLTHROUGH; case NS_ERROR_DOM_SECURITY_ERR: case NS_ERROR_DOM_SYNTAX_ERR: Throw(aCx, aLoadResult); diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index 49d9a1cf4a..476c68db18 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -385,7 +385,8 @@ AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer, FrameMetrics::ViewID aTransformScrollId, const Matrix4x4& aPreviousTransformForRoot, const Matrix4x4& aCurrentTransformForRoot, - const ScreenMargin& aFixedLayerMargins) + const ScreenMargin& aFixedLayerMargins, + bool aTransformAffectsLayerClip) { FrameMetrics::ViewID fixedTo; // the scroll id of the scroll frame we are fixed/sticky to bool isRootOfFixedSubtree = aLayer->GetIsFixedPosition() && @@ -411,7 +412,8 @@ AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer, for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot, aTransformScrollId, aPreviousTransformForRoot, - aCurrentTransformForRoot, aFixedLayerMargins); + aCurrentTransformForRoot, aFixedLayerMargins, + true /* descendants' clip rects are always affected */); } return; } @@ -489,18 +491,15 @@ AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer, IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost()); } - // Finally, apply the translation to the layer transform. Note that in - // general we need to apply the same translation to the layer's clip rect, so + // Finally, apply the translation to the layer transform. Note that in cases + // where the async transform on |aTransformedSubtreeRoot| affects this layer's + // clip rect, we need to apply the same translation to said clip rect, so // that the effective transform on the clip rect takes it back to where it was - // originally, had there been no async scroll. In the case where the - // fixed/sticky layer is the same as aTransformedSubtreeRoot, then the clip - // rect is not affected by the scroll-induced async scroll transform anyway - // (since the clip is applied post-transform) so we don't need to make the - // adjustment. Also, some layers want async scrolling to move their clip rect + // originally, had there been no async scroll. + // Also, some layers want async scrolling to move their clip rect // (IsClipFixed() = false), so we don't make a compensating adjustment for // those. - bool adjustClipRect = aLayer != aTransformedSubtreeRoot && - aLayer->IsClipFixed(); + bool adjustClipRect = aTransformAffectsLayerClip && aLayer->IsClipFixed(); TranslateShadowLayer(aLayer, ThebesPoint(translation.ToUnknownPoint()), adjustClipRect); } @@ -580,18 +579,20 @@ SampleAnimations(Layer* aLayer, TimeStamp aPoint) continue; } - AnimationTiming timing; - timing.mIterationDuration = animation.duration(); + TimingParams timing; + timing.mDuration.SetAsUnrestrictedDouble() = + animation.duration().ToMilliseconds(); // Currently animations run on the compositor have their delay factored // into their start time, hence the delay is effectively zero. timing.mDelay = TimeDuration(0); - timing.mIterationCount = animation.iterationCount(); - timing.mDirection = static_cast(animation.direction()); + timing.mIterations = animation.iterations(); + timing.mDirection = + static_cast(animation.direction()); // Animations typically only run on the compositor during their active // interval but if we end up sampling them outside that range (for // example, while they are waiting to be removed) we currently just // assume that we should fill. - timing.mFillMode = dom::FillMode::Both; + timing.mFill = dom::FillMode::Both; ComputedTiming computedTiming = dom::KeyframeEffectReadOnly::GetComputedTimingAt( @@ -900,6 +901,29 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, asyncClip = Some(TransformBy( ViewAs(asyncTransform), *asyncClip)); } + aLayer->AsLayerComposite()->SetShadowClipRect(asyncClip); + + combinedAsyncTransform *= asyncTransform; + + // For the purpose of aligning fixed and sticky layers, we disregard + // the overscroll transform as well as any OMTA transform when computing the + // 'aCurrentTransformForRoot' parameter. This ensures that the overscroll + // and OMTA transforms are not unapplied, and therefore that the visual + // effects apply to fixed and sticky layers. We do this by using + // GetTransform() as the base transform rather than GetLocalTransform(), + // which would include those factors. + Matrix4x4 transformWithoutOverscrollOrOmta = aLayer->GetTransform() * + AdjustForClip(asyncTransformWithoutOverscroll, aLayer); + + // Since fixed/sticky layers are relative to their nearest scrolling ancestor, + // we use the ViewID from the bottommost scrollable metrics here. + AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform, + transformWithoutOverscrollOrOmta, fixedLayerMargins, + asyncClip.isSome()); + + // AlignFixedAndStickyLayers may have changed the clip rect, so we have to + // read it from the layer again. + asyncClip = aLayer->AsLayerComposite()->GetShadowClipRect(); // Combine the local clip with the ancestor scrollframe clip. This is not // included in the async transform above, since the ancestor clip should not @@ -932,23 +956,6 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, Layer* ancestorMaskLayer = aLayer->GetAncestorMaskLayerAt(maskLayerIndex); ancestorMaskLayers.AppendElement(ancestorMaskLayer); } - - combinedAsyncTransform *= asyncTransform; - - // For the purpose of aligning fixed and sticky layers, we disregard - // the overscroll transform as well as any OMTA transform when computing the - // 'aCurrentTransformForRoot' parameter. This ensures that the overscroll - // and OMTA transforms are not unapplied, and therefore that the visual - // effects apply to fixed and sticky layers. We do this by using - // GetTransform() as the base transform rather than GetLocalTransform(), - // which would include those factors. - Matrix4x4 transformWithoutOverscrollOrOmta = aLayer->GetTransform() * - AdjustForClip(asyncTransformWithoutOverscroll, aLayer); - - // Since fixed/sticky layers are relative to their nearest scrolling ancestor, - // we use the ViewID from the bottommost scrollable metrics here. - AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform, - transformWithoutOverscrollOrOmta, fixedLayerMargins); } if (hasAsyncTransform || clipDeferredFromChildren) { @@ -1331,7 +1338,8 @@ AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer) // Make sure fixed position layers don't move away from their anchor points // when we're asynchronously panning or zooming AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform, - aLayer->GetLocalTransform(), fixedLayerMargins); + aLayer->GetLocalTransform(), fixedLayerMargins, + false); ExpandRootClipRect(aLayer, fixedLayerMargins); } diff --git a/gfx/layers/composite/AsyncCompositionManager.h b/gfx/layers/composite/AsyncCompositionManager.h index 24af80c0bf..e21e4da62f 100644 --- a/gfx/layers/composite/AsyncCompositionManager.h +++ b/gfx/layers/composite/AsyncCompositionManager.h @@ -177,12 +177,17 @@ private: * This function will also adjust layers so that the given content document * fixed position margins will be respected during asynchronous panning and * zooming. + * aTransformAffectsLayerClip indicates whether the transform on + * aTransformedSubtreeRoot affects aLayer's clip rects, so we know + * whether we need to perform a corresponding unadjustment to keep + * the clip rect fixed. */ void AlignFixedAndStickyLayers(Layer* aLayer, Layer* aTransformedSubtreeRoot, FrameMetrics::ViewID aTransformScrollId, const gfx::Matrix4x4& aPreviousTransformForRoot, const gfx::Matrix4x4& aCurrentTransformForRoot, - const ScreenMargin& aFixedLayerMargins); + const ScreenMargin& aFixedLayerMargins, + bool aTransformAffectsLayerClip); /** * DRAWING PHASE ONLY diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh index 28c8f24b12..2dc49a37a6 100644 --- a/gfx/layers/ipc/LayersMessages.ipdlh +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -193,7 +193,7 @@ struct Animation { // Number of times to repeat the animation, including positive infinity. // Values <= 0 mean the animation will not play (although events are still // dispatched on the main thread). - float iterationCount; + float iterations; // This uses the NS_STYLE_ANIMATION_DIRECTION_* constants. int32_t direction; nsCSSProperty property; diff --git a/layout/base/BorderConsts.h b/layout/base/BorderConsts.h new file mode 100644 index 0000000000..ce47dad206 --- /dev/null +++ b/layout/base/BorderConsts.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BorderConsts_h_ +#define mozilla_BorderConsts_h_ + +// thickness of dashed line relative to dotted line +#define DOT_LENGTH 1 // square +#define DASH_LENGTH 3 // 3 times longer than dot + +// some shorthand for side bits +#define SIDE_BIT_TOP (1 << NS_SIDE_TOP) +#define SIDE_BIT_RIGHT (1 << NS_SIDE_RIGHT) +#define SIDE_BIT_BOTTOM (1 << NS_SIDE_BOTTOM) +#define SIDE_BIT_LEFT (1 << NS_SIDE_LEFT) +#define SIDE_BITS_ALL (SIDE_BIT_TOP|SIDE_BIT_RIGHT|SIDE_BIT_BOTTOM|SIDE_BIT_LEFT) + +#define C_TL NS_CORNER_TOP_LEFT +#define C_TR NS_CORNER_TOP_RIGHT +#define C_BR NS_CORNER_BOTTOM_RIGHT +#define C_BL NS_CORNER_BOTTOM_LEFT + +#endif /* mozilla_BorderConsts_h_ */ diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 2eff22d7fb..49a99eaac9 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -17,6 +17,7 @@ #include "mozilla/HashFunctions.h" #include "mozilla/MathAlgorithms.h" +#include "BorderConsts.h" #include "nsStyleConsts.h" #include "nsPresContext.h" #include "nsIFrame.h" diff --git a/layout/base/nsCSSRenderingBorders.cpp b/layout/base/nsCSSRenderingBorders.cpp index a99d33a823..5ab3532a71 100644 --- a/layout/base/nsCSSRenderingBorders.cpp +++ b/layout/base/nsCSSRenderingBorders.cpp @@ -11,6 +11,7 @@ #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Helpers.h" #include "mozilla/gfx/PathHelpers.h" +#include "BorderConsts.h" #include "nsLayoutUtils.h" #include "nsStyleConsts.h" #include "nsCSSColorUtils.h" @@ -1616,7 +1617,7 @@ nsCSSBorderRenderer::DrawBorders() Float dash = mBorderWidths[0]; strokeOptions.mDashPattern = ‐ strokeOptions.mDashLength = 1; - strokeOptions.mDashOffset = 0.5f; + strokeOptions.mDashOffset = 0.5f * dash; DrawOptions drawOptions; drawOptions.mAntialiasMode = AntialiasMode::NONE; mDrawTarget->StrokeRect(rect, color, strokeOptions, drawOptions); diff --git a/layout/base/nsCSSRenderingBorders.h b/layout/base/nsCSSRenderingBorders.h index 581577847c..951f680da0 100644 --- a/layout/base/nsCSSRenderingBorders.h +++ b/layout/base/nsCSSRenderingBorders.h @@ -28,22 +28,6 @@ class GradientStops; // define this to enable a bunch of debug dump info #undef DEBUG_NEW_BORDERS -//thickness of dashed line relative to dotted line -#define DOT_LENGTH 1 //square -#define DASH_LENGTH 3 //3 times longer than dot - -//some shorthand for side bits -#define SIDE_BIT_TOP (1 << NS_SIDE_TOP) -#define SIDE_BIT_RIGHT (1 << NS_SIDE_RIGHT) -#define SIDE_BIT_BOTTOM (1 << NS_SIDE_BOTTOM) -#define SIDE_BIT_LEFT (1 << NS_SIDE_LEFT) -#define SIDE_BITS_ALL (SIDE_BIT_TOP|SIDE_BIT_RIGHT|SIDE_BIT_BOTTOM|SIDE_BIT_LEFT) - -#define C_TL NS_CORNER_TOP_LEFT -#define C_TR NS_CORNER_TOP_RIGHT -#define C_BR NS_CORNER_BOTTOM_RIGHT -#define C_BL NS_CORNER_BOTTOM_LEFT - /* * Helper class that handles border rendering. * diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index b9cff393f8..0cd7d0417b 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -383,7 +383,9 @@ AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty, aLayer->AddAnimationForNextTransaction() : aLayer->AddAnimation(); - const AnimationTiming& timing = aAnimation->GetEffect()->Timing(); + const TimingParams& timing = aAnimation->GetEffect()->SpecifiedTiming(); + const ComputedTiming computedTiming = + aAnimation->GetEffect()->GetComputedTiming(); Nullable startTime = aAnimation->GetCurrentOrPendingStartTime(); animation->startTime() = startTime.IsNull() ? TimeStamp() @@ -391,8 +393,8 @@ AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty, StickyTimeDuration(timing.mDelay)); animation->initialCurrentTime() = aAnimation->GetCurrentTime().Value() - timing.mDelay; - animation->duration() = timing.mIterationDuration; - animation->iterationCount() = timing.mIterationCount; + animation->duration() = computedTiming.mDuration; + animation->iterations() = computedTiming.mIterations; animation->direction() = static_cast(timing.mDirection); animation->property() = aProperty.mProperty; animation->playbackRate() = aAnimation->PlaybackRate(); diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 89cd0fbe4a..a1116bc333 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -35,6 +35,12 @@ const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits) +enum class GridLineSide +{ + eBeforeGridGap, + eAfterGridGap, +}; + class nsGridContainerFrame::GridItemCSSOrderIterator { public: @@ -914,6 +920,27 @@ struct MOZ_STACK_CLASS nsGridContainerFrame::Tracks void AlignJustifyContent(const nsHTMLReflowState& aReflowState, const LogicalSize& aContainerSize); + nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const + { + if (MOZ_UNLIKELY(mSizes.IsEmpty())) { + // https://drafts.csswg.org/css-grid/#grid-definition + // "... the explicit grid still contains one grid line in each axis." + MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid"); + return nscoord(0); + } + MOZ_ASSERT(aLine <= mSizes.Length(), "mSizes is too small"); + if (aSide == GridLineSide::eBeforeGridGap) { + if (aLine == 0) { + return nscoord(0); + } + const TrackSize& sz = mSizes[aLine - 1]; + return sz.mPosition + sz.mBase; + } + if (aLine == mSizes.Length()) { + return mContentBoxSize; + } + return mSizes[aLine].mPosition; + } nscoord SumOfGridGaps() const { auto len = mSizes.Length(); @@ -932,6 +959,7 @@ struct MOZ_STACK_CLASS nsGridContainerFrame::Tracks #endif nsAutoTArray mSizes; + nscoord mContentBoxSize; nscoord mGridGap; LogicalAxis mAxis; }; @@ -1041,36 +1069,6 @@ IsNameWithStartSuffix(const nsString& aString, uint32_t* aIndex) return IsNameWithSuffix(aString, NS_LITERAL_STRING("-start"), aIndex); } -enum class GridLineSide { - eBeforeGridGap, - eAfterGridGap, -}; - -static nscoord -GridLineEdge(uint32_t aLine, const nsTArray& aTrackSizes, - GridLineSide aSide) -{ - if (MOZ_UNLIKELY(aTrackSizes.IsEmpty())) { - // https://drafts.csswg.org/css-grid/#grid-definition - // "... the explicit grid still contains one grid line in each axis." - MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid"); - return nscoord(0); - } - MOZ_ASSERT(aLine <= aTrackSizes.Length(), "aTrackSizes is too small"); - if (aSide == GridLineSide::eBeforeGridGap) { - if (aLine == 0) { - return aTrackSizes[0].mPosition; - } - const TrackSize& sz = aTrackSizes[aLine - 1]; - return sz.mPosition + sz.mBase; - } - if (aLine == aTrackSizes.Length()) { - const TrackSize& sz = aTrackSizes[aLine - 1]; - return sz.mPosition + sz.mBase; - } - return aTrackSizes[aLine].mPosition; -} - /** * (XXX share this utility function with nsFlexContainerFrame at some point) * @@ -2139,45 +2137,73 @@ nsGridContainerFrame::PlaceGridItems(GridReflowState& aState, } } - // Count empty 'auto-fit' tracks at the end of the repeat() range. + // Count empty 'auto-fit' tracks in the repeat() range. + // |colAdjust| will have a count for each line in the grid of how many + // tracks were empty between the start of the grid and that line. + Maybe> colAdjust; uint32_t numEmptyCols = 0; if (aState.mColFunctions.mHasRepeatAuto && !gridStyle->mGridTemplateColumns.mIsAutoFill && aState.mColFunctions.NumRepeatTracks() > 0) { - for (int32_t start = aState.mColFunctions.mRepeatAutoStart, - col = aState.mColFunctions.mRepeatAutoEnd - 1; - col >= start && mCellMap.IsEmptyCol(col); - --col) { - ++numEmptyCols; + for (uint32_t col = aState.mColFunctions.mRepeatAutoStart, + endRepeat = aState.mColFunctions.mRepeatAutoEnd, + numColLines = mGridColEnd + 1; + col < numColLines; ++col) { + if (numEmptyCols) { + (*colAdjust)[col] = numEmptyCols; + } + if (col < endRepeat && mCellMap.IsEmptyCol(col)) { + ++numEmptyCols; + if (colAdjust.isNothing()) { + colAdjust.emplace(numColLines); + colAdjust->SetLength(numColLines); + PodZero(colAdjust->Elements(), colAdjust->Length()); + } + } } } + Maybe> rowAdjust; uint32_t numEmptyRows = 0; if (aState.mRowFunctions.mHasRepeatAuto && !gridStyle->mGridTemplateRows.mIsAutoFill && aState.mRowFunctions.NumRepeatTracks() > 0) { - for (int32_t start = aState.mRowFunctions.mRepeatAutoStart, - row = aState.mRowFunctions.mRepeatAutoEnd - 1; - row >= start && mCellMap.IsEmptyRow(row); - --row) { - ++numEmptyRows; + for (uint32_t row = aState.mRowFunctions.mRepeatAutoStart, + endRepeat = aState.mRowFunctions.mRepeatAutoEnd, + numRowLines = mGridRowEnd + 1; + row < numRowLines; ++row) { + if (numEmptyRows) { + (*rowAdjust)[row] = numEmptyRows; + } + if (row < endRepeat && mCellMap.IsEmptyRow(row)) { + ++numEmptyRows; + if (rowAdjust.isNothing()) { + rowAdjust.emplace(numRowLines); + rowAdjust->SetLength(numRowLines); + PodZero(rowAdjust->Elements(), rowAdjust->Length()); + } + } } } // Remove the empty 'auto-fit' tracks we found above, if any. if (numEmptyCols || numEmptyRows) { // Adjust the line numbers in the grid areas. - const uint32_t firstRemovedCol = - aState.mColFunctions.mRepeatAutoEnd - numEmptyCols; - const uint32_t firstRemovedRow = - aState.mRowFunctions.mRepeatAutoEnd - numEmptyRows; for (auto& item : mGridItems) { GridArea& area = item.mArea; - area.mCols.AdjustForRemovedTracks(firstRemovedCol, numEmptyCols); - area.mRows.AdjustForRemovedTracks(firstRemovedRow, numEmptyRows); + if (numEmptyCols) { + area.mCols.AdjustForRemovedTracks(*colAdjust); + } + if (numEmptyRows) { + area.mRows.AdjustForRemovedTracks(*rowAdjust); + } } for (auto& item : mAbsPosItems) { GridArea& area = item.mArea; - area.mCols.AdjustAbsPosForRemovedTracks(firstRemovedCol, numEmptyCols); - area.mRows.AdjustAbsPosForRemovedTracks(firstRemovedRow, numEmptyRows); + if (numEmptyCols) { + area.mCols.AdjustAbsPosForRemovedTracks(*colAdjust); + } + if (numEmptyRows) { + area.mRows.AdjustAbsPosForRemovedTracks(*rowAdjust); + } } // Adjust the grid size. mGridColEnd -= numEmptyCols; @@ -2257,6 +2283,7 @@ nsGridContainerFrame::Tracks::Initialize( aFunctions.MaxSizingFor(i)); } mGridGap = aGridGap; + mContentBoxSize = aContentBoxSize; MOZ_ASSERT(mGridGap >= nscoord(0), "negative grid gap"); } @@ -3062,7 +3089,7 @@ nsGridContainerFrame::LineRange::ToLength( void nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( - const nsTArray& aTrackSizes, nscoord aGridOrigin, + const Tracks& aTracks, nscoord aGridOrigin, nscoord* aPos, nscoord* aLength) const { // kAutoLine for abspos children contributes the corresponding edge @@ -3073,18 +3100,17 @@ nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( } else { const nscoord endPos = *aPos + *aLength; nscoord startPos = - ::GridLineEdge(mStart, aTrackSizes, GridLineSide::eAfterGridGap); + aTracks.GridLineEdge(mStart, GridLineSide::eAfterGridGap); *aPos = aGridOrigin + startPos; *aLength = std::max(endPos - *aPos, 0); } } else { if (mStart == kAutoLine) { - nscoord endPos = - ::GridLineEdge(mEnd, aTrackSizes, GridLineSide::eBeforeGridGap); + nscoord endPos = aTracks.GridLineEdge(mEnd, GridLineSide::eBeforeGridGap); *aLength = std::max(aGridOrigin + endPos, 0); } else { nscoord pos; - ToPositionAndLength(aTrackSizes, &pos, aLength); + ToPositionAndLength(aTracks.mSizes, &pos, aLength); *aPos = aGridOrigin + pos; } } @@ -3113,11 +3139,9 @@ nsGridContainerFrame::ContainingBlockForAbsPos(const GridReflowState& aState, nscoord b = aGridCB.BStart(wm); nscoord iSize = aGridCB.ISize(wm); nscoord bSize = aGridCB.BSize(wm); - aArea.mCols.ToPositionAndLengthForAbsPos(aState.mCols.mSizes, - aGridOrigin.I(wm), + aArea.mCols.ToPositionAndLengthForAbsPos(aState.mCols, aGridOrigin.I(wm), &i, &iSize); - aArea.mRows.ToPositionAndLengthForAbsPos(aState.mRows.mSizes, - aGridOrigin.B(wm), + aArea.mRows.ToPositionAndLengthForAbsPos(aState.mRows, aGridOrigin.B(wm), &b, &bSize); return LogicalRect(wm, i, b, iSize, bSize); } @@ -3313,14 +3337,21 @@ nsGridContainerFrame::Reflow(nsPresContext* aPresContext, for (const TrackSize& sz : gridReflowState.mCols.mSizes) { colTrackSizes.AppendElement(sz.mBase); } - Properties().Set(GridColTrackSizes(), - new nsTArray(mozilla::Move(colTrackSizes))); + ComputedGridTrackInfo* colInfo = new ComputedGridTrackInfo( + gridReflowState.mColFunctions.mExplicitGridOffset, + gridReflowState.mColFunctions.NumExplicitTracks(), + Move(colTrackSizes)); + Properties().Set(GridColTrackInfo(), colInfo); + nsTArray rowTrackSizes(gridReflowState.mRows.mSizes.Length()); for (const TrackSize& sz : gridReflowState.mRows.mSizes) { rowTrackSizes.AppendElement(sz.mBase); } - Properties().Set(GridRowTrackSizes(), - new nsTArray(mozilla::Move(rowTrackSizes))); + ComputedGridTrackInfo* rowInfo = new ComputedGridTrackInfo( + gridReflowState.mRowFunctions.mExplicitGridOffset, + gridReflowState.mRowFunctions.NumExplicitTracks(), + Move(rowTrackSizes)); + Properties().Set(GridRowTrackInfo(), rowInfo); nscoord bSize = 0; if (computedBSize == NS_AUTOHEIGHT) { diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h index c41fc5a502..cd43dc7e53 100644 --- a/layout/generic/nsGridContainerFrame.h +++ b/layout/generic/nsGridContainerFrame.h @@ -21,6 +21,25 @@ nsContainerFrame* NS_NewGridContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +namespace mozilla { +/** + * The number of implicit / explicit tracks and their sizes. + */ +struct ComputedGridTrackInfo +{ + ComputedGridTrackInfo(uint32_t aNumLeadingImplicitTracks, + uint32_t aNumExplicitTracks, + nsTArray&& aSizes) + : mNumLeadingImplicitTracks(aNumLeadingImplicitTracks) + , mNumExplicitTracks(aNumExplicitTracks) + , mSizes(aSizes) + {} + uint32_t mNumLeadingImplicitTracks; + uint32_t mNumExplicitTracks; + nsTArray mSizes; +}; +} // namespace mozilla + class nsGridContainerFrame final : public nsContainerFrame { public: @@ -88,18 +107,16 @@ public: NS_DECLARE_FRAME_PROPERTY(GridItemContainingBlockRect, DeleteValue) - NS_DECLARE_FRAME_PROPERTY(GridColTrackSizes, DeleteValue>) - - const nsTArray* GetComputedTemplateColumns() + NS_DECLARE_FRAME_PROPERTY(GridColTrackInfo, DeleteValue) + const ComputedGridTrackInfo* GetComputedTemplateColumns() { - return static_cast*>(Properties().Get(GridColTrackSizes())); + return static_cast(Properties().Get(GridColTrackInfo())); } - NS_DECLARE_FRAME_PROPERTY(GridRowTrackSizes, DeleteValue>) - - const nsTArray* GetComputedTemplateRows() + NS_DECLARE_FRAME_PROPERTY(GridRowTrackInfo, DeleteValue) + const ComputedGridTrackInfo* GetComputedTemplateRows() { - return static_cast*>(Properties().Get(GridRowTrackSizes())); + return static_cast(Properties().Get(GridRowTrackInfo())); } protected: @@ -192,47 +209,34 @@ protected: /** * Translate the lines to account for (empty) removed tracks. This method * is only for grid items and should only be called after placement. + * aNumRemovedTracks contains a count for each line in the grid how many + * tracks were removed between the start of the grid and that line. */ - void AdjustForRemovedTracks(uint32_t aFirstRemovedTrack, - uint32_t aNumRemovedTracks) + void AdjustForRemovedTracks(const nsTArray& aNumRemovedTracks) { MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item"); MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item"); - if (mStart >= aFirstRemovedTrack) { - MOZ_ASSERT(mStart >= aFirstRemovedTrack + aNumRemovedTracks, - "can't start in a removed range of tracks - those tracks " - "are supposed to be empty"); - mStart -= aNumRemovedTracks; - mEnd -= aNumRemovedTracks; - } else { - MOZ_ASSERT(mEnd <= aFirstRemovedTrack, "can't span into a removed " - "range of tracks - those tracks are supposed to be empty"); - } + uint32_t numRemovedTracks = aNumRemovedTracks[mStart]; + MOZ_ASSERT(numRemovedTracks == aNumRemovedTracks[mEnd], + "tracks that a grid item spans can't be removed"); + mStart -= numRemovedTracks; + mEnd -= numRemovedTracks; } /** * Translate the lines to account for (empty) removed tracks. This method * is only for abs.pos. children and should only be called after placement. * Same as for in-flow items, but we don't touch 'auto' lines here and we - * also need to adjust areas that span into the removed range. + * also need to adjust areas that span into the removed tracks. */ - void AdjustAbsPosForRemovedTracks(uint32_t aFirstRemovedTrack, - uint32_t aNumRemovedTracks) + void AdjustAbsPosForRemovedTracks(const nsTArray& aNumRemovedTracks) { - if (mStart != nsGridContainerFrame::kAutoLine && - mStart > aFirstRemovedTrack) { - if (mStart < aFirstRemovedTrack + aNumRemovedTracks) { - mStart = aFirstRemovedTrack; - } else { - mStart -= aNumRemovedTracks; - } + if (mStart != nsGridContainerFrame::kAutoLine) { + mStart -= aNumRemovedTracks[mStart]; } - if (mEnd != nsGridContainerFrame::kAutoLine && - mEnd > aFirstRemovedTrack) { - if (mEnd < aFirstRemovedTrack + aNumRemovedTracks) { - mEnd = aFirstRemovedTrack; - } else { - mEnd -= aNumRemovedTracks; - } + if (mEnd != nsGridContainerFrame::kAutoLine) { + MOZ_ASSERT(mStart == nsGridContainerFrame::kAutoLine || + mEnd > mStart, "invalid line range"); + mEnd -= aNumRemovedTracks[mEnd]; } if (mStart == mEnd) { mEnd = nsGridContainerFrame::kAutoLine; @@ -255,12 +259,12 @@ protected: */ nscoord ToLength(const nsTArray& aTrackSizes) const; /** - * Given an array of track sizes and a grid origin coordinate, adjust the + * Given a set of tracks and a grid origin coordinate, adjust the * abs.pos. containing block along an axis given by aPos and aLength. * aPos and aLength should already be initialized to the grid container * containing block for this axis before calling this method. */ - void ToPositionAndLengthForAbsPos(const nsTArray& aTrackSizes, + void ToPositionAndLengthForAbsPos(const Tracks& aTracks, nscoord aGridOrigin, nscoord* aPos, nscoord* aLength) const; diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 7bf3d72810..62649e7123 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -6268,6 +6268,7 @@ void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM, const gfxPoint& aTextBaselinePt, uint32_t aOffset, uint32_t aLength, + const nscolor* aDecorationOverrideColor, PropertyProvider& aProvider) { auto info = static_cast( @@ -6276,8 +6277,8 @@ nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM, return; } - nscolor color = nsLayoutUtils:: - GetColor(this, eCSSProperty_text_emphasis_color); + nscolor color = aDecorationOverrideColor ? *aDecorationOverrideColor : + nsLayoutUtils::GetColor(this, eCSSProperty_text_emphasis_color); aContext->SetColor(Color::FromABGR(color)); gfxPoint pt(aTextBaselinePt); if (!aWM.IsVertical()) { @@ -6799,7 +6800,8 @@ nsTextFrame::DrawTextRunAndDecorations( aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks); // Emphasis marks - DrawEmphasisMarks(aCtx, wm, aTextBaselinePt, aOffset, aLength, aProvider); + DrawEmphasisMarks(aCtx, wm, aTextBaselinePt, aOffset, aLength, + aDecorationOverrideColor, aProvider); // Line-throughs for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) { diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index f83d82ff12..904da87b0d 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -460,6 +460,7 @@ public: mozilla::WritingMode aWM, const gfxPoint& aTextBaselinePt, uint32_t aOffset, uint32_t aLength, + const nscolor* aDecorationOverrideColor, PropertyProvider& aProvider); virtual nscolor GetCaretColorAt(int32_t aOffset) override; diff --git a/layout/reftests/css-grid/grid-abspos-items-013-ref.html b/layout/reftests/css-grid/grid-abspos-items-013-ref.html new file mode 100644 index 0000000000..9104efb42a --- /dev/null +++ b/layout/reftests/css-grid/grid-abspos-items-013-ref.html @@ -0,0 +1,133 @@ + + + + + Reference: grid abs.pos. child in grid with gutters + + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + diff --git a/layout/reftests/css-grid/grid-abspos-items-013.html b/layout/reftests/css-grid/grid-abspos-items-013.html new file mode 100644 index 0000000000..0793a0c2c9 --- /dev/null +++ b/layout/reftests/css-grid/grid-abspos-items-013.html @@ -0,0 +1,120 @@ + + + + + CSS Grid Test: grid abs.pos. child in grid with gutters + + + + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + diff --git a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html index c01334985b..3c9999f850 100644 --- a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html @@ -45,8 +45,8 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; width: 51px; height: 2px; } .d { - left: 1px; top: 11px; - width: 212px; height: 1px; + left: 1px; top: 5px; + width: 212px; height: 7px; } .e { right: 5px; bottom: 1px; @@ -98,7 +98,7 @@ span {
-d +
diff --git a/layout/reftests/css-grid/grid-placement-abspos-implicit-001.html b/layout/reftests/css-grid/grid-placement-abspos-implicit-001.html index dd44d083fb..98e026bace 100644 --- a/layout/reftests/css-grid/grid-placement-abspos-implicit-001.html +++ b/layout/reftests/css-grid/grid-placement-abspos-implicit-001.html @@ -52,6 +52,7 @@ span.negative { .d { grid-column: auto / A -3; grid-row: 2 / 1; + top: -3px; } .e { grid-column: A -3 / auto; @@ -111,7 +112,7 @@ span {
-d +
diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html index 7c29879df3..0a4103f04b 100644 --- a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-005-ref.html @@ -28,7 +28,8 @@ html,body { .r0 { grid-template-rows: [a] repeat(1, [b] 0 [c]) [d]; } .r10,.r1 { grid-template-rows: [a] repeat(1, [b] 20px [c]) [d]; } -.r01 { grid-template-rows: [a] repeat(1, [b] 0 [c]) [d]; } +.r01 { grid-template-rows: [a] repeat(1, [b] 0 [c]) 20px [d]; grid-gap:0;} +.r01 a {grid-row-end:auto; } .r2 { grid-template-rows: [a] repeat(1, [b] 20px [c]) [d] 30px [e]; } .r20 { width:22px; grid-template-rows: [a] repeat(1, [b] 20px [c]) [d] 30px [e]; } .r02 { grid-template-rows: [a] repeat(1, [b] 0 [c]) [d] 30px [e]; } @@ -274,11 +275,11 @@ fill,fit {
-
+
-
+

diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html new file mode 100644 index 0000000000..81ba8aff8f --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html @@ -0,0 +1,177 @@ + + + + + Reference: repeat(auto-fit) with grid-aligned abs.pos. with removed start/middle tracks + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007.html new file mode 100644 index 0000000000..d8fae6faa8 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007.html @@ -0,0 +1,195 @@ + + + + + CSS Grid Test: repeat(auto-fit) with grid-aligned abs.pos. with removed start/middle tracks + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+ + + + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-008-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-008-ref.html new file mode 100644 index 0000000000..96f606682a --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-008-ref.html @@ -0,0 +1,194 @@ + + + + + CSS Grid Test: repeat(auto-fit) with grid-aligned abs.pos. with removed start/middle tracks and implicit tracks on either/both sides + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-008.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-008.html new file mode 100644 index 0000000000..3f8faafebd --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-008.html @@ -0,0 +1,204 @@ + + + + + CSS Grid Test: repeat(auto-fit) with grid-aligned abs.pos. with removed start/middle tracks and implicit tracks on either/both sides + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+ + + + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 3cad9b04d8..7381660017 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -24,6 +24,7 @@ fails == grid-whitespace-handling-1b.xhtml grid-whitespace-handling-1-ref.xhtml == grid-abspos-items-010.html grid-abspos-items-010-ref.html == grid-abspos-items-011.html grid-abspos-items-011-ref.html == grid-abspos-items-012.html grid-abspos-items-012-ref.html +== grid-abspos-items-013.html grid-abspos-items-013-ref.html == grid-order-abspos-items-001.html grid-order-abspos-items-001-ref.html == grid-order-placement-auto-001.html grid-order-placement-auto-001-ref.html == grid-order-placement-definite-001.html grid-order-placement-definite-001-ref.html @@ -96,3 +97,5 @@ fuzzy-if(winWidget,1,36) == grid-auto-min-sizing-definite-001.html grid-auto-min == grid-repeat-auto-fill-fit-004.html grid-repeat-auto-fill-fit-004-ref.html == grid-repeat-auto-fill-fit-005.html grid-repeat-auto-fill-fit-005-ref.html == grid-repeat-auto-fill-fit-006.html grid-repeat-auto-fill-fit-006-ref.html +== grid-repeat-auto-fill-fit-007.html grid-repeat-auto-fill-fit-007-ref.html +== grid-repeat-auto-fill-fit-008.html grid-repeat-auto-fill-fit-008-ref.html diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp index 0e239cf48b..d6c023ef20 100644 --- a/layout/style/nsAnimationManager.cpp +++ b/layout/style/nsAnimationManager.cpp @@ -262,10 +262,9 @@ CSSAnimation::QueueEvents() StickyTimeDuration elapsedTime; if (message == eAnimationStart || message == eAnimationIteration) { - TimeDuration iterationStart = mEffect->Timing().mIterationDuration * - computedTiming.mCurrentIteration; - elapsedTime = StickyTimeDuration(std::max(iterationStart, - InitialAdvance())); + StickyTimeDuration iterationStart = computedTiming.mDuration * + computedTiming.mCurrentIteration; + elapsedTime = std::max(iterationStart, StickyTimeDuration(InitialAdvance())); } else { MOZ_ASSERT(message == eAnimationEnd); elapsedTime = computedTiming.mActiveDuration; @@ -309,7 +308,8 @@ CSSAnimation::ElapsedTimeToTimeStamp(const StickyTimeDuration& return result; } - result = AnimationTimeToTimeStamp(aElapsedTime + mEffect->Timing().mDelay); + result = AnimationTimeToTimeStamp(aElapsedTime + + mEffect->SpecifiedTiming().mDelay); return result; } @@ -442,9 +442,9 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, KeyframeEffectReadOnly* oldEffect = oldAnim->GetEffect(); KeyframeEffectReadOnly* newEffect = newAnim->GetEffect(); animationChanged = - oldEffect->Timing() != newEffect->Timing() || + oldEffect->SpecifiedTiming() != newEffect->SpecifiedTiming() || oldEffect->Properties() != newEffect->Properties(); - oldEffect->SetTiming(newEffect->Timing()); + oldEffect->SetSpecifiedTiming(newEffect->SpecifiedTiming()); oldEffect->CopyPropertiesFrom(*newEffect); } @@ -644,13 +644,12 @@ nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext, dest->SetAnimationIndex(static_cast(animIdx)); aAnimations.AppendElement(dest); - AnimationTiming timing; - timing.mIterationDuration = - TimeDuration::FromMilliseconds(src.GetDuration()); + TimingParams timing; + timing.mDuration.SetAsUnrestrictedDouble() = src.GetDuration(); timing.mDelay = TimeDuration::FromMilliseconds(src.GetDelay()); - timing.mIterationCount = src.GetIterationCount(); + timing.mIterations = src.GetIterationCount(); timing.mDirection = src.GetDirection(); - timing.mFillMode = src.GetFillMode(); + timing.mFill = src.GetFillMode(); RefPtr destEffect = new KeyframeEffectReadOnly(mPresContext->Document(), aTarget, diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h index 558001567d..6aa1cd2731 100644 --- a/layout/style/nsAnimationManager.h +++ b/layout/style/nsAnimationManager.h @@ -169,11 +169,11 @@ protected: // Returns the duration from the start of the animation's source effect's // active interval to the point where the animation actually begins playback. // This is zero unless the animation's source effect has a negative delay in - // which // case it is the absolute value of that delay. + // which case it is the absolute value of that delay. // This is used for setting the elapsedTime member of CSS AnimationEvents. TimeDuration InitialAdvance() const { return mEffect ? - std::max(TimeDuration(), mEffect->Timing().mDelay * -1) : + std::max(TimeDuration(), mEffect->SpecifiedTiming().mDelay * -1) : TimeDuration(); } // Converts an AnimationEvent's elapsedTime value to an equivalent TimeStamp diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 72341c1a0c..696b5f2b33 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -2454,8 +2454,9 @@ nsComputedDOMStyle::GetGridTrackSize(const nsStyleCoord& aMinValue, } already_AddRefed -nsComputedDOMStyle::GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrackList, - const nsTArray* aTrackSizes) +nsComputedDOMStyle::GetGridTemplateColumnsRows( + const nsStyleGridTemplate& aTrackList, + const ComputedGridTrackInfo* aTrackInfo) { if (aTrackList.mIsSubgrid) { // XXX TODO: add support for repeat(auto-fill) for 'subgrid' (bug 1234311) @@ -2491,76 +2492,102 @@ nsComputedDOMStyle::GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrack uint32_t numSizes = aTrackList.mMinTrackSizingFunctions.Length(); MOZ_ASSERT(aTrackList.mMaxTrackSizingFunctions.Length() == numSizes, "Different number of min and max track sizing functions"); - if (aTrackSizes) { - numSizes = aTrackSizes->Length(); - MOZ_ASSERT(numSizes > 0 || - (aTrackList.HasRepeatAuto() && !aTrackList.mIsAutoFill), - "only 'auto-fit' can result in zero tracks"); + if (aTrackInfo) { + DebugOnly isAutoFill = + aTrackList.HasRepeatAuto() && aTrackList.mIsAutoFill; + DebugOnly isAutoFit = + aTrackList.HasRepeatAuto() && !aTrackList.mIsAutoFill; + DebugOnly numExplicitTracks = aTrackInfo->mNumExplicitTracks; + MOZ_ASSERT(numExplicitTracks == numSizes || + (isAutoFill && numExplicitTracks >= numSizes) || + (isAutoFit && numExplicitTracks + 1 >= numSizes), + "expected all explicit tracks (or possibly one less, if there's " + "an 'auto-fit' track, since that can collapse away)"); + numSizes = aTrackInfo->mSizes.Length(); } + // An empty is represented as "none" in syntax. if (numSizes == 0) { RefPtr val = new nsROCSSPrimitiveValue; val->SetIdent(eCSSKeyword_none); return val.forget(); } - // Delimiting N (specified) tracks requires N+1 lines: - // one before each track, plus one at the very end. - MOZ_ASSERT(aTrackList.mLineNameLists.Length() == - aTrackList.mMinTrackSizingFunctions.Length() + 1, - "Unexpected number of line name lists"); RefPtr valueList = GetROCSSValueList(false); - if (aTrackSizes) { + if (aTrackInfo) { // We've done layout on the grid and have resolved the sizes of its tracks, // so we'll return those sizes here. The grid spec says we MAY use // repeat(, Npx) here for consecutive tracks with the same // size, but that doesn't seem worth doing since even for repeat(auto-*) // the resolved size might differ for the repeated tracks. - int32_t endOfRepeat = 0; // first index after any repeat() tracks - int32_t offsetToLastRepeat = 0; - if (aTrackList.HasRepeatAuto()) { - // offsetToLastRepeat is -1 if all repeat(auto-fit) tracks are empty - offsetToLastRepeat = numSizes + 1 - aTrackList.mLineNameLists.Length(); - endOfRepeat = aTrackList.mRepeatAutoIndex + offsetToLastRepeat + 1; + const nsTArray& trackSizes = aTrackInfo->mSizes; + const uint32_t numExplicitTracks = aTrackInfo->mNumExplicitTracks; + const uint32_t numLeadingImplicitTracks = aTrackInfo->mNumLeadingImplicitTracks; + MOZ_ASSERT(numSizes > 0 && + numSizes >= numLeadingImplicitTracks + numExplicitTracks); + + // Add any leading implicit tracks. + for (uint32_t i = 0; i < numLeadingImplicitTracks; ++i) { + RefPtr val = new nsROCSSPrimitiveValue; + val->SetAppUnits(trackSizes[i]); + valueList->AppendCSSValue(val.forget()); } - MOZ_ASSERT(numSizes > 0); - for (int32_t i = 0;; i++) { + + // Then add any explicit tracks. + if (numExplicitTracks) { + int32_t endOfRepeat = 0; // first index after any repeat() tracks + int32_t offsetToLastRepeat = 0; if (aTrackList.HasRepeatAuto()) { - if (i == aTrackList.mRepeatAutoIndex) { - const nsTArray& lineNames = aTrackList.mLineNameLists[i]; - if (i == endOfRepeat) { - // all auto-fit tracks are empty, so "[a] repeat(...) [b]" - // becomes "[a b]" - AppendGridLineNames(valueList, lineNames, - aTrackList.mLineNameLists[i + 1]); - } else { - AppendGridLineNames(valueList, lineNames, + // offsetToLastRepeat is -1 if all repeat(auto-fit) tracks are empty + offsetToLastRepeat = numExplicitTracks + 1 - aTrackList.mLineNameLists.Length(); + endOfRepeat = aTrackList.mRepeatAutoIndex + offsetToLastRepeat + 1; + } + for (int32_t i = 0;; i++) { + if (aTrackList.HasRepeatAuto()) { + if (i == aTrackList.mRepeatAutoIndex) { + const nsTArray& lineNames = aTrackList.mLineNameLists[i]; + if (i == endOfRepeat) { + // all auto-fit tracks are empty, so "[a] repeat(...) [b]" + // becomes "[a b]" + AppendGridLineNames(valueList, lineNames, + aTrackList.mLineNameLists[i + 1]); + } else { + AppendGridLineNames(valueList, lineNames, + aTrackList.mRepeatAutoLineNameListBefore); + } + } else if (i == endOfRepeat) { + const nsTArray& lineNames = + aTrackList.mLineNameLists[aTrackList.mRepeatAutoIndex + 1]; + AppendGridLineNames(valueList, + aTrackList.mRepeatAutoLineNameListAfter, + lineNames); + } else if (i > aTrackList.mRepeatAutoIndex && i < endOfRepeat) { + AppendGridLineNames(valueList, + aTrackList.mRepeatAutoLineNameListAfter, aTrackList.mRepeatAutoLineNameListBefore); + } else { + uint32_t j = i > endOfRepeat ? i - offsetToLastRepeat : i; + const nsTArray& lineNames = aTrackList.mLineNameLists[j]; + AppendGridLineNames(valueList, lineNames); } - } else if (i == endOfRepeat) { - const nsTArray& lineNames = - aTrackList.mLineNameLists[aTrackList.mRepeatAutoIndex + 1]; - AppendGridLineNames(valueList, - aTrackList.mRepeatAutoLineNameListAfter, - lineNames); - } else if (i > aTrackList.mRepeatAutoIndex && i < endOfRepeat) { - AppendGridLineNames(valueList, - aTrackList.mRepeatAutoLineNameListAfter, - aTrackList.mRepeatAutoLineNameListBefore); } else { - uint32_t j = i > endOfRepeat ? i - offsetToLastRepeat : i; - const nsTArray& lineNames = aTrackList.mLineNameLists[j]; + const nsTArray& lineNames = aTrackList.mLineNameLists[i]; AppendGridLineNames(valueList, lineNames); } - } else { - const nsTArray& lineNames = aTrackList.mLineNameLists[i]; - AppendGridLineNames(valueList, lineNames); - } - if (uint32_t(i) == numSizes) { - break; + if (uint32_t(i) == numExplicitTracks) { + break; + } + RefPtr val = new nsROCSSPrimitiveValue; + val->SetAppUnits(trackSizes[i + numLeadingImplicitTracks]); + valueList->AppendCSSValue(val.forget()); } + } + + // Add any trailing implicit tracks. + for (uint32_t i = numLeadingImplicitTracks + numExplicitTracks; + i < numSizes; ++i) { RefPtr val = new nsROCSSPrimitiveValue; - val->SetAppUnits((*aTrackSizes)[i]); + val->SetAppUnits(trackSizes[i]); valueList->AppendCSSValue(val.forget()); } } else { @@ -2634,31 +2661,31 @@ nsComputedDOMStyle::DoGetGridAutoRows() already_AddRefed nsComputedDOMStyle::DoGetGridTemplateColumns() { - const nsTArray* trackSizes = nullptr; + const ComputedGridTrackInfo* info = nullptr; if (mInnerFrame) { nsIFrame* gridContainerCandidate = mInnerFrame->GetContentInsertionFrame(); if (gridContainerCandidate && gridContainerCandidate->GetType() == nsGkAtoms::gridContainerFrame) { - auto gridContainer = static_cast(gridContainerCandidate); - trackSizes = gridContainer->GetComputedTemplateColumns(); + info = static_cast(gridContainerCandidate)-> + GetComputedTemplateColumns(); } } - return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateColumns, trackSizes); + return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateColumns, info); } already_AddRefed nsComputedDOMStyle::DoGetGridTemplateRows() { - const nsTArray* trackSizes = nullptr; + const ComputedGridTrackInfo* info = nullptr; if (mInnerFrame) { nsIFrame* gridContainerCandidate = mInnerFrame->GetContentInsertionFrame(); if (gridContainerCandidate && gridContainerCandidate->GetType() == nsGkAtoms::gridContainerFrame) { - auto gridContainer = static_cast(gridContainerCandidate); - trackSizes = gridContainer->GetComputedTemplateRows(); - } + info = static_cast(gridContainerCandidate)-> + GetComputedTemplateRows(); + } } - return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateRows, trackSizes); + return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateRows, info); } already_AddRefed diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index df04293ce6..ea0b97999a 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -27,6 +27,7 @@ namespace mozilla { namespace dom { class Element; } // namespace dom +struct ComputedGridTrackInfo; } // namespace mozilla struct nsComputedStyleMap; @@ -190,7 +191,7 @@ private: const nsStyleCoord& aMaxSize); already_AddRefed GetGridTemplateColumnsRows( const nsStyleGridTemplate& aTrackList, - const nsTArray* aTrackSizes); + const mozilla::ComputedGridTrackInfo* aTrackInfo); already_AddRefed GetGridLine(const nsStyleGridLine& aGridLine); bool GetLineHeightCoord(nscoord& aCoord); diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp index aa7a3becac..ad8e6d1e1e 100644 --- a/layout/style/nsTransitionManager.cpp +++ b/layout/style/nsTransitionManager.cpp @@ -55,8 +55,8 @@ ElementPropertyTransition::CurrentValuePortion() const // causing us to get called *after* the animation interval. So, just in // case, we override the fill mode to 'both' to ensure the progress // is never null. - AnimationTiming timingToUse = mTiming; - timingToUse.mFillMode = dom::FillMode::Both; + TimingParams timingToUse = SpecifiedTiming(); + timingToUse.mFill = dom::FillMode::Both; ComputedTiming computedTiming = GetComputedTiming(&timingToUse); MOZ_ASSERT(!computedTiming.mProgress.IsNull(), @@ -150,7 +150,8 @@ CSSTransition::QueueEvents() nsTransitionManager* manager = presContext->TransitionManager(); manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType, property, - mEffect->Timing().mIterationDuration, + mEffect->GetComputedTiming() + .mDuration, AnimationTimeToTimeStamp(EffectEnd()), this)); } @@ -665,12 +666,12 @@ nsTransitionManager::ConsiderStartingTransition( reversePortion = valuePortion; } - AnimationTiming timing; - timing.mIterationDuration = TimeDuration::FromMilliseconds(duration); + TimingParams timing; + timing.mDuration.SetAsUnrestrictedDouble() = duration; timing.mDelay = TimeDuration::FromMilliseconds(delay); - timing.mIterationCount = 1; + timing.mIterations = 1.0; timing.mDirection = dom::PlaybackDirection::Normal; - timing.mFillMode = dom::FillMode::Backwards; + timing.mFill = dom::FillMode::Backwards; RefPtr pt = new ElementPropertyTransition(aElement->OwnerDoc(), aElement, diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h index 6b873bbc0c..134a018983 100644 --- a/layout/style/nsTransitionManager.h +++ b/layout/style/nsTransitionManager.h @@ -38,7 +38,7 @@ struct ElementPropertyTransition : public dom::KeyframeEffectReadOnly ElementPropertyTransition(nsIDocument* aDocument, dom::Element* aTarget, nsCSSPseudoElements::Type aPseudoType, - const AnimationTiming &aTiming) + const TimingParams &aTiming) : dom::KeyframeEffectReadOnly(aDocument, aTarget, aPseudoType, aTiming) { } @@ -214,7 +214,7 @@ struct TransitionEventInfo { TransitionEventInfo(dom::Element* aElement, nsCSSPseudoElements::Type aPseudoType, nsCSSProperty aProperty, - TimeDuration aDuration, + StickyTimeDuration aDuration, const TimeStamp& aTimeStamp, dom::Animation* aAnimation) : mElement(aElement) diff --git a/layout/style/test/test_grid_computed_values.html b/layout/style/test/test_grid_computed_values.html index 0ee0689a08..de77fec340 100644 --- a/layout/style/test/test_grid_computed_values.html +++ b/layout/style/test/test_grid_computed_values.html @@ -26,6 +26,13 @@ grid-auto-columns: 3fr; grid-auto-rows: 2fr; } + #grid2 { + display: grid; + width: 500px; + height: 400px; + grid-auto-columns: 10px; + grid-auto-rows: 2fr; + } @@ -36,6 +43,10 @@
+
+
+
+
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 8fa85b554c..6e81a06536 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2815,6 +2815,9 @@ pref("layout.float-fragments-inside-column.enabled", true); #endif // Is support for the Web Animations API enabled? +// Before enabling this by default, make sure also CSSPseudoElement interface +// has been spec'ed properly, or we should add a separate pref for +// CSSPseudoElement interface. See Bug 1174575 for further details. #ifdef RELEASE_BUILD pref("dom.animations-api.core.enabled", false); #else diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index 27f9441a0b..ad3755d646 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -18789,6 +18789,10 @@ "path": "vibration/silent-ignore.html", "url": "/vibration/silent-ignore.html" }, + { + "path": "web-animations/animatable/animate.html", + "url": "/web-animations/animatable/animate.html" + }, { "path": "web-animations/animation-node/animation-node-after.html", "url": "/web-animations/animation-node/animation-node-after.html" @@ -21469,6 +21473,14 @@ "path": "web-animations/keyframe-effect/constructor.html", "url": "/web-animations/keyframe-effect/constructor.html" }, + { + "path": "web-animations/keyframe-effect/getComputedTiming.html", + "url": "/web-animations/keyframe-effect/getComputedTiming.html" + }, + { + "path": "web-animations/animation/constructor.html", + "url": "/web-animations/animation/constructor.html" + }, { "path": "webdriver/user_input/clear_test.py" } diff --git a/testing/web-platform/meta/web-animations/animatable/animate.html.ini b/testing/web-platform/meta/web-animations/animatable/animate.html.ini new file mode 100644 index 0000000000..cc950d3cf4 --- /dev/null +++ b/testing/web-platform/meta/web-animations/animatable/animate.html.ini @@ -0,0 +1,8 @@ +[animate.html] + type: testharness + [Element.animate() creates an Animation object with a KeyframeEffect] + expected: FAIL + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1211783 + [Element.animate() accepts a single-valued keyframe specification] + expected: FAIL + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844 diff --git a/testing/web-platform/meta/web-animations/animation/constructor.html.ini b/testing/web-platform/meta/web-animations/animation/constructor.html.ini new file mode 100644 index 0000000000..7ad2742a8e --- /dev/null +++ b/testing/web-platform/meta/web-animations/animation/constructor.html.ini @@ -0,0 +1,13 @@ +[constructor.html] + type: testharness + [Animation can be constructed with null effect and null timeline] + expected: FAIL + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1049975 + + [Animation can be constructed with null effect and non-null timeline] + expected: FAIL + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1049975 + + [Animation can be constructed with non-null effect and null timeline] + expected: FAIL + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1096776 diff --git a/testing/web-platform/tests/web-animations/animatable/animate.html b/testing/web-platform/tests/web-animations/animatable/animate.html new file mode 100644 index 0000000000..d649aef28a --- /dev/null +++ b/testing/web-platform/tests/web-animations/animatable/animate.html @@ -0,0 +1,126 @@ + + +Animatable.animate tests + + + + + + + +
+ + + diff --git a/testing/web-platform/tests/web-animations/animation/constructor.html b/testing/web-platform/tests/web-animations/animation/constructor.html new file mode 100644 index 0000000000..dcb09a2136 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation/constructor.html @@ -0,0 +1,60 @@ + + +Animation constructor tests + + + + + + + +
+
+ + diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html index 2e549fef37..edc5f4552b 100644 --- a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html +++ b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html @@ -449,6 +449,186 @@ gKeyframeSequenceTests.forEach(function(subtest) { " roundtrips"); }); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + {left: ["10px", "20px"]}); + + var timing = effect.timing; + assert_equals(timing.delay, 0, "default delay"); + assert_equals(timing.endDelay, 0, "default endDelay"); + assert_equals(timing.fill, "auto", "default fill"); + assert_equals(timing.iterations, 1.0, "default iterations"); + assert_equals(timing.iterationStart, 0.0, "default iterationStart"); + assert_equals(timing.duration, "auto", "default duration"); + assert_equals(timing.direction, "normal", "default direction"); + assert_equals(timing.easing, "linear", "default easing"); + + assert_equals(effect.composite, "replace", "default composite"); + assert_equals(effect.iterationComposite, "replace", + "default iterationComposite"); + assert_equals(effect.spacing, "distribute", + "default spacing"); +}, "a KeyframeEffectReadOnly constructed without any " + + "KeyframeEffectOptions object"); + +var gKeyframeEffectOptionTests = [ + { desc: "an empty KeyframeEffectOption", + input: {}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: "auto", + direction: "normal"} }, + { desc: "a normal KeyframeEffectOption", + input: {delay: 1000, + fill: "auto", + iterations: 5.5, + duration: "auto", + direction: "alternate"}, + expected: {delay: 1000, + fill: "auto", + iterations: 5.5, + duration: "auto", + direction: "alternate"} }, + { desc: "a double value", + input: 3000, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: 3000, + direction: "normal"} }, + { desc: "+Infinity", + input: Infinity, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: Infinity, + direction: "normal"} }, + { desc: "-Infinity", + input: -Infinity, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: -Infinity, + direction: "normal"} }, + { desc: "NaN", + input: NaN, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: NaN, + direction: "normal"} }, + { desc: "a negative value", + input: -1, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: -1, + direction: "normal"} }, + { desc: "an Infinity duration", + input: {duration: Infinity}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: Infinity, + direction: "normal"} }, + { desc: "a negative Infinity duration", + input: {duration: -Infinity}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: -Infinity, + direction: "normal"} }, + { desc: "a NaN duration", + input: {duration: NaN}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: NaN, + direction: "normal"} }, + { desc: "a negative duration", + input: {duration: -1}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: -1, + direction: "normal"} }, + { desc: "a string duration", + input: {duration: "merrychristmas"}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: "merrychristmas", + direction: "normal"} }, + { desc: "an auto duration", + input: {duration: "auto"}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: "auto", + direction: "normal"} }, + { desc: "an Infinity iterations", + input: {iterations: Infinity}, + expected: {delay: 0, + fill: "auto", + iterations: Infinity, + duration: "auto", + direction: "normal"} }, + { desc: "a negative Infinity iterations", + input: {iterations: -Infinity}, + expected: {delay: 0, + fill: "auto", + iterations: -Infinity, + duration: "auto", + direction: "normal"} }, + { desc: "a NaN iterations", + input: {iterations: NaN}, + expected: {delay: 0, + fill: "auto", + iterations: NaN, + duration: "auto", + direction: "normal"} }, + { desc: "a negative iterations", + input: {iterations: -1}, + expected: {delay: 0, + fill: "auto", + iterations: -1, + duration: "auto", + direction: "normal"} }, + { desc: "an auto fill", + input: {fill: "auto"}, + expected: {delay: 0, + fill: "auto", + iterations: 1, + duration: "auto", + direction: "normal"} }, + { desc: "a forwards fill", + input: {fill: "forwards"}, + expected: {delay: 0, + fill: "forwards", + iterations: 1, + duration: "auto", + direction: "normal"} } +]; + +gKeyframeEffectOptionTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + {left: ["10px", "20px"]}, + stest.input); + + var timing = effect.timing; + assert_equals(timing.delay, stest.expected.delay, "timing delay"); + assert_equals(timing.fill, stest.expected.fill, "timing fill"); + assert_equals(timing.iterations, stest.expected.iterations, + "timing iterations"); + assert_equals(timing.duration, stest.expected.duration, "timing duration"); + assert_equals(timing.direction, stest.expected.direction, + "timing direction"); + }, "a KeyframeEffectReadOnly constructed by " + stest.desc); +}); + done(); diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html new file mode 100644 index 0000000000..320cd78141 --- /dev/null +++ b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html @@ -0,0 +1,195 @@ + + +KeyframeEffectReadOnly getComputedTiming() tests + + + + + + + +
+
+ + diff --git a/testing/web-platform/tests/web-animations/testcommon.js b/testing/web-platform/tests/web-animations/testcommon.js index 5a704bb4dc..d9876c30f5 100644 --- a/testing/web-platform/tests/web-animations/testcommon.js +++ b/testing/web-platform/tests/web-animations/testcommon.js @@ -34,7 +34,7 @@ function newAnimation(animationTarget) { } // creates div element, appends it to the document body and -// add removing of the created element to test cleanup +// removes the created element during test cleanup function createDiv(test, doc) { if (!doc) { doc = document; From b48f3d0c249b3665d13e3d4f27d452f20681b399 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 9 Nov 2023 15:06:27 +0800 Subject: [PATCH 06/12] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1216972 - MediaManager AsyncShutdown for content processes. r=jesup (5dd0fa94ec) - Bug 1229926 - only fake cams and mics. r=jesup (e4ca6ec0c1) - Bug 957691: Ensure that MediaManager ends all tracks before finishing its source stream r=jib,jesup,pehrsons (9322e56aec) - Bug 1248308 - Fix dom/media non-unified build errors - r=jya (57325eabcf) - Bug 1183143 - Add tools/rewriting/ThirdPartyPaths.txt file. r=ehsan (26342dc0f5) - Bug 1200450 - Add libffi, dtoa, openaes, and android/thirdparty to ThirdPartyPaths.txt. r=ehsan (ea96618277) - Bug 1203477 - Moved Mozilla specific files to glue directory, and third party files to hyphen directory. Adjusted moz.build files. r=glandium (6ffddda573) - revert like FF to non-unified sources (e44eb26342) - Bug 1107702 - Try to avoid internal memory exhaustion problems with the Windows api GetAdaptersInfo by making a priming call to the api during startup. r=jesup (a96b4ba0cb) - Bug 1229926 - don't throw OverConstrainedError(deviceId) on fake devices. r=jesup (d1b4a5e26a) - Bug 1219339: switch GetStaticInstance to use IPC's Singleton impl r=froyd (5debb6c9d4) - Bug 1232046 - Do not set a too low send bitrate in VideoConduit. r=jesup (35e0beae97) - Bug 1230197: Plumb negotiated simulcast down to webrtc.org. r=jesup (ebfd5b43c8) - Bug 1220493: clean up validation of RTP headers r=pkerr (8a713a8075) - Bug 1210170: Add RID header extension send/receive/query support r=pkerr (09e537b49a) - Bug 1226387: Add sanity check to RTCP header parser r=pkerr (79d7291542) - Bug 1237023: Cherry-pick VP9 packetization/jitter-buffer/encoder code from Webrtc.org 48 r=pkerr (1cfe274857) - Bug 1237023: Update of cherry-pick of VP9: add some base files, compiles-and-works r=pkerr (9466fd9901) - Bug 1219339 - Part2: Ensure close of webrtc trace file during shutdown. r=rjesup (d3e34e8948) - gkmedias: export `vpx_codec_set_frame_buffer_functions' to fix build (0de6df1ab6) --- dom/media/DOMMediaStream.cpp | 1 + dom/media/MP3Demuxer.h | 1 + dom/media/MediaDevices.cpp | 1 + dom/media/MediaFormatReader.cpp | 1 + dom/media/MediaManager.cpp | 329 ++++---- dom/media/MediaManager.h | 7 +- dom/media/MediaStreamGraph.cpp | 11 + dom/media/RtspMediaResource.cpp | 1 + dom/media/webrtc/MediaTrackConstraints.h | 23 +- extensions/spellcheck/src/moz.build | 2 +- intl/hyphenation/README.mozilla | 17 +- intl/hyphenation/{ => glue}/hnjalloc.h | 0 intl/hyphenation/{ => glue}/hnjstdio.cpp | 0 intl/hyphenation/{ => glue}/moz.build | 15 +- .../{ => glue}/nsHyphenationManager.cpp | 0 .../{ => glue}/nsHyphenationManager.h | 0 intl/hyphenation/{ => glue}/nsHyphenator.cpp | 0 intl/hyphenation/{ => glue}/nsHyphenator.h | 0 intl/hyphenation/{ => hyphen}/COPYING | 0 intl/hyphenation/{ => hyphen}/COPYING.LGPL | 0 intl/hyphenation/{ => hyphen}/COPYING.MPL | 0 intl/hyphenation/{ => hyphen}/README | 0 intl/hyphenation/{ => hyphen}/README.compound | 0 intl/hyphenation/{ => hyphen}/README.hyphen | 0 .../{ => hyphen}/README.nonstandard | 0 intl/hyphenation/{ => hyphen}/hyphen.c | 0 intl/hyphenation/{ => hyphen}/hyphen.h | 0 intl/hyphenation/hyphen/moz.build | 19 + intl/moz.build | 3 +- layout/build/nsLayoutStatics.cpp | 1 + layout/media/symbols.def.in | 3 + .../src/common/browser_logging/WebRtcLog.cpp | 5 + .../src/common/browser_logging/WebRtcLog.h | 1 + .../src/media-conduit/AudioConduit.cpp | 3 - .../signaling/src/media-conduit/CodecConfig.h | 5 + .../src/media-conduit/VideoConduit.cpp | 197 ++++- .../peerconnection/MediaPipelineFactory.cpp | 8 +- .../src/peerconnection/PeerConnectionCtx.cpp | 4 + media/webrtc/signaling/test/FakeIPC.cpp | 35 + media/webrtc/signaling/test/FakeIPC.h | 22 + .../signaling/test/jsep_session_unittest.cpp | 3 + .../signaling/test/jsep_track_unittest.cpp | 3 + .../signaling/test/mediaconduit_unittests.cpp | 3 + .../signaling/test/mediapipeline_unittest.cpp | 3 + .../webrtc/signaling/test/sdp_file_parser.cpp | 3 + media/webrtc/signaling/test/sdp_unittests.cpp | 3 + .../signaling/test/signaling_unittests.cpp | 3 + media/webrtc/trunk/webrtc/base/base.gyp | 5 + media/webrtc/trunk/webrtc/base/bitbuffer.cc | 296 +++++++ media/webrtc/trunk/webrtc/base/bitbuffer.h | 122 +++ .../trunk/webrtc/base/bitbuffer_unittest.cc | 330 ++++++++ media/webrtc/trunk/webrtc/base/buffer.h | 9 +- media/webrtc/trunk/webrtc/base/checks.h | 32 + .../trunk/webrtc/base/constructormagic.h | 8 + media/webrtc/trunk/webrtc/common_types.cc | 4 +- media/webrtc/trunk/webrtc/common_types.h | 13 +- .../modules/interface/module_common_types.h | 275 ++++++- .../modules/rtp_rtcp/interface/rtp_receiver.h | 2 + .../modules/rtp_rtcp/interface/rtp_rtcp.h | 5 + .../rtp_rtcp/interface/rtp_rtcp_defines.h | 1 + .../webrtc/modules/rtp_rtcp/rtp_rtcp.gypi | 2 + .../modules/rtp_rtcp/source/rtp_format.cc | 6 +- .../modules/rtp_rtcp/source/rtp_format_vp9.cc | 743 ++++++++++++++++++ .../modules/rtp_rtcp/source/rtp_format_vp9.h | 108 +++ .../source/rtp_format_vp9_unittest.cc | 690 ++++++++++++++++ .../rtp_rtcp/source/rtp_header_extension.h | 5 + .../rtp_rtcp/source/rtp_receiver_impl.cc | 18 +- .../rtp_rtcp/source/rtp_receiver_impl.h | 3 + .../modules/rtp_rtcp/source/rtp_rtcp_impl.cc | 5 + .../modules/rtp_rtcp/source/rtp_rtcp_impl.h | 2 + .../modules/rtp_rtcp/source/rtp_sender.cc | 40 + .../modules/rtp_rtcp/source/rtp_sender.h | 3 + .../rtp_rtcp/source/rtp_sender_video.cc | 8 +- .../modules/rtp_rtcp/source/rtp_utility.cc | 44 +- .../modules/rtp_rtcp/source/ssrc_database.h | 2 +- .../video_capture/linux/device_info_linux.cc | 2 +- .../codecs/interface/video_codec_interface.h | 39 +- .../codecs/vp9/screenshare_layers.cc | 93 +++ .../codecs/vp9/screenshare_layers.h | 66 ++ .../codecs/vp9/screenshare_layers_unittest.cc | 323 ++++++++ .../modules/video_coding/codecs/vp9/vp9.gyp | 12 +- .../codecs/vp9/vp9_frame_buffer_pool.cc | 136 ++++ .../codecs/vp9/vp9_frame_buffer_pool.h | 117 +++ .../video_coding/codecs/vp9/vp9_impl.cc | 612 +++++++++++++-- .../video_coding/codecs/vp9/vp9_impl.h | 60 +- .../main/source/codec_database.cc | 4 +- .../video_coding/main/source/encoded_frame.cc | 74 +- .../video_coding/main/source/frame_buffer.cc | 9 + .../video_coding/main/source/frame_buffer.h | 2 + .../main/source/generic_encoder.cc | 50 +- .../video_coding/main/source/jitter_buffer.cc | 119 ++- .../video_coding/main/source/jitter_buffer.h | 37 + .../video_coding/main/source/session_info.cc | 63 +- .../video_coding/main/source/session_info.h | 2 + .../interface/static_instance.h | 120 +-- .../source/condition_variable_native_win.cc | 13 +- .../system_wrappers/source/rw_lock_win.cc | 8 +- .../system_wrappers/source/trace_impl.cc | 8 +- .../video_engine/include/vie_rtp_rtcp.h | 14 + .../trunk/webrtc/video_engine/vie_channel.cc | 62 +- .../trunk/webrtc/video_engine/vie_channel.h | 6 + .../webrtc/video_engine/vie_codec_impl.cc | 1 + .../trunk/webrtc/video_engine/vie_receiver.cc | 23 + .../trunk/webrtc/video_engine/vie_receiver.h | 3 + .../webrtc/video_engine/vie_rtp_rtcp_impl.cc | 53 ++ .../webrtc/video_engine/vie_rtp_rtcp_impl.h | 9 + tools/rewriting/ThirdPartyPaths.txt | 63 ++ 107 files changed, 5187 insertions(+), 533 deletions(-) rename intl/hyphenation/{ => glue}/hnjalloc.h (100%) rename intl/hyphenation/{ => glue}/hnjstdio.cpp (100%) rename intl/hyphenation/{ => glue}/moz.build (73%) rename intl/hyphenation/{ => glue}/nsHyphenationManager.cpp (100%) rename intl/hyphenation/{ => glue}/nsHyphenationManager.h (100%) rename intl/hyphenation/{ => glue}/nsHyphenator.cpp (100%) rename intl/hyphenation/{ => glue}/nsHyphenator.h (100%) rename intl/hyphenation/{ => hyphen}/COPYING (100%) rename intl/hyphenation/{ => hyphen}/COPYING.LGPL (100%) rename intl/hyphenation/{ => hyphen}/COPYING.MPL (100%) rename intl/hyphenation/{ => hyphen}/README (100%) rename intl/hyphenation/{ => hyphen}/README.compound (100%) rename intl/hyphenation/{ => hyphen}/README.hyphen (100%) rename intl/hyphenation/{ => hyphen}/README.nonstandard (100%) rename intl/hyphenation/{ => hyphen}/hyphen.c (100%) rename intl/hyphenation/{ => hyphen}/hyphen.h (100%) create mode 100644 intl/hyphenation/hyphen/moz.build create mode 100644 media/webrtc/signaling/test/FakeIPC.cpp create mode 100644 media/webrtc/signaling/test/FakeIPC.h create mode 100644 media/webrtc/trunk/webrtc/base/bitbuffer.cc create mode 100644 media/webrtc/trunk/webrtc/base/bitbuffer.h create mode 100644 media/webrtc/trunk/webrtc/base/bitbuffer_unittest.cc create mode 100644 media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc create mode 100644 media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h create mode 100644 media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc create mode 100644 media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.cc create mode 100644 media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h create mode 100644 media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers_unittest.cc create mode 100644 media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.cc create mode 100644 media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h create mode 100644 tools/rewriting/ThirdPartyPaths.txt diff --git a/dom/media/DOMMediaStream.cpp b/dom/media/DOMMediaStream.cpp index 60717e14fa..3f5abffaf3 100644 --- a/dom/media/DOMMediaStream.cpp +++ b/dom/media/DOMMediaStream.cpp @@ -19,6 +19,7 @@ #include "mozilla/dom/VideoTrackList.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/MediaStreamError.h" +#include "mozilla/dom/Promise.h" #include "MediaStreamGraph.h" #include "AudioStreamTrack.h" #include "VideoStreamTrack.h" diff --git a/dom/media/MP3Demuxer.h b/dom/media/MP3Demuxer.h index f6e7c1a89b..e007fce223 100644 --- a/dom/media/MP3Demuxer.h +++ b/dom/media/MP3Demuxer.h @@ -10,6 +10,7 @@ #include "MediaDataDemuxer.h" #include "MediaResource.h" #include "mp4_demuxer/ByteReader.h" +#include namespace mozilla { namespace mp3 { diff --git a/dom/media/MediaDevices.cpp b/dom/media/MediaDevices.cpp index a193912a16..d94a2697aa 100644 --- a/dom/media/MediaDevices.cpp +++ b/dom/media/MediaDevices.cpp @@ -4,6 +4,7 @@ #include "mozilla/dom/MediaDevices.h" #include "mozilla/dom/MediaStreamBinding.h" +#include "mozilla/dom/MediaDeviceInfo.h" #include "mozilla/dom/MediaDevicesBinding.h" #include "mozilla/dom/Promise.h" #include "mozilla/MediaManager.h" diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 76aadabe3e..47cf8b0141 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -6,6 +6,7 @@ #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/Preferences.h" +#include "nsContentUtils.h" #include "nsPrintfCString.h" #include "nsSize.h" #include "Layers.h" diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 611efbb853..e986e5577b 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -9,6 +9,7 @@ #include "MediaStreamGraph.h" #include "mozilla/dom/MediaStreamTrack.h" #include "GetUserMediaRequest.h" +#include "nsContentUtils.h" #include "nsHashPropertyBag.h" #ifdef MOZ_WIDGET_GONK #include "nsIAudioManager.h" @@ -41,6 +42,7 @@ #include "mozilla/dom/MediaStreamBinding.h" #include "mozilla/dom/MediaStreamTrackBinding.h" #include "mozilla/dom/GetUserMediaRequestBinding.h" +#include "mozilla/dom/Promise.h" #include "mozilla/Preferences.h" #include "mozilla/Base64.h" #include "mozilla/ipc/BackgroundChild.h" @@ -78,6 +80,9 @@ #endif #if defined (XP_WIN) #include "mozilla/WindowsVersion.h" +#include +#include +#include #endif // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to @@ -98,6 +103,23 @@ struct nsIMediaDevice::COMTypeInfo { }; const nsIID nsIMediaDevice::COMTypeInfo::kIID = NS_IMEDIADEVICE_IID; +namespace { +already_AddRefed GetShutdownPhase() { + nsCOMPtr svc = mozilla::services::GetAsyncShutdown(); + MOZ_RELEASE_ASSERT(svc); + + nsCOMPtr shutdownPhase; + nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(shutdownPhase)); + if (!shutdownPhase) { + // We are probably in a content process. + rv = svc->GetContentChildShutdown(getter_AddRefs(shutdownPhase)); + } + MOZ_RELEASE_ASSERT(shutdownPhase); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + return shutdownPhase.forget(); +} +} + namespace mozilla { #ifdef LOG @@ -116,18 +138,19 @@ using dom::ConstrainDOMStringParameters; using dom::File; using dom::MediaSourceEnum; using dom::MediaStreamConstraints; -using dom::MediaTrackConstraintSet; -using dom::MediaTrackConstraints; -using dom::MediaStreamTrack; using dom::MediaStreamError; -using dom::GetUserMediaRequest; -using dom::Sequence; +using dom::MediaStreamTrack; +using dom::MediaTrackConstraints; +using dom::MediaTrackConstraintSet; using dom::OwningBooleanOrMediaTrackConstraints; using dom::OwningStringOrStringSequence; using dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters; -using media::Pledge; +using dom::Promise; +using dom::Sequence; using media::NewRunnableFrom; using media::NewTaskFrom; +using media::Pledge; +using media::Refcountable; static Atomic sInShutdown; @@ -317,11 +340,8 @@ public: mVideoDevice->GetSource()->Stop(source, kVideoTrack); mVideoDevice->GetSource()->Deallocate(); } - // We consider ourselves finished if all tracks have been stopped, as - // there is no way to restart them from the JS APIs. - if (mBool || ((!mAudioDevice || mAudioDevice->GetSource()->IsAvailable()) && - (!mVideoDevice || mVideoDevice->GetSource()->IsAvailable()))) { - source->Finish(); + if (mType == MEDIA_STOP) { + source->EndAllTrackAndFinish(); } nsIRunnable *event = @@ -587,11 +607,11 @@ MediaDevice::SetId(const nsAString& aID) NS_IMETHODIMP MediaDevice::GetMediaSource(nsAString& aMediaSource) { - if (mMediaSource == dom::MediaSourceEnum::Microphone) { + if (mMediaSource == MediaSourceEnum::Microphone) { aMediaSource.Assign(NS_LITERAL_STRING("microphone")); - } else if (mMediaSource == dom::MediaSourceEnum::AudioCapture) { + } else if (mMediaSource == MediaSourceEnum::AudioCapture) { aMediaSource.Assign(NS_LITERAL_STRING("audioCapture")); - } else if (mMediaSource == dom::MediaSourceEnum::Window) { // this will go away + } else if (mMediaSource == MediaSourceEnum::Window) { // this will go away aMediaSource.Assign(NS_LITERAL_STRING("window")); } else { // all the rest are shared aMediaSource.Assign(NS_ConvertUTF8toUTF16( @@ -672,26 +692,6 @@ public: } } - // For gUM streams, we have a trackunion which assigns TrackIDs. However, for a - // single-source trackunion like we have here, the TrackUnion will assign trackids - // that match the source's trackids, so we can avoid needing a mapping function. - // XXX This will not handle more complex cases well. - void StopTrack(TrackID aTrackID) override - { - if (GetSourceStream()) { - GetSourceStream()->EndTrack(aTrackID); - // We could override NotifyMediaStreamTrackEnded(), and maybe should, but it's - // risky to do late in a release since that will affect all track ends, and not - // just StopTrack()s. - RefPtr ownedTrack = FindOwnedDOMTrack(mOwnedStream, aTrackID); - if (ownedTrack) { - mListener->StopTrack(aTrackID, !!ownedTrack->AsAudioStreamTrack()); - } else { - LOG(("StopTrack(%d) on non-existent track", aTrackID)); - } - } - } - already_AddRefed ApplyConstraintsToTrack(TrackID aTrackID, const MediaTrackConstraints& aConstraints, @@ -930,7 +930,7 @@ public: // placeholders. We re-route a number of stream internaly in the MSG and mix // them down instead. if (mAudioDevice && - mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) { + mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) { domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window, msg); // It should be possible to pipe the capture stream to anything. CORS is // not a problem here, we got explicit user content. @@ -1042,8 +1042,8 @@ GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) { template static void -GetSources(MediaEngine *engine, dom::MediaSourceEnum aSrcType, - void (MediaEngine::* aEnumerate)(dom::MediaSourceEnum, +GetSources(MediaEngine *engine, MediaSourceEnum aSrcType, + void (MediaEngine::* aEnumerate)(MediaSourceEnum, nsTArray >*), nsTArray>& aResult, const char* media_device_name = nullptr) @@ -1115,7 +1115,7 @@ MediaManager::SelectSettings( sources.Clear(); const char* badConstraint = nullptr; - if (IsOn(aConstraints.mVideo)) { + if (videos.Length() && IsOn(aConstraints.mVideo)) { badConstraint = MediaConstraintsHelper::SelectSettings( GetInvariant(aConstraints.mVideo), videos); for (auto& video : videos) { @@ -1353,6 +1353,8 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId, bool aFake, bool aFakeTracks) { MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aVideoType != MediaSourceEnum::Other || + aAudioType != MediaSourceEnum::Other); RefPtr p = new PledgeSourceSet(); uint32_t id = mOutstandingPledges.Append(*p); @@ -1378,30 +1380,39 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId, videoLoopDev, aVideoType, aAudioType, aFake, aFakeTracks]() mutable { - RefPtr backend; - if (aFake) { - backend = new MediaEngineDefault(aFakeTracks); - } else { + // Only enumerate what's asked for, and only fake cams and mics. + bool hasVideo = aVideoType != MediaSourceEnum::Other; + bool hasAudio = aAudioType != MediaSourceEnum::Other; + bool fakeCams = aFake && aVideoType == MediaSourceEnum::Camera; + bool fakeMics = aFake && aAudioType == MediaSourceEnum::Microphone; + + RefPtr fakeBackend, realBackend; + if (fakeCams || fakeMics) { + fakeBackend = new MediaEngineDefault(aFakeTracks); + } + if ((!fakeCams && hasVideo) || (!fakeMics && hasAudio)) { RefPtr manager = MediaManager_GetInstance(); - backend = manager->GetBackend(aWindowId); + realBackend = manager->GetBackend(aWindowId); } ScopedDeletePtr result(new SourceSet); - nsTArray> videos; - GetSources(backend, aVideoType, &MediaEngine::EnumerateVideoDevices, videos, - videoLoopDev); - for (auto& source : videos) { - result->AppendElement(source); + if (hasVideo) { + nsTArray> videos; + GetSources(fakeCams? fakeBackend : realBackend, aVideoType, + &MediaEngine::EnumerateVideoDevices, videos, videoLoopDev); + for (auto& source : videos) { + result->AppendElement(source); + } } - - nsTArray> audios; - GetSources(backend, aAudioType, - &MediaEngine::EnumerateAudioDevices, audios, audioLoopDev); - for (auto& source : audios) { - result->AppendElement(source); + if (hasAudio) { + nsTArray> audios; + GetSources(fakeMics? fakeBackend : realBackend, aAudioType, + &MediaEngine::EnumerateAudioDevices, audios, audioLoopDev); + for (auto& source : audios) { + result->AppendElement(source); + } } - SourceSet* handoff = result.forget(); NS_DispatchToMainThread(do_AddRef(NewRunnableFrom([id, handoff]() mutable { ScopedDeletePtr result(handoff); // grab result @@ -1414,7 +1425,7 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId, p->Resolve(result.forget()); } return NS_OK; - }))); + }))); })); return p.forget(); } @@ -1532,13 +1543,7 @@ MediaManager::Get() { // Prepare async shutdown - nsCOMPtr profileBeforeChange; - { - nsCOMPtr svc = services::GetAsyncShutdown(); - MOZ_RELEASE_ASSERT(svc); - nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange)); - MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); - } + nsCOMPtr shutdownPhase = GetShutdownPhase(); class Blocker : public media::ShutdownBlocker { @@ -1547,7 +1552,7 @@ MediaManager::Get() { : media::ShutdownBlocker(NS_LITERAL_STRING( "Media shutdown: blocking on media thread")) {} - NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override + NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override { MOZ_RELEASE_ASSERT(MediaManager::GetIfExists()); MediaManager::GetIfExists()->Shutdown(); @@ -1556,10 +1561,10 @@ MediaManager::Get() { }; sSingleton->mShutdownBlocker = new Blocker(); - nsresult rv = profileBeforeChange->AddBlocker(sSingleton->mShutdownBlocker, - NS_LITERAL_STRING(__FILE__), - __LINE__, - NS_LITERAL_STRING("Media shutdown")); + nsresult rv = shutdownPhase->AddBlocker(sSingleton->mShutdownBlocker, + NS_LITERAL_STRING(__FILE__), + __LINE__, + NS_LITERAL_STRING("Media shutdown")); MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); #ifdef MOZ_B2G // Init MediaPermissionManager before sending out any permission requests. @@ -1591,6 +1596,28 @@ MediaManager::GetNonE10sParent() return mNonE10sParent; } +/* static */ void +MediaManager::StartupInit() +{ +#ifdef WIN32 + if (IsVistaOrLater() && !IsWin8OrLater()) { + // Bug 1107702 - Older Windows fail in GetAdaptersInfo (and others) if the + // first(?) call occurs after the process size is over 2GB (kb/2588507). + // Attempt to 'prime' the pump by making a call at startup. + unsigned long out_buf_len = sizeof(IP_ADAPTER_INFO); + PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *) moz_xmalloc(out_buf_len); + if (GetAdaptersInfo(pAdapterInfo, &out_buf_len) == ERROR_BUFFER_OVERFLOW) { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_INFO *) moz_xmalloc(out_buf_len); + GetAdaptersInfo(pAdapterInfo, &out_buf_len); + } + if (pAdapterInfo) { + free(pAdapterInfo); + } + } +#endif +} + /* static */ void MediaManager::PostTask(const tracked_objects::Location& from_here, Task* task) @@ -1803,19 +1830,19 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, c.mVideo.SetAsBoolean() = false; } - MediaSourceEnum videoType = dom::MediaSourceEnum::Camera; - MediaSourceEnum audioType = dom::MediaSourceEnum::Microphone; + MediaSourceEnum videoType = MediaSourceEnum::Other; // none + MediaSourceEnum audioType = MediaSourceEnum::Other; // none if (c.mVideo.IsMediaTrackConstraints()) { auto& vc = c.mVideo.GetAsMediaTrackConstraints(); videoType = StringToEnum(dom::MediaSourceEnumValues::strings, vc.mMediaSource, - dom::MediaSourceEnum::Other); + MediaSourceEnum::Other); switch (videoType) { - case dom::MediaSourceEnum::Camera: + case MediaSourceEnum::Camera: break; - case dom::MediaSourceEnum::Browser: + case MediaSourceEnum::Browser: // If no window id is passed in then default to the caller's window. // Functional defaults are helpful in tests, but also a natural outcome // of the constraints API's limited semantics for requiring input. @@ -1824,20 +1851,20 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, vc.mBrowserWindow.Construct(outer->WindowID()); } MOZ_FALLTHROUGH; - case dom::MediaSourceEnum::Screen: - case dom::MediaSourceEnum::Application: - case dom::MediaSourceEnum::Window: + case MediaSourceEnum::Screen: + case MediaSourceEnum::Application: + case MediaSourceEnum::Window: // Deny screensharing request if support is disabled, or // the requesting document is not from a host on the whitelist, or // we're on Mac OSX 10.6 and WinXP until proved that they work - if (!Preferences::GetBool(((videoType == dom::MediaSourceEnum::Browser)? + if (!Preferences::GetBool(((videoType == MediaSourceEnum::Browser)? "media.getusermedia.browser.enabled" : "media.getusermedia.screensharing.enabled"), false) || #if defined(XP_MACOSX) || defined(XP_WIN) ( // Allow tab sharing for all platforms including XP and OSX 10.6 - (videoType != dom::MediaSourceEnum::Browser) && + (videoType != MediaSourceEnum::Browser) && !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms", false) && #if defined(XP_MACOSX) @@ -1857,8 +1884,8 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, } break; - case dom::MediaSourceEnum::Microphone: - case dom::MediaSourceEnum::Other: + case MediaSourceEnum::Microphone: + case MediaSourceEnum::Other: default: { RefPtr error = new MediaStreamError(aWindow, @@ -1870,10 +1897,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, } } - if (vc.mAdvanced.WasPassed() && videoType != dom::MediaSourceEnum::Camera) { + if (vc.mAdvanced.WasPassed() && videoType != MediaSourceEnum::Camera) { // iterate through advanced, forcing all unset mediaSources to match "root" const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings, - dom::MediaSourceEnum::Camera); + MediaSourceEnum::Camera); for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) { if (cs.mMediaSource.EqualsASCII(unset)) { cs.mMediaSource = vc.mMediaSource; @@ -1898,29 +1925,31 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, // permission menu for selection of the device currently. For tab sharing, // Loop has implicit permissions within Firefox, as it is built-in, // and will manage the active tab and provide appropriate UI. - if (loop && (videoType == dom::MediaSourceEnum::Window || - videoType == dom::MediaSourceEnum::Application || - videoType == dom::MediaSourceEnum::Screen)) { + if (loop && (videoType == MediaSourceEnum::Window || + videoType == MediaSourceEnum::Application || + videoType == MediaSourceEnum::Screen)) { privileged = false; } + } else if (IsOn(c.mVideo)) { + videoType = MediaSourceEnum::Camera; } if (c.mAudio.IsMediaTrackConstraints()) { auto& ac = c.mAudio.GetAsMediaTrackConstraints(); audioType = StringToEnum(dom::MediaSourceEnumValues::strings, ac.mMediaSource, - dom::MediaSourceEnum::Other); + MediaSourceEnum::Other); // Work around WebIDL default since spec uses same dictionary w/audio & video. - if (audioType == dom::MediaSourceEnum::Camera) { - audioType = dom::MediaSourceEnum::Microphone; + if (audioType == MediaSourceEnum::Camera) { + audioType = MediaSourceEnum::Microphone; ac.mMediaSource.AssignASCII(EnumToASCII(dom::MediaSourceEnumValues::strings, audioType)); } switch (audioType) { - case dom::MediaSourceEnum::Microphone: + case MediaSourceEnum::Microphone: break; - case dom::MediaSourceEnum::AudioCapture: + case MediaSourceEnum::AudioCapture: // Only enable AudioCapture if the pref is enabled. If it's not, we can // deny right away. if (!Preferences::GetBool("media.getusermedia.audiocapture.enabled")) { @@ -1932,7 +1961,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, } break; - case dom::MediaSourceEnum::Other: + case MediaSourceEnum::Other: default: { RefPtr error = new MediaStreamError(aWindow, @@ -1946,14 +1975,17 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, if (ac.mAdvanced.WasPassed()) { // iterate through advanced, forcing all unset mediaSources to match "root" const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings, - dom::MediaSourceEnum::Camera); + MediaSourceEnum::Camera); for (MediaTrackConstraintSet& cs : ac.mAdvanced.Value()) { if (cs.mMediaSource.EqualsASCII(unset)) { cs.mMediaSource = ac.mMediaSource; } } } + } else if (IsOn(c.mAudio)) { + audioType = MediaSourceEnum::Microphone; } + StreamListeners* listeners = AddWindowID(windowID); // Create a disabled listener to act as a placeholder @@ -2016,8 +2048,8 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, (!fake || Preferences::GetBool("media.navigator.permission.fake")); RefPtr p = EnumerateDevicesImpl(windowID, videoType, - audioType, fake, - fakeTracks); + audioType, fake, + fakeTracks); p->Then([this, onSuccess, onFailure, windowID, c, listener, askPermission, prefs, isHTTPS, callID, origin](SourceSet*& aDevices) mutable { @@ -2237,8 +2269,8 @@ MediaManager::EnumerateDevicesImpl(uint64_t aWindowId, RefPtr mgr = MediaManager_GetInstance(); RefPtr p = mgr->EnumerateRawDevices(aWindowId, - aVideoType, aAudioType, - aFake, aFakeTracks); + aVideoType, aAudioType, + aFake, aFakeTracks); p->Then([id, aWindowId, aOriginKey](SourceSet*& aDevices) mutable { ScopedDeletePtr devices(aDevices); // secondary result @@ -2282,8 +2314,8 @@ MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow, bool fake = Preferences::GetBool("media.navigator.streams.fake"); RefPtr p = EnumerateDevicesImpl(windowId, - dom::MediaSourceEnum::Camera, - dom::MediaSourceEnum::Microphone, + MediaSourceEnum::Camera, + MediaSourceEnum::Microphone, fake); p->Then([onSuccess, windowId, listener](SourceSet*& aDevices) mutable { ScopedDeletePtr devices(aDevices); // grab result @@ -2369,7 +2401,7 @@ StopSharingCallback(MediaManager *aThis, GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i); if (listener->Stream()) { // aka HasBeenActivate()ed - listener->Invalidate(); + listener->Stop(); } listener->Remove(); listener->StopSharing(); @@ -2386,8 +2418,8 @@ MediaManager::OnNavigation(uint64_t aWindowID) MOZ_ASSERT(NS_IsMainThread()); LOG(("OnNavigation for %llu", aWindowID)); - // Invalidate this window. The runnables check this value before making - // a call to content. + // Stop the streams for this window. The runnables check this value before + // making a call to content. nsTArray* callIDs; if (mCallIds.Get(aWindowID, &callIDs)) { @@ -2561,6 +2593,9 @@ MediaManager::Shutdown() GetActiveWindows()->Clear(); mActiveCallbacks.Clear(); mCallIds.Clear(); +#ifdef MOZ_WEBRTC + StopWebRtcLog(); +#endif // Because mMediaThread is not an nsThread, we must dispatch to it so it can // clean up BackgroundChild. Continue stopping thread once this is done. @@ -2616,14 +2651,8 @@ MediaManager::Shutdown() // Remove async shutdown blocker - nsCOMPtr profileBeforeChange; - { - nsCOMPtr svc = services::GetAsyncShutdown(); - MOZ_RELEASE_ASSERT(svc); - nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange)); - MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); - } - profileBeforeChange->RemoveBlocker(sSingleton->mShutdownBlocker); + nsCOMPtr shutdownPhase = GetShutdownPhase(); + shutdownPhase->RemoveBlocker(sSingleton->mShutdownBlocker); // we hold a ref to 'that' which is the same as sSingleton sSingleton = nullptr; @@ -3036,7 +3065,7 @@ MediaManager::IsActivelyCapturingOrHasAPermission(uint64_t aWindowId) } void -GetUserMediaCallbackMediaStreamListener::Invalidate() +GetUserMediaCallbackMediaStreamListener::Stop() { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); if (mStopped) { @@ -3052,7 +3081,7 @@ GetUserMediaCallbackMediaStreamListener::Invalidate() this, nullptr, nullptr, !mAudioStopped ? mAudioDevice.get() : nullptr, !mVideoStopped ? mVideoDevice.get() : nullptr, - mFinished, mWindowID, nullptr)); + false, mWindowID, nullptr)); mStopped = mAudioStopped = mVideoStopped = true; } @@ -3061,23 +3090,16 @@ void GetUserMediaCallbackMediaStreamListener::StopSharing() { MOZ_ASSERT(NS_IsMainThread()); - if (mVideoDevice && !mStopped && - (mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen || - mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application || - mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window)) { - // Stop the whole stream if there's no audio; just the video track if we have both - if (!mAudioDevice) { - Invalidate(); - } else if (!mVideoStopped) { - MediaManager::PostTask(FROM_HERE, - new MediaOperationTask(MEDIA_STOP_TRACK, - this, nullptr, nullptr, - nullptr, mVideoDevice, - mFinished, mWindowID, nullptr)); - mVideoStopped = true; - } + if (mVideoDevice && + (mVideoDevice->GetMediaSource() == MediaSourceEnum::Screen || + mVideoDevice->GetMediaSource() == MediaSourceEnum::Application || + mVideoDevice->GetMediaSource() == MediaSourceEnum::Window)) { + // We want to stop the whole stream if there's no audio; + // just the video track if we have both. + // StopTrack figures this out for us. + StopTrack(kVideoTrack); } else if (mAudioDevice && - mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) { + mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) { nsCOMPtr window = nsGlobalWindow::GetInnerWindowWithId(mWindowID); MOZ_ASSERT(window); window->SetAudioCapture(false); @@ -3186,28 +3208,43 @@ GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack( // Stop backend for track void -GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID, bool aIsAudio) +GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID) { MOZ_ASSERT(NS_IsMainThread()); - if (((aIsAudio && mAudioDevice) || - (!aIsAudio && mVideoDevice)) && !mStopped) + MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack); + + // XXX to support multiple tracks of a type in a stream, this should key off + // the TrackID and not just hard coded values. + + bool stopAudio = aTrackID == kAudioTrack; + bool stopVideo = aTrackID == kVideoTrack; + + if (mStopped || + (stopAudio && (mAudioStopped || !mAudioDevice)) || + (stopVideo && (mVideoStopped || !mVideoDevice))) { - // XXX to support multiple tracks of a type in a stream, this should key off - // the TrackID and not just the type - bool stopAudio = aIsAudio && !mAudioStopped; - bool stopVideo = !aIsAudio && !mVideoStopped; - MediaManager::PostTask(FROM_HERE, - new MediaOperationTask(MEDIA_STOP_TRACK, - this, nullptr, nullptr, - stopAudio ? mAudioDevice.get() : nullptr, - stopVideo ? mVideoDevice.get() : nullptr, - mFinished, mWindowID, nullptr)); - mAudioStopped |= stopAudio; - mVideoStopped |= stopVideo; - } else { - LOG(("gUM track %d ended, but we don't have type %s", - aTrackID, aIsAudio ? "audio" : "video")); + LOG(("Can't stop gUM track %d (%s), exists=%d, stopped=%d", + aTrackID, + aTrackID == kAudioTrack ? "audio" : "video", + aTrackID == kAudioTrack ? !!mAudioDevice : !!mVideoDevice, + aTrackID == kAudioTrack ? mAudioStopped : mVideoStopped)); + return; } + + if ((stopAudio || mAudioStopped || !mAudioDevice) && + (stopVideo || mVideoStopped || !mVideoDevice)) { + Stop(); + return; + } + + MediaManager::PostTask(FROM_HERE, + new MediaOperationTask(MEDIA_STOP_TRACK, + this, nullptr, nullptr, + stopAudio ? mAudioDevice.get() : nullptr, + stopVideo ? mVideoDevice.get() : nullptr, + false , mWindowID, nullptr)); + mAudioStopped |= stopAudio; + mVideoStopped |= stopVideo; } void @@ -3215,7 +3252,7 @@ GetUserMediaCallbackMediaStreamListener::NotifyFinished() { MOZ_ASSERT(NS_IsMainThread()); mFinished = true; - Invalidate(); // we know it's been activated + Stop(); // we know it's been activated RefPtr manager(MediaManager::GetInstance()); manager->RemoveFromWindowList(mWindowID, this); diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h index 1c413ea186..02d99d56cc 100644 --- a/dom/media/MediaManager.h +++ b/dom/media/MediaManager.h @@ -168,7 +168,7 @@ public: void StopSharing(); - void StopTrack(TrackID aID, bool aIsAudio); + void StopTrack(TrackID aID); typedef media::Pledge PledgeVoid; @@ -227,7 +227,7 @@ public: // implement in .cpp to avoid circular dependency with MediaOperationTask // Can be invoked from EITHER MainThread or MSG thread - void Invalidate(); + void Stop(); void AudioConfig(bool aEchoOn, uint32_t aEcho, @@ -320,7 +320,7 @@ private: // MainThread only. bool mAudioStopped; - // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice. + // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice. // MainThread only. bool mVideoStopped; @@ -417,6 +417,7 @@ public: // from MediaManager thread. static MediaManager* Get(); static MediaManager* GetIfExists(); + static void StartupInit(); static void PostTask(const tracked_objects::Location& from_here, Task* task); #ifdef DEBUG static bool IsInMediaThread(); diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index df17b48768..d344e338c4 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -98,6 +98,17 @@ MediaStreamGraphImpl::FinishStream(MediaStream* aStream) if (aStream->mFinished) return; STREAM_LOG(LogLevel::Debug, ("MediaStream %p will finish", aStream)); +#ifdef DEBUG + for (StreamBuffer::TrackIter track(aStream->mBuffer); + !track.IsEnded(); track.Next()) { + if (!track->IsEnded()) { + STREAM_LOG(LogLevel::Error, + ("MediaStream %p will finish, but track %d has not ended.", + aStream, track->GetID())); + NS_ASSERTION(false, "Finished stream cannot contain live track"); + } + } +#endif aStream->mFinished = true; aStream->mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX); diff --git a/dom/media/RtspMediaResource.cpp b/dom/media/RtspMediaResource.cpp index e7ae666f1e..0996a7c9cd 100644 --- a/dom/media/RtspMediaResource.cpp +++ b/dom/media/RtspMediaResource.cpp @@ -13,6 +13,7 @@ #include "mozilla/Monitor.h" #include "mozilla/Preferences.h" #include "mozilla/UniquePtr.h" +#include "nsContentUtils.h" #include "nsIScriptSecurityManager.h" #include "nsIStreamingProtocolService.h" #include "nsServiceManagerUtils.h" diff --git a/dom/media/webrtc/MediaTrackConstraints.h b/dom/media/webrtc/MediaTrackConstraints.h index ec26a032e7..41f8f5c421 100644 --- a/dom/media/webrtc/MediaTrackConstraints.h +++ b/dom/media/webrtc/MediaTrackConstraints.h @@ -127,18 +127,19 @@ protected: template static bool - AreUnfitSettings(const dom::MediaTrackConstraints &aConstraints, - nsTArray>& aSources) + SomeSettingsFit(const dom::MediaTrackConstraints &aConstraints, + nsTArray>& aSources) { nsTArray aggregateConstraints; aggregateConstraints.AppendElement(&aConstraints); + MOZ_ASSERT(aSources.Length()); for (auto& source : aSources) { if (source->GetBestFitnessDistance(aggregateConstraints) != UINT32_MAX) { - return false; + return true; } } - return true; + return false; } public: @@ -178,38 +179,42 @@ public: // of the sources. Unfortunately, this is a bit laborious to find out, and // requires updating as new constraints are added! + if (!unsatisfactory.Length() || + !SomeSettingsFit(dom::MediaTrackConstraints(), unsatisfactory)) { + return ""; + } if (c.mDeviceId.IsConstrainDOMStringParameters()) { dom::MediaTrackConstraints fresh; fresh.mDeviceId = c.mDeviceId; - if (AreUnfitSettings(fresh, unsatisfactory)) { + if (!SomeSettingsFit(fresh, unsatisfactory)) { return "deviceId"; } } if (c.mWidth.IsConstrainLongRange()) { dom::MediaTrackConstraints fresh; fresh.mWidth = c.mWidth; - if (AreUnfitSettings(fresh, unsatisfactory)) { + if (!SomeSettingsFit(fresh, unsatisfactory)) { return "width"; } } if (c.mHeight.IsConstrainLongRange()) { dom::MediaTrackConstraints fresh; fresh.mHeight = c.mHeight; - if (AreUnfitSettings(fresh, unsatisfactory)) { + if (!SomeSettingsFit(fresh, unsatisfactory)) { return "height"; } } if (c.mFrameRate.IsConstrainDoubleRange()) { dom::MediaTrackConstraints fresh; fresh.mFrameRate = c.mFrameRate; - if (AreUnfitSettings(fresh, unsatisfactory)) { + if (!SomeSettingsFit(fresh, unsatisfactory)) { return "frameRate"; } } if (c.mFacingMode.IsConstrainDOMStringParameters()) { dom::MediaTrackConstraints fresh; fresh.mFacingMode = c.mFacingMode; - if (AreUnfitSettings(fresh, unsatisfactory)) { + if (!SomeSettingsFit(fresh, unsatisfactory)) { return "facingMode"; } } diff --git a/extensions/spellcheck/src/moz.build b/extensions/spellcheck/src/moz.build index 1710182456..14d721caeb 100644 --- a/extensions/spellcheck/src/moz.build +++ b/extensions/spellcheck/src/moz.build @@ -5,7 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. include('/ipc/chromium/chromium-config.mozbuild') -UNIFIED_SOURCES += [ +SOURCES += [ 'mozEnglishWordUtils.cpp', 'mozGenericWordUtils.cpp', 'mozInlineSpellChecker.cpp', diff --git a/intl/hyphenation/README.mozilla b/intl/hyphenation/README.mozilla index 196e14ae1e..6055c50774 100644 --- a/intl/hyphenation/README.mozilla +++ b/intl/hyphenation/README.mozilla @@ -1,11 +1,7 @@ About the hyphenation code in this directory ============================================ -The core hyphenation code (files "hyphen.c" and "hyphen.h") comes from the -Hyphen library, part of the hunspell project. The various COPYING* and README* -files (except this README.mozilla) are likewise from the hunspell distribution -of Hyphen: - +The hyphen directory comes from the Hyphen library, part of the hunspell project. http://sourceforge.net/projects/hunspell/files/Hyphen/. This code is distributed under the GPL 2.0/LGPL 2.1/MPL 1.1 tri-license, as @@ -15,14 +11,3 @@ Note that we do not include other tools and resources found in the complete Hyphen package from upstream, so the original README.* files may refer to additional files that are not present in the Mozilla source tree. - -The other source files here: - - hnjalloc.h - hnjstdio.cpp - nsHyphenationManager.cpp - nsHyphenator.cpp - -as well as the build files (Makefile.in and moz.build) are Mozilla-authored -code, and the standard MPL 2.0 applies to these, as noted in the comments -within the files. diff --git a/intl/hyphenation/hnjalloc.h b/intl/hyphenation/glue/hnjalloc.h similarity index 100% rename from intl/hyphenation/hnjalloc.h rename to intl/hyphenation/glue/hnjalloc.h diff --git a/intl/hyphenation/hnjstdio.cpp b/intl/hyphenation/glue/hnjstdio.cpp similarity index 100% rename from intl/hyphenation/hnjstdio.cpp rename to intl/hyphenation/glue/hnjstdio.cpp diff --git a/intl/hyphenation/moz.build b/intl/hyphenation/glue/moz.build similarity index 73% rename from intl/hyphenation/moz.build rename to intl/hyphenation/glue/moz.build index 8896bb3307..237292e67e 100644 --- a/intl/hyphenation/moz.build +++ b/intl/hyphenation/glue/moz.build @@ -17,17 +17,10 @@ UNIFIED_SOURCES += [ # These files cannot be built in unified mode because they include hnjalloc.h. SOURCES += [ 'hnjstdio.cpp', - 'hyphen.c', +] + +LOCAL_INCLUDES += [ + '../hyphen', ] FINAL_LIBRARY = 'xul' - -# Suppress warnings in third-party code. -if CONFIG['GNU_CC']: - CFLAGS += [ - '-Wno-sign-compare', - '-Wno-type-limits', - ] - -if not CONFIG['GNU_CXX']: - ALLOW_COMPILER_WARNINGS = True diff --git a/intl/hyphenation/nsHyphenationManager.cpp b/intl/hyphenation/glue/nsHyphenationManager.cpp similarity index 100% rename from intl/hyphenation/nsHyphenationManager.cpp rename to intl/hyphenation/glue/nsHyphenationManager.cpp diff --git a/intl/hyphenation/nsHyphenationManager.h b/intl/hyphenation/glue/nsHyphenationManager.h similarity index 100% rename from intl/hyphenation/nsHyphenationManager.h rename to intl/hyphenation/glue/nsHyphenationManager.h diff --git a/intl/hyphenation/nsHyphenator.cpp b/intl/hyphenation/glue/nsHyphenator.cpp similarity index 100% rename from intl/hyphenation/nsHyphenator.cpp rename to intl/hyphenation/glue/nsHyphenator.cpp diff --git a/intl/hyphenation/nsHyphenator.h b/intl/hyphenation/glue/nsHyphenator.h similarity index 100% rename from intl/hyphenation/nsHyphenator.h rename to intl/hyphenation/glue/nsHyphenator.h diff --git a/intl/hyphenation/COPYING b/intl/hyphenation/hyphen/COPYING similarity index 100% rename from intl/hyphenation/COPYING rename to intl/hyphenation/hyphen/COPYING diff --git a/intl/hyphenation/COPYING.LGPL b/intl/hyphenation/hyphen/COPYING.LGPL similarity index 100% rename from intl/hyphenation/COPYING.LGPL rename to intl/hyphenation/hyphen/COPYING.LGPL diff --git a/intl/hyphenation/COPYING.MPL b/intl/hyphenation/hyphen/COPYING.MPL similarity index 100% rename from intl/hyphenation/COPYING.MPL rename to intl/hyphenation/hyphen/COPYING.MPL diff --git a/intl/hyphenation/README b/intl/hyphenation/hyphen/README similarity index 100% rename from intl/hyphenation/README rename to intl/hyphenation/hyphen/README diff --git a/intl/hyphenation/README.compound b/intl/hyphenation/hyphen/README.compound similarity index 100% rename from intl/hyphenation/README.compound rename to intl/hyphenation/hyphen/README.compound diff --git a/intl/hyphenation/README.hyphen b/intl/hyphenation/hyphen/README.hyphen similarity index 100% rename from intl/hyphenation/README.hyphen rename to intl/hyphenation/hyphen/README.hyphen diff --git a/intl/hyphenation/README.nonstandard b/intl/hyphenation/hyphen/README.nonstandard similarity index 100% rename from intl/hyphenation/README.nonstandard rename to intl/hyphenation/hyphen/README.nonstandard diff --git a/intl/hyphenation/hyphen.c b/intl/hyphenation/hyphen/hyphen.c similarity index 100% rename from intl/hyphenation/hyphen.c rename to intl/hyphenation/hyphen/hyphen.c diff --git a/intl/hyphenation/hyphen.h b/intl/hyphenation/hyphen/hyphen.h similarity index 100% rename from intl/hyphenation/hyphen.h rename to intl/hyphenation/hyphen/hyphen.h diff --git a/intl/hyphenation/hyphen/moz.build b/intl/hyphenation/hyphen/moz.build new file mode 100644 index 0000000000..42db507146 --- /dev/null +++ b/intl/hyphenation/hyphen/moz.build @@ -0,0 +1,19 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# These files cannot be built in unified mode because they include hnjalloc.h. +SOURCES += [ + 'hyphen.c', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../glue', +] + +# Suppress warnings in third-party code. +ALLOW_COMPILER_WARNINGS = True diff --git a/intl/moz.build b/intl/moz.build index 2ff8d7069f..05f68b2bb7 100644 --- a/intl/moz.build +++ b/intl/moz.build @@ -5,7 +5,8 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DIRS += [ - 'hyphenation', + 'hyphenation/hyphen', + 'hyphenation/glue', 'locale', 'locales', 'lwbrk', diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index e390aa475c..9c91e9bc90 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -261,6 +261,7 @@ nsLayoutStatics::Initialize() } AsyncLatencyLogger::InitializeStatics(); + MediaManager::StartupInit(); CubebUtils::InitLibrary(); nsContentSink::InitializeStatics(); diff --git a/layout/media/symbols.def.in b/layout/media/symbols.def.in index c7774d9aa0..b50fda2135 100644 --- a/layout/media/symbols.def.in +++ b/layout/media/symbols.def.in @@ -60,6 +60,9 @@ vpx_codec_enc_config_default vpx_img_alloc vpx_codec_encode vpx_codec_err_to_string +#ifdef MOZ_WEBRTC +vpx_codec_set_frame_buffer_functions +#endif #endif #if defined(MOZ_VORBIS) || defined(MOZ_TREMOR) ogg_page_bos diff --git a/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp index a387864e17..469d0c240a 100644 --- a/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp +++ b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp @@ -210,3 +210,8 @@ void EnableWebRtcLog() ConfigWebRtcLog(trace_mask, log_file, aec_log_dir, multi_log); return; } + +void StopWebRtcLog() +{ + webrtc::Trace::SetTraceFile(nullptr); +} diff --git a/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h index b6b7fc0a7f..dc30408171 100644 --- a/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h +++ b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h @@ -9,5 +9,6 @@ void StartWebRtcLog(uint32_t log_level = webrtc::kTraceDefault); void EnableWebRtcLog(); +void StopWebRtcLog(); #endif diff --git a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp index ab8805d9fe..bcf8dc9e75 100644 --- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp @@ -27,7 +27,6 @@ #include "webrtc/modules/audio_processing/include/audio_processing.h" #include "webrtc/voice_engine/include/voe_errors.h" #include "webrtc/system_wrappers/interface/clock.h" -#include "browser_logging/WebRtcLog.h" #ifdef MOZ_WIDGET_ANDROID #include "AndroidJNIWrapper.h" @@ -257,8 +256,6 @@ MediaConduitErrorCode WebrtcAudioConduit::Init() return kMediaConduitSessionNotInited; } - EnableWebRtcLog(); - if(!(mPtrVoEBase = VoEBase::GetInterface(mVoiceEngine))) { CSFLogError(logTag, "%s Unable to initialize VoEBase", __FUNCTION__); diff --git a/media/webrtc/signaling/src/media-conduit/CodecConfig.h b/media/webrtc/signaling/src/media-conduit/CodecConfig.h index 74d68879b5..b8e8275f1e 100644 --- a/media/webrtc/signaling/src/media-conduit/CodecConfig.h +++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h @@ -79,6 +79,11 @@ public: std::vector mCcmFbTypes; EncodingConstraints mEncodingConstraints; + struct SimulcastEncoding { + std::string rid; + EncodingConstraints constraints; + }; + std::vector mSimulcastEncodings; std::string mSpropParameterSets; uint8_t mProfile; uint8_t mConstraints; diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index 0f08038ae4..8a09635a00 100644 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -20,7 +20,7 @@ #include "webrtc/common_video/interface/native_handle.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/video_engine/include/vie_errors.h" -#include "browser_logging/WebRtcLog.h" +#include "webrtc/video_engine/vie_defines.h" #ifdef MOZ_WIDGET_ANDROID #include "AndroidJNIWrapper.h" @@ -293,7 +293,6 @@ WebrtcVideoConduit::InitMain() } } - EnableWebRtcLog(); #ifdef MOZ_WIDGET_ANDROID // get the JVM JavaVM *jvm = jsjni_GetVM(); @@ -957,6 +956,30 @@ WebrtcVideoConduit::ConfigureRecvMediaCodecs( return kMediaConduitNoError; } +struct ResolutionAndBitrateLimits { + uint32_t resolution_in_mb; + uint16_t min_bitrate; + uint16_t max_bitrate; +}; + +#define MB_OF(w,h) ((unsigned int)((((w)>>4))*((unsigned int)((h)>>4)))) + +// For now, try to set the max rates well above the knee in the curve. +// Chosen somewhat arbitrarily; it's hard to find good data oriented for +// realtime interactive/talking-head recording. These rates assume +// 30fps. + +// XXX Populate this based on a pref (which we should consider sorting because +// people won't assume they need to). +static ResolutionAndBitrateLimits kResolutionAndBitrateLimits[] = { + {MB_OF(1920, 1200), 1500, 10000}, // >HD (3K, 4K, etc) + {MB_OF(1280, 720), 1200, 5000}, // HD ~1080-1200 + {MB_OF(800, 480), 600, 2500}, // HD ~720 + {std::max(MB_OF(400, 240), MB_OF(352, 288)), 200, 1300}, // VGA, WVGA + {MB_OF(176, 144), 100, 500}, // WQVGA, CIF + {0 , 40, 250} // QCIF and below +}; + static void SelectBandwidth(webrtc::VideoCodec& vie_codec, unsigned short width, @@ -971,42 +994,12 @@ SelectBandwidth(webrtc::VideoCodec& vie_codec, mb_height = (height + 15) >> 4; fs = mb_width * mb_height; - // For now, try to set the max rates well above the knee in the curve. - // Chosen somewhat arbitrarily; it's hard to find good data oriented for - // realtime interactive/talking-head recording. These rates assume - // 30fps. -#define MB_OF(w,h) ((unsigned int)((((w)>>4))*((unsigned int)((h)>>4)))) - - // XXX replace this with parsing a config var with roughly a format - // of "max_fs,min_bw,max_bw," repeated to populate a table (which we - // should consider sorting because people won't assume they need to). - // Then iterate through the sorted array comparing fs. - if (fs > MB_OF(1920, 1200)) { - // >HD (3K, 4K, etc) - vie_codec.minBitrate = 1500; - vie_codec.maxBitrate = 10000; - } else if (fs > MB_OF(1280, 720)) { - // HD ~1080-1200 - vie_codec.minBitrate = 1200; - vie_codec.maxBitrate = 5000; - } else if (fs > MB_OF(800, 480)) { - // HD ~720 - vie_codec.minBitrate = 600; - vie_codec.maxBitrate = 2500; - } else if (fs > std::max(MB_OF(400, 240), MB_OF(352, 288))) { - // WVGA - // VGA - vie_codec.minBitrate = 200; - vie_codec.maxBitrate = 1300; - } else if (fs > MB_OF(176, 144)) { - // WQVGA - // CIF - vie_codec.minBitrate = 100; - vie_codec.maxBitrate = 500; - } else { - // QCIF and below - vie_codec.minBitrate = 40; - vie_codec.maxBitrate = 250; + for (ResolutionAndBitrateLimits resAndLimits : kResolutionAndBitrateLimits) { + if (fs > resAndLimits.resolution_in_mb) { + vie_codec.minBitrate = resAndLimits.min_bitrate; + vie_codec.maxBitrate = resAndLimits.max_bitrate; + break; + } } // mLastFramerateTenths is an atomic, and scaled by *10 @@ -1022,6 +1015,35 @@ SelectBandwidth(webrtc::VideoCodec& vie_codec, vie_codec.minBitrate = vie_codec.minBitrate * ((10-(framerate/2))/30); vie_codec.maxBitrate = vie_codec.maxBitrate * ((10-(framerate/2))/30); } + + // If we try to set a minimum bitrate that is too low, ViE will reject it. + vie_codec.minBitrate = std::max((unsigned int) webrtc::kViEMinCodecBitrate, + vie_codec.minBitrate); +} + +static void ConstrainPreservingAspectRatioExact(uint32_t max_fs, + unsigned short* width, + unsigned short* height) +{ + unsigned int mb_width = (*width + 15) >> 4; + unsigned int mb_height = (*height + 15) >> 4; + + // We could try to pick a better starting divisor, but it won't make any real + // performance difference. + for (size_t d = 1; d < std::min(mb_width, mb_height); ++d) { + if ((mb_width % d) || (mb_height % d)) { + continue; // Not divisible + } + + if ((mb_width*mb_height)/(d*d) <= max_fs) { + *width = 16 * mb_width / d; + *height = 16 * mb_height / d; + return; + } + } + + *width = 0; + *height = 0; } static void ConstrainPreservingAspectRatio(uint16_t max_width, @@ -1206,6 +1228,47 @@ WebrtcVideoConduit::ReconfigureSendCodec(unsigned short width, vie_codec.maxFramerate = mSendingFramerate; SelectBandwidth(vie_codec, width, height, mLastFramerateTenths); + // TODO: If/when we begin supporting width/height constraints on simulcast + // streams, for each such constraint we will need to choose a resolution + // that is the same aspect ratio as all other streams. This requires us to + // store the original constraints somewhere. + for (size_t i = vie_codec.numberOfSimulcastStreams; i > 0; --i) { + webrtc::SimulcastStream& stream(vie_codec.simulcastStream[i - 1]); + if (stream.maxBitrate && (stream.maxBitrate < vie_codec.minBitrate)) { + // This stream cannot do full resolution with good quality. Need to + // scale down. + stream.width = 0; + stream.height = 0; + uint32_t max_fs_in_mb = kResolutionAndBitrateLimits[0].resolution_in_mb; + for (ResolutionAndBitrateLimits resAndLimits : + kResolutionAndBitrateLimits) { + if (resAndLimits.min_bitrate < stream.maxBitrate) { + // Use the resolution from the _previous_ entry + unsigned short adjusted_width = width; + unsigned short adjusted_height = height; + // webrtc.org won't tolerate simulcast unless every stream is + // exactly the same aspect ratio + ConstrainPreservingAspectRatioExact(max_fs_in_mb, + &adjusted_width, + &adjusted_height); + stream.width = adjusted_width; + stream.height = adjusted_height; + break; + } + max_fs_in_mb = resAndLimits.resolution_in_mb; + } + } else { + stream.width = width; + stream.height = height; + } + // webrtc.org also gets upset if the last simulcast stream has a + // different resolution than the vie_codec + if (i == vie_codec.numberOfSimulcastStreams) { + vie_codec.width = stream.width; + vie_codec.height = stream.height; + } + } + if ((err = mPtrViECodec->SetSendCodec(mChannel, vie_codec)) != 0) { CSFLogError(logTag, "%s: SetSendCodec(%ux%u) failed, err %d", @@ -1655,6 +1718,18 @@ WebrtcVideoConduit::DeliverI420Frame(const webrtc::I420VideoFrame& webrtc_frame) return -1; } +template +T MinIgnoreZero(const T& a, const T& b) +{ + if (!a) { + return b; + } else if (!b) { + return a; + } else { + return std::min(a, b); + } +} + /** * Copy the codec passed into Conduit's database */ @@ -1707,8 +1782,10 @@ WebrtcVideoConduit::CodecConfigToWebRTCCodec(const VideoCodecConfig* codecInfo, cinst.codecSpecific.H264.level = codecInfo->mLevel; cinst.codecSpecific.H264.packetizationMode = codecInfo->mPacketizationMode; if (codecInfo->mEncodingConstraints.maxBr > 0) { - cinst.maxBitrate = std::min(cinst.maxBitrate, - codecInfo->mEncodingConstraints.maxBr); + // webrtc.org uses kbps, we use bps + cinst.maxBitrate = + MinIgnoreZero(cinst.maxBitrate, + codecInfo->mEncodingConstraints.maxBr)/1000; } if (codecInfo->mEncodingConstraints.maxMbps > 0) { // Not supported yet! @@ -1720,6 +1797,46 @@ WebrtcVideoConduit::CodecConfigToWebRTCCodec(const VideoCodecConfig* codecInfo, cinst.codecSpecific.H264.spsLen = 0; cinst.codecSpecific.H264.ppsData = nullptr; cinst.codecSpecific.H264.ppsLen = 0; + } else { + // TODO(bug 1210175): H264 doesn't support simulcast yet. + for (size_t i = 0; i < codecInfo->mSimulcastEncodings.size(); ++i) { + const VideoCodecConfig::SimulcastEncoding& encoding = + codecInfo->mSimulcastEncodings[i]; + // Make sure the constraints on the whole stream are reflected. + webrtc::SimulcastStream stream; + memset(&stream, 0, sizeof(stream)); + stream.width = cinst.width; + stream.height = cinst.height; + stream.numberOfTemporalLayers = 1; + stream.maxBitrate = cinst.maxBitrate; + stream.targetBitrate = cinst.targetBitrate; + stream.minBitrate = cinst.minBitrate; + stream.qpMax = cinst.qpMax; + strncpy(stream.rid, encoding.rid.c_str(), sizeof(stream.rid)-1); + stream.rid[sizeof(stream.rid) - 1] = 0; + + // Apply encoding-specific constraints. + stream.width = MinIgnoreZero( + stream.width, + (unsigned short)encoding.constraints.maxWidth); + stream.height = MinIgnoreZero( + stream.height, + (unsigned short)encoding.constraints.maxHeight); + + if (encoding.constraints.maxBr) { + // webrtc.org uses kbps, we use bps + stream.maxBitrate = encoding.constraints.maxBr/1000; + stream.minBitrate = MinIgnoreZero(stream.minBitrate, stream.maxBitrate); + stream.targetBitrate = MinIgnoreZero(stream.targetBitrate, + stream.maxBitrate); + } + + // webrtc.org expects simulcast streams to be ordered by increasing + // fidelity, our jsep code does the opposite. + cinst.simulcastStream[codecInfo->mSimulcastEncodings.size()-i-1] = stream; + } + + cinst.numberOfSimulcastStreams = codecInfo->mSimulcastEncodings.size(); } } diff --git a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp index ad309cb412..f10ffdeb45 100644 --- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp +++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp @@ -162,8 +162,12 @@ NegotiatedDetailsToVideoCodecConfigs(const JsepTrackNegotiatedDetails& aDetails, } for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) { - if (aDetails.GetEncoding(i).HasFormat(codec->mDefaultPt)) { - // TODO(bug 1192390): Roll constraints into simulcast entries + const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i)); + if (jsepEncoding.HasFormat(codec->mDefaultPt)) { + VideoCodecConfig::SimulcastEncoding encoding; + encoding.rid = jsepEncoding.mRid; + encoding.constraints = jsepEncoding.mConstraints; + config->mSimulcastEncodings.push_back(encoding); } } aConfigs->values.push_back(config); diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp index 537b866a31..1533d49d2d 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp @@ -11,6 +11,7 @@ #include "prcvar.h" #include "mozilla/Telemetry.h" +#include "browser_logging/WebRtcLog.h" #if !defined(MOZILLA_EXTERNAL_LINKAGE) #include "mozilla/dom/RTCPeerConnectionBinding.h" @@ -135,6 +136,7 @@ nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread, } } + EnableWebRtcLog(); return NS_OK; } @@ -155,6 +157,8 @@ void PeerConnectionCtx::Destroy() { delete gInstance; gInstance = nullptr; } + + StopWebRtcLog(); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) diff --git a/media/webrtc/signaling/test/FakeIPC.cpp b/media/webrtc/signaling/test/FakeIPC.cpp new file mode 100644 index 0000000000..767082c29e --- /dev/null +++ b/media/webrtc/signaling/test/FakeIPC.cpp @@ -0,0 +1,35 @@ +/* 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 "FakeIPC.h" +#include + +// The implementations can't be in the .h file for some annoying reason + +/* static */ void +PlatformThread:: YieldCurrentThread() +{ + sleep(1); +} + +namespace base { + +void AtExitManager::RegisterCallback(AtExitCallbackType func, void* param) +{ +} + +} + +// see atomicops_internals_x86_gcc.h +// This cheats to get the unittests to build + +struct AtomicOps_x86CPUFeatureStruct { + bool field1; + bool field2; +}; + +struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures = { + false, + false, +}; diff --git a/media/webrtc/signaling/test/FakeIPC.h b/media/webrtc/signaling/test/FakeIPC.h new file mode 100644 index 0000000000..e13fc271d2 --- /dev/null +++ b/media/webrtc/signaling/test/FakeIPC.h @@ -0,0 +1,22 @@ +/* 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 FAKE_IPC_H_ +#define FAKE_IPC_H_ +#include + +class PlatformThread { +public: + static void YieldCurrentThread(); +}; + +namespace base { +class AtExitManager { +public: + typedef void (*AtExitCallbackType)(void*); + + static void RegisterCallback(AtExitCallbackType func, void* param); +}; +} +#endif diff --git a/media/webrtc/signaling/test/jsep_session_unittest.cpp b/media/webrtc/signaling/test/jsep_session_unittest.cpp index 34637b934a..cb577baaca 100644 --- a/media/webrtc/signaling/test/jsep_session_unittest.cpp +++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp @@ -30,6 +30,9 @@ #include "mtransport_test_utils.h" +#include "FakeIPC.h" +#include "FakeIPC.cpp" + namespace mozilla { static std::string kAEqualsCandidate("a=candidate:"); const static size_t kNumCandidatesPerComponent = 3; diff --git a/media/webrtc/signaling/test/jsep_track_unittest.cpp b/media/webrtc/signaling/test/jsep_track_unittest.cpp index c2c36857f0..6a6fac97cd 100644 --- a/media/webrtc/signaling/test/jsep_track_unittest.cpp +++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp @@ -18,6 +18,9 @@ #include "mtransport_test_utils.h" +#include "FakeIPC.h" +#include "FakeIPC.cpp" + namespace mozilla { class JsepTrackTest : public ::testing::Test diff --git a/media/webrtc/signaling/test/mediaconduit_unittests.cpp b/media/webrtc/signaling/test/mediaconduit_unittests.cpp index 59b37205c1..874ad4ce13 100644 --- a/media/webrtc/signaling/test/mediaconduit_unittests.cpp +++ b/media/webrtc/signaling/test/mediaconduit_unittests.cpp @@ -21,6 +21,9 @@ using namespace std; #include "runnable_utils.h" #include "signaling/src/common/EncodingConstraints.h" +#include "FakeIPC.h" +#include "FakeIPC.cpp" + #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" #include "gtest_utils.h" diff --git a/media/webrtc/signaling/test/mediapipeline_unittest.cpp b/media/webrtc/signaling/test/mediapipeline_unittest.cpp index 06febf8c9f..7471ec9256 100644 --- a/media/webrtc/signaling/test/mediapipeline_unittest.cpp +++ b/media/webrtc/signaling/test/mediapipeline_unittest.cpp @@ -35,6 +35,9 @@ #include "webrtc/modules/interface/module_common_types.h" +#include "FakeIPC.h" +#include "FakeIPC.cpp" + #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" #include "gtest_utils.h" diff --git a/media/webrtc/signaling/test/sdp_file_parser.cpp b/media/webrtc/signaling/test/sdp_file_parser.cpp index b798430312..2ae8c2153d 100644 --- a/media/webrtc/signaling/test/sdp_file_parser.cpp +++ b/media/webrtc/signaling/test/sdp_file_parser.cpp @@ -17,6 +17,9 @@ #include "signaling/src/sdp/SipccSdpParser.h" +#include "FakeIPC.h" +#include "FakeIPC.cpp" + namespace mozilla { const std::string kDefaultFilename((char *)"/tmp/sdp.bin"); diff --git a/media/webrtc/signaling/test/sdp_unittests.cpp b/media/webrtc/signaling/test/sdp_unittests.cpp index b2950891ea..a13684370f 100644 --- a/media/webrtc/signaling/test/sdp_unittests.cpp +++ b/media/webrtc/signaling/test/sdp_unittests.cpp @@ -43,6 +43,9 @@ extern "C" { #endif #define CRLF "\r\n" +#include "FakeIPC.h" +#include "FakeIPC.cpp" + using namespace mozilla; namespace test { diff --git a/media/webrtc/signaling/test/signaling_unittests.cpp b/media/webrtc/signaling/test/signaling_unittests.cpp index fd176945e5..8662eb68db 100644 --- a/media/webrtc/signaling/test/signaling_unittests.cpp +++ b/media/webrtc/signaling/test/signaling_unittests.cpp @@ -44,6 +44,9 @@ #include "PeerConnectionImplEnumsBinding.cpp" #endif +#include "FakeIPC.h" +#include "FakeIPC.cpp" + #include "ice_ctx.h" #include "ice_peer_ctx.h" diff --git a/media/webrtc/trunk/webrtc/base/base.gyp b/media/webrtc/trunk/webrtc/base/base.gyp index 164a8beb67..e7c6c90307 100644 --- a/media/webrtc/trunk/webrtc/base/base.gyp +++ b/media/webrtc/trunk/webrtc/base/base.gyp @@ -29,8 +29,13 @@ 'target_name': 'rtc_base_approved', 'type': 'static_library', 'sources': [ + 'bitbuffer.cc', + 'bitbuffer.h', + 'buffer.cc', + 'buffer.h', 'checks.cc', 'checks.h', + 'constructormagic.h', 'event.cc', 'event.h', 'event_tracer.cc', diff --git a/media/webrtc/trunk/webrtc/base/bitbuffer.cc b/media/webrtc/trunk/webrtc/base/bitbuffer.cc new file mode 100644 index 0000000000..1aa245e78c --- /dev/null +++ b/media/webrtc/trunk/webrtc/base/bitbuffer.cc @@ -0,0 +1,296 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/bitbuffer.h" + +#include +#include + +#include "webrtc/base/checks.h" + +namespace { + +// Returns the lowest (right-most) |bit_count| bits in |byte|. +uint8_t LowestBits(uint8_t byte, size_t bit_count) { + RTC_DCHECK_LE(bit_count, 8u); + return byte & ((1 << bit_count) - 1); +} + +// Returns the highest (left-most) |bit_count| bits in |byte|, shifted to the +// lowest bits (to the right). +uint8_t HighestBits(uint8_t byte, size_t bit_count) { + RTC_DCHECK_LE(bit_count, 8u); + uint8_t shift = 8 - static_cast(bit_count); + uint8_t mask = 0xFF << shift; + return (byte & mask) >> shift; +} + +// Returns the highest byte of |val| in a uint8_t. +uint8_t HighestByte(uint64_t val) { + return static_cast(val >> 56); +} + +// Returns the result of writing partial data from |source|, of +// |source_bit_count| size in the highest bits, to |target| at +// |target_bit_offset| from the highest bit. +uint8_t WritePartialByte(uint8_t source, + size_t source_bit_count, + uint8_t target, + size_t target_bit_offset) { + RTC_DCHECK(target_bit_offset < 8); + RTC_DCHECK(source_bit_count < 9); + RTC_DCHECK(source_bit_count <= (8 - target_bit_offset)); + // Generate a mask for just the bits we're going to overwrite, so: + uint8_t mask = + // The number of bits we want, in the most significant bits... + static_cast(0xFF << (8 - source_bit_count)) + // ...shifted over to the target offset from the most signficant bit. + >> target_bit_offset; + + // We want the target, with the bits we'll overwrite masked off, or'ed with + // the bits from the source we want. + return (target & ~mask) | (source >> target_bit_offset); +} + +// Counts the number of bits used in the binary representation of val. +size_t CountBits(uint64_t val) { + size_t bit_count = 0; + while (val != 0) { + bit_count++; + val >>= 1; + } + return bit_count; +} + +} // namespace + +namespace rtc { + +BitBuffer::BitBuffer(const uint8_t* bytes, size_t byte_count) + : bytes_(bytes), byte_count_(byte_count), byte_offset_(), bit_offset_() { + RTC_DCHECK(static_cast(byte_count_) <= + std::numeric_limits::max()); +} + +uint64_t BitBuffer::RemainingBitCount() const { + return (static_cast(byte_count_) - byte_offset_) * 8 - bit_offset_; +} + +bool BitBuffer::ReadUInt8(uint8_t* val) { + uint32_t bit_val; + if (!ReadBits(&bit_val, sizeof(uint8_t) * 8)) { + return false; + } + RTC_DCHECK(bit_val <= std::numeric_limits::max()); + *val = static_cast(bit_val); + return true; +} + +bool BitBuffer::ReadUInt16(uint16_t* val) { + uint32_t bit_val; + if (!ReadBits(&bit_val, sizeof(uint16_t) * 8)) { + return false; + } + RTC_DCHECK(bit_val <= std::numeric_limits::max()); + *val = static_cast(bit_val); + return true; +} + +bool BitBuffer::ReadUInt32(uint32_t* val) { + return ReadBits(val, sizeof(uint32_t) * 8); +} + +bool BitBuffer::PeekBits(uint32_t* val, size_t bit_count) { + if (!val || bit_count > RemainingBitCount() || bit_count > 32) { + return false; + } + const uint8_t* bytes = bytes_ + byte_offset_; + size_t remaining_bits_in_current_byte = 8 - bit_offset_; + uint32_t bits = LowestBits(*bytes++, remaining_bits_in_current_byte); + // If we're reading fewer bits than what's left in the current byte, just + // return the portion of this byte that we need. + if (bit_count < remaining_bits_in_current_byte) { + *val = HighestBits(bits, bit_offset_ + bit_count); + return true; + } + // Otherwise, subtract what we've read from the bit count and read as many + // full bytes as we can into bits. + bit_count -= remaining_bits_in_current_byte; + while (bit_count >= 8) { + bits = (bits << 8) | *bytes++; + bit_count -= 8; + } + // Whatever we have left is smaller than a byte, so grab just the bits we need + // and shift them into the lowest bits. + if (bit_count > 0) { + bits <<= bit_count; + bits |= HighestBits(*bytes, bit_count); + } + *val = bits; + return true; +} + +bool BitBuffer::ReadBits(uint32_t* val, size_t bit_count) { + return PeekBits(val, bit_count) && ConsumeBits(bit_count); +} + +bool BitBuffer::ConsumeBytes(size_t byte_count) { + return ConsumeBits(byte_count * 8); +} + +bool BitBuffer::ConsumeBits(size_t bit_count) { + if (bit_count > RemainingBitCount()) { + return false; + } + + byte_offset_ += (bit_offset_ + bit_count) / 8; + bit_offset_ = (bit_offset_ + bit_count) % 8; + return true; +} + +bool BitBuffer::ReadExponentialGolomb(uint32_t* val) { + if (!val) { + return false; + } + // Store off the current byte/bit offset, in case we want to restore them due + // to a failed parse. + size_t original_byte_offset = byte_offset_; + size_t original_bit_offset = bit_offset_; + + // Count the number of leading 0 bits by peeking/consuming them one at a time. + size_t zero_bit_count = 0; + uint32_t peeked_bit; + while (PeekBits(&peeked_bit, 1) && peeked_bit == 0) { + zero_bit_count++; + ConsumeBits(1); + } + + // We should either be at the end of the stream, or the next bit should be 1. + RTC_DCHECK(!PeekBits(&peeked_bit, 1) || peeked_bit == 1); + + // The bit count of the value is the number of zeros + 1. Make sure that many + // bits fits in a uint32_t and that we have enough bits left for it, and then + // read the value. + size_t value_bit_count = zero_bit_count + 1; + if (value_bit_count > 32 || !ReadBits(val, value_bit_count)) { + RTC_CHECK(Seek(original_byte_offset, original_bit_offset)); + return false; + } + *val -= 1; + return true; +} + +bool BitBuffer::ReadSignedExponentialGolomb(int32_t* val) { + uint32_t unsigned_val; + if (!ReadExponentialGolomb(&unsigned_val)) { + return false; + } + if ((unsigned_val & 1) == 0) { + *val = -static_cast(unsigned_val / 2); + } else { + *val = (unsigned_val + 1) / 2; + } + return true; +} + +void BitBuffer::GetCurrentOffset( + size_t* out_byte_offset, size_t* out_bit_offset) { + RTC_CHECK(out_byte_offset != NULL); + RTC_CHECK(out_bit_offset != NULL); + *out_byte_offset = byte_offset_; + *out_bit_offset = bit_offset_; +} + +bool BitBuffer::Seek(size_t byte_offset, size_t bit_offset) { + if (byte_offset > byte_count_ || bit_offset > 7 || + (byte_offset == byte_count_ && bit_offset > 0)) { + return false; + } + byte_offset_ = byte_offset; + bit_offset_ = bit_offset; + return true; +} + +BitBufferWriter::BitBufferWriter(uint8_t* bytes, size_t byte_count) + : BitBuffer(bytes, byte_count), writable_bytes_(bytes) { +} + +bool BitBufferWriter::WriteUInt8(uint8_t val) { + return WriteBits(val, sizeof(uint8_t) * 8); +} + +bool BitBufferWriter::WriteUInt16(uint16_t val) { + return WriteBits(val, sizeof(uint16_t) * 8); +} + +bool BitBufferWriter::WriteUInt32(uint32_t val) { + return WriteBits(val, sizeof(uint32_t) * 8); +} + +bool BitBufferWriter::WriteBits(uint64_t val, size_t bit_count) { + if (bit_count > RemainingBitCount()) { + return false; + } + size_t total_bits = bit_count; + + // For simplicity, push the bits we want to read from val to the highest bits. + val <<= (sizeof(uint64_t) * 8 - bit_count); + + uint8_t* bytes = writable_bytes_ + byte_offset_; + + // The first byte is relatively special; the bit offset to write to may put us + // in the middle of the byte, and the total bit count to write may require we + // save the bits at the end of the byte. + size_t remaining_bits_in_current_byte = 8 - bit_offset_; + size_t bits_in_first_byte = + std::min(bit_count, remaining_bits_in_current_byte); + *bytes = WritePartialByte( + HighestByte(val), bits_in_first_byte, *bytes, bit_offset_); + if (bit_count <= remaining_bits_in_current_byte) { + // Nothing left to write, so quit early. + return ConsumeBits(total_bits); + } + + // Subtract what we've written from the bit count, shift it off the value, and + // write the remaining full bytes. + val <<= bits_in_first_byte; + bytes++; + bit_count -= bits_in_first_byte; + while (bit_count >= 8) { + *bytes++ = HighestByte(val); + val <<= 8; + bit_count -= 8; + } + + // Last byte may also be partial, so write the remaining bits from the top of + // val. + if (bit_count > 0) { + *bytes = WritePartialByte(HighestByte(val), bit_count, *bytes, 0); + } + + // All done! Consume the bits we've written. + return ConsumeBits(total_bits); +} + +bool BitBufferWriter::WriteExponentialGolomb(uint32_t val) { + // We don't support reading UINT32_MAX, because it doesn't fit in a uint32_t + // when encoded, so don't support writing it either. + if (val == std::numeric_limits::max()) { + return false; + } + uint64_t val_to_encode = static_cast(val) + 1; + + // We need to write CountBits(val+1) 0s and then val+1. Since val (as a + // uint64_t) has leading zeros, we can just write the total golomb encoded + // size worth of bits, knowing the value will appear last. + return WriteBits(val_to_encode, CountBits(val_to_encode) * 2 - 1); +} + +} // namespace rtc diff --git a/media/webrtc/trunk/webrtc/base/bitbuffer.h b/media/webrtc/trunk/webrtc/base/bitbuffer.h new file mode 100644 index 0000000000..8ea044e041 --- /dev/null +++ b/media/webrtc/trunk/webrtc/base/bitbuffer.h @@ -0,0 +1,122 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_BITBUFFER_H_ +#define WEBRTC_BASE_BITBUFFER_H_ + +#include // For integer types. +#include // For size_t. + +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +// A class, similar to ByteBuffer, that can parse bit-sized data out of a set of +// bytes. Has a similar API to ByteBuffer, plus methods for reading bit-sized +// and exponential golomb encoded data. For a writable version, use +// BitBufferWriter. Unlike ByteBuffer, this class doesn't make a copy of the +// source bytes, so it can be used on read-only data. +// Sizes/counts specify bits/bytes, for clarity. +// Byte order is assumed big-endian/network. +class BitBuffer { + public: + BitBuffer(const uint8_t* bytes, size_t byte_count); + + // Gets the current offset, in bytes/bits, from the start of the buffer. The + // bit offset is the offset into the current byte, in the range [0,7]. + void GetCurrentOffset(size_t* out_byte_offset, size_t* out_bit_offset); + + // The remaining bits in the byte buffer. + uint64_t RemainingBitCount() const; + + // Reads byte-sized values from the buffer. Returns false if there isn't + // enough data left for the specified type. + bool ReadUInt8(uint8_t* val); + bool ReadUInt16(uint16_t* val); + bool ReadUInt32(uint32_t* val); + + // Reads bit-sized values from the buffer. Returns false if there isn't enough + // data left for the specified bit count.. + bool ReadBits(uint32_t* val, size_t bit_count); + + // Peeks bit-sized values from the buffer. Returns false if there isn't enough + // data left for the specified number of bits. Doesn't move the current + // offset. + bool PeekBits(uint32_t* val, size_t bit_count); + + // Reads the exponential golomb encoded value at the current offset. + // Exponential golomb values are encoded as: + // 1) x = source val + 1 + // 2) In binary, write [countbits(x) - 1] 0s, then x + // To decode, we count the number of leading 0 bits, read that many + 1 bits, + // and increment the result by 1. + // Returns false if there isn't enough data left for the specified type, or if + // the value wouldn't fit in a uint32_t. + bool ReadExponentialGolomb(uint32_t* val); + // Reads signed exponential golomb values at the current offset. Signed + // exponential golomb values are just the unsigned values mapped to the + // sequence 0, 1, -1, 2, -2, etc. in order. + bool ReadSignedExponentialGolomb(int32_t* val); + + // Moves current position |byte_count| bytes forward. Returns false if + // there aren't enough bytes left in the buffer. + bool ConsumeBytes(size_t byte_count); + // Moves current position |bit_count| bits forward. Returns false if + // there aren't enough bits left in the buffer. + bool ConsumeBits(size_t bit_count); + + // Sets the current offset to the provied byte/bit offsets. The bit + // offset is from the given byte, in the range [0,7]. + bool Seek(size_t byte_offset, size_t bit_offset); + + protected: + const uint8_t* const bytes_; + // The total size of |bytes_|. + size_t byte_count_; + // The current offset, in bytes, from the start of |bytes_|. + size_t byte_offset_; + // The current offset, in bits, into the current byte. + size_t bit_offset_; + + RTC_DISALLOW_COPY_AND_ASSIGN(BitBuffer); +}; + +// A BitBuffer API for write operations. Supports symmetric write APIs to the +// reading APIs of BitBuffer. Note that the read/write offset is shared with the +// BitBuffer API, so both reading and writing will consume bytes/bits. +class BitBufferWriter : public BitBuffer { + public: + // Constructs a bit buffer for the writable buffer of |bytes|. + BitBufferWriter(uint8_t* bytes, size_t byte_count); + + // Writes byte-sized values from the buffer. Returns false if there isn't + // enough data left for the specified type. + bool WriteUInt8(uint8_t val); + bool WriteUInt16(uint16_t val); + bool WriteUInt32(uint32_t val); + + // Writes bit-sized values to the buffer. Returns false if there isn't enough + // room left for the specified number of bits. + bool WriteBits(uint64_t val, size_t bit_count); + + // Writes the exponential golomb encoded version of the supplied value. + // Returns false if there isn't enough room left for the value. + bool WriteExponentialGolomb(uint32_t val); + + private: + // The buffer, as a writable array. + uint8_t* const writable_bytes_; + + RTC_DISALLOW_COPY_AND_ASSIGN(BitBufferWriter); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BITBUFFER_H_ diff --git a/media/webrtc/trunk/webrtc/base/bitbuffer_unittest.cc b/media/webrtc/trunk/webrtc/base/bitbuffer_unittest.cc new file mode 100644 index 0000000000..ce42257255 --- /dev/null +++ b/media/webrtc/trunk/webrtc/base/bitbuffer_unittest.cc @@ -0,0 +1,330 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/arraysize.h" +#include "webrtc/base/bitbuffer.h" +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +TEST(BitBufferTest, ConsumeBits) { + const uint8_t bytes[64] = {0}; + BitBuffer buffer(bytes, 32); + uint64_t total_bits = 32 * 8; + EXPECT_EQ(total_bits, buffer.RemainingBitCount()); + EXPECT_TRUE(buffer.ConsumeBits(3)); + total_bits -= 3; + EXPECT_EQ(total_bits, buffer.RemainingBitCount()); + EXPECT_TRUE(buffer.ConsumeBits(3)); + total_bits -= 3; + EXPECT_EQ(total_bits, buffer.RemainingBitCount()); + EXPECT_TRUE(buffer.ConsumeBits(15)); + total_bits -= 15; + EXPECT_EQ(total_bits, buffer.RemainingBitCount()); + EXPECT_TRUE(buffer.ConsumeBits(37)); + total_bits -= 37; + EXPECT_EQ(total_bits, buffer.RemainingBitCount()); + + EXPECT_FALSE(buffer.ConsumeBits(32 * 8)); + EXPECT_EQ(total_bits, buffer.RemainingBitCount()); +} + +TEST(BitBufferTest, ReadBytesAligned) { + const uint8_t bytes[] = {0x0A, 0xBC, 0xDE, 0xF1, 0x23, 0x45, 0x67, 0x89}; + uint8_t val8; + uint16_t val16; + uint32_t val32; + BitBuffer buffer(bytes, 8); + EXPECT_TRUE(buffer.ReadUInt8(&val8)); + EXPECT_EQ(0x0Au, val8); + EXPECT_TRUE(buffer.ReadUInt8(&val8)); + EXPECT_EQ(0xBCu, val8); + EXPECT_TRUE(buffer.ReadUInt16(&val16)); + EXPECT_EQ(0xDEF1u, val16); + EXPECT_TRUE(buffer.ReadUInt32(&val32)); + EXPECT_EQ(0x23456789u, val32); +} + +TEST(BitBufferTest, ReadBytesOffset4) { + const uint8_t bytes[] = {0x0A, 0xBC, 0xDE, 0xF1, 0x23, + 0x45, 0x67, 0x89, 0x0A}; + uint8_t val8; + uint16_t val16; + uint32_t val32; + BitBuffer buffer(bytes, 9); + EXPECT_TRUE(buffer.ConsumeBits(4)); + + EXPECT_TRUE(buffer.ReadUInt8(&val8)); + EXPECT_EQ(0xABu, val8); + EXPECT_TRUE(buffer.ReadUInt8(&val8)); + EXPECT_EQ(0xCDu, val8); + EXPECT_TRUE(buffer.ReadUInt16(&val16)); + EXPECT_EQ(0xEF12u, val16); + EXPECT_TRUE(buffer.ReadUInt32(&val32)); + EXPECT_EQ(0x34567890u, val32); +} + +TEST(BitBufferTest, ReadBytesOffset3) { + // The pattern we'll check against is counting down from 0b1111. It looks + // weird here because it's all offset by 3. + // Byte pattern is: + // 56701234 + // 0b00011111, + // 0b11011011, + // 0b10010111, + // 0b01010011, + // 0b00001110, + // 0b11001010, + // 0b10000110, + // 0b01000010 + // xxxxx <-- last 5 bits unused. + + // The bytes. It almost looks like counting down by two at a time, except the + // jump at 5->3->0, since that's when the high bit is turned off. + const uint8_t bytes[] = {0x1F, 0xDB, 0x97, 0x53, 0x0E, 0xCA, 0x86, 0x42}; + + uint8_t val8; + uint16_t val16; + uint32_t val32; + BitBuffer buffer(bytes, 8); + EXPECT_TRUE(buffer.ConsumeBits(3)); + EXPECT_TRUE(buffer.ReadUInt8(&val8)); + EXPECT_EQ(0xFEu, val8); + EXPECT_TRUE(buffer.ReadUInt16(&val16)); + EXPECT_EQ(0xDCBAu, val16); + EXPECT_TRUE(buffer.ReadUInt32(&val32)); + EXPECT_EQ(0x98765432u, val32); + // 5 bits left unread. Not enough to read a uint8_t. + EXPECT_EQ(5u, buffer.RemainingBitCount()); + EXPECT_FALSE(buffer.ReadUInt8(&val8)); +} + +TEST(BitBufferTest, ReadBits) { + // Bit values are: + // 0b01001101, + // 0b00110010 + const uint8_t bytes[] = {0x4D, 0x32}; + uint32_t val; + BitBuffer buffer(bytes, 2); + EXPECT_TRUE(buffer.ReadBits(&val, 3)); + // 0b010 + EXPECT_EQ(0x2u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 2)); + // 0b01 + EXPECT_EQ(0x1u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 7)); + // 0b1010011 + EXPECT_EQ(0x53u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 2)); + // 0b00 + EXPECT_EQ(0x0u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 1)); + // 0b1 + EXPECT_EQ(0x1u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 1)); + // 0b0 + EXPECT_EQ(0x0u, val); + + EXPECT_FALSE(buffer.ReadBits(&val, 1)); +} + +TEST(BitBufferTest, SetOffsetValues) { + uint8_t bytes[4] = {0}; + BitBufferWriter buffer(bytes, 4); + + size_t byte_offset, bit_offset; + // Bit offsets are [0,7]. + EXPECT_TRUE(buffer.Seek(0, 0)); + EXPECT_TRUE(buffer.Seek(0, 7)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(0u, byte_offset); + EXPECT_EQ(7u, bit_offset); + EXPECT_FALSE(buffer.Seek(0, 8)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(0u, byte_offset); + EXPECT_EQ(7u, bit_offset); + // Byte offsets are [0,length]. At byte offset length, the bit offset must be + // 0. + EXPECT_TRUE(buffer.Seek(0, 0)); + EXPECT_TRUE(buffer.Seek(2, 4)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(2u, byte_offset); + EXPECT_EQ(4u, bit_offset); + EXPECT_TRUE(buffer.Seek(4, 0)); + EXPECT_FALSE(buffer.Seek(5, 0)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(4u, byte_offset); + EXPECT_EQ(0u, bit_offset); + EXPECT_FALSE(buffer.Seek(4, 1)); + + // Disable death test on Android because it relies on fork() and doesn't play + // nicely. +#if defined(GTEST_HAS_DEATH_TEST) +#if !defined(WEBRTC_ANDROID) + // Passing a NULL out parameter is death. + EXPECT_DEATH(buffer.GetCurrentOffset(&byte_offset, NULL), ""); +#endif +#endif +} + +uint64_t GolombEncoded(uint32_t val) { + val++; + uint32_t bit_counter = val; + uint64_t bit_count = 0; + while (bit_counter > 0) { + bit_count++; + bit_counter >>= 1; + } + return static_cast(val) << (64 - (bit_count * 2 - 1)); +} + +TEST(BitBufferTest, GolombUint32Values) { + ByteBuffer byteBuffer; + byteBuffer.Resize(16); + BitBuffer buffer(reinterpret_cast(byteBuffer.Data()), + byteBuffer.Capacity()); + // Test over the uint32_t range with a large enough step that the test doesn't + // take forever. Around 20,000 iterations should do. + const int kStep = std::numeric_limits::max() / 20000; + for (uint32_t i = 0; i < std::numeric_limits::max() - kStep; + i += kStep) { + uint64_t encoded_val = GolombEncoded(i); + byteBuffer.Clear(); + byteBuffer.WriteUInt64(encoded_val); + uint32_t decoded_val; + EXPECT_TRUE(buffer.Seek(0, 0)); + EXPECT_TRUE(buffer.ReadExponentialGolomb(&decoded_val)); + EXPECT_EQ(i, decoded_val); + } +} + +TEST(BitBufferTest, SignedGolombValues) { + uint8_t golomb_bits[] = { + 0x80, // 1 + 0x40, // 010 + 0x60, // 011 + 0x20, // 00100 + 0x38, // 00111 + }; + int32_t expected[] = {0, 1, -1, 2, -3}; + for (size_t i = 0; i < sizeof(golomb_bits); ++i) { + BitBuffer buffer(&golomb_bits[i], 1); + int32_t decoded_val; + ASSERT_TRUE(buffer.ReadSignedExponentialGolomb(&decoded_val)); + EXPECT_EQ(expected[i], decoded_val) + << "Mismatch in expected/decoded value for golomb_bits[" << i + << "]: " << static_cast(golomb_bits[i]); + } +} + +TEST(BitBufferTest, NoGolombOverread) { + const uint8_t bytes[] = {0x00, 0xFF, 0xFF}; + // Make sure the bit buffer correctly enforces byte length on golomb reads. + // If it didn't, the above buffer would be valid at 3 bytes. + BitBuffer buffer(bytes, 1); + uint32_t decoded_val; + EXPECT_FALSE(buffer.ReadExponentialGolomb(&decoded_val)); + + BitBuffer longer_buffer(bytes, 2); + EXPECT_FALSE(longer_buffer.ReadExponentialGolomb(&decoded_val)); + + BitBuffer longest_buffer(bytes, 3); + EXPECT_TRUE(longest_buffer.ReadExponentialGolomb(&decoded_val)); + // Golomb should have read 9 bits, so 0x01FF, and since it is golomb, the + // result is 0x01FF - 1 = 0x01FE. + EXPECT_EQ(0x01FEu, decoded_val); +} + +TEST(BitBufferWriterTest, SymmetricReadWrite) { + uint8_t bytes[16] = {0}; + BitBufferWriter buffer(bytes, 4); + + // Write some bit data at various sizes. + EXPECT_TRUE(buffer.WriteBits(0x2u, 3)); + EXPECT_TRUE(buffer.WriteBits(0x1u, 2)); + EXPECT_TRUE(buffer.WriteBits(0x53u, 7)); + EXPECT_TRUE(buffer.WriteBits(0x0u, 2)); + EXPECT_TRUE(buffer.WriteBits(0x1u, 1)); + EXPECT_TRUE(buffer.WriteBits(0x1ABCDu, 17)); + // That should be all that fits in the buffer. + EXPECT_FALSE(buffer.WriteBits(1, 1)); + + EXPECT_TRUE(buffer.Seek(0, 0)); + uint32_t val; + EXPECT_TRUE(buffer.ReadBits(&val, 3)); + EXPECT_EQ(0x2u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 2)); + EXPECT_EQ(0x1u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 7)); + EXPECT_EQ(0x53u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 2)); + EXPECT_EQ(0x0u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 1)); + EXPECT_EQ(0x1u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 17)); + EXPECT_EQ(0x1ABCDu, val); + // And there should be nothing left. + EXPECT_FALSE(buffer.ReadBits(&val, 1)); +} + +TEST(BitBufferWriterTest, SymmetricBytesMisaligned) { + uint8_t bytes[16] = {0}; + BitBufferWriter buffer(bytes, 16); + + // Offset 3, to get things misaligned. + EXPECT_TRUE(buffer.ConsumeBits(3)); + EXPECT_TRUE(buffer.WriteUInt8(0x12u)); + EXPECT_TRUE(buffer.WriteUInt16(0x3456u)); + EXPECT_TRUE(buffer.WriteUInt32(0x789ABCDEu)); + + buffer.Seek(0, 3); + uint8_t val8; + uint16_t val16; + uint32_t val32; + EXPECT_TRUE(buffer.ReadUInt8(&val8)); + EXPECT_EQ(0x12u, val8); + EXPECT_TRUE(buffer.ReadUInt16(&val16)); + EXPECT_EQ(0x3456u, val16); + EXPECT_TRUE(buffer.ReadUInt32(&val32)); + EXPECT_EQ(0x789ABCDEu, val32); +} + +TEST(BitBufferWriterTest, SymmetricGolomb) { + char test_string[] = "my precious"; + uint8_t bytes[64] = {0}; + BitBufferWriter buffer(bytes, 64); + for (size_t i = 0; i < arraysize(test_string); ++i) { + EXPECT_TRUE(buffer.WriteExponentialGolomb(test_string[i])); + } + buffer.Seek(0, 0); + for (size_t i = 0; i < arraysize(test_string); ++i) { + uint32_t val; + EXPECT_TRUE(buffer.ReadExponentialGolomb(&val)); + EXPECT_LE(val, std::numeric_limits::max()); + EXPECT_EQ(test_string[i], static_cast(val)); + } +} + +TEST(BitBufferWriterTest, WriteClearsBits) { + uint8_t bytes[] = {0xFF, 0xFF}; + BitBufferWriter buffer(bytes, 2); + EXPECT_TRUE(buffer.ConsumeBits(3)); + EXPECT_TRUE(buffer.WriteBits(0, 1)); + EXPECT_EQ(0xEFu, bytes[0]); + EXPECT_TRUE(buffer.WriteBits(0, 3)); + EXPECT_EQ(0xE1u, bytes[0]); + EXPECT_TRUE(buffer.WriteBits(0, 2)); + EXPECT_EQ(0xE0u, bytes[0]); + EXPECT_EQ(0x7F, bytes[1]); +} + +} // namespace rtc diff --git a/media/webrtc/trunk/webrtc/base/buffer.h b/media/webrtc/trunk/webrtc/base/buffer.h index 07345a96ae..fead5048a1 100644 --- a/media/webrtc/trunk/webrtc/base/buffer.h +++ b/media/webrtc/trunk/webrtc/base/buffer.h @@ -13,7 +13,8 @@ #include -#include "webrtc/base/common.h" +// common.h isn't in the rtc_approved list +//#include "webrtc/base/common.h" #include "webrtc/base/scoped_ptr.h" namespace rtc { @@ -52,12 +53,12 @@ class Buffer { } void SetData(const void* data, size_t size) { - ASSERT(data != NULL || size == 0); + assert(data != NULL || size == 0); SetSize(size); memcpy(data_.get(), data, size); } void AppendData(const void* data, size_t size) { - ASSERT(data != NULL || size == 0); + assert(data != NULL || size == 0); size_t old_size = size_; SetSize(size_ + size); memcpy(data_.get() + old_size, data, size); @@ -76,7 +77,7 @@ class Buffer { } void TransferTo(Buffer* buf) { - ASSERT(buf != NULL); + assert(buf != NULL); buf->data_.reset(data_.release()); buf->size_ = size_; buf->capacity_ = capacity_; diff --git a/media/webrtc/trunk/webrtc/base/checks.h b/media/webrtc/trunk/webrtc/base/checks.h index 521586844a..ea3fb37c0d 100644 --- a/media/webrtc/trunk/webrtc/base/checks.h +++ b/media/webrtc/trunk/webrtc/base/checks.h @@ -91,6 +91,8 @@ namespace rtc { LAZY_STREAM(rtc::FatalMessage(__FILE__, __LINE__).stream(), !(condition)) \ << "Check failed: " #condition << std::endl << "# " +#define RTC_CHECK(condition) CHECK(condition) + // Helper macro for binary operators. // Don't use this macro directly in your code, use CHECK_EQ et al below. // @@ -185,6 +187,36 @@ DEFINE_CHECK_OP_IMPL(GT, > ) #define DCHECK_GT(v1, v2) EAT_STREAM_PARAMETERS((v1) > (v2)) #endif +#define RTC_CHECK_EQ(val1, val2) CHECK_OP(EQ, ==, val1, val2) +#define RTC_CHECK_NE(val1, val2) CHECK_OP(NE, !=, val1, val2) +#define RTC_CHECK_LE(val1, val2) CHECK_OP(LE, <=, val1, val2) +#define RTC_CHECK_LT(val1, val2) CHECK_OP(LT, < , val1, val2) +#define RTC_CHECK_GE(val1, val2) CHECK_OP(GE, >=, val1, val2) +#define RTC_CHECK_GT(val1, val2) CHECK_OP(GT, > , val1, val2) + +// The RTC_DCHECK macro is equivalent to RTC_CHECK except that it only generates +// code in debug builds. It does reference the condition parameter in all cases, +// though, so callers won't risk getting warnings about unused variables. +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define RTC_DCHECK_IS_ON 1 +#define RTC_DCHECK(condition) CHECK(condition) +#define RTC_DCHECK_EQ(v1, v2) CHECK_EQ(v1, v2) +#define RTC_DCHECK_NE(v1, v2) CHECK_NE(v1, v2) +#define RTC_DCHECK_LE(v1, v2) CHECK_LE(v1, v2) +#define RTC_DCHECK_LT(v1, v2) CHECK_LT(v1, v2) +#define RTC_DCHECK_GE(v1, v2) CHECK_GE(v1, v2) +#define RTC_DCHECK_GT(v1, v2) CHECK_GT(v1, v2) +#else +#define RTC_DCHECK_IS_ON 0 +#define RTC_DCHECK(condition) EAT_STREAM_PARAMETERS(condition) +#define RTC_DCHECK_EQ(v1, v2) EAT_STREAM_PARAMETERS((v1) == (v2)) +#define RTC_DCHECK_NE(v1, v2) EAT_STREAM_PARAMETERS((v1) != (v2)) +#define RTC_DCHECK_LE(v1, v2) EAT_STREAM_PARAMETERS((v1) <= (v2)) +#define RTC_DCHECK_LT(v1, v2) EAT_STREAM_PARAMETERS((v1) < (v2)) +#define RTC_DCHECK_GE(v1, v2) EAT_STREAM_PARAMETERS((v1) >= (v2)) +#define RTC_DCHECK_GT(v1, v2) EAT_STREAM_PARAMETERS((v1) > (v2)) +#endif + // This is identical to LogMessageVoidify but in name. class FatalMessageVoidify { public: diff --git a/media/webrtc/trunk/webrtc/base/constructormagic.h b/media/webrtc/trunk/webrtc/base/constructormagic.h index ceee37de4b..972508b4ca 100644 --- a/media/webrtc/trunk/webrtc/base/constructormagic.h +++ b/media/webrtc/trunk/webrtc/base/constructormagic.h @@ -17,6 +17,8 @@ #undef DISALLOW_ASSIGN #define DISALLOW_ASSIGN(TypeName) \ void operator=(const TypeName&) +#define RTC_DISALLOW_ASSIGN(TypeName) \ + void operator=(const TypeName&) = delete // A macro to disallow the evil copy constructor and operator= functions // This should be used in the private: declarations for a class. @@ -24,6 +26,9 @@ #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ DISALLOW_ASSIGN(TypeName) +#define RTC_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + RTC_DISALLOW_ASSIGN(TypeName) // Alternative, less-accurate legacy name. #undef DISALLOW_EVIL_CONSTRUCTORS @@ -40,6 +45,9 @@ #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ TypeName(); \ DISALLOW_EVIL_CONSTRUCTORS(TypeName) +#define RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + RTC_DISALLOW_COPY_AND_ASSIGN(TypeName) #endif // WEBRTC_BASE_CONSTRUCTORMAGIC_H_ diff --git a/media/webrtc/trunk/webrtc/common_types.cc b/media/webrtc/trunk/webrtc/common_types.cc index b67c18c986..3ed9a19702 100644 --- a/media/webrtc/trunk/webrtc/common_types.cc +++ b/media/webrtc/trunk/webrtc/common_types.cc @@ -30,7 +30,9 @@ RTPHeaderExtension::RTPHeaderExtension() hasAudioLevel(false), audioLevel(0), hasVideoRotation(false), - videoRotation(0) { + videoRotation(0), + hasRID(false), + rid(NULL) { } RTPHeader::RTPHeader() diff --git a/media/webrtc/trunk/webrtc/common_types.h b/media/webrtc/trunk/webrtc/common_types.h index 42502d1254..52f77b8c66 100644 --- a/media/webrtc/trunk/webrtc/common_types.h +++ b/media/webrtc/trunk/webrtc/common_types.h @@ -572,6 +572,7 @@ enum { kConfigParameterSize = 128}; enum { kPayloadNameSize = 32}; enum { kMaxSimulcastStreams = 4}; enum { kMaxTemporalStreams = 4}; +enum { kRIDSize = 32}; enum VideoCodecComplexity { @@ -638,6 +639,9 @@ struct VideoCodecVP9 { bool frameDroppingOn; int keyFrameInterval; bool adaptiveQpMode; + bool automaticResizeOn; + unsigned char numberOfSpatialLayers; + bool flexibleMode; }; // H264 specific. @@ -685,6 +689,7 @@ struct SimulcastStream { unsigned int targetBitrate; // kilobits/sec. unsigned int minBitrate; // kilobits/sec. unsigned int qpMax; // minimum quality + char rid[kRIDSize]; bool operator==(const SimulcastStream& other) const { return width == other.width && @@ -693,7 +698,8 @@ struct SimulcastStream { maxBitrate == other.maxBitrate && targetBitrate == other.targetBitrate && minBitrate == other.minBitrate && - qpMax == other.qpMax; + qpMax == other.qpMax && + strcmp(rid, other.rid) == 0; } bool operator!=(const SimulcastStream& other) const { @@ -728,6 +734,7 @@ struct VideoCodec { unsigned int qpMax; unsigned char numberOfSimulcastStreams; + unsigned char ridId; SimulcastStream simulcastStream[kMaxSimulcastStreams]; VideoCodecMode mode; @@ -848,6 +855,10 @@ struct RTPHeaderExtension { // ts_126114v120700p.pdf bool hasVideoRotation; uint8_t videoRotation; + + // RID values for simulcast; see draft-roach-avtext-rid + bool hasRID; + char *rid; // UTF8 string }; struct RTPHeader { diff --git a/media/webrtc/trunk/webrtc/modules/interface/module_common_types.h b/media/webrtc/trunk/webrtc/modules/interface/module_common_types.h index 3e7f954e4b..7dc430af01 100644 --- a/media/webrtc/trunk/webrtc/modules/interface/module_common_types.h +++ b/media/webrtc/trunk/webrtc/modules/interface/module_common_types.h @@ -15,6 +15,7 @@ #include // memcpy #include +#include #include "webrtc/base/constructormagic.h" #include "webrtc/common_types.h" @@ -31,8 +32,16 @@ struct RTPAudioHeader { }; const int16_t kNoPictureId = -1; +const int16_t kMaxOneBytePictureId = 0x7F; // 7 bits +const int16_t kMaxTwoBytePictureId = 0x7FFF; // 15 bits const int16_t kNoTl0PicIdx = -1; const uint8_t kNoTemporalIdx = 0xFF; +const uint8_t kNoSpatialIdx = 0xFF; +const uint8_t kNoGofIdx = 0xFF; +const uint8_t kNumVp9Buffers = 8; +const size_t kMaxVp9RefPics = 3; +const size_t kMaxVp9FramesInGof = 0xFF; // 8 bits +const size_t kMaxVp9NumberOfSpatialLayers = 8; const int kNoKeyIdx = -1; struct RTPVideoHeaderVP8 { @@ -61,37 +70,164 @@ struct RTPVideoHeaderVP8 { // in a VP8 partition. Otherwise false }; +enum TemporalStructureMode { + kTemporalStructureMode1, // 1 temporal layer structure - i.e., IPPP... + kTemporalStructureMode2, // 2 temporal layers 0-1-0-1... + kTemporalStructureMode3 // 3 temporal layers 0-2-1-2-0-2-1-2... +}; + +struct GofInfoVP9 { + void SetGofInfoVP9(TemporalStructureMode tm) { + switch (tm) { + case kTemporalStructureMode1: + num_frames_in_gof = 1; + temporal_idx[0] = 0; + temporal_up_switch[0] = false; + num_ref_pics[0] = 1; + pid_diff[0][0] = 1; + break; + case kTemporalStructureMode2: + num_frames_in_gof = 2; + temporal_idx[0] = 0; + temporal_up_switch[0] = false; + num_ref_pics[0] = 1; + pid_diff[0][0] = 2; + + temporal_idx[1] = 1; + temporal_up_switch[1] = true; + num_ref_pics[1] = 1; + pid_diff[1][0] = 1; + break; + case kTemporalStructureMode3: + num_frames_in_gof = 4; + temporal_idx[0] = 0; + temporal_up_switch[0] = false; + num_ref_pics[0] = 1; + pid_diff[0][0] = 4; + + temporal_idx[1] = 2; + temporal_up_switch[1] = true; + num_ref_pics[1] = 1; + pid_diff[1][0] = 1; + + temporal_idx[2] = 1; + temporal_up_switch[2] = true; + num_ref_pics[2] = 1; + pid_diff[2][0] = 2; + + temporal_idx[3] = 2; + temporal_up_switch[3] = false; + num_ref_pics[3] = 2; + pid_diff[3][0] = 1; + pid_diff[3][1] = 2; + break; + default: + assert(false); + } + } + + void CopyGofInfoVP9(const GofInfoVP9& src) { + num_frames_in_gof = src.num_frames_in_gof; + for (size_t i = 0; i < num_frames_in_gof; ++i) { + temporal_idx[i] = src.temporal_idx[i]; + temporal_up_switch[i] = src.temporal_up_switch[i]; + num_ref_pics[i] = src.num_ref_pics[i]; + for (uint8_t r = 0; r < num_ref_pics[i]; ++r) { + pid_diff[i][r] = src.pid_diff[i][r]; + } + } + } + + size_t num_frames_in_gof; + uint8_t temporal_idx[kMaxVp9FramesInGof]; + bool temporal_up_switch[kMaxVp9FramesInGof]; + uint8_t num_ref_pics[kMaxVp9FramesInGof]; + uint8_t pid_diff[kMaxVp9FramesInGof][kMaxVp9RefPics]; +}; + +struct RTPVideoHeaderVP9 { + void InitRTPVideoHeaderVP9() { + inter_pic_predicted = false; + flexible_mode = false; + beginning_of_frame = false; + end_of_frame = false; + ss_data_available = false; + picture_id = kNoPictureId; + max_picture_id = kMaxTwoBytePictureId; + tl0_pic_idx = kNoTl0PicIdx; + temporal_idx = kNoTemporalIdx; + spatial_idx = kNoSpatialIdx; + temporal_up_switch = false; + inter_layer_predicted = false; + gof_idx = kNoGofIdx; + num_ref_pics = 0; + num_spatial_layers = 1; + } + + bool inter_pic_predicted; // This layer frame is dependent on previously + // coded frame(s). + bool flexible_mode; // This frame is in flexible mode. + bool beginning_of_frame; // True if this packet is the first in a VP9 layer + // frame. + bool end_of_frame; // True if this packet is the last in a VP9 layer frame. + bool ss_data_available; // True if SS data is available in this payload + // descriptor. + int16_t picture_id; // PictureID index, 15 bits; + // kNoPictureId if PictureID does not exist. + int16_t max_picture_id; // Maximum picture ID index; either 0x7F or 0x7FFF; + int16_t tl0_pic_idx; // TL0PIC_IDX, 8 bits; + // kNoTl0PicIdx means no value provided. + uint8_t temporal_idx; // Temporal layer index, or kNoTemporalIdx. + uint8_t spatial_idx; // Spatial layer index, or kNoSpatialIdx. + bool temporal_up_switch; // True if upswitch to higher frame rate is possible + // starting from this frame. + bool inter_layer_predicted; // Frame is dependent on directly lower spatial + // layer frame. + + uint8_t gof_idx; // Index to predefined temporal frame info in SS data. + + uint8_t num_ref_pics; // Number of reference pictures used by this layer + // frame. + uint8_t pid_diff[kMaxVp9RefPics]; // P_DIFF signaled to derive the PictureID + // of the reference pictures. + int16_t ref_picture_id[kMaxVp9RefPics]; // PictureID of reference pictures. + + // SS data. + size_t num_spatial_layers; // Always populated. + bool spatial_layer_resolution_present; + uint16_t width[kMaxVp9NumberOfSpatialLayers]; + uint16_t height[kMaxVp9NumberOfSpatialLayers]; + GofInfoVP9 gof; +}; + +#if WEBRTC_48_H264_IMPL +// The packetization types that we support: single, aggregated, and fragmented. +enum H264PacketizationTypes { + kH264SingleNalu, // This packet contains a single NAL unit. + kH264StapA, // This packet contains STAP-A (single time + // aggregation) packets. If this packet has an + // associated NAL unit type, it'll be for the + // first such aggregated packet. + kH264FuA, // This packet contains a FU-A (fragmentation + // unit) packet, meaning it is a part of a frame + // that was too large to fit into a single packet. +}; + +struct RTPVideoHeaderH264 { + uint8_t nalu_type; // The NAL unit type. If this is a header for a + // fragmented packet, it's the NAL unit type of + // the original data. If this is the header for an + // aggregated packet, it's the NAL unit type of + // the first NAL unit in the packet. + H264PacketizationTypes packetization_type; +}; +#else +// Mozilla's OpenH264 implementation struct RTPVideoHeaderH264 { bool stap_a; bool single_nalu; }; - -// XXX fix vp9 (bug 1138629) -struct RTPVideoHeaderVP9 { - void InitRTPVideoHeaderVP9() { - nonReference = false; - pictureId = kNoPictureId; - tl0PicIdx = kNoTl0PicIdx; - temporalIdx = kNoTemporalIdx; - layerSync = false; - keyIdx = kNoKeyIdx; - partitionId = 0; - beginningOfPartition = false; - } - - bool nonReference; // Frame is discardable. - int16_t pictureId; // Picture ID index, 15 bits; - // kNoPictureId if PictureID does not exist. - int16_t tl0PicIdx; // TL0PIC_IDX, 8 bits; - // kNoTl0PicIdx means no value provided. - uint8_t temporalIdx; // Temporal layer index, or kNoTemporalIdx. - bool layerSync; // This frame is a layer sync frame. - // Disabled if temporalIdx == kNoTemporalIdx. - int keyIdx; // 5 bits; kNoKeyIdx means not used. - int partitionId; // VP9 partition ID - bool beginningOfPartition; // True if this packet is the first - // in a VP9 partition. Otherwise false -}; +#endif union RTPVideoTypeHeader { RTPVideoHeaderVP8 VP8; @@ -611,6 +747,18 @@ inline AudioFrame& AudioFrame::Append(const AudioFrame& rhs) { return *this; } +namespace { +inline int16_t ClampToInt16(int32_t input) { + if (input < -0x00008000) { + return -0x8000; + } else if (input > 0x00007FFF) { + return 0x7FFF; + } else { + return static_cast(input); + } +} +} + inline AudioFrame& AudioFrame::operator+=(const AudioFrame& rhs) { // Sanity check assert((num_channels_ > 0) && (num_channels_ < 3)); @@ -643,15 +791,9 @@ inline AudioFrame& AudioFrame::operator+=(const AudioFrame& rhs) { } else { // IMPROVEMENT this can be done very fast in assembly for (int i = 0; i < samples_per_channel_ * num_channels_; i++) { - int32_t wrapGuard = + int32_t wrap_guard = static_cast(data_[i]) + static_cast(rhs.data_[i]); - if (wrapGuard < -32768) { - data_[i] = -32768; - } else if (wrapGuard > 32767) { - data_[i] = 32767; - } else { - data_[i] = (int16_t)wrapGuard; - } + data_[i] = ClampToInt16(wrap_guard); } } energy_ = 0xffffffff; @@ -674,15 +816,9 @@ inline AudioFrame& AudioFrame::operator-=(const AudioFrame& rhs) { speech_type_ = kUndefined; for (int i = 0; i < samples_per_channel_ * num_channels_; i++) { - int32_t wrapGuard = + int32_t wrap_guard = static_cast(data_[i]) - static_cast(rhs.data_[i]); - if (wrapGuard < -32768) { - data_[i] = -32768; - } else if (wrapGuard > 32767) { - data_[i] = 32767; - } else { - data_[i] = (int16_t)wrapGuard; - } + data_[i] = ClampToInt16(wrap_guard); } energy_ = 0xffffffff; return *this; @@ -690,11 +826,24 @@ inline AudioFrame& AudioFrame::operator-=(const AudioFrame& rhs) { inline bool IsNewerSequenceNumber(uint16_t sequence_number, uint16_t prev_sequence_number) { + // Distinguish between elements that are exactly 0x8000 apart. + // If s1>s2 and |s1-s2| = 0x8000: IsNewer(s1,s2)=true, IsNewer(s2,s1)=false + // rather than having IsNewer(s1,s2) = IsNewer(s2,s1) = false. + if (static_cast(sequence_number - prev_sequence_number) == 0x8000) { + return sequence_number > prev_sequence_number; + } return sequence_number != prev_sequence_number && static_cast(sequence_number - prev_sequence_number) < 0x8000; } inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) { + // Distinguish between elements that are exactly 0x80000000 apart. + // If t1>t2 and |t1-t2| = 0x80000000: IsNewer(t1,t2)=true, + // IsNewer(t2,t1)=false + // rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false. + if (static_cast(timestamp - prev_timestamp) == 0x80000000) { + return timestamp > prev_timestamp; + } return timestamp != prev_timestamp && static_cast(timestamp - prev_timestamp) < 0x80000000; } @@ -715,6 +864,46 @@ inline uint32_t LatestTimestamp(uint32_t timestamp1, uint32_t timestamp2) { return IsNewerTimestamp(timestamp1, timestamp2) ? timestamp1 : timestamp2; } +// Utility class to unwrap a sequence number to a larger type, for easier +// handling large ranges. Note that sequence numbers will never be unwrapped +// to a negative value. +class SequenceNumberUnwrapper { + public: + SequenceNumberUnwrapper() : last_seq_(-1) {} + + // Get the unwrapped sequence, but don't update the internal state. + int64_t UnwrapWithoutUpdate(uint16_t sequence_number) { + if (last_seq_ == -1) + return sequence_number; + + uint16_t cropped_last = static_cast(last_seq_); + int64_t delta = sequence_number - cropped_last; + if (IsNewerSequenceNumber(sequence_number, cropped_last)) { + if (delta < 0) + delta += (1 << 16); // Wrap forwards. + } else if (delta > 0 && (last_seq_ + delta - (1 << 16)) >= 0) { + // If sequence_number is older but delta is positive, this is a backwards + // wrap-around. However, don't wrap backwards past 0 (unwrapped). + delta -= (1 << 16); + } + + return last_seq_ + delta; + } + + // Only update the internal state to the specified last (unwrapped) sequence. + void UpdateLast(int64_t last_sequence) { last_seq_ = last_sequence; } + + // Unwrap the sequence number and update the internal state. + int64_t Unwrap(uint16_t sequence_number) { + int64_t unwrapped = UnwrapWithoutUpdate(sequence_number); + UpdateLast(unwrapped); + return unwrapped; + } + + private: + int64_t last_seq_; +}; + } // namespace webrtc #endif // MODULE_COMMON_TYPES_H diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_receiver.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_receiver.h index 62835667df..e383923c7f 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_receiver.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_receiver.h @@ -95,6 +95,8 @@ class RtpReceiver { // Returns the current remote CSRCs. virtual int32_t CSRCs(uint32_t array_of_csrc[kRtpCsrcSize]) const = 0; + virtual void GetRID(char rid[256]) const = 0; + // Returns the current energy of the RTP stream received. virtual int32_t Energy(uint8_t array_of_energy[kRtpCsrcSize]) const = 0; }; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h index 4696be4b4b..01a515b0fd 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h @@ -217,6 +217,11 @@ class RtpRtcp : public Module { */ virtual void SetCsrcs(const std::vector& csrcs) = 0; + /* + * Set RID value for the RID header extension or RTCP SDES + */ + virtual int32_t SetRID(const char *rid) = 0; + /* * Turn on/off sending RTX (RFC 4588). The modes can be set as a combination * of values of the enumerator RtxMode. diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h index 8431fe3b50..ac35670f4d 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h @@ -81,6 +81,7 @@ enum RTPExtensionType { kRtpExtensionAbsoluteSendTime, kRtpExtensionVideoRotation, kRtpExtensionTransportSequenceNumber, + kRtpExtensionRID, }; enum RTCPAppSubTypes diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi index 7a144e4ef5..c4b9b3b43d 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi @@ -92,6 +92,8 @@ 'source/rtp_format_h264.h', 'source/rtp_format_vp8.cc', 'source/rtp_format_vp8.h', + 'source/rtp_format_vp9.cc', + 'source/rtp_format_vp9.h', 'source/rtp_format_video_generic.cc', 'source/rtp_format_video_generic.h', 'source/vp8_partition_aggregator.cc', diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format.cc index 67c1abe0f3..cdb9c4920e 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format.cc @@ -13,6 +13,7 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_format_h264.h" #include "webrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h" #include "webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h" namespace webrtc { RtpPacketizer* RtpPacketizer::Create(RtpVideoCodecTypes type, @@ -26,6 +27,8 @@ RtpPacketizer* RtpPacketizer::Create(RtpVideoCodecTypes type, assert(rtp_type_header != NULL); return new RtpPacketizerVp8(rtp_type_header->VP8, max_payload_len); case kRtpVideoVp9: + assert(rtp_type_header != NULL); + return new RtpPacketizerVp9(rtp_type_header->VP9, max_payload_len); case kRtpVideoGeneric: return new RtpPacketizerGeneric(frame_type, max_payload_len); case kRtpVideoNone: @@ -40,7 +43,8 @@ RtpDepacketizer* RtpDepacketizer::Create(RtpVideoCodecTypes type) { return new RtpDepacketizerH264(); case kRtpVideoVp8: return new RtpDepacketizerVp8(); - case kRtpVideoVp9: // XXX fix vp9 packetization (bug 1138629) + case kRtpVideoVp9: + return new RtpDepacketizerVp9(); case kRtpVideoGeneric: return new RtpDepacketizerGeneric(); case kRtpVideoNone: diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc new file mode 100644 index 0000000000..d2f22d5044 --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc @@ -0,0 +1,743 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h" + +#include +#include + +#include + +#include "webrtc/base/bitbuffer.h" +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" + +#define RETURN_FALSE_ON_ERROR(x) \ + if (!(x)) { \ + return false; \ + } + +namespace webrtc { +namespace { +// Length of VP9 payload descriptors' fixed part. +const size_t kFixedPayloadDescriptorBytes = 1; + +// Packet fragmentation mode. If true, packets are split into (almost) equal +// sizes. Otherwise, as many bytes as possible are fit into one packet. +const bool kBalancedMode = true; + +const uint32_t kReservedBitValue0 = 0; + +uint8_t TemporalIdxField(const RTPVideoHeaderVP9& hdr, uint8_t def) { + return (hdr.temporal_idx == kNoTemporalIdx) ? def : hdr.temporal_idx; +} + +uint8_t SpatialIdxField(const RTPVideoHeaderVP9& hdr, uint8_t def) { + return (hdr.spatial_idx == kNoSpatialIdx) ? def : hdr.spatial_idx; +} + +int16_t Tl0PicIdxField(const RTPVideoHeaderVP9& hdr, uint8_t def) { + return (hdr.tl0_pic_idx == kNoTl0PicIdx) ? def : hdr.tl0_pic_idx; +} + +// Picture ID: +// +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | M:0 => picture id is 7 bits. +// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. +// M: | EXTENDED PID | +// +-+-+-+-+-+-+-+-+ +// +size_t PictureIdLength(const RTPVideoHeaderVP9& hdr) { + if (hdr.picture_id == kNoPictureId) + return 0; + return (hdr.max_picture_id == kMaxOneBytePictureId) ? 1 : 2; +} + +bool PictureIdPresent(const RTPVideoHeaderVP9& hdr) { + return PictureIdLength(hdr) > 0; +} + +// Layer indices: +// +// Flexible mode (F=1): Non-flexible mode (F=0): +// +// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ +// +size_t LayerInfoLength(const RTPVideoHeaderVP9& hdr) { + if (hdr.temporal_idx == kNoTemporalIdx && + hdr.spatial_idx == kNoSpatialIdx) { + return 0; + } + return hdr.flexible_mode ? 1 : 2; +} + +bool LayerInfoPresent(const RTPVideoHeaderVP9& hdr) { + return LayerInfoLength(hdr) > 0; +} + +// Reference indices: +// +// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// P,F: | P_DIFF |N| up to 3 times has to be specified. +// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows +// current P_DIFF. +// +size_t RefIndicesLength(const RTPVideoHeaderVP9& hdr) { + if (!hdr.inter_pic_predicted || !hdr.flexible_mode) + return 0; + + RTC_DCHECK_GT(hdr.num_ref_pics, 0U); + RTC_DCHECK_LE(hdr.num_ref_pics, kMaxVp9RefPics); + return hdr.num_ref_pics; +} + +// Scalability structure (SS). +// +// +-+-+-+-+-+-+-+-+ +// V: | N_S |Y|G|-|-|-| +// +-+-+-+-+-+-+-+-+ -| +// Y: | WIDTH | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ . N_S + 1 times +// | HEIGHT | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| +// G: | N_G | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ -| +// N_G: | T |U| R |-|-| (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| . N_G times +// | P_DIFF | (OPTIONAL) . R times . +// +-+-+-+-+-+-+-+-+ -| -| +// +size_t SsDataLength(const RTPVideoHeaderVP9& hdr) { + if (!hdr.ss_data_available) + return 0; + + RTC_DCHECK_GT(hdr.num_spatial_layers, 0U); + RTC_DCHECK_LE(hdr.num_spatial_layers, kMaxVp9NumberOfSpatialLayers); + RTC_DCHECK_LE(hdr.gof.num_frames_in_gof, kMaxVp9FramesInGof); + size_t length = 1; // V + if (hdr.spatial_layer_resolution_present) { + length += 4 * hdr.num_spatial_layers; // Y + } + if (hdr.gof.num_frames_in_gof > 0) { + ++length; // G + } + // N_G + length += hdr.gof.num_frames_in_gof; // T, U, R + for (size_t i = 0; i < hdr.gof.num_frames_in_gof; ++i) { + RTC_DCHECK_LE(hdr.gof.num_ref_pics[i], kMaxVp9RefPics); + length += hdr.gof.num_ref_pics[i]; // R times + } + return length; +} + +size_t PayloadDescriptorLengthMinusSsData(const RTPVideoHeaderVP9& hdr) { + return kFixedPayloadDescriptorBytes + PictureIdLength(hdr) + + LayerInfoLength(hdr) + RefIndicesLength(hdr); +} + +size_t PayloadDescriptorLength(const RTPVideoHeaderVP9& hdr) { + return PayloadDescriptorLengthMinusSsData(hdr) + SsDataLength(hdr); +} + +void QueuePacket(size_t start_pos, + size_t size, + bool layer_begin, + bool layer_end, + RtpPacketizerVp9::PacketInfoQueue* packets) { + RtpPacketizerVp9::PacketInfo packet_info; + packet_info.payload_start_pos = start_pos; + packet_info.size = size; + packet_info.layer_begin = layer_begin; + packet_info.layer_end = layer_end; + packets->push(packet_info); +} + +// Picture ID: +// +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | M:0 => picture id is 7 bits. +// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. +// M: | EXTENDED PID | +// +-+-+-+-+-+-+-+-+ +// +bool WritePictureId(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + bool m_bit = (PictureIdLength(vp9) == 2); + RETURN_FALSE_ON_ERROR(writer->WriteBits(m_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.picture_id, m_bit ? 15 : 7)); + return true; +} + +// Layer indices: +// +// Flexible mode (F=1): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// +bool WriteLayerInfoCommon(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + RETURN_FALSE_ON_ERROR(writer->WriteBits(TemporalIdxField(vp9, 0), 3)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.temporal_up_switch ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(SpatialIdxField(vp9, 0), 3)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.inter_layer_predicted ? 1: 0, 1)); + return true; +} + +// Non-flexible mode (F=0): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ +// +bool WriteLayerInfoNonFlexibleMode(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt8(Tl0PicIdxField(vp9, 0))); + return true; +} + +bool WriteLayerInfo(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + if (!WriteLayerInfoCommon(vp9, writer)) + return false; + + if (vp9.flexible_mode) + return true; + + return WriteLayerInfoNonFlexibleMode(vp9, writer); +} + +// Reference indices: +// +// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// P,F: | P_DIFF |N| up to 3 times has to be specified. +// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows +// current P_DIFF. +// +bool WriteRefIndices(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + if (!PictureIdPresent(vp9) || + vp9.num_ref_pics == 0 || vp9.num_ref_pics > kMaxVp9RefPics) { + return false; + } + for (uint8_t i = 0; i < vp9.num_ref_pics; ++i) { + bool n_bit = !(i == vp9.num_ref_pics - 1); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.pid_diff[i], 7)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(n_bit ? 1 : 0, 1)); + } + return true; +} + +// Scalability structure (SS). +// +// +-+-+-+-+-+-+-+-+ +// V: | N_S |Y|G|-|-|-| +// +-+-+-+-+-+-+-+-+ -| +// Y: | WIDTH | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ . N_S + 1 times +// | HEIGHT | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| +// G: | N_G | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ -| +// N_G: | T |U| R |-|-| (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| . N_G times +// | P_DIFF | (OPTIONAL) . R times . +// +-+-+-+-+-+-+-+-+ -| -| +// +bool WriteSsData(const RTPVideoHeaderVP9& vp9, rtc::BitBufferWriter* writer) { + RTC_DCHECK_GT(vp9.num_spatial_layers, 0U); + RTC_DCHECK_LE(vp9.num_spatial_layers, kMaxVp9NumberOfSpatialLayers); + RTC_DCHECK_LE(vp9.gof.num_frames_in_gof, kMaxVp9FramesInGof); + bool g_bit = vp9.gof.num_frames_in_gof > 0; + + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.num_spatial_layers - 1, 3)); + RETURN_FALSE_ON_ERROR( + writer->WriteBits(vp9.spatial_layer_resolution_present ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(g_bit ? 1 : 0, 1)); // G + RETURN_FALSE_ON_ERROR(writer->WriteBits(kReservedBitValue0, 3)); + + if (vp9.spatial_layer_resolution_present) { + for (size_t i = 0; i < vp9.num_spatial_layers; ++i) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt16(vp9.width[i])); + RETURN_FALSE_ON_ERROR(writer->WriteUInt16(vp9.height[i])); + } + } + if (g_bit) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt8(vp9.gof.num_frames_in_gof)); + } + for (size_t i = 0; i < vp9.gof.num_frames_in_gof; ++i) { + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.gof.temporal_idx[i], 3)); + RETURN_FALSE_ON_ERROR( + writer->WriteBits(vp9.gof.temporal_up_switch[i] ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.gof.num_ref_pics[i], 2)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(kReservedBitValue0, 2)); + for (uint8_t r = 0; r < vp9.gof.num_ref_pics[i]; ++r) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt8(vp9.gof.pid_diff[i][r])); + } + } + return true; +} + +// Picture ID: +// +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | M:0 => picture id is 7 bits. +// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. +// M: | EXTENDED PID | +// +-+-+-+-+-+-+-+-+ +// +bool ParsePictureId(rtc::BitBuffer* parser, RTPVideoHeaderVP9* vp9) { + uint32_t picture_id; + uint32_t m_bit; + RETURN_FALSE_ON_ERROR(parser->ReadBits(&m_bit, 1)); + if (m_bit) { + RETURN_FALSE_ON_ERROR(parser->ReadBits(&picture_id, 15)); + vp9->max_picture_id = kMaxTwoBytePictureId; + } else { + RETURN_FALSE_ON_ERROR(parser->ReadBits(&picture_id, 7)); + vp9->max_picture_id = kMaxOneBytePictureId; + } + vp9->picture_id = picture_id; + return true; +} + +// Layer indices (flexible mode): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// +bool ParseLayerInfoCommon(rtc::BitBuffer* parser, RTPVideoHeaderVP9* vp9) { + uint32_t t, u_bit, s, d_bit; + RETURN_FALSE_ON_ERROR(parser->ReadBits(&t, 3)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&u_bit, 1)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&s, 3)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&d_bit, 1)); + vp9->temporal_idx = t; + vp9->temporal_up_switch = u_bit ? true : false; + vp9->spatial_idx = s; + vp9->inter_layer_predicted = d_bit ? true : false; + return true; +} + +// Layer indices (non-flexible mode): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ +// +bool ParseLayerInfoNonFlexibleMode(rtc::BitBuffer* parser, + RTPVideoHeaderVP9* vp9) { + uint8_t tl0picidx; + RETURN_FALSE_ON_ERROR(parser->ReadUInt8(&tl0picidx)); + vp9->tl0_pic_idx = tl0picidx; + return true; +} + +bool ParseLayerInfo(rtc::BitBuffer* parser, RTPVideoHeaderVP9* vp9) { + if (!ParseLayerInfoCommon(parser, vp9)) + return false; + + if (vp9->flexible_mode) + return true; + + return ParseLayerInfoNonFlexibleMode(parser, vp9); +} + +// Reference indices: +// +// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// P,F: | P_DIFF |N| up to 3 times has to be specified. +// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows +// current P_DIFF. +// +bool ParseRefIndices(rtc::BitBuffer* parser, RTPVideoHeaderVP9* vp9) { + if (vp9->picture_id == kNoPictureId) + return false; + + vp9->num_ref_pics = 0; + uint32_t n_bit; + do { + if (vp9->num_ref_pics == kMaxVp9RefPics) + return false; + + uint32_t p_diff; + RETURN_FALSE_ON_ERROR(parser->ReadBits(&p_diff, 7)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&n_bit, 1)); + + vp9->pid_diff[vp9->num_ref_pics] = p_diff; + uint32_t scaled_pid = vp9->picture_id; + if (p_diff > scaled_pid) { + // TODO(asapersson): Max should correspond to the picture id of last wrap. + scaled_pid += vp9->max_picture_id + 1; + } + vp9->ref_picture_id[vp9->num_ref_pics++] = scaled_pid - p_diff; + } while (n_bit); + + return true; +} + +// Scalability structure (SS). +// +// +-+-+-+-+-+-+-+-+ +// V: | N_S |Y|G|-|-|-| +// +-+-+-+-+-+-+-+-+ -| +// Y: | WIDTH | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ . N_S + 1 times +// | HEIGHT | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| +// G: | N_G | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ -| +// N_G: | T |U| R |-|-| (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| . N_G times +// | P_DIFF | (OPTIONAL) . R times . +// +-+-+-+-+-+-+-+-+ -| -| +// +bool ParseSsData(rtc::BitBuffer* parser, RTPVideoHeaderVP9* vp9) { + uint32_t n_s, y_bit, g_bit; + RETURN_FALSE_ON_ERROR(parser->ReadBits(&n_s, 3)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&y_bit, 1)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&g_bit, 1)); + RETURN_FALSE_ON_ERROR(parser->ConsumeBits(3)); + vp9->num_spatial_layers = n_s + 1; + vp9->spatial_layer_resolution_present = y_bit ? true : false; + vp9->gof.num_frames_in_gof = 0; + + if (y_bit) { + for (size_t i = 0; i < vp9->num_spatial_layers; ++i) { + RETURN_FALSE_ON_ERROR(parser->ReadUInt16(&vp9->width[i])); + RETURN_FALSE_ON_ERROR(parser->ReadUInt16(&vp9->height[i])); + } + } + if (g_bit) { + uint8_t n_g; + RETURN_FALSE_ON_ERROR(parser->ReadUInt8(&n_g)); + vp9->gof.num_frames_in_gof = n_g; + } + for (size_t i = 0; i < vp9->gof.num_frames_in_gof; ++i) { + uint32_t t, u_bit, r; + RETURN_FALSE_ON_ERROR(parser->ReadBits(&t, 3)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&u_bit, 1)); + RETURN_FALSE_ON_ERROR(parser->ReadBits(&r, 2)); + RETURN_FALSE_ON_ERROR(parser->ConsumeBits(2)); + vp9->gof.temporal_idx[i] = t; + vp9->gof.temporal_up_switch[i] = u_bit ? true : false; + vp9->gof.num_ref_pics[i] = r; + + for (uint8_t p = 0; p < vp9->gof.num_ref_pics[i]; ++p) { + uint8_t p_diff; + RETURN_FALSE_ON_ERROR(parser->ReadUInt8(&p_diff)); + vp9->gof.pid_diff[i][p] = p_diff; + } + } + return true; +} + +// Gets the size of next payload chunk to send. Returns 0 on error. +size_t CalcNextSize(size_t max_length, size_t rem_bytes) { + if (max_length == 0 || rem_bytes == 0) { + return 0; + } + if (kBalancedMode) { + size_t num_frags = std::ceil(static_cast(rem_bytes) / max_length); + return static_cast( + static_cast(rem_bytes) / num_frags + 0.5); + } + return max_length >= rem_bytes ? rem_bytes : max_length; +} +} // namespace + + +RtpPacketizerVp9::RtpPacketizerVp9(const RTPVideoHeaderVP9& hdr, + size_t max_payload_length) + : hdr_(hdr), + max_payload_length_(max_payload_length), + payload_(nullptr), + payload_size_(0) { +} + +RtpPacketizerVp9::~RtpPacketizerVp9() { +} + +ProtectionType RtpPacketizerVp9::GetProtectionType() { + bool protect = + hdr_.temporal_idx == 0 || hdr_.temporal_idx == kNoTemporalIdx; + return protect ? kProtectedPacket : kUnprotectedPacket; +} + +StorageType RtpPacketizerVp9::GetStorageType(uint32_t retransmission_settings) { + StorageType storage = kAllowRetransmission; + if (hdr_.temporal_idx == 0 && + !(retransmission_settings & kRetransmitBaseLayer)) { + storage = kDontRetransmit; + } else if (hdr_.temporal_idx != kNoTemporalIdx && hdr_.temporal_idx > 0 && + !(retransmission_settings & kRetransmitHigherLayers)) { + storage = kDontRetransmit; + } + return storage; +} + +std::string RtpPacketizerVp9::ToString() { + return "RtpPacketizerVp9"; +} + +void RtpPacketizerVp9::SetPayloadData( + const uint8_t* payload, + size_t payload_size, + const RTPFragmentationHeader* fragmentation) { + payload_ = payload; + payload_size_ = payload_size; + GeneratePackets(); +} + +void RtpPacketizerVp9::GeneratePackets() { + if (max_payload_length_ < PayloadDescriptorLength(hdr_) + 1) { + LOG(LS_ERROR) << "Payload header and one payload byte won't fit."; + return; + } + size_t bytes_processed = 0; + while (bytes_processed < payload_size_) { + size_t rem_bytes = payload_size_ - bytes_processed; + size_t rem_payload_len = max_payload_length_ - + (bytes_processed ? PayloadDescriptorLengthMinusSsData(hdr_) + : PayloadDescriptorLength(hdr_)); + + size_t packet_bytes = CalcNextSize(rem_payload_len, rem_bytes); + if (packet_bytes == 0) { + LOG(LS_ERROR) << "Failed to generate VP9 packets."; + while (!packets_.empty()) + packets_.pop(); + return; + } + QueuePacket(bytes_processed, packet_bytes, bytes_processed == 0, + rem_bytes == packet_bytes, &packets_); + bytes_processed += packet_bytes; + } + assert(bytes_processed == payload_size_); +} + +bool RtpPacketizerVp9::NextPacket(uint8_t* buffer, + size_t* bytes_to_send, + bool* last_packet) { + if (packets_.empty()) { + return false; + } + PacketInfo packet_info = packets_.front(); + packets_.pop(); + + if (!WriteHeaderAndPayload(packet_info, buffer, bytes_to_send)) { + return false; + } + *last_packet = + packets_.empty() && (hdr_.spatial_idx == kNoSpatialIdx || + hdr_.spatial_idx == hdr_.num_spatial_layers - 1); + return true; +} + +// VP9 format: +// +// Payload descriptor for F = 1 (flexible mode) +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|-| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ -| +// P,F: | P_DIFF |N| (CONDITIONALLY RECOMMENDED) . up to 3 times +// +-+-+-+-+-+-+-+-+ -| +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ +// +// Payload descriptor for F = 0 (non-flexible mode) +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|-| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | (CONDITIONALLY REQUIRED) +// +-+-+-+-+-+-+-+-+ +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ + +bool RtpPacketizerVp9::WriteHeaderAndPayload(const PacketInfo& packet_info, + uint8_t* buffer, + size_t* bytes_to_send) const { + size_t header_length; + if (!WriteHeader(packet_info, buffer, &header_length)) + return false; + + // Copy payload data. + memcpy(&buffer[header_length], + &payload_[packet_info.payload_start_pos], packet_info.size); + + *bytes_to_send = header_length + packet_info.size; + return true; +} + +bool RtpPacketizerVp9::WriteHeader(const PacketInfo& packet_info, + uint8_t* buffer, + size_t* header_length) const { + // Required payload descriptor byte. + bool i_bit = PictureIdPresent(hdr_); + bool p_bit = hdr_.inter_pic_predicted; + bool l_bit = LayerInfoPresent(hdr_); + bool f_bit = hdr_.flexible_mode; + bool b_bit = packet_info.layer_begin; + bool e_bit = packet_info.layer_end; + bool v_bit = hdr_.ss_data_available && b_bit; + + rtc::BitBufferWriter writer(buffer, max_payload_length_); + RETURN_FALSE_ON_ERROR(writer.WriteBits(i_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(p_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(l_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(f_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(b_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(e_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(v_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(kReservedBitValue0, 1)); + + // Add fields that are present. + if (i_bit && !WritePictureId(hdr_, &writer)) { + LOG(LS_ERROR) << "Failed writing VP9 picture id."; + return false; + } + if (l_bit && !WriteLayerInfo(hdr_, &writer)) { + LOG(LS_ERROR) << "Failed writing VP9 layer info."; + return false; + } + if (p_bit && f_bit && !WriteRefIndices(hdr_, &writer)) { + LOG(LS_ERROR) << "Failed writing VP9 ref indices."; + return false; + } + if (v_bit && !WriteSsData(hdr_, &writer)) { + LOG(LS_ERROR) << "Failed writing VP9 SS data."; + return false; + } + + size_t offset_bytes = 0; + size_t offset_bits = 0; + writer.GetCurrentOffset(&offset_bytes, &offset_bits); + assert(offset_bits == 0); + + *header_length = offset_bytes; + return true; +} + +bool RtpDepacketizerVp9::Parse(ParsedPayload* parsed_payload, + const uint8_t* payload, + size_t payload_length) { + assert(parsed_payload != nullptr); + if (payload_length == 0) { + LOG(LS_ERROR) << "Payload length is zero."; + return false; + } + + // Parse mandatory first byte of payload descriptor. + rtc::BitBuffer parser(payload, payload_length); + uint32_t i_bit, p_bit, l_bit, f_bit, b_bit, e_bit, v_bit; + RETURN_FALSE_ON_ERROR(parser.ReadBits(&i_bit, 1)); + RETURN_FALSE_ON_ERROR(parser.ReadBits(&p_bit, 1)); + RETURN_FALSE_ON_ERROR(parser.ReadBits(&l_bit, 1)); + RETURN_FALSE_ON_ERROR(parser.ReadBits(&f_bit, 1)); + RETURN_FALSE_ON_ERROR(parser.ReadBits(&b_bit, 1)); + RETURN_FALSE_ON_ERROR(parser.ReadBits(&e_bit, 1)); + RETURN_FALSE_ON_ERROR(parser.ReadBits(&v_bit, 1)); + RETURN_FALSE_ON_ERROR(parser.ConsumeBits(1)); + + // Parsed payload. + parsed_payload->type.Video.width = 0; + parsed_payload->type.Video.height = 0; + parsed_payload->type.Video.simulcastIdx = 0; + parsed_payload->type.Video.codec = kRtpVideoVp9; + + parsed_payload->frame_type = p_bit ? kVideoFrameDelta : kVideoFrameKey; + + RTPVideoHeaderVP9* vp9 = &parsed_payload->type.Video.codecHeader.VP9; + vp9->InitRTPVideoHeaderVP9(); + vp9->inter_pic_predicted = p_bit ? true : false; + vp9->flexible_mode = f_bit ? true : false; + vp9->beginning_of_frame = b_bit ? true : false; + vp9->end_of_frame = e_bit ? true : false; + vp9->ss_data_available = v_bit ? true : false; + vp9->spatial_idx = 0; + + // Parse fields that are present. + if (i_bit && !ParsePictureId(&parser, vp9)) { + LOG(LS_ERROR) << "Failed parsing VP9 picture id."; + return false; + } + if (l_bit && !ParseLayerInfo(&parser, vp9)) { + LOG(LS_ERROR) << "Failed parsing VP9 layer info."; + return false; + } + if (p_bit && f_bit && !ParseRefIndices(&parser, vp9)) { + LOG(LS_ERROR) << "Failed parsing VP9 ref indices."; + return false; + } + if (v_bit) { + if (!ParseSsData(&parser, vp9)) { + LOG(LS_ERROR) << "Failed parsing VP9 SS data."; + return false; + } + if (vp9->spatial_layer_resolution_present) { + // TODO(asapersson): Add support for spatial layers. + parsed_payload->type.Video.width = vp9->width[0]; + parsed_payload->type.Video.height = vp9->height[0]; + } + } + parsed_payload->type.Video.isFirstPacket = + b_bit && (!l_bit || !vp9->inter_layer_predicted); + + uint64_t rem_bits = parser.RemainingBitCount(); + assert(rem_bits % 8 == 0); + parsed_payload->payload_length = rem_bits / 8; + if (parsed_payload->payload_length == 0) { + LOG(LS_ERROR) << "Failed parsing VP9 payload data."; + return false; + } + parsed_payload->payload = + payload + payload_length - parsed_payload->payload_length; + + return true; +} +} // namespace webrtc diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h new file mode 100644 index 0000000000..883fbce5c8 --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// This file contains the declaration of the VP9 packetizer class. +// A packetizer object is created for each encoded video frame. The +// constructor is called with the payload data and size. +// +// After creating the packetizer, the method NextPacket is called +// repeatedly to get all packets for the frame. The method returns +// false as long as there are more packets left to fetch. +// + +#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP9_H_ +#define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP9_H_ + +#include +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/interface/module_common_types.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_format.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class RtpPacketizerVp9 : public RtpPacketizer { + public: + RtpPacketizerVp9(const RTPVideoHeaderVP9& hdr, size_t max_payload_length); + + virtual ~RtpPacketizerVp9(); + + ProtectionType GetProtectionType() override; + + StorageType GetStorageType(uint32_t retransmission_settings) override; + + std::string ToString() override; + + // The payload data must be one encoded VP9 frame. + void SetPayloadData(const uint8_t* payload, + size_t payload_size, + const RTPFragmentationHeader* fragmentation) override; + + // Gets the next payload with VP9 payload header. + // |buffer| is a pointer to where the output will be written. + // |bytes_to_send| is an output variable that will contain number of bytes + // written to buffer. + // |last_packet| is true for the last packet of the frame, false otherwise + // (i.e. call the function again to get the next packet). + // Returns true on success, false otherwise. + bool NextPacket(uint8_t* buffer, + size_t* bytes_to_send, + bool* last_packet) override; + + typedef struct { + size_t payload_start_pos; + size_t size; + bool layer_begin; + bool layer_end; + } PacketInfo; + typedef std::queue PacketInfoQueue; + + private: + // Calculates all packet sizes and loads info to packet queue. + void GeneratePackets(); + + // Writes the payload descriptor header and copies payload to the |buffer|. + // |packet_info| determines which part of the payload to write. + // |bytes_to_send| contains the number of written bytes to the buffer. + // Returns true on success, false otherwise. + bool WriteHeaderAndPayload(const PacketInfo& packet_info, + uint8_t* buffer, + size_t* bytes_to_send) const; + + // Writes payload descriptor header to |buffer|. + // Returns true on success, false otherwise. + bool WriteHeader(const PacketInfo& packet_info, + uint8_t* buffer, + size_t* header_length) const; + + const RTPVideoHeaderVP9 hdr_; + const size_t max_payload_length_; // The max length in bytes of one packet. + const uint8_t* payload_; // The payload data to be packetized. + size_t payload_size_; // The size in bytes of the payload data. + PacketInfoQueue packets_; + + DISALLOW_COPY_AND_ASSIGN(RtpPacketizerVp9); +}; + + +class RtpDepacketizerVp9 : public RtpDepacketizer { + public: + virtual ~RtpDepacketizerVp9() {} + + bool Parse(ParsedPayload* parsed_payload, + const uint8_t* payload, + size_t payload_length) override; +}; + +} // namespace webrtc +#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP9_H_ diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc new file mode 100644 index 0000000000..5bbafe459d --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc @@ -0,0 +1,690 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace { +void VerifyHeader(const RTPVideoHeaderVP9& expected, + const RTPVideoHeaderVP9& actual) { + EXPECT_EQ(expected.inter_layer_predicted, actual.inter_layer_predicted); + EXPECT_EQ(expected.inter_pic_predicted, actual.inter_pic_predicted); + EXPECT_EQ(expected.flexible_mode, actual.flexible_mode); + EXPECT_EQ(expected.beginning_of_frame, actual.beginning_of_frame); + EXPECT_EQ(expected.end_of_frame, actual.end_of_frame); + EXPECT_EQ(expected.ss_data_available, actual.ss_data_available); + EXPECT_EQ(expected.picture_id, actual.picture_id); + EXPECT_EQ(expected.max_picture_id, actual.max_picture_id); + EXPECT_EQ(expected.temporal_idx, actual.temporal_idx); + EXPECT_EQ(expected.spatial_idx == kNoSpatialIdx ? 0 : expected.spatial_idx, + actual.spatial_idx); + EXPECT_EQ(expected.gof_idx, actual.gof_idx); + EXPECT_EQ(expected.tl0_pic_idx, actual.tl0_pic_idx); + EXPECT_EQ(expected.temporal_up_switch, actual.temporal_up_switch); + + EXPECT_EQ(expected.num_ref_pics, actual.num_ref_pics); + for (uint8_t i = 0; i < expected.num_ref_pics; ++i) { + EXPECT_EQ(expected.pid_diff[i], actual.pid_diff[i]); + EXPECT_EQ(expected.ref_picture_id[i], actual.ref_picture_id[i]); + } + if (expected.ss_data_available) { + EXPECT_EQ(expected.spatial_layer_resolution_present, + actual.spatial_layer_resolution_present); + EXPECT_EQ(expected.num_spatial_layers, actual.num_spatial_layers); + if (expected.spatial_layer_resolution_present) { + for (size_t i = 0; i < expected.num_spatial_layers; i++) { + EXPECT_EQ(expected.width[i], actual.width[i]); + EXPECT_EQ(expected.height[i], actual.height[i]); + } + } + EXPECT_EQ(expected.gof.num_frames_in_gof, actual.gof.num_frames_in_gof); + for (size_t i = 0; i < expected.gof.num_frames_in_gof; i++) { + EXPECT_EQ(expected.gof.temporal_up_switch[i], + actual.gof.temporal_up_switch[i]); + EXPECT_EQ(expected.gof.temporal_idx[i], actual.gof.temporal_idx[i]); + EXPECT_EQ(expected.gof.num_ref_pics[i], actual.gof.num_ref_pics[i]); + for (uint8_t j = 0; j < expected.gof.num_ref_pics[i]; j++) { + EXPECT_EQ(expected.gof.pid_diff[i][j], actual.gof.pid_diff[i][j]); + } + } + } +} + +void VerifyPayload(const RtpDepacketizer::ParsedPayload& parsed, + const uint8_t* payload, + size_t payload_length) { + EXPECT_EQ(payload, parsed.payload); + EXPECT_EQ(payload_length, parsed.payload_length); + EXPECT_THAT(std::vector(parsed.payload, + parsed.payload + parsed.payload_length), + ::testing::ElementsAreArray(payload, payload_length)); +} + +void ParseAndCheckPacket(const uint8_t* packet, + const RTPVideoHeaderVP9& expected, + size_t expected_hdr_length, + size_t expected_length) { + rtc::scoped_ptr depacketizer(new RtpDepacketizerVp9()); + RtpDepacketizer::ParsedPayload parsed; + ASSERT_TRUE(depacketizer->Parse(&parsed, packet, expected_length)); + EXPECT_EQ(kRtpVideoVp9, parsed.type.Video.codec); + VerifyHeader(expected, parsed.type.Video.codecHeader.VP9); + const size_t kExpectedPayloadLength = expected_length - expected_hdr_length; + VerifyPayload(parsed, packet + expected_hdr_length, kExpectedPayloadLength); +} +} // namespace + +// Payload descriptor for flexible mode +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|-| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ -| +// P,F: | P_DIFF |N| (CONDITIONALLY RECOMMENDED) . up to 3 times +// +-+-+-+-+-+-+-+-+ -| +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ +// +// Payload descriptor for non-flexible mode +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|-| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | (CONDITIONALLY REQUIRED) +// +-+-+-+-+-+-+-+-+ +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ + +class RtpPacketizerVp9Test : public ::testing::Test { + protected: + RtpPacketizerVp9Test() {} + virtual void SetUp() { + expected_.InitRTPVideoHeaderVP9(); + } + + rtc::scoped_ptr packet_; + rtc::scoped_ptr payload_; + size_t payload_size_; + size_t payload_pos_; + RTPVideoHeaderVP9 expected_; + rtc::scoped_ptr packetizer_; + + void Init(size_t payload_size, size_t packet_size) { + payload_.reset(new uint8_t[payload_size]); + memset(payload_.get(), 7, payload_size); + payload_size_ = payload_size; + payload_pos_ = 0; + packetizer_.reset(new RtpPacketizerVp9(expected_, packet_size)); + packetizer_->SetPayloadData(payload_.get(), payload_size_, NULL); + + const int kMaxPayloadDescriptorLength = 100; + packet_.reset(new uint8_t[payload_size_ + kMaxPayloadDescriptorLength]); + } + + void CheckPayload(const uint8_t* packet, + size_t start_pos, + size_t end_pos, + bool last) { + for (size_t i = start_pos; i < end_pos; ++i) { + EXPECT_EQ(packet[i], payload_[payload_pos_++]); + } + EXPECT_EQ(last, payload_pos_ == payload_size_); + } + + void CreateParseAndCheckPackets(const size_t* expected_hdr_sizes, + const size_t* expected_sizes, + size_t expected_num_packets) { + ASSERT_TRUE(packetizer_.get() != NULL); + size_t length = 0; + bool last = false; + if (expected_num_packets == 0) { + EXPECT_FALSE(packetizer_->NextPacket(packet_.get(), &length, &last)); + return; + } + for (size_t i = 0; i < expected_num_packets; ++i) { + EXPECT_TRUE(packetizer_->NextPacket(packet_.get(), &length, &last)); + EXPECT_EQ(expected_sizes[i], length); + RTPVideoHeaderVP9 hdr = expected_; + hdr.beginning_of_frame = (i == 0); + hdr.end_of_frame = last; + ParseAndCheckPacket(packet_.get(), hdr, expected_hdr_sizes[i], length); + CheckPayload(packet_.get(), expected_hdr_sizes[i], length, last); + } + EXPECT_TRUE(last); + } +}; + +TEST_F(RtpPacketizerVp9Test, TestEqualSizedMode_OnePacket) { + const size_t kFrameSize = 25; + const size_t kPacketSize = 26; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:0 (1hdr + 25 payload) + const size_t kExpectedHdrSizes[] = {1}; + const size_t kExpectedSizes[] = {26}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestEqualSizedMode_TwoPackets) { + const size_t kFrameSize = 27; + const size_t kPacketSize = 27; + Init(kFrameSize, kPacketSize); + + // Two packets: + // I:0, P:0, L:0, F:0, B:1, E:0, V:0 (1hdr + 14 payload) + // I:0, P:0, L:0, F:0, B:0, E:1, V:0 (1hdr + 13 payload) + const size_t kExpectedHdrSizes[] = {1, 1}; + const size_t kExpectedSizes[] = {15, 14}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestTooShortBufferToFitPayload) { + const size_t kFrameSize = 1; + const size_t kPacketSize = 1; + Init(kFrameSize, kPacketSize); // 1hdr + 1 payload + + const size_t kExpectedNum = 0; + CreateParseAndCheckPackets(NULL, NULL, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestOneBytePictureId) { + const size_t kFrameSize = 30; + const size_t kPacketSize = 12; + + expected_.picture_id = kMaxOneBytePictureId; // 2 byte payload descriptor + expected_.max_picture_id = kMaxOneBytePictureId; + Init(kFrameSize, kPacketSize); + + // Three packets: + // I:1, P:0, L:0, F:0, B:1, E:0, V:0 (2hdr + 10 payload) + // I:1, P:0, L:0, F:0, B:0, E:0, V:0 (2hdr + 10 payload) + // I:1, P:0, L:0, F:0, B:0, E:1, V:0 (2hdr + 10 payload) + const size_t kExpectedHdrSizes[] = {2, 2, 2}; + const size_t kExpectedSizes[] = {12, 12, 12}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestTwoBytePictureId) { + const size_t kFrameSize = 31; + const size_t kPacketSize = 13; + + expected_.picture_id = kMaxTwoBytePictureId; // 3 byte payload descriptor + Init(kFrameSize, kPacketSize); + + // Four packets: + // I:1, P:0, L:0, F:0, B:1, E:0, V:0 (3hdr + 8 payload) + // I:1, P:0, L:0, F:0, B:0, E:0, V:0 (3hdr + 8 payload) + // I:1, P:0, L:0, F:0, B:0, E:0, V:0 (3hdr + 8 payload) + // I:1, P:0, L:0, F:0, B:0, E:1, V:0 (3hdr + 7 payload) + const size_t kExpectedHdrSizes[] = {3, 3, 3, 3}; + const size_t kExpectedSizes[] = {11, 11, 11, 10}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestLayerInfoWithNonFlexibleMode) { + const size_t kFrameSize = 30; + const size_t kPacketSize = 25; + + expected_.temporal_idx = 3; + expected_.temporal_up_switch = true; // U + expected_.num_spatial_layers = 3; + expected_.spatial_idx = 2; + expected_.inter_layer_predicted = true; // D + expected_.tl0_pic_idx = 117; + Init(kFrameSize, kPacketSize); + + // Two packets: + // | I:0, P:0, L:1, F:0, B:1, E:0, V:0 | (3hdr + 15 payload) + // L: | T:3, U:1, S:2, D:1 | TL0PICIDX:117 | + // | I:0, P:0, L:1, F:0, B:0, E:1, V:0 | (3hdr + 15 payload) + // L: | T:3, U:1, S:2, D:1 | TL0PICIDX:117 | + const size_t kExpectedHdrSizes[] = {3, 3}; + const size_t kExpectedSizes[] = {18, 18}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestLayerInfoWithFlexibleMode) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 23; + + expected_.flexible_mode = true; + expected_.temporal_idx = 3; + expected_.temporal_up_switch = true; // U + expected_.num_spatial_layers = 3; + expected_.spatial_idx = 2; + expected_.inter_layer_predicted = false; // D + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:1, F:1, B:1, E:1, V:0 (2hdr + 21 payload) + // L: T:3, U:1, S:2, D:0 + const size_t kExpectedHdrSizes[] = {2}; + const size_t kExpectedSizes[] = {23}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestRefIdx) { + const size_t kFrameSize = 16; + const size_t kPacketSize = 21; + + expected_.inter_pic_predicted = true; // P + expected_.flexible_mode = true; // F + expected_.picture_id = 2; + expected_.max_picture_id = kMaxOneBytePictureId; + + expected_.num_ref_pics = 3; + expected_.pid_diff[0] = 1; + expected_.pid_diff[1] = 3; + expected_.pid_diff[2] = 127; + expected_.ref_picture_id[0] = 1; // 2 - 1 = 1 + expected_.ref_picture_id[1] = 127; // (kMaxPictureId + 1) + 2 - 3 = 127 + expected_.ref_picture_id[2] = 3; // (kMaxPictureId + 1) + 2 - 127 = 3 + Init(kFrameSize, kPacketSize); + + // Two packets: + // I:1, P:1, L:0, F:1, B:1, E:1, V:0 (5hdr + 16 payload) + // I: 2 + // P,F: P_DIFF:1, N:1 + // P_DIFF:3, N:1 + // P_DIFF:127, N:0 + const size_t kExpectedHdrSizes[] = {5}; + const size_t kExpectedSizes[] = {21}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestRefIdxFailsWithoutPictureId) { + const size_t kFrameSize = 16; + const size_t kPacketSize = 21; + + expected_.inter_pic_predicted = true; + expected_.flexible_mode = true; + expected_.num_ref_pics = 1; + expected_.pid_diff[0] = 3; + Init(kFrameSize, kPacketSize); + + const size_t kExpectedNum = 0; + CreateParseAndCheckPackets(NULL, NULL, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestSsDataWithoutSpatialResolutionPresent) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 26; + + expected_.ss_data_available = true; + expected_.num_spatial_layers = 1; + expected_.spatial_layer_resolution_present = false; + expected_.gof.num_frames_in_gof = 1; + expected_.gof.temporal_idx[0] = 0; + expected_.gof.temporal_up_switch[0] = true; + expected_.gof.num_ref_pics[0] = 1; + expected_.gof.pid_diff[0][0] = 4; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:1 (5hdr + 21 payload) + // N_S:0, Y:0, G:1 + // N_G:1 + // T:0, U:1, R:1 | P_DIFF[0][0]:4 + const size_t kExpectedHdrSizes[] = {5}; + const size_t kExpectedSizes[] = {26}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestSsDataWithoutGbitPresent) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 23; + + expected_.ss_data_available = true; + expected_.num_spatial_layers = 1; + expected_.spatial_layer_resolution_present = false; + expected_.gof.num_frames_in_gof = 0; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:1 (2hdr + 21 payload) + // N_S:0, Y:0, G:0 + const size_t kExpectedHdrSizes[] = {2}; + const size_t kExpectedSizes[] = {23}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestSsData) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 40; + + expected_.ss_data_available = true; + expected_.num_spatial_layers = 2; + expected_.spatial_layer_resolution_present = true; + expected_.width[0] = 640; + expected_.width[1] = 1280; + expected_.height[0] = 360; + expected_.height[1] = 720; + expected_.gof.num_frames_in_gof = 3; + expected_.gof.temporal_idx[0] = 0; + expected_.gof.temporal_idx[1] = 1; + expected_.gof.temporal_idx[2] = 2; + expected_.gof.temporal_up_switch[0] = true; + expected_.gof.temporal_up_switch[1] = true; + expected_.gof.temporal_up_switch[2] = false; + expected_.gof.num_ref_pics[0] = 0; + expected_.gof.num_ref_pics[1] = 3; + expected_.gof.num_ref_pics[2] = 2; + expected_.gof.pid_diff[1][0] = 5; + expected_.gof.pid_diff[1][1] = 6; + expected_.gof.pid_diff[1][2] = 7; + expected_.gof.pid_diff[2][0] = 8; + expected_.gof.pid_diff[2][1] = 9; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:1 (19hdr + 21 payload) + // N_S:1, Y:1, G:1 + // WIDTH:640 // 2 bytes + // HEIGHT:360 // 2 bytes + // WIDTH:1280 // 2 bytes + // HEIGHT:720 // 2 bytes + // N_G:3 + // T:0, U:1, R:0 + // T:1, U:1, R:3 | P_DIFF[1][0]:5 | P_DIFF[1][1]:6 | P_DIFF[1][2]:7 + // T:2, U:0, R:2 | P_DIFF[2][0]:8 | P_DIFF[2][0]:9 + const size_t kExpectedHdrSizes[] = {19}; + const size_t kExpectedSizes[] = {40}; + const size_t kExpectedNum = GTEST_ARRAY_SIZE_(kExpectedSizes); + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes, kExpectedNum); +} + +TEST_F(RtpPacketizerVp9Test, TestBaseLayerProtectionAndStorageType) { + const size_t kFrameSize = 10; + const size_t kPacketSize = 12; + + // I:0, P:0, L:1, F:1, B:1, E:1, V:0 (2hdr + 10 payload) + // L: T:0, U:0, S:0, D:0 + expected_.flexible_mode = true; + expected_.temporal_idx = 0; + Init(kFrameSize, kPacketSize); + EXPECT_EQ(kProtectedPacket, packetizer_->GetProtectionType()); + EXPECT_EQ(kAllowRetransmission, + packetizer_->GetStorageType(kRetransmitBaseLayer)); + EXPECT_EQ(kDontRetransmit, packetizer_->GetStorageType(kRetransmitOff)); +} + +TEST_F(RtpPacketizerVp9Test, TestHigherLayerProtectionAndStorageType) { + const size_t kFrameSize = 10; + const size_t kPacketSize = 12; + + // I:0, P:0, L:1, F:1, B:1, E:1, V:0 (2hdr + 10 payload) + // L: T:1, U:0, S:0, D:0 + expected_.flexible_mode = true; + expected_.temporal_idx = 1; + Init(kFrameSize, kPacketSize); + EXPECT_EQ(kUnprotectedPacket, packetizer_->GetProtectionType()); + EXPECT_EQ(kDontRetransmit, packetizer_->GetStorageType(kRetransmitBaseLayer)); + EXPECT_EQ(kAllowRetransmission, + packetizer_->GetStorageType(kRetransmitHigherLayers)); +} + + +class RtpDepacketizerVp9Test : public ::testing::Test { + protected: + RtpDepacketizerVp9Test() + : depacketizer_(new RtpDepacketizerVp9()) {} + + virtual void SetUp() { + expected_.InitRTPVideoHeaderVP9(); + } + + RTPVideoHeaderVP9 expected_; + rtc::scoped_ptr depacketizer_; +}; + +TEST_F(RtpDepacketizerVp9Test, ParseBasicHeader) { + const uint8_t kHeaderLength = 1; + uint8_t packet[4] = {0}; + packet[0] = 0x0C; // I:0 P:0 L:0 F:0 B:1 E:1 V:0 R:0 + expected_.beginning_of_frame = true; + expected_.end_of_frame = true; + ParseAndCheckPacket(packet, expected_, kHeaderLength, sizeof(packet)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseOneBytePictureId) { + const uint8_t kHeaderLength = 2; + uint8_t packet[10] = {0}; + packet[0] = 0x80; // I:1 P:0 L:0 F:0 B:0 E:0 V:0 R:0 + packet[1] = kMaxOneBytePictureId; + + expected_.picture_id = kMaxOneBytePictureId; + expected_.max_picture_id = kMaxOneBytePictureId; + ParseAndCheckPacket(packet, expected_, kHeaderLength, sizeof(packet)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseTwoBytePictureId) { + const uint8_t kHeaderLength = 3; + uint8_t packet[10] = {0}; + packet[0] = 0x80; // I:1 P:0 L:0 F:0 B:0 E:0 V:0 R:0 + packet[1] = 0x80 | ((kMaxTwoBytePictureId >> 8) & 0x7F); + packet[2] = kMaxTwoBytePictureId & 0xFF; + + expected_.picture_id = kMaxTwoBytePictureId; + expected_.max_picture_id = kMaxTwoBytePictureId; + ParseAndCheckPacket(packet, expected_, kHeaderLength, sizeof(packet)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseLayerInfoWithNonFlexibleMode) { + const uint8_t kHeaderLength = 3; + const uint8_t kTemporalIdx = 2; + const uint8_t kUbit = 1; + const uint8_t kSpatialIdx = 1; + const uint8_t kDbit = 1; + const uint8_t kTl0PicIdx = 17; + uint8_t packet[13] = {0}; + packet[0] = 0x20; // I:0 P:0 L:1 F:0 B:0 E:0 V:0 R:0 + packet[1] = (kTemporalIdx << 5) | (kUbit << 4) | (kSpatialIdx << 1) | kDbit; + packet[2] = kTl0PicIdx; + + // T:2 U:1 S:1 D:1 + // TL0PICIDX:17 + expected_.temporal_idx = kTemporalIdx; + expected_.temporal_up_switch = kUbit ? true : false; + expected_.spatial_idx = kSpatialIdx; + expected_.inter_layer_predicted = kDbit ? true : false; + expected_.tl0_pic_idx = kTl0PicIdx; + ParseAndCheckPacket(packet, expected_, kHeaderLength, sizeof(packet)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseLayerInfoWithFlexibleMode) { + const uint8_t kHeaderLength = 2; + const uint8_t kTemporalIdx = 2; + const uint8_t kUbit = 1; + const uint8_t kSpatialIdx = 0; + const uint8_t kDbit = 0; + uint8_t packet[13] = {0}; + packet[0] = 0x38; // I:0 P:0 L:1 F:1 B:1 E:0 V:0 R:0 + packet[1] = (kTemporalIdx << 5) | (kUbit << 4) | (kSpatialIdx << 1) | kDbit; + + // I:0 P:0 L:1 F:1 B:1 E:0 V:0 + // L: T:2 U:1 S:0 D:0 + expected_.beginning_of_frame = true; + expected_.flexible_mode = true; + expected_.temporal_idx = kTemporalIdx; + expected_.temporal_up_switch = kUbit ? true : false; + expected_.spatial_idx = kSpatialIdx; + expected_.inter_layer_predicted = kDbit ? true : false; + ParseAndCheckPacket(packet, expected_, kHeaderLength, sizeof(packet)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseRefIdx) { + const uint8_t kHeaderLength = 6; + const int16_t kPictureId = 17; + const uint8_t kPdiff1 = 17; + const uint8_t kPdiff2 = 18; + const uint8_t kPdiff3 = 127; + uint8_t packet[13] = {0}; + packet[0] = 0xD8; // I:1 P:1 L:0 F:1 B:1 E:0 V:0 R:0 + packet[1] = 0x80 | ((kPictureId >> 8) & 0x7F); // Two byte pictureID. + packet[2] = kPictureId; + packet[3] = (kPdiff1 << 1) | 1; // P_DIFF N:1 + packet[4] = (kPdiff2 << 1) | 1; // P_DIFF N:1 + packet[5] = (kPdiff3 << 1) | 0; // P_DIFF N:0 + + // I:1 P:1 L:0 F:1 B:1 E:0 V:0 + // I: PICTURE ID:17 + // I: + // P,F: P_DIFF:17 N:1 => refPicId = 17 - 17 = 0 + // P,F: P_DIFF:18 N:1 => refPicId = (kMaxPictureId + 1) + 17 - 18 = 0x7FFF + // P,F: P_DIFF:127 N:0 => refPicId = (kMaxPictureId + 1) + 17 - 127 = 32658 + expected_.beginning_of_frame = true; + expected_.inter_pic_predicted = true; + expected_.flexible_mode = true; + expected_.picture_id = kPictureId; + expected_.num_ref_pics = 3; + expected_.pid_diff[0] = kPdiff1; + expected_.pid_diff[1] = kPdiff2; + expected_.pid_diff[2] = kPdiff3; + expected_.ref_picture_id[0] = 0; + expected_.ref_picture_id[1] = 0x7FFF; + expected_.ref_picture_id[2] = 32658; + ParseAndCheckPacket(packet, expected_, kHeaderLength, sizeof(packet)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseRefIdxFailsWithNoPictureId) { + const uint8_t kPdiff = 3; + uint8_t packet[13] = {0}; + packet[0] = 0x58; // I:0 P:1 L:0 F:1 B:1 E:0 V:0 R:0 + packet[1] = (kPdiff << 1); // P,F: P_DIFF:3 N:0 + + RtpDepacketizer::ParsedPayload parsed; + EXPECT_FALSE(depacketizer_->Parse(&parsed, packet, sizeof(packet))); +} + +TEST_F(RtpDepacketizerVp9Test, ParseRefIdxFailsWithTooManyRefPics) { + const uint8_t kPdiff = 3; + uint8_t packet[13] = {0}; + packet[0] = 0xD8; // I:1 P:1 L:0 F:1 B:1 E:0 V:0 R:0 + packet[1] = kMaxOneBytePictureId; // I: PICTURE ID:127 + packet[2] = (kPdiff << 1) | 1; // P,F: P_DIFF:3 N:1 + packet[3] = (kPdiff << 1) | 1; // P,F: P_DIFF:3 N:1 + packet[4] = (kPdiff << 1) | 1; // P,F: P_DIFF:3 N:1 + packet[5] = (kPdiff << 1) | 0; // P,F: P_DIFF:3 N:0 + + RtpDepacketizer::ParsedPayload parsed; + EXPECT_FALSE(depacketizer_->Parse(&parsed, packet, sizeof(packet))); +} + +TEST_F(RtpDepacketizerVp9Test, ParseSsData) { + const uint8_t kHeaderLength = 6; + const uint8_t kYbit = 0; + const size_t kNs = 2; + const size_t kNg = 2; + uint8_t packet[23] = {0}; + packet[0] = 0x0A; // I:0 P:0 L:0 F:0 B:1 E:0 V:1 R:0 + packet[1] = ((kNs - 1) << 5) | (kYbit << 4) | (1 << 3); // N_S Y G:1 - + packet[2] = kNg; // N_G + packet[3] = (0 << 5) | (1 << 4) | (0 << 2) | 0; // T:0 U:1 R:0 - + packet[4] = (2 << 5) | (0 << 4) | (1 << 2) | 0; // T:2 U:0 R:1 - + packet[5] = 33; + + expected_.beginning_of_frame = true; + expected_.ss_data_available = true; + expected_.num_spatial_layers = kNs; + expected_.spatial_layer_resolution_present = kYbit ? true : false; + expected_.gof.num_frames_in_gof = kNg; + expected_.gof.temporal_idx[0] = 0; + expected_.gof.temporal_idx[1] = 2; + expected_.gof.temporal_up_switch[0] = true; + expected_.gof.temporal_up_switch[1] = false; + expected_.gof.num_ref_pics[0] = 0; + expected_.gof.num_ref_pics[1] = 1; + expected_.gof.pid_diff[1][0] = 33; + ParseAndCheckPacket(packet, expected_, kHeaderLength, sizeof(packet)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseFirstPacketInKeyFrame) { + uint8_t packet[2] = {0}; + packet[0] = 0x08; // I:0 P:0 L:0 F:0 B:1 E:0 V:0 R:0 + + RtpDepacketizer::ParsedPayload parsed; + ASSERT_TRUE(depacketizer_->Parse(&parsed, packet, sizeof(packet))); + EXPECT_EQ(kVideoFrameKey, parsed.frame_type); + EXPECT_TRUE(parsed.type.Video.isFirstPacket); +} + +TEST_F(RtpDepacketizerVp9Test, ParseLastPacketInDeltaFrame) { + uint8_t packet[2] = {0}; + packet[0] = 0x44; // I:0 P:1 L:0 F:0 B:0 E:1 V:0 R:0 + + RtpDepacketizer::ParsedPayload parsed; + ASSERT_TRUE(depacketizer_->Parse(&parsed, packet, sizeof(packet))); + EXPECT_EQ(kVideoFrameDelta, parsed.frame_type); + EXPECT_FALSE(parsed.type.Video.isFirstPacket); +} + +TEST_F(RtpDepacketizerVp9Test, ParseResolution) { + const uint16_t kWidth[2] = {640, 1280}; + const uint16_t kHeight[2] = {360, 720}; + uint8_t packet[20] = {0}; + packet[0] = 0x0A; // I:0 P:0 L:0 F:0 B:1 E:0 V:1 R:0 + packet[1] = (1 << 5) | (1 << 4) | 0; // N_S:1 Y:1 G:0 + packet[2] = kWidth[0] >> 8; + packet[3] = kWidth[0] & 0xFF; + packet[4] = kHeight[0] >> 8; + packet[5] = kHeight[0] & 0xFF; + packet[6] = kWidth[1] >> 8; + packet[7] = kWidth[1] & 0xFF; + packet[8] = kHeight[1] >> 8; + packet[9] = kHeight[1] & 0xFF; + + RtpDepacketizer::ParsedPayload parsed; + ASSERT_TRUE(depacketizer_->Parse(&parsed, packet, sizeof(packet))); + EXPECT_EQ(kWidth[0], parsed.type.Video.width); + EXPECT_EQ(kHeight[0], parsed.type.Video.height); +} + +TEST_F(RtpDepacketizerVp9Test, ParseFailsForNoPayloadLength) { + uint8_t packet[1] = {0}; + RtpDepacketizer::ParsedPayload parsed; + EXPECT_FALSE(depacketizer_->Parse(&parsed, packet, 0)); +} + +TEST_F(RtpDepacketizerVp9Test, ParseFailsForTooShortBufferToFitPayload) { + const uint8_t kHeaderLength = 1; + uint8_t packet[kHeaderLength] = {0}; + RtpDepacketizer::ParsedPayload parsed; + EXPECT_FALSE(depacketizer_->Parse(&parsed, packet, sizeof(packet))); +} + +} // namespace webrtc diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h index 7be3c2e5c4..74a9ed5c07 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h @@ -26,6 +26,8 @@ const size_t kAudioLevelLength = 2; const size_t kAbsoluteSendTimeLength = 4; const size_t kVideoRotationLength = 2; const size_t kTransportSequenceNumberLength = 3; +// kRIDLength is variable +const size_t kRIDLength = 4; // max 1-byte header extension length struct HeaderExtension { HeaderExtension(RTPExtensionType extension_type) @@ -58,6 +60,9 @@ struct HeaderExtension { case kRtpExtensionTransportSequenceNumber: length = kTransportSequenceNumberLength; break; + case kRtpExtensionRID: + length = kRIDLength; + break; default: assert(false); } diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc index 3640933324..1c14ec365c 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc @@ -81,6 +81,7 @@ RtpReceiverImpl::RtpReceiverImpl(int32_t id, last_received_timestamp_(0), last_received_frame_time_ms_(-1), last_received_sequence_number_(0), + rid_(NULL), nack_method_(kNackOff) { assert(incoming_audio_messages_callback); assert(incoming_messages_callback); @@ -155,6 +156,15 @@ int32_t RtpReceiverImpl::CSRCs(uint32_t array_of_csrcs[kRtpCsrcSize]) const { return num_csrcs_; } +void RtpReceiverImpl::GetRID(char rid[256]) const { + CriticalSectionScoped lock(critical_section_rtp_receiver_.get()); + if (rid_) { + strncpy(rid, rid_, 256); + } else { + rid[0] = '\0'; + } +} + int32_t RtpReceiverImpl::Energy( uint8_t array_of_energy[kRtpCsrcSize]) const { return rtp_media_receiver_->Energy(array_of_energy); @@ -222,7 +232,13 @@ bool RtpReceiverImpl::IncomingRtpPacket( last_receive_time_ = clock_->TimeInMilliseconds(); last_received_payload_length_ = payload_data_length; - + // RID rarely if ever changes + if (rtp_header.extension.hasRID && + (!rid_ || strcmp(rtp_header.extension.rid, rid_) != 0)) { + delete [] rid_; + rid_ = new char[strlen(rtp_header.extension.rid)+1]; + strcpy(rid_, rtp_header.extension.rid); + } if (in_order) { if (last_received_timestamp_ != rtp_header.timestamp) { last_received_timestamp_ = rtp_header.timestamp; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.h index 539c22676d..33c7fdad47 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.h @@ -61,6 +61,8 @@ class RtpReceiverImpl : public RtpReceiver { int32_t CSRCs(uint32_t array_of_csrc[kRtpCsrcSize]) const override; + void GetRID(char rid[256]) const override; + int32_t Energy(uint8_t array_of_energy[kRtpCsrcSize]) const override; TelephoneEventHandler* GetTelephoneEventHandler() override; @@ -96,6 +98,7 @@ class RtpReceiverImpl : public RtpReceiver { uint32_t last_received_timestamp_; int64_t last_received_frame_time_ms_; uint16_t last_received_sequence_number_; + char *rid_; // scope NACKMethod nack_method_; }; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index 7b48bbbb0a..cb661f1d8b 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -317,6 +317,11 @@ void ModuleRtpRtcpImpl::SetCsrcs(const std::vector& csrcs) { rtp_sender_.SetCsrcs(csrcs); } +int32_t ModuleRtpRtcpImpl::SetRID(const char *rid) { + //XXX rtcp_sender_.SetRID(rid); + return rtp_sender_.SetRID(rid); +} + // TODO(pbos): Handle media and RTX streams separately (separate RTCP // feedbacks). RTCPSender::FeedbackState ModuleRtpRtcpImpl::GetFeedbackState() { diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h index 2288f061bb..e946860d9a 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -79,6 +79,8 @@ class ModuleRtpRtcpImpl : public RtpRtcp { void SetCsrcs(const std::vector& csrcs) override; + int32_t SetRID(const char *rid) override; + RTCPSender::FeedbackState GetFeedbackState(); int CurrentSendFrequencyHz() const; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.cc index cb0c6dc0c2..72aa476d8d 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.cc @@ -131,6 +131,7 @@ RTPSender::RTPSender(int32_t id, rotation_(kVideoRotation_0), cvo_mode_(kCVONone), transport_sequence_number_(0), + rid_(NULL), // NACK. nack_byte_count_times_(), nack_byte_count_(), @@ -264,6 +265,18 @@ int32_t RTPSender::SetTransportSequenceNumber(uint16_t sequence_number) { return 0; } +int32_t RTPSender::SetRID(const char* rid) { + CriticalSectionScoped cs(send_critsect_.get()); + // TODO(jesup) avoid allocations + if (!rid_ || strlen(rid_) < strlen(rid)) { + // rid rarely changes length.... + delete [] rid_; + rid_ = new char[strlen(rid)+1]; + } + strcpy(rid_, rid); + return 0; +} + int32_t RTPSender::RegisterRtpHeaderExtension(RTPExtensionType type, uint8_t id) { CriticalSectionScoped cs(send_critsect_.get()); @@ -1221,6 +1234,9 @@ uint16_t RTPSender::BuildRTPHeaderExtension(uint8_t* data_buffer, case kRtpExtensionTransportSequenceNumber: block_length = BuildTransportSequenceNumberExtension(extension_data); break; + case kRtpExtensionRID: + block_length = BuildRIDExtension(extension_data); + break; default: assert(false); } @@ -1396,6 +1412,30 @@ uint8_t RTPSender::BuildTransportSequenceNumberExtension( return kTransportSequenceNumberLength; } +uint8_t RTPSender::BuildRIDExtension( + uint8_t* data_buffer) const { + // 0 1 2 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | ID | L=? |UTF-8 RID value...... |... + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // Get id defined by user. + uint8_t id; + if (rtp_header_extension_map_.GetId(kRtpExtensionRID, + &id) != 0) { + // Not registered. + return 0; + } + size_t pos = 0; + // RID value is not null-terminated in header, so no +1 + const uint8_t len = strlen(rid_); + data_buffer[pos++] = (id << 4) + len; + memcpy(data_buffer + pos, rid_, len); + pos += len; + return pos; +} + bool RTPSender::FindHeaderExtensionPosition(RTPExtensionType type, const uint8_t* rtp_packet, size_t rtp_packet_length, diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.h index 61d835d06d..3d766d7781 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender.h @@ -163,6 +163,7 @@ class RTPSender : public RTPSenderInterface { int32_t SetAbsoluteSendTime(uint32_t absolute_send_time); void SetVideoRotation(VideoRotation rotation); int32_t SetTransportSequenceNumber(uint16_t sequence_number); + int32_t SetRID(const char* rid); int32_t RegisterRtpHeaderExtension(RTPExtensionType type, uint8_t id); virtual bool IsRtpHeaderExtensionRegistered(RTPExtensionType type) override; @@ -177,6 +178,7 @@ class RTPSender : public RTPSenderInterface { uint8_t BuildAbsoluteSendTimeExtension(uint8_t* data_buffer) const; uint8_t BuildVideoRotationExtension(uint8_t* data_buffer) const; uint8_t BuildTransportSequenceNumberExtension(uint8_t* data_buffer) const; + uint8_t BuildRIDExtension(uint8_t* data_buffer) const; bool UpdateAudioLevel(uint8_t* rtp_packet, size_t rtp_packet_length, @@ -390,6 +392,7 @@ class RTPSender : public RTPSenderInterface { VideoRotation rotation_; CVOMode cvo_mode_; uint16_t transport_sequence_number_; + char* rid_; // NACK uint32_t nack_byte_count_times_[NACK_BYTECOUNT_SIZE]; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc index 3dd0da1673..c5af226ca6 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -14,11 +14,15 @@ #include #include +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/trace_event.h" #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" #include "webrtc/modules/rtp_rtcp/source/byte_io.h" #include "webrtc/modules/rtp_rtcp/source/producer_fec.h" #include "webrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h" #include "webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_format_vp9.h" #include "webrtc/modules/rtp_rtcp/source/rtp_format_h264.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/logging.h" @@ -323,7 +327,7 @@ bool RTPSenderVideo::Send(const RtpVideoCodecTypes videoType, // output multiple partitions for VP8. Should remove below check after the // issue is fixed. const RTPFragmentationHeader* frag = - (videoType == kRtpVideoVp8 || videoType == kRtpVideoVp9) ? NULL : fragmentation; + (videoType == kRtpVideoVp8) ? NULL : fragmentation; packetizer->SetPayloadData(data, payload_bytes_to_send, frag); @@ -360,7 +364,7 @@ bool RTPSenderVideo::Send(const RtpVideoCodecTypes videoType, // a lock. It'll be a no-op if it's not registered. // TODO(guoweis): For now, all packets sent will carry the CVO such that // the RTP header length is consistent, although the receiver side will - // only exam the packets with market bit set. + // only exam the packets with marker bit set. size_t packetSize = payloadSize + rtp_header_length; RtpUtility::RtpHeaderParser rtp_parser(dataBuffer, packetSize); RTPHeader rtp_header; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_utility.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_utility.cc index cb9020c566..fadf21fec4 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_utility.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_utility.cc @@ -242,6 +242,9 @@ bool RtpHeaderParser::ParseRtcp(RTPHeader* header) const { header->payloadType = PT; header->ssrc = SSRC; header->headerLength = 4 + (len << 2); + if (header->headerLength > static_cast(length)) { + return false; + } return true; } @@ -279,12 +282,6 @@ bool RtpHeaderParser::Parse(RTPHeader& header, return false; } - const size_t CSRCocts = CC * 4; - - if ((ptr + CSRCocts) > _ptrRTPDataEnd) { - return false; - } - header.markerBit = M; header.payloadType = PT; header.sequenceNumber = sequenceNumber; @@ -293,13 +290,20 @@ bool RtpHeaderParser::Parse(RTPHeader& header, header.numCSRCs = CC; header.paddingLength = P ? *(_ptrRTPDataEnd - 1) : 0; + // 12 == sizeof(RFC rtp header) == kRtpMinParseLength, each CSRC=4 bytes + header.headerLength = 12 + (CC * 4); + // not a full validation, just safety against underflow. Padding must + // start after the header. We can have 0 payload bytes left, note. + if (header.paddingLength + header.headerLength > (size_t) length) { + return false; + } + for (uint8_t i = 0; i < CC; ++i) { uint32_t CSRC = ByteReader::ReadBigEndian(ptr); ptr += 4; header.arrOfCSRCs[i] = CSRC; } - - header.headerLength = 12 + CSRCocts; + assert((ptr - _ptrRTPDataBegin) == (ptrdiff_t) header.headerLength); // If in effect, MAY be omitted for those packets for which the offset // is zero. @@ -318,6 +322,10 @@ bool RtpHeaderParser::Parse(RTPHeader& header, header.extension.hasVideoRotation = false; header.extension.videoRotation = 0; + // May not be present in packet. + header.extension.hasRID = false; + header.extension.rid = NULL; + if (X) { /* RTP header extension, RFC 3550. 0 1 2 3 @@ -328,8 +336,9 @@ bool RtpHeaderParser::Parse(RTPHeader& header, | header extension | | .... | */ - const ptrdiff_t remain = _ptrRTPDataEnd - ptr; - if (remain < 4) { + // earlier test ensures we have at least paddingLength bytes left + const ptrdiff_t remain = (_ptrRTPDataEnd - ptr) - header.paddingLength; + if (remain < 4) { // minimum header extension length = 32 bits return false; } @@ -481,6 +490,21 @@ void RtpHeaderParser::ParseOneByteExtensionHeader( header.extension.hasTransportSequenceNumber = true; break; } + case kRtpExtensionRID: { + // 0 1 2 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | ID | L=? |UTF-8 RID value...... |... + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // TODO(jesup) - avoid allocating on each packet - high watermark the RID buffer? + char* ptrRID = new char[len+1]; + memcpy(ptrRID, ptr, len); + ptrRID[len] = '\0'; + header.extension.rid = ptrRID; + header.extension.hasRID = true; + break; + } default: { LOG(LS_WARNING) << "Extension type not implemented: " << type; return; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/ssrc_database.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/ssrc_database.h index 2d4932afa7..e95b8324d6 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/ssrc_database.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/ssrc_database.h @@ -29,10 +29,10 @@ public: int32_t RegisterSSRC(const uint32_t ssrc); int32_t ReturnSSRC(const uint32_t ssrc); -protected: SSRCDatabase(); virtual ~SSRCDatabase(); +protected: static SSRCDatabase* CreateInstance() { return new SSRCDatabase(); } private: diff --git a/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.cc b/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.cc index 2c4fb09fa3..6c9295a1c9 100644 --- a/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.cc +++ b/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.cc @@ -164,7 +164,7 @@ int32_t DeviceInfoLinux::GetDeviceName( } else { // if there's no bus info to use for uniqueId, invent one - and it has to be repeatable if (snprintf(deviceUniqueIdUTF8, deviceUniqueIdUTF8Length, "fake_%u", device_index) >= - deviceUniqueIdUTF8Length) + (int) deviceUniqueIdUTF8Length) { WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id, "buffer passed is too small"); diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/interface/video_codec_interface.h b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/interface/video_codec_interface.h index c126147f18..cecfeb1eec 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/interface/video_codec_interface.h +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/interface/video_codec_interface.h @@ -43,16 +43,35 @@ struct CodecSpecificInfoVP8 { }; struct CodecSpecificInfoVP9 { - bool hasReceivedSLI; - uint8_t pictureIdSLI; - bool hasReceivedRPSI; - uint64_t pictureIdRPSI; - int16_t pictureId; // Negative value to skip pictureId. - bool nonReference; - uint8_t temporalIdx; - bool layerSync; - int tl0PicIdx; // Negative value to skip tl0PicIdx. - int8_t keyIdx; // Negative value to skip keyIdx. + bool has_received_sli; + uint8_t picture_id_sli; + bool has_received_rpsi; + uint64_t picture_id_rpsi; + int16_t picture_id; // Negative value to skip pictureId. + + bool inter_pic_predicted; // This layer frame is dependent on previously + // coded frame(s). + bool flexible_mode; + bool ss_data_available; + + int tl0_pic_idx; // Negative value to skip tl0PicIdx. + uint8_t temporal_idx; + uint8_t spatial_idx; + bool temporal_up_switch; + bool inter_layer_predicted; // Frame is dependent on directly lower spatial + // layer frame. + uint8_t gof_idx; + + // SS data. + size_t num_spatial_layers; // Always populated. + bool spatial_layer_resolution_present; + uint16_t width[kMaxVp9NumberOfSpatialLayers]; + uint16_t height[kMaxVp9NumberOfSpatialLayers]; + GofInfoVP9 gof; + + // Frame reference data. + uint8_t num_ref_pics; + uint8_t p_diff[kMaxVp9RefPics]; }; struct CodecSpecificInfoGeneric { diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.cc b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.cc new file mode 100644 index 0000000000..53e6647cfd --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.cc @@ -0,0 +1,93 @@ +/* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. +* +* Use of this source code is governed by a BSD-style license +* that can be found in the LICENSE file in the root of the source +* tree. An additional intellectual property rights grant can be found +* in the file PATENTS. All contributing project authors may +* be found in the AUTHORS file in the root of the source tree. +*/ + +#include +#include "webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h" +#include "webrtc/base/checks.h" + +namespace webrtc { + +ScreenshareLayersVP9::ScreenshareLayersVP9(uint8_t num_layers) + : num_layers_(num_layers), + start_layer_(0), + last_timestamp_(0), + timestamp_initialized_(false) { + DCHECK_GT(num_layers, 0); + DCHECK_LE(num_layers, kMaxVp9NumberOfSpatialLayers); + memset(bits_used_, 0, sizeof(bits_used_)); + memset(threshold_kbps_, 0, sizeof(threshold_kbps_)); +} + +uint8_t ScreenshareLayersVP9::GetStartLayer() const { + return start_layer_; +} + +void ScreenshareLayersVP9::ConfigureBitrate(int threshold_kbps, + uint8_t layer_id) { + // The upper layer is always the layer we spill frames + // to when the bitrate becomes to high, therefore setting + // a max limit is not allowed. The top layer bitrate is + // never used either so configuring it makes no difference. + DCHECK_LT(layer_id, num_layers_ - 1); + threshold_kbps_[layer_id] = threshold_kbps; +} + +void ScreenshareLayersVP9::LayerFrameEncoded(unsigned int size_bytes, + uint8_t layer_id) { + DCHECK_LT(layer_id, num_layers_); + bits_used_[layer_id] += size_bytes * 8; +} + +VP9EncoderImpl::SuperFrameRefSettings +ScreenshareLayersVP9::GetSuperFrameSettings(uint32_t timestamp, + bool is_keyframe) { + VP9EncoderImpl::SuperFrameRefSettings settings; + if (!timestamp_initialized_) { + last_timestamp_ = timestamp; + timestamp_initialized_ = true; + } + float time_diff = (timestamp - last_timestamp_) / 90.f; + float total_bits_used = 0; + float total_threshold_kbps = 0; + start_layer_ = 0; + + // Up to (num_layers - 1) because we only have + // (num_layers - 1) thresholds to check. + for (int layer_id = 0; layer_id < num_layers_ - 1; ++layer_id) { + bits_used_[layer_id] = std::max( + 0.f, bits_used_[layer_id] - time_diff * threshold_kbps_[layer_id]); + total_bits_used += bits_used_[layer_id]; + total_threshold_kbps += threshold_kbps_[layer_id]; + + // If this is a keyframe then there should be no + // references to any previous frames. + if (!is_keyframe) { + settings.layer[layer_id].ref_buf1 = layer_id; + if (total_bits_used > total_threshold_kbps * 1000) + start_layer_ = layer_id + 1; + } + + settings.layer[layer_id].upd_buf = layer_id; + } + // Since the above loop does not iterate over the last layer + // the reference of the last layer has to be set after the loop, + // and if this is a keyframe there should be no references to + // any previous frames. + if (!is_keyframe) + settings.layer[num_layers_ - 1].ref_buf1 = num_layers_ - 1; + + settings.layer[num_layers_ - 1].upd_buf = num_layers_ - 1; + settings.is_keyframe = is_keyframe; + settings.start_layer = start_layer_; + settings.stop_layer = num_layers_ - 1; + last_timestamp_ = timestamp; + return settings; +} + +} // namespace webrtc diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h new file mode 100644 index 0000000000..5a901ae359 --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. +* +* Use of this source code is governed by a BSD-style license +* that can be found in the LICENSE file in the root of the source +* tree. An additional intellectual property rights grant can be found +* in the file PATENTS. All contributing project authors may +* be found in the AUTHORS file in the root of the source tree. +*/ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_SCREENSHARE_LAYERS_H_ +#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_SCREENSHARE_LAYERS_H_ + +#include "webrtc/modules/video_coding/codecs/vp9/vp9_impl.h" + +namespace webrtc { + +class ScreenshareLayersVP9 { + public: + explicit ScreenshareLayersVP9(uint8_t num_layers); + + // The target bitrate for layer with id layer_id. + void ConfigureBitrate(int threshold_kbps, uint8_t layer_id); + + // The current start layer. + uint8_t GetStartLayer() const; + + // Update the layer with the size of the layer frame. + void LayerFrameEncoded(unsigned int size_bytes, uint8_t layer_id); + + // Get the layer settings for the next superframe. + // + // In short, each time the GetSuperFrameSettings is called the + // bitrate of every layer is calculated and if the cummulative + // bitrate exceeds the configured cummulative bitrates + // (ConfigureBitrate to configure) up to and including that + // layer then the resulting encoding settings for the + // superframe will only encode layers above that layer. + VP9EncoderImpl::SuperFrameRefSettings GetSuperFrameSettings( + uint32_t timestamp, + bool is_keyframe); + + private: + // How many layers that are used. + uint8_t num_layers_; + + // The index of the first layer to encode. + uint8_t start_layer_; + + // Cummulative target kbps for the different layers. + float threshold_kbps_[kMaxVp9NumberOfSpatialLayers - 1]; + + // How many bits that has been used for a certain layer. Increased in + // FrameEncoded() by the size of the encoded frame and decreased in + // GetSuperFrameSettings() depending on the time between frames. + float bits_used_[kMaxVp9NumberOfSpatialLayers]; + + // Timestamp of last frame. + uint32_t last_timestamp_; + + // If the last_timestamp_ has been set. + bool timestamp_initialized_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_SCREENSHARE_LAYERS_H_ diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers_unittest.cc b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers_unittest.cc new file mode 100644 index 0000000000..5eb7b237ac --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/screenshare_layers_unittest.cc @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "vpx/vp8cx.h" +#include "webrtc/base/logging.h" +#include "webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h" +#include "webrtc/modules/video_coding/codecs/vp9/vp9_impl.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { + +typedef VP9EncoderImpl::SuperFrameRefSettings Settings; + +const uint32_t kTickFrequency = 90000; + +class ScreenshareLayerTestVP9 : public ::testing::Test { + protected: + ScreenshareLayerTestVP9() : clock_(0) {} + virtual ~ScreenshareLayerTestVP9() {} + + void InitScreenshareLayers(int layers) { + layers_.reset(new ScreenshareLayersVP9(layers)); + } + + void ConfigureBitrateForLayer(int kbps, uint8_t layer_id) { + layers_->ConfigureBitrate(kbps, layer_id); + } + + void AdvanceTime(int64_t milliseconds) { + clock_.AdvanceTimeMilliseconds(milliseconds); + } + + void AddKilobitsToLayer(int kilobits, uint8_t layer_id) { + layers_->LayerFrameEncoded(kilobits * 1000 / 8, layer_id); + } + + void EqualRefsForLayer(const Settings& actual, uint8_t layer_id) { + EXPECT_EQ(expected_.layer[layer_id].upd_buf, + actual.layer[layer_id].upd_buf); + EXPECT_EQ(expected_.layer[layer_id].ref_buf1, + actual.layer[layer_id].ref_buf1); + EXPECT_EQ(expected_.layer[layer_id].ref_buf2, + actual.layer[layer_id].ref_buf2); + EXPECT_EQ(expected_.layer[layer_id].ref_buf3, + actual.layer[layer_id].ref_buf3); + } + + void EqualRefs(const Settings& actual) { + for (unsigned int layer_id = 0; layer_id < kMaxVp9NumberOfSpatialLayers; + ++layer_id) { + EqualRefsForLayer(actual, layer_id); + } + } + + void EqualStartStopKeyframe(const Settings& actual) { + EXPECT_EQ(expected_.start_layer, actual.start_layer); + EXPECT_EQ(expected_.stop_layer, actual.stop_layer); + EXPECT_EQ(expected_.is_keyframe, actual.is_keyframe); + } + + // Check that the settings returned by GetSuperFrameSettings() is + // equal to the expected_ settings. + void EqualToExpected() { + uint32_t frame_timestamp_ = + clock_.TimeInMilliseconds() * (kTickFrequency / 1000); + Settings actual = + layers_->GetSuperFrameSettings(frame_timestamp_, expected_.is_keyframe); + EqualRefs(actual); + EqualStartStopKeyframe(actual); + } + + Settings expected_; + SimulatedClock clock_; + rtc::scoped_ptr layers_; +}; + +TEST_F(ScreenshareLayerTestVP9, NoRefsOnKeyFrame) { + const int kNumLayers = kMaxVp9NumberOfSpatialLayers; + InitScreenshareLayers(kNumLayers); + expected_.start_layer = 0; + expected_.stop_layer = kNumLayers - 1; + + for (int l = 0; l < kNumLayers; ++l) { + expected_.layer[l].upd_buf = l; + } + expected_.is_keyframe = true; + EqualToExpected(); + + for (int l = 0; l < kNumLayers; ++l) { + expected_.layer[l].ref_buf1 = l; + } + expected_.is_keyframe = false; + EqualToExpected(); +} + +// Test if it is possible to send at a high bitrate (over the threshold) +// after a longer period of low bitrate. This should not be possible. +TEST_F(ScreenshareLayerTestVP9, DontAccumelateAvailableBitsOverTime) { + InitScreenshareLayers(2); + ConfigureBitrateForLayer(100, 0); + + expected_.layer[0].upd_buf = 0; + expected_.layer[0].ref_buf1 = 0; + expected_.layer[1].upd_buf = 1; + expected_.layer[1].ref_buf1 = 1; + expected_.start_layer = 0; + expected_.stop_layer = 1; + + // Send 10 frames at a low bitrate (50 kbps) + for (int i = 0; i < 10; ++i) { + AdvanceTime(200); + EqualToExpected(); + AddKilobitsToLayer(10, 0); + } + + AdvanceTime(200); + EqualToExpected(); + AddKilobitsToLayer(301, 0); + + // Send 10 frames at a high bitrate (200 kbps) + expected_.start_layer = 1; + for (int i = 0; i < 10; ++i) { + AdvanceTime(200); + EqualToExpected(); + AddKilobitsToLayer(40, 1); + } +} + +// Test if used bits are accumelated over layers, as they should; +TEST_F(ScreenshareLayerTestVP9, AccumelateUsedBitsOverLayers) { + const int kNumLayers = kMaxVp9NumberOfSpatialLayers; + InitScreenshareLayers(kNumLayers); + for (int l = 0; l < kNumLayers - 1; ++l) + ConfigureBitrateForLayer(100, l); + for (int l = 0; l < kNumLayers; ++l) { + expected_.layer[l].upd_buf = l; + expected_.layer[l].ref_buf1 = l; + } + + expected_.start_layer = 0; + expected_.stop_layer = kNumLayers - 1; + EqualToExpected(); + + for (int layer = 0; layer < kNumLayers - 1; ++layer) { + expected_.start_layer = layer; + EqualToExpected(); + AddKilobitsToLayer(101, layer); + } +} + +// General testing of the bitrate controller. +TEST_F(ScreenshareLayerTestVP9, 2LayerBitrate) { + InitScreenshareLayers(2); + ConfigureBitrateForLayer(100, 0); + + expected_.layer[0].upd_buf = 0; + expected_.layer[1].upd_buf = 1; + expected_.layer[0].ref_buf1 = -1; + expected_.layer[1].ref_buf1 = -1; + expected_.start_layer = 0; + expected_.stop_layer = 1; + + expected_.is_keyframe = true; + EqualToExpected(); + AddKilobitsToLayer(100, 0); + + expected_.layer[0].ref_buf1 = 0; + expected_.layer[1].ref_buf1 = 1; + expected_.is_keyframe = false; + AdvanceTime(199); + EqualToExpected(); + AddKilobitsToLayer(100, 0); + + expected_.start_layer = 1; + for (int frame = 0; frame < 3; ++frame) { + AdvanceTime(200); + EqualToExpected(); + AddKilobitsToLayer(100, 1); + } + + // Just before enough bits become available for L0 @0.999 seconds. + AdvanceTime(199); + EqualToExpected(); + AddKilobitsToLayer(100, 1); + + // Just after enough bits become available for L0 @1.0001 seconds. + expected_.start_layer = 0; + AdvanceTime(2); + EqualToExpected(); + AddKilobitsToLayer(100, 0); + + // Keyframes always encode all layers, even if it is over budget. + expected_.layer[0].ref_buf1 = -1; + expected_.layer[1].ref_buf1 = -1; + expected_.is_keyframe = true; + AdvanceTime(499); + EqualToExpected(); + expected_.layer[0].ref_buf1 = 0; + expected_.layer[1].ref_buf1 = 1; + expected_.start_layer = 1; + expected_.is_keyframe = false; + EqualToExpected(); + AddKilobitsToLayer(100, 0); + + // 400 kb in L0 --> @3 second mark to fall below the threshold.. + // just before @2.999 seconds. + expected_.is_keyframe = false; + AdvanceTime(1499); + EqualToExpected(); + AddKilobitsToLayer(100, 1); + + // just after @3.001 seconds. + expected_.start_layer = 0; + AdvanceTime(2); + EqualToExpected(); + AddKilobitsToLayer(100, 0); +} + +// General testing of the bitrate controller. +TEST_F(ScreenshareLayerTestVP9, 3LayerBitrate) { + InitScreenshareLayers(3); + ConfigureBitrateForLayer(100, 0); + ConfigureBitrateForLayer(100, 1); + + for (int l = 0; l < 3; ++l) { + expected_.layer[l].upd_buf = l; + expected_.layer[l].ref_buf1 = l; + } + expected_.start_layer = 0; + expected_.stop_layer = 2; + + EqualToExpected(); + AddKilobitsToLayer(105, 0); + AddKilobitsToLayer(30, 1); + + AdvanceTime(199); + EqualToExpected(); + AddKilobitsToLayer(105, 0); + AddKilobitsToLayer(30, 1); + + expected_.start_layer = 1; + AdvanceTime(200); + EqualToExpected(); + AddKilobitsToLayer(130, 1); + + expected_.start_layer = 2; + AdvanceTime(200); + EqualToExpected(); + + // 400 kb in L1 --> @1.0 second mark to fall below threshold. + // 210 kb in L0 --> @1.1 second mark to fall below threshold. + // Just before L1 @0.999 seconds. + AdvanceTime(399); + EqualToExpected(); + + // Just after L1 @1.001 seconds. + expected_.start_layer = 1; + AdvanceTime(2); + EqualToExpected(); + + // Just before L0 @1.099 seconds. + AdvanceTime(99); + EqualToExpected(); + + // Just after L0 @1.101 seconds. + expected_.start_layer = 0; + AdvanceTime(2); + EqualToExpected(); + + // @1.1 seconds + AdvanceTime(99); + EqualToExpected(); + AddKilobitsToLayer(200, 1); + + expected_.is_keyframe = true; + for (int l = 0; l < 3; ++l) + expected_.layer[l].ref_buf1 = -1; + AdvanceTime(200); + EqualToExpected(); + + expected_.is_keyframe = false; + expected_.start_layer = 2; + for (int l = 0; l < 3; ++l) + expected_.layer[l].ref_buf1 = l; + AdvanceTime(200); + EqualToExpected(); +} + +// Test that the bitrate calculations are +// correct when the timestamp wrap. +TEST_F(ScreenshareLayerTestVP9, TimestampWrap) { + InitScreenshareLayers(2); + ConfigureBitrateForLayer(100, 0); + + expected_.layer[0].upd_buf = 0; + expected_.layer[0].ref_buf1 = 0; + expected_.layer[1].upd_buf = 1; + expected_.layer[1].ref_buf1 = 1; + expected_.start_layer = 0; + expected_.stop_layer = 1; + + // Advance time to just before the timestamp wraps. + AdvanceTime(std::numeric_limits::max() / (kTickFrequency / 1000)); + EqualToExpected(); + AddKilobitsToLayer(200, 0); + + // Wrap + expected_.start_layer = 1; + AdvanceTime(1); + EqualToExpected(); +} + +} // namespace webrtc diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9.gyp b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9.gyp index 795db62a30..9e02cd6e8d 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9.gyp +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9.gyp @@ -22,12 +22,20 @@ 'conditions': [ ['build_libvpx==1', { 'dependencies': [ - '<(libvpx_dir)/libvpx.gyp:libvpx', + '<(libvpx_dir)/libvpx.gyp:libvpx_new', ], - }], + }, { + 'include_dirs': [ + '../../../../../../../libvpx', + ], + }], ['build_vp9==1', { 'sources': [ 'include/vp9.h', + 'screenshare_layers.cc', + 'screenshare_layers.h', + 'vp9_frame_buffer_pool.cc', + 'vp9_frame_buffer_pool.h', 'vp9_impl.cc', 'vp9_impl.h', ], diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.cc b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.cc new file mode 100644 index 0000000000..fceb4bf9d3 --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + * + */ + +#include "webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h" + +#include "vpx/vpx_codec.h" +#include "vpx/vpx_decoder.h" +#include "vpx/vpx_frame_buffer.h" + +#include "webrtc/base/checks.h" +#include "webrtc/system_wrappers/interface/logging.h" + +namespace webrtc { + +uint8_t* Vp9FrameBufferPool::Vp9FrameBuffer::GetData() { + return (uint8_t*)(data_.data()); //data(); +} + +size_t Vp9FrameBufferPool::Vp9FrameBuffer::GetDataSize() const { + return data_.size(); +} + +void Vp9FrameBufferPool::Vp9FrameBuffer::SetSize(size_t size) { + data_.SetSize(size); +} + +bool Vp9FrameBufferPool::InitializeVpxUsePool( + vpx_codec_ctx* vpx_codec_context) { + DCHECK(vpx_codec_context); + // Tell libvpx to use this pool. + if (vpx_codec_set_frame_buffer_functions( + // In which context to use these callback functions. + vpx_codec_context, + // Called by libvpx when it needs another frame buffer. + &Vp9FrameBufferPool::VpxGetFrameBuffer, + // Called by libvpx when it no longer uses a frame buffer. + &Vp9FrameBufferPool::VpxReleaseFrameBuffer, + // |this| will be passed as |user_priv| to VpxGetFrameBuffer. + this)) { + // Failed to configure libvpx to use Vp9FrameBufferPool. + return false; + } + return true; +} + +rtc::scoped_refptr +Vp9FrameBufferPool::GetFrameBuffer(size_t min_size) { + DCHECK_GT(min_size, 0u); + rtc::scoped_refptr available_buffer = nullptr; + { + rtc::CritScope cs(&buffers_lock_); + // Do we have a buffer we can recycle? + for (const auto& buffer : allocated_buffers_) { + if (buffer->HasOneRef()) { + available_buffer = buffer; + break; + } + } + // Otherwise create one. + if (available_buffer == nullptr) { + available_buffer = new rtc::RefCountedObject(); + allocated_buffers_.push_back(available_buffer); + if (allocated_buffers_.size() > max_num_buffers_) { + LOG(LS_WARNING) + << allocated_buffers_.size() << " Vp9FrameBuffers have been " + << "allocated by a Vp9FrameBufferPool (exceeding what is " + << "considered reasonable, " << max_num_buffers_ << ")."; + RTC_NOTREACHED(); + } + } + } + + available_buffer->SetSize(min_size); + return available_buffer; +} + +int Vp9FrameBufferPool::GetNumBuffersInUse() const { + int num_buffers_in_use = 0; + rtc::CritScope cs(&buffers_lock_); + for (const auto& buffer : allocated_buffers_) { + if (!buffer->HasOneRef()) + ++num_buffers_in_use; + } + return num_buffers_in_use; +} + +void Vp9FrameBufferPool::ClearPool() { + rtc::CritScope cs(&buffers_lock_); + allocated_buffers_.clear(); +} + +// static +int32_t Vp9FrameBufferPool::VpxGetFrameBuffer(void* user_priv, + size_t min_size, + vpx_codec_frame_buffer* fb) { + DCHECK(user_priv); + DCHECK(fb); + Vp9FrameBufferPool* pool = static_cast(user_priv); + + rtc::scoped_refptr buffer = pool->GetFrameBuffer(min_size); + fb->data = buffer->GetData(); + fb->size = buffer->GetDataSize(); + // Store Vp9FrameBuffer* in |priv| for use in VpxReleaseFrameBuffer. + // This also makes vpx_codec_get_frame return images with their |fb_priv| set + // to |buffer| which is important for external reference counting. + // Release from refptr so that the buffer's |ref_count_| remains 1 when + // |buffer| goes out of scope. + fb->priv = static_cast(buffer.release()); + return 0; +} + +// static +int32_t Vp9FrameBufferPool::VpxReleaseFrameBuffer(void* user_priv, + vpx_codec_frame_buffer* fb) { + DCHECK(user_priv); + DCHECK(fb); + Vp9FrameBuffer* buffer = static_cast(fb->priv); + if (buffer != nullptr) { + buffer->Release(); + // When libvpx fails to decode and you continue to try to decode (and fail) + // libvpx can for some reason try to release the same buffer multiple times. + // Setting |priv| to null protects against trying to Release multiple times. + fb->priv = nullptr; + } + return 0; +} + +} // namespace webrtc diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h new file mode 100644 index 0000000000..97ed41a015 --- /dev/null +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + * + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_FRAME_BUFFER_POOL_H_ +#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_FRAME_BUFFER_POOL_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ref_ptr.h" + +struct vpx_codec_ctx; +struct vpx_codec_frame_buffer; + +namespace webrtc { + +// This memory pool is used to serve buffers to libvpx for decoding purposes in +// VP9, which is set up in InitializeVPXUsePool. After the initialization any +// time libvpx wants to decode a frame it will use buffers provided and released +// through VpxGetFrameBuffer and VpxReleaseFrameBuffer. +// The benefit of owning the pool that libvpx relies on for decoding is that the +// decoded frames returned by libvpx (from vpx_codec_get_frame) use parts of our +// buffers for the decoded image data. By retaining ownership of this buffer +// using scoped_refptr, the image buffer can be reused by VideoFrames and no +// frame copy has to occur during decoding and frame delivery. +// +// Pseudo example usage case: +// Vp9FrameBufferPool pool; +// pool.InitializeVpxUsePool(decoder_ctx); +// ... +// +// // During decoding, libvpx will get and release buffers from the pool. +// vpx_codec_decode(decoder_ctx, ...); +// +// vpx_image_t* img = vpx_codec_get_frame(decoder_ctx, &iter); +// // Important to use scoped_refptr to protect it against being recycled by +// // the pool. +// scoped_refptr img_buffer = (Vp9FrameBuffer*)img->fb_priv; +// ... +// +// // Destroying the codec will make libvpx release any buffers it was using. +// vpx_codec_destroy(decoder_ctx); +class Vp9FrameBufferPool { + public: + class Vp9FrameBuffer : public rtc::RefCountInterface { + public: + uint8_t* GetData(); + size_t GetDataSize() const; + void SetSize(size_t size); + + virtual bool HasOneRef() const = 0; + + private: + // Data as an easily resizable buffer. + rtc::Buffer data_; + }; + + // Configures libvpx to, in the specified context, use this memory pool for + // buffers used to decompress frames. This is only supported for VP9. + bool InitializeVpxUsePool(vpx_codec_ctx* vpx_codec_context); + + // Gets a frame buffer of at least |min_size|, recycling an available one or + // creating a new one. When no longer referenced from the outside the buffer + // becomes recyclable. + rtc::scoped_refptr GetFrameBuffer(size_t min_size); + // Gets the number of buffers currently in use (not ready to be recycled). + int GetNumBuffersInUse() const; + // Releases allocated buffers, deleting available buffers. Buffers in use are + // not deleted until they are no longer referenced. + void ClearPool(); + + // InitializeVpxUsePool configures libvpx to call this function when it needs + // a new frame buffer. Parameters: + // |user_priv| Private data passed to libvpx, InitializeVpxUsePool sets it up + // to be a pointer to the pool. + // |min_size| Minimum size needed by libvpx (to decompress a frame). + // |fb| Pointer to the libvpx frame buffer object, this is updated to + // use the pool's buffer. + // Returns 0 on success. Returns < 0 on failure. + static int32_t VpxGetFrameBuffer(void* user_priv, + size_t min_size, + vpx_codec_frame_buffer* fb); + + // InitializeVpxUsePool configures libvpx to call this function when it has + // finished using one of the pool's frame buffer. Parameters: + // |user_priv| Private data passed to libvpx, InitializeVpxUsePool sets it up + // to be a pointer to the pool. + // |fb| Pointer to the libvpx frame buffer object, its |priv| will be + // a pointer to one of the pool's Vp9FrameBuffer. + static int32_t VpxReleaseFrameBuffer(void* user_priv, + vpx_codec_frame_buffer* fb); + + private: + // Protects |allocated_buffers_|. + mutable rtc::CriticalSection buffers_lock_; + // All buffers, in use or ready to be recycled. + std::vector> allocated_buffers_ + GUARDED_BY(buffers_lock_); + // If more buffers than this are allocated we print warnings, and crash if + // in debug mode. + static const size_t max_num_buffers_ = 10; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_FRAME_BUFFER_POOL_H_ diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.cc b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.cc index 310e53af5e..850403e60e 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.cc +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.cc @@ -21,19 +21,49 @@ #include "vpx/vp8cx.h" #include "vpx/vp8dx.h" +#include "webrtc/base/bind.h" #include "webrtc/base/checks.h" +#include "webrtc/base/trace_event.h" #include "webrtc/common.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/modules/interface/module_common_types.h" +#include "webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/tick_util.h" -#include "webrtc/system_wrappers/interface/trace_event.h" + +namespace { + +// VP9DecoderImpl::ReturnFrame helper function used with WrappedI420Buffer. +static void WrappedI420BufferNoLongerUsedCb( + webrtc::Vp9FrameBufferPool::Vp9FrameBuffer* img_buffer) { + img_buffer->Release(); +} + +} // anonymous namespace namespace webrtc { +// Only positive speeds, range for real-time coding currently is: 5 - 8. +// Lower means slower/better quality, higher means fastest/lower quality. +int GetCpuSpeed(int width, int height) { + // For smaller resolutions, use lower speed setting (get some coding gain at + // the cost of increased encoding complexity). + if (width * height <= 352 * 288) + return 5; + else + return 7; +} + VP9Encoder* VP9Encoder::Create() { return new VP9EncoderImpl(); } +void VP9EncoderImpl::EncoderOutputCodedPacketCallback(vpx_codec_cx_pkt* pkt, + void* user_data) { + VP9EncoderImpl* enc = (VP9EncoderImpl*)(user_data); + enc->GetEncodedLayerFrame(pkt); +} + VP9EncoderImpl::VP9EncoderImpl() : encoded_image_(), encoded_complete_callback_(NULL), @@ -44,7 +74,15 @@ VP9EncoderImpl::VP9EncoderImpl() rc_max_intra_target_(0), encoder_(NULL), config_(NULL), - raw_(NULL) { + raw_(NULL), + input_image_(NULL), + tl0_pic_idx_(0), + frames_since_kf_(0), + num_temporal_layers_(0), + num_spatial_layers_(0), + frames_encoded_(0), + // Use two spatial when screensharing with flexible mode. + spatial_layer_(new ScreenshareLayersVP9(2)) { memset(&codec_, 0, sizeof(codec_)); uint32_t seed = static_cast(TickTime::MillisecondTimestamp()); srand(seed); @@ -78,6 +116,91 @@ int VP9EncoderImpl::Release() { return WEBRTC_VIDEO_CODEC_OK; } +bool VP9EncoderImpl::ExplicitlyConfiguredSpatialLayers() const { + // We check target_bitrate_bps of the 0th layer to see if the spatial layers + // (i.e. bitrates) were explicitly configured. +#ifdef LIBVPX_SVC + return num_spatial_layers_ > 1 && + codec_.spatialLayers[0].target_bitrate_bps > 0; +#else + return false; +#endif +} + +bool VP9EncoderImpl::SetSvcRates() { + uint8_t i = 0; + + if (ExplicitlyConfiguredSpatialLayers()) { +#ifdef LIBVPX_SVC + if (num_temporal_layers_ > 1) { + LOG(LS_ERROR) << "Multiple temporal layers when manually specifying " + "spatial layers not implemented yet!"; + return false; + } + int total_bitrate_bps = 0; + for (i = 0; i < num_spatial_layers_; ++i) + total_bitrate_bps += codec_.spatialLayers[i].target_bitrate_bps; + // If total bitrate differs now from what has been specified at the + // beginning, update the bitrates in the same ratio as before. + for (i = 0; i < num_spatial_layers_; ++i) { + config_->ss_target_bitrate[i] = config_->layer_target_bitrate[i] = + static_cast(static_cast(config_->rc_target_bitrate) * + codec_.spatialLayers[i].target_bitrate_bps / + total_bitrate_bps); + } +#endif + } else { + float rate_ratio[VPX_MAX_LAYERS] = {0}; + float total = 0; + + for (i = 0; i < num_spatial_layers_; ++i) { + if (svc_internal_.svc_params.scaling_factor_num[i] <= 0 || + svc_internal_.svc_params.scaling_factor_den[i] <= 0) { + LOG(LS_ERROR) << "Scaling factors not specified!"; + return false; + } + rate_ratio[i] = + static_cast(svc_internal_.svc_params.scaling_factor_num[i]) / + svc_internal_.svc_params.scaling_factor_den[i]; + total += rate_ratio[i]; + } + + for (i = 0; i < num_spatial_layers_; ++i) { + config_->ss_target_bitrate[i] = static_cast( + config_->rc_target_bitrate * rate_ratio[i] / total); + if (num_temporal_layers_ == 1) { + config_->layer_target_bitrate[i] = config_->ss_target_bitrate[i]; + } else if (num_temporal_layers_ == 2) { + config_->layer_target_bitrate[i * num_temporal_layers_] = + config_->ss_target_bitrate[i] * 2 / 3; + config_->layer_target_bitrate[i * num_temporal_layers_ + 1] = + config_->ss_target_bitrate[i]; + } else if (num_temporal_layers_ == 3) { + config_->layer_target_bitrate[i * num_temporal_layers_] = + config_->ss_target_bitrate[i] / 2; + config_->layer_target_bitrate[i * num_temporal_layers_ + 1] = + config_->layer_target_bitrate[i * num_temporal_layers_] + + (config_->ss_target_bitrate[i] / 4); + config_->layer_target_bitrate[i * num_temporal_layers_ + 2] = + config_->ss_target_bitrate[i]; + } else { + LOG(LS_ERROR) << "Unsupported number of temporal layers: " + << num_temporal_layers_; + return false; + } + } + } + + // For now, temporal layers only supported when having one spatial layer. + if (num_spatial_layers_ == 1) { + for (i = 0; i < num_temporal_layers_; ++i) { + config_->ts_target_bitrate[i] = config_->layer_target_bitrate[i]; + } + } + + return true; +} + int VP9EncoderImpl::SetRates(uint32_t new_bitrate_kbit, uint32_t new_framerate) { if (!inited_) { @@ -95,6 +218,12 @@ int VP9EncoderImpl::SetRates(uint32_t new_bitrate_kbit, } config_->rc_target_bitrate = new_bitrate_kbit; codec_.maxFramerate = new_framerate; + spatial_layer_->ConfigureBitrate(new_bitrate_kbit, 0); + + if (!SetSvcRates()) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + // Update encoder context if (vpx_codec_enc_config_set(encoder_, config_)) { return WEBRTC_VIDEO_CODEC_ERROR; @@ -121,6 +250,14 @@ int VP9EncoderImpl::InitEncode(const VideoCodec* inst, if (number_of_cores < 1) { return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; } + if (inst->codecSpecific.VP9.numberOfTemporalLayers > 3) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + // libvpx currently supports only one or two spatial layers. + if (inst->codecSpecific.VP9.numberOfSpatialLayers > 2) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + int retVal = Release(); if (retVal < 0) { return retVal; @@ -135,6 +272,12 @@ int VP9EncoderImpl::InitEncode(const VideoCodec* inst, if (&codec_ != inst) { codec_ = *inst; } + + num_spatial_layers_ = inst->codecSpecific.VP9.numberOfSpatialLayers; + num_temporal_layers_ = inst->codecSpecific.VP9.numberOfTemporalLayers; + if (num_temporal_layers_ == 0) + num_temporal_layers_ = 1; + // Random start 16 bits is enough. picture_id_ = static_cast(rand()) & 0x7FFF; // Allocate memory for encoded image @@ -182,11 +325,57 @@ int VP9EncoderImpl::InitEncode(const VideoCodec* inst, } else { config_->kf_mode = VPX_KF_DISABLED; } - + config_->rc_resize_allowed = inst->codecSpecific.VP9.automaticResizeOn ? + 1 : 0; // Determine number of threads based on the image size and #cores. config_->g_threads = NumberOfThreads(config_->g_w, config_->g_h, number_of_cores); + + cpu_speed_ = GetCpuSpeed(config_->g_w, config_->g_h); + + // TODO(asapersson): Check configuration of temporal switch up and increase + // pattern length. + is_flexible_mode_ = inst->codecSpecific.VP9.flexibleMode; + if (is_flexible_mode_) { + config_->temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_BYPASS; + config_->ts_number_layers = num_temporal_layers_; + if (codec_.mode == kScreensharing) + spatial_layer_->ConfigureBitrate(inst->startBitrate, 0); + } else if (num_temporal_layers_ == 1) { + gof_.SetGofInfoVP9(kTemporalStructureMode1); + config_->temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_NOLAYERING; + config_->ts_number_layers = 1; + config_->ts_rate_decimator[0] = 1; + config_->ts_periodicity = 1; + config_->ts_layer_id[0] = 0; + } else if (num_temporal_layers_ == 2) { + gof_.SetGofInfoVP9(kTemporalStructureMode2); + config_->temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_0101; + config_->ts_number_layers = 2; + config_->ts_rate_decimator[0] = 2; + config_->ts_rate_decimator[1] = 1; + config_->ts_periodicity = 2; + config_->ts_layer_id[0] = 0; + config_->ts_layer_id[1] = 1; + } else if (num_temporal_layers_ == 3) { + gof_.SetGofInfoVP9(kTemporalStructureMode3); + config_->temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_0212; + config_->ts_number_layers = 3; + config_->ts_rate_decimator[0] = 4; + config_->ts_rate_decimator[1] = 2; + config_->ts_rate_decimator[2] = 1; + config_->ts_periodicity = 4; + config_->ts_layer_id[0] = 0; + config_->ts_layer_id[1] = 2; + config_->ts_layer_id[2] = 1; + config_->ts_layer_id[3] = 2; + } else { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + tl0_pic_idx_ = static_cast(rand()); + return InitAndSetControlSettings(inst); } @@ -206,30 +395,73 @@ int VP9EncoderImpl::NumberOfThreads(int width, } int VP9EncoderImpl::InitAndSetControlSettings(const VideoCodec* inst) { + config_->ss_number_layers = num_spatial_layers_; + + if (ExplicitlyConfiguredSpatialLayers()) { +#ifdef LIBVPX_SVC + for (int i = 0; i < num_spatial_layers_; ++i) { + const auto& layer = codec_.spatialLayers[i]; + svc_internal_.svc_params.max_quantizers[i] = config_->rc_max_quantizer; + svc_internal_.svc_params.min_quantizers[i] = config_->rc_min_quantizer; + svc_internal_.svc_params.scaling_factor_num[i] = layer.scaling_factor_num; + svc_internal_.svc_params.scaling_factor_den[i] = layer.scaling_factor_den; + } +#endif + } else { + int scaling_factor_num = 256; + for (int i = num_spatial_layers_ - 1; i >= 0; --i) { + svc_internal_.svc_params.max_quantizers[i] = config_->rc_max_quantizer; + svc_internal_.svc_params.min_quantizers[i] = config_->rc_min_quantizer; + // 1:2 scaling in each dimension. + svc_internal_.svc_params.scaling_factor_num[i] = scaling_factor_num; + svc_internal_.svc_params.scaling_factor_den[i] = 256; + if (codec_.mode != kScreensharing) + scaling_factor_num /= 2; + } + } + + if (!SetSvcRates()) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (vpx_codec_enc_init(encoder_, vpx_codec_vp9_cx(), config_, 0)) { return WEBRTC_VIDEO_CODEC_UNINITIALIZED; } - // Only positive speeds, currently: 0 - 8. - // O means slowest/best quality, 8 means fastest/lower quality. - cpu_speed_ = 7; - // Note: some of these codec controls still use "VP8" in the control name. - // TODO(marpan): Update this in the next/future libvpx version. vpx_codec_control(encoder_, VP8E_SET_CPUUSED, cpu_speed_); vpx_codec_control(encoder_, VP8E_SET_MAX_INTRA_BITRATE_PCT, rc_max_intra_target_); vpx_codec_control(encoder_, VP9E_SET_AQ_MODE, inst->codecSpecific.VP9.adaptiveQpMode ? 3 : 0); + + vpx_codec_control( + encoder_, VP9E_SET_SVC, + (num_temporal_layers_ > 1 || num_spatial_layers_ > 1) ? 1 : 0); + if (num_temporal_layers_ > 1 || num_spatial_layers_ > 1) { + vpx_codec_control(encoder_, VP9E_SET_SVC_PARAMETERS, + &svc_internal_.svc_params); + } + // Register callback for getting each spatial layer. + vpx_codec_priv_output_cx_pkt_cb_pair_t cbp = { + VP9EncoderImpl::EncoderOutputCodedPacketCallback, (void*)(this)}; + vpx_codec_control(encoder_, VP9E_REGISTER_CX_CALLBACK, (void*)(&cbp)); + // Control function to set the number of column tiles in encoding a frame, in // log2 unit: e.g., 0 = 1 tile column, 1 = 2 tile columns, 2 = 4 tile columns. // The number tile columns will be capped by the encoder based on image size // (minimum width of tile column is 256 pixels, maximum is 4096). vpx_codec_control(encoder_, VP9E_SET_TILE_COLUMNS, (config_->g_threads >> 1)); -#if !defined(WEBRTC_ARCH_ARM) +#if !defined(WEBRTC_ARCH_ARM) && !defined(WEBRTC_ARCH_ARM64) // Note denoiser is still off by default until further testing/optimization, // i.e., codecSpecific.VP9.denoisingOn == 0. vpx_codec_control(encoder_, VP9E_SET_NOISE_SENSITIVITY, inst->codecSpecific.VP9.denoisingOn ? 1 : 0); #endif + if (codec_.mode == kScreensharing) { + // Adjust internal parameters to screen content. + vpx_codec_control(encoder_, VP9E_SET_TUNE_CONTENT, 1); + } + // Enable encoder skip of static/low content blocks. + vpx_codec_control(encoder_, VP8E_SET_STATIC_THRESHOLD, 1); inited_ = true; return WEBRTC_VIDEO_CODEC_OK; } @@ -268,6 +500,13 @@ int VP9EncoderImpl::Encode(const I420VideoFrame& input_image, } DCHECK_EQ(input_image.width(), static_cast(raw_->d_w)); DCHECK_EQ(input_image.height(), static_cast(raw_->d_h)); + + // Set input image for use in the callback. + // This was necessary since you need some information from input_image. + // You can save only the necessary information (such as timestamp) instead of + // doing this. + input_image_ = &input_image; + // Image in vpx_image_t format. // Input image is const. VPX's raw image is not defined as const. raw_->planes[VPX_PLANE_Y] = const_cast(input_image.buffer(kYPlane)); @@ -277,12 +516,37 @@ int VP9EncoderImpl::Encode(const I420VideoFrame& input_image, raw_->stride[VPX_PLANE_U] = input_image.stride(kUPlane); raw_->stride[VPX_PLANE_V] = input_image.stride(kVPlane); - int flags = 0; + vpx_enc_frame_flags_t flags = 0; bool send_keyframe = (frame_type == kKeyFrame); if (send_keyframe) { // Key frame request from caller. flags = VPX_EFLAG_FORCE_KF; } + +#ifdef LIBVPX_SVC + if (is_flexible_mode_) { + SuperFrameRefSettings settings; + + // These structs are copied when calling vpx_codec_control, + // therefore it is ok for them to go out of scope. + vpx_svc_ref_frame_config enc_layer_conf; + vpx_svc_layer_id layer_id; + + if (codec_.mode == kRealtimeVideo) { + // Real time video not yet implemented in flexible mode. + RTC_NOTREACHED(); + } else { + settings = spatial_layer_->GetSuperFrameSettings(input_image.timestamp(), + send_keyframe); + } + enc_layer_conf = GenerateRefsAndFlags(settings); + layer_id.temporal_layer_id = 0; + layer_id.spatial_layer_id = settings.start_layer; + vpx_codec_control(encoder_, VP9E_SET_SVC_LAYER_ID, &layer_id); + vpx_codec_control(encoder_, VP9E_SET_SVC_REF_FRAME_CONFIG, &enc_layer_conf); + } +#endif + assert(codec_.maxFramerate > 0); uint32_t duration = 90000 / codec_.maxFramerate; if (vpx_codec_encode(encoder_, raw_, timestamp_, duration, flags, @@ -290,7 +554,8 @@ int VP9EncoderImpl::Encode(const I420VideoFrame& input_image, return WEBRTC_VIDEO_CODEC_ERROR; } timestamp_ += duration; - return GetEncodedPartitions(input_image); + + return WEBRTC_VIDEO_CODEC_OK; } void VP9EncoderImpl::PopulateCodecSpecific(CodecSpecificInfo* codec_specific, @@ -299,20 +564,104 @@ void VP9EncoderImpl::PopulateCodecSpecific(CodecSpecificInfo* codec_specific, assert(codec_specific != NULL); codec_specific->codecType = kVideoCodecVP9; CodecSpecificInfoVP9 *vp9_info = &(codec_specific->codecSpecific.VP9); - vp9_info->pictureId = picture_id_; - vp9_info->keyIdx = kNoKeyIdx; - vp9_info->nonReference = (pkt.data.frame.flags & VPX_FRAME_IS_DROPPABLE) != 0; - // TODO(marpan): Temporal layers are supported in the current VP9 version, - // but for now use 1 temporal layer encoding. Will update this when temporal - // layer support for VP9 is added in webrtc. - vp9_info->temporalIdx = kNoTemporalIdx; - vp9_info->layerSync = false; - vp9_info->tl0PicIdx = kNoTl0PicIdx; - picture_id_ = (picture_id_ + 1) & 0x7FFF; + // TODO(asapersson): Set correct values. + vp9_info->inter_pic_predicted = + (pkt.data.frame.flags & VPX_FRAME_IS_KEY) ? false : true; + vp9_info->flexible_mode = codec_.codecSpecific.VP9.flexibleMode; + vp9_info->ss_data_available = ((pkt.data.frame.flags & VPX_FRAME_IS_KEY) && + !codec_.codecSpecific.VP9.flexibleMode) + ? true + : false; + + vpx_svc_layer_id_t layer_id = {0}; + vpx_codec_control(encoder_, VP9E_GET_SVC_LAYER_ID, &layer_id); + + assert(num_temporal_layers_ > 0); + assert(num_spatial_layers_ > 0); + if (num_temporal_layers_ == 1) { + assert(layer_id.temporal_layer_id == 0); + vp9_info->temporal_idx = kNoTemporalIdx; + } else { + vp9_info->temporal_idx = layer_id.temporal_layer_id; + } + if (num_spatial_layers_ == 1) { + assert(layer_id.spatial_layer_id == 0); + vp9_info->spatial_idx = kNoSpatialIdx; + } else { + vp9_info->spatial_idx = layer_id.spatial_layer_id; + } + if (layer_id.spatial_layer_id != 0) { + vp9_info->ss_data_available = false; + } + + // TODO(asapersson): this info has to be obtained from the encoder. + vp9_info->temporal_up_switch = true; + + bool is_first_frame = false; + if (is_flexible_mode_) { + is_first_frame = + layer_id.spatial_layer_id == spatial_layer_->GetStartLayer(); + } else { + is_first_frame = layer_id.spatial_layer_id == 0; + } + + if (is_first_frame) { + picture_id_ = (picture_id_ + 1) & 0x7FFF; + // TODO(asapersson): this info has to be obtained from the encoder. + vp9_info->inter_layer_predicted = false; + ++frames_since_kf_; + } else { + // TODO(asapersson): this info has to be obtained from the encoder. + vp9_info->inter_layer_predicted = true; + } + + if (pkt.data.frame.flags & VPX_FRAME_IS_KEY) { + frames_since_kf_ = 0; + } + + vp9_info->picture_id = picture_id_; + + if (!vp9_info->flexible_mode) { + if (layer_id.temporal_layer_id == 0 && layer_id.spatial_layer_id == 0) { + tl0_pic_idx_++; + } + vp9_info->tl0_pic_idx = tl0_pic_idx_; + } + + // Always populate this, so that the packetizer can properly set the marker + // bit. + vp9_info->num_spatial_layers = num_spatial_layers_; + + vp9_info->num_ref_pics = 0; + if (vp9_info->flexible_mode) { + vp9_info->gof_idx = kNoGofIdx; + vp9_info->num_ref_pics = num_ref_pics_[layer_id.spatial_layer_id]; + for (int i = 0; i < num_ref_pics_[layer_id.spatial_layer_id]; ++i) { + vp9_info->p_diff[i] = p_diff_[layer_id.spatial_layer_id][i]; + } + } else { + vp9_info->gof_idx = + static_cast(frames_since_kf_ % gof_.num_frames_in_gof); + vp9_info->temporal_up_switch = gof_.temporal_up_switch[vp9_info->gof_idx]; + } + + if (vp9_info->ss_data_available) { + vp9_info->spatial_layer_resolution_present = true; + for (size_t i = 0; i < vp9_info->num_spatial_layers; ++i) { + vp9_info->width[i] = codec_.width * + svc_internal_.svc_params.scaling_factor_num[i] / + svc_internal_.svc_params.scaling_factor_den[i]; + vp9_info->height[i] = codec_.height * + svc_internal_.svc_params.scaling_factor_num[i] / + svc_internal_.svc_params.scaling_factor_den[i]; + } + if (!vp9_info->flexible_mode) { + vp9_info->gof.CopyGofInfoVP9(gof_); + } + } } -int VP9EncoderImpl::GetEncodedPartitions(const I420VideoFrame& input_image) { - vpx_codec_iter_t iter = NULL; +int VP9EncoderImpl::GetEncodedLayerFrame(const vpx_codec_cx_pkt* pkt) { encoded_image_._length = 0; encoded_image_._frameType = kDeltaFrame; RTPFragmentationHeader frag_info; @@ -321,48 +670,149 @@ int VP9EncoderImpl::GetEncodedPartitions(const I420VideoFrame& input_image) { frag_info.VerifyAndAllocateFragmentationHeader(1); int part_idx = 0; CodecSpecificInfo codec_specific; - const vpx_codec_cx_pkt_t *pkt = NULL; - while ((pkt = vpx_codec_get_cx_data(encoder_, &iter)) != NULL) { - switch (pkt->kind) { - case VPX_CODEC_CX_FRAME_PKT: { - memcpy(&encoded_image_._buffer[encoded_image_._length], - pkt->data.frame.buf, - pkt->data.frame.sz); - frag_info.fragmentationOffset[part_idx] = encoded_image_._length; - frag_info.fragmentationLength[part_idx] = - static_cast(pkt->data.frame.sz); - frag_info.fragmentationPlType[part_idx] = 0; - frag_info.fragmentationTimeDiff[part_idx] = 0; - encoded_image_._length += static_cast(pkt->data.frame.sz); - assert(encoded_image_._length <= encoded_image_._size); - break; - } - default: { - break; - } - } - // End of frame. - if ((pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) == 0) { - // Check if encoded frame is a key frame. - if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { - encoded_image_._frameType = kKeyFrame; - } - PopulateCodecSpecific(&codec_specific, *pkt, input_image.timestamp()); - break; - } + + assert(pkt->kind == VPX_CODEC_CX_FRAME_PKT); + memcpy(&encoded_image_._buffer[encoded_image_._length], pkt->data.frame.buf, + pkt->data.frame.sz); + frag_info.fragmentationOffset[part_idx] = encoded_image_._length; + frag_info.fragmentationLength[part_idx] = + static_cast(pkt->data.frame.sz); + frag_info.fragmentationPlType[part_idx] = 0; + frag_info.fragmentationTimeDiff[part_idx] = 0; + encoded_image_._length += static_cast(pkt->data.frame.sz); + + vpx_svc_layer_id_t layer_id = {0}; + vpx_codec_control(encoder_, VP9E_GET_SVC_LAYER_ID, &layer_id); + if (is_flexible_mode_ && codec_.mode == kScreensharing) + spatial_layer_->LayerFrameEncoded( + static_cast(encoded_image_._length), + layer_id.spatial_layer_id); + + assert(encoded_image_._length <= encoded_image_._size); + + // End of frame. + // Check if encoded frame is a key frame. + if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { + encoded_image_._frameType = kKeyFrame; } + PopulateCodecSpecific(&codec_specific, *pkt, input_image_->timestamp()); + if (encoded_image_._length > 0) { TRACE_COUNTER1("webrtc", "EncodedFrameSize", encoded_image_._length); - encoded_image_._timeStamp = input_image.timestamp(); - encoded_image_.capture_time_ms_ = input_image.render_time_ms(); + encoded_image_._timeStamp = input_image_->timestamp(); + encoded_image_.capture_time_ms_ = input_image_->render_time_ms(); encoded_image_._encodedHeight = raw_->d_h; encoded_image_._encodedWidth = raw_->d_w; encoded_complete_callback_->Encoded(encoded_image_, &codec_specific, - &frag_info); + &frag_info); } return WEBRTC_VIDEO_CODEC_OK; } +#ifdef LIBVPX_SVC +vpx_svc_ref_frame_config VP9EncoderImpl::GenerateRefsAndFlags( + const SuperFrameRefSettings& settings) { + static const vpx_enc_frame_flags_t kAllFlags = + VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_LAST | + VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_GF; + vpx_svc_ref_frame_config sf_conf = {}; + if (settings.is_keyframe) { + // Used later on to make sure we don't make any invalid references. + memset(buffer_updated_at_frame_, -1, sizeof(buffer_updated_at_frame_)); + for (int layer = settings.start_layer; layer <= settings.stop_layer; + ++layer) { + num_ref_pics_[layer] = 0; + buffer_updated_at_frame_[settings.layer[layer].upd_buf] = frames_encoded_; + // When encoding a keyframe only the alt_fb_idx is used + // to specify which layer ends up in which buffer. + sf_conf.alt_fb_idx[layer] = settings.layer[layer].upd_buf; + } + } else { + for (int layer_idx = settings.start_layer; layer_idx <= settings.stop_layer; + ++layer_idx) { + vpx_enc_frame_flags_t layer_flags = kAllFlags; + num_ref_pics_[layer_idx] = 0; + int8_t refs[3] = {settings.layer[layer_idx].ref_buf1, + settings.layer[layer_idx].ref_buf2, + settings.layer[layer_idx].ref_buf3}; + + for (unsigned int ref_idx = 0; ref_idx < kMaxVp9RefPics; ++ref_idx) { + if (refs[ref_idx] == -1) + continue; + + DCHECK_GE(refs[ref_idx], 0); + DCHECK_LE(refs[ref_idx], 7); + // Easier to remove flags from all flags rather than having to + // build the flags from 0. + switch (num_ref_pics_[layer_idx]) { + case 0: { + sf_conf.lst_fb_idx[layer_idx] = refs[ref_idx]; + layer_flags &= ~VP8_EFLAG_NO_REF_LAST; + break; + } + case 1: { + sf_conf.gld_fb_idx[layer_idx] = refs[ref_idx]; + layer_flags &= ~VP8_EFLAG_NO_REF_GF; + break; + } + case 2: { + sf_conf.alt_fb_idx[layer_idx] = refs[ref_idx]; + layer_flags &= ~VP8_EFLAG_NO_REF_ARF; + break; + } + } + // Make sure we don't reference a buffer that hasn't been + // used at all or hasn't been used since a keyframe. + DCHECK_NE(buffer_updated_at_frame_[refs[ref_idx]], -1); + + p_diff_[layer_idx][num_ref_pics_[layer_idx]] = + frames_encoded_ - buffer_updated_at_frame_[refs[ref_idx]]; + num_ref_pics_[layer_idx]++; + } + + bool upd_buf_same_as_a_ref = false; + if (settings.layer[layer_idx].upd_buf != -1) { + for (unsigned int ref_idx = 0; ref_idx < kMaxVp9RefPics; ++ref_idx) { + if (settings.layer[layer_idx].upd_buf == refs[ref_idx]) { + switch (ref_idx) { + case 0: { + layer_flags &= ~VP8_EFLAG_NO_UPD_LAST; + break; + } + case 1: { + layer_flags &= ~VP8_EFLAG_NO_UPD_GF; + break; + } + case 2: { + layer_flags &= ~VP8_EFLAG_NO_UPD_ARF; + break; + } + } + upd_buf_same_as_a_ref = true; + break; + } + } + if (!upd_buf_same_as_a_ref) { + // If we have three references and a buffer is specified to be + // updated, then that buffer must be the same as one of the + // three references. + RTC_CHECK_LT(num_ref_pics_[layer_idx], kMaxVp9RefPics); + + sf_conf.alt_fb_idx[layer_idx] = settings.layer[layer_idx].upd_buf; + layer_flags ^= VP8_EFLAG_NO_UPD_ARF; + } + + int updated_buffer = settings.layer[layer_idx].upd_buf; + buffer_updated_at_frame_[updated_buffer] = frames_encoded_; + sf_conf.frame_flags[layer_idx] = layer_flags; + } + } + } + ++frames_encoded_; + return sf_conf; +} +#endif + int VP9EncoderImpl::SetChannelParameters(uint32_t packet_loss, int64_t rtt) { return WEBRTC_VIDEO_CODEC_OK; } @@ -388,6 +838,14 @@ VP9DecoderImpl::VP9DecoderImpl() VP9DecoderImpl::~VP9DecoderImpl() { inited_ = true; // in order to do the actual release Release(); + int num_buffers_in_use = frame_buffer_pool_.GetNumBuffersInUse(); + if (num_buffers_in_use > 0) { + // The frame buffers are reference counted and frames are exposed after + // decoding. There may be valid usage cases where previous frames are still + // referenced after ~VP9DecoderImpl that is not a leak. + LOG(LS_INFO) << num_buffers_in_use << " Vp9FrameBuffers are still " + << "referenced during ~VP9DecoderImpl."; + } } int VP9DecoderImpl::Reset() { @@ -421,6 +879,11 @@ int VP9DecoderImpl::InitDecode(const VideoCodec* inst, int number_of_cores) { // Save VideoCodec instance for later; mainly for duplicating the decoder. codec_ = *inst; } + + if (!frame_buffer_pool_.InitializeVpxUsePool(decoder_)) { + return WEBRTC_VIDEO_CODEC_MEMORY; + } + inited_ = true; // Always start with a complete key frame. key_frame_required_ = true; @@ -455,6 +918,8 @@ int VP9DecoderImpl::Decode(const EncodedImage& input_image, if (input_image._length == 0) { buffer = NULL; // Triggers full frame concealment. } + // During decode libvpx may get and release buffers from |frame_buffer_pool_|. + // In practice libvpx keeps a few (~3-4) buffers alive at a time. if (vpx_codec_decode(decoder_, buffer, static_cast(input_image._length), @@ -462,6 +927,9 @@ int VP9DecoderImpl::Decode(const EncodedImage& input_image, VPX_DL_REALTIME)) { return WEBRTC_VIDEO_CODEC_ERROR; } + // |img->fb_priv| contains the image data, a reference counted Vp9FrameBuffer. + // It may be released by libvpx during future vpx_codec_decode or + // vpx_codec_destroy calls. img = vpx_codec_get_frame(decoder_, &iter); int ret = ReturnFrame(img, input_image._timeStamp); if (ret != 0) { @@ -475,6 +943,32 @@ int VP9DecoderImpl::ReturnFrame(const vpx_image_t* img, uint32_t timestamp) { // Decoder OK and NULL image => No show frame. return WEBRTC_VIDEO_CODEC_NO_OUTPUT; } + +#ifdef USE_WRAPPED_I420_BUFFER + // This buffer contains all of |img|'s image data, a reference counted + // Vp9FrameBuffer. Performing AddRef/Release ensures it is not released and + // recycled during use (libvpx is done with the buffers after a few + // vpx_codec_decode calls or vpx_codec_destroy). + Vp9FrameBufferPool::Vp9FrameBuffer* img_buffer = + static_cast(img->fb_priv); + img_buffer->AddRef(); + // The buffer can be used directly by the VideoFrame (without copy) by + // using a WrappedI420Buffer. + rtc::scoped_refptr img_wrapped_buffer( + new rtc::RefCountedObject( + img->d_w, img->d_h, + img->d_w, img->d_h, + img->planes[VPX_PLANE_Y], img->stride[VPX_PLANE_Y], + img->planes[VPX_PLANE_U], img->stride[VPX_PLANE_U], + img->planes[VPX_PLANE_V], img->stride[VPX_PLANE_V], + // WrappedI420Buffer's mechanism for allowing the release of its frame + // buffer is through a callback function. This is where we should + // release |img_buffer|. + rtc::Bind(&WrappedI420BufferNoLongerUsedCb, img_buffer))); + + I420VideoFrame decoded_image_; + decoded_image_.set_video_frame_buffer(img_wrapped_buffer); +#else decoded_image_.CreateFrame(img->planes[VPX_PLANE_Y], img->planes[VPX_PLANE_U], img->planes[VPX_PLANE_V], @@ -482,7 +976,9 @@ int VP9DecoderImpl::ReturnFrame(const vpx_image_t* img, uint32_t timestamp) { img->stride[VPX_PLANE_Y], img->stride[VPX_PLANE_U], img->stride[VPX_PLANE_V]); +#endif decoded_image_.set_timestamp(timestamp); + int ret = decode_complete_callback_->Decoded(decoded_image_); if (ret != 0) return ret; @@ -497,12 +993,18 @@ int VP9DecoderImpl::RegisterDecodeCompleteCallback( int VP9DecoderImpl::Release() { if (decoder_ != NULL) { + // When a codec is destroyed libvpx will release any buffers of + // |frame_buffer_pool_| it is currently using. if (vpx_codec_destroy(decoder_)) { return WEBRTC_VIDEO_CODEC_MEMORY; } delete decoder_; decoder_ = NULL; } + // Releases buffers from the pool. Any buffers not in use are deleted. Buffers + // still referenced externally are deleted once fully released, not returning + // to the pool. + frame_buffer_pool_.ClearPool(); inited_ = false; return WEBRTC_VIDEO_CODEC_OK; } diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h index 019e73ea13..49e756ad43 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h +++ b/media/webrtc/trunk/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h @@ -13,12 +13,16 @@ #define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_IMPL_H_ #include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h" +#include "webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h" +#include "vpx/svc_context.h" #include "vpx/vpx_decoder.h" #include "vpx/vpx_encoder.h" namespace webrtc { +class ScreenshareLayersVP9; + class VP9EncoderImpl : public VP9Encoder { public: VP9EncoderImpl(); @@ -41,6 +45,20 @@ class VP9EncoderImpl : public VP9Encoder { int SetRates(uint32_t new_bitrate_kbit, uint32_t frame_rate) override; + struct LayerFrameRefSettings { + int8_t upd_buf = -1; // -1 - no update, 0..7 - update buffer 0..7 + int8_t ref_buf1 = -1; // -1 - no reference, 0..7 - reference buffer 0..7 + int8_t ref_buf2 = -1; // -1 - no reference, 0..7 - reference buffer 0..7 + int8_t ref_buf3 = -1; // -1 - no reference, 0..7 - reference buffer 0..7 + }; + + struct SuperFrameRefSettings { + LayerFrameRefSettings layer[kMaxVp9NumberOfSpatialLayers]; + uint8_t start_layer = 0; // The first spatial layer to be encoded. + uint8_t stop_layer = 0; // The last spatial layer to be encoded. + bool is_keyframe = false; + }; + private: // Determine number of encoder threads to use. int NumberOfThreads(int width, int height, int number_of_cores); @@ -52,7 +70,25 @@ class VP9EncoderImpl : public VP9Encoder { const vpx_codec_cx_pkt& pkt, uint32_t timestamp); - int GetEncodedPartitions(const I420VideoFrame& input_image); + bool ExplicitlyConfiguredSpatialLayers() const; + bool SetSvcRates(); + +#ifdef LIBVPX_SVC + // Used for flexible mode to set the flags and buffer references used + // by the encoder. Also calculates the references used by the RTP + // packetizer. + // + // Has to be called for every frame (keyframes included) to update the + // state used to calculate references. + vpx_svc_ref_frame_config GenerateRefsAndFlags( + const SuperFrameRefSettings& settings); +#endif + + virtual int GetEncodedLayerFrame(const vpx_codec_cx_pkt* pkt); + + // Callback function for outputting packets per spatial layer. + static void EncoderOutputCodedPacketCallback(vpx_codec_cx_pkt* pkt, + void* user_data); // Determine maximum target for Intra frames // @@ -73,6 +109,22 @@ class VP9EncoderImpl : public VP9Encoder { vpx_codec_ctx_t* encoder_; vpx_codec_enc_cfg_t* config_; vpx_image_t* raw_; + SvcInternal_t svc_internal_; + const I420VideoFrame* input_image_; + GofInfoVP9 gof_; // Contains each frame's temporal information for + // non-flexible mode. + uint8_t tl0_pic_idx_; // Only used in non-flexible mode. + size_t frames_since_kf_; + uint8_t num_temporal_layers_; + uint8_t num_spatial_layers_; + + // Used for flexible mode. + bool is_flexible_mode_; + int64_t buffer_updated_at_frame_[kNumVp9Buffers]; + int64_t frames_encoded_; + uint8_t num_ref_pics_[kMaxVp9NumberOfSpatialLayers]; + uint8_t p_diff_[kMaxVp9NumberOfSpatialLayers][kMaxVp9RefPics]; + rtc::scoped_ptr spatial_layer_; }; @@ -99,7 +151,13 @@ class VP9DecoderImpl : public VP9Decoder { private: int ReturnFrame(const vpx_image_t* img, uint32_t timeStamp); +#ifndef USE_WRAPPED_I420_BUFFER + // Temporarily keep VideoFrame in a separate buffer + // Once we debug WrappedI420VideoFrame usage, we can get rid of this I420VideoFrame decoded_image_; +#endif + // Memory pool used to share buffers between libvpx and webrtc. + Vp9FrameBufferPool frame_buffer_pool_; DecodedImageCallback* decode_complete_callback_; bool inited_; vpx_codec_ctx_t* decoder_; diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/codec_database.cc b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/codec_database.cc index e3608c0a3e..2c268185a2 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/codec_database.cc +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/codec_database.cc @@ -57,7 +57,9 @@ VideoCodecVP9 VideoEncoder::GetDefaultVp9Settings() { vp9_settings.frameDroppingOn = true; vp9_settings.keyFrameInterval = 3000; vp9_settings.adaptiveQpMode = true; - + vp9_settings.automaticResizeOn = true; + vp9_settings.numberOfSpatialLayers = 1; + vp9_settings.flexibleMode = false; return vp9_settings; } diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/encoded_frame.cc b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/encoded_frame.cc index 8f3cd03294..8b2a39244b 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/encoded_frame.cc +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/encoded_frame.cc @@ -141,27 +141,67 @@ void VCMEncodedFrame::CopyCodecSpecific(const RTPVideoHeader* header) case kRtpVideoVp9: { if (_codecSpecificInfo.codecType != kVideoCodecVP9) { // This is the first packet for this frame. - _codecSpecificInfo.codecSpecific.VP9.pictureId = -1; - _codecSpecificInfo.codecSpecific.VP9.temporalIdx = 0; - _codecSpecificInfo.codecSpecific.VP9.layerSync = false; - _codecSpecificInfo.codecSpecific.VP9.keyIdx = -1; + _codecSpecificInfo.codecSpecific.VP9.picture_id = -1; + _codecSpecificInfo.codecSpecific.VP9.temporal_idx = 0; + _codecSpecificInfo.codecSpecific.VP9.spatial_idx = 0; + _codecSpecificInfo.codecSpecific.VP9.gof_idx = 0; + _codecSpecificInfo.codecSpecific.VP9.inter_layer_predicted = false; + _codecSpecificInfo.codecSpecific.VP9.tl0_pic_idx = -1; _codecSpecificInfo.codecType = kVideoCodecVP9; } - _codecSpecificInfo.codecSpecific.VP9.nonReference = - header->codecHeader.VP9.nonReference; - if (header->codecHeader.VP9.pictureId != kNoPictureId) { - _codecSpecificInfo.codecSpecific.VP9.pictureId = - header->codecHeader.VP9.pictureId; + _codecSpecificInfo.codecSpecific.VP9.inter_pic_predicted = + header->codecHeader.VP9.inter_pic_predicted; + _codecSpecificInfo.codecSpecific.VP9.flexible_mode = + header->codecHeader.VP9.flexible_mode; + _codecSpecificInfo.codecSpecific.VP9.num_ref_pics = + header->codecHeader.VP9.num_ref_pics; + for (uint8_t r = 0; r < header->codecHeader.VP9.num_ref_pics; ++r) { + _codecSpecificInfo.codecSpecific.VP9.p_diff[r] = + header->codecHeader.VP9.pid_diff[r]; } - if (header->codecHeader.VP9.temporalIdx != kNoTemporalIdx) { - _codecSpecificInfo.codecSpecific.VP9.temporalIdx = - header->codecHeader.VP9.temporalIdx; - _codecSpecificInfo.codecSpecific.VP9.layerSync = - header->codecHeader.VP9.layerSync; + _codecSpecificInfo.codecSpecific.VP9.ss_data_available = + header->codecHeader.VP9.ss_data_available; + if (header->codecHeader.VP9.picture_id != kNoPictureId) { + _codecSpecificInfo.codecSpecific.VP9.picture_id = + header->codecHeader.VP9.picture_id; } - if (header->codecHeader.VP9.keyIdx != kNoKeyIdx) { - _codecSpecificInfo.codecSpecific.VP9.keyIdx = - header->codecHeader.VP9.keyIdx; + if (header->codecHeader.VP9.tl0_pic_idx != kNoTl0PicIdx) { + _codecSpecificInfo.codecSpecific.VP9.tl0_pic_idx = + header->codecHeader.VP9.tl0_pic_idx; + } + if (header->codecHeader.VP9.temporal_idx != kNoTemporalIdx) { + _codecSpecificInfo.codecSpecific.VP9.temporal_idx = + header->codecHeader.VP9.temporal_idx; + _codecSpecificInfo.codecSpecific.VP9.temporal_up_switch = + header->codecHeader.VP9.temporal_up_switch; + } + if (header->codecHeader.VP9.spatial_idx != kNoSpatialIdx) { + _codecSpecificInfo.codecSpecific.VP9.spatial_idx = + header->codecHeader.VP9.spatial_idx; + _codecSpecificInfo.codecSpecific.VP9.inter_layer_predicted = + header->codecHeader.VP9.inter_layer_predicted; + } + if (header->codecHeader.VP9.gof_idx != kNoGofIdx) { + _codecSpecificInfo.codecSpecific.VP9.gof_idx = + header->codecHeader.VP9.gof_idx; + } + if (header->codecHeader.VP9.ss_data_available) { + _codecSpecificInfo.codecSpecific.VP9.num_spatial_layers = + header->codecHeader.VP9.num_spatial_layers; + _codecSpecificInfo.codecSpecific.VP9 + .spatial_layer_resolution_present = + header->codecHeader.VP9.spatial_layer_resolution_present; + if (header->codecHeader.VP9.spatial_layer_resolution_present) { + for (size_t i = 0; i < header->codecHeader.VP9.num_spatial_layers; + ++i) { + _codecSpecificInfo.codecSpecific.VP9.width[i] = + header->codecHeader.VP9.width[i]; + _codecSpecificInfo.codecSpecific.VP9.height[i] = + header->codecHeader.VP9.height[i]; + } + } + _codecSpecificInfo.codecSpecific.VP9.gof.CopyGofInfoVP9( + header->codecHeader.VP9.gof); } break; } diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.cc b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.cc index 8bd375893d..edb1995e23 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.cc +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.cc @@ -75,6 +75,15 @@ bool VCMFrameBuffer::NonReference() const { return _sessionInfo.NonReference(); } +void VCMFrameBuffer::SetGofInfo(const GofInfoVP9& gof_info, size_t idx) { + _sessionInfo.SetGofInfo(gof_info, idx); + // TODO(asapersson): Consider adding hdr->VP9.ref_picture_id for testing. + _codecSpecificInfo.codecSpecific.VP9.temporal_idx = + gof_info.temporal_idx[idx]; + _codecSpecificInfo.codecSpecific.VP9.temporal_up_switch = + gof_info.temporal_up_switch[idx]; +} + bool VCMFrameBuffer::IsSessionComplete() const { return _sessionInfo.complete(); diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.h b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.h index d98b02463f..3af85f31a1 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.h +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/frame_buffer.h @@ -61,6 +61,8 @@ class VCMFrameBuffer : public VCMEncodedFrame { int Tl0PicId() const; bool NonReference() const; + void SetGofInfo(const GofInfoVP9& gof_info, size_t idx); + // Increments a counter to keep track of the number of packets of this frame // which were NACKed before they arrived. void IncrementNackCount(); diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/generic_encoder.cc b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/generic_encoder.cc index f5040f9ee0..e8f05c7fc2 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/generic_encoder.cc +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/generic_encoder.cc @@ -36,21 +36,49 @@ void CopyCodecSpecific(const CodecSpecificInfo* info, RTPVideoHeader* rtp) { rtp->simulcastIdx = info->codecSpecific.VP8.simulcastIdx; return; } + case kVideoCodecVP9: { + rtp->codec = kRtpVideoVp9; + rtp->codecHeader.VP9.InitRTPVideoHeaderVP9(); + rtp->codecHeader.VP9.inter_pic_predicted = + info->codecSpecific.VP9.inter_pic_predicted; + rtp->codecHeader.VP9.flexible_mode = + info->codecSpecific.VP9.flexible_mode; + rtp->codecHeader.VP9.ss_data_available = + info->codecSpecific.VP9.ss_data_available; + rtp->codecHeader.VP9.picture_id = info->codecSpecific.VP9.picture_id; + rtp->codecHeader.VP9.tl0_pic_idx = info->codecSpecific.VP9.tl0_pic_idx; + rtp->codecHeader.VP9.temporal_idx = info->codecSpecific.VP9.temporal_idx; + rtp->codecHeader.VP9.spatial_idx = info->codecSpecific.VP9.spatial_idx; + rtp->codecHeader.VP9.temporal_up_switch = + info->codecSpecific.VP9.temporal_up_switch; + rtp->codecHeader.VP9.inter_layer_predicted = + info->codecSpecific.VP9.inter_layer_predicted; + rtp->codecHeader.VP9.gof_idx = info->codecSpecific.VP9.gof_idx; + rtp->codecHeader.VP9.num_spatial_layers = + info->codecSpecific.VP9.num_spatial_layers; + + if (info->codecSpecific.VP9.ss_data_available) { + rtp->codecHeader.VP9.spatial_layer_resolution_present = + info->codecSpecific.VP9.spatial_layer_resolution_present; + if (info->codecSpecific.VP9.spatial_layer_resolution_present) { + for (size_t i = 0; i < info->codecSpecific.VP9.num_spatial_layers; + ++i) { + rtp->codecHeader.VP9.width[i] = info->codecSpecific.VP9.width[i]; + rtp->codecHeader.VP9.height[i] = info->codecSpecific.VP9.height[i]; + } + } + rtp->codecHeader.VP9.gof.CopyGofInfoVP9(info->codecSpecific.VP9.gof); + } + + rtp->codecHeader.VP9.num_ref_pics = info->codecSpecific.VP9.num_ref_pics; + for (int i = 0; i < info->codecSpecific.VP9.num_ref_pics; ++i) + rtp->codecHeader.VP9.pid_diff[i] = info->codecSpecific.VP9.p_diff[i]; + return; + } case kVideoCodecH264: rtp->codec = kRtpVideoH264; rtp->simulcastIdx = info->codecSpecific.H264.simulcastIdx; return; - case kVideoCodecVP9: - rtp->codec = kRtpVideoVp9; - rtp->codecHeader.VP9.InitRTPVideoHeaderVP9(); - rtp->codecHeader.VP9.pictureId = info->codecSpecific.VP9.pictureId; - rtp->codecHeader.VP9.nonReference = - info->codecSpecific.VP9.nonReference; - rtp->codecHeader.VP9.temporalIdx = info->codecSpecific.VP9.temporalIdx; - rtp->codecHeader.VP9.layerSync = info->codecSpecific.VP9.layerSync; - rtp->codecHeader.VP9.tl0PicIdx = info->codecSpecific.VP9.tl0PicIdx; - rtp->codecHeader.VP9.keyIdx = info->codecSpecific.VP9.keyIdx; - return; case kVideoCodecGeneric: rtp->codec = kRtpVideoGeneric; rtp->simulcastIdx = info->codecSpecific.generic.simulcast_idx; diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.cc b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.cc index a643b64eab..3961334e0a 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.cc +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.cc @@ -14,6 +14,9 @@ #include #include +#include "webrtc/base/checks.h" +#include "webrtc/base/trace_event.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" #include "webrtc/modules/video_coding/main/interface/video_coding.h" #include "webrtc/modules/video_coding/main/source/frame_buffer.h" #include "webrtc/modules/video_coding/main/source/inter_frame_delay.h" @@ -26,10 +29,12 @@ #include "webrtc/system_wrappers/interface/event_wrapper.h" #include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/metrics.h" -#include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { +// Interval for updating SS data. +static const uint32_t kSsCleanupIntervalSec = 60; + // Use this rtt if no value has been reported. static const int64_t kDefaultRtt = 200; @@ -146,6 +151,98 @@ void FrameList::Reset(UnorderedFrameList* free_frames) { } } +bool Vp9SsMap::Insert(const VCMPacket& packet) { + if (!packet.codecSpecificHeader.codecHeader.VP9.ss_data_available) + return false; + + ss_map_[packet.timestamp] = packet.codecSpecificHeader.codecHeader.VP9.gof; + return true; +} + +void Vp9SsMap::Reset() { + ss_map_.clear(); +} + +bool Vp9SsMap::Find(uint32_t timestamp, SsMap::iterator* it_out) { + bool found = false; + for (SsMap::iterator it = ss_map_.begin(); it != ss_map_.end(); ++it) { + if (it->first == timestamp || IsNewerTimestamp(timestamp, it->first)) { + *it_out = it; + found = true; + } + } + return found; +} + +void Vp9SsMap::RemoveOld(uint32_t timestamp) { + if (!TimeForCleanup(timestamp)) + return; + + SsMap::iterator it; + if (!Find(timestamp, &it)) + return; + + ss_map_.erase(ss_map_.begin(), it); + AdvanceFront(timestamp); +} + +bool Vp9SsMap::TimeForCleanup(uint32_t timestamp) const { + if (ss_map_.empty() || !IsNewerTimestamp(timestamp, ss_map_.begin()->first)) + return false; + + uint32_t diff = timestamp - ss_map_.begin()->first; + return diff / kVideoPayloadTypeFrequency >= kSsCleanupIntervalSec; +} + +void Vp9SsMap::AdvanceFront(uint32_t timestamp) { + DCHECK(!ss_map_.empty()); + GofInfoVP9 gof = ss_map_.begin()->second; + ss_map_.erase(ss_map_.begin()); + ss_map_[timestamp] = gof; +} + +// TODO(asapersson): Update according to updates in RTP payload profile. +bool Vp9SsMap::UpdatePacket(VCMPacket* packet) { + uint8_t gof_idx = packet->codecSpecificHeader.codecHeader.VP9.gof_idx; + if (gof_idx == kNoGofIdx) + return false; // No update needed. + + SsMap::iterator it; + if (!Find(packet->timestamp, &it)) + return false; // Corresponding SS not yet received. + + if (gof_idx >= it->second.num_frames_in_gof) + return false; // Assume corresponding SS not yet received. + + RTPVideoHeaderVP9* vp9 = &packet->codecSpecificHeader.codecHeader.VP9; + vp9->temporal_idx = it->second.temporal_idx[gof_idx]; + vp9->temporal_up_switch = it->second.temporal_up_switch[gof_idx]; + + // TODO(asapersson): Set vp9.ref_picture_id[i] and add usage. + vp9->num_ref_pics = it->second.num_ref_pics[gof_idx]; + for (uint8_t i = 0; i < it->second.num_ref_pics[gof_idx]; ++i) { + vp9->pid_diff[i] = it->second.pid_diff[gof_idx][i]; + } + return true; +} + +void Vp9SsMap::UpdateFrames(FrameList* frames) { + for (const auto& frame_it : *frames) { + uint8_t gof_idx = + frame_it.second->CodecSpecific()->codecSpecific.VP9.gof_idx; + if (gof_idx == kNoGofIdx) { + continue; + } + SsMap::iterator ss_it; + if (Find(frame_it.second->TimeStamp(), &ss_it)) { + if (gof_idx >= ss_it->second.num_frames_in_gof) { + continue; // Assume corresponding SS not yet received. + } + frame_it.second->SetGofInfo(ss_it->second, gof_idx); + } + } +} + VCMJitterBuffer::VCMJitterBuffer(Clock* clock, EventFactory* event_factory) : clock_(clock), running_(false), @@ -204,7 +301,7 @@ VCMJitterBuffer::~VCMJitterBuffer() { } void VCMJitterBuffer::UpdateHistograms() { - if (num_packets_ <= 0) { + if (num_packets_ <= 0 || !running_) { return; } int64_t elapsed_sec = @@ -624,6 +721,9 @@ VCMFrameBufferEnum VCMJitterBuffer::InsertPacket(const VCMPacket& packet, last_decoded_state_.UpdateOldPacket(&packet); DropPacketsFromNackList(last_decoded_state_.sequence_num()); + // Also see if this old packet made more incomplete frames continuous. + FindAndInsertContinuousFramesWithState(last_decoded_state_); + if (num_consecutive_old_packets_ > kMaxConsecutiveOldPackets) { LOG(LS_WARNING) << num_consecutive_old_packets_ @@ -800,6 +900,16 @@ void VCMJitterBuffer::FindAndInsertContinuousFrames( VCMDecodingState decoding_state; decoding_state.CopyFrom(last_decoded_state_); decoding_state.SetState(&new_frame); + FindAndInsertContinuousFramesWithState(decoding_state); +} + +void VCMJitterBuffer::FindAndInsertContinuousFramesWithState( + const VCMDecodingState& original_decoded_state) { + // Copy original_decoded_state so we can move the state forward with each + // decodable frame we find. + VCMDecodingState decoding_state; + decoding_state.CopyFrom(original_decoded_state); + // When temporal layers are available, we search for a complete or decodable // frame until we hit one of the following: // 1. Continuous base or sync layer. @@ -807,7 +917,8 @@ void VCMJitterBuffer::FindAndInsertContinuousFrames( for (FrameList::iterator it = incomplete_frames_.begin(); it != incomplete_frames_.end();) { VCMFrameBuffer* frame = it->second; - if (IsNewerTimestamp(new_frame.TimeStamp(), frame->TimeStamp())) { + if (IsNewerTimestamp(original_decoded_state.time_stamp(), + frame->TimeStamp())) { ++it; continue; } @@ -858,7 +969,7 @@ void VCMJitterBuffer::SetNackMode(VCMNackMode mode, low_rtt_nack_threshold_ms_ = low_rtt_nack_threshold_ms; high_rtt_nack_threshold_ms_ = high_rtt_nack_threshold_ms; // Don't set a high start rtt if high_rtt_nack_threshold_ms_ is used, to not - // disable NACK in hybrid mode. + // disable NACK in |kNack| mode. if (rtt_ms_ == kDefaultRtt && high_rtt_nack_threshold_ms_ != -1) { rtt_ms_ = 0; } diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.h b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.h index 7d7f024cb1..62d5b76900 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.h +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/jitter_buffer.h @@ -75,6 +75,37 @@ class FrameList void Reset(UnorderedFrameList* free_frames); }; +class Vp9SsMap { + public: + typedef std::map SsMap; + bool Insert(const VCMPacket& packet); + void Reset(); + + // Removes SS data that are older than |timestamp|. + // The |timestamp| should be an old timestamp, i.e. packets with older + // timestamps should no longer be inserted. + void RemoveOld(uint32_t timestamp); + + bool UpdatePacket(VCMPacket* packet); + void UpdateFrames(FrameList* frames); + + // Public for testing. + // Returns an iterator to the corresponding SS data for the input |timestamp|. + bool Find(uint32_t timestamp, SsMap::iterator* it); + + private: + // These two functions are called by RemoveOld. + // Checks if it is time to do a clean up (done each kSsCleanupIntervalSec). + bool TimeForCleanup(uint32_t timestamp) const; + + // Advances the oldest SS data to handle timestamp wrap in cases where SS data + // are received very seldom (e.g. only once in beginning, second when + // IsNewerTimestamp is not true). + void AdvanceFront(uint32_t timestamp); + + SsMap ss_map_; +}; + class VCMJitterBuffer { public: VCMJitterBuffer(Clock* clock, @@ -215,6 +246,12 @@ class VCMJitterBuffer { // all decodable frames into account. bool IsContinuous(const VCMFrameBuffer& frame) const EXCLUSIVE_LOCKS_REQUIRED(crit_sect_); + // Looks for frames in |incomplete_frames_| which are continuous in the + // provided |decoded_state|. Starts the search from the timestamp of + // |decoded_state|. + void FindAndInsertContinuousFramesWithState( + const VCMDecodingState& decoded_state) + EXCLUSIVE_LOCKS_REQUIRED(crit_sect_); // Looks for frames in |incomplete_frames_| which are continuous in // |last_decoded_state_| taking all decodable frames into account. Starts // the search from |new_frame|. diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.cc b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.cc index b310af98aa..fef86a45fb 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.cc +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.cc @@ -59,31 +59,52 @@ int VCMSessionInfo::HighSequenceNumber() const { } int VCMSessionInfo::PictureId() const { - if (packets_.empty() || - packets_.front().codecSpecificHeader.codec != kRtpVideoVp8) + if (packets_.empty()) return kNoPictureId; - return packets_.front().codecSpecificHeader.codecHeader.VP8.pictureId; + if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp8) { + return packets_.front().codecSpecificHeader.codecHeader.VP8.pictureId; + } else if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp9) { + return packets_.front().codecSpecificHeader.codecHeader.VP9.picture_id; + } else { + return kNoPictureId; + } } int VCMSessionInfo::TemporalId() const { - if (packets_.empty() || - packets_.front().codecSpecificHeader.codec != kRtpVideoVp8) + if (packets_.empty()) return kNoTemporalIdx; - return packets_.front().codecSpecificHeader.codecHeader.VP8.temporalIdx; + if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp8) { + return packets_.front().codecSpecificHeader.codecHeader.VP8.temporalIdx; + } else if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp9) { + return packets_.front().codecSpecificHeader.codecHeader.VP9.temporal_idx; + } else { + return kNoTemporalIdx; + } } bool VCMSessionInfo::LayerSync() const { - if (packets_.empty() || - packets_.front().codecSpecificHeader.codec != kRtpVideoVp8) + if (packets_.empty()) return false; - return packets_.front().codecSpecificHeader.codecHeader.VP8.layerSync; + if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp8) { + return packets_.front().codecSpecificHeader.codecHeader.VP8.layerSync; + } else if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp9) { + return + packets_.front().codecSpecificHeader.codecHeader.VP9.temporal_up_switch; + } else { + return false; + } } int VCMSessionInfo::Tl0PicId() const { - if (packets_.empty() || - packets_.front().codecSpecificHeader.codec != kRtpVideoVp8) + if (packets_.empty()) return kNoTl0PicIdx; - return packets_.front().codecSpecificHeader.codecHeader.VP8.tl0PicIdx; + if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp8) { + return packets_.front().codecSpecificHeader.codecHeader.VP8.tl0PicIdx; + } else if (packets_.front().codecSpecificHeader.codec == kRtpVideoVp9) { + return packets_.front().codecSpecificHeader.codecHeader.VP9.tl0_pic_idx; + } else { + return kNoTl0PicIdx; + } } bool VCMSessionInfo::NonReference() const { @@ -93,6 +114,24 @@ bool VCMSessionInfo::NonReference() const { return packets_.front().codecSpecificHeader.codecHeader.VP8.nonReference; } +void VCMSessionInfo::SetGofInfo(const GofInfoVP9& gof_info, size_t idx) { + if (packets_.empty() || + packets_.front().codecSpecificHeader.codec != kRtpVideoVp9 || + packets_.front().codecSpecificHeader.codecHeader.VP9.flexible_mode) { + return; + } + packets_.front().codecSpecificHeader.codecHeader.VP9.temporal_idx = + gof_info.temporal_idx[idx]; + packets_.front().codecSpecificHeader.codecHeader.VP9.temporal_up_switch = + gof_info.temporal_up_switch[idx]; + packets_.front().codecSpecificHeader.codecHeader.VP9.num_ref_pics = + gof_info.num_ref_pics[idx]; + for (uint8_t i = 0; i < gof_info.num_ref_pics[idx]; ++i) { + packets_.front().codecSpecificHeader.codecHeader.VP9.pid_diff[i] = + gof_info.pid_diff[idx][i]; + } +} + void VCMSessionInfo::Reset() { session_nack_ = false; complete_ = false; diff --git a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.h b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.h index 21f6c437e3..88071e19d5 100644 --- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.h +++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/session_info.h @@ -88,6 +88,8 @@ class VCMSessionInfo { int Tl0PicId() const; bool NonReference() const; + void SetGofInfo(const GofInfoVP9& gof_info, size_t idx); + // The number of packets discarded because the decoder can't make use of // them. int packets_not_decodable() const; diff --git a/media/webrtc/trunk/webrtc/system_wrappers/interface/static_instance.h b/media/webrtc/trunk/webrtc/system_wrappers/interface/static_instance.h index dad9c52dc9..071edabfa0 100644 --- a/media/webrtc/trunk/webrtc/system_wrappers/interface/static_instance.h +++ b/media/webrtc/trunk/webrtc/system_wrappers/interface/static_instance.h @@ -13,10 +13,10 @@ #include -#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#ifdef _WIN32 -#include "webrtc/system_wrappers/interface/fix_interlocked_exchange_pointer_win.h" +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_GONK) +#define OS_LINUX #endif +#include "base/singleton.h" namespace webrtc { @@ -35,119 +35,9 @@ template // Construct On First Use idiom. Avoids // "static initialization order fiasco". static T* GetStaticInstance(CountOperation count_operation) { - // TODO (hellner): use atomic wrapper instead. - static volatile long instance_count = 0; - static T* volatile instance = NULL; - CreateOperation state = kInstanceExists; -#ifndef _WIN32 - // This memory is staticly allocated once. The application does not try to - // free this memory. This approach is taken to avoid issues with - // destruction order for statically allocated memory. The memory will be - // reclaimed by the OS and memory leak tools will not recognize memory - // reachable from statics leaked so no noise is added by doing this. - static CriticalSectionWrapper* crit_sect( - CriticalSectionWrapper::CreateCriticalSection()); - CriticalSectionScoped lock(crit_sect); - - if (count_operation == - kAddRefNoCreate && instance_count == 0) { - return NULL; - } - if (count_operation == - kAddRef || - count_operation == kAddRefNoCreate) { - instance_count++; - if (instance_count == 1) { - state = kCreate; - } - } else { - instance_count--; - if (instance_count == 0) { - state = kDestroy; - } - } - if (state == kCreate) { - instance = T::CreateInstance(); - } else if (state == kDestroy) { - T* old_instance = instance; - instance = NULL; - // The state will not change past this point. Release the critical - // section while deleting the object in case it would be blocking on - // access back to this object. (This is the case for the tracing class - // since the thread owned by the tracing class also traces). - // TODO(hellner): this is a bit out of place but here goes, de-couple - // thread implementation with trace implementation. - crit_sect->Leave(); - if (old_instance) { - delete old_instance; - } - // Re-acquire the lock since the scoped critical section will release - // it. - crit_sect->Enter(); - return NULL; - } -#else // _WIN32 - if (count_operation == - kAddRefNoCreate && instance_count == 0) { - return NULL; - } - if (count_operation == kAddRefNoCreate) { - if (1 == InterlockedIncrement(&instance_count)) { - // The instance has been destroyed by some other thread. Rollback. - InterlockedDecrement(&instance_count); - assert(false); - return NULL; - } - // Sanity to catch corrupt state. - if (instance == NULL) { - assert(false); - InterlockedDecrement(&instance_count); - return NULL; - } - } else if (count_operation == kAddRef) { - if (instance_count == 0) { - state = kCreate; - } else { - if (1 == InterlockedIncrement(&instance_count)) { - // InterlockedDecrement because reference count should not be - // updated just yet (that's done when the instance is created). - InterlockedDecrement(&instance_count); - state = kCreate; - } - } - } else { - int new_value = InterlockedDecrement(&instance_count); - if (new_value == 0) { - state = kDestroy; - } - } - - if (state == kCreate) { - // Create instance and let whichever thread finishes first assign its - // local copy to the global instance. All other threads reclaim their - // local copy. - T* new_instance = T::CreateInstance(); - if (1 == InterlockedIncrement(&instance_count)) { - InterlockedExchangePointer(reinterpret_cast(&instance), - new_instance); - } else { - InterlockedDecrement(&instance_count); - if (new_instance) { - delete static_cast(new_instance); - } - } - } else if (state == kDestroy) { - T* old_value = static_cast(InterlockedExchangePointer( - reinterpret_cast(&instance), NULL)); - if (old_value) { - delete static_cast(old_value); - } - return NULL; - } -#endif // #ifndef _WIN32 - return instance; + // Simple solution since we don't use this for large objects anymore + return Singleton::get(); } - } // namspace webrtc #endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_STATIC_INSTANCE_H_ diff --git a/media/webrtc/trunk/webrtc/system_wrappers/source/condition_variable_native_win.cc b/media/webrtc/trunk/webrtc/system_wrappers/source/condition_variable_native_win.cc index 22ddb6f8fd..b8c19a4270 100644 --- a/media/webrtc/trunk/webrtc/system_wrappers/source/condition_variable_native_win.cc +++ b/media/webrtc/trunk/webrtc/system_wrappers/source/condition_variable_native_win.cc @@ -52,7 +52,12 @@ bool ConditionVariableNativeWin::Init() { if (library) { // TODO(henrike): not thread safe as reading and writing to library is not // serialized. Fix. - WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, "Loaded Kernel.dll"); + + // Don't log here, since we may be creating a FileWrapper for + // the TraceImpl, from within GetStaticInstance. With singleton.h, you + // can't safely call GetStaticInstance on the same object from within + // creating that object. + //WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, "Loaded Kernel.dll"); PInitializeConditionVariable_ = (PInitializeConditionVariable) GetProcAddress( @@ -66,9 +71,9 @@ bool ConditionVariableNativeWin::Init() { if (PInitializeConditionVariable_ && PSleepConditionVariableCS_ && PWakeConditionVariable_ && PWakeAllConditionVariable_) { - WEBRTC_TRACE( - kTraceStateInfo, kTraceUtility, -1, - "Loaded native condition variables"); + //WEBRTC_TRACE( + // kTraceStateInfo, kTraceUtility, -1, + // "Loaded native condition variables"); win_support_condition_variables_primitive = true; } } diff --git a/media/webrtc/trunk/webrtc/system_wrappers/source/rw_lock_win.cc b/media/webrtc/trunk/webrtc/system_wrappers/source/rw_lock_win.cc index aea74fa4a3..a1d4b58195 100644 --- a/media/webrtc/trunk/webrtc/system_wrappers/source/rw_lock_win.cc +++ b/media/webrtc/trunk/webrtc/system_wrappers/source/rw_lock_win.cc @@ -69,7 +69,11 @@ bool RWLockWin::LoadModule() { if (!library) { return false; } - WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, "Loaded Kernel.dll"); + // Don't log here, since we may be creating a FileWrapper for + // the TraceImpl, from within GetStaticInstance. With singleton.h, you + // can't safely call GetStaticInstance on the same object from within + // creating that object (go figure...) + //WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, "Loaded Kernel.dll"); initialize_srw_lock = (InitializeSRWLock)GetProcAddress(library, "InitializeSRWLock"); @@ -88,7 +92,7 @@ bool RWLockWin::LoadModule() { if (initialize_srw_lock && acquire_srw_lock_exclusive && release_srw_lock_exclusive && acquire_srw_lock_shared && release_srw_lock_shared) { - WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, "Loaded Native RW Lock"); + //WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, "Loaded Native RW Lock"); native_rw_locks_supported = true; } return native_rw_locks_supported; diff --git a/media/webrtc/trunk/webrtc/system_wrappers/source/trace_impl.cc b/media/webrtc/trunk/webrtc/system_wrappers/source/trace_impl.cc index 661aefd683..4322603412 100644 --- a/media/webrtc/trunk/webrtc/system_wrappers/source/trace_impl.cc +++ b/media/webrtc/trunk/webrtc/system_wrappers/source/trace_impl.cc @@ -14,6 +14,7 @@ #include #include #include +#include "base/singleton.h" #ifdef _WIN32 #include "webrtc/system_wrappers/source/trace_win.h" @@ -63,7 +64,12 @@ TraceImpl* TraceImpl::StaticInstance(CountOperation count_operation, } } TraceImpl* impl = - GetStaticInstance(count_operation); +#if defined(_WIN32) + GetStaticInstance(count_operation); +#else + GetStaticInstance(count_operation); +#endif + return impl; } diff --git a/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h b/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h index 3898d5b1fe..94eb5083d8 100644 --- a/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h +++ b/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h @@ -113,6 +113,11 @@ class WEBRTC_DLLEXPORT ViERTP_RTCP { virtual int GetRemoteCSRCs(const int video_channel, unsigned int CSRCs[kRtpCsrcSize]) const = 0; + // This function gets the RID value (if any) for the incoming RTP stream + // for the specified channel. + virtual int GetRemoteRID(const int video_channel, + char rid[256]) const = 0; + // This sets a specific payload type for the RTX stream. Note that this // doesn't enable RTX, SetLocalSSRC must still be called to enable RTX. virtual int SetRtxSendPayloadType(const int video_channel, @@ -265,6 +270,15 @@ class WEBRTC_DLLEXPORT ViERTP_RTCP { bool enable, int id) = 0; + virtual int SetSendRIDStatus(int video_channel, + bool enable, + int id, + const char *rid) = 0; + + virtual int SetReceiveRIDStatus(int video_channel, + bool enable, + int id) = 0; + // Enables/disables RTCP Receiver Reference Time Report Block extension/ // DLRR Report Block extension (RFC 3611). virtual int SetRtcpXrRrtrStatus(int video_channel, bool enable) = 0; diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc b/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc index 514b793db2..ef4fefbe61 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc +++ b/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc @@ -123,6 +123,7 @@ ViEChannel::ViEChannel(int32_t channel_id, send_timestamp_extension_id_(kInvalidRtpExtensionId), absolute_send_time_extension_id_(kInvalidRtpExtensionId), video_rotation_extension_id_(kInvalidRtpExtensionId), + rid_extension_id_(kInvalidRtpExtensionId), external_transport_(NULL), decoder_reset_(true), wait_for_key_frame_(false), @@ -370,10 +371,19 @@ int32_t ViEChannel::SetSendCodec(const VideoCodec& video_codec, if (rtp_rtcp_->Sending() && new_stream) { restart_rtp = true; rtp_rtcp_->SetSendingStatus(false); + int i = 0; for (std::list::iterator it = simulcast_rtp_rtcp_.begin(); - it != simulcast_rtp_rtcp_.end(); ++it) { + it != simulcast_rtp_rtcp_.end(); ++it, ++i) { (*it)->SetSendingStatus(false); (*it)->SetSendingMediaStatus(false); + if (video_codec.simulcastStream[i].rid[0] != 0) { + (*it)->RegisterSendRtpHeaderExtension( + kRtpExtensionRID, video_codec.ridId); + (*it)->SetRID(video_codec.simulcastStream[i].rid); + } else { + (*it)->DeregisterSendRtpHeaderExtension( + kRtpExtensionRID); + } } } @@ -503,6 +513,19 @@ int32_t ViEChannel::SetSendCodec(const VideoCodec& video_codec, rtp_rtcp->DeregisterSendRtpHeaderExtension( kRtpExtensionVideoRotation); } + if (rid_extension_id_ != kInvalidRtpExtensionId) { + // Deregister in case the extension was previously enabled. + rtp_rtcp->DeregisterSendRtpHeaderExtension( + kRtpExtensionRID); + if (rtp_rtcp->RegisterSendRtpHeaderExtension( + kRtpExtensionRID, + rid_extension_id_) != 0) { + LOG(LS_WARNING) << "Register RID extension failed"; + } + } else { + rtp_rtcp->DeregisterSendRtpHeaderExtension( + kRtpExtensionRID); + } rtp_rtcp->RegisterRtcpStatisticsCallback( rtp_rtcp_->GetRtcpStatisticsCallback()); rtp_rtcp->RegisterSendChannelRtpStatisticsCallback( @@ -966,6 +989,37 @@ int ViEChannel::SetReceiveVideoRotationStatus(bool enable, int id) { return vie_receiver_.SetReceiveVideoRotationStatus(enable, id) ? 0 : -1; } +int ViEChannel::SetSendRIDStatus(bool enable, int id, const char *rid) { + CriticalSectionScoped cs(rtp_rtcp_cs_.get()); + int error = 0; + if (enable) { + // Enable the extension, but disable possible old id to avoid errors. + rid_extension_id_ = id; + rtp_rtcp_->DeregisterSendRtpHeaderExtension( + kRtpExtensionRID); + error = rtp_rtcp_->RegisterSendRtpHeaderExtension( + kRtpExtensionRID, id); + rtp_rtcp_->SetRID(rid); + // NOTE: simulcast streams must be set via the SetSendCodec() API + } else { + // Disable the extension. + rid_extension_id_ = kInvalidRtpExtensionId; + rtp_rtcp_->DeregisterSendRtpHeaderExtension( + kRtpExtensionRID); + // This may be overkill... + for (std::list::iterator it = simulcast_rtp_rtcp_.begin(); + it != simulcast_rtp_rtcp_.end(); it++) { + (*it)->DeregisterSendRtpHeaderExtension( + kRtpExtensionRID); + } + } + return error; +} + +int ViEChannel::SetReceiveRIDStatus(bool enable, int id) { + return vie_receiver_.SetReceiveRIDStatus(enable, id) ? 0 : -1; +} + void ViEChannel::SetRtcpXrRrtrStatus(bool enable) { CriticalSectionScoped cs(rtp_rtcp_cs_.get()); rtp_rtcp_->SetRtcpXrRrtrStatus(enable); @@ -1037,6 +1091,12 @@ int32_t ViEChannel::GetRemoteCSRC(uint32_t CSRCs[kRtpCsrcSize]) { return 0; } +int32_t ViEChannel::GetRemoteRID(char rid[256]) +{ + vie_receiver_.GetRID(rid); + return 0; +} + int ViEChannel::SetRtxSendPayloadType(int payload_type) { rtp_rtcp_->SetRtxSendPayloadType(payload_type); for (std::list::iterator it = simulcast_rtp_rtcp_.begin(); diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_channel.h b/media/webrtc/trunk/webrtc/video_engine/vie_channel.h index 1197115e28..9658b209cd 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_channel.h +++ b/media/webrtc/trunk/webrtc/video_engine/vie_channel.h @@ -138,6 +138,8 @@ class ViEChannel bool GetReceiveAbsoluteSendTimeStatus() const; int SetSendVideoRotationStatus(bool enable, int id); int SetReceiveVideoRotationStatus(bool enable, int id); + int SetSendRIDStatus(bool enable, int id, const char *rid); + int SetReceiveRIDStatus(bool enable, int id); void SetRtcpXrRrtrStatus(bool enable); void SetTransmissionSmoothingStatus(bool enable); void EnableTMMBR(bool enable); @@ -157,6 +159,9 @@ class ViEChannel // Gets the CSRC for the incoming stream. int32_t GetRemoteCSRC(uint32_t CSRCs[kRtpCsrcSize]); + // Gets the RID (if any) for the incoming stream. + int32_t GetRemoteRID(char rid[256]); + int SetRtxSendPayloadType(int payload_type); void SetRtxReceivePayloadType(int payload_type); @@ -554,6 +559,7 @@ class ViEChannel int send_timestamp_extension_id_; int absolute_send_time_extension_id_; int video_rotation_extension_id_; + int rid_extension_id_; Transport* external_transport_; diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.cc b/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.cc index b4c1b8c9d4..002aa97a01 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.cc +++ b/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.cc @@ -89,6 +89,7 @@ static void LogCodec(const VideoCodec& codec) { << codec.codecSpecific.H264.ppsLen; } else if (codec.codecType == kVideoCodecVP9) { LOG(LS_INFO) << "VP9 specific settings"; + // XXX FIX!! log VP9 specific settings } } diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_receiver.cc b/media/webrtc/trunk/webrtc/video_engine/vie_receiver.cc index daac6b93c4..d4989777db 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_receiver.cc +++ b/media/webrtc/trunk/webrtc/video_engine/vie_receiver.cc @@ -61,6 +61,7 @@ ViEReceiver::ViEReceiver(const int32_t channel_id, restored_packet_in_use_(false), receiving_ast_enabled_(false), receiving_cvo_enabled_(false), + receiving_rid_enabled_(false), last_packet_log_ms_(-1) { assert(remote_bitrate_estimator); } @@ -144,6 +145,10 @@ int ViEReceiver::GetCsrcs(uint32_t* csrcs) const { return rtp_receiver_->CSRCs(csrcs); } +void ViEReceiver::GetRID(char rid[256]) const { + rtp_receiver_->GetRID(rid); +} + void ViEReceiver::SetRtpRtcpModule(RtpRtcp* module) { rtp_rtcp_ = module; } @@ -206,6 +211,22 @@ bool ViEReceiver::SetReceiveVideoRotationStatus(bool enable, int id) { } } +bool ViEReceiver::SetReceiveRIDStatus(bool enable, int id) { + if (enable) { + if (rtp_header_parser_->RegisterRtpHeaderExtension( + kRtpExtensionRID, id)) { + receiving_rid_enabled_ = true; + return true; + } else { + return false; + } + } else { + receiving_rid_enabled_ = false; + return rtp_header_parser_->DeregisterRtpHeaderExtension( + kRtpExtensionRID); + } +} + int ViEReceiver::ReceivedRTPPacket(const void* rtp_packet, size_t rtp_packet_length, const PacketTime& packet_time) { @@ -295,6 +316,8 @@ int ViEReceiver::InsertRTPPacket(const uint8_t* rtp_packet, ss << ", toffset: " << header.extension.transmissionTimeOffset; if (header.extension.hasAbsoluteSendTime) ss << ", abs send time: " << header.extension.absoluteSendTime; + if (header.extension.hasRID) + ss << ", rid: " << header.extension.rid; LOG(LS_INFO) << ss.str(); last_packet_log_ms_ = now_ms; } diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_receiver.h b/media/webrtc/trunk/webrtc/video_engine/vie_receiver.h index 1002f64165..4b06476790 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_receiver.h +++ b/media/webrtc/trunk/webrtc/video_engine/vie_receiver.h @@ -55,6 +55,7 @@ class ViEReceiver : public RtpData { uint32_t GetRemoteSsrc() const; int GetCsrcs(uint32_t* csrcs) const; + void GetRID(char rid[256]) const; void SetRtpRtcpModule(RtpRtcp* module); @@ -65,6 +66,7 @@ class ViEReceiver : public RtpData { bool SetReceiveTimestampOffsetStatus(bool enable, int id); bool SetReceiveAbsoluteSendTimeStatus(bool enable, int id); bool SetReceiveVideoRotationStatus(bool enable, int id); + bool SetReceiveRIDStatus(bool enable, int id); void StartReceive(); void StopReceive(); @@ -129,6 +131,7 @@ class ViEReceiver : public RtpData { bool restored_packet_in_use_; bool receiving_ast_enabled_; bool receiving_cvo_enabled_; + bool receiving_rid_enabled_; int64_t last_packet_log_ms_; }; diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc index b8f8bd33a2..236d677146 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc +++ b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc @@ -191,6 +191,21 @@ int ViERTP_RTCPImpl::GetRemoteCSRCs(const int video_channel, return 0; } +int ViERTP_RTCPImpl::GetRemoteRID(const int video_channel, + char rid[256]) const { + ViEChannelManagerScoped cs(*(shared_data_->channel_manager())); + ViEChannel* vie_channel = cs.Channel(video_channel); + if (!vie_channel) { + shared_data_->SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (vie_channel->GetRemoteRID(rid) != 0) { + shared_data_->SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + int ViERTP_RTCPImpl::SetRtxSendPayloadType(const int video_channel, const uint8_t payload_type) { LOG_F(LS_INFO) << "channel: " << video_channel @@ -683,6 +698,44 @@ int ViERTP_RTCPImpl::SetReceiveVideoRotationStatus(int video_channel, return 0; } +int ViERTP_RTCPImpl::SetSendRIDStatus(int video_channel, + bool enable, + int id, + const char *rid) { + LOG_F(LS_INFO) << "channel: " << video_channel + << " enable: " << (enable ? "on" : "off") << " id: " << id << " RID: " << rid; + + ViEChannelManagerScoped cs(*(shared_data_->channel_manager())); + ViEChannel* vie_channel = cs.Channel(video_channel); + if (!vie_channel) { + shared_data_->SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (vie_channel->SetSendRIDStatus(enable, id, rid) != 0) { + shared_data_->SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +int ViERTP_RTCPImpl::SetReceiveRIDStatus(int video_channel, + bool enable, + int id) { + LOG_F(LS_INFO) << "channel: " << video_channel + << " enable: " << (enable ? "on" : "off") << " id: " << id; + ViEChannelManagerScoped cs(*(shared_data_->channel_manager())); + ViEChannel* vie_channel = cs.Channel(video_channel); + if (!vie_channel) { + shared_data_->SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (vie_channel->SetReceiveRIDStatus(enable, id) != 0) { + shared_data_->SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + int ViERTP_RTCPImpl::SetRtcpXrRrtrStatus(int video_channel, bool enable) { LOG_F(LS_INFO) << "channel: " << video_channel << " enable: " << (enable ? "on" : "off"); diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h index 8f92a72525..e1ac4f8cbc 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h +++ b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h @@ -39,6 +39,8 @@ class ViERTP_RTCPImpl unsigned int& SSRC) const; // NOLINT virtual int GetRemoteCSRCs(const int video_channel, unsigned int CSRCs[kRtpCsrcSize]) const; + virtual int GetRemoteRID(const int video_channel, + char rid[256]) const; virtual int SetRtxSendPayloadType(const int video_channel, const uint8_t payload_type); virtual int SetRtxReceivePayloadType(const int video_channel, @@ -105,6 +107,13 @@ class ViERTP_RTCPImpl virtual int SetReceiveVideoRotationStatus(int video_channel, bool enable, int id); + virtual int SetSendRIDStatus(int video_channel, + bool enable, + int id, + const char *rid); + virtual int SetReceiveRIDStatus(int video_channel, + bool enable, + int id); virtual int SetRtcpXrRrtrStatus(int video_channel, bool enable); virtual int SetTransmissionSmoothingStatus(int video_channel, bool enable); virtual int SetMinTransmitBitrate(int video_channel, diff --git a/tools/rewriting/ThirdPartyPaths.txt b/tools/rewriting/ThirdPartyPaths.txt new file mode 100644 index 0000000000..989028921d --- /dev/null +++ b/tools/rewriting/ThirdPartyPaths.txt @@ -0,0 +1,63 @@ +browser/components/translation/cld2/ +build/stlport/ +db/sqlite3/src/ +dom/media/platforms/ffmpeg/libav +extensions/spellcheck/hunspell/src/ +gfx/2d/convolver +gfx/2d/image_operations +gfx/angle/ +gfx/cairo/ +gfx/graphite2/ +gfx/harfbuzz/ +gfx/ots/ +gfx/qcms/ +gfx/skia/ +gfx/ycbcr/ +intl/hyphenation/hyphen/ +intl/icu/ +ipc/chromium/ +js/src/ctypes/libffi/ +js/src/dtoa.c +js/src/jit/arm64/vixl/ +media/gmp-clearkey/0.1/openaes/ +media/kiss_ftt/ +media/libav/ +media/libcubeb/ +media/libjpeg/ +media/libmkv/ +media/libnestegg/ +media/libogg/ +media/libopus/ +media/libpng/ +media/libsoundtouch/ +media/libspeex_resampler/ +media/libstagefright/ +media/libtheora/ +media/libtremor/ +media/libvorbis/ +media/libvpx/ +media/libyuv/ +media/mtransport/ +media/openmax_dl/ +media/pocketsphinx/ +media/sphinxbase/ +media/webrtc/trunk/ +memory/jemalloc/src/ +mfbt/decimal/ +mfbt/double-conversion/ +mfbt/lz4 +mobile/android/thirdparty/ +modules/brotli/ +modules/freetype2/ +modules/libbz2/ +modules/libmar/ +modules/zlib/ +netwerk/sctp/src/ +netwerk/srtp/src/ +nsprpub/ +other-licenses/ +security/sandbox/chromium/ +testing/gtest/gmock/ +testing/gtest/gtest/ +toolkit/components/protobuf/ +toolkit/crashreporter/google-breakpad/ From 67e04cfead9fe2cc53d7169aeab96d8126bbef39 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 9 Nov 2023 16:35:18 +0800 Subject: [PATCH 07/12] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1236750 - Introduce a new unit type CSSTransformedLayerPixel. r=kats (4e9bba3d19) - Bug 1236750 - Add a ViewAs() overload for casting (with a justification) one typed matrix to another. r=kats (c48e143508) - Bug 1236750 - Add some specialized typedefs of Matrix4x4 to represent layer transform matrices. r=kats (cc50113c98) - Bug 1239300 - reject promise with null while creating imagebitmap from empty blob; r=smaug (bff7483bf1) - Bug 1233056 - Long tapping on a link will select a different link from the page r=tnikkel (3a727b33c1) - Bug 1245674. Null-check mGlobal before dereferencing it in one more place in Promise code. r=smaug (68cf5312dc) - Bug 1236750 - Use strongly-typed matrices to represent layer transforms in APZ code. r=kats (63931eb2a1) - Bug 1236750 - Add typed getters for layer transform matrices. r=kats (baef978fe1) - Bug 1147673 - Relax the ancestor transform assertion a little. r=kats (ec9ce47ba4) - Bug 1154161 Initialize WidgetQueryContentEvent::mReply r=smaug (6086291313) - Bug 1240921 - Use nsAutoTArray in nsStyleSet::RuleNodeWithReplacement. r=bz. (66f6823b46) - Bug 1229437 part 1 - Add a helper function to get the float containing block of a given frame. r=dbaron (72de452e5d) - Bug 1229437 part 2 - Reparent floats inside pulled ruby segment. r=dbaron (82cc44632d) - Bug 1229437 part 3 - Support iterating frames of RubyColumn. r=dholbert (5d43e7f6ff) - Bug 1229437 part 4 - Reparent floats inside pulled ruby column. r=dbaron (4c1a7ff20b) - Bug 1229437 part 5 - Add crashtests for this bug. r=dbaron (6afabe1604) - Bug 1229437 followup - Fix sign-compare error in RubyColumn::Iterator on CLOSED TREE. (e93453d00a) - Bug 1229437 followup 2 - Fix another sign-compare error in RubyColumn::Iterator on CLOSED TREE. (c0bf6a2a7b) - Bug 1229437 followup 3 - Fix a mistake in RubyColumn::Iterator::SkipUntilExistingFrame(). a=me (1ce408e194) - bits of Bug 1072150 - Use the opt-out for various sloppy consumers (29d97c59ca) - Make test_bug946632 compatible with asynchronous scrolling. (bug 1140293, r=mstange) (e975a8350c) - fix misspatch of 1072150 (a3e580fa4b) - Bug 686281 - Implement nsStyleImageLayers; r=dbaron (85bb33c8e6) - Bug 686281 - Rename *background* to *imagelayer*; r=dbaron. (36d90f112d) - Bug 1230034 part 4 - Make FramePropertyDescriptor to be a template. r=froydnj,dbaron (271cd19b6e) - Bug 1230034 part 5 - Convert all frame properties which use DeleteValue and ReleaseValue as destructor to be typesafe. r=dbaron (efc8d63c9d) - Bug 1230034 part 6 - Convert all frame properties which do not hold pointer to be typed. r=dbaron (b5541775f7) - Bug 1230034 part 7 - Convert nsIFrame::GenConProperty to be typed. r=dbaron (2b71527b2c) - Bug 1230034 part 8 - Convert frame properties which assert on destructor to be typed. r=dbaron (0f125a3414) - Bug 1230034 part 9 - Convert FrameLayerBuilder::LayerManagerDataProperty to be typed. r=dbaron (1147498c2f) - Bug 1230034 part 10 - Convert remaining frame properties to by typed and remove the unsafe declaring macro. r=dbaron (d59d94eac4) - Bug 1072501: Unmap file mapping on source surface destruction. r=jrmuizel (19fd63890a) - Bug 1235613 - Make gfxCriticalError/Note strings in gfx/ unique. r=jrmuizel (780c6ff862) - Bug 1247535 - Fix -Wunreachable-code warning in mfbt/Poison.cpp. r=froydnj (0e7cf60b6d) - Bug 1239479: Add comments to mfbt/XorShift128PlusRNG.h from the RNG's designer. DONTBUILD r=Waldo (bb674b07ce) - Add an assertion message to the assert-is-empty in LinkedList::~LinkedList, to indicate to users who hit it that it's the fault of the caller, not the fault of MFBT code. No bug, rs=froydnj (4cad80874c) - Bug 1221103 - Add a comment to nsIChannel::securityInfo noting that this info may appear on non-nsHttpChannels and how that may happen. r=bz IGNORE IDL (cd9cebc3f2) - Bug 1001765 - Make login credentials in Saved Passwords manager editable. r=MattN (09eec4f6f8) - Bug 1188478 - Add an Import button to the password manager to open the browser migrator. r=dolske (d1126a89fc) - Bug 1199382 - Rename some strings from "password" to "login" in preferences and the manager. r=markh (60638f5e2a) - Bug 1207733 - Update @disabled on the Remove Password button when selection changes. r=MattN (64ac9f22f6) - bits of 1124472 and 1166840 (26e2681183) - Bug 1219707 - fix argument passing to migration.js, r=jaws (55d332f5c6) - bug 1215657 - make AccessibleWrap::get_accSelection work with proxies=davidb (4e72111032) - fix missing telemetry entry (4fcfabb3e6) and follow-up API changes of TFF#493. --- accessible/windows/msaa/AccessibleWrap.cpp | 12 +- browser/components/preferences/security.xul | 6 +- .../chrome/browser/preferences/security.dtd | 10 +- dom/base/nsContentUtils.cpp | 2 +- dom/base/nsDOMWindowUtils.cpp | 18 +- dom/canvas/ImageBitmap.cpp | 6 +- dom/canvas/test/imagebitmap_bug1239300.js | 19 ++ dom/canvas/test/imagebitmap_on_worker.js | 5 + dom/canvas/test/mochitest.ini | 9 + dom/canvas/test/test_imagebitmap.html | 2 + .../test/test_imagebitmap_on_worker.html | 3 + dom/events/Event.cpp | 2 +- dom/events/test/test_bug946632.html | 50 ++-- dom/promise/Promise.cpp | 8 +- dom/promise/Promise.h | 2 + gfx/2d/DrawTargetD2D1.cpp | 4 +- gfx/2d/Factory.cpp | 6 +- gfx/2d/SourceSurfaceD2D1.cpp | 4 +- gfx/2d/SourceSurfaceD2DTarget.cpp | 4 +- gfx/layers/BufferTexture.cpp | 6 +- gfx/layers/LayerMetricsWrapper.h | 6 + gfx/layers/Layers.cpp | 12 + gfx/layers/Layers.h | 12 +- gfx/layers/LayersTypes.h | 17 ++ gfx/layers/TextureDIB.cpp | 129 ++++++--- gfx/layers/TextureDIB.h | 2 - gfx/layers/apz/src/APZCTreeManager.cpp | 28 +- gfx/layers/apz/src/APZUtils.h | 12 + gfx/layers/apz/src/AsyncPanZoomController.cpp | 11 +- gfx/layers/apz/src/AsyncPanZoomController.h | 4 +- gfx/layers/apz/src/HitTestingTreeNode.cpp | 15 +- gfx/layers/apz/src/HitTestingTreeNode.h | 4 +- gfx/layers/client/TextureClient.cpp | 1 - gfx/layers/client/TiledContentClient.cpp | 10 +- .../composite/AsyncCompositionManager.cpp | 88 +++--- .../composite/AsyncCompositionManager.h | 17 +- .../composite/ContainerLayerComposite.cpp | 4 +- gfx/layers/d3d11/TextureD3D11.cpp | 12 +- gfx/layers/d3d9/DeviceManagerD3D9.cpp | 34 +-- gfx/layers/d3d9/TextureD3D9.cpp | 8 +- gfx/layers/opengl/GrallocTextureClient.cpp | 4 +- gfx/thebes/gfxWindowsPlatform.cpp | 4 +- layout/base/ActiveLayerTracker.cpp | 10 +- layout/base/FrameLayerBuilder.cpp | 18 +- layout/base/FrameLayerBuilder.h | 4 +- layout/base/FramePropertyTable.cpp | 21 +- layout/base/FramePropertyTable.h | 230 ++++++++++++---- layout/base/RestyleManager.cpp | 5 +- layout/base/RestyleTracker.h | 8 +- layout/base/UnitTransforms.h | 19 +- layout/base/Units.h | 24 ++ layout/base/nsBidi.h | 14 +- layout/base/nsBidiPresUtils.cpp | 30 +- layout/base/nsCSSFrameConstructor.cpp | 8 +- layout/base/nsCSSRendering.cpp | 214 ++++++++------- layout/base/nsCSSRendering.h | 40 +-- layout/base/nsDisplayList.cpp | 69 +++-- layout/base/nsDisplayList.h | 7 +- layout/base/nsLayoutUtils.cpp | 42 ++- layout/base/nsLayoutUtils.h | 11 + layout/forms/nsTextControlFrame.cpp | 3 +- layout/forms/nsTextControlFrame.h | 11 +- layout/generic/RubyUtils.cpp | 47 +++- layout/generic/RubyUtils.h | 48 ++++ layout/generic/StickyScrollContainer.cpp | 4 +- layout/generic/crashtests/1229437-1.html | 8 + layout/generic/crashtests/1229437-2.html | 5 + layout/generic/crashtests/crashtests.list | 2 + layout/generic/nsBlockFrame.cpp | 36 +-- layout/generic/nsBlockFrame.h | 4 +- layout/generic/nsBulletFrame.cpp | 17 +- layout/generic/nsCanvasFrame.cpp | 7 +- layout/generic/nsCanvasFrame.h | 4 +- layout/generic/nsContainerFrame.cpp | 50 ++-- layout/generic/nsContainerFrame.h | 35 +-- layout/generic/nsFlexContainerFrame.cpp | 2 +- layout/generic/nsFloatManager.cpp | 2 +- layout/generic/nsFontInflationData.cpp | 4 +- layout/generic/nsFrame.cpp | 55 ++-- layout/generic/nsGfxScrollFrame.cpp | 2 +- layout/generic/nsGridContainerFrame.cpp | 4 +- layout/generic/nsGridContainerFrame.h | 18 +- layout/generic/nsHTMLReflowState.cpp | 4 +- layout/generic/nsIFrame.h | 115 +++++--- layout/generic/nsInlineFrame.cpp | 13 +- layout/generic/nsRubyBaseContainerFrame.cpp | 37 ++- layout/generic/nsRubyFrame.cpp | 20 +- layout/generic/nsRubyFrame.h | 3 +- layout/generic/nsTextFrame.cpp | 65 ++--- layout/mathml/nsMathMLContainerFrame.cpp | 8 +- layout/mathml/nsMathMLmtableFrame.cpp | 14 +- layout/style/Declaration.cpp | 20 +- layout/style/StyleAnimationValue.cpp | 30 +- layout/style/nsCSSParser.cpp | 189 ++++++------- layout/style/nsCSSPropList.h | 22 +- layout/style/nsCSSProps.cpp | 66 ++--- layout/style/nsCSSProps.h | 12 +- layout/style/nsCSSValue.cpp | 4 +- layout/style/nsComputedDOMStyle.cpp | 91 +++--- layout/style/nsComputedDOMStyle.h | 11 +- layout/style/nsRuleNode.cpp | 259 +++++++++--------- layout/style/nsStyleConsts.h | 75 ++--- layout/style/nsStyleSet.cpp | 4 +- layout/style/nsStyleStruct.cpp | 253 +++++++++-------- layout/style/nsStyleStruct.h | 114 +++++--- layout/style/nsStyleUtil.cpp | 4 +- layout/svg/SVGTextFrame.cpp | 6 +- layout/svg/nsSVGEffects.cpp | 22 +- layout/svg/nsSVGEffects.h | 47 ++-- layout/svg/nsSVGUtils.h | 8 +- layout/tables/nsTableFrame.cpp | 8 +- layout/tables/nsTableFrame.h | 4 +- layout/tables/nsTablePainter.cpp | 6 +- layout/tables/nsTableRowFrame.cpp | 6 +- layout/tables/nsTableRowGroupFrame.cpp | 7 +- layout/xul/nsMenuFrame.cpp | 13 +- mfbt/LinkedList.h | 7 +- mfbt/Poison.cpp | 4 +- mfbt/XorShift128PlusRNG.h | 24 +- netwerk/base/nsIChannel.idl | 11 +- .../passwordmgr/content/passwordManager.js | 62 ++++- .../passwordmgr/content/passwordManager.xul | 10 +- .../content/passwordManagerExceptions.xul | 4 +- toolkit/components/passwordmgr/moz.build | 3 + .../passwordmgr/test/browser/browser.ini | 1 + .../browser/browser_passwordmgr_editing.js | 126 +++++++++ toolkit/components/telemetry/Histograms.json | 52 +++- .../chrome/passwordmgr/passwordManager.dtd | 11 +- .../chrome/passwordmgr/passwordmgr.properties | 2 - widget/TextEvents.h | 26 +- 130 files changed, 2222 insertions(+), 1435 deletions(-) create mode 100644 dom/canvas/test/imagebitmap_bug1239300.js create mode 100644 layout/generic/crashtests/1229437-1.html create mode 100644 layout/generic/crashtests/1229437-2.html create mode 100644 toolkit/components/passwordmgr/test/browser/browser_passwordmgr_editing.js diff --git a/accessible/windows/msaa/AccessibleWrap.cpp b/accessible/windows/msaa/AccessibleWrap.cpp index 30969d688f..b159e23b29 100644 --- a/accessible/windows/msaa/AccessibleWrap.cpp +++ b/accessible/windows/msaa/AccessibleWrap.cpp @@ -838,7 +838,17 @@ AccessibleWrap::get_accSelection(VARIANT __RPC_FAR *pvarChildren) if (IsSelect()) { nsAutoTArray selectedItems; - SelectedItems(&selectedItems); + if (IsProxy()) { + nsTArray proxies; + Proxy()->SelectedItems(&proxies); + + uint32_t selectedCount = proxies.Length(); + for (uint32_t i = 0; i < selectedCount; i++) { + selectedItems.AppendElement(WrapperFor(proxies[i])); + } + } else { + SelectedItems(&selectedItems); + } // 1) Create and initialize the enumeration RefPtr pEnum = new AccessibleEnumerator(selectedItems); diff --git a/browser/components/preferences/security.xul b/browser/components/preferences/security.xul index 43352b926c..95f2dfd991 100644 --- a/browser/components/preferences/security.xul +++ b/browser/components/preferences/security.xul @@ -93,11 +93,11 @@ -
Cell
+