Title: [275537] trunk
Revision
275537
Author
eric.carl...@apple.com
Date
2021-04-06 12:06:53 -0700 (Tue, 06 Apr 2021)

Log Message

[macOS] Add MediaSessionCoordinator API test
https://bugs.webkit.org/show_bug.cgi?id=224140
<rdar://problem/76171173>

Reviewed by Jer Noble.

Source/WebKit:

* Sources.txt: Include RemoteMediaSessionCoordinatorProxy.cpp and RemoteMediaSessionCoordinator.cpp
so they don't have to be included in the Xcode project.

* UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h: Declare enums, struct, and protocols
necessary to implement and use an object derived from MediaSessionCoordinatorProxyPrivate.
* UIProcess/API/Cocoa/WKWebViewTesting.mm:
(-[WKWebView _createMediaSessionCoordinatorForTesting:completionHandler:]): New private
method to create a MediaSessionCoordinatorProxyPrivate.
(-[WKMediaSessionCoordinatorHelper initWithCoordinator:]):
(-[WKMediaSessionCoordinatorHelper seekSessionToTime:withCompletion:]):
(-[WKMediaSessionCoordinatorHelper playSessionWithCompletion:]):
(-[WKMediaSessionCoordinatorHelper pauseSessionWithCompletion:]):
(-[WKMediaSessionCoordinatorHelper setSessionTrack:withCompletion:]):

* UIProcess/Cocoa/WebViewImpl.h:
(WebKit::WebViewImpl::mediaSessionCoordinatorForTesting): MediaSessionCoordinatorProxyPrivate
getter.
* UIProcess/Cocoa/WebViewImpl.mm:
(WebKit::WebViewImpl::setMediaSessionCoordinatorForTesting): Setter for
MediaSessionCoordinatorProxyPrivate drived object.

* UIProcess/Media/MediaSessionCoordinatorProxyPrivate.h: Add typedef for the completion
handler used by many coordinator proxy methods.

* UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp:
(WebKit::RemoteMediaSessionCoordinatorProxy::RemoteMediaSessionCoordinatorProxy):
Set the private coordinator client so calls to the session can be forwarded.
(WebKit::RemoteMediaSessionCoordinatorProxy::join): Use MediaSessionCommandCompletionHandler.
(WebKit::RemoteMediaSessionCoordinatorProxy::coordinateSeekTo): Ditto.
(WebKit::RemoteMediaSessionCoordinatorProxy::coordinatePlay): Ditto.
(WebKit::RemoteMediaSessionCoordinatorProxy::coordinatePause): Ditto.
(WebKit::RemoteMediaSessionCoordinatorProxy::coordinateSetTrack): Ditto.
(WebKit::RemoteMediaSessionCoordinatorProxy::coordinatorStateChanged): Ditto.
* UIProcess/Media/RemoteMediaSessionCoordinatorProxy.h:

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::createMediaSessionCoordinator): Change completion handler
parameter to return a bool instead of a RemoteMediaSessionCoordinatorProxy as the
client interface provides all of the session access needed.
* UIProcess/WebPageProxy.h:

* WebKit.xcodeproj/project.pbxproj: Remove RemoteMediaSessionCoordinatorProxy.cpp
and RemoteMediaSessionCoordinator.cpp

* WebProcess/MediaSession/RemoteMediaSessionCoordinator.cpp:
(WebKit::RemoteMediaSessionCoordinator::join): Use ExceptionData's toException()
method instead of creating an Exception directly. Remove the destination ID parameter
to sendWithAsyncReply so messageSenderDestinationID() will be used.
(WebKit::RemoteMediaSessionCoordinator::leave): Ditto.
(WebKit::RemoteMediaSessionCoordinator::seekTo) Ditto.:
(WebKit::RemoteMediaSessionCoordinator::play): Ditto.
(WebKit::RemoteMediaSessionCoordinator::pause): Ditto.
(WebKit::RemoteMediaSessionCoordinator::setTrack): Ditto.
(WebKit::RemoteMediaSessionCoordinator::positionStateChanged): Ditto.
(WebKit::RemoteMediaSessionCoordinator::readyStateChanged): Ditto.
(WebKit::RemoteMediaSessionCoordinator::playbackStateChanged): Ditto.
(WebKit::RemoteMediaSessionCoordinator::coordinatorStateChanged): Ditto.

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/TestWebKitAPI.xcodeproj/xcshareddata/xcschemes/TestWebKitAPI.xcscheme:
* TestWebKitAPI/Tests/WebKit/MediaSessionCoordinatorTest.mm: Added.
(-[_WKMockMediaSessionCoordinator lastStateChange]):
(-[_WKMockMediaSessionCoordinator lastMethodCalled]):
(-[_WKMockMediaSessionCoordinator identifier]):
(-[_WKMockMediaSessionCoordinator joinWithCompletion:]):
(-[_WKMockMediaSessionCoordinator leave]):
(-[_WKMockMediaSessionCoordinator seekTo:withCompletion:]):
(-[_WKMockMediaSessionCoordinator playWithCompletion:]):
(-[_WKMockMediaSessionCoordinator pauseWithCompletion:]):
(-[_WKMockMediaSessionCoordinator setTrack:withCompletion:]):
(-[_WKMockMediaSessionCoordinator positionStateChanged:]):
(-[_WKMockMediaSessionCoordinator readyStateChanged:]):
(-[_WKMockMediaSessionCoordinator playbackStateChanged:]):
(-[_WKMockMediaSessionCoordinator coordinatorStateChanged:]):
(-[_WKMockMediaSessionCoordinator seekSessionToTime:]):
(-[_WKMockMediaSessionCoordinator playSession]):
(-[_WKMockMediaSessionCoordinator pauseSession]):
(-[_WKMockMediaSessionCoordinator setSessionTrack:]):
(TestWebKitAPI::MediaSessionCoordinatorTest::createCoordinator):
(TestWebKitAPI::MediaSessionCoordinatorTest::webView const):
(TestWebKitAPI::MediaSessionCoordinatorTest::coordinator const):
(TestWebKitAPI::MediaSessionCoordinatorTest::loadPageAndBecomeReady):
(TestWebKitAPI::MediaSessionCoordinatorTest::runScriptWithUserGesture):
(TestWebKitAPI::MediaSessionCoordinatorTest::play):
(TestWebKitAPI::MediaSessionCoordinatorTest::pause):
(TestWebKitAPI::MediaSessionCoordinatorTest::listenForEventMessages):
(TestWebKitAPI::MediaSessionCoordinatorTest::eventListenerWasCalled):
(TestWebKitAPI::MediaSessionCoordinatorTest::clearEventListenerState):
(TestWebKitAPI::MediaSessionCoordinatorTest::executeUntil):
(TestWebKitAPI::MediaSessionCoordinatorTest::waitForEventListenerToBeCalled):
(TestWebKitAPI::MediaSessionCoordinatorTest::listenForMessagesPosted):
(TestWebKitAPI::MediaSessionCoordinatorTest::clearMessagesPosted):
(TestWebKitAPI::MediaSessionCoordinatorTest::listenForSessionHandlerMessages):
(TestWebKitAPI::MediaSessionCoordinatorTest::sessionHandlerWasCalled):
(TestWebKitAPI::MediaSessionCoordinatorTest::waitForSessionHandlerToBeCalled):
(TestWebKitAPI::MediaSessionCoordinatorTest::listenForPromiseMessages):
(TestWebKitAPI::MediaSessionCoordinatorTest::clearPromiseMessages):
(TestWebKitAPI::MediaSessionCoordinatorTest::promiseWasResolved):
(TestWebKitAPI::MediaSessionCoordinatorTest::promiseWasRejected):
(TestWebKitAPI::MediaSessionCoordinatorTest::waitForPromise):
(TestWebKitAPI::TEST_F):
* TestWebKitAPI/Tests/WebKitCocoa/media-remote.html:

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (275536 => 275537)


--- trunk/Source/WebKit/ChangeLog	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/ChangeLog	2021-04-06 19:06:53 UTC (rev 275537)
@@ -1,3 +1,69 @@
+2021-04-06  Eric Carlson  <eric.carl...@apple.com>
+
+        [macOS] Add MediaSessionCoordinator API test
+        https://bugs.webkit.org/show_bug.cgi?id=224140
+        <rdar://problem/76171173>
+
+        Reviewed by Jer Noble.
+
+        * Sources.txt: Include RemoteMediaSessionCoordinatorProxy.cpp and RemoteMediaSessionCoordinator.cpp
+        so they don't have to be included in the Xcode project.
+
+        * UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h: Declare enums, struct, and protocols
+        necessary to implement and use an object derived from MediaSessionCoordinatorProxyPrivate.
+        * UIProcess/API/Cocoa/WKWebViewTesting.mm:
+        (-[WKWebView _createMediaSessionCoordinatorForTesting:completionHandler:]): New private
+        method to create a MediaSessionCoordinatorProxyPrivate.
+        (-[WKMediaSessionCoordinatorHelper initWithCoordinator:]):
+        (-[WKMediaSessionCoordinatorHelper seekSessionToTime:withCompletion:]):
+        (-[WKMediaSessionCoordinatorHelper playSessionWithCompletion:]):
+        (-[WKMediaSessionCoordinatorHelper pauseSessionWithCompletion:]):
+        (-[WKMediaSessionCoordinatorHelper setSessionTrack:withCompletion:]):
+
+        * UIProcess/Cocoa/WebViewImpl.h:
+        (WebKit::WebViewImpl::mediaSessionCoordinatorForTesting): MediaSessionCoordinatorProxyPrivate
+        getter.
+        * UIProcess/Cocoa/WebViewImpl.mm:
+        (WebKit::WebViewImpl::setMediaSessionCoordinatorForTesting): Setter for 
+        MediaSessionCoordinatorProxyPrivate drived object.
+
+        * UIProcess/Media/MediaSessionCoordinatorProxyPrivate.h: Add typedef for the completion
+        handler used by many coordinator proxy methods.
+
+        * UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp:
+        (WebKit::RemoteMediaSessionCoordinatorProxy::RemoteMediaSessionCoordinatorProxy):
+        Set the private coordinator client so calls to the session can be forwarded.
+        (WebKit::RemoteMediaSessionCoordinatorProxy::join): Use MediaSessionCommandCompletionHandler.
+        (WebKit::RemoteMediaSessionCoordinatorProxy::coordinateSeekTo): Ditto.
+        (WebKit::RemoteMediaSessionCoordinatorProxy::coordinatePlay): Ditto.
+        (WebKit::RemoteMediaSessionCoordinatorProxy::coordinatePause): Ditto.
+        (WebKit::RemoteMediaSessionCoordinatorProxy::coordinateSetTrack): Ditto.
+        (WebKit::RemoteMediaSessionCoordinatorProxy::coordinatorStateChanged): Ditto.
+        * UIProcess/Media/RemoteMediaSessionCoordinatorProxy.h:
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::createMediaSessionCoordinator): Change completion handler
+        parameter to return a bool instead of a RemoteMediaSessionCoordinatorProxy as the
+        client interface provides all of the session access needed.
+        * UIProcess/WebPageProxy.h:
+
+        * WebKit.xcodeproj/project.pbxproj: Remove RemoteMediaSessionCoordinatorProxy.cpp 
+        and RemoteMediaSessionCoordinator.cpp
+        
+        * WebProcess/MediaSession/RemoteMediaSessionCoordinator.cpp:
+        (WebKit::RemoteMediaSessionCoordinator::join): Use ExceptionData's toException()
+        method instead of creating an Exception directly. Remove the destination ID parameter
+        to sendWithAsyncReply so messageSenderDestinationID() will be used.
+        (WebKit::RemoteMediaSessionCoordinator::leave): Ditto.
+        (WebKit::RemoteMediaSessionCoordinator::seekTo) Ditto.:
+        (WebKit::RemoteMediaSessionCoordinator::play): Ditto.
+        (WebKit::RemoteMediaSessionCoordinator::pause): Ditto.
+        (WebKit::RemoteMediaSessionCoordinator::setTrack): Ditto.
+        (WebKit::RemoteMediaSessionCoordinator::positionStateChanged): Ditto.
+        (WebKit::RemoteMediaSessionCoordinator::readyStateChanged): Ditto.
+        (WebKit::RemoteMediaSessionCoordinator::playbackStateChanged): Ditto.
+        (WebKit::RemoteMediaSessionCoordinator::coordinatorStateChanged): Ditto.
+
 2021-04-06  Aditya Keerthi  <akeer...@apple.com>
 
         [iOS] Prevent background blur when presenting date/time pickers

