[iOS] Unmuting capture of a page is not working
https://bugs.webkit.org/show_bug.cgi?id=202627

Reviewed by Eric Carlson.

Source/WebCore:

Before the patch, we were muting the capture tracks but never unmuting them.
The patch updates the code to make sure we unmute capture tracks based on the document state.
In addition, the iOS code wass process-wide while capture might happen between two documents in the same process.
The patch updates the capturestate computation and muting logic to be Document based.
A follow-up refactoring will merge back iOS and MacOS code paths.
Covered by API test.

* Modules/mediastream/MediaStreamTrack.cpp:
(WebCore::MediaStreamTrack::captureState):
(WebCore::MediaStreamTrack::updateCaptureAccordingMutedState):
(WebCore::MediaStreamTrack::muteCapture): Deleted.
* Modules/mediastream/MediaStreamTrack.h:
* dom/Document.cpp:
(WebCore::Document::updateIsPlayingMedia):
(WebCore::Document::pageMutedStateDidChange):

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKit/GetUserMedia.mm: Added.
(-[GetUserMediaCaptureUIDelegate _webView:requestMediaCaptureAuthorization:decisionHandler:]):
(-[GetUserMediaCaptureUIDelegate _webView:checkUserMediaPermissionForURL:mainFrameURL:frameIdentifier:decisionHandler:]):
(-[GetUserMediaTestView haveStream:]):
(TestWebKitAPI::waitUntilCaptureState):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKit/getUserMedia.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@250774 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index e862acd..93b107a 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,26 @@
+2019-10-07  youenn fablet  <youenn@apple.com>
+
+        [iOS] Unmuting capture of a page is not working
+        https://bugs.webkit.org/show_bug.cgi?id=202627
+
+        Reviewed by Eric Carlson.
+
+        Before the patch, we were muting the capture tracks but never unmuting them.
+        The patch updates the code to make sure we unmute capture tracks based on the document state.
+        In addition, the iOS code wass process-wide while capture might happen between two documents in the same process.
+        The patch updates the capturestate computation and muting logic to be Document based.
+        A follow-up refactoring will merge back iOS and MacOS code paths.
+        Covered by API test.
+
+        * Modules/mediastream/MediaStreamTrack.cpp:
+        (WebCore::MediaStreamTrack::captureState):
+        (WebCore::MediaStreamTrack::updateCaptureAccordingMutedState):
+        (WebCore::MediaStreamTrack::muteCapture): Deleted.
+        * Modules/mediastream/MediaStreamTrack.h:
+        * dom/Document.cpp:
+        (WebCore::Document::updateIsPlayingMedia):
+        (WebCore::Document::pageMutedStateDidChange):
+
 2019-10-06  Ryosuke Niwa  <rniwa@webkit.org>
 
         attachShadow should support attaching a shadow root to a main element
diff --git a/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp b/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp
index 8513179..d08798e 100644
--- a/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp
+++ b/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp
@@ -465,22 +465,24 @@
 }
 
 #if PLATFORM(IOS_FAMILY)
-MediaProducer::MediaStateFlags MediaStreamTrack::captureState()
+MediaProducer::MediaStateFlags MediaStreamTrack::captureState(Document& document)
 {
     MediaProducer::MediaStateFlags state = MediaProducer::IsNotPlaying;
-    if (auto* source = RealtimeMediaSourceCenter::singleton().audioCaptureFactory().activeSource())
-        state |= sourceCaptureState(*source);
-    if (auto* source = RealtimeMediaSourceCenter::singleton().videoCaptureFactory().activeSource())
-        state |= sourceCaptureState(*source);
+    for (auto* captureTrack : allCaptureTracks()) {
+        if (captureTrack->document() != &document || captureTrack->ended())
+            continue;
+        state |= sourceCaptureState(captureTrack->source());
+    }
     return state;
 }
 
