WebKit fails to leave audio routing arbitration during navigation, closing.
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 21 Jun 2020 15:34:12 +0000 (15:34 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 21 Jun 2020 15:34:12 +0000 (15:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=213426
<rdar://problem/64395051>

Reviewed by Eric Carlson.

Source/WebCore:

When setting the AudioSession category, make sure to leave routing arbitration before bailing out early. Also,
HTMLMediaElement::canProduceAudio() should return `false` when the element's document is suspended or stopped.
Otherwise, the AudioSession will continue in the `MediaPlayback` category indefinitely, and routing arbitration
will remain active.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::canProduceAudio const):
* platform/audio/mac/AudioSessionMac.mm:
(WebCore::AudioSession::setCategory):

Source/WebKit:

Add testing SPIs to verify whether a WebPage successfully entered or left audio routing
arbitration. Notify the Arbitration proxy when the page is shut down, which ensures arbitration
will end if the client closes the WKWebView.

* UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h:
* UIProcess/API/Cocoa/WKWebViewTesting.mm:
(-[WKWebView _audioRoutingArbitrationStatus]):
* UIProcess/API/mac/WKWebViewTestingMac.mm:
* UIProcess/Media/AudioSessionRoutingArbitratorProxy.h:
(WebKit::AudioSessionRoutingArbitratorProxy::arbitrationStatus const):
* UIProcess/Media/cocoa/AudioSessionRoutingArbitratorProxyCocoa.mm:
(WebKit::AudioSessionRoutingArbitratorProxy::processDidTerminate):
(WebKit::AudioSessionRoutingArbitratorProxy::beginRoutingArbitrationWithCategory):
(WebKit::AudioSessionRoutingArbitratorProxy::endRoutingArbitration):
* UIProcess/WebPageProxy.h:
* UIProcess/WebProcessProxy.cpp:
(WebKit::WebProcessProxy::shutDown):
* UIProcess/WebProcessProxy.h:
(WebKit::WebProcessProxy::audioSessionRoutingArbitrator):

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/AudioRoutingArbitration.mm: Added.
(AudioRoutingArbitration::statusShouldBecomeEqualTo):
(TEST_F):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263328 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/platform/audio/mac/AudioSessionMac.mm
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h
Source/WebKit/UIProcess/API/Cocoa/WKWebViewTesting.mm
Source/WebKit/UIProcess/API/mac/WKWebViewTestingMac.mm
Source/WebKit/UIProcess/Media/AudioSessionRoutingArbitratorProxy.h
Source/WebKit/UIProcess/Media/cocoa/AudioSessionRoutingArbitratorProxyCocoa.mm
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebProcessProxy.cpp
Source/WebKit/UIProcess/WebProcessProxy.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/AudioRoutingArbitration.mm [new file with mode: 0644]

index 005989b..4ef26a4 100644 (file)
@@ -1,3 +1,21 @@
+2020-06-21  Jer Noble  <jer.noble@apple.com>
+
+        WebKit fails to leave audio routing arbitration during navigation, closing.
+        https://bugs.webkit.org/show_bug.cgi?id=213426
+        <rdar://problem/64395051>
+
+        Reviewed by Eric Carlson.
+
+        When setting the AudioSession category, make sure to leave routing arbitration before bailing out early. Also,
+        HTMLMediaElement::canProduceAudio() should return `false` when the element's document is suspended or stopped.
+        Otherwise, the AudioSession will continue in the `MediaPlayback` category indefinitely, and routing arbitration
+        will remain active.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::canProduceAudio const):
+        * platform/audio/mac/AudioSessionMac.mm:
+        (WebCore::AudioSession::setCategory):
+
 2020-06-21  Zalan Bujtas  <zalan@apple.com>
 
         [LFC][TFC] Add support for percentage min/max-width
index 58bfca1..bbd21b0 100644 (file)
@@ -7369,6 +7369,9 @@ bool HTMLMediaElement::canProduceAudio() const
         return true;
 #endif
 
+    if (isSuspended())
+        return false;
+
     if (muted())
         return false;
 
