[MediaStream] Clear cached gUM prompt state
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 25 Oct 2017 22:15:05 +0000 (22:15 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 25 Oct 2017 22:15:05 +0000 (22:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178754
<rdar://problem/32742356>

Reviewed by Youenn Fablet.

Source/WebKit:

* Shared/WebPreferences.yaml: Define new settings.

* Shared/WebPreferencesDefinitionsBase.h: New default values.

* UIProcess/API/C/WKPreferences.cpp:
(WKPreferencesSetInactiveMediaCaptureSteamRepromptIntervalInMinutes): New.
(WKPreferencesGetInactiveMediaCaptureSteamRepromptIntervalInMinutes): Ditto.
* UIProcess/API/C/WKPreferencesRefPrivate.h:

* UIProcess/API/Cocoa/WKPreferences.mm:
(-[WKPreferences _inactiveMediaCaptureSteamRepromptIntervalInMinutes]): Ditto.
(-[WKPreferences _setInactiveMediaCaptureSteamRepromptIntervalInMinutes:]): Ditto.
* UIProcess/API/Cocoa/WKPreferencesPrivate.h:

* UIProcess/UserMediaPermissionRequestManagerProxy.cpp:
(WebKit::UserMediaPermissionRequestManagerProxy::UserMediaPermissionRequestManagerProxy): Initialize
the timer.
(WebKit::UserMediaPermissionRequestManagerProxy::createRequest): Remove unneeded namespace.
(WebKit::toWebCore): Remove unneeded breaks.
(WebKit::UserMediaPermissionRequestManagerProxy::searchForGrantedRequest const): Remove unneeded namespace.
(WebKit::UserMediaPermissionRequestManagerProxy::wasRequestDenied): Ditto.
(WebKit::UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame): Ditto.
(WebKit::UserMediaPermissionRequestManagerProxy::getUserMediaPermissionInfo): Ditto.
(WebKit::UserMediaPermissionRequestManagerProxy::enumerateMediaDevicesForFrame): Ditto.
(WebKit::UserMediaPermissionRequestManagerProxy::syncWithWebCorePrefs const): Ditto.
(WebKit::UserMediaPermissionRequestManagerProxy::captureStateChanged): Set the watchdog timer
to the correct interval based on capture state.
(WebKit::UserMediaPermissionRequestManagerProxy::watchdogTimerFired): Clear cached state.
* UIProcess/UserMediaPermissionRequestManagerProxy.h:

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: Add new test

* TestWebKitAPI/Tests/WebKit/GetUserMediaReprompt.mm: Added.
(-[GetUserMediaRepromptUIDelegate _webView:requestUserMediaAuthorizationForDevices:url:mainFrameURL:decisionHandler:]):
(-[GetUserMediaRepromptUIDelegate _webView:checkUserMediaPermissionForURL:mainFrameURL:frameIdentifier:decisionHandler:]):
(TestWebKitAPI::TEST):

* TestWebKitAPI/Tests/WebKit/getUserMedia.html:

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

13 files changed:
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/Shared/WebPreferencesDefinitionsBase.h
Source/WebKit/UIProcess/API/C/WKPreferences.cpp
Source/WebKit/UIProcess/API/C/WKPreferencesRefPrivate.h
Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm
Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h
Source/WebKit/UIProcess/UserMediaPermissionRequestManagerProxy.cpp
Source/WebKit/UIProcess/UserMediaPermissionRequestManagerProxy.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKit/GetUserMediaReprompt.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKit/getUserMedia.html

index 81da9c5..5e3c33a 100644 (file)
@@ -1,3 +1,41 @@
+2017-10-25  Eric Carlson  <eric.carlson@apple.com>
+
+        [MediaStream] Clear cached gUM prompt state
+        https://bugs.webkit.org/show_bug.cgi?id=178754
+        <rdar://problem/32742356>
+
+        Reviewed by Youenn Fablet.
+
+        * Shared/WebPreferences.yaml: Define new settings.
+
+        * Shared/WebPreferencesDefinitionsBase.h: New default values.
+
+        * UIProcess/API/C/WKPreferences.cpp:
+        (WKPreferencesSetInactiveMediaCaptureSteamRepromptIntervalInMinutes): New.
+        (WKPreferencesGetInactiveMediaCaptureSteamRepromptIntervalInMinutes): Ditto.
+        * UIProcess/API/C/WKPreferencesRefPrivate.h:
+
+        * UIProcess/API/Cocoa/WKPreferences.mm:
+        (-[WKPreferences _inactiveMediaCaptureSteamRepromptIntervalInMinutes]): Ditto.
+        (-[WKPreferences _setInactiveMediaCaptureSteamRepromptIntervalInMinutes:]): Ditto.
+        * UIProcess/API/Cocoa/WKPreferencesPrivate.h:
+
+        * UIProcess/UserMediaPermissionRequestManagerProxy.cpp:
+        (WebKit::UserMediaPermissionRequestManagerProxy::UserMediaPermissionRequestManagerProxy): Initialize
+        the timer.
+        (WebKit::UserMediaPermissionRequestManagerProxy::createRequest): Remove unneeded namespace.
+        (WebKit::toWebCore): Remove unneeded breaks.
+        (WebKit::UserMediaPermissionRequestManagerProxy::searchForGrantedRequest const): Remove unneeded namespace.
+        (WebKit::UserMediaPermissionRequestManagerProxy::wasRequestDenied): Ditto.
+        (WebKit::UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame): Ditto.
+        (WebKit::UserMediaPermissionRequestManagerProxy::getUserMediaPermissionInfo): Ditto.
+        (WebKit::UserMediaPermissionRequestManagerProxy::enumerateMediaDevicesForFrame): Ditto.
+        (WebKit::UserMediaPermissionRequestManagerProxy::syncWithWebCorePrefs const): Ditto.
+        (WebKit::UserMediaPermissionRequestManagerProxy::captureStateChanged): Set the watchdog timer
+        to the correct interval based on capture state.
+        (WebKit::UserMediaPermissionRequestManagerProxy::watchdogTimerFired): Clear cached state.
+        * UIProcess/UserMediaPermissionRequestManagerProxy.h:
+
 2017-10-25  Simon Fraser  <simon.fraser@apple.com>
 
         When navigating back to a page, compositing layers may not use accelerated drawing
index 9e544b0..d0e08b9 100644 (file)
@@ -822,6 +822,13 @@ InspectorWindowFrame:
   defaultValue: '""'
   webkitOnly: true
 
+InactiveMediaCaptureSteamRepromptIntervalInMinutes:
+    type: double
+    defaultValue: DEFAULT_INTERACTIVE_MNEDIA_CAPTURE_STREAM_REPROMPT_INTERVAL_IN_MINUTES
+
+LongRunningMediaCaptureStreamRepromptIntervalInHours:
+    type: double
+    defaultValue: 24
 
 # Debug Preferences
 
index 312f060..0bdbcdd 100644 (file)
@@ -75,6 +75,7 @@
 #define DEFAULT_TEMPORARY_TILE_COHORT_RETENTION_ENABLED false
 #define DEFAULT_REQUIRES_USER_GESTURE_FOR_AUDIO_PLAYBACK true
 #define DEFAULT_LEGACY_ENCRYPTED_MEDIA_API_ENABLED false
+#define DEFAULT_INTERACTIVE_MNEDIA_CAPTURE_STREAM_REPROMPT_INTERVAL_IN_MINUTES 1
 #else
 #define DEFAULT_ALLOWS_PICTURE_IN_PICTURE_MEDIA_PLAYBACK false
 #define DEFAULT_BACKSPACE_KEY_NAVIGATION_ENABLED true
@@ -93,6 +94,7 @@
 #define DEFAULT_TEMPORARY_TILE_COHORT_RETENTION_ENABLED true
 #define DEFAULT_REQUIRES_USER_GESTURE_FOR_AUDIO_PLAYBACK false
 #define DEFAULT_LEGACY_ENCRYPTED_MEDIA_API_ENABLED true
+#define DEFAULT_INTERACTIVE_MNEDIA_CAPTURE_STREAM_REPROMPT_INTERVAL_IN_MINUTES 10
 #endif
 
 #if PLATFORM(COCOA)
index 7a0a3fc..ecc30fa 100644 (file)
@@ -1650,6 +1650,16 @@ bool WKPreferencesGetMediaCaptureRequiresSecureConnection(WKPreferencesRef prefe
     return toImpl(preferencesRef)->mediaCaptureRequiresSecureConnection();
 }
 
+void WKPreferencesSetInactiveMediaCaptureSteamRepromptIntervalInMinutes(WKPreferencesRef preferencesRef, double interval)
+{
+    toImpl(preferencesRef)->setInactiveMediaCaptureSteamRepromptIntervalInMinutes(interval);
+}
+
+double WKPreferencesGetInactiveMediaCaptureSteamRepromptIntervalInMinutes(WKPreferencesRef preferencesRef)
+{
+    return toImpl(preferencesRef)->inactiveMediaCaptureSteamRepromptIntervalInMinutes();
+}
+
 void WKPreferencesSetFetchAPIEnabled(WKPreferencesRef preferencesRef, bool flag)
 {
     toImpl(preferencesRef)->setFetchAPIEnabled(flag);
index 2677930..f20bb1e 100644 (file)
@@ -441,6 +441,10 @@ WK_EXPORT bool WKPreferencesGetEnumeratingAllNetworkInterfacesEnabled(WKPreferen
 WK_EXPORT void WKPreferencesSetMediaCaptureRequiresSecureConnection(WKPreferencesRef, bool);
 WK_EXPORT bool WKPreferencesGetMediaCaptureRequiresSecureConnection(WKPreferencesRef);
 
+// Defaults to 1 minute on iOS, 10 minutes elsewhere
+WK_EXPORT void WKPreferencesSetInactiveMediaCaptureSteamRepromptIntervalInMinutes(WKPreferencesRef, double);
+WK_EXPORT double WKPreferencesGetInactiveMediaCaptureSteamRepromptIntervalInMinutes(WKPreferencesRef);
+
 // Defaults to false
 WK_EXPORT void WKPreferencesSetFetchAPIEnabled(WKPreferencesRef, bool flag);
 WK_EXPORT bool WKPreferencesGetFetchAPIEnabled(WKPreferencesRef);
index 80c7742..b876b56 100644 (file)
@@ -612,6 +612,16 @@ static _WKStorageBlockingPolicy toAPI(WebCore::SecurityOrigin::StorageBlockingPo
     _preferences->setMediaCaptureRequiresSecureConnection(requiresSecureConnection);
 }
 
+- (double)_inactiveMediaCaptureSteamRepromptIntervalInMinutes
+{
+    return _preferences->inactiveMediaCaptureSteamRepromptIntervalInMinutes();
+}
+
+- (void)_setInactiveMediaCaptureSteamRepromptIntervalInMinutes:(double)interval
+{
+    _preferences->setInactiveMediaCaptureSteamRepromptIntervalInMinutes(interval);
+}
+
 - (BOOL)_enumeratingAllNetworkInterfacesEnabled
 {
     return _preferences->enumeratingAllNetworkInterfacesEnabled();
index 2e1a2b1..2f88b9d 100644 (file)
@@ -110,6 +110,7 @@ typedef NS_OPTIONS(NSUInteger, _WKJavaScriptRuntimeFlags) {
 @property (nonatomic, setter=_setEnumeratingAllNetworkInterfacesEnabled:) BOOL _enumeratingAllNetworkInterfacesEnabled WK_API_AVAILABLE(macosx(10.13), ios(11.0));
 @property (nonatomic, setter=_setICECandidateFilteringEnabled:) BOOL _iceCandidateFilteringEnabled WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 @property (nonatomic, setter=_setWebRTCLegacyAPIEnabled:) BOOL _webRTCLegacyAPIEnabled WK_API_AVAILABLE(macosx(10.13), ios(11.0));
+@property (nonatomic, setter=_setInactiveMediaCaptureSteamRepromptIntervalInMinutes:) double _inactiveMediaCaptureSteamRepromptIntervalInMinutes WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @property (nonatomic, setter=_setJavaScriptCanAccessClipboard:) BOOL _javaScriptCanAccessClipboard WK_API_AVAILABLE(macosx(10.13), ios(11.0));
 @property (nonatomic, setter=_setDOMPasteAllowed:) BOOL _domPasteAllowed WK_API_AVAILABLE(macosx(10.13), ios(11.0));
index 10696d2..b460905 100644 (file)
 #include <WebCore/SecurityOriginData.h>
 #include <WebCore/UserMediaRequest.h>
 
-#if ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION)
-#include <WebCore/RealtimeMediaSourceCenterMac.h>
-#endif
-
 using namespace WebCore;
 
 namespace WebKit {
 
+static const MediaProducer::MediaStateFlags activeCaptureMask = MediaProducer::HasActiveAudioCaptureDevice | MediaProducer::HasActiveVideoCaptureDevice;
+
 UserMediaPermissionRequestManagerProxy::UserMediaPermissionRequestManagerProxy(WebPageProxy& page)
     : m_page(page)
     , m_rejectionTimer(RunLoop::main(), this, &UserMediaPermissionRequestManagerProxy::rejectionTimerFired)
+    , m_watchdogTimer(RunLoop::main(), this, &UserMediaPermissionRequestManagerProxy::watchdogTimerFired)
 {
 #if ENABLE(MEDIA_STREAM)
     UserMediaProcessManager::singleton().addUserMediaPermissionRequestManagerProxy(*this);
@@ -81,7 +80,7 @@ void UserMediaPermissionRequestManagerProxy::clearCachedState()
     invalidatePendingRequests();
 }
 
-Ref<UserMediaPermissionRequestProxy> UserMediaPermissionRequestManagerProxy::createRequest(uint64_t userMediaID, uint64_t mainFrameID, uint64_t frameID, Ref<WebCore::SecurityOrigin>&& userMediaDocumentOrigin, Ref<WebCore::SecurityOrigin>&& topLevelDocumentOrigin, Vector<String>&& audioDeviceUIDs, Vector<String>&& videoDeviceUIDs, String&& deviceIDHashSalt)
+Ref<UserMediaPermissionRequestProxy> UserMediaPermissionRequestManagerProxy::createRequest(uint64_t userMediaID, uint64_t mainFrameID, uint64_t frameID, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin, Vector<String>&& audioDeviceUIDs, Vector<String>&& videoDeviceUIDs, String&& deviceIDHashSalt)
 {
     auto request = UserMediaPermissionRequestProxy::create(*this, userMediaID, mainFrameID, frameID, WTFMove(userMediaDocumentOrigin), WTFMove(topLevelDocumentOrigin), WTFMove(audioDeviceUIDs), WTFMove(videoDeviceUIDs), WTFMove(deviceIDHashSalt));
     m_pendingUserMediaRequests.add(userMediaID, request.ptr());
@@ -94,25 +93,18 @@ static uint64_t toWebCore(UserMediaPermissionRequestProxy::UserMediaAccessDenial
     switch (reason) {
     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::NoConstraints:
         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::NoConstraints);
-        break;
     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::UserMediaDisabled:
         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::UserMediaDisabled);
-        break;
     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::NoCaptureDevices:
         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::NoCaptureDevices);
-        break;
     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::InvalidConstraint:
         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::InvalidConstraint);
-        break;
     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::HardwareError:
         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::HardwareError);
-        break;
     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::PermissionDenied:
         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::PermissionDenied);
-        break;
     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::OtherFailure:
         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::OtherFailure);
-        break;
     }
 
     ASSERT_NOT_REACHED();
@@ -178,7 +170,7 @@ void UserMediaPermissionRequestManagerProxy::resetAccess(uint64_t frameID)
     m_deniedRequests.clear();
 }
 
-const UserMediaPermissionRequestProxy* UserMediaPermissionRequestManagerProxy::searchForGrantedRequest(uint64_t frameID, const WebCore::SecurityOrigin& userMediaDocumentOrigin, const WebCore::SecurityOrigin& topLevelDocumentOrigin, bool needsAudio, bool needsVideo) const
+const UserMediaPermissionRequestProxy* UserMediaPermissionRequestManagerProxy::searchForGrantedRequest(uint64_t frameID, const SecurityOrigin& userMediaDocumentOrigin, const SecurityOrigin& topLevelDocumentOrigin, bool needsAudio, bool needsVideo) const
 {
     if (m_page.isMediaStreamCaptureMuted())
         return nullptr;
@@ -207,7 +199,7 @@ const UserMediaPermissionRequestProxy* UserMediaPermissionRequestManagerProxy::s
     return nullptr;
 }
 
-bool UserMediaPermissionRequestManagerProxy::wasRequestDenied(uint64_t mainFrameID, const WebCore::SecurityOrigin& userMediaDocumentOrigin, const WebCore::SecurityOrigin& topLevelDocumentOrigin, bool needsAudio, bool needsVideo)
+bool UserMediaPermissionRequestManagerProxy::wasRequestDenied(uint64_t mainFrameID, const SecurityOrigin& userMediaDocumentOrigin, const SecurityOrigin& topLevelDocumentOrigin, bool needsAudio, bool needsVideo)
 {
     for (const auto& deniedRequest : m_deniedRequests) {
         if (!deniedRequest.userMediaDocumentOrigin->isSameSchemeHostPort(userMediaDocumentOrigin))
@@ -253,7 +245,7 @@ static inline void allowRequest(UserMediaPermissionRequestProxy& request)
     request.allow(request.audioDeviceUIDs().isEmpty() ? String() : request.audioDeviceUIDs()[0], request.videoDeviceUIDs().isEmpty() ? String() : request.videoDeviceUIDs()[0]);
 }
 
-void UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame(uint64_t userMediaID, uint64_t frameID, Ref<WebCore::SecurityOrigin>&& userMediaDocumentOrigin, Ref<WebCore::SecurityOrigin>&& topLevelDocumentOrigin, const WebCore::MediaConstraints& audioConstraints, const WebCore::MediaConstraints& videoConstraints)
+void UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame(uint64_t userMediaID, uint64_t frameID, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin, const MediaConstraints& audioConstraints, const MediaConstraints& videoConstraints)
 {
 #if ENABLE(MEDIA_STREAM)
     if (!UserMediaProcessManager::singleton().captureEnabled()) {
@@ -262,14 +254,14 @@ void UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame(
         return;
     }
 
-    WebCore::RealtimeMediaSourceCenter::InvalidConstraintsHandler invalidHandler = [this, userMediaID](const String& invalidConstraint) {
+    RealtimeMediaSourceCenter::InvalidConstraintsHandler invalidHandler = [this, userMediaID](const String& invalidConstraint) {
         if (!m_page.isValid())
             return;
 
         denyRequest(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::InvalidConstraint, invalidConstraint);
     };
 
-    WebCore::RealtimeMediaSourceCenter::ValidConstraintsHandler validHandler = [this, userMediaID, frameID, userMediaDocumentOrigin = userMediaDocumentOrigin.copyRef(), topLevelDocumentOrigin = topLevelDocumentOrigin.copyRef()](Vector<String>&& audioDeviceUIDs, Vector<String>&& videoDeviceUIDs, String&& deviceIdentifierHashSalt) mutable {
+    RealtimeMediaSourceCenter::ValidConstraintsHandler validHandler = [this, userMediaID, frameID, userMediaDocumentOrigin = userMediaDocumentOrigin.copyRef(), topLevelDocumentOrigin = topLevelDocumentOrigin.copyRef()](Vector<String>&& audioDeviceUIDs, Vector<String>&& videoDeviceUIDs, String&& deviceIdentifierHashSalt) mutable {
         if (!m_page.isValid() || !m_page.mainFrame())
             return;
 
@@ -318,7 +310,7 @@ void UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame(
             userMediaAccessWasDenied(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::UserMediaDisabled);
     };
 
-    auto haveDeviceSaltHandler = [this, validHandler = WTFMove(validHandler), invalidHandler = WTFMove(invalidHandler), audioConstraints = WebCore::MediaConstraints(audioConstraints), videoConstraints = WebCore::MediaConstraints(videoConstraints)](uint64_t userMediaID, String&& deviceIdentifierHashSalt, bool originHasPersistentAccess) mutable {
+    auto haveDeviceSaltHandler = [this, validHandler = WTFMove(validHandler), invalidHandler = WTFMove(invalidHandler), audioConstraints = MediaConstraints(audioConstraints), videoConstraints = MediaConstraints(videoConstraints)](uint64_t userMediaID, String&& deviceIdentifierHashSalt, bool originHasPersistentAccess) mutable {
 
         auto request = m_pendingDeviceRequests.take(userMediaID);
         if (!request)
@@ -347,7 +339,7 @@ void UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame(
 }
 
 #if ENABLE(MEDIA_STREAM)
-void UserMediaPermissionRequestManagerProxy::getUserMediaPermissionInfo(uint64_t userMediaID, uint64_t frameID, UserMediaPermissionCheckProxy::CompletionHandler&& handler, Ref<WebCore::SecurityOrigin>&& userMediaDocumentOrigin, Ref<WebCore::SecurityOrigin>&& topLevelDocumentOrigin)
+void UserMediaPermissionRequestManagerProxy::getUserMediaPermissionInfo(uint64_t userMediaID, uint64_t frameID, UserMediaPermissionCheckProxy::CompletionHandler&& handler, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin)
 {
     auto userMediaOrigin = API::SecurityOrigin::create(userMediaDocumentOrigin.get());
     auto topLevelOrigin = API::SecurityOrigin::create(topLevelDocumentOrigin.get());
@@ -360,7 +352,7 @@ void UserMediaPermissionRequestManagerProxy::getUserMediaPermissionInfo(uint64_t
 } 
 #endif
 
-void UserMediaPermissionRequestManagerProxy::enumerateMediaDevicesForFrame(uint64_t userMediaID, uint64_t frameID, Ref<WebCore::SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin)
+void UserMediaPermissionRequestManagerProxy::enumerateMediaDevicesForFrame(uint64_t userMediaID, uint64_t frameID, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin)
 {
 #if ENABLE(MEDIA_STREAM)
     auto completionHandler = [this](uint64_t userMediaID, String&& deviceIdentifierHashSalt, bool originHasPersistentAccess) {
@@ -391,25 +383,42 @@ void UserMediaPermissionRequestManagerProxy::syncWithWebCorePrefs() const
     // Enable/disable the mock capture devices for the UI process as per the WebCore preferences. Note that
     // this is a noop if the preference hasn't changed since the last time this was called.
     bool mockDevicesEnabled = m_page.preferences().mockCaptureDevicesEnabled();
-    WebCore::MockRealtimeMediaSourceCenter::setMockRealtimeMediaSourceCenterEnabled(mockDevicesEnabled);
+    MockRealtimeMediaSourceCenter::setMockRealtimeMediaSourceCenterEnabled(mockDevicesEnabled);
 #endif
 }
 
-void UserMediaPermissionRequestManagerProxy::captureStateChanged(WebCore::MediaProducer::MediaStateFlags oldState, WebCore::MediaProducer::MediaStateFlags newState)
+void UserMediaPermissionRequestManagerProxy::captureStateChanged(MediaProducer::MediaStateFlags oldState, MediaProducer::MediaStateFlags newState)
 {
     if (!m_page.isValid())
         return;
 
 #if ENABLE(MEDIA_STREAM)
-    bool wasCapturingAudio = oldState & WebCore::MediaProducer::AudioCaptureMask;
-    bool wasCapturingVideo = oldState & WebCore::MediaProducer::VideoCaptureMask;
-    bool isCapturingAudio = newState & WebCore::MediaProducer::AudioCaptureMask;
-    bool isCapturingVideo = newState & WebCore::MediaProducer::VideoCaptureMask;
+    bool wasCapturingAudio = oldState & MediaProducer::AudioCaptureMask;
+    bool wasCapturingVideo = oldState & MediaProducer::VideoCaptureMask;
+    bool isCapturingAudio = newState & MediaProducer::AudioCaptureMask;
+    bool isCapturingVideo = newState & MediaProducer::VideoCaptureMask;
 
     if ((wasCapturingAudio && !isCapturingAudio) || (wasCapturingVideo && !isCapturingVideo))
         UserMediaProcessManager::singleton().endedCaptureSession(*this);
     if ((!wasCapturingAudio && isCapturingAudio) || (!wasCapturingVideo && isCapturingVideo))
         UserMediaProcessManager::singleton().startedCaptureSession(*this);
+
+    if (m_captureState == (newState & activeCaptureMask))
+        return;
+
+    m_captureState = newState & activeCaptureMask;
+
+    Seconds interval;
+    if (m_captureState & activeCaptureMask)
+        interval = Seconds::fromHours(m_page.preferences().longRunningMediaCaptureStreamRepromptIntervalInHours());
+    else
+        interval = Seconds::fromMinutes(m_page.preferences().inactiveMediaCaptureSteamRepromptIntervalInMinutes());
+
+    if (interval == m_currentWatchdogInterval)
+        return;
+
+    m_currentWatchdogInterval = interval;
+    m_watchdogTimer.startOneShot(m_currentWatchdogInterval);
 #endif
 }
 
@@ -420,4 +429,11 @@ void UserMediaPermissionRequestManagerProxy::processPregrantedRequests()
     m_pregrantedRequests.clear();
 }
 
+void UserMediaPermissionRequestManagerProxy::watchdogTimerFired()
+{
+    m_grantedRequests.clear();
+    m_pregrantedRequests.clear();
+    m_currentWatchdogInterval = 0_s;
+}
+
 } // namespace WebKit
index 3e1a624..dfbfe2f 100644 (file)
@@ -75,6 +75,7 @@ private:
     void getUserMediaPermissionInfo(uint64_t userMediaID, uint64_t frameID, UserMediaPermissionCheckProxy::CompletionHandler&&, Ref<WebCore::SecurityOrigin>&& userMediaDocumentOrigin, Ref<WebCore::SecurityOrigin>&& topLevelDocumentOrigin);
 
     void syncWithWebCorePrefs() const;
+    void watchdogTimerFired();
 
     HashMap<uint64_t, RefPtr<UserMediaPermissionRequestProxy>> m_pendingUserMediaRequests;
     HashMap<uint64_t, Ref<UserMediaPermissionCheckProxy>> m_pendingDeviceRequests;
@@ -95,6 +96,10 @@ private:
         bool isVideoDenied;
     };
     Vector<DeniedRequest> m_deniedRequests;
+
+    WebCore::MediaProducer::MediaStateFlags m_captureState { WebCore::MediaProducer::IsNotPlaying };
+    RunLoop::Timer<UserMediaPermissionRequestManagerProxy> m_watchdogTimer;
+    Seconds m_currentWatchdogInterval;
 };
 
 } // namespace WebKit
index fcab9b8..876f192 100644 (file)
@@ -1,3 +1,20 @@
+2017-10-25  Eric Carlson  <eric.carlson@apple.com>
+
+        [MediaStream] Clear cached gUM prompt state
+        https://bugs.webkit.org/show_bug.cgi?id=178754
+        <rdar://problem/32742356>
+
+        Reviewed by Youenn Fablet.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: Add new test
+
+        * TestWebKitAPI/Tests/WebKit/GetUserMediaReprompt.mm: Added.
+        (-[GetUserMediaRepromptUIDelegate _webView:requestUserMediaAuthorizationForDevices:url:mainFrameURL:decisionHandler:]):
+        (-[GetUserMediaRepromptUIDelegate _webView:checkUserMediaPermissionForURL:mainFrameURL:frameIdentifier:decisionHandler:]):
+        (TestWebKitAPI::TEST):
+
+        * TestWebKitAPI/Tests/WebKit/getUserMedia.html:
+
 2017-10-25  Ross Kirsling  <ross.kirsling@sony.com>
 
         Add committer status for Ross Kirsling
index 23e8eff..930cf79 100644 (file)
@@ -30,6 +30,7 @@
                0799C34B1EBA3301003B7532 /* disableGetUserMedia.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 0799C34A1EBA32F4003B7532 /* disableGetUserMedia.html */; };
                07C046CA1E4262A8007201E7 /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07C046C91E42573E007201E7 /* CARingBuffer.cpp */; };
                07CE1CF31F06A7E000BF89F5 /* GetUserMediaNavigation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07CE1CF21F06A7E000BF89F5 /* GetUserMediaNavigation.mm */; };
+               07E499911F9E56DF002F1EF3 /* GetUserMediaReprompt.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07E499901F9E56A1002F1EF3 /* GetUserMediaReprompt.mm */; };
                0F139E771A423A5B00F590F5 /* WeakObjCPtr.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0F139E751A423A5300F590F5 /* WeakObjCPtr.mm */; };
                0F139E781A423A6B00F590F5 /* PlatformUtilitiesCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0F139E721A423A2B00F590F5 /* PlatformUtilitiesCocoa.mm */; };
                0F139E791A42457000F590F5 /* PlatformUtilitiesCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0F139E721A423A2B00F590F5 /* PlatformUtilitiesCocoa.mm */; };
                0799C34A1EBA32F4003B7532 /* disableGetUserMedia.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = disableGetUserMedia.html; sourceTree = "<group>"; };
                07C046C91E42573E007201E7 /* CARingBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CARingBuffer.cpp; sourceTree = "<group>"; };
                07CE1CF21F06A7E000BF89F5 /* GetUserMediaNavigation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GetUserMediaNavigation.mm; sourceTree = "<group>"; };
+               07E499901F9E56A1002F1EF3 /* GetUserMediaReprompt.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GetUserMediaReprompt.mm; sourceTree = "<group>"; };
                07EDEFAC1EB9400C00D43292 /* UserMediaDisabled.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = UserMediaDisabled.mm; sourceTree = "<group>"; };
                0BCD833414857CE400EA2003 /* HashMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HashMap.cpp; sourceTree = "<group>"; };
                0BCD85691485C98B00EA2003 /* SetForScope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SetForScope.cpp; sourceTree = "<group>"; };
                BC9096411255616000083756 /* WebKit */ = {
                        isa = PBXGroup;
                        children = (
+                               07E499901F9E56A1002F1EF3 /* GetUserMediaReprompt.mm */,
                                0F139E741A423A4600F590F5 /* cocoa */,
                                C0C5D3BB14598B6F00A802A6 /* mac */,
                                BC90977B125571AE00083756 /* Resources */,
                                46397B951DC2C850009A78AE /* DOMNode.mm in Sources */,
                                7CCE7EBC1A411A7E00447C4C /* DOMNodeFromJSObject.mm in Sources */,
                                7CCE7EBD1A411A7E00447C4C /* DOMRangeOfString.mm in Sources */,
+                               07E499911F9E56DF002F1EF3 /* GetUserMediaReprompt.mm in Sources */,
                                7CCE7EEC1A411AE600447C4C /* DOMWindowExtensionBasic.cpp in Sources */,
                                7CCE7EED1A411AE600447C4C /* DOMWindowExtensionNoCache.cpp in Sources */,
                                7CCE7F231A411AF600447C4C /* Download.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/GetUserMediaReprompt.mm b/Tools/TestWebKitAPI/Tests/WebKit/GetUserMediaReprompt.mm
new file mode 100644 (file)
index 0000000..a612666
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 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 WK_API_ENABLED
+
+#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>
+
+static bool wasPrompted = false;
+
+@interface GetUserMediaRepromptUIDelegate : NSObject<WKUIDelegate>
+- (void)_webView:(WKWebView *)webView requestUserMediaAuthorizationForDevices:(_WKCaptureDevices)devices url:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL decisionHandler:(void (^)(BOOL authorized))decisionHandler;
+- (void)_webView:(WKWebView *)webView checkUserMediaPermissionForURL:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL frameIdentifier:(NSUInteger)frameIdentifier decisionHandler:(void (^)(NSString *salt, BOOL authorized))decisionHandler;
+@end
+
+@implementation GetUserMediaRepromptUIDelegate
+- (void)_webView:(WKWebView *)webView requestUserMediaAuthorizationForDevices:(_WKCaptureDevices)devices url:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL decisionHandler:(void (^)(BOOL authorized))decisionHandler
+{
+    wasPrompted = true;
+    decisionHandler(YES);
+}
+
+- (void)_webView:(WKWebView *)webView checkUserMediaPermissionForURL:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL frameIdentifier:(NSUInteger)frameIdentifier decisionHandler:(void (^)(NSString *salt, BOOL authorized))decisionHandler
+{
+    decisionHandler(@"0x987654321", YES);
+}
+@end
+
+namespace TestWebKitAPI {
+
+TEST(WebKit2, GetUserMediaReprompt)
+{
+    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([[GetUserMediaRepromptUIDelegate alloc] init]);
+    webView.UIDelegate = delegate.get();
+
+    wasPrompted = false;
+    [webView loadTestPageNamed:@"getUserMedia"];
+    TestWebKitAPI::Util::run(&wasPrompted);
+
+    auto result = [webView stringByEvaluatingJavaScript:@"haveStream()"];
+    EXPECT_TRUE([result boolValue]);
+
+    [webView stringByEvaluatingJavaScript:@"stop()"];
+    result = [webView stringByEvaluatingJavaScript:@"haveStream()"];
+    EXPECT_FALSE([result boolValue]);
+
+    wasPrompted = false;
+    [webView stringByEvaluatingJavaScript:@"promptForCapture()"];
+    result = [webView stringByEvaluatingJavaScript:@"haveStream()"];
+    EXPECT_TRUE([result boolValue]);
+    EXPECT_FALSE(wasPrompted);
+
+    preferences._inactiveMediaCaptureSteamRepromptIntervalInMinutes = .5 / 60;
+    [webView stringByEvaluatingJavaScript:@"stop()"];
+    result = [webView stringByEvaluatingJavaScript:@"haveStream()"];
+    EXPECT_FALSE([result boolValue]);
+
+    // Sleep long enough for the reprompt timer to fire and clear cached state.
+    Util::sleep(1);
+
+    wasPrompted = false;
+    [webView stringByEvaluatingJavaScript:@"promptForCapture()"];
+    TestWebKitAPI::Util::run(&wasPrompted);
+    result = [webView stringByEvaluatingJavaScript:@"haveStream()"];
+    EXPECT_TRUE([result boolValue]);
+}
+
+} // namespace TestWebKitAPI
+
+#endif // ENABLE(MEDIA_STREAM)
+
+#endif // WK_API_ENABLED
index 4327ea9..73f71b9 100644 (file)
@@ -1,16 +1,48 @@
-<script>
-    function gotUserMedia(mediaStream)
-    {
-        console.log("Got user media");
-    }
+<!DOCTYPE html>
+<html>
+    <head>
+        <script>
 
-    function userMediaError(error)
-    {
-        console.log(error);
-    }
+            let stream = null;
 
-    var constraints = { audio: false, video: true};
-    navigator.mediaDevices.getUserMedia(constraints)
-        .then(gotUserMedia).
-        catch(userMediaError);
-</script>
+            function promptForCapture()
+            {
+                navigator.mediaDevices.getUserMedia({ audio: false, video: true })
+                .then((s) => {
+                      stream = s;
+                      video.srcObject = stream;
+                      console.log("Got user media");
+                })
+                .catch((error) => console.log(`Failed with error: ${error}`));
+            }
+
+            function stop(kind)
+            {
+                let activeTracks = [];
+                stream.getTracks().forEach(track => {
+                    if (!kind || track.kind == kind)
+                        track.stop();
+                    else
+                        activeTracks.push(track);
+                });
+
+                if (!activeTracks.length) {
+                    stream = null;
+                    video.srcObject = null;
+                }
+            }
+
+            function haveStream()
+            {
+                return stream !== null;
+            }
+        </script>
+    <head>
+
+    <body onload="promptForCapture()">
+        <video id="video" controls></video>
+        <p>
+        <button onclick="stop()">Stop</button>
+        </p>
+    </body>
+</html>