Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "OscillatorNode.h"
8 #include "AudioNodeEngine.h"
9 #include "AudioNodeStream.h"
10 #include "AudioDestinationNode.h"
11 #include "WebAudioUtils.h"
12 #include "blink/PeriodicWave.h"
14 namespace mozilla {
15 namespace dom {
17 NS_IMPL_CYCLE_COLLECTION_INHERITED(OscillatorNode, AudioNode,
18 mPeriodicWave, mFrequency, mDetune)
20 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OscillatorNode)
21 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
23 NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioNode)
24 NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioNode)
26 static const float sLeakTriangle = 0.995f;
27 static const float sLeak = 0.999f;
29 class DCBlocker
30 {
31 public:
32 // These are sane defauts when the initial mPhase is zero
33 DCBlocker(float aLastInput = 0.0f,
34 float aLastOutput = 0.0f,
35 float aPole = 0.995)
36 :mLastInput(aLastInput),
37 mLastOutput(aLastOutput),
38 mPole(aPole)
39 {
40 MOZ_ASSERT(aPole > 0);
41 }
43 inline float Process(float aInput)
44 {
45 float out;
47 out = mLastOutput * mPole + aInput - mLastInput;
48 mLastOutput = out;
49 mLastInput = aInput;
51 return out;
52 }
53 private:
54 float mLastInput;
55 float mLastOutput;
56 float mPole;
57 };
60 class OscillatorNodeEngine : public AudioNodeEngine
61 {
62 public:
63 OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
64 : AudioNodeEngine(aNode)
65 , mSource(nullptr)
66 , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
67 , mStart(-1)
68 , mStop(TRACK_TICKS_MAX)
69 // Keep the default values in sync with OscillatorNode::OscillatorNode.
70 , mFrequency(440.f)
71 , mDetune(0.f)
72 , mType(OscillatorType::Sine)
73 , mPhase(0.)
74 // mSquare, mTriangle, and mSaw are not used for default type "sine".
75 // They are initialized if and when switching to the OscillatorTypes that
76 // use them.
77 // mFinalFrequency, mNumberOfHarmonics, mSignalPeriod, mAmplitudeAtZero,
78 // mPhaseIncrement, and mPhaseWrap are initialized in
79 // UpdateParametersIfNeeded() when mRecomputeParameters is set.
80 , mRecomputeParameters(true)
81 , mCustomLength(0)
82 {
83 }
85 void SetSourceStream(AudioNodeStream* aSource)
86 {
87 mSource = aSource;
88 }
90 enum Parameters {
91 FREQUENCY,
92 DETUNE,
93 TYPE,
94 PERIODICWAVE,
95 START,
96 STOP,
97 };
98 void SetTimelineParameter(uint32_t aIndex,
99 const AudioParamTimeline& aValue,
100 TrackRate aSampleRate) MOZ_OVERRIDE
101 {
102 mRecomputeParameters = true;
103 switch (aIndex) {
104 case FREQUENCY:
105 MOZ_ASSERT(mSource && mDestination);
106 mFrequency = aValue;
107 WebAudioUtils::ConvertAudioParamToTicks(mFrequency, mSource, mDestination);
108 break;
109 case DETUNE:
110 MOZ_ASSERT(mSource && mDestination);
111 mDetune = aValue;
112 WebAudioUtils::ConvertAudioParamToTicks(mDetune, mSource, mDestination);
113 break;
114 default:
115 NS_ERROR("Bad OscillatorNodeEngine TimelineParameter");
116 }
117 }
119 virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
120 {
121 switch (aIndex) {
122 case START: mStart = aParam; break;
123 case STOP: mStop = aParam; break;
124 default:
125 NS_ERROR("Bad OscillatorNodeEngine StreamTimeParameter");
126 }
127 }
129 virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
130 {
131 switch (aIndex) {
132 case TYPE:
133 // Set the new type.
134 mType = static_cast<OscillatorType>(aParam);
135 if (mType != OscillatorType::Custom) {
136 // Forget any previous custom data.
137 mCustomLength = 0;
138 mCustom = nullptr;
139 mPeriodicWave = nullptr;
140 mRecomputeParameters = true;
141 }
142 // Update BLIT integrators with the new initial conditions.
143 switch (mType) {
144 case OscillatorType::Sine:
145 mPhase = 0.0;
146 break;
147 case OscillatorType::Square:
148 mPhase = 0.0;
149 // Initial integration condition is -0.5, because our
150 // square has 50% duty cycle.
151 mSquare = -0.5;
152 break;
153 case OscillatorType::Triangle:
154 // Initial mPhase and related integration condition so the
155 // triangle is in the middle of the first upward slope.
156 // XXX actually do the maths and put the right number here.
157 mPhase = (float)(M_PI / 2);
158 mSquare = 0.5;
159 mTriangle = 0.0;
160 break;
161 case OscillatorType::Sawtooth:
162 // Initial mPhase so the oscillator starts at the
163 // middle of the ramp, per spec.
164 mPhase = (float)(M_PI / 2);
165 // mSaw = 0 when mPhase = pi/2.
166 mSaw = 0.0;
167 break;
168 case OscillatorType::Custom:
169 // Custom waveforms don't use BLIT.
170 break;
171 default:
172 NS_ERROR("Bad OscillatorNodeEngine type parameter.");
173 }
174 // End type switch.
175 break;
176 case PERIODICWAVE:
177 MOZ_ASSERT(aParam >= 0, "negative custom array length");
178 mCustomLength = static_cast<uint32_t>(aParam);
179 break;
180 default:
181 NS_ERROR("Bad OscillatorNodeEngine Int32Parameter.");
182 }
183 // End index switch.
184 }
186 virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
187 {
188 MOZ_ASSERT(mCustomLength, "Custom buffer sent before length");
189 mCustom = aBuffer;
190 MOZ_ASSERT(mCustom->GetChannels() == 2,
191 "PeriodicWave should have sent two channels");
192 mPeriodicWave = WebCore::PeriodicWave::create(mSource->SampleRate(),
193 mCustom->GetData(0), mCustom->GetData(1), mCustomLength);
194 }
196 void IncrementPhase()
197 {
198 mPhase += mPhaseIncrement;
199 if (mPhase > mPhaseWrap) {
200 mPhase -= mPhaseWrap;
201 }
202 }
204 // Square and triangle are using a bipolar band-limited impulse train, saw is
205 // using a normal band-limited impulse train.
206 bool UsesBipolarBLIT() {
207 return mType == OscillatorType::Square || mType == OscillatorType::Triangle;
208 }
210 void UpdateParametersIfNeeded(TrackTicks ticks, size_t count)
211 {
212 double frequency, detune;
214 bool simpleFrequency = mFrequency.HasSimpleValue();
215 bool simpleDetune = mDetune.HasSimpleValue();
217 // Shortcut if frequency-related AudioParam are not automated, and we
218 // already have computed the frequency information and related parameters.
219 if (simpleFrequency && simpleDetune && !mRecomputeParameters) {
220 return;
221 }
223 if (simpleFrequency) {
224 frequency = mFrequency.GetValue();
225 } else {
226 frequency = mFrequency.GetValueAtTime(ticks, count);
227 }
228 if (simpleDetune) {
229 detune = mDetune.GetValue();
230 } else {
231 detune = mDetune.GetValueAtTime(ticks, count);
232 }
234 mFinalFrequency = frequency * pow(2., detune / 1200.);
235 mRecomputeParameters = false;
237 // When using bipolar BLIT, we divide the signal period by two, because we
238 // are using two BLIT out of phase.
239 mSignalPeriod = UsesBipolarBLIT() ? 0.5 * mSource->SampleRate() / mFinalFrequency
240 : mSource->SampleRate() / mFinalFrequency;
241 // Wrap the phase accordingly:
242 mPhaseWrap = UsesBipolarBLIT() || mType == OscillatorType::Sine ? 2 * M_PI
243 : M_PI;
244 // Even number of harmonics for bipolar blit, odd otherwise.
245 mNumberOfHarmonics = UsesBipolarBLIT() ? 2 * floor(0.5 * mSignalPeriod)
246 : 2 * floor(0.5 * mSignalPeriod) + 1;
247 mPhaseIncrement = mType == OscillatorType::Sine ? 2 * M_PI / mSignalPeriod
248 : M_PI / mSignalPeriod;
249 mAmplitudeAtZero = mNumberOfHarmonics / mSignalPeriod;
250 }
252 void FillBounds(float* output, TrackTicks ticks,
253 uint32_t& start, uint32_t& end)
254 {
255 MOZ_ASSERT(output);
256 static_assert(TrackTicks(WEBAUDIO_BLOCK_SIZE) < UINT_MAX,
257 "WEBAUDIO_BLOCK_SIZE overflows interator bounds.");
258 start = 0;
259 if (ticks < mStart) {
260 start = mStart - ticks;
261 for (uint32_t i = 0; i < start; ++i) {
262 output[i] = 0.0;
263 }
264 }
265 end = WEBAUDIO_BLOCK_SIZE;
266 if (ticks + end > mStop) {
267 end = mStop - ticks;
268 for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) {
269 output[i] = 0.0;
270 }
271 }
272 }
274 float BipolarBLIT()
275 {
276 float blit;
277 float denom = sin(mPhase);
279 if (fabs(denom) < std::numeric_limits<float>::epsilon()) {
280 if (mPhase < 0.1f || mPhase > 2 * M_PI - 0.1f) {
281 blit = mAmplitudeAtZero;
282 } else {
283 blit = -mAmplitudeAtZero;
284 }
285 } else {
286 blit = sin(mNumberOfHarmonics * mPhase);
287 blit /= mSignalPeriod * denom;
288 }
289 return blit;
290 }
292 float UnipolarBLIT()
293 {
294 float blit;
295 float denom = sin(mPhase);
297 if (fabs(denom) <= std::numeric_limits<float>::epsilon()) {
298 blit = mAmplitudeAtZero;
299 } else {
300 blit = sin(mNumberOfHarmonics * mPhase);
301 blit /= mSignalPeriod * denom;
302 }
304 return blit;
305 }
307 void ComputeSine(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
308 {
309 for (uint32_t i = aStart; i < aEnd; ++i) {
310 UpdateParametersIfNeeded(ticks, i);
312 aOutput[i] = sin(mPhase);
314 IncrementPhase();
315 }
316 }
318 void ComputeSquare(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
319 {
320 for (uint32_t i = aStart; i < aEnd; ++i) {
321 UpdateParametersIfNeeded(ticks, i);
322 // Integration to get us a square. It turns out we can have a
323 // pure integrator here.
324 mSquare = mSquare * sLeak + BipolarBLIT();
325 aOutput[i] = mSquare;
326 // maybe we want to apply a gain, the wg has not decided yet
327 aOutput[i] *= 1.5;
328 IncrementPhase();
329 }
330 }
332 void ComputeSawtooth(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
333 {
334 float dcoffset;
335 for (uint32_t i = aStart; i < aEnd; ++i) {
336 UpdateParametersIfNeeded(ticks, i);
337 // DC offset so the Saw does not ramp up to infinity when integrating.
338 dcoffset = mFinalFrequency / mSource->SampleRate();
339 // Integrate and offset so we get mAmplitudeAtZero sawtooth. We have a
340 // very low frequency component somewhere here, but I'm not sure where.
341 mSaw = mSaw * sLeak + (UnipolarBLIT() - dcoffset);
342 // reverse the saw so we are spec compliant
343 aOutput[i] = -mSaw * 1.5;
345 IncrementPhase();
346 }
347 }
349 void ComputeTriangle(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
350 {
351 for (uint32_t i = aStart; i < aEnd; ++i) {
352 UpdateParametersIfNeeded(ticks, i);
353 // Integrate to get a square
354 mSquare += BipolarBLIT();
355 // Leaky integrate to get a triangle. We get too much dc offset if we don't
356 // leaky integrate here.
357 // C6 = k0 / period
358 // (period is samplingrate / frequency, k0 = (PI/2)/(2*PI)) = 0.25
359 float C6 = 0.25 / (mSource->SampleRate() / mFinalFrequency);
360 mTriangle = mTriangle * sLeakTriangle + mSquare + C6;
361 // DC Block, and scale back to [-1.0; 1.0]
362 aOutput[i] = mDCBlocker.Process(mTriangle) / (mSignalPeriod/2) * 1.5;
364 IncrementPhase();
365 }
366 }
368 void ComputeCustom(float* aOutput,
369 TrackTicks ticks,
370 uint32_t aStart,
371 uint32_t aEnd)
372 {
373 MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
375 uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
376 // Mask to wrap wave data indices into the range [0,periodicWaveSize).
377 uint32_t indexMask = periodicWaveSize - 1;
378 MOZ_ASSERT(periodicWaveSize && (periodicWaveSize & indexMask) == 0,
379 "periodicWaveSize must be power of 2");
380 float* higherWaveData = nullptr;
381 float* lowerWaveData = nullptr;
382 float tableInterpolationFactor;
383 // Phase increment at frequency of 1 Hz.
384 // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI).
385 float basePhaseIncrement =
386 static_cast<float>(periodicWaveSize) / mSource->SampleRate();
388 for (uint32_t i = aStart; i < aEnd; ++i) {
389 UpdateParametersIfNeeded(ticks, i);
390 mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
391 lowerWaveData,
392 higherWaveData,
393 tableInterpolationFactor);
394 // Bilinear interpolation between adjacent samples in each table.
395 float floorPhase = floorf(mPhase);
396 uint32_t j1 = floorPhase;
397 j1 &= indexMask;
398 uint32_t j2 = j1 + 1;
399 j2 &= indexMask;
401 float sampleInterpolationFactor = mPhase - floorPhase;
403 float lower = (1.0f - sampleInterpolationFactor) * lowerWaveData[j1] +
404 sampleInterpolationFactor * lowerWaveData[j2];
405 float higher = (1.0f - sampleInterpolationFactor) * higherWaveData[j1] +
406 sampleInterpolationFactor * higherWaveData[j2];
407 aOutput[i] = (1.0f - tableInterpolationFactor) * lower +
408 tableInterpolationFactor * higher;
410 // Calculate next phase position from wrapped value j1 to avoid loss of
411 // precision at large values.
412 mPhase =
413 j1 + sampleInterpolationFactor + basePhaseIncrement * mFinalFrequency;
414 }
415 }
417 void ComputeSilence(AudioChunk *aOutput)
418 {
419 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
420 }
422 virtual void ProcessBlock(AudioNodeStream* aStream,
423 const AudioChunk& aInput,
424 AudioChunk* aOutput,
425 bool* aFinished) MOZ_OVERRIDE
426 {
427 MOZ_ASSERT(mSource == aStream, "Invalid source stream");
429 TrackTicks ticks = aStream->GetCurrentPosition();
430 if (mStart == -1) {
431 ComputeSilence(aOutput);
432 return;
433 }
435 if (ticks >= mStop) {
436 // We've finished playing.
437 ComputeSilence(aOutput);
438 *aFinished = true;
439 return;
440 }
441 if (ticks + WEBAUDIO_BLOCK_SIZE < mStart) {
442 // We're not playing yet.
443 ComputeSilence(aOutput);
444 return;
445 }
447 AllocateAudioBlock(1, aOutput);
448 float* output = static_cast<float*>(
449 const_cast<void*>(aOutput->mChannelData[0]));
451 uint32_t start, end;
452 FillBounds(output, ticks, start, end);
454 // Synthesize the correct waveform.
455 switch(mType) {
456 case OscillatorType::Sine:
457 ComputeSine(output, ticks, start, end);
458 break;
459 case OscillatorType::Square:
460 ComputeSquare(output, ticks, start, end);
461 break;
462 case OscillatorType::Triangle:
463 ComputeTriangle(output, ticks, start, end);
464 break;
465 case OscillatorType::Sawtooth:
466 ComputeSawtooth(output, ticks, start, end);
467 break;
468 case OscillatorType::Custom:
469 ComputeCustom(output, ticks, start, end);
470 break;
471 default:
472 ComputeSilence(aOutput);
473 };
475 }
477 virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
478 {
479 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
481 // Not owned:
482 // - mSource
483 // - mDestination
484 // - mFrequency (internal ref owned by node)
485 // - mDetune (internal ref owned by node)
487 if (mCustom) {
488 amount += mCustom->SizeOfIncludingThis(aMallocSizeOf);
489 }
491 if (mPeriodicWave) {
492 amount += mPeriodicWave->sizeOfIncludingThis(aMallocSizeOf);
493 }
495 return amount;
496 }
498 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
499 {
500 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
501 }
503 DCBlocker mDCBlocker;
504 AudioNodeStream* mSource;
505 AudioNodeStream* mDestination;
506 TrackTicks mStart;
507 TrackTicks mStop;
508 AudioParamTimeline mFrequency;
509 AudioParamTimeline mDetune;
510 OscillatorType mType;
511 float mPhase;
512 float mFinalFrequency;
513 uint32_t mNumberOfHarmonics;
514 float mSignalPeriod;
515 float mAmplitudeAtZero;
516 float mPhaseIncrement;
517 float mSquare;
518 float mTriangle;
519 float mSaw;
520 float mPhaseWrap;
521 bool mRecomputeParameters;
522 nsRefPtr<ThreadSharedFloatArrayBufferList> mCustom;
523 uint32_t mCustomLength;
524 nsAutoPtr<WebCore::PeriodicWave> mPeriodicWave;
525 };
527 OscillatorNode::OscillatorNode(AudioContext* aContext)
528 : AudioNode(aContext,
529 2,
530 ChannelCountMode::Max,
531 ChannelInterpretation::Speakers)
532 , mType(OscillatorType::Sine)
533 , mFrequency(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
534 SendFrequencyToStream, 440.0f))
535 , mDetune(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
536 SendDetuneToStream, 0.0f))
537 , mStartCalled(false)
538 , mStopped(false)
539 {
540 OscillatorNodeEngine* engine = new OscillatorNodeEngine(this, aContext->Destination());
541 mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::SOURCE_STREAM);
542 engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
543 mStream->AddMainThreadListener(this);
544 }
546 OscillatorNode::~OscillatorNode()
547 {
548 }
550 size_t
551 OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
552 {
553 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
555 // For now only report if we know for sure that it's not shared.
556 amount += mPeriodicWave->SizeOfExcludingThisIfNotShared(aMallocSizeOf);
557 amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
558 amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
559 return amount;
560 }
562 size_t
563 OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
564 {
565 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
566 }
568 JSObject*
569 OscillatorNode::WrapObject(JSContext* aCx)
570 {
571 return OscillatorNodeBinding::Wrap(aCx, this);
572 }
574 void
575 OscillatorNode::SendFrequencyToStream(AudioNode* aNode)
576 {
577 OscillatorNode* This = static_cast<OscillatorNode*>(aNode);
578 SendTimelineParameterToStream(This, OscillatorNodeEngine::FREQUENCY, *This->mFrequency);
579 }
581 void
582 OscillatorNode::SendDetuneToStream(AudioNode* aNode)
583 {
584 OscillatorNode* This = static_cast<OscillatorNode*>(aNode);
585 SendTimelineParameterToStream(This, OscillatorNodeEngine::DETUNE, *This->mDetune);
586 }
588 void
589 OscillatorNode::SendTypeToStream()
590 {
591 if (mType == OscillatorType::Custom) {
592 // The engine assumes we'll send the custom data before updating the type.
593 SendPeriodicWaveToStream();
594 }
595 SendInt32ParameterToStream(OscillatorNodeEngine::TYPE, static_cast<int32_t>(mType));
596 }
598 void OscillatorNode::SendPeriodicWaveToStream()
599 {
600 NS_ASSERTION(mType == OscillatorType::Custom,
601 "Sending custom waveform to engine thread with non-custom type");
602 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
603 MOZ_ASSERT(ns, "Missing node stream.");
604 MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object.");
605 SendInt32ParameterToStream(OscillatorNodeEngine::PERIODICWAVE,
606 mPeriodicWave->DataLength());
607 nsRefPtr<ThreadSharedFloatArrayBufferList> data =
608 mPeriodicWave->GetThreadSharedBuffer();
609 ns->SetBuffer(data.forget());
610 }
612 void
613 OscillatorNode::Start(double aWhen, ErrorResult& aRv)
614 {
615 if (!WebAudioUtils::IsTimeValid(aWhen)) {
616 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
617 return;
618 }
620 if (mStartCalled) {
621 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
622 return;
623 }
624 mStartCalled = true;
626 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
627 if (!ns) {
628 // Nothing to play, or we're already dead for some reason
629 return;
630 }
632 // TODO: Perhaps we need to do more here.
633 ns->SetStreamTimeParameter(OscillatorNodeEngine::START,
634 Context(), aWhen);
636 MarkActive();
637 }
639 void
640 OscillatorNode::Stop(double aWhen, ErrorResult& aRv)
641 {
642 if (!WebAudioUtils::IsTimeValid(aWhen)) {
643 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
644 return;
645 }
647 if (!mStartCalled) {
648 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
649 return;
650 }
652 AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
653 if (!ns || !Context()) {
654 // We've already stopped and had our stream shut down
655 return;
656 }
658 // TODO: Perhaps we need to do more here.
659 ns->SetStreamTimeParameter(OscillatorNodeEngine::STOP,
660 Context(), std::max(0.0, aWhen));
661 }
663 void
664 OscillatorNode::NotifyMainThreadStateChanged()
665 {
666 if (mStream->IsFinished()) {
667 class EndedEventDispatcher : public nsRunnable
668 {
669 public:
670 explicit EndedEventDispatcher(OscillatorNode* aNode)
671 : mNode(aNode) {}
672 NS_IMETHODIMP Run()
673 {
674 // If it's not safe to run scripts right now, schedule this to run later
675 if (!nsContentUtils::IsSafeToRunScript()) {
676 nsContentUtils::AddScriptRunner(this);
677 return NS_OK;
678 }
680 mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
681 return NS_OK;
682 }
683 private:
684 nsRefPtr<OscillatorNode> mNode;
685 };
686 if (!mStopped) {
687 // Only dispatch the ended event once
688 NS_DispatchToMainThread(new EndedEventDispatcher(this));
689 mStopped = true;
690 }
692 // Drop the playing reference
693 // Warning: The below line might delete this.
694 MarkInactive();
695 }
696 }
698 }
699 }