Implement ondevicechange
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Apr 2017 17:22:19 +0000 (17:22 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Apr 2017 17:22:19 +0000 (17:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=169872
<rdar://problem/28945035>

Reviewed by Jer Noble.

Source/WebCore:

Test: fast/mediastream/device-change-event.html

* Modules/mediastream/MediaDevices.cpp:
(WebCore::MediaDevices::MediaDevices): Register for devicechange callbacks.
(WebCore::MediaDevices::~MediaDevices): Unregister.
(WebCore::MediaDevices::scheduledEventTimerFired):
* Modules/mediastream/MediaDevices.h:
* Modules/mediastream/MediaDevices.idl:

* Modules/mediastream/MediaDevicesEnumerationRequest.cpp:
(WebCore::MediaDevicesEnumerationRequest::setDeviceInfo): Remove unnecessary instance variables.
* Modules/mediastream/MediaDevicesEnumerationRequest.h:

* Modules/mediastream/MediaDevicesRequest.cpp:
(WebCore::MediaDevicesRequest::start): Remove m_idHashSalt, it isn't used. RealtimeMediaSourceCenter
now has the method to hash ids and group IDs.
(WebCore::hashString): Deleted.
(WebCore::MediaDevicesRequest::hashID): Deleted.
* Modules/mediastream/MediaDevicesRequest.h:

* dom/Document.h:
(WebCore::Document::setDeviceIDHashSalt):
(WebCore::Document::deviceIDHashSalt):

* dom/EventNames.h: Add devicechange.

* dom/EventTargetFactory.in: Add MediaDevices.

* html/HTMLAttributeNames.in: Add ondevicechange.

* platform/mediastream/CaptureDeviceManager.cpp:
(WebCore::CaptureDeviceManager::captureDeviceFromPersistentID): Renamed from captureDeviceFromDeviceID
and changed to return a std::optional<CaptureDevice>.
(WebCore::CaptureDeviceManager::captureDeviceFromDeviceID): Deleted.

* platform/mediastream/RealtimeMediaSourceCenter.cpp:
(WebCore::addStringToSHA): New, add string bytes to SHA1. Moved from MediaDevicesRequest
so we can use for in testing.
(WebCore::RealtimeMediaSourceCenter::hashStringWithSalt): Generate hash for a string with a
salt. Moved from MediaDevicesRequest so we can use for in testing.
(WebCore::RealtimeMediaSourceCenter::captureDeviceWithUniqueID): Find a CaptureDevice given
a unique ID and the process hash salt.
(WebCore::RealtimeMediaSourceCenter::setDeviceEnabled): Enable/disable a device. Used for
layout tests only.
(WebCore::RealtimeMediaSourceCenter::addDevicesChangedObserver): Add a devices changed listener.
(WebCore::RealtimeMediaSourceCenter::removeDevicesChangedObserver): Remove a listener.
(WebCore::RealtimeMediaSourceCenter::captureDevicesChanged): Notify listeners.
* platform/mediastream/RealtimeMediaSourceCenter.h:

* platform/mediastream/mac/AVCaptureDeviceManager.mm:
(WebCore::AVCaptureDeviceManager::addDevicesChangedObserver): Update for change to captureDeviceFromDeviceID.

* platform/mock/MockRealtimeMediaSource.cpp:
(WebCore::MockRealtimeMediaSource::audioDevices): All devices are enabled by default.
(WebCore::MockRealtimeMediaSource::videoDevices): Ditto.

* platform/mock/MockRealtimeMediaSourceCenter.cpp:
(WebCore::MockRealtimeMediaSourceCenter::createMediaStream): Drive-by cleanup: use the vector
of devices instead of making assumptions about the number.
(WebCore::MockRealtimeMediaSourceCenter::getMediaStreamDevices): Only include enabled devices.
(WebCore::MockRealtimeMediaSourceCenter::setDeviceEnabled): Enable or disable a device.
* platform/mock/MockRealtimeMediaSourceCenter.h:

* testing/Internals.cpp:
(WebCore::Internals::setMediaDeviceState): Enable or disable a mock capture device.
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* fast/mediastream/device-change-event-expected.txt: Added.
* fast/mediastream/device-change-event.html: Added.

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

27 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/mediastream/device-change-event-expected.txt [new file with mode: 0644]
LayoutTests/fast/mediastream/device-change-event.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/mediastream/MediaDevices.cpp
Source/WebCore/Modules/mediastream/MediaDevices.h
Source/WebCore/Modules/mediastream/MediaDevices.idl
Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.cpp
Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.h
Source/WebCore/Modules/mediastream/MediaDevicesRequest.cpp
Source/WebCore/Modules/mediastream/MediaDevicesRequest.h
Source/WebCore/Modules/mediastream/UserMediaController.h
Source/WebCore/dom/Document.h
Source/WebCore/dom/EventNames.h
Source/WebCore/dom/EventTargetFactory.in
Source/WebCore/html/HTMLAttributeNames.in
Source/WebCore/platform/mediastream/CaptureDeviceManager.cpp
Source/WebCore/platform/mediastream/CaptureDeviceManager.h
Source/WebCore/platform/mediastream/RealtimeMediaSourceCenter.cpp
Source/WebCore/platform/mediastream/RealtimeMediaSourceCenter.h
Source/WebCore/platform/mediastream/mac/AVCaptureDeviceManager.mm
Source/WebCore/platform/mock/MockRealtimeMediaSource.cpp
Source/WebCore/platform/mock/MockRealtimeMediaSourceCenter.cpp
Source/WebCore/platform/mock/MockRealtimeMediaSourceCenter.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl

index 63af52e..3cc45d2 100644 (file)
@@ -1,3 +1,14 @@
+2017-04-28  Eric Carlson  <eric.carlson@apple.com>
+
+        Implement ondevicechange
+        https://bugs.webkit.org/show_bug.cgi?id=169872
+        <rdar://problem/28945035>
+
+        Reviewed by Jer Noble.
+
+        * fast/mediastream/device-change-event-expected.txt: Added.
+        * fast/mediastream/device-change-event.html: Added.
+
 2017-04-28  Joanmarie Diggs  <jdiggs@igalia.com>
 
         AX: Implement aria-value support for focusable separators
diff --git a/LayoutTests/fast/mediastream/device-change-event-expected.txt b/LayoutTests/fast/mediastream/device-change-event-expected.txt
new file mode 100644 (file)
index 0000000..64c6b61
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS 'devicechange' event fired when device list changes 
+
diff --git a/LayoutTests/fast/mediastream/device-change-event.html b/LayoutTests/fast/mediastream/device-change-event.html
new file mode 100644 (file)
index 0000000..41124c9
--- /dev/null
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Testing local audio capture playback causes "playing" event to fire</title>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script>
+    <script>
+    let deviceIds = [];
+    promise_test((test) => {
+        if (window.testRunner)
+            testRunner.setUserMediaPermission(true);
+
+        return navigator.mediaDevices.enumerateDevices().then((devices) => {
+            return new Promise((resolve, reject) => {
+                devices.forEach(function(device) { deviceIds.push(device.deviceId); });
+                navigator.mediaDevices.ondevicechange = (event) => { resolve(event); };
+                setTimeout(() => resolve(new Event('devicechange')), 100);
+            });
+        }).then((event) => {
+            assert_equals(event.type, "devicechange", "correct event type");
+            return new Promise((resolve, reject) => {
+                return navigator.mediaDevices.enumerateDevices().then((devices) => {
+                    let deviceIDs2 = [];
+                    devices.forEach(function(device) { deviceIDs2.push(device.deviceId); });
+                    try {
+                        assert_true(deviceIDs2.indexOf(deviceIds[0]) != -1, "device not reported by enumerateDevices");
+                        assert_equals(deviceIDs2.length, deviceIds.length, "correct number of devices");
+                    } catch(e) {
+                        reject(e);
+                        return;
+                    }
+
+                    navigator.mediaDevices.ondevicechange = (event) => { resolve(event); };
+                    setTimeout(() => resolve(new Event('devicechange')), 100);
+                });
+            });
+        }).then((event) => {
+            assert_equals(event.type, "devicechange", "correct event type");
+            return new Promise((resolve, reject) => {
+                return navigator.mediaDevices.enumerateDevices().then((devices) => {
+                    let deviceIDs2 = [];
+                    devices.forEach(function(device) { deviceIDs2.push(device.deviceId); });
+                    try {
+                        assert_in_array(deviceIds[0], deviceIDs2, "device reported by enumerateDevices again");
+                        assert_equals(deviceIDs2.length, deviceIds.length, "correct number of devices");
+                    } catch(e) {
+                        reject(e);
+                        return;
+                    }
+
+                    resolve();
+                });
+            });
+        });
+
+    }, "'devicechange' event fired when device list changes");
+    </script>
+</head>
+<body>
+</body>
+</html>
index 8c9e663..705d4a8 100644 (file)
@@ -1,3 +1,79 @@
+2017-04-28  Eric Carlson  <eric.carlson@apple.com>
+
+        Implement ondevicechange
+        https://bugs.webkit.org/show_bug.cgi?id=169872
+        <rdar://problem/28945035>
+
+        Reviewed by Jer Noble.
+
+        Test: fast/mediastream/device-change-event.html
+
+        * Modules/mediastream/MediaDevices.cpp:
+        (WebCore::MediaDevices::MediaDevices): Register for devicechange callbacks.
+        (WebCore::MediaDevices::~MediaDevices): Unregister.
+        (WebCore::MediaDevices::scheduledEventTimerFired):
+        * Modules/mediastream/MediaDevices.h:
+        * Modules/mediastream/MediaDevices.idl:
+
+        * Modules/mediastream/MediaDevicesEnumerationRequest.cpp:
+        (WebCore::MediaDevicesEnumerationRequest::setDeviceInfo): Remove unnecessary instance variables.
+        * Modules/mediastream/MediaDevicesEnumerationRequest.h:
+
+        * Modules/mediastream/MediaDevicesRequest.cpp:
+        (WebCore::MediaDevicesRequest::start): Remove m_idHashSalt, it isn't used. RealtimeMediaSourceCenter
+        now has the method to hash ids and group IDs.
+        (WebCore::hashString): Deleted.
+        (WebCore::MediaDevicesRequest::hashID): Deleted.
+        * Modules/mediastream/MediaDevicesRequest.h:
+
+        * dom/Document.h:
+        (WebCore::Document::setDeviceIDHashSalt):
+        (WebCore::Document::deviceIDHashSalt):
+
+        * dom/EventNames.h: Add devicechange.
+
+        * dom/EventTargetFactory.in: Add MediaDevices.
+
+        * html/HTMLAttributeNames.in: Add ondevicechange.
+
+        * platform/mediastream/CaptureDeviceManager.cpp:
+        (WebCore::CaptureDeviceManager::captureDeviceFromPersistentID): Renamed from captureDeviceFromDeviceID
+        and changed to return a std::optional<CaptureDevice>.
+        (WebCore::CaptureDeviceManager::captureDeviceFromDeviceID): Deleted.
+
+        * platform/mediastream/RealtimeMediaSourceCenter.cpp:
+        (WebCore::addStringToSHA): New, add string bytes to SHA1. Moved from MediaDevicesRequest
+        so we can use for in testing.
+        (WebCore::RealtimeMediaSourceCenter::hashStringWithSalt): Generate hash for a string with a
+        salt. Moved from MediaDevicesRequest so we can use for in testing.
+        (WebCore::RealtimeMediaSourceCenter::captureDeviceWithUniqueID): Find a CaptureDevice given 
+        a unique ID and the process hash salt.
+        (WebCore::RealtimeMediaSourceCenter::setDeviceEnabled): Enable/disable a device. Used for
+        layout tests only.
+        (WebCore::RealtimeMediaSourceCenter::addDevicesChangedObserver): Add a devices changed listener.
+        (WebCore::RealtimeMediaSourceCenter::removeDevicesChangedObserver): Remove a listener.
+        (WebCore::RealtimeMediaSourceCenter::captureDevicesChanged): Notify listeners.
+        * platform/mediastream/RealtimeMediaSourceCenter.h:
+
+        * platform/mediastream/mac/AVCaptureDeviceManager.mm:
+        (WebCore::AVCaptureDeviceManager::addDevicesChangedObserver): Update for change to captureDeviceFromDeviceID.
+
+        * platform/mock/MockRealtimeMediaSource.cpp:
+        (WebCore::MockRealtimeMediaSource::audioDevices): All devices are enabled by default.
+        (WebCore::MockRealtimeMediaSource::videoDevices): Ditto.
+
+        * platform/mock/MockRealtimeMediaSourceCenter.cpp:
+        (WebCore::MockRealtimeMediaSourceCenter::createMediaStream): Drive-by cleanup: use the vector
+        of devices instead of making assumptions about the number.
+        (WebCore::MockRealtimeMediaSourceCenter::getMediaStreamDevices): Only include enabled devices.
+        (WebCore::MockRealtimeMediaSourceCenter::setDeviceEnabled): Enable or disable a device.
+        * platform/mock/MockRealtimeMediaSourceCenter.h:
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::setMediaDeviceState): Enable or disable a mock capture device.
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2017-04-28  Alex Christensen  <achristensen@webkit.org>
 
         Fix memory corruption issue after r215883.
index 0f16ffa..660e59a 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 Ericsson AB. All rights reserved.
+ * Copyright (C) 2015-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
 #if ENABLE(MEDIA_STREAM)
 
 #include "Document.h"
+#include "Event.h"
+#include "EventNames.h"
 #include "MediaConstraintsImpl.h"
 #include "MediaDevicesRequest.h"
 #include "MediaTrackSupportedConstraints.h"
-#include "RealtimeMediaSourceCenter.h"
 #include "UserMediaRequest.h"
+#include <wtf/RandomNumber.h>
 
 namespace WebCore {
 
 inline MediaDevices::MediaDevices(Document& document)
     : ContextDestructionObserver(&document)
+    , m_scheduledEventTimer(*this, &MediaDevices::scheduledEventTimerFired)
 {
+    m_deviceChangedToken = RealtimeMediaSourceCenter::singleton().addDevicesChangedObserver([this]() {
+
+        // FIXME: We should only dispatch an event if the user has been granted access to the type of
+        // device that was added or removed.
+        if (!m_scheduledEventTimer.isActive())
+            m_scheduledEventTimer.startOneShot(Seconds(randomNumber() / 2));
+    });
+}
+
+MediaDevices::~MediaDevices()
+{
+    if (m_deviceChangedToken)
+        RealtimeMediaSourceCenter::singleton().removeDevicesChangedObserver(m_deviceChangedToken);
 }
 
 Ref<MediaDevices> MediaDevices::create(Document& document)
@@ -108,6 +125,11 @@ MediaTrackSupportedConstraints MediaDevices::getSupportedConstraints()
     return result;
 }
 
+void MediaDevices::scheduledEventTimerFired()
+{
+    dispatchEvent(Event::create(eventNames().devicechangeEvent, false, false));
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(MEDIA_STREAM)
index 0d90d25..2855ea5 100644 (file)
 
 #if ENABLE(MEDIA_STREAM)
 
+#include "EventTarget.h"
 #include "ExceptionOr.h"
 #include "JSDOMPromise.h"
 #include "MediaTrackConstraints.h"
+#include "RealtimeMediaSourceCenter.h"
+#include "Timer.h"
 
 namespace WebCore {
 
@@ -45,10 +48,12 @@ class MediaStream;
 
 struct MediaTrackSupportedConstraints;
 
-class MediaDevices : public ScriptWrappable, public RefCounted<MediaDevices>, public ContextDestructionObserver {
+class MediaDevices : public RefCounted<MediaDevices>, public ContextDestructionObserver, public EventTargetWithInlineData {
 public:
     static Ref<MediaDevices> create(Document&);
 
+    ~MediaDevices();
+
     Document* document() const;
 
     using Promise = DOMPromise<IDLInterface<MediaStream>>;
@@ -62,8 +67,22 @@ public:
     void enumerateDevices(EnumerateDevicesPromise&&) const;
     MediaTrackSupportedConstraints getSupportedConstraints();
 
+    using RefCounted<MediaDevices>::ref;
+    using RefCounted<MediaDevices>::deref;
+
 private:
     explicit MediaDevices(Document&);
+
+    void scheduledEventTimerFired();
+
+    // EventTargetWithInlineData.
+    EventTargetInterface eventTargetInterface() const override { return MediaDevicesEventTargetInterfaceType; }
+    ScriptExecutionContext* scriptExecutionContext() const final { return m_scriptExecutionContext; }
+    void refEventTarget() override { ref(); }
+    void derefEventTarget() override { deref(); }
+
+    Timer m_scheduledEventTimer;
+    RealtimeMediaSourceCenter::DevicesChangedObserverToken m_deviceChangedToken { 0 };
 };
 
 } // namespace WebCore
index 6553441..abab178 100644 (file)
@@ -33,7 +33,7 @@
     Conditional=MEDIA_STREAM,
     NoInterfaceObject,
 ] interface MediaDevices {
-    // FIXME 169872: missing ondevicechange
+    attribute EventHandler ondevicechange;
     Promise<sequence<MediaDeviceInfo>> enumerateDevices();
 
     MediaTrackSupportedConstraints getSupportedConstraints();
index c89d43d..71a4d51 100644 (file)
@@ -99,12 +99,8 @@ void MediaDevicesEnumerationRequest::cancel()
 
 void MediaDevicesEnumerationRequest::setDeviceInfo(const Vector<CaptureDevice>& deviceList, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess)
 {
-    m_deviceList = deviceList;
-    m_deviceIdentifierHashSalt = deviceIdentifierHashSalt;
-    m_originHasPersistentAccess = originHasPersistentAccess;
-
     if (m_completionHandler)
-        m_completionHandler(m_deviceList, m_deviceIdentifierHashSalt, m_originHasPersistentAccess);
+        m_completionHandler(deviceList, deviceIdentifierHashSalt, originHasPersistentAccess);
     m_completionHandler = nullptr;
 }
 
index ec4df00..285575b 100644 (file)
@@ -61,9 +61,6 @@ private:
     void contextDestroyed() final;
 
     CompletionHandler m_completionHandler;
-    Vector<CaptureDevice> m_deviceList;
-    String m_deviceIdentifierHashSalt;
-    bool m_originHasPersistentAccess { false };
 };
 
 } // namespace WebCore
index 4a0b3e1..eb3ff53 100644 (file)
@@ -34,6 +34,7 @@
 #include "Frame.h"
 #include "JSMediaDeviceInfo.h"
 #include "MediaDevicesEnumerationRequest.h"
+#include "RealtimeMediaSourceCenter.h"
 #include "SecurityOrigin.h"
 #include "UserMediaController.h"
 #include <wtf/MainThread.h>
@@ -86,11 +87,7 @@ void MediaDevicesRequest::start()
             return;
 
         Document& document = downcast<Document>(*scriptExecutionContext());
-        UserMediaController* controller = UserMediaController::from(document.page());
-        if (!controller)
-            return;
-
-        m_idHashSalt = deviceIdentifierHashSalt;
+        document.setDeviceIDHashSalt(deviceIdentifierHashSalt);
 
         Vector<RefPtr<MediaDeviceInfo>> devices;
         for (auto& deviceInfo : captureDevices) {
@@ -98,11 +95,11 @@ void MediaDevicesRequest::start()
             if (originHasPersistentAccess || document.hasHadActiveMediaStreamTrack())
                 label = deviceInfo.label();
 
-            auto id = hashID(deviceInfo.persistentId());
+            auto id = RealtimeMediaSourceCenter::singleton().hashStringWithSalt(deviceInfo.persistentId(), deviceIdentifierHashSalt);
             if (id.isEmpty())
                 continue;
 
-            auto groupId = hashID(deviceInfo.groupId());
+            auto groupId = RealtimeMediaSourceCenter::singleton().hashStringWithSalt(deviceInfo.groupId(), deviceIdentifierHashSalt);
             auto deviceType = deviceInfo.type() == CaptureDevice::DeviceType::Audio ? MediaDeviceInfo::Kind::Audioinput : MediaDeviceInfo::Kind::Videoinput;
             devices.append(MediaDeviceInfo::create(scriptExecutionContext(), label, id, groupId, deviceType));
         }
@@ -116,38 +113,6 @@ void MediaDevicesRequest::start()
     m_enumerationRequest->start();
 }
 
