Title: [238738] trunk/Source/WebCore
Revision
238738
Author
calva...@igalia.com
Date
2018-11-30 08:21:35 -0800 (Fri, 30 Nov 2018)

Log Message

[GStreamer][EME] CDMInstance should be shipped as a GstContext to the decryptors
https://bugs.webkit.org/show_bug.cgi?id=192075

Reviewed by Philippe Normand.

So far, we were shipping the CDMInstance in an event to the
decryptors and they were requesting it with bus messages when it
was not found. Now we ship it with a GstContext that is set to the
pipeline and read from the decryptors, which is now always
available.

As a consequence of changing this flow, the attemptToDecrypt one
was affected as well because it was tied to CDMInstance
shipment. A workaround was added: when the decryptors send the
waitingForKey, an attemptToDecrypt will be performed. A FIXME was
added for this. A subconsequence is that
attemptToDecryptWithInstance is reworked to rely always in
attemptToDecryptWithLocal instance, the former becomes final and
the latter virtual.

This is a rework, no new tests needed.

* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::handleMessage):
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp:
(WebCore::MediaPlayerPrivateGStreamerBase::cdmInstanceAttached):
(WebCore::MediaPlayerPrivateGStreamerBase::cdmInstanceDetached):
(WebCore::MediaPlayerPrivateGStreamerBase::attemptToDecryptWithLocalInstance):
(WebCore::MediaPlayerPrivateGStreamerBase::dispatchCDMInstance): Deleted.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h:
* platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
(webkit_media_common_encryption_decrypt_class_init):
(webkitMediaCommonEncryptionDecryptTransformInPlace):
(webkitMediaCommonEncryptionDecryptIsCDMInstanceAvailable):
(webkitMediaCommonEncryptionDecryptSinkEventHandler):
(webKitMediaCommonEncryptionDecryptorSetContext):
* platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp:
(WebCore::MediaPlayerPrivateGStreamerMSE::attemptToDecryptWithLocalInstance):
(WebCore::MediaPlayerPrivateGStreamerMSE::attemptToDecryptWithInstance): Deleted.
* platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.h:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (238737 => 238738)


--- trunk/Source/WebCore/ChangeLog	2018-11-30 15:26:10 UTC (rev 238737)
+++ trunk/Source/WebCore/ChangeLog	2018-11-30 16:21:35 UTC (rev 238738)
@@ -1,3 +1,46 @@
+2018-11-30  Xabier Rodriguez Calvar  <calva...@igalia.com>
+
+        [GStreamer][EME] CDMInstance should be shipped as a GstContext to the decryptors
+        https://bugs.webkit.org/show_bug.cgi?id=192075
+
+        Reviewed by Philippe Normand.
+
+        So far, we were shipping the CDMInstance in an event to the
+        decryptors and they were requesting it with bus messages when it
+        was not found. Now we ship it with a GstContext that is set to the
+        pipeline and read from the decryptors, which is now always
+        available.
+
+        As a consequence of changing this flow, the attemptToDecrypt one
+        was affected as well because it was tied to CDMInstance
+        shipment. A workaround was added: when the decryptors send the
+        waitingForKey, an attemptToDecrypt will be performed. A FIXME was
+        added for this. A subconsequence is that
+        attemptToDecryptWithInstance is reworked to rely always in
+        attemptToDecryptWithLocal instance, the former becomes final and
+        the latter virtual.
+
+        This is a rework, no new tests needed.
+
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
+        (WebCore::MediaPlayerPrivateGStreamer::handleMessage):
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp:
+        (WebCore::MediaPlayerPrivateGStreamerBase::cdmInstanceAttached):
+        (WebCore::MediaPlayerPrivateGStreamerBase::cdmInstanceDetached):
+        (WebCore::MediaPlayerPrivateGStreamerBase::attemptToDecryptWithLocalInstance):
+        (WebCore::MediaPlayerPrivateGStreamerBase::dispatchCDMInstance): Deleted.
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h:
+        * platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
+        (webkit_media_common_encryption_decrypt_class_init):
+        (webkitMediaCommonEncryptionDecryptTransformInPlace):
+        (webkitMediaCommonEncryptionDecryptIsCDMInstanceAvailable):
+        (webkitMediaCommonEncryptionDecryptSinkEventHandler):
+        (webKitMediaCommonEncryptionDecryptorSetContext):
+        * platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp:
+        (WebCore::MediaPlayerPrivateGStreamerMSE::attemptToDecryptWithLocalInstance):
+        (WebCore::MediaPlayerPrivateGStreamerMSE::attemptToDecryptWithInstance): Deleted.
+        * platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.h:
+
 2018-11-30  Zalan Bujtas  <za...@apple.com>
 
         [LFC][BFC] Compute min/maxHeight margins only when they are needed.