index 996b07e..f798f68 100644 (file)
@@ -83,23 +83,15 @@ AudioSession::CategoryType AudioSession::category() const
 void AudioSession::setCategory(CategoryType category, RouteSharingPolicy)
 {
 #if ENABLE(ROUTING_ARBITRATION)
-    if (category == AmbientSound || category == SoloAmbientSound || category == AudioProcessing) {
-        m_private->category = category;
-        return;
-    }
-
     if (category == m_private->category)
         return;
+    m_private->category = category;
 
     if (m_private->setupArbitrationOngoing) {
         RELEASE_LOG_ERROR(Media, "AudioSession::setCategory() - a beginArbitrationWithCategory is still ongoing");
         return;
     }
 
-    m_private->category = category;
-    if (m_private->category == None)
-        return;
-
     if (!m_routingArbitrationClient)
         return;
 
@@ -108,6 +100,9 @@ void AudioSession::setCategory(CategoryType category, RouteSharingPolicy)
         m_routingArbitrationClient->leaveRoutingAbritration();
     }
 
+    if (category == AmbientSound || category == SoloAmbientSound || category == AudioProcessing || category == None)
+        return;
+
     using RoutingArbitrationError = AudioSessionRoutingArbitrationClient::RoutingArbitrationError;
     using DefaultRouteChanged = AudioSessionRoutingArbitrationClient::DefaultRouteChanged;
 
index 840da8f..5024b12 100644 (file)
@@ -1,3 +1,31 @@
+2020-06-21  Jer Noble  <jer.noble@apple.com>
+
+        WebKit fails to leave audio routing arbitration during navigation, closing.
+        https://bugs.webkit.org/show_bug.cgi?id=213426
+        <rdar://problem/64395051>
+
+        Reviewed by Eric Carlson.
+
+        Add testing SPIs to verify whether a WebPage successfully entered or left audio routing
+        arbitration. Notify the Arbitration proxy when the page is shut down, which ensures arbitration
+        will end if the client closes the WKWebView.
+
+        * UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h:
+        * UIProcess/API/Cocoa/WKWebViewTesting.mm:
+        (-[WKWebView _audioRoutingArbitrationStatus]):
+        * UIProcess/API/mac/WKWebViewTestingMac.mm:
+        * UIProcess/Media/AudioSessionRoutingArbitratorProxy.h:
+        (WebKit::AudioSessionRoutingArbitratorProxy::arbitrationStatus const):
+        * UIProcess/Media/cocoa/AudioSessionRoutingArbitratorProxyCocoa.mm:
+        (WebKit::AudioSessionRoutingArbitratorProxy::processDidTerminate):
+        (WebKit::AudioSessionRoutingArbitratorProxy::beginRoutingArbitrationWithCategory):
+        (WebKit::AudioSessionRoutingArbitratorProxy::endRoutingArbitration):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebProcessProxy.cpp:
+        (WebKit::WebProcessProxy::shutDown):
+        * UIProcess/WebProcessProxy.h:
+        (WebKit::WebProcessProxy::audioSessionRoutingArbitrator):
+
 2020-06-21  Michael Catanzaro  <mcatanzaro@gnome.org>
 
         [WPE][GTK] Specify underlying storage type for InputMethodState::Hint
index 0184f0b..8755e46 100644 (file)
 #import "WKWebViewPrivateForTestingIOS.h"
 #import "WKWebViewPrivateForTestingMac.h"
 
+typedef enum {
+    WKWebViewAudioRoutingArbitrationStatusNone,
+    WKWebViewAudioRoutingArbitrationStatusPending,
+    WKWebViewAudioRoutingArbitrationStatusActive,
+} WKWebViewAudioRoutingArbitrationStatus;
+
 @interface WKWebView (WKTesting)
 
 - (void)_setPageScale:(CGFloat)scale withOrigin:(CGPoint)origin;
@@ -66,4 +72,5 @@
 + (void)_clearApplicationBundleIdentifierTestingOverride;
 
 - (BOOL)_hasSleepDisabler;
+- (WKWebViewAudioRoutingArbitrationStatus)_audioRoutingArbitrationStatus;
 @end
index c4e574b..45e70e6 100644 (file)
@@ -26,6 +26,7 @@
 #import "config.h"
 #import "WKWebViewPrivateForTesting.h"
 
