Changeset 267081 in webkit


Ignore:
Timestamp:
Sep 15, 2020 1:56:47 AM (4 years ago)
Author:
youenn@apple.com
Message:

End of media capture should not be reported before 3 seconds of the start of capture
https://bugs.webkit.org/show_bug.cgi?id=216415
Source/WebCore:

Reviewed by Eric Carlson.

Add a timer that allows taking a function and a delay as parameter.
Covered by added API test.

  • platform/Timer.h:

(WebCore::DeferrableTaskTimer::fired):
(WebCore::DeferrableTaskTimer::doTask):
(WebCore::DeferrableTaskTimer::cancel):

Source/WebKit:

<rdar://problem/68512358>

Reviewed by Eric Carlson.

Add support for delaying of end of capture notification to the application.
This allows to ensure that a capture indicator stays for long enough for the user to notice it.
A capture indicator should be visible to the user for at least three seconds.

A timer is scheduled when starting a capture and there is no ongoing capture.
As long as the timer is active, the capture state cannot be transitioned to no capture.
Other transitions are allowed.
Once the timer kicks in, any capture state update is done synchronously.
Ensure to update the capture state when the timer kicks in.

Note that even navigations will not allow transitioning the capture state sooner.
This is done to ensure a page does not try to capture one frame before navigating to another page.
In practice, very few pages should navigate quickly after starting capture.

  • UIProcess/API/C/WKPage.cpp:

(WKPageGetMediaState):
(WKPageSetMediaCaptureReportingDelayForTesting):

  • UIProcess/API/C/WKPagePrivate.h:
  • UIProcess/API/Cocoa/WKWebView.mm:

(-[WKWebView _mediaCaptureState]):

  • UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h:
  • UIProcess/API/Cocoa/WKWebViewTesting.mm:

(-[WKWebView _mediaCaptureReportingDelayForTesting]):
(-[WKWebView _setMediaCaptureReportingDelayForTesting:]):

  • UIProcess/WebPageProxy.cpp:

(WebKit::WebPageProxy::updatePlayingMediaDidChange):
(WebKit::WebPageProxy::updateReportedMediaCaptureState):

  • UIProcess/WebPageProxy.h:

(WebKit::WebPageProxy::reportedMediaCaptureState const):
(WebKit::WebPageProxy::mediaCaptureReportingDelay const):
(WebKit::WebPageProxy::setMediaCaptureReportingDelay):

Tools:

Reviewed by Eric Carlson.

  • TestWebKitAPI/Tests/WebKit/GetUserMedia.mm:

(TestWebKitAPI::TEST):

  • TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:

(-[GetUserMediaUIDelegate _webView:mediaCaptureStateDidChange:]):
Add a notCapturing boolean and wait for the notification before going back to the capturing page.
Decrease delay to 1 second to make the test run faster.

  • WebKitTestRunner/TestController.cpp:

(WTR::TestController::resetStateToConsistentValues):
Make sure to set media capture state delay to zero.