-void MediaStreamTrack::muteCapture()
+void MediaStreamTrack::updateCaptureAccordingToMutedState(Document& document)
 {
-    if (auto* source = RealtimeMediaSourceCenter::singleton().audioCaptureFactory().activeSource())
-        source->setMuted(true);
-    if (auto* source = RealtimeMediaSourceCenter::singleton().videoCaptureFactory().activeSource())
-        source->setMuted(true);
+    for (auto* captureTrack : allCaptureTracks()) {
+        if (captureTrack->document() != &document || captureTrack->ended())
+            continue;
+        captureTrack->setMuted(document.page()->mutedState());
+    }
 }
 #endif
 
diff --git a/Source/WebCore/Modules/mediastream/MediaStreamTrack.h b/Source/WebCore/Modules/mediastream/MediaStreamTrack.h
index 627f6df..47abd2b 100644
--- a/Source/WebCore/Modules/mediastream/MediaStreamTrack.h
+++ b/Source/WebCore/Modules/mediastream/MediaStreamTrack.h
@@ -72,8 +72,8 @@
     static void endCapture(Document&);
 
 #if PLATFORM(IOS_FAMILY)
-    static MediaProducer::MediaStateFlags captureState();
-    static void muteCapture();
+    static MediaProducer::MediaStateFlags captureState(Document&);
+    static void updateCaptureAccordingToMutedState(Document&);
 #endif
 
     virtual bool isCanvas() const { return false; }
diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp
index 1fbb3a7..a74922d 100644
--- a/Source/WebCore/dom/Document.cpp
+++ b/Source/WebCore/dom/Document.cpp
@@ -3987,7 +3987,7 @@
         state |= audioProducer.mediaState();
 
 #if ENABLE(MEDIA_STREAM) && PLATFORM(IOS_FAMILY)
-    state |= MediaStreamTrack::captureState();
+    state |= MediaStreamTrack::captureState(*this);
 #endif
 
 #if ENABLE(MEDIA_SESSION)
@@ -4033,7 +4033,7 @@
         audioProducer.pageMutedStateDidChange();
 
 #if ENABLE(MEDIA_STREAM) && PLATFORM(IOS_FAMILY)
-    MediaStreamTrack::muteCapture();
+    MediaStreamTrack::updateCaptureAccordingToMutedState(*this);
 #endif
 }
 
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 69b301e..b96b23e 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,19 @@
+2019-10-07  youenn fablet  <youenn@apple.com>
+
+        [iOS] Unmuting capture of a page is not working
+        https://bugs.webkit.org/show_bug.cgi?id=202627
+
+        Reviewed by Eric Carlson.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKit/GetUserMedia.mm: Added.
+        (-[GetUserMediaCaptureUIDelegate _webView:requestMediaCaptureAuthorization:decisionHandler:]):
+        (-[GetUserMediaCaptureUIDelegate _webView:checkUserMediaPermissionForURL:mainFrameURL:frameIdentifier:decisionHandler:]):
+        (-[GetUserMediaTestView haveStream:]):
+        (TestWebKitAPI::waitUntilCaptureState):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKit/getUserMedia.html:
+
 2019-10-07  Philippe Normand  <pnormand@igalia.com>
 
         [GStreamer][JHBuild] Update to 1.16.1
diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
index 88f6d08..8f0cb48 100644
--- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
+++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
@@ -213,6 +213,7 @@
 		3FBD1B4A1D3D66AB00E6D6FA /* FullscreenLayoutConstraints.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 3FBD1B491D39D1DB00E6D6FA /* FullscreenLayoutConstraints.html */; };
 		3FCC4FE51EC4E8520076E37C /* PictureInPictureDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FCC4FE41EC4E8520076E37C /* PictureInPictureDelegate.mm */; };
 		3FCC4FE81EC4E8CA0076E37C /* PictureInPictureDelegate.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 3FCC4FE61EC4E87E0076E37C /* PictureInPictureDelegate.html */; };