-static void hashString(SHA1& sha1, const String& string)
-{
-    if (string.isEmpty())
-        return;
-
-    if (string.is8Bit() && string.containsOnlyASCII()) {
-        const uint8_t nullByte = 0;
-        sha1.addBytes(string.characters8(), string.length());
-        sha1.addBytes(&nullByte, 1);
-        return;
-    }
-
-    auto utf8 = string.utf8();
-    sha1.addBytes(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length() + 1); // Include terminating null byte.
-}
-
-String MediaDevicesRequest::hashID(const String& id)
-{
-    if (id.isEmpty() || m_idHashSalt.isEmpty())
-        return emptyString();
-
-    SHA1 sha1;
-
-    hashString(sha1, id);
-    hashString(sha1, m_idHashSalt);
-
-    SHA1::Digest digest;
-    sha1.computeHash(digest);
-
-    return SHA1::hexDigest(digest).data();
-}
-
 } // namespace WebCore
 
 #endif // ENABLE(MEDIA_STREAM)
index d47002f..753e64c 100644 (file)
@@ -51,13 +51,9 @@ private:
 
     void contextDestroyed() final;
 
-    String hashID(const String&);
-
     MediaDevices::EnumerateDevicesPromise m_promise;
     RefPtr<MediaDevicesRequest> m_protector;
     RefPtr<MediaDevicesEnumerationRequest> m_enumerationRequest;
