LibWeb: Validate parameters for source node start() and stop() methods

This commit is contained in:
Tim Ledbetter 2025-01-09 23:47:30 +00:00 committed by Andreas Kling
commit 3261f873c5
Notes: github-actions[bot] 2025-01-11 01:44:33 +00:00
6 changed files with 162 additions and 5 deletions

View file

@ -110,9 +110,30 @@ double AudioBufferSourceNode::loop_end() const
// https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-start` // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-start`
WebIDL::ExceptionOr<void> AudioBufferSourceNode::start(Optional<double> when, Optional<double> offset, Optional<double> duration) WebIDL::ExceptionOr<void> AudioBufferSourceNode::start(Optional<double> when, Optional<double> offset, Optional<double> duration)
{ {
(void)when; // 1. If this AudioBufferSourceNode internal slot [[source started]] is true, an InvalidStateError exception MUST be thrown.
(void)offset; if (source_started())
(void)duration; return WebIDL::InvalidStateError::create(realm(), "AudioBufferSourceNode has already been started"_string);
// 2. Check for any errors that must be thrown due to parameter constraints described below. If any exception is thrown during this step, abort those steps.
// A RangeError exception MUST be thrown if when is negative.
if (when.has_value() && when.value() < 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "when must not be negative"sv };
// A RangeError exception MUST be thrown if offset is negative
if (offset.has_value() && offset.value() < 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "offset must not be negative"sv };
// A RangeError exception MUST be thrown if duration is negative.
if (duration.has_value() && duration.value() < 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "duration must not be negative"sv };
// 3. Set the internal slot [[source started]] on this AudioBufferSourceNode to true.
set_source_started(true);
// FIXME: 4. Queue a control message to start the AudioBufferSourceNode, including the parameter values in the message.
// FIXME: 5. Acquire the contents of the buffer if the buffer has been set.
// FIXME: 6. Send a control message to the associated AudioContext to start running its rendering thread only when all the following conditions are met:
dbgln("FIXME: Implement AudioBufferSourceNode::start(when, offset, duration)"); dbgln("FIXME: Implement AudioBufferSourceNode::start(when, offset, duration)");
return {}; return {};
} }

View file

@ -35,14 +35,38 @@ void AudioScheduledSourceNode::set_onended(GC::Ptr<WebIDL::CallbackType> value)
// https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-start // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-start
WebIDL::ExceptionOr<void> AudioScheduledSourceNode::start(double when) WebIDL::ExceptionOr<void> AudioScheduledSourceNode::start(double when)
{ {
(void)when; // 1. If this AudioScheduledSourceNode internal slot [[source started]] is true, an InvalidStateError exception MUST be thrown.
if (source_started())
return WebIDL::InvalidStateError::create(realm(), "AudioScheduledSourceNode source has already started"_string);
// 2. Check for any errors that must be thrown due to parameter constraints described below. If any exception is thrown during this step, abort those steps.
// A RangeError exception MUST be thrown if when is negative.
if (when < 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "when must not be negative"sv };
// 3. Set the internal slot [[source started]] on this AudioScheduledSourceNode to true.
set_source_started(true);
// FIXME: 4. Queue a control message to start the AudioScheduledSourceNode, including the parameter values in the message.
// FIXME: 5. Send a control message to the associated AudioContext to start running its rendering thread only when all the following conditions are met:
return WebIDL::NotSupportedError::create(realm(), "FIXME: Implement AudioScheduledSourceNode::start"_string); return WebIDL::NotSupportedError::create(realm(), "FIXME: Implement AudioScheduledSourceNode::start"_string);
} }
// https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-stop // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-stop
WebIDL::ExceptionOr<void> AudioScheduledSourceNode::stop(double when) WebIDL::ExceptionOr<void> AudioScheduledSourceNode::stop(double when)
{ {
(void)when; // 1. If this AudioScheduledSourceNode internal slot [[source started]] is not true, an InvalidStateError exception MUST be thrown.
if (!m_source_started)
return WebIDL::InvalidStateError::create(realm(), "AudioScheduledSourceNode source has not been started"_string);
// 2. Check for any errors that must be thrown due to parameter constraints described below.
// A RangeError exception MUST be thrown if when is negative.
if (when < 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "when must not be negative"sv };
// FIXME: 3. Queue a control message to stop the AudioScheduledSourceNode, including the parameter values in the message.
return WebIDL::NotSupportedError::create(realm(), "FIXME: Implement AudioScheduledSourceNode::stop"_string); return WebIDL::NotSupportedError::create(realm(), "FIXME: Implement AudioScheduledSourceNode::stop"_string);
} }