+		41157237234B240C0050A1D1 /* GetUserMedia.mm in Sources */ = {isa = PBXBuildFile; fileRef = 41157236234B24040050A1D1 /* GetUserMedia.mm */; };
 		4135FB842011FAA700332139 /* InjectInternals_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4135FB832011FAA300332139 /* InjectInternals_Bundle.cpp */; };
 		4135FB852011FABF00332139 /* libWebCoreTestSupport.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4135FB862011FABF00332139 /* libWebCoreTestSupport.dylib */; };
 		414AD6862285D1C000777F2D /* StorageQuota.mm in Sources */ = {isa = PBXBuildFile; fileRef = 414AD6852285D1B000777F2D /* StorageQuota.mm */; };
@@ -1702,6 +1703,7 @@
 		3FBD1B491D39D1DB00E6D6FA /* FullscreenLayoutConstraints.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = FullscreenLayoutConstraints.html; sourceTree = "<group>"; };
 		3FCC4FE41EC4E8520076E37C /* PictureInPictureDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PictureInPictureDelegate.mm; sourceTree = "<group>"; };
 		3FCC4FE61EC4E87E0076E37C /* PictureInPictureDelegate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = PictureInPictureDelegate.html; sourceTree = "<group>"; };
+		41157236234B24040050A1D1 /* GetUserMedia.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GetUserMedia.mm; sourceTree = "<group>"; };
 		4135FB832011FAA300332139 /* InjectInternals_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = InjectInternals_Bundle.cpp; path = Tests/InjectInternals_Bundle.cpp; sourceTree = SOURCE_ROOT; };
 		4135FB862011FABF00332139 /* libWebCoreTestSupport.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libWebCoreTestSupport.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
 		414AD6852285D1B000777F2D /* StorageQuota.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StorageQuota.mm; sourceTree = "<group>"; };
@@ -3514,6 +3516,7 @@
 				26F52EAA182872600023D412 /* Geolocation.cpp */,
 				F660AA0C15A5F061003A1243 /* GetInjectedBundleInitializationUserDataCallback.cpp */,
 				F660AA0F15A5F624003A1243 /* GetInjectedBundleInitializationUserDataCallback_Bundle.cpp */,
+				41157236234B24040050A1D1 /* GetUserMedia.mm */,
 				07CE1CF21F06A7E000BF89F5 /* GetUserMediaNavigation.mm */,
 				07E499901F9E56A1002F1EF3 /* GetUserMediaReprompt.mm */,
 				4BFDFFA8131477770061F24B /* HitTestResultNodeHandle.cpp */,
@@ -4553,6 +4556,7 @@
 				7CCE7EF91A411AE600447C4C /* GetInjectedBundleInitializationUserDataCallback.cpp in Sources */,
 				7CCE7EE21A411A9A00447C4C /* GetPIDAfterAbortedProcessLaunch.cpp in Sources */,
 				2DADF26321CB8F32003D3E3A /* GetResourceData.mm in Sources */,