+#import "AudioSessionRoutingArbitratorProxy.h"
 #import "UserMediaProcessManager.h"
 #import "ViewGestureController.h"
 #import "WKWebViewIOS.h"
     return _page && _page->process().hasSleepDisabler();
 }
 
+- (WKWebViewAudioRoutingArbitrationStatus)_audioRoutingArbitrationStatus
+{
+#if ENABLE(ROUTING_ARBITRATION)
+    switch (_page->process().audioSessionRoutingArbitrator().arbitrationStatus()) {
+    default: ASSERT_NOT_REACHED();
+    case WebKit::AudioSessionRoutingArbitratorProxy::ArbitrationStatus::None: return WKWebViewAudioRoutingArbitrationStatusNone;
+    case WebKit::AudioSessionRoutingArbitratorProxy::ArbitrationStatus::Pending: return WKWebViewAudioRoutingArbitrationStatusPending;
+    case WebKit::AudioSessionRoutingArbitratorProxy::ArbitrationStatus::Active: return WKWebViewAudioRoutingArbitrationStatusActive;
+    }
+#else
+    return WKWebViewAudioRoutingArbitrationStatusNone;
+#endif
+}
+
 @end
index 8c54204..2d84132 100644 (file)
 
 #if PLATFORM(MAC)
 
+#import "AudioSessionRoutingArbitratorProxy.h"
 #import "WKWebViewMac.h"
 #import "_WKFrameHandleInternal.h"
 #import "WebPageProxy.h"
+#import "WebProcessProxy.h"
 #import "WebViewImpl.h"
 
 @implementation WKWebView (WKTestingMac)
index 258b81c..f62bbef 100644 (file)
@@ -52,6 +52,14 @@ public:
     using DefaultRouteChanged = WebCore::AudioSessionRoutingArbitrationClient::DefaultRouteChanged;
     using ArbitrationCallback = CompletionHandler<void(RoutingArbitrationError, DefaultRouteChanged)>;
 
+    enum class ArbitrationStatus : uint8_t {
+        None,
+        Pending,
+        Active,
+    };
+
+    ArbitrationStatus arbitrationStatus() const { return m_arbitrationStatus; }
+
 private:
     // IPC::MessageReceiver
     void didReceiveMessage(IPC::Connection&, IPC::Decoder&) final;
@@ -62,6 +70,7 @@ private:
 
     WebProcessProxy& m_process;
     WebCore::AudioSession::CategoryType m_category { WebCore::AudioSession::None };
+    ArbitrationStatus m_arbitrationStatus { ArbitrationStatus::None };
 };
 
 }
index 1fa51fb..8cbe9a6 100644 (file)
@@ -160,18 +160,24 @@ AudioSessionRoutingArbitratorProxy::~AudioSessionRoutingArbitratorProxy()
 void AudioSessionRoutingArbitratorProxy::processDidTerminate()
 {
     if (SharedArbitrator::sharedInstance().isInRoutingArbitrationForArbitrator(*this))
-        SharedArbitrator::sharedInstance().endRoutingArbitrationForArbitrator(*this);
+        endRoutingArbitration();
 }
 
 void AudioSessionRoutingArbitratorProxy::beginRoutingArbitrationWithCategory(WebCore::AudioSession::CategoryType category, ArbitrationCallback&& callback)
 {
     m_category = category;
-    SharedArbitrator::sharedInstance().beginRoutingArbitrationForArbitrator(*this, WTFMove(callback));
+    m_arbitrationStatus = ArbitrationStatus::Pending;
+    SharedArbitrator::sharedInstance().beginRoutingArbitrationForArbitrator(*this, [weakThis = makeWeakPtr(*this), callback = WTFMove(callback)] (RoutingArbitrationError error, DefaultRouteChanged routeChanged) mutable {
+        if (weakThis)
+            weakThis->m_arbitrationStatus = error == RoutingArbitrationError::None ? ArbitrationStatus::Active : ArbitrationStatus::None;
+        callback(error, routeChanged);
+    });
 }
 
 void AudioSessionRoutingArbitratorProxy::endRoutingArbitration()
 {
     SharedArbitrator::sharedInstance().endRoutingArbitrationForArbitrator(*this);
+    m_arbitrationStatus = ArbitrationStatus::None;
 }
 
 }