Modified: trunk/Source/WebKit/Sources.txt (275536 => 275537)


--- trunk/Source/WebKit/Sources.txt	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/Sources.txt	2021-04-06 19:06:53 UTC (rev 275537)
@@ -469,6 +469,7 @@
 
 UIProcess/Media/AudioSessionRoutingArbitratorProxy.cpp
 UIProcess/Media/MediaUsageManager.cpp
+UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp
 
 UIProcess/Notifications/NotificationPermissionRequestManagerProxy.cpp
 UIProcess/Notifications/WebNotification.cpp
@@ -581,6 +582,8 @@
 WebProcess/Inspector/WebPageInspectorTargetController.cpp
 WebProcess/Inspector/WebPageInspectorTargetFrontendChannel.cpp
 
+WebProcess/MediaSession/RemoteMediaSessionCoordinator.cpp
+
 WebProcess/MediaStream/MediaDeviceSandboxExtensions.cpp
 WebProcess/MediaStream/UserMediaPermissionRequestManager.cpp
 

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h	2021-04-06 19:06:53 UTC (rev 275537)
@@ -26,6 +26,8 @@
 #import "WKWebViewPrivateForTestingIOS.h"
 #import "WKWebViewPrivateForTestingMac.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 typedef enum {
     WKWebViewAudioRoutingArbitrationStatusNone,
     WKWebViewAudioRoutingArbitrationStatusPending,
@@ -37,6 +39,8 @@
     BOOL hasLoadedNonAppBoundRequestTesting;
 };
 
+@protocol _WKMediaSessionCoordinator;
+
 @interface WKWebView (WKTesting)
 
 - (void)_setPageScale:(CGFloat)scale withOrigin:(CGPoint)origin;
@@ -99,4 +103,56 @@
 - (void)_appBoundNavigationData:(void(^)(struct WKAppBoundNavigationTestingData data))completionHandler;
 - (void)_clearAppBoundNavigationData:(void(^)(void))completionHandler;
 
+- (void)_createMediaSessionCoordinatorForTesting:(id <_WKMediaSessionCoordinator>)privateCoordinator completionHandler:(void(^)(BOOL))completionHandler;
+
 @end
+
+typedef NS_ENUM(NSInteger, _WKMediaSessionReadyState) {
+    WKMediaSessionReadyStateHaveNothing,
+    WKMediaSessionReadyStateHaveMetadata,
+    WKMediaSessionReadyStateHaveCurrentData,
+    WKMediaSessionReadyStateHaveFutureData,
+    WKMediaSessionReadyStateHaveEnoughData
+};
+
+typedef NS_ENUM(NSInteger, _WKMediaSessionPlaybackState) {
+    WKMediaSessionPlaybackStateNone,
+    WKMediaSessionPlaybackStatePaused,
+    WKMediaSessionPlaybackStatePlaying
+};
+
+typedef NS_ENUM(NSInteger, _WKMediaSessionCoordinatorState) {
+    WKMediaSessionCoordinatorStateWaiting,
+    WKMediaSessionCoordinatorStateJoined,
+    WKMediaSessionCoordinatorStateClosed
+};
+
+struct _WKMediaPositionState {
+    double duration;
+    double playbackRate;
+    double position;
+};
+
+@protocol _WKMediaSessionCoordinatorDelegate <NSObject>
+- (void)seekSessionToTime:(double)time withCompletion:(void(^)(BOOL))completionHandler;
+- (void)playSessionWithCompletion:(void(^)(BOOL))completionHandler;
+- (void)pauseSessionWithCompletion:(void(^)(BOOL))completionHandler;
+- (void)setSessionTrack:(NSString*)trackIdentifier withCompletion:(void(^)(BOOL))completionHandler;
+@end
+
+@protocol _WKMediaSessionCoordinator <NSObject>
+@property (nullable, weak) id <_WKMediaSessionCoordinatorDelegate> delegate;
+@property (nonatomic, readonly) NSString * _Nonnull identifier;
+- (void)joinWithCompletion:(void(^ _Nonnull)(BOOL))completionHandler;
+- (void)leave;
+- (void)seekTo:(double)time withCompletion:(void(^ _Nonnull)(BOOL))completionHandler;
+- (void)playWithCompletion:(void(^ _Nonnull)(BOOL))completionHandler;
+- (void)pauseWithCompletion:(void(^ _Nonnull)(BOOL))completionHandler;
+- (void)setTrack:(NSString *_Nonnull)trackIdentifier withCompletion:(void(^ _Nonnull)(BOOL))completionHandler;
+- (void)positionStateChanged:(struct _WKMediaPositionState * _Nullable)state;
+- (void)readyStateChanged:(_WKMediaSessionReadyState)state;
+- (void)playbackStateChanged:(_WKMediaSessionPlaybackState)state;
+- (void)coordinatorStateChanged:(_WKMediaSessionCoordinatorState)state;
+@end
+
+NS_ASSUME_NONNULL_END

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewTesting.mm (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewTesting.mm	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewTesting.mm	2021-04-06 19:06:53 UTC (rev 275537)
@@ -27,6 +27,7 @@
 #import "WKWebViewPrivateForTesting.h"
 
 #import "AudioSessionRoutingArbitratorProxy.h"
+#import "MediaSessionCoordinatorProxyPrivate.h"
 #import "PlaybackSessionManagerProxy.h"
 #import "UserMediaProcessManager.h"
 #import "ViewGestureController.h"
@@ -39,7 +40,17 @@
 #import "_WKInspectorInternal.h"
 #import <WebCore/RuntimeApplicationChecks.h>
 #import <WebCore/ValidationBubble.h>
+#import <wtf/RetainPtr.h>
 
+#if ENABLE(MEDIA_SESSION_COORDINATOR)
+@interface WKMediaSessionCoordinatorHelper : NSObject <_WKMediaSessionCoordinatorDelegate>
+- (id)initWithCoordinator:(WebCore::MediaSessionCoordinatorClient*)coordinator;
+- (void)seekSessionToTime:(double)time withCompletion:(void(^)(BOOL))completionHandler;
+- (void)playSessionWithCompletion:(void(^)(BOOL))completionHandler;
+- (void)pauseSessionWithCompletion:(void(^)(BOOL))completionHandler;
+- (void)setSessionTrack:(NSString*)trackIdentifier withCompletion:(void(^)(BOOL))completionHandler;
+@end
+#endif
 
 @implementation WKWebView (WKTesting)
 
@@ -360,4 +371,240 @@
     });
 }
 