-
-    String m_idHashSalt;
 };
 
 } // namespace WebCore
index 707fd8e..8804aca 100644 (file)
@@ -46,11 +46,15 @@ public:
     void enumerateMediaDevices(MediaDevicesEnumerationRequest&);
     void cancelMediaDevicesEnumerationRequest(MediaDevicesEnumerationRequest&);
 
+    void setDeviceIDHashSalt(const String& salt) { m_idHashSalt = salt; }
+    String deviceIDHashSalt() const { return m_idHashSalt; }
+
     WEBCORE_EXPORT static const char* supplementName();
     static UserMediaController* from(Page* page) { return static_cast<UserMediaController*>(Supplement<Page>::from(page, supplementName())); }
 
 private:
     UserMediaClient* m_client;
+    String m_idHashSalt;
 };
 
 inline void UserMediaController::requestUserMediaAccess(UserMediaRequest& request)
index fd7baf6..fb2d97f 100644 (file)
@@ -1274,6 +1274,8 @@ public:
 #if ENABLE(MEDIA_STREAM)
     void setHasActiveMediaStreamTrack() { m_hasHadActiveMediaStreamTrack = true; }
     bool hasHadActiveMediaStreamTrack() const { return m_hasHadActiveMediaStreamTrack; }
+    void setDeviceIDHashSalt(const String& salt) { m_idHashSalt = salt; }
+    String deviceIDHashSalt() const { return m_idHashSalt; }
 #endif
 
 // FIXME: Find a better place for this functionality.