+				41157237234B240C0050A1D1 /* GetUserMedia.mm in Sources */,
 				07CE1CF31F06A7E000BF89F5 /* GetUserMediaNavigation.mm in Sources */,
 				07E499911F9E56DF002F1EF3 /* GetUserMediaReprompt.mm in Sources */,
 				8E4A85371E1D1AB200F53B0F /* GridPosition.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm b/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm
new file mode 100644
index 0000000..c946234
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 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_STREAM)
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKUIDelegatePrivate.h>
+#import <WebKit/WKWebView.h>
+#import <WebKit/WKWebViewConfiguration.h>
+#import <WebKit/_WKProcessPoolConfiguration.h>
+
+@interface GetUserMediaCaptureUIDelegate : NSObject<WKUIDelegate>
+- (void)_webView:(WKWebView *)webView requestMediaCaptureAuthorization: (_WKCaptureDevices)devices decisionHandler:(void (^)(BOOL))decisionHandler;
+- (void)_webView:(WKWebView *)webView checkUserMediaPermissionForURL:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL frameIdentifier:(NSUInteger)frameIdentifier decisionHandler:(void (^)(NSString *salt, BOOL authorized))decisionHandler;
+@end
+
+@implementation GetUserMediaCaptureUIDelegate
+- (void)_webView:(WKWebView *)webView requestMediaCaptureAuthorization: (_WKCaptureDevices)devices decisionHandler:(void (^)(BOOL))decisionHandler
+{
+    decisionHandler(YES);
+}
+
+- (void)_webView:(WKWebView *)webView checkUserMediaPermissionForURL:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL frameIdentifier:(NSUInteger)frameIdentifier decisionHandler:(void (^)(NSString *salt, BOOL authorized))decisionHandler
+{
+    decisionHandler(@"0x9876543210", YES);
+}
+@end
+
+namespace TestWebKitAPI {
+
+void waitUntilCaptureState(WKWebView *webView, _WKMediaCaptureState expectedState)
+{
+    do {
+        auto state = [webView _mediaCaptureState];
+        if (state == expectedState)
+            return;
+        TestWebKitAPI::Util::spinRunLoop(1);
+    } while (true);
+}
+
+TEST(WebKit2, CaptureMute)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto processPoolConfig = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
+    auto preferences = [configuration preferences];
+    preferences._mediaCaptureRequiresSecureConnection = NO;
+    preferences._mediaDevicesEnabled = YES;
+    preferences._mockCaptureDevicesEnabled = YES;
+    auto webView = [[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration.get() processPoolConfiguration:processPoolConfig.get()];
+    auto delegate = adoptNS([[GetUserMediaCaptureUIDelegate alloc] init]);
+    webView.UIDelegate = delegate.get();
+
+    [webView loadTestPageNamed:@"getUserMedia"];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateActiveCamera);
+
+    [webView _setPageMuted: _WKMediaCaptureDevicesMuted];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateMutedCamera);
+    [webView _setPageMuted: _WKMediaNoneMuted];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateActiveCamera);
+
+    [webView stringByEvaluatingJavaScript:@"stop()"];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateNone);
+
+    [webView stringByEvaluatingJavaScript:@"captureAudio()"];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateActiveMicrophone);
+    [webView _setPageMuted: _WKMediaCaptureDevicesMuted];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateMutedMicrophone);
+    [webView _setPageMuted: _WKMediaNoneMuted];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateActiveMicrophone);
+
+    [webView _setPageMuted: _WKMediaCaptureDevicesMuted];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateMutedMicrophone);
+
+    [webView stringByEvaluatingJavaScript:@"stop()"];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateNone);
+
+    [webView stringByEvaluatingJavaScript:@"captureAudioAndVideo()"];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateActiveCamera | _WKMediaCaptureStateActiveMicrophone);
+    [webView _setPageMuted: _WKMediaCaptureDevicesMuted];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateMutedCamera | _WKMediaCaptureStateMutedMicrophone);
+    [webView _setPageMuted: _WKMediaNoneMuted];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateActiveCamera | _WKMediaCaptureStateActiveMicrophone);
+
+    [webView stringByEvaluatingJavaScript:@"stop()"];
+    waitUntilCaptureState(webView, _WKMediaCaptureStateNone);
+}
+
+} // namespace TestWebKitAPI
+
+#endif // ENABLE(MEDIA_STREAM)
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html b/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html
index 82f245b..cb32d80 100644
--- a/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html
+++ b/Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html
@@ -50,6 +50,16 @@
                 navigator.mediaDevices.getUserMedia({audio: true});
                 navigator.mediaDevices.getUserMedia({audio: true, video: true});
             }
+
+            function captureAudio()
+            {
+                navigator.mediaDevices.getUserMedia({audio: true}).then(s => stream = s);
+            }
+
+            function captureAudioAndVideo()
+            {
+                navigator.mediaDevices.getUserMedia({audio: true, video: true}).then(s => stream = s);
+            }
         </script>
     <head>