+- (void)_createMediaSessionCoordinatorForTesting:(id <_WKMediaSessionCoordinator>)privateCoordinator completionHandler:(void(^)(BOOL))completionHandler
+{
+#if ENABLE(MEDIA_SESSION_COORDINATOR)
+    class WKMediaSessionCoordinatorForTesting
+        : public WebKit::MediaSessionCoordinatorProxyPrivate
+        , public WebCore::MediaSessionCoordinatorClient {
+        WTF_MAKE_FAST_ALLOCATED;
+    public:
+
+        static Ref<WKMediaSessionCoordinatorForTesting> create(id <_WKMediaSessionCoordinator> privateCoordinator)
+        {
+            return adoptRef(*new WKMediaSessionCoordinatorForTesting(privateCoordinator));
+        }
+
+        using WebCore::MediaSessionCoordinatorClient::weakPtrFactory;
+        using WeakValueType = WebCore::MediaSessionCoordinatorClient::WeakValueType;
+
+    private:
+        explicit WKMediaSessionCoordinatorForTesting(id <_WKMediaSessionCoordinator> clientCoordinator)
+            : WebKit::MediaSessionCoordinatorProxyPrivate()
+            , m_clientCoordinator(clientCoordinator)
+        {
+            auto* delegateHelper = [[WKMediaSessionCoordinatorHelper alloc] initWithCoordinator:this];
+            [m_clientCoordinator setDelegate:delegateHelper];
+            m_coordinatorDelegate = adoptNS(delegateHelper);
+        }
+
+        void seekSessionToTime(double time, CompletionHandler<void(bool)>&& callback) final
+        {
+            if (auto coordinatorClient = client())
+                coordinatorClient->seekSessionToTime(time, WTFMove(callback));
+            else
+                callback(false);
+        }
+
+        void playSession(CompletionHandler<void(bool)>&& callback) final
+        {
+            if (auto coordinatorClient = client())
+                coordinatorClient->playSession(WTFMove(callback));
+            else
+                callback(false);
+        }
+
+        void pauseSession(CompletionHandler<void(bool)>&& callback) final
+        {
+            if (auto coordinatorClient = client())
+                coordinatorClient->pauseSession(WTFMove(callback));
+            else
+                callback(false);
+        }
+
+        void setSessionTrack(const String& trackIdentifier, CompletionHandler<void(bool)>&& callback) final
+        {
+            if (auto coordinatorClient = client())
+                coordinatorClient->setSessionTrack(trackIdentifier, WTFMove(callback));
+            else
+                callback(false);
+        }
+
+        Optional<WebCore::ExceptionData> result(bool success) const
+        {
+            if (!success)
+                return { WebCore::ExceptionData { WebCore::InvalidStateError } };
+
+            return { };
+        }
+
+        String identifier() const
+        {
+            return [m_clientCoordinator identifier];
+        }
+
+        void join(WebKit::MediaSessionCommandCompletionHandler&& callback) final
+        {
+            [m_clientCoordinator joinWithCompletion:makeBlockPtr([weakThis = makeWeakPtr(this), callback = WTFMove(callback)] (BOOL success) mutable {
+                if (!weakThis) {
+                    callback(WebCore::ExceptionData { WebCore::InvalidStateError });
+                    return;
+                }
+
+                callback(weakThis->result(success));
+            }).get()];
+        }
+
+        void leave() final
+        {
+            [m_clientCoordinator leave];
+        }
+
+        void seekTo(double time, WebKit::MediaSessionCommandCompletionHandler&& callback) final
+        {
+            [m_clientCoordinator seekTo:time withCompletion:makeBlockPtr([weakThis = makeWeakPtr(this), callback = WTFMove(callback)] (BOOL success) mutable {
+                if (!weakThis) {
+                    callback(WebCore::ExceptionData { WebCore::InvalidStateError });
+                    return;
+                }
+
+                callback(weakThis->result(success));
+            }).get()];
+        }
+
+        void play(WebKit::MediaSessionCommandCompletionHandler&& callback) final
+        {
+            [m_clientCoordinator playWithCompletion:makeBlockPtr([weakThis = makeWeakPtr(this), callback = WTFMove(callback)] (BOOL success) mutable {
+                if (!weakThis) {
+                    callback(WebCore::ExceptionData { WebCore::InvalidStateError });
+                    return;
+                }
+
+                callback(weakThis->result(success));
+            }).get()];
+        }
+
+        void pause(WebKit::MediaSessionCommandCompletionHandler&& callback) final
+        {
+            [m_clientCoordinator pauseWithCompletion:makeBlockPtr([weakThis = makeWeakPtr(this), callback = WTFMove(callback)] (BOOL success) mutable {
+                if (!weakThis) {
+                    callback(WebCore::ExceptionData { WebCore::InvalidStateError });
+                    return;
+                }
+
+                callback(weakThis->result(success));
+            }).get()];
+        }
+
+        void setTrack(const String& track, WebKit::MediaSessionCommandCompletionHandler&& callback) final
+        {
+            [m_clientCoordinator setTrack:track withCompletion:makeBlockPtr([weakThis = makeWeakPtr(this), callback = WTFMove(callback)] (BOOL success) mutable {
+                if (!weakThis) {
+                    callback(WebCore::ExceptionData { WebCore::InvalidStateError });
+                    return;
+                }
+
+                callback(weakThis->result(success));
+            }).get()];
+        }
+
+        void positionStateChanged(const Optional<WebCore::MediaPositionState>& state) final
+        {
+            if (!state) {
+                [m_clientCoordinator positionStateChanged:nil];
+                return;
+            }
+
+            _WKMediaPositionState position = {
+                .duration = state->duration,
+                .playbackRate = state->playbackRate,
+                .position = state->position
+            };
+            [m_clientCoordinator positionStateChanged:&position];
+        }
+
+        void readyStateChanged(WebCore::MediaSessionReadyState state) final
+        {
+            static_assert(static_cast<size_t>(WebCore::MediaSessionReadyState::HaveNothing) == static_cast<size_t>(WKMediaSessionReadyStateHaveNothing), "WKMediaSessionReadyStateHaveNothing does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionReadyState::HaveMetadata) == static_cast<size_t>(WKMediaSessionReadyStateHaveMetadata), "WKMediaSessionReadyStateHaveMetadata does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionReadyState::HaveCurrentData) == static_cast<size_t>(WKMediaSessionReadyStateHaveCurrentData), "WKMediaSessionReadyStateHaveCurrentData does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionReadyState::HaveFutureData) == static_cast<size_t>(WKMediaSessionReadyStateHaveFutureData), "WKMediaSessionReadyStateHaveFutureData does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionReadyState::HaveEnoughData) == static_cast<size_t>(WKMediaSessionReadyStateHaveEnoughData), "WKMediaSessionReadyStateHaveEnoughData does not match WebKit value");
+
+            [m_clientCoordinator readyStateChanged:static_cast<_WKMediaSessionReadyState>(state)];
+        }
+
+        void playbackStateChanged(WebCore::MediaSessionPlaybackState state) final
+        {
+            static_assert(static_cast<size_t>(WebCore::MediaSessionPlaybackState::None) == static_cast<size_t>(WKMediaSessionPlaybackStateNone), "WKMediaSessionPlaybackStateNone does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionPlaybackState::Paused) == static_cast<size_t>(WKMediaSessionPlaybackStatePaused), "WKMediaSessionPlaybackStatePaused does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionPlaybackState::Playing) == static_cast<size_t>(WKMediaSessionPlaybackStatePlaying), "WKMediaSessionPlaybackStatePlaying does not match WebKit value");
+
+            [m_clientCoordinator playbackStateChanged:static_cast<_WKMediaSessionPlaybackState>(state)];
+        }
+
+        void coordinatorStateChanged(WebCore::MediaSessionCoordinatorState state) final
+        {
+            static_assert(static_cast<size_t>(WebCore::MediaSessionCoordinatorState::Waiting) == static_cast<size_t>(WKMediaSessionCoordinatorStateWaiting), "WKMediaSessionCoordinatorStateWaiting does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionCoordinatorState::Joined) == static_cast<size_t>(WKMediaSessionCoordinatorStateJoined), "WKMediaSessionCoordinatorStateJoined does not match WebKit value");
+            static_assert(static_cast<size_t>(WebCore::MediaSessionCoordinatorState::Closed) == static_cast<size_t>(WKMediaSessionCoordinatorStateClosed), "WKMediaSessionCoordinatorStateClosed does not match WebKit value");
+
+            [m_clientCoordinator coordinatorStateChanged:static_cast<_WKMediaSessionCoordinatorState>(state)];
+        }
+
+    private:
+        RetainPtr<id <_WKMediaSessionCoordinator>> m_clientCoordinator;
+        RetainPtr<WKMediaSessionCoordinatorHelper> m_coordinatorDelegate;
+    };
+
+    ASSERT(!_impl->mediaSessionCoordinatorForTesting());
+
+    _impl->setMediaSessionCoordinatorForTesting(WKMediaSessionCoordinatorForTesting::create(privateCoordinator).ptr());
+    _impl->page().createMediaSessionCoordinator(*_impl->mediaSessionCoordinatorForTesting(), [completionHandler = makeBlockPtr(completionHandler)] (bool success) {
+        completionHandler(success);
+    });
+#else
+
+    completionHandler(NO);
+
+#endif
+}
+
 @end
+
+#if ENABLE(MEDIA_SESSION_COORDINATOR)
+@implementation WKMediaSessionCoordinatorHelper {
+    WeakPtr<WebCore::MediaSessionCoordinatorClient> m_coordinatorClient;
+}
+
+- (id)initWithCoordinator:(WebCore::MediaSessionCoordinatorClient*)coordinator
+{
+    self = [super init];
+    if (!self)
+        return nil;
+    m_coordinatorClient = makeWeakPtr(coordinator);
+    return self;
+}
+
+- (void)seekSessionToTime:(double)time withCompletion:(void(^)(BOOL))completionHandler
+{
+    m_coordinatorClient->seekSessionToTime(time, makeBlockPtr(completionHandler));
+}
+
+- (void)playSessionWithCompletion:(void(^)(BOOL))completionHandler
+{
+    m_coordinatorClient->playSession(makeBlockPtr(completionHandler));
+}
+
+- (void)pauseSessionWithCompletion:(void(^)(BOOL))completionHandler
+{
+    m_coordinatorClient->pauseSession(makeBlockPtr(completionHandler));
+}
+
+- (void)setSessionTrack:(NSString*)trackIdentifier withCompletion:(void(^)(BOOL))completionHandler
+{
+    m_coordinatorClient->setSessionTrack(trackIdentifier, makeBlockPtr(completionHandler));
+}
+
+@end
+#endif

Modified: trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.h (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.h	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.h	2021-04-06 19:06:53 UTC (rev 275537)
@@ -152,6 +152,7 @@
 class PageClient;
 class PageClientImpl;
 class DrawingAreaProxy;
+class MediaSessionCoordinatorProxyPrivate;
 class SafeBrowsingWarning;
 class ViewGestureController;
 class ViewSnapshot;
@@ -645,6 +646,11 @@
     void handleContextMenuTranslation(const String& text, const WebCore::IntRect& boundsInView, const WebCore::IntPoint& menuLocation);
 #endif
 
+#if ENABLE(MEDIA_SESSION_COORDINATOR)
+    MediaSessionCoordinatorProxyPrivate* mediaSessionCoordinatorForTesting() { return m_coordinatorForTesting.get(); }
+    void setMediaSessionCoordinatorForTesting(MediaSessionCoordinatorProxyPrivate*);
+#endif
+
 private:
 #if HAVE(TOUCH_BAR)
     void setUpTextTouchBar(NSTouchBar *);
@@ -851,6 +857,10 @@
     RetainPtr<WKDOMPasteMenuDelegate> m_domPasteMenuDelegate;
     CompletionHandler<void(WebCore::DOMPasteAccessResponse)> m_domPasteRequestHandler;
 
+#if ENABLE(MEDIA_SESSION_COORDINATOR)
+    RefPtr<MediaSessionCoordinatorProxyPrivate> m_coordinatorForTesting;
+#endif
+
 #if USE(APPLE_INTERNAL_SDK)
 #import <WebKitAdditions/WebViewImplAdditionsAfter.h>
 #endif

Modified: trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm	2021-04-06 19:06:53 UTC (rev 275537)
@@ -139,6 +139,10 @@
 #import <WebCore/ImageExtractionResult.h>
 #endif
 
+#if ENABLE(MEDIA_SESSION_COORDINATOR)
+#include <WebKit/MediaSessionCoordinatorProxyPrivate.h>
+#endif
+
 #if HAVE(TRANSLATION_UI_SERVICES)
 #import <TranslationUIServices/LTUITranslationViewController.h>
 
@@ -5582,6 +5586,13 @@
     return false;
 }
 