Modified: trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp (238737 => 238738)


--- trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp	2018-11-30 15:26:10 UTC (rev 238737)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp	2018-11-30 16:21:35 UTC (rev 238738)
@@ -1329,12 +1329,13 @@
         else if (gst_structure_has_name(structure, "drm-waiting-for-key")) {
             GST_DEBUG_OBJECT(pipeline(), "drm-waiting-for-key message from %s", GST_MESSAGE_SRC_NAME(message));
             setWaitingForKey(true);
+            // FIXME: The decryptors should be able to attempt to decrypt after being created and linked in a pipeline but currently they are not and current
+            // architecture does not make this very easy. Fortunately, the arch will change soon and it does not pay off to fix this now with something that could be
+            // more convoluted. In the meantime, force attempt to decrypt when they get blocked.
+            attemptToDecryptWithLocalInstance();
         } else if (gst_structure_has_name(structure, "drm-key-received")) {
             GST_DEBUG_OBJECT(pipeline(), "drm-key-received message from %s", GST_MESSAGE_SRC_NAME(message));
             setWaitingForKey(false);
-        } else if (gst_structure_has_name(structure, "drm-cdm-instance-needed")) {
-            GST_DEBUG_OBJECT(pipeline(), "drm-cdm-instance-needed message from %s", GST_MESSAGE_SRC_NAME(message));
-            dispatchCDMInstance();
         }
 #endif
         else if (gst_structure_has_name(structure, "http-headers")) {

Modified: trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp (238737 => 238738)


--- trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp	2018-11-30 15:26:10 UTC (rev 238737)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp	2018-11-30 16:21:35 UTC (rev 238738)
@@ -1197,23 +1197,48 @@
 
 void MediaPlayerPrivateGStreamerBase::cdmInstanceAttached(CDMInstance& instance)
 {
-    if (m_cdmInstance != &instance) {
-        m_cdmInstance = &instance;
-        GST_DEBUG_OBJECT(pipeline(), "CDM instance %p set", m_cdmInstance.get());
-        m_protectionCondition.notifyAll();
+    ASSERT(isMainThread());
+
+    if (m_cdmInstance == &instance)
+        return;
+
+    if (!m_pipeline) {
+        GST_ERROR("no pipeline yet");
+        ASSERT_NOT_REACHED();
+        return;
     }
+
+    m_cdmInstance = &instance;
+
+    GRefPtr<GstContext> context = adoptGRef(gst_context_new("drm-cdm-instance", FALSE));
+    GstStructure* contextStructure = gst_context_writable_structure(context.get());
+    gst_structure_set(contextStructure, "cdm-instance", G_TYPE_POINTER, m_cdmInstance.get(), nullptr);
+    gst_element_set_context(GST_ELEMENT(m_pipeline.get()), context.get());
+
+    GST_DEBUG_OBJECT(m_pipeline.get(), "CDM instance %p dispatched as context", m_cdmInstance.get());
+
+    m_protectionCondition.notifyAll();
 }
 
 void MediaPlayerPrivateGStreamerBase::cdmInstanceDetached(CDMInstance& instance)
 {
-#ifdef NDEBUG
-    UNUSED_PARAM(instance);
-#endif
-    if (m_cdmInstance == &instance) {
-        GST_DEBUG_OBJECT(pipeline(), "detaching CDM instance %p", m_cdmInstance.get());
-        m_cdmInstance = nullptr;
-        m_protectionCondition.notifyAll();
+    ASSERT(isMainThread());
+
+    if (m_cdmInstance != &instance) {
+        GST_WARNING("passed CDMInstance %p is different from stored one %p", &instance, m_cdmInstance.get());
+        ASSERT_NOT_REACHED();
+        return;
     }
+
+    ASSERT(m_pipeline);
+
+    GST_DEBUG_OBJECT(m_pipeline.get(), "detaching CDM instance %p, setting empty context", m_cdmInstance.get());
+    m_cdmInstance = nullptr;
+
+    GRefPtr<GstContext> context = adoptGRef(gst_context_new("drm-cdm-instance", FALSE));
+    gst_element_set_context(GST_ELEMENT(m_pipeline.get()), context.get());
+
+    m_protectionCondition.notifyAll();
 }
 
 void MediaPlayerPrivateGStreamerBase::attemptToDecryptWithInstance(CDMInstance& instance)
@@ -1225,8 +1250,7 @@
 
 void MediaPlayerPrivateGStreamerBase::attemptToDecryptWithLocalInstance()
 {
-    bool eventHandled = gst_element_send_event(pipeline(), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB,
-        gst_structure_new("attempt-to-decrypt", "cdm-instance", G_TYPE_POINTER, m_cdmInstance.get(), nullptr)));
+    bool eventHandled = gst_element_send_event(pipeline(), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, gst_structure_new_empty("attempt-to-decrypt")));
     GST_DEBUG("attempting to decrypt, event handled %s", boolForPrinting(eventHandled));
 }
 
@@ -1237,13 +1261,6 @@
     GST_TRACE("emitted decryption cipher key on pipeline, event handled %s", boolForPrinting(eventHandled));
 }
 
