author | alwu <alwu@mozilla.com> |
Tue, 20 Nov 2018 22:58:22 +0000 | |
changeset 447505 | 3d997ec4174de4296a6bd30641d42309999703b0 |
parent 447504 | 929c91b8afc67ab6b89df4f242dcfbb5fbf21019 |
child 447506 | 3b5ff99abb5ee601d7798e194d587dc56758139e |
push id | 35081 |
push user | ccoroiu@mozilla.com |
push date | Wed, 21 Nov 2018 21:41:51 +0000 |
treeherder | mozilla-central@e52cc9776809 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | padenot |
bugs | 1491475 |
milestone | 65.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/dom/media/webaudio/AudioBufferSourceNode.cpp +++ b/dom/media/webaudio/AudioBufferSourceNode.cpp @@ -699,16 +699,18 @@ void AudioBufferSourceNode::Start(double if (mBuffer) { SendOffsetAndDurationParametersToStream(ns); } // Don't set parameter unnecessarily if (aWhen > 0.0) { ns->SetDoubleParameter(START, aWhen); } + + Context()->NotifyScheduledSourceNodeStarted(); } void AudioBufferSourceNode::Start(double aWhen, ErrorResult& aRv) { Start(aWhen, 0 /* offset */, Optional<double>(), aRv); } void AudioBufferSourceNode::SendBufferParameterToStream(JSContext* aCx) { AudioNodeStream* ns = mStream;
--- a/dom/media/webaudio/AudioContext.cpp +++ b/dom/media/webaudio/AudioContext.cpp @@ -59,16 +59,17 @@ #include "DynamicsCompressorNode.h" #include "GainNode.h" #include "IIRFilterNode.h" #include "MediaElementAudioSourceNode.h" #include "MediaStreamAudioDestinationNode.h" #include "MediaStreamAudioSourceNode.h" #include "MediaStreamGraph.h" #include "nsContentUtils.h" +#include "nsIScriptError.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" #include "nsPrintfCString.h" #include "nsRFPService.h" #include "OscillatorNode.h" #include "PannerNode.h" #include "PeriodicWave.h" @@ -146,63 +147,96 @@ AudioContext::AudioContext(nsPIDOMWindow mSampleRate(GetSampleRateForAudioContext(aIsOffline, aSampleRate)), mAudioContextState(AudioContextState::Suspended), mNumberOfChannels(aNumberOfChannels), mIsOffline(aIsOffline), mIsStarted(!aIsOffline), mIsShutDown(false), mCloseCalled(false), mSuspendCalled(false), - mIsDisconnecting(false) { + mIsDisconnecting(false), + mWasAllowedToStart(true) { bool mute = aWindow->AddAudioContext(this); // Note: AudioDestinationNode needs an AudioContext that must already be // bound to the window. - bool allowedToStart = AutoplayPolicy::IsAllowedToPlay(*this); + const bool allowedToStart = AutoplayPolicy::IsAllowedToPlay(*this); mDestination = new AudioDestinationNode(this, aIsOffline, allowedToStart, aNumberOfChannels, aLength); // The context can't be muted until it has a destination. if (mute) { Mute(); } + // If an AudioContext is not allowed to start, we would postpone its state + // transition from `suspended` to `running` until sites explicitly call + // AudioContext.resume() or AudioScheduledSourceNode.start(). if (!allowedToStart) { - // Not allowed to start, delay the transition from `suspended` to `running`. + AUTOPLAY_LOG("AudioContext %p is not allowed to start", this); + mWasAllowedToStart = false; SuspendInternal(nullptr); - EnsureAutoplayRequested(); + DispatchBlockedEvent(); } FFTBlock::MainThreadInit(); } +void AudioContext::NotifyScheduledSourceNodeStarted() { + MOZ_ASSERT(NS_IsMainThread()); + // Only try to start AudioContext when AudioContext was not allowed to start. + if (mWasAllowedToStart) { + return; + } + + const bool isAllowedToPlay = AutoplayPolicy::IsAllowedToPlay(*this); + AUTOPLAY_LOG("Trying to start AudioContext %p, IsAllowedToPlay=%d", + this, isAllowedToPlay); + if (isAllowedToPlay) { + ResumeInternal(); + } else { + EnsureAutoplayRequested(); + } +} + void AudioContext::EnsureAutoplayRequested() { nsPIDOMWindowInner* parent = GetParentObject(); if (!parent || !parent->AsGlobal()) { return; } RefPtr<AutoplayPermissionManager> request = AutoplayPolicy::RequestFor(*(parent->GetExtantDoc())); if (!request) { return; } + AUTOPLAY_LOG("AudioContext %p EnsureAutoplayRequested %p", + this, request.get()); RefPtr<AudioContext> self = this; request->RequestWithPrompt()->Then( parent->AsGlobal()->AbstractMainThreadFor(TaskCategory::Other), __func__, [self, request](bool aApproved) { AUTOPLAY_LOG("%p Autoplay request approved request=%p", self.get(), request.get()); + self->mWasAllowedToStart = true; self->ResumeInternal(); }, [self, request](nsresult aError) { AUTOPLAY_LOG("%p Autoplay request denied request=%p", self.get(), request.get()); + self->mWasAllowedToStart = false; self->DispatchBlockedEvent(); + nsIDocument* doc = self->GetParentObject() ? + self->GetParentObject()->GetExtantDoc() : nullptr; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Media"), + doc, + nsContentUtils::eDOM_PROPERTIES, + "BlockAutoplayError"); }); } nsresult AudioContext::Init() { if (!mIsOffline) { nsresult rv = mDestination->CreateAudioChannelAgent(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; @@ -938,28 +972,30 @@ already_AddRefed<Promise> AudioContext:: if (mAudioContextState == AudioContextState::Closed || mCloseCalled) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); return promise.forget(); } mPendingResumePromises.AppendElement(promise); const bool isAllowedToPlay = AutoplayPolicy::IsAllowedToPlay(*this); + AUTOPLAY_LOG("Trying to resume AudioContext %p, IsAllowedToPlay=%d", + this, isAllowedToPlay); if (isAllowedToPlay) { + mWasAllowedToStart = true; ResumeInternal(); - } else { - DispatchBlockedEvent(); + } else if (!isAllowedToPlay && !mWasAllowedToStart) { + EnsureAutoplayRequested(); } - AUTOPLAY_LOG("Resume AudioContext %p, IsAllowedToPlay=%d", this, - isAllowedToPlay); return promise.forget(); } void AudioContext::ResumeInternal() { + AUTOPLAY_LOG("Allow to resume AudioContext %p", this); Destination()->Resume(); nsTArray<MediaStream*> streams; // If mSuspendCalled is false then we already resumed all our streams, // so don't resume them again (since suspend(); resume(); resume(); should // be OK). But we still need to do ApplyAudioContextOperation // to ensure our new promise is resolved. if (mSuspendCalled) {
--- a/dom/media/webaudio/AudioContext.h +++ b/dom/media/webaudio/AudioContext.h @@ -178,16 +178,20 @@ class AudioContext final : public DOMEve AudioListener* Listener(); AudioContextState State() const { return mAudioContextState; } Worklet* GetAudioWorklet(ErrorResult& aRv); bool IsRunning() const; + // Called when an AudioScheduledSourceNode started, this method might resume + // the AudioContext if it was not allowed to start. + void NotifyScheduledSourceNodeStarted(); + // Those three methods return a promise to content, that is resolved when an // (possibly long) operation is completed on the MSG (and possibly other) // thread(s). To avoid having to match the calls and asychronous result when // the operation is completed, we keep a reference to the promises on the main // thread, and then send the promises pointers down the MSG thread, as a void* // (to make it very clear that the pointer is to merely be treated as an ID). // When back on the main thread, we can resolve or reject the promise, by // casting it back to a `Promise*` while asserting we're back on the main @@ -357,16 +361,18 @@ class AudioContext final : public DOMEve bool mIsOffline; bool mIsStarted; bool mIsShutDown; // Close has been called, reject suspend and resume call. bool mCloseCalled; // Suspend has been called with no following resume. bool mSuspendCalled; bool mIsDisconnecting; + // This flag stores the value of previous status of `allowed-to-start`. + bool mWasAllowedToStart; }; static const dom::AudioContext::AudioContextId NO_AUDIO_CONTEXT = 0; } // namespace dom } // namespace mozilla #endif
--- a/dom/media/webaudio/ConstantSourceNode.cpp +++ b/dom/media/webaudio/ConstantSourceNode.cpp @@ -219,16 +219,17 @@ void ConstantSourceNode::Start(double aW if (!mStream) { return; } mStream->SetStreamTimeParameter(ConstantSourceNodeEngine::START, Context(), aWhen); MarkActive(); + Context()->NotifyScheduledSourceNodeStarted(); } void ConstantSourceNode::Stop(double aWhen, ErrorResult& aRv) { if (!WebAudioUtils::IsTimeValid(aWhen)) { aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>(NS_LITERAL_STRING("stop time")); return; }
--- a/dom/media/webaudio/OscillatorNode.cpp +++ b/dom/media/webaudio/OscillatorNode.cpp @@ -483,16 +483,17 @@ void OscillatorNode::Start(double aWhen, return; } // TODO: Perhaps we need to do more here. mStream->SetStreamTimeParameter(OscillatorNodeEngine::START, Context(), aWhen); MarkActive(); + Context()->NotifyScheduledSourceNodeStarted(); } void OscillatorNode::Stop(double aWhen, ErrorResult& aRv) { if (!WebAudioUtils::IsTimeValid(aWhen)) { aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; }