index 4912404..1298c55 100644 (file)
@@ -2837,10 +2837,6 @@ private:
     bool m_userScriptsNotified { false };
     bool m_limitsNavigationsToAppBoundDomains { false };
     bool m_hasExecutedAppBoundBehaviorBeforeNavigation { false };
-
-#if ENABLE(ROUTING_ARBITRATION)
-    std::unique_ptr<AudioSessionRoutingArbitratorProxy> m_routingArbitrator;
-#endif
 };
 
 } // namespace WebKit
index 0dd010d..2ec7b98 100644 (file)
@@ -435,6 +435,10 @@ void WebProcessProxy::shutDown()
     m_sleepDisablers.clear();
 
     m_processPool->disconnectProcess(this);
+
+#if ENABLE(ROUTING_ARBITRATION)
+    m_routingArbitrator->processDidTerminate();
+#endif
 }
 
 WebPageProxy* WebProcessProxy::webPage(WebPageProxyIdentifier pageID)
index 260ee74..b1fb0ff 100644 (file)
@@ -397,6 +397,10 @@ public:
     void markHasManagedSessionSandboxAccess() { m_hasManagedSessionSandboxAccess = true; }
 #endif
 
+#if ENABLE(ROUTING_ARBITRATION)
+    AudioSessionRoutingArbitratorProxy& audioSessionRoutingArbitrator() { return m_routingArbitrator.get(); }
+#endif
+
 protected:
     WebProcessProxy(WebProcessPool&, WebsiteDataStore*, IsPrewarmed);
 
index a52c023..debda55 100644 (file)
@@ -1,3 +1,16 @@
+2020-06-21  Jer Noble  <jer.noble@apple.com>
+
+        WebKit fails to leave audio routing arbitration during navigation, closing.
+        https://bugs.webkit.org/show_bug.cgi?id=213426
+        <rdar://problem/64395051>
+
+        Reviewed by Eric Carlson.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/AudioRoutingArbitration.mm: Added.
+        (AudioRoutingArbitration::statusShouldBecomeEqualTo):
+        (TEST_F):
+
 2020-06-20  Jer Noble  <jer.noble@apple.com>
 
         REGRESSION(r259219): Sleep assertion remains active if WKWebView is closed or WebContent process crashes
index 9e88112..289a070 100644 (file)
                CDCFFEC122E26A1500DF4223 /* NoPauseWhenSwitchingTabs.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDCFFEC022E268D500DF4223 /* NoPauseWhenSwitchingTabs.mm */; };
                CDD68F0D22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDD68F0C22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm */; };
                CDE195B51CFE0B880053D256 /* FullscreenTopContentInset.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CDE195B21CFE0ADE0053D256 /* FullscreenTopContentInset.html */; };
+               CDED342F249DDE0E0002AE7A /* AudioRoutingArbitration.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDED342E249DDD9D0002AE7A /* AudioRoutingArbitration.mm */; };
                CDF0B78A216D48DC00421ECC /* CloseWebViewDuringEnterFullscreen.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDF0B789216D484300421ECC /* CloseWebViewDuringEnterFullscreen.mm */; };
                CDF92237216D186400647AA7 /* CloseWebViewAfterEnterFullscreen.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDF92236216D186400647AA7 /* CloseWebViewAfterEnterFullscreen.mm */; };
                CE06DF9B1E1851F200E570C9 /* SecurityOrigin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE06DF9A1E1851F200E570C9 /* SecurityOrigin.cpp */; };
                CDD68F0C22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKWebViewCloseAllMediaPresentations.mm; sourceTree = "<group>"; };
                CDE195B21CFE0ADE0053D256 /* FullscreenTopContentInset.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = FullscreenTopContentInset.html; sourceTree = "<group>"; };
                CDE195B31CFE0ADE0053D256 /* TopContentInset.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TopContentInset.mm; sourceTree = "<group>"; };
+               CDED342E249DDD9D0002AE7A /* AudioRoutingArbitration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioRoutingArbitration.mm; sourceTree = "<group>"; };
                CDF0B789216D484300421ECC /* CloseWebViewDuringEnterFullscreen.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CloseWebViewDuringEnterFullscreen.mm; sourceTree = "<group>"; };
                CDF92236216D186400647AA7 /* CloseWebViewAfterEnterFullscreen.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CloseWebViewAfterEnterFullscreen.mm; sourceTree = "<group>"; };
                CE06DF9A1E1851F200E570C9 /* SecurityOrigin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SecurityOrigin.cpp; sourceTree = "<group>"; };
                                51B40D9D23AC960E00E05241 /* AsyncFunction.mm */,
                                834138C6203261B900F26960 /* AsyncPolicyForNavigationResponse.mm */,
                                3760C4F0211249AF00233ACC /* AttrStyle.mm */,