-void MediaPlayerPrivateGStreamerBase::dispatchCDMInstance()
-{
-    // This function dispatches the CDMInstance in GStreamer playback pipeline.
-    if (m_cdmInstance)
-        m_player->attemptToDecryptWithInstance(const_cast<CDMInstance&>(*m_cdmInstance.get()));
-}
-
 void MediaPlayerPrivateGStreamerBase::handleProtectionEvent(GstEvent* event)
 {
     if (m_handledProtectionEvents.contains(GST_EVENT_SEQNUM(event))) {

Modified: trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h (238737 => 238738)


--- trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h	2018-11-30 15:26:10 UTC (rev 238737)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h	2018-11-30 16:21:35 UTC (rev 238738)
@@ -150,9 +150,8 @@
     void cdmInstanceDetached(CDMInstance&) override;
     void dispatchDecryptionKey(GstBuffer*);
     void handleProtectionEvent(GstEvent*);
-    void attemptToDecryptWithLocalInstance();
-    void attemptToDecryptWithInstance(CDMInstance&) override;
-    void dispatchCDMInstance();
+    virtual void attemptToDecryptWithLocalInstance();
+    void attemptToDecryptWithInstance(CDMInstance&) final;
     void initializationDataEncountered(InitData&&);
     void setWaitingForKey(bool);
     bool waitingForKey() const override;

Modified: trunk/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp (238737 => 238738)


--- trunk/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp	2018-11-30 15:26:10 UTC (rev 238737)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp	2018-11-30 16:21:35 UTC (rev 238738)
@@ -31,10 +31,12 @@
 #include <wtf/PrintStream.h>
 #include <wtf/RunLoop.h>
 
+using WebCore::CDMInstance;
+
 #define WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_CENC_DECRYPT, WebKitMediaCommonEncryptionDecryptPrivate))
 struct _WebKitMediaCommonEncryptionDecryptPrivate {
     GRefPtr<GstEvent> protectionEvent;
-    RefPtr<WebCore::CDMInstance> cdmInstance;
+    RefPtr<CDMInstance> cdmInstance;
     bool keyReceived;
     bool waitingForKey { false };
     Lock mutex;
@@ -42,13 +44,14 @@
 };
 
 static GstStateChangeReturn webKitMediaCommonEncryptionDecryptorChangeState(GstElement*, GstStateChange transition);
+static void webKitMediaCommonEncryptionDecryptorSetContext(GstElement*, GstContext*);
 static void webKitMediaCommonEncryptionDecryptorFinalize(GObject*);
 static GstCaps* webkitMediaCommonEncryptionDecryptTransformCaps(GstBaseTransform*, GstPadDirection, GstCaps*, GstCaps*);
 static GstFlowReturn webkitMediaCommonEncryptionDecryptTransformInPlace(GstBaseTransform*, GstBuffer*);
 static gboolean webkitMediaCommonEncryptionDecryptSinkEventHandler(GstBaseTransform*, GstEvent*);
 static gboolean webkitMediaCommonEncryptionDecryptorQueryHandler(GstBaseTransform*, GstPadDirection, GstQuery*);
+static bool webkitMediaCommonEncryptionDecryptIsCDMInstanceAvailable(WebKitMediaCommonEncryptionDecrypt*);
 
-
 GST_DEBUG_CATEGORY_STATIC(webkit_media_common_encryption_decrypt_debug_category);
 #define GST_CAT_DEFAULT webkit_media_common_encryption_decrypt_debug_category
 
@@ -65,6 +68,7 @@
 
     GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
     elementClass->change_state = GST_DEBUG_FUNCPTR(webKitMediaCommonEncryptionDecryptorChangeState);
+    elementClass->set_context = GST_DEBUG_FUNCPTR(webKitMediaCommonEncryptionDecryptorSetContext);
 
     GstBaseTransformClass* baseTransformClass = GST_BASE_TRANSFORM_CLASS(klass);
     baseTransformClass->transform_ip = GST_DEBUG_FUNCPTR(webkitMediaCommonEncryptionDecryptTransformInPlace);
@@ -209,7 +213,6 @@
         // Send "drm-cdm-instance-needed" message to the player to resend the CDMInstance if available and inform we are waiting for key.
         priv->waitingForKey = true;
         gst_element_post_message(GST_ELEMENT(self), gst_message_new_element(GST_OBJECT(self), gst_structure_new_empty("drm-waiting-for-key")));
-        gst_element_post_message(GST_ELEMENT(self), gst_message_new_element(GST_OBJECT(self), gst_structure_new_empty("drm-cdm-instance-needed")));
 
         priv->condition.waitFor(priv->mutex, Seconds(5), [priv] {
             return priv->keyReceived;
@@ -299,7 +302,33 @@
     return GST_FLOW_OK;
 }
 
+static bool webkitMediaCommonEncryptionDecryptIsCDMInstanceAvailable(WebKitMediaCommonEncryptionDecrypt* self)
+{
+    WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
 
+    ASSERT(priv->mutex.isLocked());
+
+    if (!priv->cdmInstance) {
+        GRefPtr<GstContext> context = adoptGRef(gst_element_get_context(GST_ELEMENT(self), "drm-cdm-instance"));
+        // According to the GStreamer documentation, if we can't find the context, we should run a downstream query, then an upstream one and then send a bus
+        // message. In this case that does not make a lot of sense since only the app (player) answers it, meaning that no query is going to solve it. A message
+        // could be helpful but the player sets the context as soon as it gets the CDMInstance and if it does not have it, we have no way of asking for one as it is
+        // something provided by crossplatform code. This means that we won't be able to answer the bus request in any way either. Summing up, neither queries nor bus
+        // requests are useful here.
+        if (context) {
+            const GValue* value = gst_structure_get_value(gst_context_get_structure(context.get()), "cdm-instance");
+            priv->cdmInstance = value ? reinterpret_cast<CDMInstance*>(g_value_get_pointer(value)) : nullptr;
+            if (priv->cdmInstance)
+                GST_DEBUG_OBJECT(self, "received new CDMInstance %p", priv->cdmInstance.get());
+            else
+                GST_TRACE_OBJECT(self, "former instance was detached");
+        }
+    }
+
+    GST_TRACE_OBJECT(self, "CDMInstance available %s", boolForPrinting(priv->cdmInstance.get()));
+    return priv->cdmInstance;
+}
+
 static gboolean webkitMediaCommonEncryptionDecryptSinkEventHandler(GstBaseTransform* trans, GstEvent* event)
 {
     WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(trans);
@@ -307,7 +336,6 @@
     WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self);
     gboolean result = FALSE;
 
-
     switch (GST_EVENT_TYPE(event)) {
     case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: {
         // FIXME: https://bugs.webkit.org/show_bug.cgi?id=191355
@@ -316,14 +344,12 @@
         // preferred system ID context is set, any future protection
         // events will not be handled by the demuxer, so the must be
         // handled in here.
-        const GstStructure* structure = gst_event_get_structure(event);
-        gst_structure_get(structure, "cdm-instance", G_TYPE_POINTER, &priv->cdmInstance, nullptr);
-        if (!priv->cdmInstance) {
-            GST_ERROR_OBJECT(self, "No CDM instance received");
+        LockHolder locker(priv->mutex);
+        if (!webkitMediaCommonEncryptionDecryptIsCDMInstanceAvailable(self)) {
+            GST_ERROR_OBJECT(self, "No CDM instance available");
             result = FALSE;
             break;
         }
-        GST_DEBUG_OBJECT(self, "received a cdm instance %p", priv->cdmInstance.get());
 
         if (klass->handleKeyResponse(self, event)) {
             GST_DEBUG_OBJECT(self, "key received");
@@ -374,4 +400,20 @@
     return result;
 }
 
+static void webKitMediaCommonEncryptionDecryptorSetContext(GstElement* element, GstContext* context)
+{
+    WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(element);
+    WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
+
+    if (gst_context_has_context_type(context, "drm-cdm-instance")) {
+        const GValue* value = gst_structure_get_value(gst_context_get_structure(context), "cdm-instance");
+        LockHolder locker(priv->mutex);
+        priv->cdmInstance = value ? reinterpret_cast<CDMInstance*>(g_value_get_pointer(value)) : nullptr;
+        GST_DEBUG_OBJECT(self, "received new CDMInstance %p", priv->cdmInstance.get());
+        return;
+    }
+
+    GST_ELEMENT_CLASS(parent_class)->set_context(element, context);
+}
+
 #endif // ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)

Modified: trunk/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp (238737 => 238738)


--- trunk/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp	2018-11-30 15:26:10 UTC (rev 238737)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp	2018-11-30 16:21:35 UTC (rev 238738)
@@ -909,11 +909,11 @@
 }
 
 #if ENABLE(ENCRYPTED_MEDIA)
-void MediaPlayerPrivateGStreamerMSE::attemptToDecryptWithInstance(CDMInstance& instance)
+void MediaPlayerPrivateGStreamerMSE::attemptToDecryptWithLocalInstance()
 {
-    if (is<CDMInstanceClearKey>(instance)) {
-        auto& ckInstance = downcast<CDMInstanceClearKey>(instance);
-        if (ckInstance.keys().isEmpty())
+    if (is<CDMInstanceClearKey>(*m_cdmInstance)) {
+        auto& clearkeyCDMInstance = downcast<CDMInstanceClearKey>(*m_cdmInstance);
+        if (clearkeyCDMInstance.keys().isEmpty())
             return;
 
         GValue keyIDList = G_VALUE_INIT, keyValueList = G_VALUE_INIT;
@@ -930,7 +930,7 @@
                 gst_value_list_append_and_take_value(valueList, bufferValue);
             };
 
-        for (auto& key : ckInstance.keys()) {
+        for (auto& key : clearkeyCDMInstance.keys()) {
             appendBuffer(&keyIDList, *key.keyIDData);
             appendBuffer(&keyValueList, *key.keyValueData);
         }
@@ -938,7 +938,6 @@
         GUniquePtr<GstStructure> structure(gst_structure_new_empty("drm-cipher-clearkey"));
         gst_structure_set_value(structure.get(), "key-ids", &keyIDList);
         gst_structure_set_value(structure.get(), "key-values", &keyValueList);
-        gst_structure_set(structure.get(), "cdm-instance", G_TYPE_POINTER, &instance, nullptr);
 
         gst_element_send_event(m_playbackPipeline->pipeline(), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure.release()));
     }

Modified: trunk/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.h (238737 => 238738)


--- trunk/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.h	2018-11-30 15:26:10 UTC (rev 238737)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.h	2018-11-30 16:21:35 UTC (rev 238738)
@@ -86,7 +86,7 @@
     static bool supportsAllCodecs(const Vector<String>& codecs);
 
 #if ENABLE(ENCRYPTED_MEDIA)
-    void attemptToDecryptWithInstance(CDMInstance&) final;
+    void attemptToDecryptWithLocalInstance() final;
 #endif
 
 private:
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to