@@ -1748,6 +1750,7 @@ private:
 #endif
 
 #if ENABLE(MEDIA_STREAM)
+    String m_idHashSalt;
     bool m_hasHadActiveMediaStreamTrack { false };
 #endif
 
index d4f3d7f..280bb0a 100644 (file)
@@ -91,6 +91,7 @@ namespace WebCore {
     macro(cut) \
     macro(datachannel) \
     macro(dblclick) \
+    macro(devicechange) \
     macro(devicemotion) \
     macro(deviceorientation) \
     macro(dischargingtimechange) \
index ca381f8..1c6c9f1 100644 (file)
@@ -15,6 +15,7 @@ IDBOpenDBRequest conditional=INDEXED_DATABASE
 IDBRequest conditional=INDEXED_DATABASE
 IDBTransaction conditional=INDEXED_DATABASE
 MediaController conditional=VIDEO
+MediaDevices conditional=MEDIA_STREAM
 MediaKeySession conditional=ENCRYPTED_MEDIA
 MediaRemoteControls conditional=MEDIA_SESSION
 MediaSource conditional=MEDIA_SOURCE
index f554513..41a54ee 100644 (file)
@@ -202,6 +202,7 @@ oncontextmenu
 oncopy
 oncut
 ondblclick
+ondevicechange
 ondrag
 ondragend
 ondragenter
index 8648ab7..bac3360 100644 (file)
@@ -69,18 +69,38 @@ Vector<CaptureDevice> CaptureDeviceManager::getVideoSourcesInfo()
     return sourcesInfo;
 }
 