+#if ENABLE(MEDIA_SESSION_COORDINATOR)
+void WebViewImpl::setMediaSessionCoordinatorForTesting(MediaSessionCoordinatorProxyPrivate* coordinator)
+{
+    m_coordinatorForTesting = coordinator;
+}
+#endif
+
 #if HAVE(TRANSLATION_UI_SERVICES) && ENABLE(CONTEXT_MENUS)
 
 bool WebViewImpl::canHandleContextMenuTranslation() const

Modified: trunk/Source/WebKit/UIProcess/Media/MediaSessionCoordinatorProxyPrivate.h (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/Media/MediaSessionCoordinatorProxyPrivate.h	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/Media/MediaSessionCoordinatorProxyPrivate.h	2021-04-06 19:06:53 UTC (rev 275537)
@@ -38,6 +38,8 @@
 
 namespace WebKit {
 
+using MediaSessionCommandCompletionHandler = CompletionHandler<void(Optional<WebCore::ExceptionData>&&)>;
+
 class MediaSessionCoordinatorProxyPrivate
     : public CanMakeWeakPtr<MediaSessionCoordinatorProxyPrivate>
     , public RefCounted<MediaSessionCoordinatorProxyPrivate> {
@@ -46,12 +48,12 @@
 
     virtual String identifier() const = 0;
 
-    virtual void join(CompletionHandler<void(Optional<WebCore::ExceptionData>&&)>&&) = 0;
+    virtual void join(MediaSessionCommandCompletionHandler&&) = 0;
     virtual void leave() = 0;
-    virtual void seekTo(double, CompletionHandler<void(Optional<WebCore::ExceptionData>&&)>&&) = 0;
-    virtual void play(CompletionHandler<void(Optional<WebCore::ExceptionData>&&)>&&) = 0;
-    virtual void pause(CompletionHandler<void(Optional<WebCore::ExceptionData>&&)>&&) = 0;
-    virtual void setTrack(const String&, CompletionHandler<void(Optional<WebCore::ExceptionData>&&)>&&) = 0;
+    virtual void seekTo(double, MediaSessionCommandCompletionHandler&&) = 0;
+    virtual void play(MediaSessionCommandCompletionHandler&&) = 0;
+    virtual void pause(MediaSessionCommandCompletionHandler&&) = 0;
+    virtual void setTrack(const String&, MediaSessionCommandCompletionHandler&&) = 0;
 
     virtual void positionStateChanged(const Optional<WebCore::MediaPositionState>&) = 0;
     virtual void readyStateChanged(WebCore::MediaSessionReadyState) = 0;

Modified: trunk/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.cpp	2021-04-06 19:06:53 UTC (rev 275537)
@@ -59,6 +59,7 @@
     , m_logIdentifier(LoggerHelper::uniqueLogIdentifier())
 #endif
 {
+    m_privateCoordinator->setClient(makeWeakPtr(this));
     m_webPageProxy.process().addMessageReceiver(Messages::RemoteMediaSessionCoordinatorProxy::messageReceiverName(), m_webPageProxy.webPageID(), *this);
 }
 
@@ -67,7 +68,7 @@
     m_webPageProxy.process().removeMessageReceiver(Messages::RemoteMediaSessionCoordinatorProxy::messageReceiverName(), m_webPageProxy.webPageID());
 }
 
-void RemoteMediaSessionCoordinatorProxy::join(CommandCompletionHandler&& completionHandler)
+void RemoteMediaSessionCoordinatorProxy::join(MediaSessionCommandCompletionHandler&& completionHandler)
 {
     auto identifier = LOGIDENTIFIER;
     ALWAYS_LOG(identifier);
@@ -85,7 +86,7 @@
     m_privateCoordinator->leave();
 }
 
-void RemoteMediaSessionCoordinatorProxy::coordinateSeekTo(double time, CommandCompletionHandler&& completionHandler)
+void RemoteMediaSessionCoordinatorProxy::coordinateSeekTo(double time, MediaSessionCommandCompletionHandler&& completionHandler)
 {
     auto identifier = LOGIDENTIFIER;
     ALWAYS_LOG(identifier, time);
@@ -96,7 +97,7 @@
     });
 }
 
-void RemoteMediaSessionCoordinatorProxy::coordinatePlay(CommandCompletionHandler&& completionHandler)
+void RemoteMediaSessionCoordinatorProxy::coordinatePlay(MediaSessionCommandCompletionHandler&& completionHandler)
 {
     auto identifier = LOGIDENTIFIER;
     ALWAYS_LOG(identifier);
@@ -107,7 +108,7 @@
     });
 }
 
-void RemoteMediaSessionCoordinatorProxy::coordinatePause(CommandCompletionHandler&& completionHandler)
+void RemoteMediaSessionCoordinatorProxy::coordinatePause(MediaSessionCommandCompletionHandler&& completionHandler)
 {
     auto identifier = LOGIDENTIFIER;
     ALWAYS_LOG(identifier);
@@ -118,7 +119,7 @@
     });
 }
 
-void RemoteMediaSessionCoordinatorProxy::coordinateSetTrack(const String& track, CommandCompletionHandler&& completionHandler)
+void RemoteMediaSessionCoordinatorProxy::coordinateSetTrack(const String& track, MediaSessionCommandCompletionHandler&& completionHandler)
 {
     auto identifier = LOGIDENTIFIER;
     ALWAYS_LOG(identifier);
@@ -147,6 +148,12 @@
     m_privateCoordinator->readyStateChanged(state);
 }
 
+void RemoteMediaSessionCoordinatorProxy::coordinatorStateChanged(WebCore::MediaSessionCoordinatorState state)
+{
+    ALWAYS_LOG(LOGIDENTIFIER);
+    m_privateCoordinator->coordinatorStateChanged(state);
+}
+
 void RemoteMediaSessionCoordinatorProxy::seekSessionToTime(double time, CompletionHandler<void(bool)>&& callback)
 {
     m_webPageProxy.sendWithAsyncReply(Messages::RemoteMediaSessionCoordinator::SeekSessionToTime { time }, callback);
@@ -167,12 +174,6 @@
     m_webPageProxy.sendWithAsyncReply(Messages::RemoteMediaSessionCoordinator::SetSessionTrack { trackId }, callback);
 }
 
-void RemoteMediaSessionCoordinatorProxy::coordinatorStateChanged(WebCore::MediaSessionCoordinatorState state)
-{
-    ALWAYS_LOG(LOGIDENTIFIER);
-    m_privateCoordinator->coordinatorStateChanged(state);
-}
-
 #if !RELEASE_LOG_DISABLED
 WTFLogChannel& RemoteMediaSessionCoordinatorProxy::logChannel() const
 {

Modified: trunk/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.h (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.h	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/Media/RemoteMediaSessionCoordinatorProxy.h	2021-04-06 19:06:53 UTC (rev 275537)
@@ -41,7 +41,7 @@
 class WebPageProxy;
 
 class RemoteMediaSessionCoordinatorProxy
-    : public IPC::MessageReceiver
+    : private IPC::MessageReceiver
     , public RefCounted<RemoteMediaSessionCoordinatorProxy>
     , public WebCore::MediaSessionCoordinatorClient {
     WTF_MAKE_FAST_ALLOCATED;
@@ -60,19 +60,16 @@
 private:
     explicit RemoteMediaSessionCoordinatorProxy(WebPageProxy&, Ref<MediaSessionCoordinatorProxyPrivate>&&);
 
-    using CoordinateCompletionHandler = CompletionHandler<void(const WebCore::ExceptionData&)>;
-
     // IPC::MessageReceiver.
     void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override;
 
     // Receivers.
-    using CommandCompletionHandler = CompletionHandler<void(Optional<WebCore::ExceptionData>&&)>;
-    void join(CommandCompletionHandler&&);
+    void join(MediaSessionCommandCompletionHandler&&);
     void leave();
-    void coordinateSeekTo(double, CommandCompletionHandler&&);
-    void coordinatePlay(CommandCompletionHandler&&);
-    void coordinatePause(CommandCompletionHandler&&);
-    void coordinateSetTrack(const String&, CommandCompletionHandler&&);
+    void coordinateSeekTo(double, MediaSessionCommandCompletionHandler&&);
+    void coordinatePlay(MediaSessionCommandCompletionHandler&&);
+    void coordinatePause(MediaSessionCommandCompletionHandler&&);
+    void coordinateSetTrack(const String&, MediaSessionCommandCompletionHandler&&);
     void positionStateChanged(const Optional<WebCore::MediaPositionState>&);
     void readyStateChanged(WebCore::MediaSessionReadyState);
     void playbackStateChanged(WebCore::MediaSessionPlaybackState);

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2021-04-06 19:06:53 UTC (rev 275537)
@@ -10403,7 +10403,7 @@
 #endif
 
 #if ENABLE(MEDIA_SESSION_COORDINATOR)
-void WebPageProxy::createMediaSessionCoordinator(Ref<MediaSessionCoordinatorProxyPrivate>&& privateCoordinator, CompletionHandler<void(WeakPtr<RemoteMediaSessionCoordinatorProxy>)>&& completionHandler)
+void WebPageProxy::createMediaSessionCoordinator(Ref<MediaSessionCoordinatorProxyPrivate>&& privateCoordinator, CompletionHandler<void(bool)>&& completionHandler)
 {
     ASSERT(!m_mediaSessionCoordinatorProxy);
 
@@ -10410,12 +10410,12 @@
     sendWithAsyncReply(Messages::WebPage::CreateMediaSessionCoordinator(privateCoordinator->identifier()), [weakThis = makeWeakPtr(*this), privateCoordinator = WTFMove(privateCoordinator), completionHandler = WTFMove(completionHandler)](bool success) mutable {
 
         if (!weakThis || !success) {
-            completionHandler({ });
+            completionHandler(false);
             return;
         }
 
         weakThis->m_mediaSessionCoordinatorProxy = RemoteMediaSessionCoordinatorProxy::create(*weakThis, WTFMove(privateCoordinator));
-        completionHandler(makeWeakPtr(weakThis->m_mediaSessionCoordinatorProxy.get()));
+        completionHandler(true);
     });
 }
 #endif

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (275536 => 275537)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.h	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h	2021-04-06 19:06:53 UTC (rev 275537)
@@ -1892,7 +1892,7 @@
 #endif
 
 #if ENABLE(MEDIA_SESSION_COORDINATOR)
-    void createMediaSessionCoordinator(Ref<MediaSessionCoordinatorProxyPrivate>&&, CompletionHandler<void(WeakPtr<RemoteMediaSessionCoordinatorProxy>)>&&);
+    void createMediaSessionCoordinator(Ref<MediaSessionCoordinatorProxyPrivate>&&, CompletionHandler<void(bool)>&&);
 #endif
 
 #if PLATFORM(COCOA)

Modified: trunk/Source/WebKit/WebKit.xcodeproj/project.pbxproj (275536 => 275537)


--- trunk/Source/WebKit/WebKit.xcodeproj/project.pbxproj	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/WebKit.xcodeproj/project.pbxproj	2021-04-06 19:06:53 UTC (rev 275537)
@@ -105,9 +105,9 @@
 		07297FA31C186ADB003F0735 /* WKUserMediaPermissionCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 07297FA11C186ADB003F0735 /* WKUserMediaPermissionCheck.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		074879B92373A90900F5678E /* AppKitSoftLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 074879B72373A90900F5678E /* AppKitSoftLink.h */; };
 		074E75FE1DF2211900D318EC /* UserMediaProcessManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 074E75FB1DF1FD1300D318EC /* UserMediaProcessManager.h */; };
+		075A9CF326169BAB006DFA3A /* MediaSessionCoordinatorProxyPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 077BA570260E8F630072F19F /* MediaSessionCoordinatorProxyPrivate.h */; };
 		076E884E1A13CADF005E90FC /* APIContextMenuClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 076E884D1A13CADF005E90FC /* APIContextMenuClient.h */; };
 		0772811D21234FF600C8EF2E /* UserMediaPermissionRequestManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A410F4319AF7B27002EBAB5 /* UserMediaPermissionRequestManager.h */; };
