Prerender: Defer Access to Speaker selection API for Prerendering This CL adds a web tests to ensure that the access of the Speaker selection API are deferred until prerendered page is activated. Bug: 1202032 Change-Id: Ice6e4ba1228de1d4acbbfc534f68d277b8f13a73 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2850496 Commit-Queue: Miyoung Shin <myid.shin@igalia.com> Reviewed-by: Lingqi Chi <lingqi@chromium.org> Reviewed-by: Matt Falkenhagen <falken@chromium.org> Reviewed-by: Guido Urdaneta <guidou@chromium.org> Cr-Commit-Position: refs/heads/master@{#884013}
diff --git a/third_party/blink/renderer/modules/audio_output_devices/html_media_element_audio_output_device.cc b/third_party/blink/renderer/modules/audio_output_devices/html_media_element_audio_output_device.cc index 8d587c2..c2b6710 100644 --- a/third_party/blink/renderer/modules/audio_output_devices/html_media_element_audio_output_device.cc +++ b/third_party/blink/renderer/modules/audio_output_devices/html_media_element_audio_output_device.cc
@@ -58,6 +58,8 @@ ~SetSinkIdResolver() override = default; void StartAsync(); + void Start(); + void Trace(Visitor*) const override; private: @@ -96,6 +98,25 @@ WrapWeakPersistent(this))); } +void SetSinkIdResolver::Start() { + auto* context = GetExecutionContext(); + if (!context || context->IsContextDestroyed()) + return; + + if (LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context)) { + if (window->document()->IsPrerendering()) { + window->document()->AddPostPrerenderingActivationStep( + WTF::Bind(&SetSinkIdResolver::Start, WrapWeakPersistent(this))); + return; + } + } + + if (sink_id_ == HTMLMediaElementAudioOutputDevice::sinkId(*element_)) + Resolve(); + else + StartAsync(); +} + void SetSinkIdResolver::DoSetSinkId() { auto set_sink_id_completion_callback = WTF::Bind(&SetSinkIdResolver::OnSetSinkIdComplete, WrapPersistent(this)); @@ -189,11 +210,7 @@ SetSinkIdResolver* resolver = SetSinkIdResolver::Create(script_state, element, sink_id); ScriptPromise promise = resolver->Promise(); - if (sink_id == HTMLMediaElementAudioOutputDevice::sinkId(element)) - resolver->Resolve(); - else - resolver->StartAsync(); - + resolver->Start(); return promise; }
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/audio-setSinkId.https.html b/third_party/blink/web_tests/wpt_internal/prerender/resources/audio-setSinkId.https.html new file mode 100644 index 0000000..bec8e09b --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/audio-setSinkId.https.html
@@ -0,0 +1,31 @@ +<!-- + Copyright 2021 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<script src="deferred-promise-utils.js"></script> +<audio controls id="beat" src="/media/mp3/sound_5.mp3" loop></audio> +<script> + +const params = new URLSearchParams(location.search); + +// The main test page (restriction-audio-setSinkId.https.html) loads the +// initiator page, then the initiator page will prerender itself with the +// `prerendering` parameter. +const isPrerendering = params.has('prerendering'); + +if (!isPrerendering) { + loadInitiatorPage(); +} else { + const prerenderEventCollector = new PrerenderEventCollector(); + + const promise = beat.setSinkId( + params.get('sinkId') === 'invalid' ? 'fakeId' : ''); + prerenderEventCollector.start(promise, 'Audio.setSinkId'); +} +</script>
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-audio-setSinkId-with-invalid-sinkId.https.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-audio-setSinkId-with-invalid-sinkId.https.html new file mode 100644 index 0000000..66122a2 --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-audio-setSinkId-with-invalid-sinkId.https.html
@@ -0,0 +1,64 @@ +<!-- + Copyright 2021 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<!DOCTYPE html> +<!-- +This file cannot be upstreamed to WPT until: +* startPrerendering() usage is replaced with a WebDriver API +* 'speaker-selection' gets permission to use speaker devices with a WebDriver +* API +--> + +<title> +Access to the setSinkId of the Audio API with an invalid value is deferred +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<body> +<script> + +promise_test(async t => { + const bc = new BroadcastChannel('test-channel'); + t.add_cleanup(_ => bc.close()); + + const gotMessage = new Promise(resolve => { + bc.addEventListener('message', e => { + resolve(e.data); + }, { + once: true + }); + }); + + const url = `resources/audio-setSinkId.https.html?sinkId=invalid`; + window.open(url, '_blank', 'noopener'); + + const result = await gotMessage; + const expected = [ + { + event: 'started waiting Audio.setSinkId', + prerendering: true + }, + { + event: 'prerendering change', + prerendering: false + }, + { + event: 'Audio.setSinkId rejected: NotFoundError', + prerendering: false + }, + ]; + assert_equals(result.length, expected.length); + for (let i = 0; i < result.length; i++) { + assert_equals(result[i].event, expected[i].event, `event${i}`); + assert_equals(result[i].prerendering, expected[i].prerendering, + `prerendering${i}`); + } + +}, `the access to the setSinkId of Audio API with the invalid sinkId should be + deferred until the prerendered page is activated`); +</script> +</body>
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-audio-setSinkId.https.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-audio-setSinkId.https.html new file mode 100644 index 0000000..c7309c5 --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-audio-setSinkId.https.html
@@ -0,0 +1,62 @@ +<!-- + Copyright 2021 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<!DOCTYPE html> +<!-- +This file cannot be upstreamed to WPT until: +* startPrerendering() usage is replaced with a WebDriver API +* 'speaker-selection' is used with a WebDriver API +--> + +<title>Access to the setSinkId of the Audio API is deferred</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<body> +<script> + +promise_test(async t => { + const bc = new BroadcastChannel('test-channel'); + t.add_cleanup(_ => bc.close()); + + const gotMessage = new Promise(resolve => { + bc.addEventListener('message', e => { + resolve(e.data); + }, { + once: true + }); + }); + + const url = `resources/audio-setSinkId.https.html?sinkId=default`; + window.open(url, '_blank', 'noopener'); + + const result = await gotMessage; + const expected = [ + { + event: 'started waiting Audio.setSinkId', + prerendering: true + }, + { + event: 'prerendering change', + prerendering: false + }, + { + event: 'finished waiting Audio.setSinkId', + prerendering: false + }, + ]; + assert_equals(result.length, expected.length); + for (let i = 0; i < result.length; i++) { + assert_equals(result[i].event, expected[i].event, `event${i}`); + assert_equals(result[i].prerendering, expected[i].prerendering, + `prerendering${i}`); + } + +}, `the access to the setSinkId of Audio API should be deferred until the + prerendered page is activated`); + +</script> +</body>