WebUserMediaClient.mm   [plain text]


/*
 * Copyright (C) 2013-2016 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 "WebUserMediaClient.h"

#if ENABLE(MEDIA_STREAM)

#import "WebDelegateImplementationCaching.h"
#import "WebSecurityOriginInternal.h"
#import "WebUIDelegatePrivate.h"
#import "WebViewInternal.h"
#import <WebCore/CaptureDevice.h>
#import <WebCore/MediaDevicesEnumerationRequest.h>
#import <WebCore/RealtimeMediaSourceCenter.h>
#import <WebCore/ScriptExecutionContext.h>
#import <WebCore/UserMediaRequest.h>
#import <wtf/BlockObjCExceptions.h>
#import <wtf/HashMap.h>
#import <wtf/NeverDestroyed.h>
#import <wtf/RefPtr.h>
#import <wtf/RetainPtr.h>

using namespace WebCore;

@interface WebUserMediaPolicyListener : NSObject <WebAllowDenyPolicyListener>
{
    RefPtr<UserMediaRequest> _request;
}
- (id)initWithUserMediaRequest:(Ref<UserMediaRequest>&&)request;
- (void)cancelUserMediaAccessRequest;
- (void)deny;
@end

@interface WebUserMediaPolicyCheckerListener : NSObject <WebAllowDenyPolicyListener> {
    RefPtr<MediaDevicesEnumerationRequest> _request;
}
- (id)initWithMediaDevicesEnumerationRequest:(Ref<MediaDevicesEnumerationRequest>&&)request;
- (void)cancelMediaDevicesEnumerationRequest;
- (void)deny;
@end

typedef HashMap<RefPtr<UserMediaRequest>, RetainPtr<WebUserMediaPolicyListener>> UserMediaRequestsMap;

static UserMediaRequestsMap& userMediaRequestsMap()
{
    static NeverDestroyed<UserMediaRequestsMap> requests;
    return requests;
}

static void AddRequestToRequestMap(UserMediaRequest* request, RetainPtr<WebUserMediaPolicyListener> listener)
{
    userMediaRequestsMap().set(request, listener);
}

static void RemoveRequestFromRequestMap(UserMediaRequest* request)
{
    userMediaRequestsMap().remove(request);
}

typedef HashMap<RefPtr<MediaDevicesEnumerationRequest>, RetainPtr<WebUserMediaPolicyCheckerListener>> UserMediaCheckMap;

static UserMediaCheckMap& userMediaCheckMap()
{
    static NeverDestroyed<UserMediaCheckMap> requests;
    return requests;
}

static void AddPermissionCheckToMap(MediaDevicesEnumerationRequest* request, RetainPtr<WebUserMediaPolicyCheckerListener> listener)
{
    userMediaCheckMap().set(request, listener);
}

static void RemovePermissionCheckFromMap(MediaDevicesEnumerationRequest* request)
{
    userMediaCheckMap().remove(request);
}

WebUserMediaClient::WebUserMediaClient(WebView* webView)
    : m_webView(webView)
{
}

WebUserMediaClient::~WebUserMediaClient()
{
}

void WebUserMediaClient::pageDestroyed()
{
    auto& requestsMap = userMediaRequestsMap();
    Vector<RetainPtr<WebUserMediaPolicyListener>> pendingRequests;
    copyValuesToVector(requestsMap, pendingRequests);
    requestsMap.clear();

    for (auto& request : pendingRequests)
        [request cancelUserMediaAccessRequest];

    ASSERT(userMediaRequestsMap().isEmpty());

    auto& checkMap = userMediaCheckMap();
    Vector<RetainPtr<WebUserMediaPolicyCheckerListener>> pendingChecks;
    copyValuesToVector(checkMap, pendingChecks);
    checkMap.clear();

    for (auto& check : pendingChecks)
        [check cancelMediaDevicesEnumerationRequest];

    ASSERT(userMediaCheckMap().isEmpty());

    delete this;
}

void WebUserMediaClient::requestUserMediaAccess(UserMediaRequest& request)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS;

    SEL selector = @selector(webView:decidePolicyForUserMediaRequestFromOrigin:listener:);
    if (![[m_webView UIDelegate] respondsToSelector:selector]) {
        request.deny(UserMediaRequest::MediaAccessDenialReason::UserMediaDisabled, "");
        return;
    }

    WebUserMediaPolicyListener *listener = [[WebUserMediaPolicyListener alloc] initWithUserMediaRequest:request];
    WebSecurityOrigin *webOrigin = [[WebSecurityOrigin alloc] _initWithWebCoreSecurityOrigin:request.userMediaDocumentOrigin()];

    AddRequestToRequestMap(&request, listener);
    CallUIDelegate(m_webView, selector, webOrigin, listener);

    [webOrigin release];
    [listener release];

    END_BLOCK_OBJC_EXCEPTIONS;
}

void WebUserMediaClient::cancelUserMediaAccessRequest(UserMediaRequest& request)
{
    UserMediaRequestsMap& requestsMap = userMediaRequestsMap();
    UserMediaRequestsMap::iterator it = requestsMap.find(&request);
    if (it == requestsMap.end())
        return;

    [it->value cancelUserMediaAccessRequest];
    requestsMap.remove(it);
}

void WebUserMediaClient::enumerateMediaDevices(MediaDevicesEnumerationRequest& request)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS;

    SEL selector = @selector(webView:checkPolicyForUserMediaRequestFromOrigin:listener:);
    if (![[m_webView UIDelegate] respondsToSelector:selector]) {
        request.setDeviceInfo(Vector<CaptureDevice>(), emptyString(), false);
        return;
    }

    WebUserMediaPolicyCheckerListener *listener = [[WebUserMediaPolicyCheckerListener alloc] initWithMediaDevicesEnumerationRequest:request];
    WebSecurityOrigin *webOrigin = [[WebSecurityOrigin alloc] _initWithWebCoreSecurityOrigin:request.userMediaDocumentOrigin()];

    AddPermissionCheckToMap(&request, listener);
    CallUIDelegate(m_webView, selector, webOrigin, listener);

    [webOrigin release];
    [listener release];

    END_BLOCK_OBJC_EXCEPTIONS;
}

void WebUserMediaClient::cancelMediaDevicesEnumerationRequest(WebCore::MediaDevicesEnumerationRequest& request)
{
    UserMediaCheckMap& requestsMap = userMediaCheckMap();
    UserMediaCheckMap::iterator it = requestsMap.find(&request);
    if (it == requestsMap.end())
        return;

    [it->value cancelMediaDevicesEnumerationRequest];
    requestsMap.remove(it);
}

@implementation WebUserMediaPolicyListener

- (id)initWithUserMediaRequest:(Ref<UserMediaRequest>&&)request
{
#if ENABLE(MEDIA_STREAM)
    if (!(self = [super init]))
        return nil;

    _request = WTFMove(request);
    return self;
#endif
}

- (void)cancelUserMediaAccessRequest
{
#if ENABLE(MEDIA_STREAM)
    if (!_request)
        return;

    _request = nullptr;
#endif
    
}

- (void)allow
{
#if ENABLE(MEDIA_STREAM)
    if (!_request)
        return;
    
    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=162154
    RemoveRequestFromRequestMap(_request.get());
#endif
}

- (void)deny
{
#if ENABLE(MEDIA_STREAM)
    if (!_request)
        return;
    
    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=162154
    RemoveRequestFromRequestMap(_request.get());
#endif
}

#if PLATFORM(IOS)
- (void)denyOnlyThisRequest
{
}

- (BOOL)shouldClearCache
{
    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=146245
    ASSERT_NOT_REACHED();
    return true;
}
#endif

@end


@implementation WebUserMediaPolicyCheckerListener

- (id)initWithMediaDevicesEnumerationRequest:(Ref<MediaDevicesEnumerationRequest>&&)request
{
#if ENABLE(MEDIA_STREAM)
    if (!(self = [super init]))
        return nil;

    _request = WTFMove(request);
    return self;
#endif
}

- (void)cancelMediaDevicesEnumerationRequest
{
#if ENABLE(MEDIA_STREAM)
    if (!_request)
        return;

    _request = nullptr;
#endif

}

- (void)allow
{
#if ENABLE(MEDIA_STREAM)
    if (!_request)
        return;

    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=162154
    RemovePermissionCheckFromMap(_request.get());
#endif
}

- (void)deny
{
#if ENABLE(MEDIA_STREAM)
    if (!_request)
        return;

    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=162154
    RemovePermissionCheckFromMap(_request.get());
#endif
}

#if PLATFORM(IOS)
- (void)denyOnlyThisRequest
{
}

- (BOOL)shouldClearCache
{
    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=146245
    ASSERT_NOT_REACHED();
    return true;
}
#endif

@end

#endif // ENABLE(MEDIA_STREAM)