View file

@ -27,8 +27,15 @@ public:
protected: protected:
AudioScheduledSourceNode(JS::Realm&, GC::Ref<BaseAudioContext>); AudioScheduledSourceNode(JS::Realm&, GC::Ref<BaseAudioContext>);
bool source_started() const { return m_source_started; }
void set_source_started(bool started) { m_source_started = started; }
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
private:
// https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-source-started-slot
bool m_source_started { false };
}; };
} }

View file

@ -0,0 +1,23 @@
Harness status: OK
Found 18 tests
18 Pass
Pass # AUDIT TASK RUNNER STARTED.
Pass Executing "start/stop exceptions"
Pass Audit report
Pass > [start/stop exceptions]
Pass start(NaN) threw TypeError: "Expected when to be a finite floating-point number".
Pass start(Infinity) threw TypeError: "Expected when to be a finite floating-point number".
Pass start(-Infinity) threw TypeError: "Expected when to be a finite floating-point number".
Pass Calling stop() before start() threw InvalidStateError: "AudioScheduledSourceNode source has not been started".
Pass start(-1) threw RangeError: "when must not be negative".
Pass start(0,-1) threw RangeError: "offset must not be negative".
Pass start(0,0,-1) threw RangeError: "duration must not be negative".
Pass Calling start() twice threw InvalidStateError: "AudioBufferSourceNode has already been started".
Pass stop(-1) threw RangeError: "when must not be negative".
Pass stop(NaN) threw TypeError: "Expected when to be a finite floating-point number".
Pass stop(Infinity) threw TypeError: "Expected when to be a finite floating-point number".
Pass stop(-Infinity) threw TypeError: "Expected when to be a finite floating-point number".
Pass < [start/stop exceptions] All assertions passed. (total 12 assertions)
Pass # AUDIT TASK RUNNER FINISHED: 1 tasks ran successfully.

View file

@ -0,0 +1,45 @@
// Test that exceptions are throw for invalid values for start and
// stop.
function testStartStop(should, node, options) {
// Test non-finite values for start. These should all throw a TypeError
const nonFiniteValues = [NaN, Infinity, -Infinity];
nonFiniteValues.forEach(time => {
should(() => {
node.start(time);
}, `start(${time})`)
.throw(TypeError);
});
should(() => {
node.stop();
}, 'Calling stop() before start()').throw(DOMException, 'InvalidStateError');
should(() => {
node.start(-1);
}, 'start(-1)').throw(RangeError);
if (options) {
options.forEach(test => {
should(() => {node.start(...test.args)},
'start(' + test.args + ')').throw(test.errorType);
});
}
node.start();
should(() => {
node.start();
}, 'Calling start() twice').throw(DOMException, 'InvalidStateError');
should(() => {
node.stop(-1);
}, 'stop(-1)').throw(RangeError);
// Test non-finite stop times
nonFiniteValues.forEach(time => {
should(() => {
node.stop(time);
}, `stop(${time})`)
.throw(TypeError);
});
}

View file

@ -0,0 +1,37 @@
<!doctype html>
<html>
<head>
<title>
Basic Test of AudioBufferSourceNode
</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../webaudio/resources/audit-util.js"></script>
<script src="../../../webaudio/resources/audit.js"></script>
<script src="../../../webaudio/resources/start-stop-exceptions.js"></script>
</head>
<script id="layout-test-code">
let sampleRate = 44100;
let renderLengthSeconds = 0.25;
let oscTypes = ['sine', 'square', 'sawtooth', 'triangle', 'custom'];
let audit = Audit.createTaskRunner();
audit.define('start/stop exceptions', (task, should) => {
// We're not going to render anything, so make it simple
let context = new OfflineAudioContext(1, 1, sampleRate);
let node = new AudioBufferSourceNode(context);
testStartStop(should, node, [
{args: [0, -1], errorType: RangeError},
{args: [0, 0, -1], errorType: RangeError}
]);
task.done();
});
audit.run();
</script>
<body>
</body>
</html>