-bool CaptureDeviceManager::captureDeviceFromDeviceID(const String& captureDeviceID, CaptureDevice& foundDevice)
+std::optional<CaptureDevice> CaptureDeviceManager::captureDeviceFromPersistentID(const String& captureDeviceID)
 {
     for (auto& device : captureDevices()) {
-        if (device.persistentId() == captureDeviceID) {
-            foundDevice = device;
-            return true;
-        }
+        if (device.persistentId() == captureDeviceID)
+            return device;
     }
 
-    return false;
+    return std::nullopt;
 }
 
+#if 0
+std::optional<CaptureDevice> CaptureDeviceManager::captureDeviceFromPersistentID(const String& captureDeviceID, const String& idHashSalt)
+{
+    for (auto& device : captureDevices()) {
+        auto hashedID = RealtimeMediaSourceCenter::singleton().hashStringWithSalt(device.persistentId(), idHashSalt);
+        if (device.persistentId() == captureDeviceID)
+            return device;
+    }
+
+    return std::nullopt;
+}
+
+for (auto& captureDevice : getMediaStreamDevices()) {
+    auto hashedID = RealtimeMediaSourceCenter::singleton().hashStringWithSalt(captureDevice.persistentId(), idHashSalt);
+    if (hashedID == uniqueID)
+        return String { captureDevice.persistentId() };
+}
+
+return Exception { NOT_FOUND_ERR };
+
+#endif
+
 std::optional<CaptureDevice> CaptureDeviceManager::deviceWithUID(const String& deviceUID, RealtimeMediaSource::Type type)
 {
     for (auto& captureDevice : captureDevices()) {
index 379fe74..5810f49 100644 (file)
@@ -42,7 +42,7 @@ public:
 
 protected:
     virtual ~CaptureDeviceManager();
-    bool captureDeviceFromDeviceID(const String& captureDeviceID, CaptureDevice& source);
+    std::optional<CaptureDevice> captureDeviceFromPersistentID(const String& captureDeviceID);
 };
 
 } // namespace WebCore