+                               CDED342E249DDD9D0002AE7A /* AudioRoutingArbitration.mm */,
                                754CEC801F6722DC00D0039A /* AutoFillAvailable.mm */,
                                2DD355351BD08378005DF4A7 /* AutoLayoutIntegration.mm */,
                                07CD32F52065B5420064A4BE /* AVFoundationPreference.mm */,
                                1C2B81801C891E7C00A5529F /* CancelFontSubresource.mm in Sources */,
                                7CCE7EB61A411A7E00447C4C /* CancelLoadFromResourceLoadDelegate.mm in Sources */,
                                7C83E0411D0A63F200FEBCF3 /* CandidateTests.mm in Sources */,
+                               CDED342F249DDE0E0002AE7A /* AudioRoutingArbitration.mm in Sources */,
                                7CCE7EE71A411AE600447C4C /* CanHandleRequest.cpp in Sources */,
                                07C046CA1E4262A8007201E7 /* CARingBuffer.cpp in Sources */,
                                57303BCB2008376500355965 /* CBORReaderTest.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/AudioRoutingArbitration.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/AudioRoutingArbitration.mm
new file mode 100644 (file)
index 0000000..c1e0dcc
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#if ENABLE(ROUTING_ARBITRATION)
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKWebViewConfigurationPrivate.h>
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/WKWebViewPrivateForTesting.h>
+
+class AudioRoutingArbitration : public testing::Test {
+public:
+    RetainPtr<TestWKWebView> webView;
+
+    void SetUp() final
+    {
+        auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+        configuration.get()._mediaDataLoadsAutomatically = YES;
+        configuration.get().mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
+        webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration.get() addToWindow:YES]);
+
+        bool isPlaying = false;
+        [webView performAfterReceivingMessage:@"playing" action:[&] { isPlaying = true; }];
+        [webView synchronouslyLoadTestPageNamed:@"video-with-audio"];
+        TestWebKitAPI::Util::run(&isPlaying);
+    }
+
+    void TearDown() final
+    {
+        [webView _close];
+    }
+
+    void statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatus status)
+    {
+        int tries = 0;
+        do {
+            if ([webView _audioRoutingArbitrationStatus] == status)
+                break;
+
+            TestWebKitAPI::Util::sleep(0.1);
+        } while (++tries <= 100);
+
+        EXPECT_EQ(status, [webView _audioRoutingArbitrationStatus]);
+    }
+};
+
+TEST_F(AudioRoutingArbitration, Basic)
+{
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusActive);
+}
+
+TEST_F(AudioRoutingArbitration, Mute)
+{
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusActive);
+
+    [webView objectByEvaluatingJavaScriptWithUserGesture:@"document.querySelector('video').muted = true"];
+
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusNone);
+
+    [webView objectByEvaluatingJavaScriptWithUserGesture:@"document.querySelector('video').muted = false"];
+
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusActive);
+}
+
+TEST_F(AudioRoutingArbitration, Navigation)
+{
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusActive);
+
+    [webView synchronouslyLoadHTMLString:@"<html>no contents</html>"];
+
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusNone);
+}
+
+TEST_F(AudioRoutingArbitration, Deletion)
+{
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusActive);
+
+    [webView objectByEvaluatingJavaScriptWithUserGesture:@"document.querySelector('video').parentNode.innerHTML = ''"];
+
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusNone);
+}
+
+TEST_F(AudioRoutingArbitration, Close)
+{
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusActive);
+
+    [webView _close];
+
+    statusShouldBecomeEqualTo(WKWebViewAudioRoutingArbitrationStatusNone);
+}
+
+
+#endif