author | Wes Kocher <wkocher@mozilla.com> |
Thu, 08 Oct 2015 14:29:04 -0700 | |
changeset 300254 | 46da59584acb32e4189f3b35f14b03ca20d9c727 |
parent 300214 | 28a0a90ea90a3517bcf38fe362468ac7feb3c94b (current diff) |
parent 300253 | ca546bb0c669ec55f6264c80c86992d0fc18f10b (diff) |
child 300280 | e5f1bc63ad52d0eb86f7fb838226ca6036774660 |
push id | 5392 |
push user | raliiev@mozilla.com |
push date | Mon, 14 Dec 2015 20:08:23 +0000 |
treeherder | mozilla-beta@16ce8562a975 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 44.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/generic/ARIAGridAccessible.cpp +++ b/accessible/generic/ARIAGridAccessible.cpp @@ -542,18 +542,19 @@ ARIARowAccessible:: } NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible, Accessible) GroupPos ARIARowAccessible::GroupPosition() { int32_t count = 0, index = 0; - if (nsCoreUtils::GetUIntAttr(nsAccUtils::TableFor(this)->GetContent(), - nsGkAtoms::aria_rowcount, &count) && + Accessible* table = nsAccUtils::TableFor(this); + if (table && nsCoreUtils::GetUIntAttr(table->GetContent(), + nsGkAtoms::aria_rowcount, &count) && nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) { return GroupPos(0, index, count); } return AccessibleWrap::GroupPosition(); }
--- a/browser/components/extensions/test/browser/browser.ini +++ b/browser/components/extensions/test/browser/browser.ini @@ -1,10 +1,9 @@ [DEFAULT] -skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac' support-files = head.js context.html ctxmenu-image.png [browser_ext_simple.js] [browser_ext_currentWindow.js] [browser_ext_browserAction_simple.js]
--- a/build/mach_bootstrap.py +++ b/build/mach_bootstrap.py @@ -98,24 +98,25 @@ SEARCH_PATHS = [ 'testing/marionette/transport', 'testing/marionette/driver', 'testing/luciddream', 'testing/mozbase/mozcrash', 'testing/mozbase/mozdebug', 'testing/mozbase/mozdevice', 'testing/mozbase/mozfile', 'testing/mozbase/mozhttpd', + 'testing/mozbase/mozinfo', + 'testing/mozbase/mozinstall', 'testing/mozbase/mozleak', 'testing/mozbase/mozlog', 'testing/mozbase/moznetwork', 'testing/mozbase/mozprocess', 'testing/mozbase/mozprofile', 'testing/mozbase/mozrunner', 'testing/mozbase/mozsystemmonitor', - 'testing/mozbase/mozinfo', 'testing/mozbase/mozscreenshot', 'testing/mozbase/moztest', 'testing/mozbase/mozversion', 'testing/mozbase/manifestparser', 'xpcom/idl-parser', ] # Individual files providing mach commands.
--- a/docshell/test/unit/test_nsDefaultURIFixup_info.js +++ b/docshell/test/unit/test_nsDefaultURIFixup_info.js @@ -207,21 +207,20 @@ var testcases = [ { fixedURI: "http://[64:ff9b::8.8.8.8]/~moz", protocolChange: true }, { input: "[::1][::1]", keywordLookup: true, protocolChange: true }, { input: "[::1][100", - fixedURI: "http://[::1][100/", - alternateURI: "http://[::1][100/", + fixedURI: null, + alternateURI: null, keywordLookup: true, - protocolChange: true, - affectedByDNSForSingleHosts: true, + protocolChange: true }, { input: "[::1]]", keywordLookup: true, protocolChange: true }, { input: "1234", fixedURI: "http://1234/", alternateURI: "http://www.1234.com/", @@ -498,23 +497,24 @@ if (Services.appinfo.OS.toLowerCase().st fixedURI: "file:///some/file.txt", protocolChange: true, }); testcases.push({ input: "//mozilla", fixedURI: "file:////mozilla", protocolChange: true, }); + // \ is an invalid character in the hostname until bug 652186 is implemented testcases.push({ input: "mozilla\\", - fixedURI: "http://mozilla\\/", - alternateURI: "http://www.mozilla/", + // fixedURI: "http://mozilla\\/", + // alternateURI: "http://www.mozilla/", keywordLookup: true, protocolChange: true, - affectedByDNSForSingleHosts: true, + // affectedByDNSForSingleHosts: true, }); } function sanitize(input) { return input.replace(/\r|\n/g, "").trim(); }
--- a/dom/media/MP3Demuxer.cpp +++ b/dom/media/MP3Demuxer.cpp @@ -79,21 +79,20 @@ MP3Demuxer::GetTrackDemuxer(TrackInfo::T } bool MP3Demuxer::IsSeekable() const { return true; } void -MP3Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) { +MP3Demuxer::NotifyDataArrived() { // TODO: bug 1169485. NS_WARNING("Unimplemented function NotifyDataArrived"); - MP3DEMUXER_LOGV("NotifyDataArrived(%u, %" PRId64 ") mOffset=%" PRId64, - aLength, aOffset, mTrackDemuxer->GetResourceOffset()); + MP3DEMUXER_LOGV("NotifyDataArrived()"); } void MP3Demuxer::NotifyDataRemoved() { // TODO: bug 1169485. NS_WARNING("Unimplemented function NotifyDataRemoved"); MP3DEMUXER_LOGV("NotifyDataRemoved()"); }
--- a/dom/media/MP3Demuxer.h +++ b/dom/media/MP3Demuxer.h @@ -20,17 +20,17 @@ public: // MediaDataDemuxer interface. explicit MP3Demuxer(MediaResource* aSource); nsRefPtr<InitPromise> Init() override; bool HasTrackType(TrackInfo::TrackType aType) const override; uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override; already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer( TrackInfo::TrackType aType, uint32_t aTrackNumber) override; bool IsSeekable() const override; - void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override; + void NotifyDataArrived() override; void NotifyDataRemoved() override; // Do not shift the calculated buffered range by the start time of the first // decoded frame. The mac MP3 decoder will buffer some samples and the first // frame returned has typically a start time that is non-zero, causing our // buffered range to have a negative start time. bool ShouldComputeStartTime() const override { return false; } private:
--- a/dom/media/MediaDataDemuxer.h +++ b/dom/media/MediaDataDemuxer.h @@ -72,25 +72,27 @@ public: // Returns the media's crypto information, or nullptr if media isn't // encrypted. virtual UniquePtr<EncryptionInfo> GetCrypto() { return nullptr; } - // Notifies the demuxer that the underlying resource has received more data. + // Notifies the demuxer that the underlying resource has received more data + // since the demuxer was initialized. // The demuxer can use this mechanism to inform all track demuxers that new - // data is available. - virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) { } + // data is available and to refresh its buffered range. + virtual void NotifyDataArrived() { } - // Notifies the demuxer that the underlying resource has had data removed. + // Notifies the demuxer that the underlying resource has had data removed + // since the demuxer was initialized. // The demuxer can use this mechanism to inform all track demuxers to update - // its TimeIntervals. - // This will be called should the demuxer be used with MediaSource. + // its buffered range. + // This will be called should the demuxer be used with MediaSourceResource. virtual void NotifyDataRemoved() { } // Indicate to MediaFormatReader if it should compute the start time // of the demuxed data. If true (default) the first sample returned will be // used as reference time base. virtual bool ShouldComputeStartTime() const { return true; } protected:
--- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -1598,17 +1598,17 @@ MediaFormatReader::NotifyDemuxer(uint32_ LOGV("aLength=%u, aOffset=%lld", aLength, aOffset); if (mShutdown || !mDemuxer || (!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) { return; } if (aLength || aOffset) { - mDemuxer->NotifyDataArrived(aLength, aOffset); + mDemuxer->NotifyDataArrived(); } else { mDemuxer->NotifyDataRemoved(); } if (!mInitDone) { return; } if (HasVideo()) { mVideo.mReceivedNewData = true;
--- a/dom/media/fmp4/MP4Demuxer.cpp +++ b/dom/media/fmp4/MP4Demuxer.cpp @@ -181,17 +181,17 @@ MP4Demuxer::GetTrackDemuxer(TrackInfo::T bool MP4Demuxer::IsSeekable() const { return mMetadata->CanSeek(); } void -MP4Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) +MP4Demuxer::NotifyDataArrived() { for (uint32_t i = 0; i < mDemuxers.Length(); i++) { mDemuxers[i]->NotifyDataArrived(); } } void MP4Demuxer::NotifyDataRemoved()
--- a/dom/media/fmp4/MP4Demuxer.h +++ b/dom/media/fmp4/MP4Demuxer.h @@ -35,17 +35,17 @@ public: virtual already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) override; virtual bool IsSeekable() const override; virtual UniquePtr<EncryptionInfo> GetCrypto() override; - virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override; + virtual void NotifyDataArrived() override; virtual void NotifyDataRemoved() override; private: friend class MP4TrackDemuxer; nsRefPtr<MediaResource> mResource; nsRefPtr<mp4_demuxer::ResourceStream> mStream; nsRefPtr<MediaByteBuffer> mInitData;
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp +++ b/dom/media/mediasource/MediaSourceDemuxer.cpp @@ -48,17 +48,17 @@ MediaSourceDemuxer::AttemptInit() return InitPromise::CreateAndResolve(NS_OK, __func__); } nsRefPtr<InitPromise> p = mInitPromise.Ensure(__func__); return p; } -void MediaSourceDemuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) +void MediaSourceDemuxer::NotifyDataArrived() { nsRefPtr<MediaSourceDemuxer> self = this; nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([self] () { if (self->mInitPromise.IsEmpty()) { return; } if (self->ScanSourceBuffersForContent()) {
--- a/dom/media/mediasource/MediaSourceDemuxer.h +++ b/dom/media/mediasource/MediaSourceDemuxer.h @@ -37,17 +37,17 @@ public: uint32_t aTrackNumber) override; bool IsSeekable() const override; UniquePtr<EncryptionInfo> GetCrypto() override; bool ShouldComputeStartTime() const override { return false; } - void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override; + void NotifyDataArrived() override; /* interface for TrackBuffersManager */ void AttachSourceBuffer(TrackBuffersManager* aSourceBuffer); void DetachSourceBuffer(TrackBuffersManager* aSourceBuffer); TaskQueue* GetTaskQueue() { return mTaskQueue; } // Returns a string describing the state of the MediaSource internal // buffered data. Used for debugging purposes.
--- a/dom/media/mediasource/TrackBuffersManager.cpp +++ b/dom/media/mediasource/TrackBuffersManager.cpp @@ -871,20 +871,18 @@ TrackBuffersManager::OnDemuxerResetDone( SegmentParserLoop(); } void TrackBuffersManager::AppendDataToCurrentInputBuffer(MediaByteBuffer* aData) { MOZ_ASSERT(mCurrentInputBuffer); - int64_t offset = mCurrentInputBuffer->GetLength(); mCurrentInputBuffer->AppendData(aData); - // A MediaByteBuffer has a maximum size of 2GiB. - mInputDemuxer->NotifyDataArrived(uint32_t(aData->Length()), offset); + mInputDemuxer->NotifyDataArrived(); } void TrackBuffersManager::InitializationSegmentReceived() { MOZ_ASSERT(mParser->HasCompleteInitData()); mCurrentInputBuffer = new SourceBufferResource(mType); // The demuxer isn't initialized yet ; we don't want to notify it
--- a/dom/media/omx/MediaCodecProxy.cpp +++ b/dom/media/omx/MediaCodecProxy.cpp @@ -9,17 +9,16 @@ #include <binder/IPCThreadState.h> #include <stagefright/foundation/ABuffer.h> #include <stagefright/foundation/ADebug.h> #include <stagefright/MetaData.h> #include "stagefright/MediaErrors.h" #include <android/log.h> #define MCP_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "MediaCodecProxy", __VA_ARGS__) -#define TIMEOUT_DEQUEUE_INPUTBUFFER_MS 1000000ll namespace android { // General Template: MediaCodec::getOutputGraphicBufferFromIndex(...) template <typename T, bool InterfaceSupported> struct OutputGraphicBufferStub { static status_t GetOutputGraphicBuffer(T *aMediaCodec, @@ -566,31 +565,34 @@ bool MediaCodecProxy::UpdateOutputBuffer if (err != OK){ MCP_LOG("Couldn't update output buffers from MediaCodec"); return false; } return true; } status_t MediaCodecProxy::Input(const uint8_t* aData, uint32_t aDataSize, - int64_t aTimestampUsecs, uint64_t aflags) + int64_t aTimestampUsecs, uint64_t aflags, + int64_t aTimeoutUs) { // Read Lock for mCodec { RWLock::AutoRLock autolock(mCodecLock); if (mCodec == nullptr) { MCP_LOG("MediaCodec has not been inited from input!"); return NO_INIT; } } size_t index; - status_t err = dequeueInputBuffer(&index, TIMEOUT_DEQUEUE_INPUTBUFFER_MS); + status_t err = dequeueInputBuffer(&index, aTimeoutUs); if (err != OK) { - MCP_LOG("dequeueInputBuffer returned %d", err); + if (err != -EAGAIN) { + MCP_LOG("dequeueInputBuffer returned %d", err); + } return err; } if (aData) { const sp<ABuffer> &dstBuffer = mInputBuffers.itemAt(index); CHECK_LE(aDataSize, dstBuffer->capacity()); dstBuffer->setRange(0, aDataSize);
--- a/dom/media/omx/MediaCodecProxy.h +++ b/dom/media/omx/MediaCodecProxy.h @@ -124,17 +124,17 @@ public: sp<GraphicBuffer> *aGraphicBuffer); status_t getCapability(uint32_t *aCapability); // Utility functions // If aData is null, will notify decoder input EOS status_t Input(const uint8_t* aData, uint32_t aDataSize, - int64_t aTimestampUsecs, uint64_t flags); + int64_t aTimestampUsecs, uint64_t flags, int64_t aTimeoutUs = 0); status_t Output(MediaBuffer** aBuffer, int64_t aTimeoutUs); bool Prepare(); void ReleaseMediaResources(); // This updates mOutputBuffer when receiving INFO_OUTPUT_BUFFERS_CHANGED event. bool UpdateOutputBuffers(); void ReleaseMediaBuffer(MediaBuffer* abuffer);
--- a/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp +++ b/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp @@ -16,71 +16,66 @@ #include "stagefright/MetaData.h" #include "stagefright/MediaErrors.h" #include <stagefright/foundation/AMessage.h> #include <stagefright/foundation/ALooper.h> #include "media/openmax/OMX_Audio.h" #include "MediaData.h" #include "MediaInfo.h" +#define CODECCONFIG_TIMEOUT_US 10000LL +#define READ_OUTPUT_BUFFER_TIMEOUT_US 0LL + #include <android/log.h> #define GADM_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkAudioDecoderManager", __VA_ARGS__) extern PRLogModuleInfo* GetPDMLog(); #define LOG(...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, (__VA_ARGS__)) -#define READ_OUTPUT_BUFFER_TIMEOUT_US 3000 using namespace android; typedef android::MediaCodecProxy MediaCodecProxy; namespace mozilla { GonkAudioDecoderManager::GonkAudioDecoderManager(const AudioInfo& aConfig) - : mLastDecodedTime(0) - , mAudioChannels(aConfig.mChannels) + : mAudioChannels(aConfig.mChannels) , mAudioRate(aConfig.mRate) , mAudioProfile(aConfig.mProfile) , mAudioBuffer(nullptr) - , mMonitor("GonkAudioDecoderManager") { MOZ_COUNT_CTOR(GonkAudioDecoderManager); MOZ_ASSERT(mAudioChannels); mCodecSpecificData = aConfig.mCodecSpecificConfig; mMimeType = aConfig.mMimeType; - } GonkAudioDecoderManager::~GonkAudioDecoderManager() { MOZ_COUNT_DTOR(GonkAudioDecoderManager); } nsRefPtr<MediaDataDecoder::InitPromise> -GonkAudioDecoderManager::Init(MediaDataDecoderCallback* aCallback) +GonkAudioDecoderManager::Init() { - if (InitMediaCodecProxy(aCallback)) { + if (InitMediaCodecProxy()) { return InitPromise::CreateAndResolve(TrackType::kAudioTrack, __func__); } else { return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); } } bool -GonkAudioDecoderManager::InitMediaCodecProxy(MediaDataDecoderCallback* aCallback) +GonkAudioDecoderManager::InitMediaCodecProxy() { status_t rv = OK; - if (mLooper != nullptr) { + if (!InitLoopers(MediaData::AUDIO_DATA)) { return false; } - // Create ALooper - mLooper = new ALooper; - mLooper->setName("GonkAudioDecoderManager"); - mLooper->start(); - mDecoder = MediaCodecProxy::CreateByType(mLooper, mMimeType.get(), false, nullptr); + mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mMimeType.get(), false, nullptr); if (!mDecoder.get()) { return false; } if (!mDecoder->AskMediaCodecAndWait()) { mDecoder = nullptr; return false; } @@ -94,73 +89,28 @@ GonkAudioDecoderManager::InitMediaCodecP format->setInt32("aac-profile", mAudioProfile); status_t err = mDecoder->configure(format, nullptr, nullptr, 0); if (err != OK || !mDecoder->Prepare()) { return false; } if (mMimeType.EqualsLiteral("audio/mp4a-latm")) { rv = mDecoder->Input(mCodecSpecificData->Elements(), mCodecSpecificData->Length(), 0, - android::MediaCodec::BUFFER_FLAG_CODECCONFIG); + android::MediaCodec::BUFFER_FLAG_CODECCONFIG, + CODECCONFIG_TIMEOUT_US); } if (rv == OK) { return true; } else { GADM_LOG("Failed to input codec specific data!"); return false; } } -bool -GonkAudioDecoderManager::HasQueuedSample() -{ - MonitorAutoLock mon(mMonitor); - return mQueueSample.Length(); -} - -nsresult -GonkAudioDecoderManager::Input(MediaRawData* aSample) -{ - MonitorAutoLock mon(mMonitor); - nsRefPtr<MediaRawData> sample; - - if (aSample) { - sample = aSample; - } else { - // It means EOS with empty sample. - sample = new MediaRawData(); - } - - mQueueSample.AppendElement(sample); - - status_t rv; - while (mQueueSample.Length()) { - nsRefPtr<MediaRawData> data = mQueueSample.ElementAt(0); - { - MonitorAutoUnlock mon_exit(mMonitor); - rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->Data()), - data->Size(), - data->mTime, - 0); - } - if (rv == OK) { - mQueueSample.RemoveElementAt(0); - } else if (rv == -EAGAIN || rv == -ETIMEDOUT) { - // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill - // buffer on time. - return NS_OK; - } else { - return NS_ERROR_UNEXPECTED; - } - } - - return NS_OK; -} - nsresult GonkAudioDecoderManager::CreateAudioData(int64_t aStreamOffset, AudioData **v) { if (!(mAudioBuffer != nullptr && mAudioBuffer->data() != nullptr)) { GADM_LOG("Audio Buffer is not valid!"); return NS_ERROR_UNEXPECTED; } int64_t timeUs; @@ -170,23 +120,23 @@ GonkAudioDecoderManager::CreateAudioData if (mAudioBuffer->range_length() == 0) { // Some decoders may return spurious empty buffers that we just want to ignore // quoted from Android's AwesomePlayer.cpp ReleaseAudioBuffer(); return NS_ERROR_NOT_AVAILABLE; } - if (mLastDecodedTime > timeUs) { + if (mLastTime > timeUs) { ReleaseAudioBuffer(); GADM_LOG("Output decoded sample time is revert. time=%lld", timeUs); MOZ_ASSERT(false); return NS_ERROR_NOT_AVAILABLE; } - mLastDecodedTime = timeUs; + mLastTime = timeUs; const uint8_t *data = static_cast<const uint8_t*>(mAudioBuffer->data()); size_t dataOffset = mAudioBuffer->range_offset(); size_t size = mAudioBuffer->range_length(); nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[size/2]); memcpy(buffer.get(), data+dataOffset, size); uint32_t frames = size / (2 * mAudioChannels); @@ -203,33 +153,16 @@ GonkAudioDecoderManager::CreateAudioData mAudioChannels, mAudioRate); ReleaseAudioBuffer(); audioData.forget(v); return NS_OK; } nsresult -GonkAudioDecoderManager::Flush() -{ - { - MonitorAutoLock mon(mMonitor); - mQueueSample.Clear(); - } - - mLastDecodedTime = 0; - - if (mDecoder->flush() != OK) { - return NS_ERROR_FAILURE; - } - - return NS_OK; -} - -nsresult GonkAudioDecoderManager::Output(int64_t aStreamOffset, nsRefPtr<MediaData>& aOutData) { aOutData = nullptr; status_t err; err = mDecoder->Output(&mAudioBuffer, READ_OUTPUT_BUFFER_TIMEOUT_US); switch (err) {
--- a/dom/media/platforms/gonk/GonkAudioDecoderManager.h +++ b/dom/media/platforms/gonk/GonkAudioDecoderManager.h @@ -8,62 +8,43 @@ #define GonkAudioDecoderManager_h_ #include "mozilla/RefPtr.h" #include "GonkMediaDataDecoder.h" using namespace android; namespace android { -struct MOZ_EXPORT ALooper; class MOZ_EXPORT MediaBuffer; } // namespace android namespace mozilla { class GonkAudioDecoderManager : public GonkDecoderManager { typedef android::MediaCodecProxy MediaCodecProxy; public: GonkAudioDecoderManager(const AudioInfo& aConfig); - virtual ~GonkAudioDecoderManager() override; + virtual ~GonkAudioDecoderManager(); - nsRefPtr<InitPromise> Init(MediaDataDecoderCallback* aCallback) override; - - nsresult Input(MediaRawData* aSample) override; + nsRefPtr<InitPromise> Init() override; nsresult Output(int64_t aStreamOffset, nsRefPtr<MediaData>& aOutput) override; - nsresult Flush() override; - - bool HasQueuedSample() override; - private: - bool InitMediaCodecProxy(MediaDataDecoderCallback* aCallback); + bool InitMediaCodecProxy(); nsresult CreateAudioData(int64_t aStreamOffset, AudioData** aOutData); void ReleaseAudioBuffer(); - int64_t mLastDecodedTime; - uint32_t mAudioChannels; uint32_t mAudioRate; const uint32_t mAudioProfile; - MediaDataDecoderCallback* mReaderCallback; android::MediaBuffer* mAudioBuffer; - android::sp<ALooper> mLooper; - - // This monitor protects mQueueSample. - Monitor mMonitor; - - // An queue with the MP4 samples which are waiting to be sent into OMX. - // If an element is an empty MP4Sample, that menas EOS. There should not - // any sample be queued after EOS. - nsTArray<nsRefPtr<MediaRawData>> mQueueSample; }; } // namespace mozilla #endif // GonkAudioDecoderManager_h_
--- a/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp +++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp @@ -3,64 +3,282 @@ /* 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 "GonkMediaDataDecoder.h" #include "VideoUtils.h" #include "nsTArray.h" #include "MediaCodecProxy.h" +#include <stagefright/foundation/ADebug.h> + #include "mozilla/Logging.h" #include <android/log.h> #define GMDD_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkMediaDataDecoder", __VA_ARGS__) +#define INPUT_TIMEOUT_US 0LL // Don't wait for buffer if none is available. +#define MIN_QUEUED_SAMPLES 2 extern PRLogModuleInfo* GetPDMLog(); #define LOG(...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, (__VA_ARGS__)) using namespace android; namespace mozilla { +bool +GonkDecoderManager::InitLoopers(MediaData::Type aType) +{ + MOZ_ASSERT(mDecodeLooper.get() == nullptr && mTaskLooper.get() == nullptr); + MOZ_ASSERT(aType == MediaData::VIDEO_DATA || aType == MediaData::AUDIO_DATA); + + const char* suffix = (aType == MediaData::VIDEO_DATA ? "video" : "audio"); + mDecodeLooper = new ALooper; + android::AString name("MediaCodecProxy/"); + name.append(suffix); + mDecodeLooper->setName(name.c_str()); + + mTaskLooper = new ALooper; + name.setTo("GonkDecoderManager/"); + name.append(suffix); + mTaskLooper->setName(name.c_str()); + mTaskLooper->registerHandler(this); + + return mDecodeLooper->start() == OK && mTaskLooper->start() == OK; +} + +nsresult +GonkDecoderManager::Input(MediaRawData* aSample) +{ + nsRefPtr<MediaRawData> sample; + + if (aSample) { + sample = aSample; + } else { + // It means EOS with empty sample. + sample = new MediaRawData(); + } + { + MutexAutoLock lock(mMutex); + mQueuedSamples.AppendElement(sample); + } + + sp<AMessage> input = new AMessage(kNotifyProcessInput, id()); + if (!aSample) { + input->setInt32("input-eos", 1); + } + input->post(); + return NS_OK; +} + +int32_t +GonkDecoderManager::ProcessQueuedSamples() +{ + MutexAutoLock lock(mMutex); + status_t rv; + while (mQueuedSamples.Length()) { + nsRefPtr<MediaRawData> data = mQueuedSamples.ElementAt(0); + { + rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->Data()), + data->Size(), + data->mTime, + 0, + INPUT_TIMEOUT_US); + } + if (rv == OK) { + mQueuedSamples.RemoveElementAt(0); + mWaitOutput.AppendElement(data->mOffset); + } else if (rv == -EAGAIN || rv == -ETIMEDOUT) { + // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill + // buffer on time. + break; + } else { + return rv; + } + } + return mQueuedSamples.Length(); +} + +nsresult +GonkDecoderManager::Flush() +{ + if (mDecoder == nullptr) { + GMDD_LOG("Decoder is not initialized"); + return NS_ERROR_UNEXPECTED; + } + { + MutexAutoLock lock(mMutex); + mQueuedSamples.Clear(); + } + + mLastTime = 0; + + MonitorAutoLock lock(mFlushMonitor); + mIsFlushing = true; + sp<AMessage> flush = new AMessage(kNotifyProcessFlush, id()); + flush->post(); + while (mIsFlushing) { + lock.Wait(); + } + return NS_OK; +} + nsresult GonkDecoderManager::Shutdown() { if (mDecoder.get()) { mDecoder->stop(); mDecoder->ReleaseMediaResources(); mDecoder = nullptr; } mInitPromise.RejectIfExists(DecoderFailureReason::CANCELED, __func__); return NS_OK; } +bool +GonkDecoderManager::HasQueuedSample() +{ + MutexAutoLock lock(mMutex); + return mQueuedSamples.Length(); +} + +void +GonkDecoderManager::ProcessInput(bool aEndOfStream) +{ + status_t rv = ProcessQueuedSamples(); + if (rv >= 0) { + if (!aEndOfStream && rv <= MIN_QUEUED_SAMPLES) { + mDecodeCallback->InputExhausted(); + } + + if (mToDo.get() == nullptr) { + mToDo = new AMessage(kNotifyDecoderActivity, id()); + if (aEndOfStream) { + mToDo->setInt32("input-eos", 1); + } + mDecoder->requestActivityNotification(mToDo); + } + } else { + GMDD_LOG("input processed: error#%d", rv); + mDecodeCallback->Error(); + } +} + +void +GonkDecoderManager::ProcessFlush() +{ + MonitorAutoLock lock(mFlushMonitor); + mWaitOutput.Clear(); + if (mDecoder->flush() != OK) { + GMDD_LOG("flush error"); + mDecodeCallback->Error(); + } + mIsFlushing = false; + lock.NotifyAll(); +} + +void +GonkDecoderManager::ProcessToDo(bool aEndOfStream) +{ + MOZ_ASSERT(mToDo.get() != nullptr); + mToDo.clear(); + + if (HasQueuedSample()) { + status_t pendingInput = ProcessQueuedSamples(); + if (pendingInput < 0) { + mDecodeCallback->Error(); + return; + } + if (!aEndOfStream && pendingInput <= MIN_QUEUED_SAMPLES) { + mDecodeCallback->InputExhausted(); + } + } + + nsresult rv = NS_OK; + while (mWaitOutput.Length() > 0) { + nsRefPtr<MediaData> output; + int64_t offset = mWaitOutput.ElementAt(0); + rv = Output(offset, output); + if (rv == NS_OK) { + mWaitOutput.RemoveElementAt(0); + mDecodeCallback->Output(output); + } else if (rv == NS_ERROR_ABORT) { + GMDD_LOG("eos output"); + mWaitOutput.RemoveElementAt(0); + MOZ_ASSERT(mQueuedSamples.IsEmpty()); + MOZ_ASSERT(mWaitOutput.IsEmpty()); + // EOS + if (output) { + mDecodeCallback->Output(output); + } + mDecodeCallback->DrainComplete(); + return; + } else if (rv == NS_ERROR_NOT_AVAILABLE) { + break; + } else { + mDecodeCallback->Error(); + return; + } + } + + if (HasQueuedSample() || mWaitOutput.Length() > 0) { + mToDo = new AMessage(kNotifyDecoderActivity, id()); + mDecoder->requestActivityNotification(mToDo); + } +} + +void +GonkDecoderManager::onMessageReceived(const sp<AMessage> &aMessage) +{ + switch (aMessage->what()) { + case kNotifyProcessInput: + { + int32_t eos = 0; + ProcessInput(aMessage->findInt32("input-eos", &eos) && eos); + break; + } + case kNotifyProcessFlush: + { + ProcessFlush(); + break; + } + case kNotifyDecoderActivity: + { + int32_t eos = 0; + ProcessToDo(aMessage->findInt32("input-eos", &eos) && eos); + break; + } + default: + { + TRESPASS(); + break; + } + } +} + GonkMediaDataDecoder::GonkMediaDataDecoder(GonkDecoderManager* aManager, FlushableTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback) : mTaskQueue(aTaskQueue) - , mCallback(aCallback) , mManager(aManager) - , mSignaledEOS(false) - , mDrainComplete(false) { MOZ_COUNT_CTOR(GonkMediaDataDecoder); + mManager->SetDecodeCallback(aCallback); } GonkMediaDataDecoder::~GonkMediaDataDecoder() { MOZ_COUNT_DTOR(GonkMediaDataDecoder); } nsRefPtr<MediaDataDecoder::InitPromise> GonkMediaDataDecoder::Init() { - mDrainComplete = false; - - return mManager->Init(mCallback); + return mManager->Init(); } nsresult GonkMediaDataDecoder::Shutdown() { nsresult rv = mManager->Shutdown(); // Because codec allocated runnable and init promise is at reader TaskQueue, @@ -68,112 +286,31 @@ GonkMediaDataDecoder::Shutdown() mManager = nullptr; return rv; } // Inserts data into the decoder's pipeline. nsresult GonkMediaDataDecoder::Input(MediaRawData* aSample) { - nsCOMPtr<nsIRunnable> runnable( - NS_NewRunnableMethodWithArg<nsRefPtr<MediaRawData>>( - this, - &GonkMediaDataDecoder::ProcessDecode, - nsRefPtr<MediaRawData>(aSample))); - mTaskQueue->Dispatch(runnable.forget()); + mManager->Input(aSample); return NS_OK; } -void -GonkMediaDataDecoder::ProcessDecode(MediaRawData* aSample) -{ - nsresult rv = mManager->Input(aSample); - if (rv != NS_OK) { - NS_WARNING("GonkMediaDataDecoder failed to input data"); - GMDD_LOG("Failed to input data err: %d",int(rv)); - mCallback->Error(); - return; - } - if (aSample) { - mLastStreamOffset = aSample->mOffset; - } - ProcessOutput(); -} - -void -GonkMediaDataDecoder::ProcessOutput() -{ - nsRefPtr<MediaData> output; - nsresult rv = NS_ERROR_ABORT; - - while (!mDrainComplete) { - // There are samples in queue, try to send them into decoder when EOS. - if (mSignaledEOS && mManager->HasQueuedSample()) { - GMDD_LOG("ProcessOutput: drain all input samples"); - rv = mManager->Input(nullptr); - } - rv = mManager->Output(mLastStreamOffset, output); - if (rv == NS_OK) { - mCallback->Output(output); - continue; - } else if (rv == NS_ERROR_NOT_AVAILABLE && mSignaledEOS) { - // Try to get more frames before getting EOS frame - continue; - } - else { - break; - } - } - - if (rv == NS_ERROR_NOT_AVAILABLE && !mSignaledEOS) { - mCallback->InputExhausted(); - return; - } - if (rv != NS_OK) { - NS_WARNING("GonkMediaDataDecoder failed to output data"); - GMDD_LOG("Failed to output data"); - // GonkDecoderManangers report NS_ERROR_ABORT when EOS is reached. - if (rv == NS_ERROR_ABORT) { - if (output) { - mCallback->Output(output); - } - mCallback->DrainComplete(); - MOZ_ASSERT_IF(mSignaledEOS, !mManager->HasQueuedSample()); - mSignaledEOS = false; - mDrainComplete = true; - return; - } - GMDD_LOG("Callback error!"); - mCallback->Error(); - } -} - nsresult GonkMediaDataDecoder::Flush() { // Flush the input task queue. This cancels all pending Decode() calls. // Note this blocks until the task queue finishes its current job, if // it's executing at all. Note the MP4Reader ignores all output while // flushing. mTaskQueue->Flush(); - mDrainComplete = false; return mManager->Flush(); } -void -GonkMediaDataDecoder::ProcessDrain() -{ - // Notify decoder input EOS by sending a null data. - ProcessDecode(nullptr); - mSignaledEOS = true; - ProcessOutput(); -} - nsresult GonkMediaDataDecoder::Drain() { - nsCOMPtr<nsIRunnable> runnable = - NS_NewRunnableMethod(this, &GonkMediaDataDecoder::ProcessDrain); - mTaskQueue->Dispatch(runnable.forget()); + mManager->Input(nullptr); return NS_OK; } } // namespace mozilla
--- a/dom/media/platforms/gonk/GonkMediaDataDecoder.h +++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.h @@ -2,66 +2,128 @@ /* 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/. */ #if !defined(GonkMediaDataDecoder_h_) #define GonkMediaDataDecoder_h_ #include "PlatformDecoderModule.h" +#include <stagefright/foundation/AHandler.h> namespace android { +struct ALooper; class MediaCodecProxy; } // namespace android namespace mozilla { class MediaRawData; // Manage the data flow from inputting encoded data and outputting decode data. -class GonkDecoderManager { +class GonkDecoderManager : public android::AHandler { public: typedef TrackInfo::TrackType TrackType; typedef MediaDataDecoder::InitPromise InitPromise; typedef MediaDataDecoder::DecoderFailureReason DecoderFailureReason; virtual ~GonkDecoderManager() {} - virtual nsRefPtr<InitPromise> Init(MediaDataDecoderCallback* aCallback) = 0; - - // Add samples into OMX decoder or queue them if decoder is out of input buffer. - virtual nsresult Input(MediaRawData* aSample) = 0; + virtual nsRefPtr<InitPromise> Init() = 0; - // Produces decoded output, it blocks until output can be produced or a timeout - // is expired or until EOS. Returns NS_OK on success, or NS_ERROR_NOT_AVAILABLE - // if there's not enough data to produce more output. If this returns a failure - // code other than NS_ERROR_NOT_AVAILABLE, an error will be reported to the - // MP4Reader. - // The overrided class should follow the same behaviour. - virtual nsresult Output(int64_t aStreamOffset, - nsRefPtr<MediaData>& aOutput) = 0; + // Asynchronously send sample into mDecoder. If out of input buffer, aSample + // will be queued for later re-send. + nsresult Input(MediaRawData* aSample); // Flush the queued sample. - virtual nsresult Flush() = 0; + nsresult Flush(); // Shutdown decoder and rejects the init promise. nsresult Shutdown(); // True if sample is queued. - virtual bool HasQueuedSample() = 0; + bool HasQueuedSample(); + + // Set callback for decoder events, such as requesting more input, + // returning output, or reporting error. + void SetDecodeCallback(MediaDataDecoderCallback* aCallback) + { + mDecodeCallback = aCallback; + } protected: + GonkDecoderManager() + : mMutex("GonkDecoderManager") + , mLastTime(0) + , mFlushMonitor("GonkDecoderManager::Flush") + , mIsFlushing(false) + , mDecodeCallback(nullptr) + {} + + bool InitLoopers(MediaData::Type aType); + + void onMessageReceived(const android::sp<android::AMessage> &aMessage) override; + + // Produces decoded output. It returns NS_OK on success, or NS_ERROR_NOT_AVAILABLE + // when output is not produced yet. + // If this returns a failure code other than NS_ERROR_NOT_AVAILABLE, an error + // will be reported through mDecodeCallback. + virtual nsresult Output(int64_t aStreamOffset, + nsRefPtr<MediaData>& aOutput) = 0; + + // Send queued samples to OMX. It returns how many samples are still in + // queue after processing, or negtive error code if failed. + int32_t ProcessQueuedSamples(); + + void ProcessInput(bool aEndOfStream); + void ProcessFlush(); + void ProcessToDo(bool aEndOfStream); + nsRefPtr<MediaByteBuffer> mCodecSpecificData; nsAutoCString mMimeType; // MediaCodedc's wrapper that performs the decoding. android::sp<android::MediaCodecProxy> mDecoder; + // Looper for mDecoder to run on. + android::sp<android::ALooper> mDecodeLooper; + // Looper to run decode tasks such as processing input, output, flush, and + // recycling output buffers. + android::sp<android::ALooper> mTaskLooper; + enum { + // Decoder will send this to indicate internal state change such as input or + // output buffers availability. Used to run pending input & output tasks. + kNotifyDecoderActivity = 'nda ', + // Signal the decoder to flush. + kNotifyProcessFlush = 'npf ', + // Used to process queued samples when there is new input. + kNotifyProcessInput = 'npi ', + }; MozPromiseHolder<InitPromise> mInitPromise; + Mutex mMutex; // Protects mQueuedSamples. + // A queue that stores the samples waiting to be sent to mDecoder. + // Empty element means EOS and there shouldn't be any sample be queued after it. + // Samples are queued in caller's thread and dequeued in mTaskLooper. + nsTArray<nsRefPtr<MediaRawData>> mQueuedSamples; + + int64_t mLastTime; // The last decoded frame presentation time. + + Monitor mFlushMonitor; // Waits for flushing to complete. + bool mIsFlushing; + + // Remembers the notification that is currently waiting for the decoder event + // to avoid requesting more than one notification at the time, which is + // forbidden by mDecoder. + android::sp<android::AMessage> mToDo; + + // Stores the offset of every output that needs to be read from mDecoder. + nsTArray<int64_t> mWaitOutput; + + MediaDataDecoderCallback* mDecodeCallback; // Reports decoder output or error. }; // Samples are decoded using the GonkDecoder (MediaCodec) // created by the GonkDecoderManager. This class implements // the higher-level logic that drives mapping the Gonk to the async // MediaDataDecoder interface. The specifics of decoding the exact stream // type are handled by GonkDecoderManager and the GonkDecoder it creates. class GonkMediaDataDecoder : public MediaDataDecoder { @@ -78,40 +140,16 @@ public: nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; private: - - // Called on the task queue. Inserts the sample into the decoder, and - // extracts output if available, if aSample is null, it means there is - // no data from source, it will notify the decoder EOS and flush all the - // decoded frames. - void ProcessDecode(MediaRawData* aSample); - - // Called on the task queue. Extracts output if available, and delivers - // it to the reader. Called after ProcessDecode() and ProcessDrain(). - void ProcessOutput(); + RefPtr<FlushableTaskQueue> mTaskQueue; - // Called on the task queue. Orders the Gonk to drain, and then extracts - // all available output. - void ProcessDrain(); - - RefPtr<FlushableTaskQueue> mTaskQueue; - MediaDataDecoderCallback* mCallback; - - nsAutoPtr<GonkDecoderManager> mManager; - - // The last offset into the media resource that was passed into Input(). - // This is used to approximate the decoder's position in the media resource. - int64_t mLastStreamOffset; - // Set it ture when there is no input data - bool mSignaledEOS; - // Set if there is no more output data from decoder - bool mDrainComplete; + android::sp<GonkDecoderManager> mManager; }; } // namespace mozilla #endif // GonkMediaDataDecoder_h_
--- a/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp +++ b/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp @@ -12,76 +12,70 @@ #include "ImageContainer.h" #include "VideoUtils.h" #include "nsThreadUtils.h" #include "Layers.h" #include "mozilla/Logging.h" #include "stagefright/MediaBuffer.h" #include "stagefright/MetaData.h" #include "stagefright/MediaErrors.h" -#include <stagefright/foundation/ADebug.h> -#include <stagefright/foundation/AMessage.h> #include <stagefright/foundation/AString.h> -#include <stagefright/foundation/ALooper.h> #include "GonkNativeWindow.h" #include "GonkNativeWindowClient.h" #include "mozilla/layers/GrallocTextureClient.h" #include "mozilla/layers/TextureClient.h" #include <cutils/properties.h> -#define READ_OUTPUT_BUFFER_TIMEOUT_US 3000 +#define CODECCONFIG_TIMEOUT_US 10000LL +#define READ_OUTPUT_BUFFER_TIMEOUT_US 0LL #include <android/log.h> #define GVDM_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkVideoDecoderManager", __VA_ARGS__) extern PRLogModuleInfo* GetPDMLog(); #define LOG(...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, (__VA_ARGS__)) using namespace mozilla::layers; using namespace android; typedef android::MediaCodecProxy MediaCodecProxy; namespace mozilla { GonkVideoDecoderManager::GonkVideoDecoderManager( mozilla::layers::ImageContainer* aImageContainer, const VideoInfo& aConfig) : mImageContainer(aImageContainer) - , mReaderCallback(nullptr) - , mLastDecodedTime(0) , mColorConverterBufferSize(0) , mNativeWindow(nullptr) , mPendingReleaseItemsLock("GonkVideoDecoderManager::mPendingReleaseItemsLock") - , mMonitor("GonkVideoDecoderManager") { MOZ_COUNT_CTOR(GonkVideoDecoderManager); mMimeType = aConfig.mMimeType; mVideoWidth = aConfig.mDisplay.width; mVideoHeight = aConfig.mDisplay.height; mDisplayWidth = aConfig.mDisplay.width; mDisplayHeight = aConfig.mDisplay.height; mInfo.mVideo = aConfig; mCodecSpecificData = aConfig.mCodecSpecificConfig; nsIntRect pictureRect(0, 0, mVideoWidth, mVideoHeight); nsIntSize frameSize(mVideoWidth, mVideoHeight); mPicture = pictureRect; mInitialFrame = frameSize; - mHandler = new MessageHandler(this); mVideoListener = new VideoResourceListener(this); } GonkVideoDecoderManager::~GonkVideoDecoderManager() { mVideoListener->NotifyManagerRelease(); MOZ_COUNT_DTOR(GonkVideoDecoderManager); } nsRefPtr<MediaDataDecoder::InitPromise> -GonkVideoDecoderManager::Init(MediaDataDecoderCallback* aCallback) +GonkVideoDecoderManager::Init() { nsIntSize displaySize(mDisplayWidth, mDisplayHeight); nsIntRect pictureRect(0, 0, mVideoWidth, mVideoHeight); uint32_t maxWidth, maxHeight; char propValue[PROPERTY_VALUE_MAX]; property_get("ro.moz.omx.hw.max_width", propValue, "-1"); maxWidth = -1 == atoi(propValue) ? MAX_VIDEO_WIDTH : atoi(propValue); @@ -96,94 +90,41 @@ GonkVideoDecoderManager::Init(MediaDataD // Validate the container-reported frame and pictureRect sizes. This ensures // that our video frame creation code doesn't overflow. nsIntSize frameSize(mVideoWidth, mVideoHeight); if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) { GVDM_LOG("It is not a valid region"); return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); } - mReaderCallback = aCallback; - mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue(); MOZ_ASSERT(mReaderTaskQueue); - if (mLooper.get() != nullptr) { + if (mDecodeLooper.get() != nullptr) { return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); } - // Create ALooper - mLooper = new ALooper; - mManagerLooper = new ALooper; - mManagerLooper->setName("GonkVideoDecoderManager"); - // Register AMessage handler to ALooper. - mManagerLooper->registerHandler(mHandler); - // Start ALooper thread. - if (mLooper->start() != OK || mManagerLooper->start() != OK ) { + + if (!InitLoopers(MediaData::VIDEO_DATA)) { return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); } + nsRefPtr<InitPromise> p = mInitPromise.Ensure(__func__); - mDecoder = MediaCodecProxy::CreateByType(mLooper, mMimeType.get(), false, mVideoListener); + mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mMimeType.get(), false, mVideoListener); mDecoder->AsyncAskMediaCodec(); uint32_t capability = MediaCodecProxy::kEmptyCapability; if (mDecoder->getCapability(&capability) == OK && (capability & MediaCodecProxy::kCanExposeGraphicBuffer)) { mNativeWindow = new GonkNativeWindow(); } return p; } nsresult -GonkVideoDecoderManager::Input(MediaRawData* aSample) -{ - MonitorAutoLock mon(mMonitor); - nsRefPtr<MediaRawData> sample; - - if (!aSample) { - // It means EOS with empty sample. - sample = new MediaRawData(); - } else { - sample = aSample; - } - - mQueueSample.AppendElement(sample); - - status_t rv; - while (mQueueSample.Length()) { - nsRefPtr<MediaRawData> data = mQueueSample.ElementAt(0); - { - MonitorAutoUnlock mon_unlock(mMonitor); - rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->Data()), - data->Size(), - data->mTime, - 0); - } - if (rv == OK) { - mQueueSample.RemoveElementAt(0); - } else if (rv == -EAGAIN || rv == -ETIMEDOUT) { - // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill - // buffer on time. - return NS_OK; - } else { - return NS_ERROR_UNEXPECTED; - } - } - - return NS_OK; -} - -bool -GonkVideoDecoderManager::HasQueuedSample() -{ - MonitorAutoLock mon(mMonitor); - return mQueueSample.Length(); -} - -nsresult GonkVideoDecoderManager::CreateVideoData(int64_t aStreamOffset, VideoData **v) { *v = nullptr; nsRefPtr<VideoData> data; int64_t timeUs; int32_t keyFrame; if (mVideoBuffer == nullptr) { @@ -192,22 +133,22 @@ GonkVideoDecoderManager::CreateVideoData } if (!mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) { ReleaseVideoBuffer(); GVDM_LOG("Decoder did not return frame time"); return NS_ERROR_UNEXPECTED; } - if (mLastDecodedTime > timeUs) { + if (mLastTime > timeUs) { ReleaseVideoBuffer(); GVDM_LOG("Output decoded sample time is revert. time=%lld", timeUs); return NS_ERROR_NOT_AVAILABLE; } - mLastDecodedTime = timeUs; + mLastTime = timeUs; if (mVideoBuffer->range_length() == 0) { // Some decoders may return spurious empty buffers that we just want to ignore // quoted from Android's AwesomePlayer.cpp ReleaseVideoBuffer(); return NS_ERROR_NOT_AVAILABLE; } @@ -362,37 +303,16 @@ GonkVideoDecoderManager::SetVideoFormat( return false; } return true; } GVDM_LOG("Fail to get output format"); return false; } -nsresult -GonkVideoDecoderManager::Flush() -{ - if (mDecoder == nullptr) { - GVDM_LOG("Decoder is not inited"); - return NS_ERROR_UNEXPECTED; - } - { - MonitorAutoLock mon(mMonitor); - mQueueSample.Clear(); - } - - mLastDecodedTime = 0; - - if (mDecoder->flush() != OK) { - return NS_ERROR_FAILURE; - } - - return NS_OK; -} - // Blocks until decoded sample is produced by the deoder. nsresult GonkVideoDecoderManager::Output(int64_t aStreamOffset, nsRefPtr<MediaData>& aOutData) { aOutData = nullptr; status_t err; if (mDecoder == nullptr) { @@ -492,17 +412,18 @@ GonkVideoDecoderManager::codecReserved() surface = new Surface(mNativeWindow->getBufferQueue()); } mDecoder->configure(format, surface, nullptr, 0); mDecoder->Prepare(); if (mMimeType.EqualsLiteral("video/mp4v-es")) { rv = mDecoder->Input(mCodecSpecificData->Elements(), mCodecSpecificData->Length(), 0, - android::MediaCodec::BUFFER_FLAG_CODECCONFIG); + android::MediaCodec::BUFFER_FLAG_CODECCONFIG, + CODECCONFIG_TIMEOUT_US); } if (rv != OK) { GVDM_LOG("Failed to configure codec!!!!"); mInitPromise.Reject(DecoderFailureReason::INIT_ERROR, __func__); return; } @@ -511,48 +432,32 @@ GonkVideoDecoderManager::codecReserved() void GonkVideoDecoderManager::codecCanceled() { GVDM_LOG("codecCanceled"); mInitPromise.RejectIfExists(DecoderFailureReason::CANCELED, __func__); } -// Called on GonkVideoDecoderManager::mManagerLooper thread. +// Called on GonkDecoderManager::mTaskLooper thread. void GonkVideoDecoderManager::onMessageReceived(const sp<AMessage> &aMessage) { switch (aMessage->what()) { case kNotifyPostReleaseBuffer: { ReleaseAllPendingVideoBuffers(); break; } default: - TRESPASS(); + { + GonkDecoderManager::onMessageReceived(aMessage); break; - } -} - -GonkVideoDecoderManager::MessageHandler::MessageHandler(GonkVideoDecoderManager *aManager) - : mManager(aManager) -{ -} - -GonkVideoDecoderManager::MessageHandler::~MessageHandler() -{ - mManager = nullptr; -} - -void -GonkVideoDecoderManager::MessageHandler::onMessageReceived(const android::sp<android::AMessage> &aMessage) -{ - if (mManager != nullptr) { - mManager->onMessageReceived(aMessage); + } } } GonkVideoDecoderManager::VideoResourceListener::VideoResourceListener(GonkVideoDecoderManager *aManager) : mManager(aManager) { } @@ -647,17 +552,17 @@ void GonkVideoDecoderManager::PostReleas { { MutexAutoLock autoLock(mPendingReleaseItemsLock); if (aBuffer) { mPendingReleaseItems.AppendElement(ReleaseItem(aBuffer, aReleaseFence)); } } sp<AMessage> notify = - new AMessage(kNotifyPostReleaseBuffer, mHandler->id()); + new AMessage(kNotifyPostReleaseBuffer, id()); notify->post(); } void GonkVideoDecoderManager::ReleaseAllPendingVideoBuffers() { nsTArray<ReleaseItem> releasingItems; {
--- a/dom/media/platforms/gonk/GonkVideoDecoderManager.h +++ b/dom/media/platforms/gonk/GonkVideoDecoderManager.h @@ -2,32 +2,29 @@ /* 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/. */ #if !defined(GonkVideoDecoderManager_h_) #define GonkVideoDecoderManager_h_ -#include <set> #include "nsRect.h" #include "GonkMediaDataDecoder.h" #include "mozilla/RefPtr.h" #include "I420ColorConverterHelper.h" #include "MediaCodecProxy.h" -#include <stagefright/foundation/AHandler.h> #include "GonkNativeWindow.h" #include "GonkNativeWindowClient.h" #include "mozilla/layers/FenceUtils.h" #include <ui/Fence.h> using namespace android; namespace android { -struct ALooper; class MediaBuffer; struct MOZ_EXPORT AString; class GonkNativeWindow; } // namespace android namespace mozilla { namespace layers { @@ -37,61 +34,40 @@ class TextureClient; class GonkVideoDecoderManager : public GonkDecoderManager { typedef android::MediaCodecProxy MediaCodecProxy; typedef mozilla::layers::TextureClient TextureClient; public: GonkVideoDecoderManager(mozilla::layers::ImageContainer* aImageContainer, const VideoInfo& aConfig); - virtual ~GonkVideoDecoderManager() override; + virtual ~GonkVideoDecoderManager(); - nsRefPtr<InitPromise> Init(MediaDataDecoderCallback* aCallback) override; - - nsresult Input(MediaRawData* aSample) override; + nsRefPtr<InitPromise> Init() override; nsresult Output(int64_t aStreamOffset, nsRefPtr<MediaData>& aOutput) override; - nsresult Flush() override; - - bool HasQueuedSample() override; - static void RecycleCallback(TextureClient* aClient, void* aClosure); private: struct FrameInfo { int32_t mWidth = 0; int32_t mHeight = 0; int32_t mStride = 0; int32_t mSliceHeight = 0; int32_t mColorFormat = 0; int32_t mCropLeft = 0; int32_t mCropTop = 0; int32_t mCropRight = 0; int32_t mCropBottom = 0; }; - class MessageHandler : public android::AHandler - { - public: - MessageHandler(GonkVideoDecoderManager *aManager); - ~MessageHandler(); - void onMessageReceived(const android::sp<android::AMessage> &aMessage) override; - - private: - // Forbidden - MessageHandler() = delete; - MessageHandler(const MessageHandler &rhs) = delete; - const MessageHandler &operator=(const MessageHandler &rhs) = delete; - - GonkVideoDecoderManager *mManager; - }; - friend class MessageHandler; + void onMessageReceived(const android::sp<android::AMessage> &aMessage) override; class VideoResourceListener : public android::MediaCodecProxy::CodecResourceListener { public: VideoResourceListener(GonkVideoDecoderManager *aManager); ~VideoResourceListener(); void codecReserved() override; @@ -114,43 +90,36 @@ private: nsresult CreateVideoData(int64_t aStreamOffset, VideoData** aOutData); void ReleaseVideoBuffer(); uint8_t* GetColorConverterBuffer(int32_t aWidth, int32_t aHeight); // For codec resource management void codecReserved(); void codecCanceled(); - void onMessageReceived(const sp<AMessage> &aMessage); void ReleaseAllPendingVideoBuffers(); void PostReleaseVideoBuffer(android::MediaBuffer *aBuffer, layers::FenceHandle mReleaseFence); uint32_t mVideoWidth; uint32_t mVideoHeight; uint32_t mDisplayWidth; uint32_t mDisplayHeight; nsIntRect mPicture; nsIntSize mInitialFrame; nsRefPtr<layers::ImageContainer> mImageContainer; android::MediaBuffer* mVideoBuffer; - MediaDataDecoderCallback* mReaderCallback; MediaInfo mInfo; android::sp<VideoResourceListener> mVideoListener; - android::sp<MessageHandler> mHandler; - android::sp<ALooper> mLooper; - android::sp<ALooper> mManagerLooper; FrameInfo mFrameInfo; - int64_t mLastDecodedTime; // The last decoded frame presentation time. - // color converter android::I420ColorConverterHelper mColorConverter; nsAutoArrayPtr<uint8_t> mColorConverterBuffer; size_t mColorConverterBufferSize; android::sp<android::GonkNativeWindow> mNativeWindow; enum { kNotifyPostReleaseBuffer = 'nprb', @@ -163,24 +132,16 @@ private: android::MediaBuffer* mBuffer; layers::FenceHandle mReleaseFence; }; nsTArray<ReleaseItem> mPendingReleaseItems; // The lock protects mPendingReleaseItems. Mutex mPendingReleaseItemsLock; - // This monitor protects mQueueSample. - Monitor mMonitor; - - // This TaskQueue should be the same one in mReaderCallback->OnReaderTaskQueue(). + // This TaskQueue should be the same one in mDecodeCallback->OnReaderTaskQueue(). // It is for codec resource mangement, decoding task should not dispatch to it. nsRefPtr<TaskQueue> mReaderTaskQueue; - - // An queue with the MP4 samples which are waiting to be sent into OMX. - // If an element is an empty MP4Sample, that menas EOS. There should not - // any sample be queued after EOS. - nsTArray<nsRefPtr<MediaRawData>> mQueueSample; }; } // namespace mozilla #endif // GonkVideoDecoderManager_h_
--- a/dom/media/webaudio/WaveShaperNode.cpp +++ b/dom/media/webaudio/WaveShaperNode.cpp @@ -225,36 +225,44 @@ public: // Optimize the case where we don't have a curve buffer, // or the input is null. *aOutput = aInput; return; } aOutput->AllocateChannels(channelCount); for (uint32_t i = 0; i < channelCount; ++i) { - float* scaledSample = (float *)(aInput.mChannelData[i]); - AudioBlockInPlaceScale(scaledSample, aInput.mVolume); - const float* inputBuffer = static_cast<const float*>(scaledSample); + const float* inputSamples; + float scaledInput[WEBAUDIO_BLOCK_SIZE]; + if (aInput.mVolume != 1.0f) { + AudioBlockCopyChannelWithScale( + static_cast<const float*>(aInput.mChannelData[i]), + aInput.mVolume, + scaledInput); + inputSamples = scaledInput; + } else { + inputSamples = static_cast<const float*>(aInput.mChannelData[i]); + } float* outputBuffer = aOutput->ChannelFloatsForWrite(i); float* sampleBuffer; switch (mType) { case OverSampleType::None: mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::None); - ProcessCurve<1>(inputBuffer, outputBuffer); + ProcessCurve<1>(inputSamples, outputBuffer); break; case OverSampleType::_2x: mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_2x); - sampleBuffer = mResampler.UpSample(i, inputBuffer, 2); + sampleBuffer = mResampler.UpSample(i, inputSamples, 2); ProcessCurve<2>(sampleBuffer, sampleBuffer); mResampler.DownSample(i, outputBuffer, 2); break; case OverSampleType::_4x: mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_4x); - sampleBuffer = mResampler.UpSample(i, inputBuffer, 4); + sampleBuffer = mResampler.UpSample(i, inputSamples, 4); ProcessCurve<4>(sampleBuffer, sampleBuffer); mResampler.DownSample(i, outputBuffer, 4); break; default: NS_NOTREACHED("We should never reach here"); } } }
--- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -167,11 +167,12 @@ skip-if = (toolkit == 'gonk' && !debug) [test_scriptProcessorNodePassThrough.html] [test_scriptProcessorNode_playbackTime1.html] [test_scriptProcessorNodeZeroInputOutput.html] [test_scriptProcessorNodeNotConnected.html] [test_singleSourceDest.html] [test_stereoPanningWithGain.html] [test_waveDecoder.html] [test_waveShaper.html] +[test_waveShaperGain.html] [test_waveShaperNoCurve.html] [test_waveShaperPassThrough.html] [test_waveShaperInvalidLengthCurve.html]
new file mode 100644 --- /dev/null +++ b/dom/media/webaudio/test/test_waveShaperGain.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> + <title>Test that WaveShaperNode doesn't corrupt its inputs when the gain is != + 1.0 (bug 1203616)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +var samplerate = 44100; +var context = new OfflineAudioContext(1, 44100, samplerate); + +var dc = context.createBufferSource(); + +var buffer = context.createBuffer(1, 1, samplerate); +buffer.getChannelData(0)[0] = 1.0; +dc.buffer = buffer; + +var gain = context.createGain(); +var ws2 = context.createWaveShaper(); +var ws = []; + +// No-op waveshaper curves. +for (var i = 0; i < 2; i++) { + ws[i] = context.createWaveShaper(); + var curve = new Float32Array(2); + curve[0] = -1.0; + curve[1] = 1.0; + ws[i].curve = curve; + ws[i].connect(context.destination); + gain.connect(ws[i]); +} + +dc.connect(gain); +dc.start(); + +gain.gain.value = 0.5; + +context.startRendering().then(buffer => { + document.querySelector("pre").innerHTML = buffer.getChannelData(0)[0]; + ok(buffer.getChannelData(0)[0] == 1.0, "Volume was handled properly"); + SimpleTest.finish(); +}); +</script> +<pre> +</pre> +</body> +
--- a/dom/media/webm/WebMDemuxer.cpp +++ b/dom/media/webm/WebMDemuxer.cpp @@ -457,19 +457,19 @@ WebMDemuxer::EnsureUpToDateIndex() if (!mIsMediaSource) { return; } mLastWebMBlockOffset = mBufferedState->GetLastBlockOffset(); MOZ_ASSERT(mLastWebMBlockOffset <= mResource.GetLength()); } void -WebMDemuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) +WebMDemuxer::NotifyDataArrived() { - WEBM_DEBUG("length: %ld offset: %ld", aLength, aOffset); + WEBM_DEBUG(""); mNeedReIndex = true; } void WebMDemuxer::NotifyDataRemoved() { mBufferedState->Reset(); if (mInitData) {
--- a/dom/media/webm/WebMDemuxer.h +++ b/dom/media/webm/WebMDemuxer.h @@ -124,17 +124,17 @@ public: private: friend class WebMTrackDemuxer; ~WebMDemuxer(); void Cleanup(); void InitBufferedState(); nsresult ReadMetadata(); - void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override; + void NotifyDataArrived() override; void NotifyDataRemoved() override; void EnsureUpToDateIndex(); media::TimeIntervals GetBuffered(); virtual nsresult SeekInternal(const media::TimeUnit& aTarget); // Read a packet from the nestegg file. Returns nullptr if all packets for // the particular track have been read. Pass TrackInfo::kVideoTrack or // TrackInfo::kVideoTrack to indicate the type of the packet we want to read.
--- a/dom/presentation/PresentationReceiver.cpp +++ b/dom/presentation/PresentationReceiver.cpp @@ -163,17 +163,17 @@ PresentationReceiver::NotifySessionConne const nsAString& aSessionId) { if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) { return NS_ERROR_INVALID_ARG; } nsRefPtr<PresentationSession> session = PresentationSession::Create(GetOwner(), aSessionId, - PresentationSessionState::Disconnected); + PresentationSessionState::Closed); if (NS_WARN_IF(!session)) { return NS_ERROR_NOT_AVAILABLE; } mSessions.AppendElement(session); // Resolve pending |GetSession| promises if any. if (!mPendingGetSessionPromises.IsEmpty()) { for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) {
--- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -424,27 +424,41 @@ PresentationService::SendSessionMessage( if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->Send(aStream); } NS_IMETHODIMP -PresentationService::Terminate(const nsAString& aSessionId) +PresentationService::CloseSession(const nsAString& aSessionId) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } - return info->Close(NS_OK); + return info->Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED); +} + +NS_IMETHODIMP +PresentationService::TerminateSession(const nsAString& aSessionId) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aSessionId.IsEmpty()); + + nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED); } NS_IMETHODIMP PresentationService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener) { MOZ_ASSERT(NS_IsMainThread()); if (NS_WARN_IF(mAvailabilityListeners.Contains(aListener))) { @@ -490,17 +504,17 @@ PresentationService::RegisterSessionList NS_IMETHODIMP PresentationService::UnregisterSessionListener(const nsAString& aSessionId) { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId); if (info) { - NS_WARN_IF(NS_FAILED(info->Close(NS_OK))); + NS_WARN_IF(NS_FAILED(info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED))); UntrackSessionInfo(aSessionId); return info->SetListener(nullptr); } return NS_OK; } NS_IMETHODIMP PresentationService::RegisterRespondingListener(uint64_t aWindowId,
--- a/dom/presentation/PresentationSession.cpp +++ b/dom/presentation/PresentationSession.cpp @@ -137,58 +137,71 @@ PresentationSession::Send(const nsAStrin if(NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsCOMPtr<nsIPresentationService> service = do_GetService(PRESENTATION_SERVICE_CONTRACTID); if(NS_WARN_IF(!service)) { - aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); return; } rv = service->SendSessionMessage(mId, stream); if(NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); } } void -PresentationSession::Close() +PresentationSession::Close(ErrorResult& aRv) { - // Closing does nothing if the session is already terminated. - if (NS_WARN_IF(mState == PresentationSessionState::Terminated)) { + // It only works when the state is CONNECTED. + if (NS_WARN_IF(mState != PresentationSessionState::Connected)) { + return; + } + + // TODO Bug 1210340 - Support close semantics. + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); +} + +void +PresentationSession::Terminate(ErrorResult& aRv) +{ + // It only works when the state is CONNECTED. + if (NS_WARN_IF(mState != PresentationSessionState::Connected)) { return; } nsCOMPtr<nsIPresentationService> service = do_GetService(PRESENTATION_SERVICE_CONTRACTID); if(NS_WARN_IF(!service)) { + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); return; } - NS_WARN_IF(NS_FAILED(service->Terminate(mId))); + NS_WARN_IF(NS_FAILED(service->TerminateSession(mId))); } NS_IMETHODIMP PresentationSession::NotifyStateChange(const nsAString& aSessionId, uint16_t aState) { if (!aSessionId.Equals(mId)) { return NS_ERROR_INVALID_ARG; } PresentationSessionState state; switch (aState) { case nsIPresentationSessionListener::STATE_CONNECTED: state = PresentationSessionState::Connected; break; - case nsIPresentationSessionListener::STATE_DISCONNECTED: - state = PresentationSessionState::Disconnected; + case nsIPresentationSessionListener::STATE_CLOSED: + state = PresentationSessionState::Closed; break; case nsIPresentationSessionListener::STATE_TERMINATED: state = PresentationSessionState::Terminated; break; default: NS_WARNING("Unknown presentation session state."); return NS_ERROR_INVALID_ARG; }
--- a/dom/presentation/PresentationSession.h +++ b/dom/presentation/PresentationSession.h @@ -18,34 +18,36 @@ class PresentationSession final : public , public nsIPresentationSessionListener { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationSession, DOMEventTargetHelper) NS_DECL_NSIPRESENTATIONSESSIONLISTENER - static already_AddRefed<PresentationSession> - Create(nsPIDOMWindow* aWindow, - const nsAString& aId, - PresentationSessionState aState); + static already_AddRefed<PresentationSession> Create(nsPIDOMWindow* aWindow, + const nsAString& aId, + PresentationSessionState aState); virtual void DisconnectFromOwner() override; virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; // WebIDL (public APIs) void GetId(nsAString& aId) const; PresentationSessionState State() const; - void Send(const nsAString& aData, ErrorResult& aRv); + void Send(const nsAString& aData, + ErrorResult& aRv); - void Close(); + void Close(ErrorResult& aRv); + + void Terminate(ErrorResult& aRv); IMPL_EVENT_HANDLER(statechange); IMPL_EVENT_HANDLER(message); private: PresentationSession(nsPIDOMWindow* aWindow, const nsAString& aId, PresentationSessionState aState);
--- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -169,20 +169,17 @@ PresentationSessionInfo::SetListener(nsI nsresult rv = mTransport->EnableDataNotification(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // The transport might become ready, or might become un-ready again, before // the listener has registered. So notify the listener of the state change. - uint16_t state = IsSessionReady() ? - nsIPresentationSessionListener::STATE_CONNECTED : - nsIPresentationSessionListener::STATE_DISCONNECTED; - return mListener->NotifyStateChange(mSessionId, state); + return mListener->NotifyStateChange(mSessionId, mState); } return NS_OK; } nsresult PresentationSessionInfo::Send(nsIInputStream* aData) { @@ -193,46 +190,33 @@ PresentationSessionInfo::Send(nsIInputSt if (NS_WARN_IF(!mTransport)) { return NS_ERROR_NOT_AVAILABLE; } return mTransport->Send(aData); } nsresult -PresentationSessionInfo::Close(nsresult aReason) +PresentationSessionInfo::Close(nsresult aReason, + uint32_t aState) { - // The session is disconnected and it's a normal close. Simply change the - // state to TERMINATED. - if (!IsSessionReady() && NS_SUCCEEDED(aReason)) { - if (mListener) { - // Notify the listener and the service will untrack the session info after - // the listener calls |UnregisterSessionListener|. - nsresult rv = mListener->NotifyStateChange(mSessionId, - nsIPresentationSessionListener::STATE_TERMINATED); - NS_WARN_IF(NS_FAILED(rv)); - } else { - // Directly untrack the session info from the service. - NS_WARN_IF(NS_FAILED(UntrackFromService())); - } + if (NS_WARN_IF(!IsSessionReady())) { + return NS_ERROR_DOM_INVALID_STATE_ERR; } + SetState(aState); + Shutdown(aReason); return NS_OK; } nsresult PresentationSessionInfo::ReplySuccess() { - if (mListener) { - // Notify session state change. - nsresult rv = mListener->NotifyStateChange(mSessionId, - nsIPresentationSessionListener::STATE_CONNECTED); - NS_WARN_IF(NS_FAILED(rv)); - } + SetState(nsIPresentationSessionListener::STATE_CONNECTED); if (mCallback) { NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess())); SetCallback(nullptr); } return NS_OK; } @@ -303,27 +287,24 @@ PresentationSessionInfo::NotifyTransport if (NS_WARN_IF(!IsSessionReady())) { // It happens before the session is ready. Reply the callback. return ReplyError(NS_ERROR_DOM_OPERATION_ERR); } // Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above. mIsTransportReady = false; + if (mState == nsIPresentationSessionListener::STATE_CONNECTED) { + // The transport channel is closed unexpectedly (not caused by a |Close| call). + SetState(nsIPresentationSessionListener::STATE_CLOSED); + } + Shutdown(aReason); - uint16_t state = (NS_WARN_IF(NS_FAILED(aReason))) ? - nsIPresentationSessionListener::STATE_DISCONNECTED : - nsIPresentationSessionListener::STATE_TERMINATED; - if (mListener) { - // It happens after the session is ready. Notify session state change. - // If the new state is TERMINATED. the service will untrack the session info - // after the listener calls |UnregisterSessionListener|. - return mListener->NotifyStateChange(mSessionId, state); - } else if (state == nsIPresentationSessionListener::STATE_TERMINATED) { + if (mState == nsIPresentationSessionListener::STATE_TERMINATED) { // Directly untrack the session info from the service. return UntrackFromService(); } return NS_OK; } NS_IMETHODIMP @@ -504,22 +485,19 @@ PresentationControllingInfo::NotifyClose { MOZ_ASSERT(NS_IsMainThread()); // Unset control channel here so it won't try to re-close it in potential // subsequent |Shutdown| calls. SetControlChannel(nullptr); if (NS_WARN_IF(NS_FAILED(aReason))) { - if (mListener) { - // The presentation session instance at receiver side may already exist. - // Change the state to TERMINATED since it never succeeds. - return mListener->NotifyStateChange(mSessionId, - nsIPresentationSessionListener::STATE_TERMINATED); - } + // The presentation session instance may already exist. + // Change the state to TERMINATED since it never succeeds. + SetState(nsIPresentationSessionListener::STATE_TERMINATED); // Reply error for an abnormal close. return ReplyError(NS_ERROR_DOM_OPERATION_ERR); } return NS_OK; } @@ -561,21 +539,18 @@ PresentationControllingInfo::OnStopListe Shutdown(aStatus); if (NS_WARN_IF(!IsSessionReady())) { // It happens before the session is ready. Reply the callback. return ReplyError(NS_ERROR_DOM_OPERATION_ERR); } - // It happens after the session is ready. Notify session state change. - if (mListener) { - return mListener->NotifyStateChange(mSessionId, - nsIPresentationSessionListener::STATE_DISCONNECTED); - } + // It happens after the session is ready. Change the state to CLOSED. + SetState(nsIPresentationSessionListener::STATE_CLOSED); return NS_OK; } /* * Implementation of PresentationPresentingInfo * * During presentation session establishment, the receiver expects the following @@ -770,22 +745,19 @@ PresentationPresentingInfo::NotifyClosed { MOZ_ASSERT(NS_IsMainThread()); // Unset control channel here so it won't try to re-close it in potential // subsequent |Shutdown| calls. SetControlChannel(nullptr); if (NS_WARN_IF(NS_FAILED(aReason))) { - if (mListener) { - // The presentation session instance at receiver side may already exist. - // Change the state to TERMINATED since it never succeeds. - return mListener->NotifyStateChange(mSessionId, - nsIPresentationSessionListener::STATE_TERMINATED); - } + // The presentation session instance may already exist. + // Change the state to TERMINATED since it never succeeds. + SetState(nsIPresentationSessionListener::STATE_TERMINATED); // Reply error for an abnormal close. return ReplyError(NS_ERROR_DOM_OPERATION_ERR); } return NS_OK; }
--- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -34,16 +34,17 @@ public: PresentationSessionInfo(const nsAString& aUrl, const nsAString& aSessionId, nsIPresentationServiceCallback* aCallback) : mUrl(aUrl) , mSessionId(aSessionId) , mIsResponderReady(false) , mIsTransportReady(false) + , mState(nsIPresentationSessionListener::STATE_CLOSED) , mCallback(aCallback) { MOZ_ASSERT(!mUrl.IsEmpty()); MOZ_ASSERT(!mSessionId.IsEmpty()); } virtual nsresult Init(nsIPresentationControlChannel* aControlChannel); @@ -84,17 +85,18 @@ public: mControlChannel = aControlChannel; if (mControlChannel) { mControlChannel->SetListener(this); } } nsresult Send(nsIInputStream* aData); - nsresult Close(nsresult aReason); + nsresult Close(nsresult aReason, + uint32_t aState); nsresult ReplyError(nsresult aReason); virtual bool IsAccessible(base::ProcessId aProcessId); protected: virtual ~PresentationSessionInfo() { @@ -107,20 +109,36 @@ protected: bool IsSessionReady() { return mIsResponderReady && mIsTransportReady; } virtual nsresult UntrackFromService(); + void SetState(uint32_t aState) + { + if (mState == aState) { + return; + } + + mState = aState; + + // Notify session state change. + if (mListener) { + nsresult rv = mListener->NotifyStateChange(mSessionId, mState); + NS_WARN_IF(NS_FAILED(rv)); + } + } + nsString mUrl; nsString mSessionId; bool mIsResponderReady; bool mIsTransportReady; + uint32_t mState; // CONNECTED, CLOSED, TERMINATED nsCOMPtr<nsIPresentationServiceCallback> mCallback; nsCOMPtr<nsIPresentationSessionListener> mListener; nsCOMPtr<nsIPresentationDevice> mDevice; nsCOMPtr<nsIPresentationSessionTransport> mTransport; nsCOMPtr<nsIPresentationControlChannel> mControlChannel; }; // Session info with controlling browsing context (sender side) behaviors.
--- a/dom/presentation/interfaces/nsIPresentationListener.idl +++ b/dom/presentation/interfaces/nsIPresentationListener.idl @@ -8,21 +8,21 @@ interface nsIPresentationAvailabilityListener : nsISupports { /* * Called when device availability changes. */ void notifyAvailableChange(in bool available); }; -[scriptable, uuid(3b9ae71f-2905-4969-9117-101627c1c2ea)] +[scriptable, uuid(7dd48df8-8f8c-48c7-ac37-7b9fd1acf2f8)] interface nsIPresentationSessionListener : nsISupports { const unsigned short STATE_CONNECTED = 0; - const unsigned short STATE_DISCONNECTED = 1; + const unsigned short STATE_CLOSED = 1; const unsigned short STATE_TERMINATED = 2; /* * Called when session state changes. */ void notifyStateChange(in DOMString sessionId, in unsigned short state);
--- a/dom/presentation/interfaces/nsIPresentationService.idl +++ b/dom/presentation/interfaces/nsIPresentationService.idl @@ -28,17 +28,17 @@ interface nsIPresentationServiceCallback /* * Called when the operation fails. * * @param error: error message. */ void notifyError(in nsresult error); }; -[scriptable, uuid(5a254519-45c3-4229-9b16-f58c05b78fc6)] +[scriptable, uuid(c177a13a-bf1a-48bf-8032-d415c3343c46)] interface nsIPresentationService : nsISupports { /* * Start a new presentation session and display a prompt box which asks users * to select a device. * * @param url: The url of presenting page. * @param sessionId: An ID to identify presentation session. @@ -58,21 +58,28 @@ interface nsIPresentationService : nsISu * * @param sessionId: An ID to identify presentation session. * @param stream: The message is converted to an input stream. */ void sendSessionMessage(in DOMString sessionId, in nsIInputStream stream); /* + * Close the session. + * + * @param sessionId: An ID to identify presentation session. + */ + void closeSession(in DOMString sessionId); + + /* * Terminate the session. * * @param sessionId: An ID to identify presentation session. */ - void terminate(in DOMString sessionId); + void terminateSession(in DOMString sessionId); /* * Register an availability listener. Must be called from the main thread. * * @param listener: The listener to register. */ void registerAvailabilityListener(in nsIPresentationAvailabilityListener listener);
--- a/dom/presentation/ipc/PPresentation.ipdl +++ b/dom/presentation/ipc/PPresentation.ipdl @@ -20,26 +20,32 @@ struct StartSessionRequest }; struct SendSessionMessageRequest { nsString sessionId; InputStreamParams data; }; -struct TerminateRequest +struct CloseSessionRequest +{ + nsString sessionId; +}; + +struct TerminateSessionRequest { nsString sessionId; }; union PresentationIPCRequest { StartSessionRequest; SendSessionMessageRequest; - TerminateRequest; + CloseSessionRequest; + TerminateSessionRequest; }; sync protocol PPresentation { manager PContent; manages PPresentationRequest; child:
--- a/dom/presentation/ipc/PresentationIPCService.cpp +++ b/dom/presentation/ipc/PresentationIPCService.cpp @@ -66,21 +66,29 @@ PresentationIPCService::SendSessionMessa nsTArray<mozilla::ipc::FileDescriptor> fds; SerializeInputStream(aStream, stream, fds); MOZ_ASSERT(fds.IsEmpty()); return SendRequest(nullptr, SendSessionMessageRequest(nsAutoString(aSessionId), stream)); } NS_IMETHODIMP -PresentationIPCService::Terminate(const nsAString& aSessionId) +PresentationIPCService::CloseSession(const nsAString& aSessionId) { MOZ_ASSERT(!aSessionId.IsEmpty()); - return SendRequest(nullptr, TerminateRequest(nsAutoString(aSessionId))); + return SendRequest(nullptr, CloseSessionRequest(nsAutoString(aSessionId))); +} + +NS_IMETHODIMP +PresentationIPCService::TerminateSession(const nsAString& aSessionId) +{ + MOZ_ASSERT(!aSessionId.IsEmpty()); + + return SendRequest(nullptr, TerminateSessionRequest(nsAutoString(aSessionId))); } nsresult PresentationIPCService::SendRequest(nsIPresentationServiceCallback* aCallback, const PresentationIPCRequest& aRequest) { if (sPresentationChild) { PresentationRequestChild* actor = new PresentationRequestChild(aCallback);
--- a/dom/presentation/ipc/PresentationParent.cpp +++ b/dom/presentation/ipc/PresentationParent.cpp @@ -69,18 +69,21 @@ PresentationParent::RecvPPresentationReq nsresult rv = NS_ERROR_FAILURE; switch (aRequest.type()) { case PresentationIPCRequest::TStartSessionRequest: rv = actor->DoRequest(aRequest.get_StartSessionRequest()); break; case PresentationIPCRequest::TSendSessionMessageRequest: rv = actor->DoRequest(aRequest.get_SendSessionMessageRequest()); break; - case PresentationIPCRequest::TTerminateRequest: - rv = actor->DoRequest(aRequest.get_TerminateRequest()); + case PresentationIPCRequest::TCloseSessionRequest: + rv = actor->DoRequest(aRequest.get_CloseSessionRequest()); + break; + case PresentationIPCRequest::TTerminateSessionRequest: + rv = actor->DoRequest(aRequest.get_TerminateSessionRequest()); break; default: MOZ_CRASH("Unknown PresentationIPCRequest type"); } return NS_WARN_IF(NS_FAILED(rv)) ? false : true; } @@ -273,28 +276,47 @@ PresentationRequestParent::DoRequest(con nsresult rv = mService->SendSessionMessage(aRequest.sessionId(), stream); if (NS_WARN_IF(NS_FAILED(rv))) { return NotifyError(rv); } return NotifySuccess(); } nsresult -PresentationRequestParent::DoRequest(const TerminateRequest& aRequest) +PresentationRequestParent::DoRequest(const CloseSessionRequest& aRequest) { MOZ_ASSERT(mService); // Validate the accessibility (primarily for receiver side) so that a // compromised child process can't fake the ID. if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())-> IsSessionAccessible(aRequest.sessionId(), OtherPid()))) { return NotifyError(NS_ERROR_DOM_SECURITY_ERR); } - nsresult rv = mService->Terminate(aRequest.sessionId()); + nsresult rv = mService->CloseSession(aRequest.sessionId()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NotifyError(rv); + } + return NotifySuccess(); +} + +nsresult +PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest) +{ + MOZ_ASSERT(mService); + + // Validate the accessibility (primarily for receiver side) so that a + // compromised child process can't fake the ID. + if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())-> + IsSessionAccessible(aRequest.sessionId(), OtherPid()))) { + return NotifyError(NS_ERROR_DOM_SECURITY_ERR); + } + + nsresult rv = mService->TerminateSession(aRequest.sessionId()); if (NS_WARN_IF(NS_FAILED(rv))) { return NotifyError(rv); } return NotifySuccess(); } NS_IMETHODIMP PresentationRequestParent::NotifySuccess()
--- a/dom/presentation/ipc/PresentationParent.h +++ b/dom/presentation/ipc/PresentationParent.h @@ -84,17 +84,19 @@ private: virtual ~PresentationRequestParent(); nsresult SendResponse(nsresult aResult); nsresult DoRequest(const StartSessionRequest& aRequest); nsresult DoRequest(const SendSessionMessageRequest& aRequest); - nsresult DoRequest(const TerminateRequest& aRequest); + nsresult DoRequest(const CloseSessionRequest& aRequest); + + nsresult DoRequest(const TerminateSessionRequest& aRequest); bool mActorDestroyed; nsCOMPtr<nsIPresentationService> mService; }; } // namespace dom } // namespace mozilla
--- a/dom/presentation/tests/mochitest/file_presentation_receiver.html +++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html @@ -37,22 +37,22 @@ function testSessionAvailable() { ok(navigator.presentation, "navigator.presentation should be available."); ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available."); navigator.presentation.receiver.getSession().then( function(aSession) { session = aSession; ok(session.id, "Session ID should be set: " + session.id); - is(session.state, "disconnected", "Session state at receiver side should be disconnected by default."); + is(session.state, "closed", "Session state at receiver side should be closed by default."); aResolve(); }, function(aError) { ok(false, "Error occurred when getting the session: " + aError); - teardown(); + finish(); aReject(); } ); }); } function testSessionReady() { return new Promise(function(aResolve, aReject) { @@ -76,29 +76,29 @@ function testIncomingMessage() { aResolve(); }); command({ name: 'trigger-incoming-message', data: incomingMessage }); }); } -function testCloseSession() { +function testTerminateSession() { return new Promise(function(aResolve, aReject) { session.onstatechange = function() { session.onstatechange = null; is(session.state, "terminated", "Session should be terminated."); aResolve(); }; - session.close(); + session.terminate(); }); } testSessionAvailable(). then(testSessionReady). then(testIncomingMessage). -then(testCloseSession). +then(testTerminateSession). then(finish); </script> </body> </html>
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html +++ b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html @@ -37,22 +37,22 @@ function testSessionAvailable() { ok(navigator.presentation, "navigator.presentation should be available."); ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available."); navigator.presentation.receiver.getSession().then( function(aSession) { session = aSession; ok(session.id, "Session ID should be set: " + session.id); - is(session.state, "disconnected", "Session state at receiver side should be disconnected by default."); + is(session.state, "closed", "Session state at receiver side should be closed by default."); aResolve(); }, function(aError) { ok(false, "Error occurred when getting the session: " + aError); - teardown(); + finish(); aReject(); } ); }); } function testSessionReady() { return new Promise(function(aResolve, aReject) { @@ -76,29 +76,29 @@ function testIncomingMessage() { aResolve(); }); command({ name: 'trigger-incoming-message', data: incomingMessage }); }); } -function testCloseSession() { +function testTerminateSession() { return new Promise(function(aResolve, aReject) { session.onstatechange = function() { session.onstatechange = null; is(session.state, "terminated", "Session should be terminated."); aResolve(); }; - session.close(); + session.terminate(); }); } testSessionAvailable(). then(testSessionReady). then(testIncomingMessage). -then(testCloseSession). +then(testTerminateSession). then(finish); </script> </body> </html>
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html +++ b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html @@ -37,22 +37,22 @@ function testSessionAvailable() { ok(navigator.presentation, "navigator.presentation should be available."); ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available."); navigator.presentation.receiver.getSession().then( function(aSession) { session = aSession; ok(session.id, "Session ID should be set: " + session.id); - is(session.state, "disconnected", "Session state at receiver side should be disconnected by default."); + is(session.state, "closed", "Session state at receiver side should be closed by default."); aResolve(); }, function(aError) { ok(false, "Error occurred when getting the session: " + aError); - teardown(); + finish(); aReject(); } ); }); } function testUnexpectedControlChannelClose() { return new Promise(function(aResolve, aReject) {
--- a/dom/presentation/tests/mochitest/test_presentation_sender.html +++ b/dom/presentation/tests/mochitest/test_presentation_sender.html @@ -143,30 +143,30 @@ function testIncomingMessage() { is(aEvent.data, incomingMessage, "An incoming message should be received."); aResolve(); }); gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage); }); } -function testCloseSession() { +function testTerminateSession() { return new Promise(function(aResolve, aReject) { gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) { gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler); info("The data transport is closed. " + aReason); }); session.onstatechange = function() { session.onstatechange = null; is(session.state, "terminated", "Session should be terminated."); aResolve(); }; - session.close(); + session.terminate(); }); } function teardown() { gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() { gScript.removeMessageListener('teardown-complete', teardownCompleteHandler); gScript.destroy(); SimpleTest.finish(); @@ -177,17 +177,17 @@ function teardown() { function runTests() { ok(window.PresentationRequest, "PresentationRequest should be available."); testSetup(). then(testStartSession). then(testSend). then(testIncomingMessage). - then(testCloseSession). + then(testTerminateSession). then(teardown); } SimpleTest.expectAssertions(0, 5); SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPermissions([ {type: 'presentation-device-manage', allow: false, context: document}, {type: 'presentation', allow: true, context: document},
--- a/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html +++ b/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html @@ -92,30 +92,30 @@ function testStartSession() { aResolve(); }; // Simulate the UA triggers |start()| of the default request. navigator.presentation.defaultRequest.start(); }); } -function testCloseSession() { +function testTerminateSession() { return new Promise(function(aResolve, aReject) { gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) { gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler); info("The data transport is closed. " + aReason); }); session.onstatechange = function() { session.onstatechange = null; is(session.state, "terminated", "Session should be terminated."); aResolve(); }; - session.close(); + session.terminate(); }); } function teardown() { gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() { gScript.removeMessageListener('teardown-complete', teardownCompleteHandler); gScript.destroy(); SimpleTest.finish(); @@ -125,17 +125,17 @@ function teardown() { } function runTests() { ok(window.PresentationRequest, "PresentationRequest should be available."); ok(navigator.presentation, "navigator.presentation should be available."); testSetup(). then(testStartSession). - then(testCloseSession). + then(testTerminateSession). then(teardown); } SimpleTest.expectAssertions(0, 5); SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPermissions([ {type: 'presentation-device-manage', allow: false, context: document}, {type: 'presentation', allow: true, context: document},
--- a/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html +++ b/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html @@ -108,53 +108,40 @@ function testSessionDisconnection() { return new Promise(function(aResolve, aReject) { gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) { gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler); info("The data transport is closed. " + aReason); }); session.onstatechange = function() { session.onstatechange = null; - is(session.state, "disconnected", "Session should be disconnected."); + is(session.state, "closed", "Session should be closed."); aResolve(); }; gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_FAILURE); }); } -function testCloseSession() { - return new Promise(function(aResolve, aReject) { - session.onstatechange = function() { - session.onstatechange = null; - is(session.state, "terminated", "Session should be terminated."); - aResolve(); - }; - - session.close(); - }); -} - function teardown() { gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() { gScript.removeMessageListener('teardown-complete', teardownCompleteHandler); gScript.destroy(); SimpleTest.finish(); }); gScript.sendAsyncMessage('teardown'); } function runTests() { ok(window.PresentationRequest, "PresentationRequest should be available."); testSetup(). then(testStartSession). then(testSessionDisconnection). - then(testCloseSession). then(teardown); } SimpleTest.expectAssertions(0, 5); SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPermissions([ {type: 'presentation-device-manage', allow: false, context: document}, {type: 'presentation', allow: true, context: document},
--- a/dom/tv/TVSimulatorService.manifest +++ b/dom/tv/TVSimulatorService.manifest @@ -1,3 +1,2 @@ component {94b065ad-d45a-436a-b394-6dabc3cf110f} TVSimulatorService.js contract @mozilla.org/tv/simulatorservice;1 {94b065ad-d45a-436a-b394-6dabc3cf110f} -TVSimulatorService @mozilla.org/tv/simulatorservice;1
--- a/dom/webidl/PresentationSession.webidl +++ b/dom/webidl/PresentationSession.webidl @@ -5,65 +5,71 @@ */ enum PresentationSessionState { // Existing presentation, and the communication channel is active. "connected", // Existing presentation, but the communication channel is inactive. - "disconnected", + "closed", // The presentation is nonexistent anymore. It could be terminated manually, - // or either requesting page or presenting page is no longer available. + // or either controlling or receiving browsing context is no longer available. "terminated" }; [Pref="dom.presentation.enabled", CheckAnyPermissions="presentation"] interface PresentationSession : EventTarget { /* * Unique id for all existing sessions. */ [Constant] readonly attribute DOMString id; /* - * Please refer to PresentationSessionStateEvent.webidl for the declaration of - * PresentationSessionState. - * - * @value "connected", "disconnected", or "terminated". + * @value "connected", "closed", or "terminated". */ readonly attribute PresentationSessionState state; /* - * It is called when session state changes. New state is dispatched with the - * event. + * It is called when session state changes. */ attribute EventHandler onstatechange; /* - * After a communication channel has been established between the requesting - * page and the presenting page, send() is called to send message out, and the - * event handler "onmessage" will be invoked on the remote side. + * After a communication channel has been established between the controlling + * and receiving context, this function is called to send message out, and the + * event handler "onmessage" will be invoked at the remote side. * - * This function only works when state equals "connected". + * This function only works when the state is "connected". * - * @data: String literal-only for current implementation. + * TODO bug 1148307 Implement PresentationSessionTransport with DataChannel to + * support other binary types. */ [Throws] void send(DOMString data); /* * It is triggered when receiving messages. */ attribute EventHandler onmessage; /* - * Both the requesting page and the presenting page can close the session by - * calling terminate(). Then, the session is destroyed and its state is - * truned into "terminated". After getting into the state of "terminated", - * resumeSession() is incapable of re-establishing the connection. + * Both the controlling and receving browsing context can close the session. + * Then, the session state should turn into "closed". * - * This function does nothing if the state has already been "terminated". + * This function only works when the state is not "connected". */ - void close(); + // TODO Bug 1210340 - Support close semantics. + // [Throws] + // void close(); + + /* + * Both the controlling and receving browsing context can terminate the session. + * Then the session state should turn into "terminated". + * + * This function only works when the state is not "connected". + */ + [Throws] + void terminate(); };
--- a/extensions/cookie/nsPermissionManager.cpp +++ b/extensions/cookie/nsPermissionManager.cpp @@ -2310,40 +2310,16 @@ nsPermissionManager::RemoveAllModifiedSi } NS_IMETHODIMP nsPermissionManager::RemovePermissionsForApp(uint32_t aAppId, bool aBrowserOnly) { ENSURE_NOT_CHILD_PROCESS; NS_ENSURE_ARG(aAppId != nsIScriptSecurityManager::NO_APP_ID); - // We begin by removing all the permissions from the DB. - // After clearing the DB, we call AddInternal() to make sure that all - // processes are aware of this change and the representation of the DB in - // memory is updated. - // We have to get all permissions associated with an application first - // because removing entries from the permissions table while iterating over - // it is dangerous. - - nsAutoCString sql; - sql.AppendLiteral("DELETE FROM moz_perms WHERE appId="); - sql.AppendInt(aAppId); - - if (aBrowserOnly) { - sql.AppendLiteral(" AND isInBrowserElement=1"); - } - - nsCOMPtr<mozIStorageAsyncStatement> removeStmt; - nsresult rv = mDBConn->CreateAsyncStatement(sql, getter_AddRefs(removeStmt)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<mozIStoragePendingStatement> pending; - rv = removeStmt->ExecuteAsync(nullptr, getter_AddRefs(pending)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMArray<nsIPermission> permissions; for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) { PermissionHashKey* entry = iter.Get(); nsCOMPtr<nsIPrincipal> principal; nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, getter_AddRefs(principal)); if (NS_FAILED(rv)) {
new file mode 100644 --- /dev/null +++ b/extensions/cookie/test/unit/test_permmanager_removeforapp.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + // initialize the permission manager service + let ssm = Services.scriptSecurityManager; + let pm = Services.perms; + + function mkPrin(uri, appId, inBrowser) { + return ssm.createCodebasePrincipal(Services.io.newURI(uri, null, null), + {appId: appId, inBrowser: inBrowser}); + } + + function checkPerms(perms) { + perms.forEach((perm) => { + // Look up the expected permission + do_check_eq(pm.getPermissionObject(mkPrin(perm[0], perm[1], perm[2]), + perm[3], true).capability, + perm[4], "Permission is expected in the permission database"); + }); + + // Count the entries + let count = 0; + let enumerator = Services.perms.enumerator; + while (enumerator.hasMoreElements()) { enumerator.getNext(); count++; } + + do_check_eq(count, perms.length, "There should be the right number of permissions in the DB"); + } + + checkPerms([]); + + let permissions = [ + ['http://google.com', 1001, false, 'a', 1], + ['http://google.com', 1001, false, 'b', 1], + ['http://mozilla.com', 1001, false, 'b', 1], + ['http://mozilla.com', 1001, false, 'a', 1], + + ['http://google.com', 1001, true, 'a', 1], + ['http://google.com', 1001, true, 'b', 1], + ['http://mozilla.com', 1001, true, 'b', 1], + ['http://mozilla.com', 1001, true, 'a', 1], + + ['http://google.com', 1011, false, 'a', 1], + ['http://google.com', 1011, false, 'b', 1], + ['http://mozilla.com', 1011, false, 'b', 1], + ['http://mozilla.com', 1011, false, 'a', 1], + ]; + + permissions.forEach((perm) => { + pm.addFromPrincipal(mkPrin(perm[0], perm[1], perm[2]), perm[3], perm[4]); + }); + + checkPerms(permissions); + + let remove_false_perms = [ + ['http://google.com', 1011, false, 'a', 1], + ['http://google.com', 1011, false, 'b', 1], + ['http://mozilla.com', 1011, false, 'b', 1], + ['http://mozilla.com', 1011, false, 'a', 1], + ]; + + pm.removePermissionsForApp(1001, false); + checkPerms(remove_false_perms); + + let restore = [ + ['http://google.com', 1001, false, 'a', 1], + ['http://google.com', 1001, false, 'b', 1], + ['http://mozilla.com', 1001, false, 'b', 1], + ['http://mozilla.com', 1001, false, 'a', 1], + + ['http://google.com', 1001, true, 'a', 1], + ['http://google.com', 1001, true, 'b', 1], + ['http://mozilla.com', 1001, true, 'b', 1], + ['http://mozilla.com', 1001, true, 'a', 1], + ]; + + restore.forEach((perm) => { + pm.addFromPrincipal(mkPrin(perm[0], perm[1], perm[2]), perm[3], perm[4]); + }); + checkPerms(permissions); + + let remove_true_perms = [ + ['http://google.com', 1001, false, 'a', 1], + ['http://google.com', 1001, false, 'b', 1], + ['http://mozilla.com', 1001, false, 'b', 1], + ['http://mozilla.com', 1001, false, 'a', 1], + + ['http://google.com', 1011, false, 'a', 1], + ['http://google.com', 1011, false, 'b', 1], + ['http://mozilla.com', 1011, false, 'b', 1], + ['http://mozilla.com', 1011, false, 'a', 1], + ]; + + pm.removePermissionsForApp(1001, true); + checkPerms(remove_true_perms); +}
--- a/extensions/cookie/test/unit/xpcshell.ini +++ b/extensions/cookie/test/unit/xpcshell.ini @@ -21,16 +21,17 @@ skip-if = true # Bug 863738 [test_domain_eviction.js] [test_eviction.js] [test_permmanager_defaults.js] [test_permmanager_expiration.js] [test_permmanager_getPermissionObject.js] [test_permmanager_notifications.js] [test_permmanager_removeall.js] [test_permmanager_removesince.js] +[test_permmanager_removeforapp.js] [test_permmanager_load_invalid_entries.js] skip-if = debug == true [test_permmanager_idn.js] [test_permmanager_subdomains.js] [test_permmanager_local_files.js] [test_permmanager_cleardata.js] [test_schema_2_migration.js] [test_schema_3_migration.js]
--- a/gfx/2d/FilterNodeD2D1.cpp +++ b/gfx/2d/FilterNodeD2D1.cpp @@ -531,16 +531,37 @@ IsTransferFilterType(FilterType aType) case FilterType::TABLE_TRANSFER: case FilterType::DISCRETE_TRANSFER: return true; default: return false; } } +static bool +HasUnboundedOutputRegion(FilterType aType) +{ + if (IsTransferFilterType(aType)) { + return true; + } + + switch (aType) { + case FilterType::COLOR_MATRIX: + case FilterType::POINT_DIFFUSE: + case FilterType::SPOT_DIFFUSE: + case FilterType::DISTANT_DIFFUSE: + case FilterType::POINT_SPECULAR: + case FilterType::SPOT_SPECULAR: + case FilterType::DISTANT_SPECULAR: + return true; + default: + return false; + } +} + /* static */ already_AddRefed<FilterNode> FilterNodeD2D1::Create(ID2D1DeviceContext *aDC, FilterType aType) { if (aType == FilterType::CONVOLVE_MATRIX) { return MakeAndAddRef<FilterNodeConvolveD2D1>(aDC); } @@ -551,17 +572,17 @@ FilterNodeD2D1::Create(ID2D1DeviceContex if (FAILED(hr) || !effect) { gfxCriticalErrorOnce() << "Failed to create effect for FilterType: " << hexa(hr); return nullptr; } RefPtr<FilterNodeD2D1> filter = new FilterNodeD2D1(effect, aType); - if (IsTransferFilterType(aType) || aType == FilterType::COLOR_MATRIX) { + if (HasUnboundedOutputRegion(aType)) { // These filters can produce non-transparent output from transparent // input pixels, and we want them to have an unbounded output region. filter = new FilterNodeExtendInputAdapterD2D1(aDC, filter, aType); } if (IsTransferFilterType(aType)) { // Component transfer filters should appear to apply on unpremultiplied // colors, but the D2D1 effects apply on premultiplied colors.
--- a/gfx/2d/FilterNodeSoftware.cpp +++ b/gfx/2d/FilterNodeSoftware.cpp @@ -3335,17 +3335,17 @@ FilterNodeLightingSoftware<LightType, Li mColor = aColor; Invalidate(); } template<typename LightType, typename LightingType> IntRect FilterNodeLightingSoftware<LightType, LightingType>::GetOutputRectInRect(const IntRect& aRect) { - return GetInputRectInRect(IN_LIGHTING_IN, aRect); + return aRect; } Point3D PointLightSoftware::GetVectorToLight(const Point3D &aTargetPoint) { return Normalized(mPosition - aTargetPoint); } @@ -3477,17 +3477,17 @@ FilterNodeLightingSoftware<LightType, Li ceil(float(aKernelUnitLengthY))); // Inflate the source rect by another pixel because the bilinear filtering in // ColorComponentAtPoint may want to access the margins. srcRect.Inflate(1); RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(IN_LIGHTING_IN, srcRect, CAN_HANDLE_A8, - EDGE_MODE_DUPLICATE); + EDGE_MODE_NONE); if (!input) { return nullptr; } if (input->GetFormat() != SurfaceFormat::A8) { input = FilterProcessing::ExtractAlpha(input); }
--- a/gfx/layers/LayerScope.cpp +++ b/gfx/layers/LayerScope.cpp @@ -430,38 +430,41 @@ protected: #ifdef MOZ_WIDGET_GONK // B2G optimization. class DebugGLGraphicBuffer final: public DebugGLData { public: DebugGLGraphicBuffer(void *layerRef, GLenum target, GLuint name, - const LayerRenderState &aState) + const LayerRenderState &aState, + bool aIsMask) : DebugGLData(Packet::TEXTURE), mLayerRef(reinterpret_cast<uint64_t>(layerRef)), mTarget(target), mName(name), - mState(aState) + mState(aState), + mIsMask(aIsMask) { } virtual bool Write() override { return WriteToStream(mPacket); } bool TryPack(bool packData) { android::sp<android::GraphicBuffer> buffer = mState.mSurface; MOZ_ASSERT(buffer.get()); mPacket.set_type(mDataType); TexturePacket* tp = mPacket.mutable_texture(); tp->set_layerref(mLayerRef); tp->set_name(mName); tp->set_target(mTarget); + tp->set_ismask(mIsMask); int pFormat = buffer->getPixelFormat(); if (HAL_PIXEL_FORMAT_RGBA_8888 != pFormat && HAL_PIXEL_FORMAT_RGBX_8888 != pFormat) { return false; } int32_t stride = buffer->getStride() * 4; @@ -508,33 +511,36 @@ public: return true; } private: uint64_t mLayerRef; GLenum mTarget; GLuint mName; const LayerRenderState &mState; + bool mIsMask; Packet mPacket; }; #endif class DebugGLTextureData final: public DebugGLData { public: DebugGLTextureData(GLContext* cx, void* layerRef, GLenum target, GLuint name, - DataSourceSurface* img) + DataSourceSurface* img, + bool aIsMask) : DebugGLData(Packet::TEXTURE), mLayerRef(reinterpret_cast<uint64_t>(layerRef)), mTarget(target), mName(name), mContextAddress(reinterpret_cast<intptr_t>(cx)), - mDatasize(0) + mDatasize(0), + mIsMask(aIsMask) { // pre-packing // DataSourceSurface may have locked buffer, // so we should compress now, and then it could // be unlocked outside. pack(img); } @@ -547,16 +553,17 @@ private: mPacket.set_type(mDataType); TexturePacket* tp = mPacket.mutable_texture(); tp->set_layerref(mLayerRef); tp->set_name(mName); tp->set_target(mTarget); tp->set_dataformat(LOCAL_GL_RGBA); tp->set_glcontext(static_cast<uint64_t>(mContextAddress)); + tp->set_ismask(mIsMask); if (aImage) { tp->set_width(aImage->GetSize().width); tp->set_height(aImage->GetSize().height); tp->set_stride(aImage->Stride()); mDatasize = aImage->GetSize().height * aImage->Stride(); @@ -585,16 +592,17 @@ private: } protected: uint64_t mLayerRef; GLenum mTarget; GLuint mName; intptr_t mContextAddress; uint32_t mDatasize; + bool mIsMask; // Packet data Packet mPacket; }; class DebugGLColorData final: public DebugGLData { public: DebugGLColorData(void* layerRef, @@ -867,19 +875,21 @@ NS_IMPL_ISUPPORTS(DebugDataSender::SendT /* * LayerScope SendXXX Structure * 1. SendLayer * 2. SendEffectChain * 1. SendTexturedEffect * -> SendTextureSource - * 2. SendYCbCrEffect + * 2. SendMaskEffect * -> SendTextureSource - * 3. SendColor + * 3. SendYCbCrEffect + * -> SendTextureSource + * 4. SendColor */ class SenderHelper { // Sender public APIs public: static void SendLayer(LayerComposite* aLayer, int aWidth, int aHeight); @@ -890,80 +900,74 @@ public: int aHeight = 0); static void SetLayersTreeSendable(bool aSet) {sLayersTreeSendable = aSet;} static void SetLayersBufferSendable(bool aSet) {sLayersBufferSendable = aSet;} static bool GetLayersTreeSendable() {return sLayersTreeSendable;} - static void ClearTextureIdList(); + static void ClearSentTextureIds(); // Sender private functions private: static void SendColor(void* aLayerRef, const Color& aColor, int aWidth, int aHeight); static void SendTextureSource(GLContext* aGLContext, void* aLayerRef, TextureSourceOGL* aSource, - GLuint aTexID, - bool aFlipY); + bool aFlipY, + bool aIsMask); #ifdef MOZ_WIDGET_GONK - static bool SendGraphicBuffer(void* aLayerRef, + static bool SendGraphicBuffer(GLContext* aGLContext, + void* aLayerRef, TextureSourceOGL* aSource, - GLuint aTexID, - const TexturedEffect* aEffect); + const TexturedEffect* aEffect, + bool aIsMask); #endif static void SendTexturedEffect(GLContext* aGLContext, void* aLayerRef, const TexturedEffect* aEffect); + static void SendMaskEffect(GLContext* aGLContext, + void* aLayerRef, + const EffectMask* aEffect); static void SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef, const EffectYCbCr* aEffect); static GLuint GetTextureID(GLContext* aGLContext, TextureSourceOGL* aSource); - static bool IsTextureIdContainsInList(GLuint aTextureId); + static bool HasTextureIdBeenSent(GLuint aTextureId); // Data fields private: static bool sLayersTreeSendable; static bool sLayersBufferSendable; - static std::list<GLuint> sTextureIdList; + static std::vector<GLuint> sSentTextureIds; }; bool SenderHelper::sLayersTreeSendable = true; bool SenderHelper::sLayersBufferSendable = true; -std::list<GLuint> SenderHelper::sTextureIdList; +std::vector<GLuint> SenderHelper::sSentTextureIds; // ---------------------------------------------- // SenderHelper implementation // ---------------------------------------------- void -SenderHelper::ClearTextureIdList() +SenderHelper::ClearSentTextureIds() { - std::list<GLuint>::iterator it; - while (!sTextureIdList.empty()) { - it = sTextureIdList.begin(); - sTextureIdList.erase(it); - } + sSentTextureIds.clear(); } bool -SenderHelper::IsTextureIdContainsInList(GLuint aTextureId) +SenderHelper::HasTextureIdBeenSent(GLuint aTextureId) { - for (std::list<GLuint>::iterator it = sTextureIdList.begin(); - it != sTextureIdList.end(); ++it) { - if (*it == aTextureId) { - return true; - } - } - return false; + return std::find(sSentTextureIds.begin(), sSentTextureIds.end(), aTextureId) != sSentTextureIds.end(); } void SenderHelper::SendLayer(LayerComposite* aLayer, int aWidth, int aHeight) { MOZ_ASSERT(aLayer && aLayer->GetLayer()); @@ -1030,132 +1034,138 @@ SenderHelper::GetTextureID(GLContext* aG return texID; } void SenderHelper::SendTextureSource(GLContext* aGLContext, void* aLayerRef, TextureSourceOGL* aSource, - GLuint aTexID, - bool aFlipY) + bool aFlipY, + bool aIsMask) { MOZ_ASSERT(aGLContext); if (!aGLContext) { return; } + GLuint texID = GetTextureID(aGLContext, aSource); + if (HasTextureIdBeenSent(texID)) { + return; + } GLenum textureTarget = aSource->GetTextureTarget(); ShaderConfigOGL config = ShaderConfigFromTargetAndFormat(textureTarget, aSource->GetFormat()); int shaderConfig = config.mFeatures; gfx::IntSize size = aSource->GetSize(); // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding // texture correctly. texID is used for tracking in DebugGLTextureData. RefPtr<DataSourceSurface> img = aGLContext->ReadTexImageHelper()->ReadTexImage(0, textureTarget, size, shaderConfig, aFlipY); gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLTextureData(aGLContext, aLayerRef, textureTarget, - aTexID, img)); + texID, img, aIsMask)); - sTextureIdList.push_back(aTexID); - gLayerScopeManager.CurrentSession().mTexIDs.push_back(aTexID); + sSentTextureIds.push_back(texID); + gLayerScopeManager.CurrentSession().mTexIDs.push_back(texID); } #ifdef MOZ_WIDGET_GONK bool -SenderHelper::SendGraphicBuffer(void* aLayerRef, +SenderHelper::SendGraphicBuffer(GLContext* aGLContext, + void* aLayerRef, TextureSourceOGL* aSource, - GLuint aTexID, - const TexturedEffect* aEffect) { + const TexturedEffect* aEffect, + bool aIsMask) { + GLuint texID = GetTextureID(aGLContext, aSource); + if (HasTextureIdBeenSent(texID)) { + return false; + } if (!aEffect->mState.mSurface.get()) { return false; } GLenum target = aSource->GetTextureTarget(); mozilla::UniquePtr<DebugGLGraphicBuffer> package = - MakeUnique<DebugGLGraphicBuffer>(aLayerRef, target, aTexID, aEffect->mState); + MakeUnique<DebugGLGraphicBuffer>(aLayerRef, target, texID, aEffect->mState, aIsMask); // The texure content in this TexureHost is not altered, // we don't need to send it again. bool changed = gLayerScopeManager.GetContentMonitor()->IsChangedOrNew( aEffect->mState.mTexture); if (!package->TryPack(changed)) { return false; } // Transfer ownership to SocketManager. gLayerScopeManager.GetSocketManager()->AppendDebugData(package.release()); - sTextureIdList.push_back(aTexID); + sSentTextureIds.push_back(texID); - gLayerScopeManager.CurrentSession().mTexIDs.push_back(aTexID); + gLayerScopeManager.CurrentSession().mTexIDs.push_back(texID); gLayerScopeManager.GetContentMonitor()->ClearChangedHost(aEffect->mState.mTexture); return true; } #endif void SenderHelper::SendTexturedEffect(GLContext* aGLContext, void* aLayerRef, const TexturedEffect* aEffect) { TextureSourceOGL* source = aEffect->mTexture->AsSourceOGL(); if (!source) { return; } - GLuint texID = GetTextureID(aGLContext, source); - if (IsTextureIdContainsInList(texID)) { - return; - } - #ifdef MOZ_WIDGET_GONK - if (SendGraphicBuffer(aLayerRef, source, texID, aEffect)) { + if (SendGraphicBuffer(aGLContext, aLayerRef, source, aEffect, false)) { return; } #endif // Fallback texture sending path. // Render to texture and read pixels back. - SendTextureSource(aGLContext, aLayerRef, source, texID, false); + SendTextureSource(aGLContext, aLayerRef, source, false, false); +} + +void +SenderHelper::SendMaskEffect(GLContext* aGLContext, + void* aLayerRef, + const EffectMask* aEffect) +{ + TextureSourceOGL* source = aEffect->mMaskTexture->AsSourceOGL(); + if (!source) { + return; + } + + SendTextureSource(aGLContext, aLayerRef, source, false, true); } void SenderHelper::SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef, const EffectYCbCr* aEffect) { TextureSource* sourceYCbCr = aEffect->mTexture; if (!sourceYCbCr) return; const int Y = 0, Cb = 1, Cr = 2; TextureSourceOGL* sourceY = sourceYCbCr->GetSubSource(Y)->AsSourceOGL(); TextureSourceOGL* sourceCb = sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(); TextureSourceOGL* sourceCr = sourceYCbCr->GetSubSource(Cr)->AsSourceOGL(); - GLuint texID = GetTextureID(aGLContext, sourceY); - if (!IsTextureIdContainsInList(texID)) { - SendTextureSource(aGLContext, aLayerRef, sourceY, texID, false); - } - - texID = GetTextureID(aGLContext, sourceCb); - if (!IsTextureIdContainsInList(texID)) { - SendTextureSource(aGLContext, aLayerRef, sourceCb, texID, false); - } - - texID = GetTextureID(aGLContext, sourceCr); - if (!IsTextureIdContainsInList(texID)) { - SendTextureSource(aGLContext, aLayerRef, sourceCr, texID, false); - } + SendTextureSource(aGLContext, aLayerRef, sourceY, false, false); + SendTextureSource(aGLContext, aLayerRef, sourceCb, false, false); + SendTextureSource(aGLContext, aLayerRef, sourceCr, false, false); } void SenderHelper::SendEffectChain(GLContext* aGLContext, const EffectChain& aEffectChain, int aWidth, int aHeight) { @@ -1183,18 +1193,21 @@ SenderHelper::SendEffectChain(GLContext* break; } case EffectTypes::COMPONENT_ALPHA: case EffectTypes::RENDER_TARGET: default: break; } - //const Effect* secondaryEffect = aEffectChain.mSecondaryEffects[EffectTypes::MASK]; - // TODO: + if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { + const EffectMask* effectMask = + static_cast<const EffectMask*>(aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); + SendMaskEffect(aGLContext, aEffectChain.mLayerRef, effectMask); + } } void LayerScope::ContentChanged(TextureHost *host) { if (!CheckSendable()) { return; } @@ -1867,17 +1880,17 @@ LayerScopeAutoFrame::~LayerScopeAutoFram } void LayerScopeAutoFrame::BeginFrame(int64_t aFrameStamp) { if (!LayerScope::CheckSendable()) { return; } - SenderHelper::ClearTextureIdList(); + SenderHelper::ClearSentTextureIds(); gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLFrameStatusData(Packet::FRAMESTART, aFrameStamp)); } void LayerScopeAutoFrame::EndFrame() {
--- a/gfx/layers/TextureDIB.cpp +++ b/gfx/layers/TextureDIB.cpp @@ -48,16 +48,20 @@ TextureClientDIB::BorrowDrawTarget() { MOZ_ASSERT(mIsLocked && IsAllocated()); if (!mDrawTarget) { mDrawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mSurface, mSize); } + if (!mDrawTarget) { + gfxCriticalNote << "DIB failed draw target surface " << mSize << ", " << (int)mIsLocked << ", " << IsAllocated(); + } + return mDrawTarget; } void TextureClientDIB::UpdateFromSurface(gfx::SourceSurface* aSurface) { MOZ_ASSERT(mIsLocked && IsAllocated());
--- a/gfx/layers/d3d11/TextureD3D11.cpp +++ b/gfx/layers/d3d11/TextureD3D11.cpp @@ -419,17 +419,17 @@ TextureClientD3D11::BorrowDrawTarget() if (mTexture) { mDrawTarget = Factory::CreateDrawTargetForD3D11Texture(mTexture, mFormat); } else { MOZ_ASSERT(mTexture10); mDrawTarget = Factory::CreateDrawTargetForD3D10Texture(mTexture10, mFormat); } if (!mDrawTarget) { - gfxWarning() << "Invalid draw target for borrowing"; + gfxCriticalNote << "Invalid draw target for borrowing D3D11 " << (int)mFormat; } return mDrawTarget; } void TextureClientD3D11::UpdateFromSurface(gfx::SourceSurface* aSurface) { RefPtr<DataSourceSurface> srcSurf = aSurface->GetDataSurface();
--- a/gfx/layers/d3d9/TextureD3D9.cpp +++ b/gfx/layers/d3d9/TextureD3D9.cpp @@ -536,44 +536,52 @@ TextureClientD3D9::ToSurfaceDescriptor(S } gfx::DrawTarget* TextureClientD3D9::BorrowDrawTarget() { MOZ_ASSERT(mIsLocked && mD3D9Surface); if (!mIsLocked || !mD3D9Surface) { NS_WARNING("Calling BorrowDrawTarget on an Unlocked TextureClient"); + gfxCriticalNote << "BorrowDrawTarget on an Unlocked TextureClient"; return nullptr; } if (mDrawTarget) { return mDrawTarget; } if (ContentForFormat(mFormat) == gfxContentType::COLOR) { nsRefPtr<gfxASurface> surface = new gfxWindowsSurface(mD3D9Surface); if (!surface || surface->CairoStatus()) { NS_WARNING("Could not create surface for d3d9 surface"); + gfxCriticalNote << "Failed creation on D3D9"; return nullptr; } mDrawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, mSize); + if (!mDrawTarget) { + gfxCriticalNote << "Bad draw target creation for surface D3D9 " << mSize; + } } else { // gfxWindowsSurface don't support transparency so we can't use the d3d9 // windows surface optimization. // Instead we have to use a gfxImageSurface and fallback for font drawing. D3DLOCKED_RECT rect; HRESULT hr = mD3D9Surface->LockRect(&rect, nullptr, 0); if (FAILED(hr) || !rect.pBits) { gfxCriticalError() << "Failed to lock rect borrowing the target in D3D9 " << hexa(hr); return nullptr; } mDrawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForData((uint8_t*)rect.pBits, mSize, rect.Pitch, mFormat); + if (!mDrawTarget) { + gfxCriticalNote << "Bad draw target creation for data D3D9 " << mSize << ", " << (int)mFormat; + } mLockRect = true; } if (mNeedsClear) { mDrawTarget->ClearRect(Rect(0, 0, GetSize().width, GetSize().height)); mNeedsClear = false; } if (mNeedsClearWhite) {
--- a/gfx/layers/protobuf/LayerScopePacket.pb.cc +++ b/gfx/layers/protobuf/LayerScopePacket.pb.cc @@ -643,16 +643,17 @@ const int TexturePacket::kLayerrefFieldN const int TexturePacket::kWidthFieldNumber; const int TexturePacket::kHeightFieldNumber; const int TexturePacket::kStrideFieldNumber; const int TexturePacket::kNameFieldNumber; const int TexturePacket::kTargetFieldNumber; const int TexturePacket::kDataformatFieldNumber; const int TexturePacket::kGlcontextFieldNumber; const int TexturePacket::kDataFieldNumber; +const int TexturePacket::kIsMaskFieldNumber; #endif // !_MSC_VER TexturePacket::TexturePacket() : ::google::protobuf::MessageLite() { SharedCtor(); // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.TexturePacket) } @@ -673,16 +674,17 @@ void TexturePacket::SharedCtor() { width_ = 0u; height_ = 0u; stride_ = 0u; name_ = 0u; target_ = 0u; dataformat_ = 0u; glcontext_ = GOOGLE_ULONGLONG(0); data_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited()); + ismask_ = false; ::memset(_has_bits_, 0, sizeof(_has_bits_)); } TexturePacket::~TexturePacket() { // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.TexturePacket) SharedDtor(); } @@ -727,20 +729,23 @@ void TexturePacket::Clear() { size_t f = OFFSET_OF_FIELD_(first); \ size_t n = OFFSET_OF_FIELD_(last) - f + sizeof(last); \ ::memset(&first, 0, n); \ } while (0) if (_has_bits_[0 / 32] & 255) { ZR_(layerref_, glcontext_); } - if (has_data()) { - if (data_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { - data_->clear(); + if (_has_bits_[8 / 32] & 768) { + if (has_data()) { + if (data_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) { + data_->clear(); + } } + ismask_ = false; } #undef OFFSET_OF_FIELD_ #undef ZR_ ::memset(_has_bits_, 0, sizeof(_has_bits_)); mutable_unknown_fields()->clear(); } @@ -750,17 +755,17 @@ bool TexturePacket::MergePartialFromCode #define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure ::google::protobuf::uint32 tag; ::google::protobuf::io::StringOutputStream unknown_fields_string( mutable_unknown_fields()); ::google::protobuf::io::CodedOutputStream unknown_fields_stream( &unknown_fields_string); // @@protoc_insertion_point(parse_start:mozilla.layers.layerscope.TexturePacket) for (;;) { - ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127); + ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(16383); tag = p.first; if (!p.second) goto handle_unusual; switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) { // required uint64 layerref = 1; case 1: { if (tag == 8) { DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>( @@ -882,16 +887,31 @@ bool TexturePacket::MergePartialFromCode case 9: { if (tag == 74) { parse_data: DO_(::google::protobuf::internal::WireFormatLite::ReadBytes( input, this->mutable_data())); } else { goto handle_unusual; } + if (input->ExpectTag(160)) goto parse_isMask; + break; + } + + // optional bool isMask = 20; + case 20: { + if (tag == 160) { + parse_isMask: + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + bool, ::google::protobuf::internal::WireFormatLite::TYPE_BOOL>( + input, &ismask_))); + set_has_ismask(); + } else { + goto handle_unusual; + } if (input->ExpectAtEnd()) goto success; break; } default: { handle_unusual: if (tag == 0 || ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == @@ -957,16 +977,21 @@ void TexturePacket::SerializeWithCachedS } // optional bytes data = 9; if (has_data()) { ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased( 9, this->data(), output); } + // optional bool isMask = 20; + if (has_ismask()) { + ::google::protobuf::internal::WireFormatLite::WriteBool(20, this->ismask(), output); + } + output->WriteRaw(unknown_fields().data(), unknown_fields().size()); // @@protoc_insertion_point(serialize_end:mozilla.layers.layerscope.TexturePacket) } int TexturePacket::ByteSize() const { int total_size = 0; @@ -1031,16 +1056,21 @@ int TexturePacket::ByteSize() const { if (_has_bits_[8 / 32] & (0xffu << (8 % 32))) { // optional bytes data = 9; if (has_data()) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::BytesSize( this->data()); } + // optional bool isMask = 20; + if (has_ismask()) { + total_size += 2 + 1; + } + } total_size += unknown_fields().size(); GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); _cached_size_ = total_size; GOOGLE_SAFE_CONCURRENT_WRITES_END(); return total_size; } @@ -1077,16 +1107,19 @@ void TexturePacket::MergeFrom(const Text if (from.has_glcontext()) { set_glcontext(from.glcontext()); } } if (from._has_bits_[8 / 32] & (0xffu << (8 % 32))) { if (from.has_data()) { set_data(from.data()); } + if (from.has_ismask()) { + set_ismask(from.ismask()); + } } mutable_unknown_fields()->append(from.unknown_fields()); } void TexturePacket::CopyFrom(const TexturePacket& from) { if (&from == this) return; Clear(); MergeFrom(from); @@ -1104,16 +1137,17 @@ void TexturePacket::Swap(TexturePacket* std::swap(width_, other->width_); std::swap(height_, other->height_); std::swap(stride_, other->stride_); std::swap(name_, other->name_); std::swap(target_, other->target_); std::swap(dataformat_, other->dataformat_); std::swap(glcontext_, other->glcontext_); std::swap(data_, other->data_); + std::swap(ismask_, other->ismask_); std::swap(_has_bits_[0], other->_has_bits_[0]); _unknown_fields_.swap(other->_unknown_fields_); std::swap(_cached_size_, other->_cached_size_); } } ::std::string TexturePacket::GetTypeName() const { return "mozilla.layers.layerscope.TexturePacket";
--- a/gfx/layers/protobuf/LayerScopePacket.pb.h +++ b/gfx/layers/protobuf/LayerScopePacket.pb.h @@ -455,16 +455,23 @@ class TexturePacket : public ::google::p inline const ::std::string& data() const; inline void set_data(const ::std::string& value); inline void set_data(const char* value); inline void set_data(const void* value, size_t size); inline ::std::string* mutable_data(); inline ::std::string* release_data(); inline void set_allocated_data(::std::string* data); + // optional bool isMask = 20; + inline bool has_ismask() const; + inline void clear_ismask(); + static const int kIsMaskFieldNumber = 20; + inline bool ismask() const; + inline void set_ismask(bool value); + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.TexturePacket) private: inline void set_has_layerref(); inline void clear_has_layerref(); inline void set_has_width(); inline void clear_has_width(); inline void set_has_height(); inline void clear_has_height(); @@ -475,30 +482,33 @@ class TexturePacket : public ::google::p inline void set_has_target(); inline void clear_has_target(); inline void set_has_dataformat(); inline void clear_has_dataformat(); inline void set_has_glcontext(); inline void clear_has_glcontext(); inline void set_has_data(); inline void clear_has_data(); + inline void set_has_ismask(); + inline void clear_has_ismask(); ::std::string _unknown_fields_; ::google::protobuf::uint32 _has_bits_[1]; mutable int _cached_size_; ::google::protobuf::uint64 layerref_; ::google::protobuf::uint32 width_; ::google::protobuf::uint32 height_; ::google::protobuf::uint32 stride_; ::google::protobuf::uint32 name_; ::google::protobuf::uint32 target_; ::google::protobuf::uint32 dataformat_; ::google::protobuf::uint64 glcontext_; ::std::string* data_; + bool ismask_; #ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER friend void protobuf_AddDesc_LayerScopePacket_2eproto_impl(); #else friend void protobuf_AddDesc_LayerScopePacket_2eproto(); #endif friend void protobuf_AssignDesc_LayerScopePacket_2eproto(); friend void protobuf_ShutdownFile_LayerScopePacket_2eproto(); @@ -2659,16 +2669,40 @@ inline void TexturePacket::set_allocated data_ = data; } else { clear_has_data(); data_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited()); } // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.TexturePacket.data) } +// optional bool isMask = 20; +inline bool TexturePacket::has_ismask() const { + return (_has_bits_[0] & 0x00000200u) != 0; +} +inline void TexturePacket::set_has_ismask() { + _has_bits_[0] |= 0x00000200u; +} +inline void TexturePacket::clear_has_ismask() { + _has_bits_[0] &= ~0x00000200u; +} +inline void TexturePacket::clear_ismask() { + ismask_ = false; + clear_has_ismask(); +} +inline bool TexturePacket::ismask() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.isMask) + return ismask_; +} +inline void TexturePacket::set_ismask(bool value) { + set_has_ismask(); + ismask_ = value; + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.isMask) +} + // ------------------------------------------------------------------- // LayersPacket_Layer_Size // optional int32 w = 1; inline bool LayersPacket_Layer_Size::has_w() const { return (_has_bits_[0] & 0x00000001u) != 0; }
--- a/gfx/layers/protobuf/LayerScopePacket.proto +++ b/gfx/layers/protobuf/LayerScopePacket.proto @@ -15,25 +15,33 @@ message FramePacket { message ColorPacket { required uint64 layerref = 1; optional uint32 width = 2; optional uint32 height = 3; optional uint32 color = 4; } message TexturePacket { + // Basic info required uint64 layerref = 1; optional uint32 width = 2; optional uint32 height = 3; optional uint32 stride = 4; optional uint32 name = 5; optional uint32 target = 6; optional uint32 dataformat = 7; optional uint64 glcontext = 8; optional bytes data = 9; + + // Texture effect attributes (10 to 19) + // TODO: reserved for primary textured effect attributes, see Bug 1205521 + + // Mask effect attributes (20 to 29) + optional bool isMask = 20; + // TODO: reserved for secondary mask effect attributes, see Bug 1205521 } message LayersPacket { message Layer { enum LayerType { UnknownLayer = 0; LayerManager = 1; ContainerLayer = 2;
--- a/gfx/src/FilterSupport.cpp +++ b/gfx/src/FilterSupport.cpp @@ -1227,19 +1227,16 @@ FilterNodeGraphFromDescription(DrawTarge SourceSurface* aFillPaint, const IntRect& aFillPaintRect, SourceSurface* aStrokePaint, const IntRect& aStrokePaintRect, nsTArray<RefPtr<SourceSurface>>& aAdditionalImages) { const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives; - Rect resultNeededRect(aResultNeededRect); - resultNeededRect.RoundOut(); - RefPtr<FilterCachedColorModels> sourceFilters[4]; nsTArray<RefPtr<FilterCachedColorModels> > primitiveFilters; for (size_t i = 0; i < primitives.Length(); ++i) { const FilterPrimitiveDescription& descr = primitives[i]; nsTArray<RefPtr<FilterNode> > inputFilterNodes; nsTArray<IntRect> inputSourceRects; @@ -1402,16 +1399,19 @@ ResultChangeRegionForPrimitive(const Fil return aInputChangeRegions[0].Inflated(nsIntMargin(ry, rx, ry, rx)); } case PrimitiveType::Tile: return aDescription.PrimitiveSubregion(); case PrimitiveType::ConvolveMatrix: { + if (atts.GetUint(eConvolveMatrixEdgeMode) != EDGE_MODE_NONE) { + return aDescription.PrimitiveSubregion(); + } Size kernelUnitLength = atts.GetSize(eConvolveMatrixKernelUnitLength); IntSize kernelSize = atts.GetIntSize(eConvolveMatrixKernelSize); IntPoint target = atts.GetIntPoint(eConvolveMatrixTarget); nsIntMargin m(ceil(kernelUnitLength.width * (target.x)), ceil(kernelUnitLength.height * (target.y)), ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1)), ceil(kernelUnitLength.height * (kernelSize.height - target.y - 1))); return aInputChangeRegions[0].Inflated(m); @@ -1598,16 +1598,18 @@ FilterSupport::PostFilterExtentsForPrimi if (ResultOfZeroUnderTransferFunction(functionAttributes) > 0.0f) { return aDescription.PrimitiveSubregion(); } return aInputExtents[0]; } case PrimitiveType::Turbulence: case PrimitiveType::Image: + case PrimitiveType::DiffuseLighting: + case PrimitiveType::SpecularLighting: { return aDescription.PrimitiveSubregion(); } case PrimitiveType::Morphology: { uint32_t op = atts.GetUint(eMorphologyOperator); if (op == SVG_OPERATOR_ERODE) {
--- a/gfx/src/FilterSupport.h +++ b/gfx/src/FilterSupport.h @@ -432,17 +432,18 @@ public: SourceSurface* aStrokePaint, const IntRect& aStrokePaintRect, nsTArray<RefPtr<SourceSurface>>& aAdditionalImages, const Point& aDestPoint, const DrawOptions& aOptions = DrawOptions()); /** * Computes the region that changes in the filter output due to a change in - * input. + * input. This is primarily needed when an individual piece of content inside + * a filtered container element changes. */ static nsIntRegion ComputeResultChangeRegion(const FilterDescription& aFilter, const nsIntRegion& aSourceGraphicChange, const nsIntRegion& aFillPaintChange, const nsIntRegion& aStrokePaintChange); /**
--- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -176,25 +176,20 @@ static inline bool IsSimulatedOOMAllocat static inline bool ShouldFailWithOOM() { return false; } } /* namespace oom */ } /* namespace js */ # endif /* DEBUG || JS_OOM_BREAKPOINT */ namespace js { -MOZ_NORETURN MOZ_COLD void -CrashAtUnhandlableOOM(const char* reason); - /* Disable OOM testing in sections which are not OOM safe. */ struct MOZ_RAII AutoEnterOOMUnsafeRegion { - void crash(const char* reason) { - CrashAtUnhandlableOOM(reason); - } + MOZ_NORETURN MOZ_COLD void crash(const char* reason); #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) AutoEnterOOMUnsafeRegion() : oomEnabled_(oom::IsThreadSimulatingOOM() && OOM_maxAllocations != UINT32_MAX), oomAfter_(0) { if (oomEnabled_) { oomAfter_ = int64_t(OOM_maxAllocations) - OOM_counter;
--- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -92,22 +92,22 @@ function ModuleDeclarationInstantiation( { if (!IsObject(this) || !IsModule(this)) return callFunction(CallModuleMethodIfWrapped, this, "ModuleDeclarationInstantiation"); // Step 1 let module = this; // Step 5 - if (module.environment !== undefined) + if (GetModuleEnvironment(module) !== undefined) return; // Step 7 CreateModuleEnvironment(module); - let env = module.environment; + let env = GetModuleEnvironment(module); // Step 8 let requestedModules = module.requestedModules; for (let i = 0; i < requestedModules.length; i++) { let required = requestedModules[i]; let requiredModule = HostResolveImportedModule(module, required); requiredModule.declarationInstantiation(); } @@ -126,17 +126,17 @@ function ModuleDeclarationInstantiation( // Step 12 let importEntries = module.importEntries; for (let i = 0; i < importEntries.length; i++) { let imp = importEntries[i]; let importedModule = HostResolveImportedModule(module, imp.moduleRequest); if (imp.importName === "*") { // TODO // let namespace = GetModuleNamespace(importedModule); - // CreateNamespaceBinding(module.environment, imp.localName, namespace); + // CreateNamespaceBinding(env, imp.localName, namespace); } else { let resolution = importedModule.resolveExport(imp.importName); if (resolution === null) ThrowSyntaxError(JSMSG_MISSING_IMPORT); if (resolution === "ambiguous") ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT); CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName); }
--- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -405,31 +405,27 @@ ModuleObject::setEvaluated() bool ModuleObject::evaluate(JSContext* cx, MutableHandleValue rval) { RootedScript script(cx, this->script()); return JS_ExecuteScript(cx, script, rval); } -DEFINE_GETTER_FUNCTIONS(ModuleObject, initialEnvironment, InitialEnvironmentSlot) -DEFINE_GETTER_FUNCTIONS(ModuleObject, environment, EnvironmentSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluated, EvaluatedSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot) JSObject* js::InitModuleClass(JSContext* cx, HandleObject obj) { static const JSPropertySpec protoAccessors[] = { - JS_PSG("initialEnvironment", ModuleObject_initialEnvironmentGetter, 0), - JS_PSG("environment", ModuleObject_environmentGetter, 0), JS_PSG("evaluated", ModuleObject_evaluatedGetter, 0), JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0), JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0), JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0), JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0), JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0), JS_PS_END };
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2881,16 +2881,90 @@ SetRNGState(JSContext* cx, unsigned argc if (!ToNumber(cx, args[0], &seed)) return false; cx->compartment()->rngState = static_cast<uint64_t>(seed) & RNG_MASK; return true; } #endif +static ModuleEnvironmentObject* +GetModuleEnvironment(JSContext* cx, HandleValue moduleValue) +{ + RootedModuleObject module(cx, &moduleValue.toObject().as<ModuleObject>()); + + // Use the initial environment so that tests can check bindings exists + // before they have been instantiated. + RootedModuleEnvironmentObject env(cx, &module->initialEnvironment()); + MOZ_ASSERT(env); + MOZ_ASSERT_IF(module->environment(), module->environment() == env); + + return env; +} + +static bool +GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportError(cx, "Wrong number of arguments"); + return false; + } + + if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) { + JS_ReportError(cx, "First argument should be a ModuleObject"); + return false; + } + + RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0])); + Rooted<IdVector> ids(cx, IdVector(cx)); + if (!JS_Enumerate(cx, env, &ids)) + return false; + + uint32_t length = ids.length(); + RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length)); + if (!array) + return false; + + array->setDenseInitializedLength(length); + for (uint32_t i = 0; i < length; i++) + array->initDenseElement(i, StringValue(JSID_TO_STRING(ids[i]))); + + args.rval().setObject(*array); + return true; +} + +static bool +GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) { + JS_ReportError(cx, "Wrong number of arguments"); + return false; + } + + if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) { + JS_ReportError(cx, "First argument should be a ModuleObject"); + return false; + } + + if (!args[1].isString()) { + JS_ReportError(cx, "Second argument should be a string"); + return false; + } + + RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0])); + RootedString name(cx, args[1].toString()); + RootedId id(cx); + if (!JS_StringToId(cx, name, &id)) + return false; + + return GetProperty(cx, env, env, id, args.rval()); +} + static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("gc", ::GC, 0, 0, "gc([obj] | 'compartment' [, 'shrinking'])", " Run the garbage collector. When obj is given, GC only its compartment.\n" " If 'compartment' is given, GC any compartments that were scheduled for\n" " GC via schedulegc.\n" " If 'shrinking' is passed as the optional second argument, perform a\n" " shrinking GC rather than a normal GC."), @@ -3354,16 +3428,24 @@ gc::ZealModeHelpText), " Enables the deprecated, non-standard __noSuchMethod__ feature.\n"), #ifdef DEBUG JS_FN_HELP("setRNGState", SetRNGState, 1, 0, "setRNGState(seed)", " Set this compartment's RNG state.\n"), #endif + JS_FN_HELP("getModuleEnvironmentNames", GetModuleEnvironmentNames, 1, 0, +"getModuleEnvironmentNames(module)", +" Get the list of a module environment's bound names for a specified module.\n"), + + JS_FN_HELP("getModuleEnvironmentValue", GetModuleEnvironmentValue, 2, 0, +"getModuleEnvironmentValue(module, name)", +" Get the value of a bound name in a module environment.\n"), + JS_FS_HELP_END }; static const JSPropertySpec TestingProperties[] = { JS_PSG("timesAccessed", TimesAccessed, 0), JS_PS_END };
--- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -947,17 +947,17 @@ Statistics::beginSlice(const ZoneGCStats this->zoneStats = zoneStats; bool first = !runtime->gc.isIncrementalGCInProgress(); if (first) beginGC(gckind); SliceData data(budget, reason, PRMJ_Now(), JS_GetCurrentEmbedderTime(), GetPageFaultCount()); if (!slices.append(data)) { - // OOM testing fails if we CrashAtUnhandlableOOM here. + // OOM testing fails if we crash here, so set a flag instead. aborted = true; return; } runtime->addTelemetry(JS_TELEMETRY_GC_REASON, reason); // Slice callbacks should only fire for the outermost level if (++gcDepth == 1) {
--- a/js/src/jit-test/tests/baseline/bug847425.js +++ b/js/src/jit-test/tests/baseline/bug847425.js @@ -1,9 +1,9 @@ -// |jit-test| allow-oom +// |jit-test| allow-oom; allow-unhandlable-oom gcparam("maxBytes", gcparam("gcBytes") + 4*1024); var max = 400; function f(b) { if (b) { f(b - 1); } else { g = { apply:function(x,y) { }
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-978802.js @@ -0,0 +1,17 @@ +load(libdir + 'oomTest.js'); + +oomTest(() => { + try { + var max = 400; + function f(b) { + if (b) { + f(b - 1); + } else { + g = {}; + } + g.apply(null, arguments); + } + f(max - 1); + } catch(exc0) {} + f(); +});
--- a/js/src/jit-test/tests/modules/module-declaration-instantiation.js +++ b/js/src/jit-test/tests/modules/module-declaration-instantiation.js @@ -1,35 +1,30 @@ // Exercise ModuleDeclarationInstantiation() operation. -function parseAndInstantiate(source) { - let m = parseModule(source); - m.declarationInstantiation(); - return m.environment; -} - function testModuleEnvironment(module, expected) { - var actual = Object.keys(module.environment); + var actual = getModuleEnvironmentNames(module); assertEq(actual.length, expected.length); for (var i = 0; i < actual.length; i++) { assertEq(actual[i], expected[i]); } } // Check the environment of an empty module. -let e = parseAndInstantiate(""); -assertEq(Object.keys(e).length, 0); +let m = parseModule(""); +m.declarationInstantiation(); +testModuleEnvironment(m, []); let moduleRepo = new Map(); setModuleResolveHook(function(module, specifier) { if (specifier in moduleRepo) return moduleRepo[specifier]; throw "Module " + specifier + " not found"; }); -a = moduleRepo['a'] = parseModule("var x = 1; export { x };"); -b = moduleRepo['b'] = parseModule("import { x as y } from 'a';"); +let a = moduleRepo['a'] = parseModule("var x = 1; export { x };"); +let b = moduleRepo['b'] = parseModule("import { x as y } from 'a';"); a.declarationInstantiation(); b.declarationInstantiation(); testModuleEnvironment(a, ['x']); testModuleEnvironment(b, ['y']);
--- a/js/src/jit-test/tests/modules/module-environment.js +++ b/js/src/jit-test/tests/modules/module-environment.js @@ -1,20 +1,18 @@ load(libdir + "class.js"); // Test top-level module environment function testInitialEnvironment(source, expected) { - print(source); - let m = parseModule(source); - let scope = m.initialEnvironment; - let keys = Object.keys(scope); - assertEq(keys.length, expected.length); + let module = parseModule(source); + let names = getModuleEnvironmentNames(module); + assertEq(names.length, expected.length); expected.forEach(function(name) { - assertEq(name in scope, true); + assertEq(names.includes(name), true); }); } testInitialEnvironment('', []); testInitialEnvironment('var x = 1;', ['x']); testInitialEnvironment('let x = 1;', ['x']); testInitialEnvironment("if (true) { let x = 1; }", []); testInitialEnvironment("if (true) { var x = 1; }", ['x']);
--- a/js/src/jit-test/tests/modules/module-evaluation.js +++ b/js/src/jit-test/tests/modules/module-evaluation.js @@ -21,25 +21,25 @@ assertEq(typeof parseAndEvaluate(""), "u // Check evaluation returns evaluation result the first time, then undefined. let m = parseModule("1"); m.declarationInstantiation(); assertEq(m.evaluation(), 1); assertEq(typeof m.evaluation(), "undefined"); // Check top level variables are initialized by evaluation. m = parseModule("export var x = 2 + 2;"); -assertEq(typeof m.initialEnvironment.x, "undefined"); +assertEq(typeof getModuleEnvironmentValue(m, "x"), "undefined"); m.declarationInstantiation(); m.evaluation(); -assertEq(m.environment.x, 4); +assertEq(getModuleEnvironmentValue(m, "x"), 4); m = parseModule("export let x = 2 * 3;"); m.declarationInstantiation(); m.evaluation(); -assertEq(m.environment.x, 6); +assertEq(getModuleEnvironmentValue(m, "x"), 6); // Set up a module to import from. let a = moduleRepo['a'] = parseModule(`var x = 1; export { x }; export default 2; export function f(x) { return x + 1; }`); @@ -94,9 +94,10 @@ assertDeepEq(parseAndEvaluate(`import { import { x as x2, y as y2 } from 'c2'; [x1, y1, x2, y2]`), [1, 2, 1, 2]); // Import access in functions m = parseModule("import { x } from 'a'; function f() { return x; }") m.declarationInstantiation(); m.evaluation(); -assertEq(m.environment.f(), 1); +let f = getModuleEnvironmentValue(m, "f"); +assertEq(f(), 1);
--- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -2026,17 +2026,17 @@ SnapshotIterator::floatAllocationPointer Value SnapshotIterator::maybeRead(const RValueAllocation& a, MaybeReadFallback& fallback) { if (allocationReadable(a)) return allocationValue(a); if (fallback.canRecoverResults()) { if (!initInstructionResults(fallback)) - js::CrashAtUnhandlableOOM("Unable to recover allocations."); + MOZ_CRASH("Unable to recover allocations."); if (allocationReadable(a)) return allocationValue(a); MOZ_ASSERT_UNREACHABLE("All allocations should be readable."); } return fallback.unreadablePlaceholder();
--- a/js/src/jit/arm/Assembler-arm.h +++ b/js/src/jit/arm/Assembler-arm.h @@ -988,17 +988,17 @@ class BOffImm uint32_t data; public: explicit BOffImm(int offset) : data ((offset - 8) >> 2 & 0x00ffffff) { MOZ_ASSERT((offset & 0x3) == 0); if (!IsInRange(offset)) - CrashAtUnhandlableOOM("BOffImm"); + MOZ_CRASH("BOffImm offset out of range"); } explicit BOffImm() : data(INVALID) { } private: BOffImm(Instruction& inst);
--- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -1194,29 +1194,31 @@ JS::AutoCheckRequestDepth::~AutoCheckReq MOZ_ASSERT(cx->runtime()->checkRequestDepth != 0); cx->runtime()->checkRequestDepth--; } } #endif #ifdef JS_CRASH_DIAGNOSTICS -void CompartmentChecker::check(InterpreterFrame* fp) +void +CompartmentChecker::check(InterpreterFrame* fp) { if (fp) check(fp->scopeChain()); } -void CompartmentChecker::check(AbstractFramePtr frame) +void +CompartmentChecker::check(AbstractFramePtr frame) { if (frame) check(frame.scopeChain()); } #endif void -js::CrashAtUnhandlableOOM(const char* reason) +AutoEnterOOMUnsafeRegion::crash(const char* reason) { char msgbuf[1024]; JS_snprintf(msgbuf, sizeof(msgbuf), "[unhandlable oom] %s", reason); MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__); MOZ_CRASH(); }
--- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -3543,30 +3543,34 @@ js::CloneScriptIntoFunction(JSContext* c // // This is so that when cloning nested functions, they can walk the static // scope chain via fun and correctly compute the presence of a // non-syntactic global. RootedScript dst(cx, CreateEmptyScriptForClone(cx, enclosingScope, src)); if (!dst) return nullptr; + // Save flags in case we need to undo the early mutations. + const int preservedFlags = fun->flags(); + dst->setFunction(fun); Rooted<LazyScript*> lazy(cx); if (fun->isInterpretedLazy()) { lazy = fun->lazyScriptOrNull(); fun->setUnlazifiedScript(dst); } else { fun->initScript(dst); } if (!detail::CopyScript(cx, fun, src, dst)) { if (lazy) fun->initLazyScript(lazy); else fun->setScript(nullptr); + fun->setFlags(preservedFlags); return nullptr; } return dst; } DebugScript* JSScript::debugScript() @@ -4018,27 +4022,21 @@ JSScript::argumentsOptimizationFailed(JS * implies fp->hasArgsObj", the Ion bail mechanism will create an * arguments object right after restoring the BaselineFrame and before * entering Baseline code (in jit::FinishBailoutToBaseline). */ if (i.isIon()) continue; AbstractFramePtr frame = i.abstractFramePtr(); if (frame.isFunctionFrame() && frame.script() == script) { + /* We crash on OOM since cleaning up here would be complicated. */ + AutoEnterOOMUnsafeRegion oomUnsafe; ArgumentsObject* argsobj = ArgumentsObject::createExpected(cx, frame); - if (!argsobj) { - /* - * We can't leave stack frames with script->needsArgsObj but no - * arguments object. It is, however, safe to leave frames with - * an arguments object but !script->needsArgsObj. - */ - script->needsArgsObj_ = false; - return false; - } - + if (!argsobj) + oomUnsafe.crash("JSScript::argumentsOptimizationFailed"); SetFrameArgumentsObject(cx, frame, script, argsobj); } } return true; } bool
--- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1289,16 +1289,33 @@ intrinsic_HostResolveImportedModule(JSCo return false; } args.rval().set(result); return true; } static bool +intrinsic_GetModuleEnvironment(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>()); + RootedModuleEnvironmentObject env(cx, module->environment()); + args.rval().setUndefined(); + if (!env) { + args.rval().setUndefined(); + return true; + } + + args.rval().setObject(*env); + return true; +} + +static bool intrinsic_CreateModuleEnvironment(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>()); module->createEnvironment(); args.rval().setUndefined(); return true; @@ -1581,17 +1598,18 @@ static const JSFunctionSpec intrinsic_fu JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0), JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0), JS_FN("regexp_construct_no_statics", regexp_construct_no_statics, 2,0), JS_FN("IsModule", intrinsic_IsModule, 1, 0), JS_FN("CallModuleMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<ModuleObject>>, 2, 0), JS_FN("HostResolveImportedModule", intrinsic_HostResolveImportedModule, 2, 0), - JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 2, 0), + JS_FN("GetModuleEnvironment", intrinsic_GetModuleEnvironment, 1, 0), + JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 1, 0), JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0), JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0), JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0), JS_FS_END }; void
--- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -729,19 +729,23 @@ SCOutput::extractBuffer(uint64_t** datap JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX); JSStructuredCloneWriter::~JSStructuredCloneWriter() { // Free any transferable data left lying around in the buffer uint64_t* data; size_t size; - MOZ_ALWAYS_TRUE(extractBuffer(&data, &size)); - DiscardTransferables(data, size, callbacks, closure); - js_free(data); + { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!extractBuffer(&data, &size)) + oomUnsafe.crash("Unable to extract clone buffer"); + DiscardTransferables(data, size, callbacks, closure); + js_free(data); + } } bool JSStructuredCloneWriter::parseTransferable() { MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data"); if (transferable.isNull() || transferable.isUndefined())
--- a/layout/reftests/svg/filters/feDiffuseLighting-1-ref.svg +++ b/layout/reftests/svg/filters/feDiffuseLighting-1-ref.svg @@ -1,7 +1,16 @@ <!-- Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ --> <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'> - <image width='100' height='100' xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG6SURBVHhe7d3BbcJAGAXhJYXQg32mCV8p2vRAEb4mREoJe/g2GiTf55/hiRu+jTG+P08fxMAXwhHGn4GCYF+FghQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDGfqQh6Px9j3HTtxLZypQZ7P5ziOYy0DGO3UIPf7fWzbhp24Fs7UIGudbtIWBOtSkIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFrIfw7yfr/H6/XCTlwL5/bBnfY+9d8/47+ua5znuZYFiHZqEOiuZVH6DcHSFaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtBAvyA+wPEw79WuphAAAAAElFTkSuQmCC"/> + <image width='100' height='100' + xlink:href="data:image/png;base64, +iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI +WXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3wkbDDIilL5aRAAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl +YXRlZCB3aXRoIEdJTVBkLmUHAAAA9UlEQVR42u3csRHDIBBFwbPHddADiqmEduhFlSiWelBOSirX +YFsB49lXAMHf4DIeEXGFpqi1Fk8zzFPOGchsAQEiIEAEBIiAABEQIAIiIEAEBIiAAPmgUkosy2LV +H3rd+VitNXrvse+7ZWcASSlFSsmqbggQAQEiIAICRECACAgQAQEiIEAERECACAgQAQEiIEAERECA +CAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAfnHbv3Z+jzP6L1bdRaQdV1j +jGHVWUC2bbOoGwJEQIAIiIAAERAgAgJEQIAIiIAAERAg+qrjOOINBcEbJFN4kugAAAAASUVORK5C +YII="/> </svg>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/filters/filter-lighting-region-ref.svg @@ -0,0 +1,11 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<svg xmlns="http://www.w3.org/2000/svg" width="600px" height="300px"> + + <rect x="50" y="50" width="200" height="200" fill="black" /> + + <rect x="340" y="40" width="220" height="220" fill="grey" /> + <rect x="350" y="50" width="200" height="200" fill="white" /> +</svg>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/filters/filter-lighting-region.svg @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<svg xmlns="http://www.w3.org/2000/svg" width="600px" height="300px"> + <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=1203376 --> + <defs> + <!-- the filter lights are intentionally chosen to fill the entire area with + a solid color since we're only testing the extents of the filter region + --> + <filter id="diffuse" x="-50%" y="-50%" width="200%" height="200%"> + <!-- gives a black filter region --> + <feDiffuseLighting lighting-color="black"> + <feDistantLight /> + </feDiffuseLighting> + </filter> + + <filter id="specular" x="-50%" y="-50%" width="200%" height="200%"> + <!-- gives a white filter region --> + <feSpecularLighting lighting-color="white" specularConstant="100"> + <feDistantLight elevation="90"/> + </feSpecularLighting> + </filter> + </defs> + + <rect x="100" y="100" width="100" height="100" filter="url(#diffuse)" /> + + <rect x="340" y="40" width="220" height="220" fill="grey" /> + <rect x="400" y="100" width="100" height="100" filter="url(#specular)" /> +</svg>
--- a/layout/reftests/svg/filters/reftest.list +++ b/layout/reftests/svg/filters/reftest.list @@ -104,19 +104,21 @@ fuzzy(2,500) == feDisplacementMap-colour == feMorphology-radius-negative-02.svg pass.svg == feMorphology-radius-zero-01.svg pass.svg == feMorphology-radius-zero-02.svg pass.svg == feTile-large-01.svg pass.svg == feTile-large-02.svg feTile-large-02-ref.svg == feTile-outside-01.svg feTile-outside-01-ref.svg -fuzzy(1,119) == feDiffuseLighting-1.svg feDiffuseLighting-1-ref.svg +fuzzy(1,217) == feDiffuseLighting-1.svg feDiffuseLighting-1-ref.svg fuzzy(2,2659) skip-if(d2d) == feSpecularLighting-1.svg feSpecularLighting-1-ref.svg +== filter-lighting-region.svg filter-lighting-region-ref.svg + == fePointLight-zoomed-page.svg fePointLight-zoomed-page-ref.svg == feTurbulence-offset.svg feTurbulence-offset-ref.svg == outside-sourcegraphic-1.svg outside-sourcegraphic-ref.svg == outside-sourcegraphic-2.svg outside-sourcegraphic-ref.svg == outside-sourcegraphic-3.svg outside-sourcegraphic-ref.svg
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c @@ -279,18 +279,16 @@ static void nr_ice_candidate_mark_done(n assert(0); return; } /* If this is a relay candidate, there's likely to be a srflx that is * piggybacking on it. Make sure it is marked done too. */ if ((cand->type == RELAYED) && cand->u.relayed.srvflx_candidate) { nr_ice_candidate *srflx=cand->u.relayed.srvflx_candidate; - /* Calling done_cb can destroy this, make sure it doesn't dangle. */ - cand->u.relayed.srvflx_candidate=0; if (state == NR_ICE_CAND_STATE_INITIALIZED && nr_turn_client_get_mapped_address(cand->u.relayed.turn, &srflx->addr)) { r_log(LOG_ICE, LOG_WARNING, "ICE(%s)/CAND(%s): Failed to get mapped address from TURN allocate response, srflx failed.", cand->ctx->label, cand->label); nr_ice_candidate_mark_done(srflx, NR_ICE_CAND_STATE_FAILED); } else { nr_ice_candidate_mark_done(srflx, state); } @@ -320,23 +318,27 @@ int nr_ice_candidate_destroy(nr_ice_cand switch(cand->type){ case HOST: break; #ifdef USE_TURN case RELAYED: if (cand->u.relayed.turn_handle) nr_ice_socket_deregister(cand->isock, cand->u.relayed.turn_handle); + if (cand->u.relayed.srvflx_candidate) + cand->u.relayed.srvflx_candidate->u.srvrflx.relay_candidate=0; nr_turn_client_ctx_destroy(&cand->u.relayed.turn); nr_socket_destroy(&cand->u.relayed.turn_sock); break; #endif /* USE_TURN */ case SERVER_REFLEXIVE: if (cand->u.srvrflx.stun_handle) nr_ice_socket_deregister(cand->isock, cand->u.srvrflx.stun_handle); + if (cand->u.srvrflx.relay_candidate) + cand->u.srvrflx.relay_candidate->u.relayed.srvflx_candidate=0; nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun); break; default: break; } NR_async_timer_cancel(cand->delay_timer); NR_async_timer_cancel(cand->ready_cb_timer);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h +++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h @@ -71,16 +71,19 @@ struct nr_ice_candidate_ { void *delay_timer; void *resolver_handle; /* Holding data for STUN and TURN */ union { struct { nr_stun_client_ctx *stun; void *stun_handle; + /* If this is a srflx that is piggybacking on a relay candidate, this is + * a back pointer to that relay candidate. */ + nr_ice_candidate *relay_candidate; } srvrflx; struct { nr_turn_client_ctx *turn; nr_ice_turn_server *server; nr_ice_candidate *srvflx_candidate; nr_socket *turn_sock; void *turn_handle; } relayed;
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c @@ -281,17 +281,20 @@ static int nr_ice_component_initialize_u } /* relayed*/ if(r=nr_socket_turn_create(sock, &turn_sock)) ABORT(r); if(r=nr_ice_candidate_create(ctx,component, isock,turn_sock,RELAYED,0, &ctx->turn_servers[j].turn_server,component->component_id,&cand)) ABORT(r); - cand->u.relayed.srvflx_candidate=srvflx_cand; + if (srvflx_cand) { + cand->u.relayed.srvflx_candidate=srvflx_cand; + srvflx_cand->u.srvrflx.relay_candidate=cand; + } cand->u.relayed.server=&ctx->turn_servers[j]; TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp); component->candidate_ct++; cand=0; } #endif /* USE_TURN */
--- a/mfbt/Assertions.h +++ b/mfbt/Assertions.h @@ -16,16 +16,30 @@ #include "mozilla/Attributes.h" #include "mozilla/Compiler.h" #include "mozilla/Likely.h" #include "mozilla/MacroArgs.h" #ifdef MOZ_DUMP_ASSERTION_STACK #include "nsTraceRefcnt.h" #endif +#if defined(MOZ_CRASHREPORTER) && defined(MOZILLA_INTERNAL_API) && \ + !defined(MOZILLA_EXTERNAL_LINKAGE) && defined(__cplusplus) +# define MOZ_CRASH_CRASHREPORT +namespace CrashReporter { +// This declaration is present here as well as in nsExceptionHandler.h +// nsExceptionHandler.h is not directly included in this file as it includes +// windows.h, which can cause problems when it is imported into some files due +// to the number of macros defined. +// XXX If you change this definition - also change the definition in +// nsExceptionHandler.h +void AnnotateMozCrashReason(const char* aReason); +} // namespace CrashReporter +#endif + #include <stddef.h> #include <stdio.h> #include <stdlib.h> #ifdef WIN32 /* * TerminateProcess and GetCurrentProcess are defined in <winbase.h>, which * further depends on <windef.h>. We hardcode these few definitions manually * because those headers clutter the global namespace with a significant @@ -244,17 +258,25 @@ MOZ_ReportCrash(const char* aStr, const * * If we're a DEBUG build and we crash at a MOZ_CRASH which provides an * explanation-string, we print the string to stderr. Otherwise, we don't * print anything; this is because we want MOZ_CRASH to be 100% safe in release * builds, and it's hard to print to stderr safely when memory might have been * corrupted. */ #ifndef DEBUG -# define MOZ_CRASH(...) MOZ_REALLY_CRASH() +# ifdef MOZ_CRASH_CRASHREPORT +# define MOZ_CRASH(...) \ + do { \ + CrashReporter::AnnotateMozCrashReason("MOZ_CRASH(" __VA_ARGS__ ")"); \ + MOZ_REALLY_CRASH(); \ + } while (0) +# else +# define MOZ_CRASH(...) MOZ_REALLY_CRASH() +# endif #else # define MOZ_CRASH(...) \ do { \ MOZ_ReportCrash("" __VA_ARGS__, __FILE__, __LINE__); \ MOZ_REALLY_CRASH(); \ } while (0) #endif @@ -504,10 +526,11 @@ struct AssertionConditionType do { \ if ( ( expr ) ) { \ /* Silence MOZ_WARN_UNUSED_RESULT. */ \ } \ } while (0) #endif #undef MOZ_DUMP_ASSERTION_STACK +#undef MOZ_CRASH_CRASHREPORT #endif /* mozilla_Assertions_h */
--- a/netwerk/base/nsIPackagedAppUtils.idl +++ b/netwerk/base/nsIPackagedAppUtils.idl @@ -19,17 +19,17 @@ interface nsIVerificationCallback; * store the hash values of each resource. When a resource is ready, Necko * will calculate its hash value (including the header like Content-Location: xxx), * and calls checkIntegrity(...) to verify the integrity. * * For more detail: * https://wiki.mozilla.org/FirefoxOS/New_security_model/Packaging */ -[scriptable, uuid(d0a98a69-a215-4cf9-abb3-7a0b9237cd27)] +[scriptable, uuid(cc245638-6a38-4f70-8d77-21c55aabd636)] interface nsIPackagedAppUtils : nsISupports { /** * @aHeader is the package's header including * - "manifest-signature: xxxxxx" (base64 encoding) * @aManifest is the manifest of the package * - the multipart header is included * - manifest must be the first resource of the package @@ -43,16 +43,22 @@ interface nsIPackagedAppUtils : nsISuppo * @aFileName is the name of a resource in the package * @aHashValue is the hash value of this resource named aFileName * - aHashValue should be computed by the caller of this method * @aCallback is the callback, see comments of nsIVerificationCallback below */ void checkIntegrity(in ACString aFileName, in ACString aHashValue, in nsIVerificationCallback aVerifier); + + /** + * The package identifier for signed package. Only available after the + * manifest is verified. + */ + readonly attribute ACString packageIdentifier; }; /** * The callback passed to verifyManifest and checkIntegrity */ [scriptable, uuid(e1912028-93e5-4378-aa3f-a58702937169)] interface nsIVerificationCallback : nsISupports {
--- a/netwerk/base/nsStandardURL.cpp +++ b/netwerk/base/nsStandardURL.cpp @@ -417,39 +417,42 @@ nsStandardURL::NormalizeIDN(const nsCSub return true; } result.Truncate(); return false; } bool -nsStandardURL::ValidIPv6orHostname(const char *host) +nsStandardURL::ValidIPv6orHostname(const char *host, uint32_t length) { - if (!host || !*host) { - // Should not be NULL or empty string + if (!host) { return false; } - int32_t length = strlen(host); + if (length != strlen(host)) { + // Embedded null + return false; + } bool openBracket = host[0] == '['; bool closeBracket = host[length - 1] == ']'; if (openBracket && closeBracket) { return net_IsValidIPv6Addr(host + 1, length - 2); } if (openBracket || closeBracket) { // Fail if only one of the brackets is present return false; } - if (PL_strchr(host, ':')) { - // Hostnames should not contain a colon + const char *end = host + length; + if (end != net_FindCharInSet(host, end, "\t\n\v\f\r #/:?@[\\]")) { + // % is allowed because we don't do hostname percent decoding yet. return false; } return true; } void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char *path) @@ -577,16 +580,21 @@ nsStandardURL::BuildNormalizedSpec(const if (tempHost.Contains('\0')) return NS_ERROR_MALFORMED_URI; // null embedded in hostname if (tempHost.Contains(' ')) return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname if ((useEncHost = NormalizeIDN(tempHost, encHost))) approxLen += encHost.Length(); else approxLen += mHost.mLen; + + if ((useEncHost && !ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) || + (!useEncHost && !ValidIPv6orHostname(tempHost.BeginReading(), tempHost.Length()))) { + return NS_ERROR_MALFORMED_URI; + } } // // generate the normalized URL string // // approxLen should be correct or 1 high if (!mSpec.SetLength(approxLen+1, mozilla::fallible)) // buf needs a trailing '\0' below return NS_ERROR_OUT_OF_MEMORY; @@ -1604,32 +1612,32 @@ nsStandardURL::SetHost(const nsACString if (strlen(host) < flat.Length()) return NS_ERROR_MALFORMED_URI; // found embedded null // For consistency with SetSpec/nsURLParsers, don't allow spaces // in the hostname. if (strchr(host, ' ')) return NS_ERROR_MALFORMED_URI; - if (!ValidIPv6orHostname(host)) { - return NS_ERROR_MALFORMED_URI; - } - InvalidateCache(); mHostEncoding = eEncoding_ASCII; - int32_t len; + uint32_t len; nsAutoCString hostBuf; if (NormalizeIDN(flat, hostBuf)) { host = hostBuf.get(); len = hostBuf.Length(); } else len = flat.Length(); + if (!ValidIPv6orHostname(host, len)) { + return NS_ERROR_MALFORMED_URI; + } + if (mHost.mLen < 0) { int port_length = 0; if (mPort != -1) { nsAutoCString buf; buf.Assign(':'); buf.AppendInt(mPort); port_length = buf.Length(); }
--- a/netwerk/base/nsStandardURL.h +++ b/netwerk/base/nsStandardURL.h @@ -171,17 +171,17 @@ protected: virtual nsresult EnsureFile(); private: int32_t Port() { return mPort == -1 ? mDefaultPort : mPort; } void Clear(); void InvalidateCache(bool invalidateCachedFile = true); - bool ValidIPv6orHostname(const char *host); + bool ValidIPv6orHostname(const char *host, uint32_t aLen); bool NormalizeIDN(const nsCSubstring &host, nsCString &result); void CoalescePath(netCoalesceFlags coalesceFlag, char *path); uint32_t AppendSegmentToBuf(char *, uint32_t, const char *, URLSegment &, const nsCString *esc=nullptr, bool useEsc = false); uint32_t AppendToBuf(char *, uint32_t, const char *, uint32_t); nsresult BuildNormalizedSpec(const char *spec);
--- a/netwerk/protocol/http/PackagedAppUtils.js +++ b/netwerk/protocol/http/PackagedAppUtils.js @@ -8,17 +8,17 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); const PACKAGEDAPPUTILS_CONTRACTID = "@mozilla.org/network/packaged-app-utils;1"; const PACKAGEDAPPUTILS_CID = Components.ID("{fe8f1c2e-3c13-11e5-9a3f-bbf47d1e6697}"); function PackagedAppUtils() { - + this.packageIdentifier = ''; } var DEBUG = 0 function debug(s) { if (DEBUG) { dump("-*- PackagedAppUtils: " + s + "\n"); } } @@ -52,17 +52,19 @@ PackagedAppUtils.prototype = { // Base64 decode signature = atob(signature); // Remove header let manifestBody = aManifest.substr(aManifest.indexOf('\r\n\r\n') + 4); debug("manifestBody: " + manifestBody); // Parse manifest, store resource hashes - this.resources = JSON.parse(manifestBody)["moz-resources"]; + let manifestObj = JSON.parse(manifestBody); + this.resources = manifestObj["moz-resources"]; + this.packageIdentifier = manifestObj["package-identifier"]; } catch (e) { debug("JSON parsing failure"); aCallback.fireVerifiedEvent(true, false); return; } let manifestStream = Cc["@mozilla.org/io/string-input-stream;1"] .createInstance(Ci.nsIStringInputStream);
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp +++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp @@ -659,16 +659,20 @@ nsMultiMixedConv::OnDataAvailable(nsIReq } else { // Token is probed. mToken = Substring(tokenPos, mTokenLen); mPreamble = nsCString(Substring(buffer, tokenPos)); // Push the cursor to the token so that the while loop below will // find token from the right position. cursor = tokenPos; + + // Update bufLen to exlude the preamble. Otherwise, the first + // |SendData| would claim longer buffer length. + bufLen -= mPreamble.Length(); } } else { // If the boundary was set in the header, // we need to check it matches with the one in the file. if (mTokenLen && !StringBeginsWith(Substring(firstBuffer, 2), mToken)) { return NS_ERROR_FAILURE; }
--- a/netwerk/test/unit/test_multipart_streamconv_application_package.js +++ b/netwerk/test/unit/test_multipart_streamconv_application_package.js @@ -71,21 +71,27 @@ function contentHandler_chunked_headers( function contentHandler_type_missing(metadata, response) { response.setHeader("Content-Type", 'text/plain'); var body = testData.getData(); response.bodyOutputStream.write(body, body.length); } -function contentHandler_with_package_header(metadata, response) +function contentHandler_with_package_header(chunkSize, metadata, response) { response.setHeader("Content-Type", 'application/package'); var body = testData.packageHeader + testData.getData(); - response.bodyOutputStream.write(body, body.length); + + response.bodyOutputStream.write(body.substring(0,chunkSize), chunkSize); + response.processAsync(); + do_timeout(5, function() { + response.bodyOutputStream.write(body.substring(chunkSize), body.length-chunkSize); + response.finish(); + }); } var testData = { packageHeader: 'manifest-signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\r\n', content: [ { headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n <head>\r\n <script src=\"/scripts/app.js\"></script>\r\n ...\r\n </head>\r\n ...\r\n</html>\r\n", type: "text/html" }, { headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" }, { headers: ["Content-Location: /scripts/helpers/math.js", "Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" } @@ -239,39 +245,69 @@ function test_multipart_content_type_oth "*/*", new multipartListener(testData, true), null); var chan = make_channel(uri + "/multipart4"); chan.asyncOpen(conv, null); } -function test_multipart_package_header() { +function test_multipart_package_header(aChunkSize) { var streamConv = Cc["@mozilla.org/streamConverters;1"] .getService(Ci.nsIStreamConverterService); var conv = streamConv.asyncConvertData("application/package", "*/*", new multipartListener(testData, false, true), null); - var chan = make_channel(uri + "/multipart5"); + var chan = make_channel(uri + "/multipart5_" + aChunkSize); chan.asyncOpen(conv, null); } +// Bug 1212223 - Test multipart with package header and different chunk size. +// Use explict function name to make the test case log more readable. + +function test_multipart_package_header_50() { + return test_multipart_package_header(50); +} + +function test_multipart_package_header_100() { + return test_multipart_package_header(100); +} + +function test_multipart_package_header_150() { + return test_multipart_package_header(150); +} + +function test_multipart_package_header_200() { + return test_multipart_package_header(200); +} + function run_test() { httpserver = new HttpServer(); httpserver.registerPathHandler("/multipart", contentHandler); httpserver.registerPathHandler("/multipart2", contentHandler_with_boundary); httpserver.registerPathHandler("/multipart3", contentHandler_chunked_headers); httpserver.registerPathHandler("/multipart4", contentHandler_type_missing); - httpserver.registerPathHandler("/multipart5", contentHandler_with_package_header); + + // Bug 1212223 - Test multipart with package header and different chunk size. + httpserver.registerPathHandler("/multipart5_50", contentHandler_with_package_header.bind(null, 50)); + httpserver.registerPathHandler("/multipart5_100", contentHandler_with_package_header.bind(null, 100)); + httpserver.registerPathHandler("/multipart5_150", contentHandler_with_package_header.bind(null, 150)); + httpserver.registerPathHandler("/multipart5_200", contentHandler_with_package_header.bind(null, 200)); + httpserver.start(-1); run_next_test(); } add_test(test_multipart); add_test(test_multipart_with_boundary); add_test(test_multipart_chunked_headers); add_test(test_multipart_content_type_other); -add_test(test_multipart_package_header); + +// Bug 1212223 - Test multipart with package header and different chunk size. +add_test(test_multipart_package_header_50); +add_test(test_multipart_package_header_100); +add_test(test_multipart_package_header_150); +add_test(test_multipart_package_header_200);
--- a/netwerk/test/unit/test_packaged_app_utils.js +++ b/netwerk/test/unit/test_packaged_app_utils.js @@ -1,24 +1,24 @@ const header_missing_signature = "header1: content1"; const header_invalid_signature = "header1: content1\r\nmanifest-signature: invalid-signature\r\n"; -const header = "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MDkxMDA4MDQ0M1owIwYJKoZIhvcNAQkEMRYEFNg6lGtV9bJbL2hA0c5DdOeuCQ6lMA0GCSqGSIb3DQEBAQUABIIBAKGziwzA5Q38rIvNUDHCjYVTR1FhALGZv677Tc2+pwd82W6O9q5GG9IfkF3ajb1fquUIpGPkf7r0oiO4udC8cSehA+lfhR94A8aCM9UhzvTtRI3tFB+TPSk1UcXlX8tB7dNkx4zC06ujlSaRKkmaZODVXQFEcsF6CKMApsBuUJrwzvbQqVi2KHXUO6oGlMEyt4tY+g2OY/vyxGajfAL49dAYOTtrV0arvJvoTYh+E0iSrsbuiuAxKAVjK/QnLJoV/dTaCqW4t3lzHrpE3+avqMXiewxu84VJSURxoryY89uAZS9+4MKrSOGlGCJy/8xDIAm9pi6lPJBP2pIRjaRt9r0=\r\n"; +const header = "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTAwMTIxMTEwNlowIwYJKoZIhvcNAQkEMRYEFHAisUYrrt+gBxYFhZ5KQQusOmN3MA0GCSqGSIb3DQEBAQUABIIBACHW4V0BsPWOvWrGOTRj6mPpNbH/JI1bN2oyqQZrpUQoaBY+BbYxO7TY4Uwe+aeIR/TTPJznOMF/dl3Bna6TPabezU4ylg7TVFI6W7zC5f5DZKp+Xv6uTX6knUzbbW1fkJqMtE8hGUzYXc3/C++Ci6kuOzrpWOhk6DpJHeUO/ioV56H0+QK/oMAjYpEsOohaPqvTPNOBhMQ0OQP3bmuJ6HcjZ/oz96PpzXUPKT1tDe6VykIYkV5NvtC8Tu2lDbYvp9ug3gyDgdyNSV47y5i/iWkzEhsAJB+9Z50wKhplnkxxVHEXkB/6tmfvExvQ28gLd/VbaEGDX2ljCaTSUjhD0o0=\r\n"; const manifest = "Content-Location: manifest.webapp\r\n" + "Content-Type: application/x-web-app-manifest+json\r\n\r\n" + `{ "name": "My App", "moz-resources": [ { "src": "page2.html", "integrity": "JREF3JbXGvZ+I1KHtoz3f46ZkeIPrvXtG4VyFQrJ7II=" }, { "src": "index.html", - "integrity": "B5Phw8L1tpyRBkI0gwg/evy1fgtMlMq3BIY3Q8X0rYU=" + "integrity": "zEubR310nePwd30NThIuoCxKJdnz7Mf5z+dZHUbH1SE=" }, { "src": "scripts/script.js", "integrity": "6TqtNArQKrrsXEQWu3D9ZD8xvDRIkhyV6zVdTcmsT5Q=" }, { "src": "scripts/library.js", "integrity": "TN2ByXZiaBiBCvS4MeZ02UyNi44vED+KjdjLInUl4o8=" @@ -29,17 +29,17 @@ const manifest = "Content-Location: mani "systemXHR": { "description": "Needed to download stuff" }, "devicestorage:pictures": { "description": "Need to load pictures" } } ], - "moz-uuid": "some-uuid", + "package-identifier": "611FC2FE-491D-4A47-B3B3-43FBDF6F404F", "moz-package-location": "https://example.com/myapp/app.pak", "description": "A great app!" }`; const manifest_missing_moz_resources = "Content-Location: manifest.webapp\r\n" + "Content-Type: application/x-web-app-manifest+json\r\n\r\n" + `{ "name": "My App", @@ -81,16 +81,24 @@ function test_verify_manifest(aHeader, a return function() { do_test_pending(); packagedAppUtils = Cc["@mozilla.org/network/packaged-app-utils;1"]. createInstance(Ci.nsIPackagedAppUtils); let fakeVerifier = { fireVerifiedEvent: function(aForManifest, aSuccess) { ok(aForManifest, "aForManifest should be true"); equal(aSuccess, aShouldSucceed, "Expected verification result: " + aShouldSucceed); + + // Verify packageIdentifier if it's a successful verification. + if (aShouldSucceed) { + equal(packagedAppUtils.packageIdentifier, + '611FC2FE-491D-4A47-B3B3-43FBDF6F404F', + 'package identifier'); + } + do_test_finished(); run_next_test(); } }; packagedAppUtils.verifyManifest(aHeader, aManifest, fakeVerifier); } }
--- a/security/sandbox/mac/Sandbox.mm +++ b/security/sandbox/mac/Sandbox.mm @@ -412,16 +412,20 @@ static const char contentSandboxRules[] "; bug 1153809\n" " (allow iokit-open\n" " (iokit-user-client-class \"NVDVDContextTesla\")\n" " (iokit-user-client-class \"Gen6DVDContext\"))\n" "\n" "; bug 1190032\n" " (allow file*\n" " (home-regex \"/Library/Caches/TemporaryItems/plugtmp.*\"))\n" + "\n" + "; bug 1201935\n" + " (allow file-read*\n" + " (home-subpath \"/Library/Caches/TemporaryItems\"))\n" " )\n" ")\n"; bool StartMacSandbox(MacSandboxInfo aInfo, std::string &aErrorMessage) { char *profile = NULL; if (aInfo.type == MacSandboxType_Plugin) { if (OSXVersion::OnLionOrLater()) {
new file mode 100644 --- /dev/null +++ b/testing/mozharness/mozharness/mozilla/checksums.py @@ -0,0 +1,21 @@ +def parse_checksums_file(checksums): + """Parses checksums files that the build system generates and uploads: + https://hg.mozilla.org/mozilla-central/file/default/build/checksums.py""" + fileInfo = {} + for line in checksums.splitlines(): + hash_, type_, size, file_ = line.split(None, 3) + size = int(size) + if size < 0: + raise ValueError("Found negative value (%d) for size." % size) + if file_ not in fileInfo: + fileInfo[file_] = {"hashes": {}} + # If the file already exists, make sure that the size matches the + # previous entry. + elif fileInfo[file_]['size'] != size: + raise ValueError("Found different sizes for same file %s (%s and %s)" % (file_, fileInfo[file_]['size'], size)) + # Same goes for the hash. + elif type_ in fileInfo[file_]['hashes'] and fileInfo[file_]['hashes'][type_] != hash_: + raise ValueError("Found different %s hashes for same file %s (%s and %s)" % (type_, file_, fileInfo[file_]['hashes'][type_], hash_)) + fileInfo[file_]['size'] = size + fileInfo[file_]['hashes'][type_] = hash_ + return fileInfo
--- a/testing/mozharness/mozharness/mozilla/release.py +++ b/testing/mozharness/mozharness/mozilla/release.py @@ -35,15 +35,16 @@ class ReleaseMixin(): self.fatal("Release config file %s not found!" % c["release_config_file"]) except RuntimeError: self.fatal("Invalid release config file %s!" % c["release_config_file"]) self.release_config['version'] = rc['version'] self.release_config['buildnum'] = rc['buildNumber'] self.release_config['ftp_server'] = rc['stagingServer'] self.release_config['ftp_user'] = c.get('ftp_user', rc['hgUsername']) self.release_config['ftp_ssh_key'] = c.get('ftp_ssh_key', rc['hgSshKey']) + self.release_config['release_channel'] = rc['releaseChannel'] else: self.info("No release config file; using default config.") for key in ('version', 'buildnum', 'ftp_server', 'ftp_user', 'ftp_ssh_key'): self.release_config[key] = c[key] self.info("Release config:\n%s" % self.release_config) return self.release_config
--- a/testing/mozharness/mozharness/mozilla/signing.py +++ b/testing/mozharness/mozharness/mozilla/signing.py @@ -30,17 +30,17 @@ AndroidSignatureVerificationErrorList = "explanation": "Not signed!" }] # SigningMixin {{{1 class SigningMixin(BaseSigningMixin): """Generic signing helper methods.""" - def query_moz_sign_cmd(self, formats='gpg'): + def query_moz_sign_cmd(self, formats=['gpg']): if 'MOZ_SIGNING_SERVERS' not in os.environ: self.fatal("MOZ_SIGNING_SERVERS not in env; no MOZ_SIGN_CMD for you!") dirs = self.query_abs_dirs() signing_dir = os.path.join(dirs['abs_work_dir'], 'tools', 'release', 'signing') cache_dir = os.path.join(dirs['abs_work_dir'], 'signing_cache') token = os.path.join(dirs['base_work_dir'], 'token') nonce = os.path.join(dirs['base_work_dir'], 'nonce') host_cert = os.path.join(signing_dir, 'host.cert') @@ -49,17 +49,18 @@ class SigningMixin(BaseSigningMixin): python, os.path.join(signing_dir, 'signtool.py'), '--cachedir', cache_dir, '-t', token, '-n', nonce, '-c', host_cert, ] if formats: - cmd += ['-f', formats] + for f in formats: + cmd += ['-f', f] for h in os.environ['MOZ_SIGNING_SERVERS'].split(","): cmd += ['-H', h] return cmd # MobileSigningMixin {{{1 class MobileSigningMixin(AndroidSigningMixin, SigningMixin): def verify_android_signature(self, apk, script=None, key_alias="nightly",
--- a/testing/mozharness/mozharness/mozilla/updates/balrog.py +++ b/testing/mozharness/mozharness/mozilla/updates/balrog.py @@ -85,16 +85,17 @@ class BalrogMixin(object): def submit_balrog_release_pusher(self, dirs): product = self.buildbot_config["properties"]["product"] cmd = [self.query_exe("python"), os.path.join(os.path.join(dirs['abs_tools_dir'], "scripts/updates/balrog-release-pusher.py"))] cmd.extend(["--build-properties", os.path.join(dirs["base_work_dir"], "balrog_props.json")]) cmd.extend(["--buildbot-configs", "https://hg.mozilla.org/build/buildbot-configs"]) cmd.extend(["--release-config", os.path.join(dirs['build_dir'], self.config.get("release_config_file"))]) cmd.extend(["--credentials-file", os.path.join(dirs['base_work_dir'], self.config.get("balrog_credentials_file"))]) + cmd.extend(["--release-channel", self.query_release_config()['release_channel']]) return_codes = [] for server in self.config["balrog_servers"]: server_args = [ "--api-root", server["balrog_api_root"], "--username", self._query_balrog_username(server, product) ]
--- a/testing/mozharness/scripts/b2g_build.py +++ b/testing/mozharness/scripts/b2g_build.py @@ -632,17 +632,17 @@ class B2GBuild(LocalesMixin, PurgeMixin, self.sign_updates() def sign_updates(self): if 'MOZ_SIGNING_SERVERS' not in os.environ: self.info("Skipping signing since no MOZ_SIGNING_SERVERS set") return self.checkout_tools() - cmd = self.query_moz_sign_cmd(formats='b2gmar') + cmd = self.query_moz_sign_cmd(formats=['b2gmar']) cmd.append(self.query_marfile_path()) retval = self.run_command(cmd) if retval != 0: self.fatal("failed to sign complete update", exit_code=2) def prep_upload(self): if not self.query_do_upload():
--- a/testing/mozharness/scripts/mobile_l10n.py +++ b/testing/mozharness/scripts/mobile_l10n.py @@ -161,17 +161,17 @@ class MobileSingleLocale(MockMixin, Loca 'buildnum': rc['buildnum'] } repack_env = self.query_env(partial_env=c.get("repack_env"), replace_dict=replace_dict) if c.get('base_en_us_binary_url') and c.get('release_config_file'): rc = self.query_release_config() repack_env['EN_US_BINARY_URL'] = c['base_en_us_binary_url'] % replace_dict if 'MOZ_SIGNING_SERVERS' in os.environ: - repack_env['MOZ_SIGN_CMD'] = subprocess.list2cmdline(self.query_moz_sign_cmd(formats='jar')) + repack_env['MOZ_SIGN_CMD'] = subprocess.list2cmdline(self.query_moz_sign_cmd(formats=['jar'])) self.repack_env = repack_env return self.repack_env def query_upload_env(self): if self.upload_env: return self.upload_env c = self.config replace_dict = {
new file mode 100644 --- /dev/null +++ b/testing/mozharness/scripts/release/antivirus.py @@ -0,0 +1,192 @@ +from multiprocessing.pool import ThreadPool +import os +import re +import sys +import shutil + +sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0]))) + +from mozharness.base.python import VirtualenvMixin, virtualenv_config_options +from mozharness.base.script import BaseScript + + +class AntivirusScan(BaseScript, VirtualenvMixin): + config_options = [ + [["--product"], { + "dest": "product", + "help": "Product being released, eg: firefox, thunderbird", + }], + [["--version"], { + "dest": "version", + "help": "Version of release, eg: 39.0b5", + }], + [["--build-number"], { + "dest": "build_number", + "help": "Build number of release, eg: 2", + }], + [["--bucket-name"], { + "dest": "bucket_name", + "help": "S3 Bucket to retrieve files from", + }], + [["--exclude"], { + "dest": "excludes", + "action": "append", + "help": "List of filename patterns to exclude. See script source for default", + }], + [["-d", "--download-parallelization"], { + "dest": "download_parallelization", + "default": 6, + "type": "int", + "help": "Number of concurrent file downloads", + }], + [["-s", "--scan-parallelization"], { + "dest": "scan_parallelization", + "default": 4, + "type": "int", + "help": "Number of concurrent file scans", + }], + [["--tools-repo"], { + "dest": "tools_repo", + "default": "https://hg.mozilla.org/build/tools", + }], + [["--tools-revision"], { + "dest": "tools_revision", + "help": "Revision of tools repo to use when downloading extract_and_run_command.py", + }], + ] + virtualenv_config_options + + DEFAULT_EXCLUDES = [ + r"^.*tests.*$", + r"^.*crashreporter.*$", + r"^.*\.zip(\.asc)?$", + r"^.*\.log$", + r"^.*\.txt$", + r"^.*\.asc$", + r"^.*/partner-repacks.*$", + r"^.*.checksums(\.asc)?$", + r"^.*/logs/.*$", + r"^.*/jsshell.*$", + r"^.*json$", + r"^.*/host.*$", + r"^.*/mar-tools/.*$", + r"^.*gecko-unsigned-unaligned.apk$", + r"^.*robocop.apk$", + r"^.*contrib.*" + ] + CACHE_DIR = 'cache' + + def __init__(self): + BaseScript.__init__(self, + config_options=self.config_options, + require_config_file=False, + config={ + "virtualenv_modules": [ + "boto", + "redo", + "mar", + ], + "virtualenv_path": "venv", + }, + all_actions=[ + "create-virtualenv", + "activate-virtualenv", + "get-extract-script", + "get-files", + "scan-files", + "cleanup-cache", + ], + default_actions=[ + "create-virtualenv", + "activate-virtualenv", + "get-extract-script", + "get-files", + "scan-files", + "cleanup-cache", + ], + ) + self.excludes = self.config.get('excludes', self.DEFAULT_EXCLUDES) + self.dest_dir = self.CACHE_DIR + + def _get_candidates_prefix(self): + return "pub/{}/candidates/{}-candidates/build{}".format( + self.config['product'], + self.config["version"], + self.config["build_number"] + ) + + def _matches_exclude(self, keyname): + for exclude in self.excludes: + if re.search(exclude, keyname): + return True + return False + + def get_extract_script(self): + """Gets a copy of extract_and_run_command.py from tools, and the supporting mar.py, + so that we can unpack various files for clam to scan them.""" + remote_file = "{}/raw-file/{}/stage/extract_and_run_command.py".format(self.config["tools_repo"], + self.config["tools_revision"]) + self.download_file(remote_file, file_name="extract_and_run_command.py") + + def get_files(self): + """Pull the candidate files down from S3 for scanning, using parallel requests""" + from boto.s3.connection import S3Connection + from boto.exception import S3CopyError, S3ResponseError + from redo import retry + + # suppress boto debug logging, it's too verbose with --loglevel=debug + import logging + logging.getLogger('boto').setLevel(logging.INFO) + + self.info("Connecting to S3") + conn = S3Connection(anon=True) + self.info("Getting bucket {}".format(self.config["bucket_name"])) + bucket = conn.get_bucket(self.config["bucket_name"]) + + if os.path.exists(self.dest_dir): + self.info('Emptying {}'.format(self.dest_dir)) + shutil.rmtree(self.dest_dir) + os.makedirs(self.dest_dir) + + def worker(item): + source, destination = item + + self.info("Downloading {} to {}".format(source, destination)) + key = bucket.get_key(source) + return retry(key.get_contents_to_filename, + args=(destination, ), + sleeptime=5, max_sleeptime=60, + retry_exceptions=(S3CopyError, S3ResponseError)) + + def find_release_files(): + candidates_prefix = self._get_candidates_prefix() + self.info("Getting key names from candidates") + for key in bucket.list(prefix=candidates_prefix): + keyname = key.name + if self._matches_exclude(keyname): + self.debug("Excluding {}".format(keyname)) + else: + destination = self.dest_dir + keyname.replace(candidates_prefix, '') + dest_dir = os.path.dirname(destination) + if not os.path.isdir(dest_dir): + os.makedirs(dest_dir) + yield (keyname, destination) + + pool = ThreadPool(self.config["download_parallelization"]) + pool.map(worker, find_release_files()) + + def scan_files(self): + """Scan the files we've collected. We do the download and scan concurrently to make + it easier to have a coherent log afterwards. Uses the venv python.""" + self.run_command([self.query_python_path(), 'extract_and_run_command.py', + '-j{}'.format(self.config['scan_parallelization']), + 'clamdscan', '-m', '--no-summary', '--', self.dest_dir]) + + def cleanup_cache(self): + """If we have simultaneous releases in flight an av slave may end up doing another + av job before being recycled, and we need to make sure the full disk is available.""" + shutil.rmtree(self.dest_dir) + + +if __name__ == "__main__": + myScript = AntivirusScan() + myScript.run_and_exit()
new file mode 100644 --- /dev/null +++ b/testing/mozharness/scripts/release/generate-checksums.py @@ -0,0 +1,256 @@ +from multiprocessing.pool import ThreadPool +import os +from os import path +import re +import sys + +sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0]))) + +from mozharness.base.python import VirtualenvMixin, virtualenv_config_options +from mozharness.base.script import BaseScript +from mozharness.base.vcs.vcsbase import VCSMixin +from mozharness.mozilla.checksums import parse_checksums_file +from mozharness.mozilla.signing import SigningMixin + +class ChecksumsGenerator(BaseScript, VirtualenvMixin, SigningMixin, VCSMixin): + config_options = [ + [["--stage-product"], { + "dest": "stage_product", + "help": "Name of product used in file server's directory structure, eg: firefox, mobile", + }], + [["--version"], { + "dest": "version", + "help": "Version of release, eg: 39.0b5", + }], + [["--build-number"], { + "dest": "build_number", + "help": "Build number of release, eg: 2", + }], + [["--bucket-name-prefix"], { + "dest": "bucket_name_prefix", + "help": "Prefix of bucket name, eg: net-mozaws-prod-delivery. This will be used to generate a full bucket name (such as net-mozaws-prod-delivery-{firefox,archive}.", + }], + [["-j", "--parallelization"], { + "dest": "parallelization", + "default": 20, + "type": int, + "help": "Number of checksums file to download concurrently", + }], + [["-f", "--format"], { + "dest": "formats", + "default": [], + "action": "append", + "help": "Format(s) to generate big checksums file for. Default: sha512", + }], + [["--include"], { + "dest": "includes", + "default": [], + "action": "append", + "help": "List of patterns to include in big checksums file. See script source for default.", + }], + [["--tools-repo"], { + "dest": "tools_repo", + "default": "https://hg.mozilla.org/build/tools", + }], + [["--gecko-repo"], { + "dest": "gecko_repo", + "help": "Gecko repo to get upload.py from, eg: https://hg.mozilla.org/releases/mozilla-beta", + }], + [["--gecko-revision"], { + "dest": "gecko_revision", + "help": "Revision of gecko repo to use when getting upload.py. Must be a valid hg revision identifier.", + }], + [["--upload-host"], { + "dest": "upload_host", + "help": "Host to upload big checksums file to, eg: stage.mozilla.org", + }], + [["--upload-user"], { + "dest": "upload_user", + "help": "Username to use when uploading, eg: ffxbld", + }], + [["--upload-ssh-key"], { + "dest": "upload_ssh_key", + "help": "SSH Key to use when uploading, eg: ~/.ssh/ffxbld_dsa", + }], + ] + virtualenv_config_options + + def __init__(self): + BaseScript.__init__(self, + config_options=self.config_options, + require_config_file=False, + config={ + "virtualenv_modules": [ + "boto", + "redo", + ], + "virtualenv_path": "venv", + }, + all_actions=[ + "create-virtualenv", + "activate-virtualenv", + "collect-individual-checksums", + "create-big-checksums", + "sign", + "get-upload-script", + "upload", + ], + default_actions=[ + "create-virtualenv", + "activate-virtualenv", + "collect-individual-checksums", + "create-big-checksums", + "sign", + "get-upload-script", + "upload", + ], + ) + + self.checksums = {} + + def _pre_config_lock(self, rw_config): + super(ChecksumsGenerator, self)._pre_config_lock(rw_config) + + # These defaults are set here rather in the config because default + # lists cannot be completely overidden, only appended to. + if not self.config.get("formats"): + self.config["formats"] = ["sha512"] + + if not self.config.get("includes"): + self.config["includes"] = [ + "^.*\.tar\.bz2$", + "^.*\.dmg$", + "^.*\.bundle$", + "^.*\.mar$", + "^.*Setup.*\.exe$", + "^.*\.xpi$", + ] + + def _get_bucket_name(self): + suffix = "archive" + # Firefox has a special bucket, per https://github.com/mozilla-services/product-delivery-tools/blob/master/bucketmap.go + if self.config["stage_product"] == "firefox": + suffix = "firefox" + + return "{}-{}".format(self.config["bucket_name_prefix"], suffix) + + def _get_file_prefix(self): + return "pub/{}/candidates/{}-candidates/build{}".format( + self.config["stage_product"], self.config["version"], self.config["build_number"] + ) + + def _get_sums_filename(self, format_): + return "{}SUMS".format(format_.upper()) + + def collect_individual_checksums(self): + """This step grabs all of the small checksums files for the release, + filters out any unwanted files from within them, and adds the remainder + to self.checksums for subsequent steps to use.""" + from boto.s3.connection import S3Connection + + bucket_name = self._get_bucket_name() + file_prefix = self._get_file_prefix() + self.info("Bucket name is: {}".format(bucket_name)) + self.info("File prefix is: {}".format(file_prefix)) + + self.info("Connecting to S3") + conn = S3Connection(anon=True) + self.debug("Successfully connected to S3") + candidates = conn.get_bucket(bucket_name) + + # Temporary holding place for checksums + raw_checksums = [] + def worker(item): + self.debug("Downloading {}".format(item)) + # TODO: It would be nice to download the associated .asc file + # and verify against it. + sums = candidates.get_key(item).get_contents_as_string() + raw_checksums.append(sums) + + def find_checksums_files(): + self.info("Getting key names from bucket") + for key in candidates.list(prefix=file_prefix): + if key.key.endswith(".checksums"): + self.debug("Found checksums file: {}".format(key.key)) + yield key.key + else: + self.debug("Ignoring non-checksums file: {}".format(key.key)) + + pool = ThreadPool(self.config["parallelization"]) + pool.map(worker, find_checksums_files()) + + for c in raw_checksums: + for f, info in parse_checksums_file(c).iteritems(): + for pattern in self.config["includes"]: + if re.search(pattern, f): + if f in self.checksums: + self.fatal("Found duplicate checksum entry for {}, don't know which one to pick.".format(f)) + if not set(self.config["formats"]) <= set(info["hashes"]): + self.fatal("Missing necessary format for file {}".format(f)) + self.debug("Adding checksums for file: {}".format(f)) + self.checksums[f] = info + break + else: + self.debug("Ignoring checksums for file: {}".format(f)) + + def create_big_checksums(self): + for fmt in self.config["formats"]: + sums = self._get_sums_filename(fmt) + self.info("Creating big checksums file: {}".format(sums)) + with open(sums, "w+") as output_file: + for fn in sorted(self.checksums): + output_file.write("{} {}\n".format(self.checksums[fn]["hashes"][fmt], fn)) + + def sign(self): + dirs = self.query_abs_dirs() + + tools_dir = path.join(dirs["abs_work_dir"], "tools") + self.vcs_checkout( + repo=self.config["tools_repo"], + vcs="hgtool", + dest=tools_dir, + ) + + sign_cmd = self.query_moz_sign_cmd(formats=["gpg"]) + + for fmt in self.config["formats"]: + sums = self._get_sums_filename(fmt) + self.info("Signing big checksums file: {}".format(sums)) + retval = self.run_command(sign_cmd + [sums]) + if retval != 0: + self.fatal("Failed to sign {}".format(sums)) + + def get_upload_script(self): + remote_file = "{}/raw-file/{}/build/upload.py".format(self.config["gecko_repo"], self.config["gecko_revision"]) + self.download_file(remote_file, file_name="upload.py") + + def upload(self): + dirs = self.query_abs_dirs() + + sys.path.append(path.dirname(path.abspath("upload.py"))) + from upload import UploadFiles + + files = [] + for fmt in self.config["formats"]: + files.append(self._get_sums_filename(fmt)) + files.append("{}.asc".format(self._get_sums_filename(fmt))) + + post_upload_cmd = " ".join([ + "post_upload.py", "-p", self.config["stage_product"], "-n", self.config["build_number"], + "-v", self.config["version"], "--release-to-candidates-dir", "--signed", + "--bucket-prefix", self.config["bucket_name_prefix"], + ]) + + UploadFiles( + self.config["upload_user"], + self.config["upload_host"], + "dummy", + files, + ssh_key=self.config["upload_ssh_key"], + base_path=dirs["abs_work_dir"], + upload_to_temp_dir=True, + post_upload_command=post_upload_cmd, + ) + +if __name__ == "__main__": + myScript = ChecksumsGenerator() + myScript.run_and_exit()
new file mode 100644 --- /dev/null +++ b/testing/mozharness/scripts/release/push-candidate-to-releases.py @@ -0,0 +1,171 @@ +from multiprocessing.pool import ThreadPool +import os +import re +import sys + +sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0]))) + +from mozharness.base.python import VirtualenvMixin, virtualenv_config_options +from mozharness.base.script import BaseScript + + +class ReleasePusher(BaseScript, VirtualenvMixin): + config_options = [ + [["--product"], { + "dest": "product", + "help": "Product being released, eg: firefox, thunderbird", + }], + [["--version"], { + "dest": "version", + "help": "Version of release, eg: 39.0b5", + }], + [["--build-number"], { + "dest": "build_number", + "help": "Build number of release, eg: 2", + }], + [["--bucket-name"], { + "dest": "bucket_name", + "help": "Bucket to copy files from candidates/ to releases/", + }], + [["--credentials"], { + "dest": "credentials", + "help": "File containing access key and secret access key", + }], + [["--exclude"], { + "dest": "excludes", + "default": [], + "action": "append", + "help": "List of patterns to exclude from copy. See script source for default.", + }], + [["-j", "--parallelization"], { + "dest": "parallelization", + "default": 20, + "type": "int", + "help": "Number of copy requests to run concurrently", + }], + ] + virtualenv_config_options + + def __init__(self): + BaseScript.__init__(self, + config_options=self.config_options, + require_config_file=False, + config={ + "virtualenv_modules": [ + "boto", + "redo", + ], + "virtualenv_path": "venv", + }, + all_actions=[ + "create-virtualenv", + "activate-virtualenv", + "push-to-releases", + ], + default_actions=[ + "create-virtualenv", + "activate-virtualenv", + "push-to-releases", + ], + ) + + # set the env var for boto to read our special config file + # rather than anything else we have at ~/.boto + os.environ["BOTO_CONFIG"] = os.path.abspath(self.config["credentials"]) + + def _pre_config_lock(self, rw_config): + super(ReleasePusher, self)._pre_config_lock(rw_config) + + # This default is set here rather in the config because default + # lists cannot be completely overidden, only appended to. + if not self.config.get("excludes"): + self.config["excludes"] = [ + r"^.*tests.*$", + r"^.*crashreporter.*$", + r"^.*\.zip(\.asc)?$", + r"^.*\.log$", + r"^.*\.txt$", + r"^.*/partner-repacks.*$", + r"^.*.checksums(\.asc)?$", + r"^.*/logs/.*$", + r"^.*/jsshell.*$", + r"^.*json$", + r"^.*/host.*$", + r"^.*/mar-tools/.*$", + r"^.*gecko-unsigned-unaligned.apk$", + r"^.*robocop.apk$", + r"^.*contrib.*" + ] + + def _get_candidates_prefix(self): + return "pub/{}/candidates/{}-candidates/build{}".format( + self.config['product'], + self.config["version"], + self.config["build_number"] + ) + + def _get_releases_prefix(self): + return "pub/{}/releases/{}".format( + self.config["product"], + self.config["version"] + ) + + def _matches_exclude(self, keyname): + for exclude in self.config["excludes"]: + if re.search(exclude, keyname): + return True + return False + + def push_to_releases(self): + """This step grabs the list of files in the candidates dir, + filters out any unwanted files from within them, and copies + the remainder.""" + from boto.s3.connection import S3Connection + from boto.exception import S3CopyError, S3ResponseError + from redo import retry + + # suppress boto debug logging, it's too verbose with --loglevel=debug + import logging + logging.getLogger('boto').setLevel(logging.INFO) + + self.info("Connecting to S3") + conn = S3Connection() + self.info("Getting bucket {}".format(self.config["bucket_name"])) + bucket = conn.get_bucket(self.config["bucket_name"]) + + # ensure the destination is empty + self.info("Checking destination {} is empty".format(self._get_releases_prefix())) + keys = [k for k in bucket.list(prefix=self._get_releases_prefix())] + if keys: + self.fatal("Destination already exists with %s keys, aborting" % + len(keys)) + + def worker(item): + source, destination = item + + self.info("Copying {} to {}".format(source, destination)) + return retry(bucket.copy_key, + args=(destination, + self.config["bucket_name"], + source), + sleeptime=5, max_sleeptime=60, + retry_exceptions=(S3CopyError, S3ResponseError)) + + def find_release_files(): + candidates_prefix = self._get_candidates_prefix() + release_prefix = self._get_releases_prefix() + self.info("Getting key names from candidates") + for key in bucket.list(prefix=candidates_prefix): + keyname = key.name + if self._matches_exclude(keyname): + self.debug("Excluding {}".format(keyname)) + else: + destination = keyname.replace(candidates_prefix, + release_prefix) + yield (keyname, destination) + + pool = ThreadPool(self.config["parallelization"]) + pool.map(worker, find_release_files()) + +if __name__ == "__main__": + myScript = ReleasePusher() + myScript.run_and_exit()
deleted file mode 100644 --- a/testing/web-platform/meta/content-security-policy/blink-contrib/shared-worker-connect-src-blocked.sub.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[shared-worker-connect-src-blocked.sub.html] - type: testharness - [Expecting alerts: ["xhr blocked"\]] - expected: FAIL -
--- a/testing/web-platform/meta/html/browsers/history/the-location-interface/location_assign.html.ini +++ b/testing/web-platform/meta/html/browsers/history/the-location-interface/location_assign.html.ini @@ -1,3 +1,5 @@ [location_assign.html] type: testharness - expected: TIMEOUT + [URL that fails to parse] + expected: FAIL +
--- a/testing/web-platform/meta/url/url-constructor.html.ini +++ b/testing/web-platform/meta/url/url-constructor.html.ini @@ -46,22 +46,16 @@ expected: FAIL [Parsing: <[61:24:74\]:98> against <http://example.org/foo/bar>] expected: FAIL [Parsing: <http:[61:27\]/:foo> against <http://example.org/foo/bar>] expected: FAIL - [Parsing: <http://2001::1\]> against <http://example.org/foo/bar>] - expected: FAIL - - [Parsing: <http://2001::1\]:80> against <http://example.org/foo/bar>] - expected: FAIL - [Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>] expected: FAIL [Parsing: <data:/example.com/> against <http://example.org/foo/bar>] expected: FAIL [Parsing: <gopher:example.com/> against <http://example.org/foo/bar>] expected: FAIL @@ -184,31 +178,19 @@ expected: FAIL [Parsing: <http://a:b@/www.example.com> against <about:blank>] expected: FAIL [Parsing: <http://www.@pple.com> against <about:blank>] expected: FAIL - [Parsing: <http:@:www.example.com> against <about:blank>] - expected: FAIL - - [Parsing: <http:/@:www.example.com> against <about:blank>] - expected: FAIL - - [Parsing: <http://@:www.example.com> against <about:blank>] - expected: FAIL - [Parsing: <http://:@www.example.com> against <about:blank>] expected: FAIL - [Parsing: <http://GOO goo.com> against <http://other.com/>] - expected: FAIL - [Parsing: <http://zyx.com> against <http://other.com/>] expected: FAIL [Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/>] expected: FAIL [Parsing: <http://%41.com> against <http://other.com/>] expected: FAIL
deleted file mode 100644 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[update.https.html] - type: testharness - [Update a registration on ServiceWorkerGlobalScope] - expected: FAIL -
new file mode 100644 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js @@ -0,0 +1,25 @@ +importScripts('../../resources/test-helpers.sub.js'); +importScripts('../../resources/worker-testharness.js'); + +var events_seen = []; + +self.registration.addEventListener('updatefound', function() { + events_seen.push('updatefound'); + }); + +self.addEventListener('activate', function(e) { + events_seen.push('activate'); + }); + +self.addEventListener('fetch', function(e) { + events_seen.push('fetch'); + e.respondWith(new Response(events_seen)); + }); + +self.addEventListener('message', function(e) { + events_seen.push('message'); + self.registration.update(); + }); + +// update() during the script evaluation should be ignored. +self.registration.update();
new file mode 100644 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py @@ -0,0 +1,14 @@ +import os +import time + +def main(request, response): + # update() does not bypass cache so set the max-age to 0 such that update() + # can find a new version in the network. + headers = [('Cache-Control', 'max-age: 0'), + ('Content-Type', 'application/javascript')] + with open(os.path.join(os.path.dirname(__file__), + 'update-worker.js'), 'r') as file: + script = file.read() + # Return a different script for each access. + return headers, '// %s\n%s' % (time.time(), script) +
--- a/testing/xpcshell/remotexpcshelltests.py +++ b/testing/xpcshell/remotexpcshelltests.py @@ -568,17 +568,16 @@ class PathMapping: self.remote = remoteDir def main(): if sys.version_info < (2,7): print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0" sys.exit(1) parser = parser_remote() - commandline.add_logging_group(parser) options = parser.parse_args() if not options.localAPK: for file in os.listdir(os.path.join(options.objdir, "dist")): if (file.endswith(".apk") and file.startswith("fennec")): options.localAPK = os.path.join(options.objdir, "dist") options.localAPK = os.path.join(options.localAPK, file) print >>sys.stderr, "using APK: " + options.localAPK break
--- a/testing/xpcshell/runtestsb2g.py +++ b/testing/xpcshell/runtestsb2g.py @@ -143,17 +143,16 @@ def run_remote_xpcshell(parser, options, sys.exit(1) except: print "Automation Error: Exception caught while running tests" traceback.print_exc() sys.exit(1) def main(): parser = parser_b2g() - commandline.add_logging_group(parser) options = parser.parse_args() log = commandline.setup_logging("Remote XPCShell", options, {"tbpl": sys.stdout}) run_remote_xpcshell(parser, options, log) # You usually run this like : # python runtestsb2g.py --emulator arm --b2gpath $B2GPATH --manifest $MANIFEST [--xre-path $MOZ_HOST_BIN
--- a/testing/xpcshell/runxpcshelltests.py +++ b/testing/xpcshell/runxpcshelltests.py @@ -1423,17 +1423,16 @@ class XPCShellTests(object): return False self.log.suite_end() return self.failCount == 0 def main(): parser = parser_desktop() - commandline.add_logging_group(parser) options = parser.parse_args() log = commandline.setup_logging("XPCShell", options, {"tbpl": sys.stdout}) if options.xpcshell is None: print >> sys.stderr, """Must provide path to xpcshell using --xpcshell""" xpcsh = XPCShellTests(log)
--- a/testing/xpcshell/xpcshellcommandline.py +++ b/testing/xpcshell/xpcshellcommandline.py @@ -1,10 +1,12 @@ import argparse +from mozlog import commandline + def add_common_arguments(parser): parser.add_argument("--app-path", type=unicode, dest="appPath", default=None, help="application directory (as opposed to XRE directory)") parser.add_argument("--interactive", action="store_true", dest="interactive", default=False, help="don't automatically run tests, drop to an xpcshell prompt") parser.add_argument("--verbose", @@ -174,29 +176,33 @@ def add_b2g_arguments(parser): help="Path to busybox binary to install on device") parser.set_defaults(remoteTestRoot="/data/local/tests", dm_trans="adb") def parser_desktop(): parser = argparse.ArgumentParser() add_common_arguments(parser) + commandline.add_logging_group(parser) + return parser def parser_remote(): parser = argparse.ArgumentParser() common = parser.add_argument_group("Common Options") add_common_arguments(common) remote = parser.add_argument_group("Remote Options") add_remote_arguments(remote) + commandline.add_logging_group(parser) return parser def parser_b2g(): parser = argparse.ArgumentParser() common = parser.add_argument_group("Common Options") add_common_arguments(common) remote = parser.add_argument_group("Remote Options") add_remote_arguments(remote) b2g = parser.add_argument_group("B2G Options") add_b2g_arguments(b2g) + commandline.add_logging_group(parser) return parser
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini +++ b/toolkit/components/extensions/test/mochitest/mochitest.ini @@ -1,10 +1,10 @@ [DEFAULT] -skip-if = os == 'android' || buildapp == 'b2g' || buildapp == 'mulet' || os == 'mac' || asan +skip-if = os == 'android' || buildapp == 'b2g' || buildapp == 'mulet' || asan support-files = head.js file_WebRequest_page1.html file_WebRequest_page2.html file_image_good.png file_image_bad.png file_image_redirect.png file_style_good.css
--- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -180,16 +180,17 @@ static char const * const kCrashEventAnn // "IsGarbageCollecting" // "AvailablePageFile" // "AvailableVirtualMemory" // "SystemMemoryUsePercentage" // "OOMAllocationSize" // "TotalPageFile" // "TotalPhysicalMemory" // "TotalVirtualMemory" + // "MozCrashReason" }; static const char kCrashMainID[] = "crash.main.2\n"; static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr; static XP_CHAR* pendingDirectory; static XP_CHAR* crashReporterPath; @@ -448,16 +449,23 @@ Concat(XP_CHAR* str, const XP_CHAR* toAp memcpy(str, toAppend, appendLen * sizeof(XP_CHAR)); str += appendLen; *str = '\0'; *size -= appendLen; return str; } +static const char* gMozCrashReason = nullptr; + +void AnnotateMozCrashReason(const char* aReason) +{ + gMozCrashReason = aReason; +} + static size_t gOOMAllocationSize = 0; void AnnotateOOMAllocationSize(size_t size) { gOOMAllocationSize = size; } #ifndef XP_WIN @@ -836,16 +844,22 @@ bool MinidumpCallback( WRITE_STATEX_FIELD(ullTotalPageFile, "TotalPageFile", _ui64toa); WRITE_STATEX_FIELD(ullAvailPageFile, "AvailablePageFile", _ui64toa); WRITE_STATEX_FIELD(ullTotalPhys, "TotalPhysicalMemory", _ui64toa); WRITE_STATEX_FIELD(ullAvailPhys, "AvailablePhysicalMemory", _ui64toa); #undef WRITE_STATEX_FIELD } #endif // XP_WIN + + if (gMozCrashReason) { + WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason); + WriteAnnotation(eventFile, "MozCrashReason", gMozCrashReason); + } + if (oomAllocationSizeBuffer[0]) { WriteAnnotation(apiData, "OOMAllocationSize", oomAllocationSizeBuffer); WriteAnnotation(eventFile, "OOMAllocationSize", oomAllocationSizeBuffer); } if (memoryReportPath) { WriteLiteral(apiData, "ContainsMemoryReport=1\n"); WriteLiteral(eventFile, "ContainsMemoryReport=1\n");
--- a/toolkit/crashreporter/nsExceptionHandler.h +++ b/toolkit/crashreporter/nsExceptionHandler.h @@ -66,16 +66,19 @@ nsresult SetMinidumpPath(const nsAString // AnnotateCrashReport, RemoveCrashReportAnnotation and // AppendAppNotesToCrashReport may be called from any thread in a chrome // process, but may only be called from the main thread in a content process. nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data); nsresult RemoveCrashReportAnnotation(const nsACString& key); nsresult AppendAppNotesToCrashReport(const nsACString& data); +// NOTE: If you change this definition, also change the definition in Assertions.h +// as it is intended to be defining this same function. +void AnnotateMozCrashReason(const char* aReason); void AnnotateOOMAllocationSize(size_t size); nsresult SetGarbageCollecting(bool collecting); void SetEventloopNestingLevel(uint32_t level); nsresult SetRestartArgs(int argc, char** argv); nsresult SetupExtraData(nsIFile* aAppDataDirectory, const nsACString& aBuildID); bool GetLastRunCrashID(nsAString& id);