-		077BA56E260E6D1C0072F19F /* RemoteMediaSessionCoordinator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0736B755260D03A000E06994 /* RemoteMediaSessionCoordinator.cpp */; };
 		07923148239CBCB8009598E2 /* RemoteMediaPlayerManagerProxyMessagesReplies.h in Headers */ = {isa = PBXBuildFile; fileRef = 07923142239CBCB5009598E2 /* RemoteMediaPlayerManagerProxyMessagesReplies.h */; };
 		0792314B239CBCB8009598E2 /* RemoteMediaPlayerManagerProxyMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07923145239CBCB7009598E2 /* RemoteMediaPlayerManagerProxyMessageReceiver.cpp */; };
 		0792314C239CBCB8009598E2 /* RemoteMediaPlayerManagerProxyMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 07923146239CBCB7009598E2 /* RemoteMediaPlayerManagerProxyMessages.h */; };
@@ -1864,7 +1864,6 @@
 		CD0C6831201FD10100A59409 /* WKFullScreenViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = CD0C682F201FD10100A59409 /* WKFullScreenViewController.h */; };
 		CD19A26E1A13E834008D650E /* WebDiagnosticLoggingClient.h in Headers */ = {isa = PBXBuildFile; fileRef = CD19A26A1A13E821008D650E /* WebDiagnosticLoggingClient.h */; };
 		CD19D2EA2046406F0017074A /* FullscreenTouchSecheuristic.h in Headers */ = {isa = PBXBuildFile; fileRef = CD19D2E82046406F0017074A /* FullscreenTouchSecheuristic.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		CD1FE2D72613E18600ADC29B /* RemoteMediaSessionCoordinatorProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0736B75C260D4A4E00E06994 /* RemoteMediaSessionCoordinatorProxy.cpp */; };
 		CD3EEF462579C261006563BB /* RemoteMediaEngineConfigurationFactoryProxyMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3EEF442579C1D4006563BB /* RemoteMediaEngineConfigurationFactoryProxyMessageReceiver.cpp */; };
 		CD4570D3244113B500A3DCEB /* AudioSessionRoutingArbitratorProxyMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD4570D02441014A00A3DCEB /* AudioSessionRoutingArbitratorProxyMessageReceiver.cpp */; };
 		CD4570D424411D0F00A3DCEB /* AudioSessionRoutingArbitrator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD4570CB2440FB2A00A3DCEB /* AudioSessionRoutingArbitrator.cpp */; };
@@ -9268,10 +9267,10 @@
 				5321594F1DBAE6D70054AA3C /* NetworkDataTaskCocoa.h */,
 				5CBC9B8B1C65257300A8FDCF /* NetworkDataTaskCocoa.mm */,
 				7EC4F0F918E4A945008056AF /* NetworkProcessCocoa.mm */,
+				518324782615B00D003F239E /* NetworkSchemeURLProtocol.h */,
+				518324792615B00D003F239E /* NetworkSchemeURLProtocol.mm */,
 				532159501DBAE6D70054AA3C /* NetworkSessionCocoa.h */,
 				5C20CB9B1BB0DCD200895BB1 /* NetworkSessionCocoa.mm */,
-				518324782615B00D003F239E /* NetworkSchemeURLProtocol.h */,
-				518324792615B00D003F239E /* NetworkSchemeURLProtocol.mm */,
 				41287D4C225C05C5009A3E26 /* WebSocketTaskCocoa.h */,
 				41287D4B225C05C4009A3E26 /* WebSocketTaskCocoa.mm */,
 			);
@@ -11676,7 +11675,6 @@
 				317FE7C51C487A6C00A0CA89 /* APIExperimentalFeature.h in Headers */,
 				00B9661618E24CBA00CE1F88 /* APIFindClient.h in Headers */,
 				2DD67A2E1BD819730053B251 /* APIFindMatchesClient.h in Headers */,
-				5183247D26168FF7003F239E /* NetworkURLSchemeTask.h in Headers */,
 				37E25D6E18FDE5D6005D3A00 /* APIFormClient.h in Headers */,
 				1AC1337218566C7C00F3EC05 /* APIFrameHandle.h in Headers */,
 				2DF9EEE61A781FB400B6CFBE /* APIFrameInfo.h in Headers */,
@@ -11929,6 +11927,7 @@
 				07E19EFD23D401F10094FFB4 /* MediaPlayerPrivateRemoteMessagesReplies.h in Headers */,
 				A1B4DCE125A7923C007D178C /* MediaSampleByteRange.h in Headers */,
 				A15799BD2584433200528236 /* MediaSampleCursor.h in Headers */,
+				075A9CF326169BAB006DFA3A /* MediaSessionCoordinatorProxyPrivate.h in Headers */,
 				A15799B72584433200528236 /* MediaTrackReader.h in Headers */,
 				51933DEF1965EB31008AC3EA /* MenuUtilities.h in Headers */,
 				9B47908D25314D8300EC11AB /* MessageArgumentDescriptions.h in Headers */,
@@ -11997,6 +11996,8 @@
 				5C20CBA01BB1ECD800895BB1 /* NetworkSession.h in Headers */,
 				532159551DBAE7290054AA3C /* NetworkSessionCocoa.h in Headers */,
 				417915B92257046F00D6F97E /* NetworkSocketChannel.h in Headers */,
+				5183247C26168C62003F239E /* NetworkURLSchemeHandler.h in Headers */,
+				5183247D26168FF7003F239E /* NetworkURLSchemeTask.h in Headers */,
 				570DAAC22303730300E8FC04 /* NfcConnection.h in Headers */,
 				570DAAAE23026F5C00E8FC04 /* NfcService.h in Headers */,
 				31A2EC5614899C0900810D71 /* NotificationPermissionRequest.h in Headers */,
@@ -12827,7 +12828,6 @@
 				0FCB4E6818BBE3D9000FCFC9 /* WKTextInputWindowController.h in Headers */,
 				CE21215F240EE571006ED443 /* WKTextPlaceholder.h in Headers */,
 				CE45945C240F88550078019F /* WKTextSelectionRect.h in Headers */,
-				5183247C26168C62003F239E /* NetworkURLSchemeHandler.h in Headers */,
 				2EB6FC01203021960017E619 /* WKTimePickerViewController.h in Headers */,
 				71A676A622C62325007D6295 /* WKTouchActionGestureRecognizer.h in Headers */,
 				BC407608124FF0270068F20A /* WKType.h in Headers */,
@@ -14005,8 +14005,6 @@
 				0701789E23BE9CFC005F0FAA /* RemoteMediaPlayerMIMETypeCache.cpp in Sources */,
 				071BC58F23CE1EAA00680D7C /* RemoteMediaPlayerProxyMessageReceiver.cpp in Sources */,
 				1D4D737023A9E54700717A25 /* RemoteMediaResourceManagerMessageReceiver.cpp in Sources */,
-				077BA56E260E6D1C0072F19F /* RemoteMediaSessionCoordinator.cpp in Sources */,
-				CD1FE2D72613E18600ADC29B /* RemoteMediaSessionCoordinatorProxy.cpp in Sources */,
 				1DD2A6632561246F00FF7B6F /* RemoteMediaSourceProxyMessageReceiver.cpp in Sources */,
 				1AC1338518590C4600F3EC05 /* RemoteObjectRegistryMessageReceiver.cpp in Sources */,
 				CD8252E225D4919100862FD8 /* RemoteRemoteCommandListenerMessageReceiver.cpp in Sources */,

Modified: trunk/Source/WebKit/WebProcess/MediaSession/RemoteMediaSessionCoordinator.cpp (275536 => 275537)


--- trunk/Source/WebKit/WebProcess/MediaSession/RemoteMediaSessionCoordinator.cpp	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Source/WebKit/WebProcess/MediaSession/RemoteMediaSessionCoordinator.cpp	2021-04-06 19:06:53 UTC (rev 275537)
@@ -72,17 +72,17 @@
         }
 
         if (exception) {
-            callback(Exception { exception->code, WTFMove(exception->message) });
+            callback(exception->toException());
             return;
         }
 
         callback({ });
-    }, 0);
+    });
 }
 
 void RemoteMediaSessionCoordinator::leave()
 {
-    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::Leave { }, 0);
+    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::Leave { });
 }
 
 void RemoteMediaSessionCoordinator::seekTo(double time, CompletionHandler<void(Optional<WebCore::Exception>&&)>&& callback)
@@ -95,12 +95,12 @@
         }
 
         if (exception) {
-            callback(Exception { exception->code, WTFMove(exception->message) });
+            callback(exception->toException());
             return;
         }
 
         callback({ });
-    }, 0);
+    });
 }
 
 void RemoteMediaSessionCoordinator::play(CompletionHandler<void(Optional<WebCore::Exception>&&)>&& callback)