index 955998d..e6d15f7 100644 (file)
  */
 
 #include "config.h"
+#include "RealtimeMediaSourceCenter.h"
 
 #if ENABLE(MEDIA_STREAM)
-#include "RealtimeMediaSourceCenter.h"
 
 #include "MediaStreamPrivate.h"
+#include <wtf/SHA1.h>
 
 namespace WebCore {
 
@@ -109,7 +110,74 @@ void RealtimeMediaSourceCenter::unsetVideoCaptureDeviceManager(CaptureDeviceMana
     if (m_videoCaptureDeviceManager == &deviceManager)
         m_videoCaptureDeviceManager = nullptr;
 }
+
+static void addStringToSHA1(SHA1& sha1, const String& string)
+{
+    if (string.isEmpty())
+        return;
+
+    if (string.is8Bit() && string.containsOnlyASCII()) {
+        const uint8_t nullByte = 0;
+        sha1.addBytes(string.characters8(), string.length());
+        sha1.addBytes(&nullByte, 1);
+        return;
+    }
+
+    auto utf8 = string.utf8();
+    sha1.addBytes(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length() + 1); // Include terminating null byte.
+}
+
+String RealtimeMediaSourceCenter::hashStringWithSalt(const String& id, const String& hashSalt)
+{
+    if (id.isEmpty() || hashSalt.isEmpty())
+        return emptyString();
+
+    SHA1 sha1;
+
+    addStringToSHA1(sha1, id);
+    addStringToSHA1(sha1, hashSalt);
     
+    SHA1::Digest digest;
+    sha1.computeHash(digest);
+    
+    return SHA1::hexDigest(digest).data();
+}
+
+std::optional<CaptureDevice> RealtimeMediaSourceCenter::captureDeviceWithUniqueID(const String& uniqueID, const String& idHashSalt)
+{
+    for (auto& device : getMediaStreamDevices()) {
+        if (uniqueID == hashStringWithSalt(device.persistentId(), idHashSalt))
+            return device;
+    }
+
+    return std::nullopt;
+}
+
+ExceptionOr<void> RealtimeMediaSourceCenter::setDeviceEnabled(const String&, bool)
+{
+    return Exception { NOT_FOUND_ERR, ASCIILiteral("Not implemented!") };
+}
+
+RealtimeMediaSourceCenter::DevicesChangedObserverToken RealtimeMediaSourceCenter::addDevicesChangedObserver(std::function<void()>&& observer)
+{
+    DevicesChangedObserverToken nextToken = 0;
+    m_devicesChangedObservers.set(++nextToken, WTFMove(observer));
+    return nextToken;
+}
+
+void RealtimeMediaSourceCenter::removeDevicesChangedObserver(DevicesChangedObserverToken token)
+{
+    bool wasRemoved = m_devicesChangedObservers.remove(token);
+    ASSERT_UNUSED(wasRemoved, wasRemoved);
+}
+
+void RealtimeMediaSourceCenter::captureDevicesChanged()
+{
+    auto callbacks = m_devicesChangedObservers;
+    for (auto it : callbacks)
+        it.value();
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(MEDIA_STREAM)
index 51241ad..1b07a88 100644 (file)
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef RealtimeMediaSourceCenter_h
-#define RealtimeMediaSourceCenter_h
+#pragma once
 
 #if ENABLE(MEDIA_STREAM)
 
+#include "ExceptionOr.h"
 #include "RealtimeMediaSource.h"
 #include "RealtimeMediaSourceSupportedConstraints.h"
 #include <wtf/PassRefPtr.h>
@@ -88,6 +88,15 @@ public:
     WEBCORE_EXPORT void unsetVideoCaptureDeviceManager(CaptureDeviceManager&);
     CaptureDeviceManager* videoCaptureDeviceManager() const { return m_videoCaptureDeviceManager; }
 
+    String hashStringWithSalt(const String& id, const String& hashSalt);
+    WEBCORE_EXPORT std::optional<CaptureDevice> captureDeviceWithUniqueID(const String& id, const String& hashSalt);
+    virtual ExceptionOr<void> setDeviceEnabled(const String&, bool);
+
+    using DevicesChangedObserverToken = unsigned;
+    DevicesChangedObserverToken addDevicesChangedObserver(std::function<void()>&&);
+    void removeDevicesChangedObserver(DevicesChangedObserverToken);
+    void captureDevicesChanged();
+
 protected:
     RealtimeMediaSourceCenter();
 
@@ -99,10 +108,11 @@ protected:
 
     CaptureDeviceManager* m_audioCaptureDeviceManager { nullptr };
     CaptureDeviceManager* m_videoCaptureDeviceManager { nullptr };
+
+    HashMap<unsigned, std::function<void()>> m_devicesChangedObservers;
 };
 
 } // namespace WebCore
 
 #endif // ENABLE(MEDIA_STREAM)
 