Location:
trunk
Files:
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r267077 r267081  
     12020-09-15  Youenn Fablet  <youenn@apple.com>
     2
     3        End of media capture should not be reported before 3 seconds of the start of capture
     4        https://bugs.webkit.org/show_bug.cgi?id=216415
     5
     6        Reviewed by Eric Carlson.
     7
     8        Add a timer that allows taking a function and a delay as parameter.
     9        Covered by added API test.
     10
     11        * platform/Timer.h:
     12        (WebCore::DeferrableTaskTimer::fired):
     13        (WebCore::DeferrableTaskTimer::doTask):
     14        (WebCore::DeferrableTaskTimer::cancel):
     15
    1162020-09-14  Fujii Hironori  <Hironori.Fujii@sony.com>
    217
  • trunk/Source/WebCore/platform/Timer.h

    r244288 r267081  
    196196};
    197197
    198 }
     198class DeferrableTaskTimer final : private TimerBase {
     199    WTF_MAKE_FAST_ALLOCATED;
     200public:
     201    DeferrableTaskTimer() = default;
     202
     203    void doTask(Function<void()>&&, Seconds);
     204    void cancel();
     205    bool isActive() const { return TimerBase::isActive(); }
     206
     207private:
     208    void fired() final;
     209
     210    Function<void()> m_function;
     211};
     212
     213inline void DeferrableTaskTimer::fired()
     214{
     215    std::exchange(m_function, { })();
     216}
     217
     218inline void DeferrableTaskTimer::doTask(Function<void()>&& function, Seconds delay)
     219{
     220    ASSERT(!isActive());
     221    ASSERT(!m_function);
     222    m_function = WTFMove(function);
     223    startOneShot(delay);
     224}
     225
     226inline void DeferrableTaskTimer::cancel()
     227{
     228    std::exchange(m_function, { });
     229    stop();
     230}
     231
     232}
  • trunk/Source/WebKit/ChangeLog

    r267079 r267081  
     12020-09-15  Youenn Fablet  <youenn@apple.com>
     2
     3        End of media capture should not be reported before 3 seconds of the start of capture
     4        https://bugs.webkit.org/show_bug.cgi?id=216415
     5        <rdar://problem/68512358>
     6
     7        Reviewed by Eric Carlson.
     8
     9        Add support for delaying of end of capture notification to the application.
     10        This allows to ensure that a capture indicator stays for long enough for the user to notice it.
     11        A capture indicator should be visible to the user for at least three seconds.
     12
     13        A timer is scheduled when starting a capture and there is no ongoing capture.
     14        As long as the timer is active, the capture state cannot be transitioned to no capture.
     15        Other transitions are allowed.
     16        Once the timer kicks in, any capture state update is done synchronously.
     17        Ensure to update the capture state when the timer kicks in.
     18
     19        Note that even navigations will not allow transitioning the capture state sooner.
     20        This is done to ensure a page does not try to capture one frame before navigating to another page.
     21        In practice, very few pages should navigate quickly after starting capture.
     22
     23        * UIProcess/API/C/WKPage.cpp:
     24        (WKPageGetMediaState):
     25        (WKPageSetMediaCaptureReportingDelayForTesting):
     26        * UIProcess/API/C/WKPagePrivate.h:
     27        * UIProcess/API/Cocoa/WKWebView.mm:
     28        (-[WKWebView _mediaCaptureState]):
     29        * UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h:
     30        * UIProcess/API/Cocoa/WKWebViewTesting.mm:
     31        (-[WKWebView _mediaCaptureReportingDelayForTesting]):
     32        (-[WKWebView _setMediaCaptureReportingDelayForTesting:]):
     33        * UIProcess/WebPageProxy.cpp:
     34        (WebKit::WebPageProxy::updatePlayingMediaDidChange):
     35        (WebKit::WebPageProxy::updateReportedMediaCaptureState):
     36        * UIProcess/WebPageProxy.h:
     37        (WebKit::WebPageProxy::reportedMediaCaptureState const):
     38        (WebKit::WebPageProxy::mediaCaptureReportingDelay const):
     39        (WebKit::WebPageProxy::setMediaCaptureReportingDelay):
     40
    1412020-09-15  Youenn Fablet  <youenn@apple.com>
    242
  • trunk/Source/WebKit/UIProcess/API/C/WKPage.cpp

    r265916 r267081  
    28632863WKMediaState WKPageGetMediaState(WKPageRef page)
    28642864{
    2865     WebCore::MediaProducer::MediaStateFlags coreState = toImpl(page)->mediaStateFlags();
     2865    WebCore::MediaProducer::MediaStateFlags coreState = toImpl(page)->reportedMediaCaptureState();
    28662866    WKMediaState state = kWKMediaIsNotPlaying;
    28672867
     
    29982998#endif
    29992999}
     3000
     3001void WKPageSetMediaCaptureReportingDelayForTesting(WKPageRef page, double delay)
     3002{
     3003    toImpl(page)->setMediaCaptureReportingDelay(Seconds(delay));
     3004}
  • trunk/Source/WebKit/UIProcess/API/C/WKPagePrivate.h

    r265916 r267081  
    199199WK_EXPORT void WKPageClearLoadedSubresourceDomains(WKPageRef page);
    200200
     201WK_EXPORT void WKPageSetMediaCaptureReportingDelayForTesting(WKPageRef page, double delay);
     202
    201203#ifdef __cplusplus
    202204}
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm

    r266993 r267081  
    29522952- (_WKMediaCaptureState)_mediaCaptureState
    29532953{
    2954     return WebKit::toWKMediaCaptureState(_page->mediaStateFlags());
     2954    return WebKit::toWKMediaCaptureState(_page->reportedMediaCaptureState());
    29552955}
    29562956
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivateForTesting.h

    r266847 r267081  
    4949
    5050- (void)_denyNextUserMediaRequest;
     51@property (nonatomic, setter=_setMediaCaptureReportingDelayForTesting:) double _mediaCaptureReportingDelayForTesting WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
    5152
    5253- (BOOL)_beginBackSwipeForTesting;
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewTesting.mm

    r266847 r267081  
    227227}
    228228
     229- (double)_mediaCaptureReportingDelayForTesting
     230{
     231    return _page->mediaCaptureReportingDelay().value();
     232}
     233
     234- (void)_setMediaCaptureReportingDelayForTesting:(double)captureReportingDelay
     235{
     236    _page->setMediaCaptureReportingDelay(Seconds(captureReportingDelay));
     237}
     238
    229239- (void)_doAfterProcessingAllPendingMouseEvents:(dispatch_block_t)action
    230240{
  • trunk/Source/WebKit/UIProcess/WebPageProxy.cpp

    r266890 r267081  
    90389038#if ENABLE(MEDIA_STREAM)
    90399039    if (oldMediaCaptureState != newMediaCaptureState) {
    9040         m_uiClient->mediaCaptureStateDidChange(m_mediaState);
     9040        updateReportedMediaCaptureState();
     9041
    90419042        ASSERT(m_userMediaPermissionRequestManager);
    90429043        if (m_userMediaPermissionRequestManager)
     
    90559056
    90569057    m_process->updateAudibleMediaAssertions();
     9058}
     9059
     9060void WebPageProxy::updateReportedMediaCaptureState()
     9061{
     9062    if (m_reportedMediaCaptureState == m_mediaState)
     9063        return;
     9064
     9065    bool haveReportedCapture = m_reportedMediaCaptureState & MediaProducer::MediaCaptureMask;
     9066    bool willReportCapture = m_mediaState & MediaProducer::MediaCaptureMask;
     9067
     9068    if (haveReportedCapture && !willReportCapture && m_delayStopCapturingReportingTimer.isActive())
     9069        return;
     9070
     9071    if (!haveReportedCapture && willReportCapture) {
     9072        m_delayStopCapturingReporting = true;
     9073        m_delayStopCapturingReportingTimer.doTask([this] {
     9074            m_delayStopCapturingReporting = false;
     9075            updateReportedMediaCaptureState();
     9076        }, m_mediaCaptureReportingDelay);
     9077    }
     9078
     9079    m_reportedMediaCaptureState = m_mediaState;
     9080    m_uiClient->mediaCaptureStateDidChange(m_mediaState);
    90579081}
    90589082
  • trunk/Source/WebKit/UIProcess/WebPageProxy.h

    r266890 r267081  
    14501450    bool isPlayingAudio() const { return !!(m_mediaState & WebCore::MediaProducer::IsPlayingAudio); }
    14511451    void isPlayingMediaDidChange(WebCore::MediaProducer::MediaStateFlags, uint64_t);
     1452    void updateReportedMediaCaptureState();
     1453
    14521454    void updatePlayingMediaDidChange(WebCore::MediaProducer::MediaStateFlags);
    14531455    bool isCapturingAudio() const { return m_mediaState & WebCore::MediaProducer::AudioCaptureMask; }
     
    14561458    bool hasActiveVideoStream() const { return m_mediaState & WebCore::MediaProducer::HasActiveVideoCaptureDevice; }
    14571459    WebCore::MediaProducer::MediaStateFlags mediaStateFlags() const { return m_mediaState; }
     1460    WebCore::MediaProducer::MediaStateFlags reportedMediaCaptureState() const { return m_reportedMediaCaptureState; }
    14581461    WebCore::MediaProducer::MutedStateFlags mutedStateFlags() const { return m_mutedState; }
    14591462
     
    18211824    void pdfOpenWithPreview(PDFPluginIdentifier);
    18221825#endif
     1826
     1827    Seconds mediaCaptureReportingDelay() const { return m_mediaCaptureReportingDelay; }
     1828    void setMediaCaptureReportingDelay(Seconds captureReportingDelay) { m_mediaCaptureReportingDelay = captureReportingDelay; }
    18231829
    18241830private:
     
    27572763    WebCore::MediaProducer::MediaStateFlags m_mediaState { WebCore::MediaProducer::IsNotPlaying };
    27582764
     2765    // To make sure capture indicators are visible long enough, m_reportedMediaCaptureState is the same as m_mediaState except that we might delay a bit transition from capturing to not-capturing.
     2766    WebCore::MediaProducer::MediaStateFlags m_reportedMediaCaptureState { WebCore::MediaProducer::IsNotPlaying };
     2767    WebCore::DeferrableTaskTimer m_delayStopCapturingReportingTimer;
     2768    bool m_delayStopCapturingReporting { false };
     2769    static constexpr Seconds DefaultMediaCaptureReportingDelay { 3_s };
     2770    Seconds m_mediaCaptureReportingDelay { DefaultMediaCaptureReportingDelay };
     2771
    27592772    bool m_hasHadSelectionChangesFromUserInteraction { false };
    27602773
  • trunk/Tools/ChangeLog

    r267069 r267081  
     12020-09-15  Youenn Fablet  <youenn@apple.com>
     2
     3        End of media capture should not be reported before 3 seconds of the start of capture
     4        https://bugs.webkit.org/show_bug.cgi?id=216415
     5
     6        Reviewed by Eric Carlson.
     7
     8        * TestWebKitAPI/Tests/WebKit/GetUserMedia.mm:
     9        (TestWebKitAPI::TEST):
     10        * TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:
     11        (-[GetUserMediaUIDelegate _webView:mediaCaptureStateDidChange:]):
     12        Add a notCapturing boolean and wait for the notification before going back to the capturing page.
     13        Decrease delay to 1 second to make the test run faster.
     14        * WebKitTestRunner/TestController.cpp:
     15        (WTR::TestController::resetStateToConsistentValues):
     16        Make sure to set media capture state delay to zero.
     17
    1182020-09-14  Sam Weinig  <weinig@apple.com>
    219
  • trunk/Tools/TestWebKitAPI/Tests/WebKit/GetUserMedia.mm

    r256377 r267081  
    3737#import <WebKit/WKWebView.h>
    3838#import <WebKit/WKWebViewConfiguration.h>
     39#import <WebKit/WKWebViewPrivateForTesting.h>
    3940#import <WebKit/_WKProcessPoolConfiguration.h>
    4041#import <wtf/text/StringBuilder.h>
     
    125126    webView.UIDelegate = delegate.get();
    126127
     128    webView._mediaCaptureReportingDelayForTesting = 0;
     129
    127130    [webView loadTestPageNamed:@"getUserMedia"];
    128131    EXPECT_TRUE(waitUntilCaptureState(webView, _WKMediaCaptureStateActiveCamera));
     
    202205}
    203206
     207TEST(WebKit2, CaptureIndicatorDelay)
     208{
     209    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
     210    auto processPoolConfig = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
     211    auto preferences = [configuration preferences];
     212    preferences._mediaCaptureRequiresSecureConnection = NO;
     213    preferences._mediaDevicesEnabled = YES;
     214    preferences._mockCaptureDevicesEnabled = YES;
     215
     216    auto messageHandler = adoptNS([[GUMMessageHandler alloc] init]);
     217    [[configuration.get() userContentController] addScriptMessageHandler:messageHandler.get() name:@"gum"];
     218
     219    auto webView = [[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration.get() processPoolConfiguration:processPoolConfig.get()];
     220    webView._mediaCaptureReportingDelayForTesting = 2;
     221
     222    auto delegate = adoptNS([[GetUserMediaCaptureUIDelegate alloc] init]);
     223    webView.UIDelegate = delegate.get();
     224
     225    wasPrompted = false;
     226
     227    [webView loadTestPageNamed:@"getUserMedia"];
     228    EXPECT_TRUE(waitUntilCaptureState(webView, _WKMediaCaptureStateActiveCamera));
     229
     230    TestWebKitAPI::Util::run(&wasPrompted);
     231    wasPrompted = false;
     232
     233    [webView stringByEvaluatingJavaScript:@"stop()"];
     234
     235    // We wait 1 second, we should still see camera be reported.
     236    sleep(1_s);
     237    EXPECT_EQ([webView _mediaCaptureState], _WKMediaCaptureStateActiveCamera);
     238
     239    // One additional second should allow us to go back to no capture being reported.
     240    EXPECT_TRUE(waitUntilCaptureState(webView, _WKMediaCaptureStateNone));
     241
     242}
     243
    204244TEST(WebKit2, GetCapabilities)
    205245{
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm

    r266951 r267081  
    63976397
    63986398static bool isCapturing = false;
     6399static bool isNotCapturing = false;
    63996400@interface GetUserMediaUIDelegate : NSObject<WKUIDelegate>
    64006401- (void)_webView:(WKWebView *)webView requestUserMediaAuthorizationForDevices:(_WKCaptureDevices)devices url:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL decisionHandler:(void (^)(BOOL authorized))decisionHandler;
     
    64176418{
    64186419    isCapturing = state == _WKMediaCaptureStateActiveCamera;
     6420    isNotCapturing = !state;
    64196421}
    64206422@end
     
    64496451
    64506452    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
     6453    webView.get()._mediaCaptureReportingDelayForTesting = 1;
    64516454
    64526455    auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
     
    64736476
    64746477    auto pid2 = [webView _webProcessIdentifier];
     6478    TestWebKitAPI::Util::run(&isNotCapturing);
    64756479
    64766480    EXPECT_FALSE(isCapturing);
     
    64796483    isCapturing = false;
    64806484    [webView goBack];
     6485
    64816486    TestWebKitAPI::Util::run(&isCapturing);
    64826487    isCapturing = false;
     6488    isNotCapturing = true;
    64836489}
    64846490
  • trunk/Tools/WebKitTestRunner/TestController.cpp

    r266951 r267081  
    10751075    WKPageSetMockCameraOrientation(m_mainWebView->page(), 0);
    10761076    resetMockMediaDevices();
     1077    WKPageSetMediaCaptureReportingDelayForTesting(m_mainWebView->page(), 0);
    10771078
    10781079    // FIXME: This function should also ensure that there is only one page open.
Note: See TracChangeset for help on using the changeset viewer.