@@ -113,12 +113,12 @@
         }
 
         if (exception) {
-            callback(Exception { exception->code, WTFMove(exception->message) });
+            callback(exception->toException());
             return;
         }
 
         callback({ });
-    }, 0);
+    });
 }
 
 void RemoteMediaSessionCoordinator::pause(CompletionHandler<void(Optional<WebCore::Exception>&&)>&& callback)
@@ -131,12 +131,12 @@
         }
 
         if (exception) {
-            callback(Exception { exception->code, WTFMove(exception->message) });
+            callback(exception->toException());
             return;
         }
 
         callback({ });
-    }, 0);
+    });
 }
 
 void RemoteMediaSessionCoordinator::setTrack(const String& trackIdentifier, CompletionHandler<void(Optional<WebCore::Exception>&&)>&& callback)
@@ -149,36 +149,36 @@
         }
 
         if (exception) {
-            callback(Exception { exception->code, WTFMove(exception->message) });
+            callback(exception->toException());
             return;
         }
 
         callback({ });
-    }, 0);
+    });
 }
 
 void RemoteMediaSessionCoordinator::positionStateChanged(const Optional<WebCore::MediaPositionState>& state)
 {
     ALWAYS_LOG_IF_POSSIBLE(LOGIDENTIFIER);
-    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::PositionStateChanged { state }, 0);
+    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::PositionStateChanged { state });
 }
 
 void RemoteMediaSessionCoordinator::readyStateChanged(WebCore::MediaSessionReadyState state)
 {
     ALWAYS_LOG_IF_POSSIBLE(LOGIDENTIFIER, state);
-    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::ReadyStateChanged { state }, 0);
+    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::ReadyStateChanged { state });
 }
 
 void RemoteMediaSessionCoordinator::playbackStateChanged(WebCore::MediaSessionPlaybackState state)
 {
     ALWAYS_LOG_IF_POSSIBLE(LOGIDENTIFIER, state);
-    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::PlaybackStateChanged { state }, 0);
+    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::PlaybackStateChanged { state });
 }
 
 void RemoteMediaSessionCoordinator::coordinatorStateChanged(WebCore::MediaSessionCoordinatorState state)
 {
     ALWAYS_LOG_IF_POSSIBLE(LOGIDENTIFIER, state);
-    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::CoordinatorStateChanged { state }, 0);
+    m_page.send(Messages::RemoteMediaSessionCoordinatorProxy::CoordinatorStateChanged { state });
 }
 
 void RemoteMediaSessionCoordinator::seekSessionToTime(double time, CompletionHandler<void(bool)>&& completionHandler)

Modified: trunk/Tools/ChangeLog (275536 => 275537)


--- trunk/Tools/ChangeLog	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Tools/ChangeLog	2021-04-06 19:06:53 UTC (rev 275537)
@@ -1,3 +1,56 @@
+2021-04-06  Eric Carlson  <eric.carl...@apple.com>
+
+        [macOS] Add MediaSessionCoordinator API test
+        https://bugs.webkit.org/show_bug.cgi?id=224140
+        <rdar://problem/76171173>
+
+        Reviewed by Jer Noble.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/xcshareddata/xcschemes/TestWebKitAPI.xcscheme:
+        * TestWebKitAPI/Tests/WebKit/MediaSessionCoordinatorTest.mm: Added.
+        (-[_WKMockMediaSessionCoordinator lastStateChange]):
+        (-[_WKMockMediaSessionCoordinator lastMethodCalled]):
+        (-[_WKMockMediaSessionCoordinator identifier]):
+        (-[_WKMockMediaSessionCoordinator joinWithCompletion:]):
+        (-[_WKMockMediaSessionCoordinator leave]):
+        (-[_WKMockMediaSessionCoordinator seekTo:withCompletion:]):
+        (-[_WKMockMediaSessionCoordinator playWithCompletion:]):
+        (-[_WKMockMediaSessionCoordinator pauseWithCompletion:]):
+        (-[_WKMockMediaSessionCoordinator setTrack:withCompletion:]):
+        (-[_WKMockMediaSessionCoordinator positionStateChanged:]):
+        (-[_WKMockMediaSessionCoordinator readyStateChanged:]):
+        (-[_WKMockMediaSessionCoordinator playbackStateChanged:]):
+        (-[_WKMockMediaSessionCoordinator coordinatorStateChanged:]):
+        (-[_WKMockMediaSessionCoordinator seekSessionToTime:]):
+        (-[_WKMockMediaSessionCoordinator playSession]):
+        (-[_WKMockMediaSessionCoordinator pauseSession]):
+        (-[_WKMockMediaSessionCoordinator setSessionTrack:]):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::createCoordinator):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::webView const):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::coordinator const):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::loadPageAndBecomeReady):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::runScriptWithUserGesture):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::play):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::pause):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::listenForEventMessages):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::eventListenerWasCalled):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::clearEventListenerState):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::executeUntil):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::waitForEventListenerToBeCalled):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::listenForMessagesPosted):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::clearMessagesPosted):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::listenForSessionHandlerMessages):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::sessionHandlerWasCalled):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::waitForSessionHandlerToBeCalled):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::listenForPromiseMessages):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::clearPromiseMessages):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::promiseWasResolved):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::promiseWasRejected):
+        (TestWebKitAPI::MediaSessionCoordinatorTest::waitForPromise):
+        (TestWebKitAPI::TEST_F):
+        * TestWebKitAPI/Tests/WebKitCocoa/media-remote.html:
+
 2021-04-06  David Kilzer  <ddkil...@apple.com>
 
         set-webkit-configuration should print current settings

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (275536 => 275537)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2021-04-06 19:06:53 UTC (rev 275537)
@@ -54,6 +54,7 @@
 		074994421EA5034B000DA44F /* ondevicechange.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 4A410F4D19AF7BEF002EBAB6 /* ondevicechange.html */; };
 		074994421EA5034B000DA45E /* getUserMediaAudioVideoCapture.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 4A410F4D19AF7BEF002EBAC5 /* getUserMediaAudioVideoCapture.html */; };
 		074994521EA5034B000DA44E /* getUserMedia2.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 41BAF4E225AC9DB800D82F32 /* getUserMedia2.html */; };
+		075A9CF526177218006DFA3A /* MediaSessionCoordinatorTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 075A9CF426177217006DFA3A /* MediaSessionCoordinatorTest.mm */; };
 		076E507F1F4513D6006E9F5A /* Logging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 076E507E1F45031E006E9F5A /* Logging.cpp */; };
 		077A5AF3230638A600A7105C /* AccessibilityTestPlugin.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0746645822FF630500E3451A /* AccessibilityTestPlugin.mm */; };
 		0794740D25CA0BDE00C597EB /* MediaSession.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0794740C25CA0BDE00C597EB /* MediaSession.mm */; };
@@ -1760,6 +1761,7 @@
 		0746645822FF630500E3451A /* AccessibilityTestPlugin.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AccessibilityTestPlugin.mm; sourceTree = "<group>"; };
 		07492B391DF8ADA400633DE1 /* enumerateMediaDevices.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = enumerateMediaDevices.html; sourceTree = "<group>"; };
 		07492B3A1DF8AE2D00633DE1 /* EnumerateMediaDevices.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EnumerateMediaDevices.cpp; sourceTree = "<group>"; };
+		075A9CF426177217006DFA3A /* MediaSessionCoordinatorTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MediaSessionCoordinatorTest.mm; sourceTree = "<group>"; };
 		0766DD1F1A5AD5200023E3BB /* PendingAPIRequestURL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PendingAPIRequestURL.cpp; sourceTree = "<group>"; };
 		076E507E1F45031E006E9F5A /* Logging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Logging.cpp; sourceTree = "<group>"; };
 		0794740C25CA0BDE00C597EB /* MediaSession.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MediaSession.mm; sourceTree = "<group>"; };
@@ -4274,6 +4276,7 @@
 				33DC8910141953A300747EF7 /* LoadCanceledNoServerRedirectCallback.cpp */,
 				33DC89131419579F00747EF7 /* LoadCanceledNoServerRedirectCallback_Bundle.cpp */,
 				8AA28C1916D2FA7B002FF4DB /* LoadPageOnCrash.cpp */,
+				075A9CF426177217006DFA3A /* MediaSessionCoordinatorTest.mm */,
 				CDC9442C1EF1FC080059C3C4 /* MediaStreamTrackDetached.mm */,
 				7A5623101AD5AF3E0096B920 /* MenuTypesForMouseEvents.cpp */,
 				51CB4AD71B3A079C00C1B1C6 /* ModalAlertsSPI.cpp */,
@@ -5500,6 +5503,7 @@
 				07EF76D52540FC060053ED53 /* MediaMutedState.mm in Sources */,
 				CDA315981ED53651009F60D3 /* MediaPlaybackSleepAssertion.mm in Sources */,
 				0794740D25CA0BDE00C597EB /* MediaSession.mm in Sources */,
+				075A9CF526177218006DFA3A /* MediaSessionCoordinatorTest.mm in Sources */,
 				CDC9442E1EF1FC080059C3C4 /* MediaStreamTrackDetached.mm in Sources */,
 				51BE9E662376089F00B4E117 /* MediaType.mm in Sources */,
 				7CCE7EC51A411A7E00447C4C /* MemoryCacheDisableWithinResourceLoadDelegate.mm in Sources */,

Added: trunk/Tools/TestWebKitAPI/Tests/WebKit/MediaSessionCoordinatorTest.mm (0 => 275537)


