Skip to content
Permalink
Browse files
Only require a "transient" user activation for Web Audio rendering
https://bugs.webkit.org/show_bug.cgi?id=247614
rdar://102364692

Reviewed by Jer Noble.

Only require a "transient" user activation for Web Audio rendering instead of
having a strict synchronous user gesture requirement. Our behavior was so
strict it was confusing Web developers. It was also causing a lot of demo sites
to fail in Safari and work in other browsers.

For example:
- https://googlechromelabs.github.io/web-audio-samples/audio-worklet/basic/hello-audio-worklet/

Such demos require you to click a button but then they use an async function
and use `await` before actually starting the audio rendering.

* LayoutTests/webaudio/require-transient-activation-expected.txt: Added.
* LayoutTests/webaudio/require-transient-activation.html: Added.
* Source/WebCore/Modules/webaudio/AudioContext.cpp:
(WebCore::shouldDocumentAllowWebAudioToAutoPlay):

Canonical link: https://commits.webkit.org/256721@main
  • Loading branch information
cdumez committed Nov 16, 2022
1 parent 4e7d68f commit 7cc4b4216132642cf20a83a674947c4c578e86a7
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 2 deletions.
@@ -0,0 +1,16 @@
Tests that starting Web Audio rendering requires a transient user activation.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS context.state is "suspended"
node.connect(context.destination)
PASS context.state is "suspended"
PASS navigator.userActivation.isActive is true
PASS resume() promise was resolved
PASS context.state is "running"
PASS navigator.userActivation.isActive is true
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<script src="../resources/js-test.js"></script>
<script type="text/javascript" src="resources/audio-testing.js"></script>
</head>
<body>
<script>
description("Tests that starting Web Audio rendering requires a transient user activation.");
jsTestIsAsync = true;

onload = () => {
context = new AudioContext();

if (window.internals)
internals.setAudioContextRestrictions(context, 'RequireUserGestureForAudioStart');

shouldBeEqualToString('context.state', 'suspended');

node = context.createBufferSource();
evalAndLog('node.connect(context.destination)');

shouldBeEqualToString('context.state', 'suspended');

runWithKeyDown(function() {
setTimeout(() => {
// We should have transient activation.
shouldBeTrue("navigator.userActivation.isActive");
context.resume().then(() => {
testPassed("resume() promise was resolved");
shouldBeEqualToString('context.state', 'running');
// Transient activation should not have been consumed.
shouldBeTrue("navigator.userActivation.isActive");
finishJSTest();
}, () => {
testFailed("resume() promise was rejected");
finishJSTest();
});
}, 10);
});
};
</script>
</body>
</html>
@@ -77,9 +77,12 @@ static std::optional<float>& defaultSampleRateForTesting()

static bool shouldDocumentAllowWebAudioToAutoPlay(const Document& document)
{
if (document.processingUserGestureForMedia() || document.isCapturing())
if (document.isCapturing())
return true;
return document.quirks().shouldAutoplayWebAudioForArbitraryUserGesture() && document.topDocument().hasHadUserInteraction();
if (document.quirks().shouldAutoplayWebAudioForArbitraryUserGesture() && document.topDocument().hasHadUserInteraction())
return true;
auto* window = document.domWindow();
return window && window->hasTransientActivation();
}

void AudioContext::setDefaultSampleRateForTesting(std::optional<float> sampleRate)

0 comments on commit 7cc4b42

Please sign in to comment.