-#endif // RealtimeMediaSourceCenter_h
index 8b79090..f6474d6 100644 (file)
@@ -131,8 +131,8 @@ void AVCaptureDeviceManager::refreshAVCaptureDevicesOfType(CaptureDevice::Device
         if (!hasMatchingType)
             continue;
 
-        CaptureDevice existingCaptureDevice;
-        if (captureDeviceFromDeviceID(platformDevice.uniqueID, existingCaptureDevice) && existingCaptureDevice.type() == type)
+        std::optional<CaptureDevice> existingCaptureDevice = captureDeviceFromPersistentID(platformDevice.uniqueID);
+        if (existingCaptureDevice && existingCaptureDevice->type() == type)
             continue;
 
         CaptureDevice captureDevice(platformDevice.uniqueID, type, platformDevice.localizedName);
index 7595cfc..76723ae 100644 (file)
@@ -48,8 +48,13 @@ Vector<CaptureDevice>& MockRealtimeMediaSource::audioDevices()
 {
     static NeverDestroyed<Vector<CaptureDevice>> info;
     if (!info.get().size()) {
-        info.get().append(CaptureDevice("239c24b0-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Audio, "Mock audio device 1"));
-        info.get().append(CaptureDevice("239c24b1-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Audio, "Mock audio device 2"));
+        auto captureDevice = CaptureDevice("239c24b0-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Audio, "Mock audio device 1");
+        captureDevice.setEnabled(true);
+        info.get().append(captureDevice);
+
+        captureDevice = CaptureDevice("239c24b1-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Audio, "Mock audio device 2");
+        captureDevice.setEnabled(true);
+        info.get().append(captureDevice);
     }
     return info;
 }
@@ -58,8 +63,13 @@ Vector<CaptureDevice>& MockRealtimeMediaSource::videoDevices()
 {
     static NeverDestroyed<Vector<CaptureDevice>> info;
     if (!info.get().size()) {
-        info.get().append(CaptureDevice("239c24b2-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Video, "Mock video device 1"));
-        info.get().append(CaptureDevice("239c24b3-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Video, "Mock video device 2"));
+        auto captureDevice = CaptureDevice("239c24b2-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Video, "Mock video device 1");
+        captureDevice.setEnabled(true);
+        info.get().append(captureDevice);
+
+        captureDevice = CaptureDevice("239c24b3-2b15-11e3-8224-0800200c9a66", CaptureDevice::DeviceType::Video, "Mock video device 2");
+        captureDevice.setEnabled(true);
+        info.get().append(captureDevice);
     }
     return info;
 }
index e20c632..1298348 100644 (file)
@@ -132,28 +132,28 @@ void MockRealtimeMediaSourceCenter::createMediaStream(NewMediaStreamHandler comp
     Vector<Ref<RealtimeMediaSource>> videoSources;
 
     if (!audioDeviceID.isEmpty()) {
-        auto& audioDevices = MockRealtimeMediaSource::audioDevices();
-        if (audioDeviceID == audioDevices[0].persistentId()) {
-            auto source = MockRealtimeAudioSource::create(audioDevices[0].label(), audioConstraints);
-            if (source)
-                audioSources.append(source.releaseNonNull());
-        } else if (audioDeviceID == audioDevices[1].persistentId()) {
-            auto source = MockRealtimeAudioSource::create(audioDevices[1].label(), audioConstraints);
-            if (source)
-                audioSources.append(source.releaseNonNull());
+        for (auto& captureDevice : MockRealtimeMediaSource::audioDevices()) {
+            if (!captureDevice.enabled())
+                continue;
+
+            if (audioDeviceID == captureDevice.persistentId()) {
+                auto source = MockRealtimeAudioSource::create(captureDevice.label(), audioConstraints);
+                if (source)
+                    audioSources.append(source.releaseNonNull());
+            }
         }
     }
 
     if (!videoDeviceID.isEmpty()) {
-        auto& videoDevices = MockRealtimeMediaSource::videoDevices();
-        if (videoDeviceID == videoDevices[0].persistentId()) {
-            auto source = MockRealtimeVideoSource::create(videoDevices[0].label(), videoConstraints);
-            if (source)
-                videoSources.append(source.releaseNonNull());
-        } else if (videoDeviceID == videoDevices[1].persistentId()) {
-            auto source = MockRealtimeVideoSource::create(videoDevices[1].label(), videoConstraints);
-            if (source)
-                videoSources.append(source.releaseNonNull());
+        for (auto& captureDevice : MockRealtimeMediaSource::videoDevices()) {
+            if (!captureDevice.enabled())
+                continue;
+
+            if (videoDeviceID == captureDevice.persistentId()) {
+                auto source = MockRealtimeVideoSource::create(captureDevice.label(), videoConstraints);
+                if (source)
+                    videoSources.append(source.releaseNonNull());
+            }
         }
     }
 
@@ -167,8 +167,19 @@ Vector<CaptureDevice> MockRealtimeMediaSourceCenter::getMediaStreamDevices()
 {
     Vector<CaptureDevice> sources;
 
-    sources.appendVector(MockRealtimeMediaSource::audioDevices());
-    sources.appendVector(MockRealtimeMediaSource::videoDevices());
+    for (auto& captureDevice : MockRealtimeMediaSource::audioDevices()) {
+        if (!captureDevice.enabled())
+            continue;
+
+        sources.append(captureDevice);
+    }
+
+    for (auto& captureDevice : MockRealtimeMediaSource::videoDevices()) {
+        if (!captureDevice.enabled())
+            continue;
+
+        sources.append(captureDevice);
+    }
 
     return sources;
 }
@@ -183,6 +194,21 @@ RealtimeMediaSource::CaptureFactory* MockRealtimeMediaSourceCenter::defaultVideo
     return &MockRealtimeVideoSource::factory();
 }
 
+ExceptionOr<void> MockRealtimeMediaSourceCenter::setDeviceEnabled(const String& id, bool enabled)
+{
+    for (auto& captureDevice : getMediaStreamDevices()) {
+        if (id == captureDevice.persistentId()) {
+            if (enabled != captureDevice.enabled()) {
+                captureDevice.setEnabled(enabled);
+                captureDevicesChanged();
+            }
+
+            return { };
+        }
+    }
+
+    return Exception { NOT_FOUND_ERR };
+}
 
 } // namespace WebCore
 
index cde650f..bb2de01 100644 (file)
@@ -47,6 +47,8 @@ private:
 
     RealtimeMediaSource::CaptureFactory* defaultAudioFactory() final;
     RealtimeMediaSource::CaptureFactory* defaultVideoFactory() final;
+
+    ExceptionOr<void> setDeviceEnabled(const String& persistentID, bool) final;
 };
 
 }
index 3f179b7..ff10496 100644 (file)
@@ -4025,6 +4025,27 @@ void Internals::videoSampleAvailable(MediaSample& sample)
     m_nextTrackFramePromise = std::nullopt;
 }
 
+ExceptionOr<void> Internals::setMediaDeviceState(const String& id, const String& property, bool value)
+{
+    auto* document = contextDocument();
+    if (!document)
+        return Exception { INVALID_ACCESS_ERR, ASCIILiteral("No context document") };
+
+    if (!equalLettersIgnoringASCIICase(property, "enabled"))
+        return Exception { INVALID_ACCESS_ERR, makeString("\"" + property, "\" is not a valid property for this method.") };
+
+    auto salt = document->deviceIDHashSalt();
+    std::optional<CaptureDevice> device = RealtimeMediaSourceCenter::singleton().captureDeviceWithUniqueID(id, salt);
+    if (!device)
+        return Exception { INVALID_ACCESS_ERR, makeString("device with ID \"" + id, "\" not found.") };
+
+    auto result = RealtimeMediaSourceCenter::singleton().setDeviceEnabled(device->persistentId(), value);
+    if (result.hasException())
+        return result.releaseException();
+
+    return { };
+}
+
 #endif
 
 } // namespace WebCore
index 98ce61b..14be6c4 100644 (file)
@@ -570,6 +570,7 @@ public:
 #endif
 
 #if ENABLE(MEDIA_STREAM)
+    ExceptionOr<void> setMediaDeviceState(const String& id, const String& property, bool value);
     unsigned long trackAudioSampleCount() const { return m_trackAudioSampleCount; }
     unsigned long trackVideoSampleCount() const { return m_trackVideoSampleCount; }
     void observeMediaStreamTrack(MediaStreamTrack&);
index e38671e..ca453f6 100644 (file)
@@ -536,4 +536,5 @@ enum EventThrottlingBehavior {
     [Conditional=MEDIA_STREAM] Promise<ImageData> grabNextMediaStreamTrackFrame();
     [Conditional=MEDIA_STREAM] readonly attribute unsigned long trackAudioSampleCount;
     [Conditional=MEDIA_STREAM] readonly attribute unsigned long trackVideoSampleCount;
+    [Conditional=MEDIA_STREAM, MayThrowException] void setMediaDeviceState(DOMString deviceID, DOMString property, boolean value);
 };