--- trunk/Tools/TestWebKitAPI/Tests/WebKit/MediaSessionCoordinatorTest.mm	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit/MediaSessionCoordinatorTest.mm	2021-04-06 19:06:53 UTC (rev 275537)
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2021 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(MEDIA_SESSION_COORDINATOR)
+
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKProcessPoolPrivate.h>
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/WKWebViewPrivateForTesting.h>
+#import <WebKit/_WKExperimentalFeature.h>
+#import <WebKit/_WKProcessPoolConfiguration.h>
+#import <wtf/BlockPtr.h>
+#import <wtf/HashSet.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/RunLoop.h>
+#import <wtf/WeakObjCPtr.h>
+#import <wtf/text/StringHash.h>
+
+@interface _WKMockMediaSessionCoordinator : NSObject <_WKMediaSessionCoordinator>
+@property (nonatomic, readonly) NSString *lastStateChange;
+@property (nonatomic, readonly) NSString *lastMethodCalled;
+@property (nonatomic) BOOL failsCommands;
+
+- (void)seekSessionToTime:(double)time;
+- (void)playSession;
+- (void)pauseSession;
+- (void)setSessionTrack:(NSString*)trackIdentifier;
+@end
+
+@implementation _WKMockMediaSessionCoordinator {
+    RetainPtr<NSString> _lastStateChange;
+    RetainPtr<NSString> _lastMethodCalled;
+    id <_WKMediaSessionCoordinatorDelegate> _delegate;
+}
+
+@synthesize delegate;
+
+- (NSString *)lastStateChange
+{
+    return std::exchange(_lastStateChange, @"").get();
+}
+
+- (NSString *)lastMethodCalled
+{
+    return std::exchange(_lastMethodCalled, @"").get();
+}
+
+- (NSString *)identifier
+{
+    return @"TestCoordinator";
+}
+
+- (void)joinWithCompletion:(void(^ _Nonnull)(BOOL))completionHandler
+{
+    _lastMethodCalled = @"join";
+    dispatch_async(dispatch_get_main_queue(), ^() {
+        completionHandler(!_failsCommands);
+    });
+}
+
+- (void)leave
+{
+    _lastMethodCalled = @"leave";
+}
+
+- (void)seekTo:(double)time withCompletion:(void(^ _Nonnull)(BOOL))completionHandler
+{
+    _lastMethodCalled = @"seekTo";
+    dispatch_async(dispatch_get_main_queue(), ^() {
+        completionHandler(!_failsCommands);
+    });
+}
+
+- (void)playWithCompletion:(void(^ _Nonnull)(BOOL))completionHandler
+{
+    _lastMethodCalled = @"play";
+    dispatch_async(dispatch_get_main_queue(), ^() {
+        completionHandler(!_failsCommands);
+    });
+}
+
+- (void)pauseWithCompletion:(void(^ _Nonnull)(BOOL))completionHandler
+{
+    _lastMethodCalled = @"pause";
+    dispatch_async(dispatch_get_main_queue(), ^() {
+        completionHandler(!_failsCommands);
+    });
+}
+
+- (void)setTrack:(NSString*)trackIdentifier withCompletion:(void(^ _Nonnull)(BOOL))completionHandler
+{
+    _lastMethodCalled = @"setTrack";
+    dispatch_async(dispatch_get_main_queue(), ^() {
+        completionHandler(!_failsCommands);
+    });
+}
+
+- (void)positionStateChanged:(nullable _WKMediaPositionState *)state
+{
+    _lastStateChange = @"positionStateChanged";
+}
+
+- (void)readyStateChanged:(_WKMediaSessionReadyState)state
+{
+    _lastStateChange = @"readyStateChanged";
+}
+
+- (void)playbackStateChanged:(_WKMediaSessionPlaybackState)state
+{
+    _lastStateChange = @"playbackStateChanged";
+}
+
+- (void)coordinatorStateChanged:(_WKMediaSessionCoordinatorState)state
+{
+    _lastStateChange = @"coordinatorStateChanged";
+}
+
+- (void)seekSessionToTime:(double)time
+{
+    [self.delegate seekSessionToTime:time withCompletion:^(BOOL result) {
+        _lastMethodCalled = @"seekSessionToTime";
+    }];
+}
+
+- (void)playSession
+{
+    [self.delegate playSessionWithCompletion:^(BOOL result) {
+        _lastMethodCalled = @"playSession";
+    }];
+}
+
+- (void)pauseSession
+{
+    [self.delegate pauseSessionWithCompletion:^(BOOL result) {
+        _lastMethodCalled = @"pauseSession";
+    }];
+}
+
+- (void)setSessionTrack:(NSString*)trackIdentifier
+{
+    [self.delegate setSessionTrack:trackIdentifier withCompletion:^(BOOL result) {
+        _lastMethodCalled = @"setSessionTrack";
+    }];
+}
+
+@end
+
+namespace TestWebKitAPI {
+
+class MediaSessionCoordinatorTest : public testing::Test {
+public:
+    void SetUp() final
+    {
+        auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+        auto preferences = [configuration preferences];
+
+        for (_WKExperimentalFeature *feature in [WKPreferences _experimentalFeatures]) {
+            if ([feature.key isEqualToString:@"MediaSessionCoordinatorEnabled"])
+                [preferences _setEnabled:YES forFeature:feature];
+            if ([feature.key isEqualToString:@"MediaSessionEnabled"])
+                [preferences _setEnabled:YES forFeature:feature];
+        }
+
+        _webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400) configuration:configuration.get()]);
+    }
+
+    void TearDown() override
+    {
+        [_webView clearMessageHandlers:_messageHandlers.get()];
+    }
+
+    void createCoordinator()
+    {
+        _coordinator = [[_WKMockMediaSessionCoordinator alloc] init];
+
+        __block bool result = false;
+        __block bool done = false;
+        [webView() _createMediaSessionCoordinatorForTesting:(id <_WKMediaSessionCoordinator>)_coordinator.get() completionHandler:^(BOOL success) {
+            result = success;
+            done = true;
+        }];
+        TestWebKitAPI::Util::run(&done);
+
+        listenForEventMessages({ "coordinatorchange"_s });
+
+        EXPECT_TRUE(result);
+        if (!result)
+            NSLog(@"-[_createMediaSessionCoordinatorForTesting:completionHandler:] failed!");
+
+        waitForEventListenerToBeCalled("coordinatorchange"_s);
+        ASSERT_TRUE(eventListenerWasCalled("coordinatorchange"_s));
+    }
+
+    TestWKWebView* webView() const { return _webView.get(); }
+    _WKMockMediaSessionCoordinator* coordinator() const { return _coordinator.get(); }
+
+    void loadPageAndBecomeReady(const String& pageName)
+    {
+        [_webView synchronouslyLoadTestPageNamed:pageName];
+
+        bool canplaythrough = false;
+        [webView() performAfterReceivingMessage:@"canplaythrough event" action:[&] {
+            canplaythrough = true;
+        }];
+        runScriptWithUserGesture("load()");
+        Util::run(&canplaythrough);
+    }
+
+    void runScriptWithUserGesture(const String& script)
+    {
+        [_webView objectByEvaluatingJavaScriptWithUserGesture:script];
+    }
+
+    void play()
+    {
+        bool playing = false;
+        [_webView performAfterReceivingMessage:@"play event" action:[&] { playing = true; }];
+        runScriptWithUserGesture("audio.play()");
+        Util::run(&playing);
+    }
+
+    void pause()
+    {
+        bool paused = false;
+        [_webView performAfterReceivingMessage:@"pause event" action:[&] { paused = true; }];
+        runScriptWithUserGesture("audio.pause()");
+        Util::run(&paused);
+    }
+
+    void listenForEventMessages(std::initializer_list<const char*> events)
+    {
+        for (auto* event : events) {
+            auto eventMessage = makeString(event, " event");
+            [webView() performAfterReceivingMessage:eventMessage action:[this, eventMessage = WTFMove(eventMessage)] {
+                _eventListenersCalled.add(eventMessage);
+            }];
+        }
+    }
+
+    bool eventListenerWasCalled(const String& event)
+    {
+        return _eventListenersCalled.contains(makeString(event, " event"));
+    }
+
+    void clearEventListenerState()
+    {
+        _eventListenersCalled.clear();
+    }
+
+    void executeUntil(Function<bool()>&& callback, int retries = 50)
+    {
+        int tries = 0;
+        do {
+            if (callback())
+                return;
+            Util::sleep(0.1);
+        } while (++tries <= retries);
+
+        return;
+    }
+
+    void waitForEventListenerToBeCalled(const String& event)
+    {
+        executeUntil([&] {
+            return eventListenerWasCalled(event);
+        });
+    }
+
+    void listenForMessagesPosted(std::initializer_list<const char*> handlers, const char* suffix)
+    {
+        for (auto* handler : handlers) {
+            auto handlerMessage = makeString(handler, suffix);
+            [_messageHandlers addObject:handlerMessage];
+            [webView() performAfterReceivingMessage:handlerMessage action:[this, handlerMessage = WTFMove(handlerMessage)] {
+                _sessionMessagesPosted.add(handlerMessage);
+            }];
+        }
+    }
+
+    void clearMessagesPosted()
+    {
+        _sessionMessagesPosted.clear();
+    }
+
+    void listenForSessionHandlerMessages(std::initializer_list<const char*> handlers)
+    {
+        listenForMessagesPosted(handlers, " handler");
+    }
+
+    bool sessionHandlerWasCalled(const String& handler)
+    {
+        return _sessionMessagesPosted.contains(makeString(handler, " handler"));
+    }
+
+    void waitForSessionHandlerToBeCalled(const String& handler)
+    {
+        executeUntil([&] {
+            return sessionHandlerWasCalled(handler);
+        });
+    }
+
+    void listenForPromiseMessages(std::initializer_list<const char*> handlers)
+    {
+        listenForMessagesPosted(handlers, " resolved");
+        listenForMessagesPosted(handlers, " rejected");
+    }
+
+    void clearPromiseMessages(const String& promise)
+    {
+        _sessionMessagesPosted.remove(makeString(promise, " resolved"));
+        _sessionMessagesPosted.remove(makeString(promise, " rejected"));
+    }
+
+    bool promiseWasResolved(const String& promise)
+    {
+        return _sessionMessagesPosted.contains(makeString(promise, " resolved"));
+    }
+
+    bool promiseWasRejected(const String& promise)
+    {
+        return _sessionMessagesPosted.contains(makeString(promise, " rejected"));
+    }
+
+    void waitForPromise(const String& promise)
+    {
+        executeUntil([&] {
+            return promiseWasResolved(promise) || promiseWasRejected(promise);
+        }, 200);
+    }
+
+private:
+    RetainPtr<_WKMockMediaSessionCoordinator> _coordinator;
+    RetainPtr<WKWebViewConfiguration> _configuration;
+    RetainPtr<TestWKWebView> _webView;
+    HashSet<String> _sessionMessagesPosted;
+    HashSet<String> _eventListenersCalled;
+    RetainPtr<NSMutableArray> _messageHandlers;
+};
+
+TEST_F(MediaSessionCoordinatorTest, JoinAndLeave)
+{
+    loadPageAndBecomeReady("media-remote"_s);
+    listenForPromiseMessages({ "join"_s });
+
+    createCoordinator();
+
+    RetainPtr<NSString> state = [webView() stringByEvaluatingJavaScript:@"navigator.mediaSession.coordinator.state"];
+    EXPECT_STREQ("waiting", [state UTF8String]);
+
+    // 'join()' should fail if the remote coordinator refuses.
+    coordinator().failsCommands = YES;
+    [webView() objectByEvaluatingJavaScript:@"joinSession()"];
+    waitForPromise("join"_s);
+    ASSERT_TRUE(promiseWasRejected("join"_s));
+    clearPromiseMessages("join"_s);
+    EXPECT_STREQ("join", coordinator().lastMethodCalled.UTF8String);
+
+    // And it shoud succeed if it allows it.
+    coordinator().failsCommands = NO;
+    [webView() objectByEvaluatingJavaScript:@"joinSession()"];
+    waitForPromise("join"_s);
+    ASSERT_TRUE(promiseWasResolved("join"_s));
+    clearPromiseMessages("join"_s);
+    EXPECT_STREQ("join", coordinator().lastMethodCalled.UTF8String);
+
+    state = [webView() stringByEvaluatingJavaScript:@"navigator.mediaSession.coordinator.state"];
+    EXPECT_STREQ("joined", [state UTF8String]);
+
+    [webView() objectByEvaluatingJavaScript:@"navigator.mediaSession.coordinator.leave()"];
+    String lastMethodCalled;
+    executeUntil([&] {
+        lastMethodCalled = coordinator().lastMethodCalled;
+        return lastMethodCalled == "leave";
+    });
+    EXPECT_STREQ("leave", lastMethodCalled.utf8().data());
+
+    state = [webView() stringByEvaluatingJavaScript:@"navigator.mediaSession.coordinator.state"];
+    EXPECT_STREQ("closed", [state UTF8String]);
+
+    // It shouldn't be possible to re-join a close coordinator.
+    [webView() objectByEvaluatingJavaScript:@"joinSession()"];
+    waitForPromise("join"_s);
+    ASSERT_TRUE(promiseWasRejected("join"_s));
+    EXPECT_STREQ("", coordinator().lastMethodCalled.UTF8String);
+}
+
+TEST_F(MediaSessionCoordinatorTest, StateChanges)
+{
+    loadPageAndBecomeReady("media-remote"_s);
+
+    listenForPromiseMessages({ "join"_s });
+    createCoordinator();
+    [webView() objectByEvaluatingJavaScript:@"joinSession()"];
+    waitForPromise("join"_s);
+    ASSERT_TRUE(promiseWasResolved("join"_s));
+    clearPromiseMessages("join"_s);
+    EXPECT_STREQ("join", coordinator().lastMethodCalled.UTF8String);
+
+    [webView() objectByEvaluatingJavaScript:@"navigator.mediaSession.setPositionState({ duration: 1, playbackRate: 1, position: 0 })"];
+    String lastStateChange;
+    executeUntil([&] {
+        lastStateChange = coordinator().lastStateChange;
+        return lastStateChange == "positionStateChanged";
+    });
+    EXPECT_STREQ("positionStateChanged", lastStateChange.utf8().data());
+
+    for (NSString *state in @[ @"haveMetadata", @"haveCurrentData", @"haveFutureData", @"haveEnoughData", @"haveNothing" ]) {
+        [webView() objectByEvaluatingJavaScript:[NSString stringWithFormat:@"navigator.mediaSession.readyState = '%@'", state]];
+        executeUntil([&] {
+            lastStateChange = coordinator().lastStateChange;
+            return lastStateChange == "readyStateChanged";
+        });
+        EXPECT_STREQ("readyStateChanged", lastStateChange.utf8().data());
+
+        RetainPtr<NSString> currentState = [webView() stringByEvaluatingJavaScript:@"navigator.mediaSession.readyState"];
+        EXPECT_STREQ(state.UTF8String, currentState.get().UTF8String);
+    }
+
+    for (NSString *state in @[ @"paused", @"playing", @"none" ]) {
+        [webView() objectByEvaluatingJavaScript:[NSString stringWithFormat:@"navigator.mediaSession.playbackState = '%@'", state]];
+        executeUntil([&] {
+            lastStateChange = coordinator().lastStateChange;
+            return lastStateChange == "playbackStateChanged";
+        });
+        EXPECT_STREQ("playbackStateChanged", lastStateChange.utf8().data());
+
+        RetainPtr<NSString> currentState = [webView() stringByEvaluatingJavaScript:@"navigator.mediaSession.playbackState"];
+        EXPECT_STREQ(state.UTF8String, currentState.get().UTF8String);
+    }
+
+    [webView() objectByEvaluatingJavaScript:@"navigator.mediaSession.coordinator.leave()"];
+    String lastMethodCalled;
+    executeUntil([&] {
+        lastStateChange = coordinator().lastStateChange;
+        return lastStateChange == "coordinatorStateChanged";
+    });
+    EXPECT_STREQ("coordinatorStateChanged", lastStateChange.utf8().data());
+}
+
+TEST_F(MediaSessionCoordinatorTest, CoordinatorMethodCallbacks)
+{
+    loadPageAndBecomeReady("media-remote"_s);
+
+    listenForPromiseMessages({ "join"_s });
+    createCoordinator();
+    [webView() objectByEvaluatingJavaScript:@"joinSession()"];
+    waitForPromise("join"_s);
+    ASSERT_TRUE(promiseWasResolved("join"_s));
+    clearPromiseMessages("join"_s);
+    EXPECT_STREQ("join", coordinator().lastMethodCalled.UTF8String);
+
+    listenForPromiseMessages({ "play"_s, "pause"_s, "seekTo"_s, "setTrack"_s });
+    auto methodsAndArgs = @[
+        @[ @"play", @"" ],
+        @[ @"pause", @"" ],
+        @[ @"seekTo", @"10" ],
+        @[ @"setTrack", @"'\\'test-track-1\\''" ],
+    ];
+    for (NSArray *methodInfo in methodsAndArgs) {
+        NSString *method = methodInfo[0];
+        NSString *args = methodInfo[1];
+        auto str = [NSString stringWithFormat:@"callMethod('%@', %@)", method, args];
+        [webView() objectByEvaluatingJavaScript:str];
+        waitForPromise(method);
+        ASSERT_TRUE(promiseWasResolved(method));
+        clearPromiseMessages(method);
+        EXPECT_STREQ(method.UTF8String, coordinator().lastMethodCalled.UTF8String);
+    }
+}
+
+TEST_F(MediaSessionCoordinatorTest, CallSessionMethods)
+{
+    loadPageAndBecomeReady("media-remote"_s);
+    listenForSessionHandlerMessages({ "play"_s, "pause"_s, "seekto"_s, "nexttrack"_s });
+
+    listenForPromiseMessages({ "join"_s });
+    createCoordinator();
+    [webView() objectByEvaluatingJavaScript:@"joinSession()"];
+    waitForPromise("join"_s);
+    ASSERT_TRUE(promiseWasResolved("join"_s));
+    clearPromiseMessages("join"_s);
+    EXPECT_STREQ("join", coordinator().lastMethodCalled.UTF8String);
+
+    String lastMethodCalled;
+
+    [coordinator() seekSessionToTime:20];
+    executeUntil([&] {
+        lastMethodCalled = coordinator().lastMethodCalled;
+        return lastMethodCalled == "seekSessionToTime";
+    });
+    EXPECT_STREQ("seekSessionToTime", lastMethodCalled.utf8().data());
+
+    [coordinator() playSession];
+    executeUntil([&] {
+        lastMethodCalled = coordinator().lastMethodCalled;
+        return lastMethodCalled == "playSession";
+    });
+    EXPECT_STREQ("playSession", lastMethodCalled.utf8().data());
+
+    [coordinator() pauseSession];
+    executeUntil([&] {
+        lastMethodCalled = coordinator().lastMethodCalled;
+        return lastMethodCalled == "pauseSession";
+    });
+    EXPECT_STREQ("pauseSession", lastMethodCalled.utf8().data());
+
+    [coordinator() setSessionTrack:@"Track 0"];
+    executeUntil([&] {
+        lastMethodCalled = coordinator().lastMethodCalled;
+        return lastMethodCalled == "setSessionTrack";
+    });
+    EXPECT_STREQ("setSessionTrack", lastMethodCalled.utf8().data());
+}
+
+} // namespace TestWebKitAPI
+
+#endif // ENABLE(MEDIA_SESSION_COORDINATOR)

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/media-remote.html (275536 => 275537)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/media-remote.html	2021-04-06 18:15:34 UTC (rev 275536)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/media-remote.html	2021-04-06 19:06:53 UTC (rev 275537)
@@ -40,11 +40,23 @@
             navigator.mediaSession.metadata = new MediaMetadata(data);
             postMessage('set metadata')
         }
+        
+        function joinSession()
+        {
+            let promise = navigator.mediaSession.coordinator.join();
+            postPromise("join", promise);
+        }
 
+        function callMethod(method, args)
+        {
+            let code = `navigator.mediaSession.coordinator.${method}(${args})`;
+            let promise = eval(code);
+            postPromise(method, promise);
+        }
+
         function setPlaylistMetadata(index)
         {
             setMetadata(playlist.metadata[index]);
-        
         }
 
         function clearActionHandlers()
@@ -56,17 +68,11 @@
         {
             const actions = {
                 'play' : () => { postMessage('play handler'); },
-
                 'pause' : () => { postMessage('pause handler') },
-                
                 'seekto' : (details) => { postMessage('seekto handler') },
-
                 'seekforward' : (details) => { postMessage('seekforward handler') },
-
-                'seekbackward' : (details) => { postMessage('seekbackward handler') }, 
-
-                'previoustrack' : () => { postMessage('previoustrack handler') }, 
-
+                'seekbackward' : (details) => { postMessage('seekbackward handler') },
+                'previoustrack' : () => { postMessage('previoustrack handler') },
                 'nexttrack' : () => { postMessage('nexttrack handler') }, 
             };
 
@@ -95,6 +101,15 @@
             postMessage(`${evt.type} event`);
         }
         
+        async function postPromise(type, promise)
+        {
+            log(`waiting for ${type} promise`);
+            await promise
+                .then(  () => { postMessage(`${type} resolved`); } )
+                .catch( () => { postMessage(`${type} rejected`); } );
+            log(`${type} promise settled`);
+        }
+        
         function load()
         {
             let src = ""
@@ -112,7 +127,8 @@
             eventLog.insertBefore(note, eventLog.firstChild);
         }
 
-        
+        const session = navigator.mediaSession;
+
         window.addEventListener("load", evt => {
             audio = document.getElementsByTagName('audio')[0];
             audio.addEventListener('canplaythrough', postEvent);
@@ -119,6 +135,7 @@
             audio.addEventListener('play', postEvent);
             audio.addEventListener('pause', postEvent);
             audio.addEventListener('seeked', postEvent);
+            navigator.mediaSession.addEventListener('coordinatorchange', postEvent);
         }, false);
 
     </script>
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to