IDB should allow storing RTCCertificate
authoryouenn@apple.com <youenn@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 4 Nov 2018 10:28:11 +0000 (10:28 +0000)
committeryouenn@apple.com <youenn@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 4 Nov 2018 10:28:11 +0000 (10:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191077

Reviewed by Chris Dumez.

LayoutTests/imported/w3c:

* web-platform-tests/webrtc/RTCCertificate-postMessage.html: Added.
* web-platform-tests/webrtc/resources/RTCCertificate-postMessage-iframe.html: Added.

Source/WebCore:

Add support for serialization/deserialization of RTCCertificate.
Store the origin in RTCCertificate and make sure that a certificate
with a different origin cannot be used to create a RTCPeerConnection.

Test: imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage.html

* Modules/mediastream/PeerConnectionBackend.cpp:
(WebCore::PeerConnectionBackend::generateCertificate):
* Modules/mediastream/RTCCertificate.cpp:
(WebCore::RTCCertificate::create):
(WebCore::RTCCertificate::RTCCertificate):
* Modules/mediastream/RTCCertificate.h:
(WebCore::RTCCertificate::origin const):
* Modules/mediastream/RTCPeerConnection.cpp:
(WebCore::RTCPeerConnection::certificatesFromConfiguration):
* Modules/mediastream/RTCPeerConnection.h:
* Modules/mediastream/libwebrtc/LibWebRTCCertificateGenerator.cpp:
(WebCore::LibWebRTCCertificateGenerator::RTCCertificateGeneratorCallback::RTCCertificateGeneratorCallback):
(WebCore::LibWebRTCCertificateGenerator::generateCertificate):
* Modules/mediastream/libwebrtc/LibWebRTCCertificateGenerator.h:
* bindings/js/SerializedScriptValue.cpp:
(WebCore::CloneSerializer::dumpIfTerminal):
(WebCore::CloneDeserializer::CachedString::takeString):
(WebCore::CloneDeserializer::readRTCCertificate):
(WebCore::CloneDeserializer::readTerminal):

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

12 files changed:
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/resources/RTCCertificate-postMessage-iframe.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/mediastream/PeerConnectionBackend.cpp
Source/WebCore/Modules/mediastream/RTCCertificate.cpp
Source/WebCore/Modules/mediastream/RTCCertificate.h
Source/WebCore/Modules/mediastream/RTCPeerConnection.cpp
Source/WebCore/Modules/mediastream/RTCPeerConnection.h
Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCCertificateGenerator.cpp
Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCCertificateGenerator.h
Source/WebCore/bindings/js/SerializedScriptValue.cpp

index 7f98d23..a4abd64 100644 (file)
@@ -1,5 +1,15 @@
 2018-11-04  Youenn Fablet  <youenn@apple.com>
 
+        IDB should allow storing RTCCertificate
+        https://bugs.webkit.org/show_bug.cgi?id=191077
+
+        Reviewed by Chris Dumez.
+
+        * web-platform-tests/webrtc/RTCCertificate-postMessage.html: Added.
+        * web-platform-tests/webrtc/resources/RTCCertificate-postMessage-iframe.html: Added.
+
+2018-11-04  Youenn Fablet  <youenn@apple.com>
+
         Add support for RTCMuxPolicy
         https://bugs.webkit.org/show_bug.cgi?id=191188
 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage.html
new file mode 100644 (file)
index 0000000..5885f9f
--- /dev/null
@@ -0,0 +1,77 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>RTCCertificate persistent Tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+    function findMatchingFingerprint(fingerprints, fingerprint) {
+        for (let f of fingerprints) {
+            if (f.value == fingerprint.value && f.algorithm == fingerprint.algorithm)
+                return true;
+        }
+        return false;
+    }
+
+    function with_iframe(url) {
+        return new Promise(function(resolve) {
+            var frame = document.createElement('iframe');
+            frame.src = url;
+            frame.onload = function() { resolve(frame); };
+            document.body.appendChild(frame);
+        });
+    }
+
+    function testPostMessageCertificate(isCrossOrigin) {
+        promise_test(async t => {
+            let certificate = await  RTCPeerConnection.generateCertificate({ name: 'ECDSA', namedCurve: 'P-256' });
+
+            let url = "resources/RTCCertificate-postMessage-iframe.html";
+            if (isCrossOrigin)
+                url = get_host_info().HTTP_REMOTE_ORIGIN + "/webrtc/" + url;
+
+            let iframe = await with_iframe(url);
+
+            let promise = new Promise((resolve, reject) => {
+                window.onmessage = (event) => {
+                    resolve(event.data);
+                };
+                t.step_timeout(() => reject("Timed out waiting for frame to send back certificate"), 5000);
+            });
+            iframe.contentWindow.postMessage(certificate, "*");
+            let certificate2 = await promise;
+
+            new RTCPeerConnection({certificates: [certificate]});
+
+            new RTCPeerConnection({certificates: [certificate2]});
+
+            assert_equals(certificate.expires, certificate2.expires);
+            for (let fingerprint of certificate2.getFingerprints())
+                assert_true(findMatchingFingerprint(certificate.getFingerprints(), fingerprint), "check fingerprints");
+
+            iframe.remove();
+        }, "Check " + (isCrossOrigin ? "cross-origin" : "same-origin") + " RTCCertificate serialization");
+    }
+
+    testPostMessageCertificate(false);
+    testPostMessageCertificate(true);
+
+    promise_test(async t => {
+        let url = get_host_info().HTTP_REMOTE_ORIGIN + "/webrtc/resources/RTCCertificate-postMessage-iframe.html";
+        let iframe = await with_iframe(url);
+
+        let promise = new Promise((resolve, reject) => {
+            window.onmessage = (event) => {
+                resolve(event.data);
+            };
+            t.step_timeout(() => reject("Timed out waiting for frame to send back certificate"), 5000);
+        });
+        iframe.contentWindow.postMessage(null, "*");
+        let certificate2 = await promise;
+
+        assert_throws("InvalidAccessError", () => { new RTCPeerConnection({certificates: [certificate2]}) });
+        iframe.remove();
+    }, "Check cross-origin created RTCCertificate");
+</script>
+</body>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/resources/RTCCertificate-postMessage-iframe.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/resources/RTCCertificate-postMessage-iframe.html
new file mode 100644 (file)
index 0000000..9e52ba0
--- /dev/null
@@ -0,0 +1,9 @@
+<!doctype html>
+<script>
+window.onmessage = async (event) => {
+    let certificate = event.data;
+    if (!certificate)
+        certificate = await RTCPeerConnection.generateCertificate({ name: 'ECDSA', namedCurve: 'P-256'});
+    event.source.postMessage(certificate, "*");
+}
+</script>
index 36d59d8..0f477a1 100644 (file)
@@ -1,5 +1,38 @@
 2018-11-04  Youenn Fablet  <youenn@apple.com>
 
+        IDB should allow storing RTCCertificate
+        https://bugs.webkit.org/show_bug.cgi?id=191077
+
+        Reviewed by Chris Dumez.
+
+        Add support for serialization/deserialization of RTCCertificate.
+        Store the origin in RTCCertificate and make sure that a certificate
+        with a different origin cannot be used to create a RTCPeerConnection.
+
+        Test: imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage.html
+
+        * Modules/mediastream/PeerConnectionBackend.cpp:
+        (WebCore::PeerConnectionBackend::generateCertificate):
+        * Modules/mediastream/RTCCertificate.cpp:
+        (WebCore::RTCCertificate::create):
+        (WebCore::RTCCertificate::RTCCertificate):
+        * Modules/mediastream/RTCCertificate.h:
+        (WebCore::RTCCertificate::origin const):
+        * Modules/mediastream/RTCPeerConnection.cpp:
+        (WebCore::RTCPeerConnection::certificatesFromConfiguration):
+        * Modules/mediastream/RTCPeerConnection.h:
+        * Modules/mediastream/libwebrtc/LibWebRTCCertificateGenerator.cpp:
+        (WebCore::LibWebRTCCertificateGenerator::RTCCertificateGeneratorCallback::RTCCertificateGeneratorCallback):
+        (WebCore::LibWebRTCCertificateGenerator::generateCertificate):
+        * Modules/mediastream/libwebrtc/LibWebRTCCertificateGenerator.h:
+        * bindings/js/SerializedScriptValue.cpp:
+        (WebCore::CloneSerializer::dumpIfTerminal):
+        (WebCore::CloneDeserializer::CachedString::takeString):
+        (WebCore::CloneDeserializer::readRTCCertificate):
+        (WebCore::CloneDeserializer::readTerminal):
+
+2018-11-04  Youenn Fablet  <youenn@apple.com>
+
         Add support for RTCMuxPolicy
         https://bugs.webkit.org/show_bug.cgi?id=191188
 
index 60e3361..2e69d8e 100644 (file)
@@ -517,7 +517,7 @@ ExceptionOr<Ref<RTCRtpTransceiver>> PeerConnectionBackend::addTransceiver(Ref<Me
 void PeerConnectionBackend::generateCertificate(Document& document, const CertificateInformation& info, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&& promise)
 {
 #if USE(LIBWEBRTC)
-    LibWebRTCCertificateGenerator::generateCertificate(document.page()->libWebRTCProvider(), info, WTFMove(promise));
+    LibWebRTCCertificateGenerator::generateCertificate(document.securityOrigin(), document.page()->libWebRTCProvider(), info, WTFMove(promise));
 #else
     UNUSED_PARAM(document);
     UNUSED_PARAM(expires);
index c137fa1..ab380a9 100644 (file)
 
 namespace WebCore {
 
-Ref<RTCCertificate> RTCCertificate::create(double expires, Vector<DtlsFingerprint>&& fingerprints, String&& pemCertificate, String&& pemPrivateKey)
+Ref<RTCCertificate> RTCCertificate::create(Ref<SecurityOrigin>&& origin, double expires, Vector<DtlsFingerprint>&& fingerprints, String&& pemCertificate, String&& pemPrivateKey)
 {
-    return adoptRef(*new RTCCertificate(expires, WTFMove(fingerprints), WTFMove(pemCertificate), WTFMove(pemPrivateKey)));
+    return adoptRef(*new RTCCertificate(WTFMove(origin), expires, WTFMove(fingerprints), WTFMove(pemCertificate), WTFMove(pemPrivateKey)));
 }
 
-RTCCertificate::RTCCertificate(double expires, Vector<DtlsFingerprint>&& fingerprints, String&& pemCertificate, String&& pemPrivateKey)
-    : m_expires(expires)
+RTCCertificate::RTCCertificate(Ref<SecurityOrigin>&& origin, double expires, Vector<DtlsFingerprint>&& fingerprints, String&& pemCertificate, String&& pemPrivateKey)
+    : m_origin(WTFMove(origin))
+    , m_expires(expires)
     , m_fingerprints(WTFMove(fingerprints))
     , m_pemCertificate(WTFMove(pemCertificate))
     , m_pemPrivateKey(WTFMove(pemPrivateKey))
index 6e27c35..486fd34 100644 (file)
@@ -26,6 +26,7 @@
 
 #if ENABLE(WEB_RTC)
 
+#include "SecurityOrigin.h"
 #include <wtf/RefCounted.h>
 #include <wtf/text/WTFString.h>
 
@@ -38,17 +39,19 @@ public:
         String value;
     };
 
-    static Ref<RTCCertificate> create(double expires, Vector<DtlsFingerprint>&&, String&& pemCertificate, String&& pemPrivateKey);
+    static Ref<RTCCertificate> create(Ref<SecurityOrigin>&&, double expires, Vector<DtlsFingerprint>&&, String&& pemCertificate, String&& pemPrivateKey);
 
     double expires() const { return m_expires; }
     const Vector<DtlsFingerprint>& getFingerprints() const { return m_fingerprints; }
 
     const String& pemCertificate() const { return m_pemCertificate; }
     const String& pemPrivateKey() const { return m_pemPrivateKey; }
+    const SecurityOrigin& origin() const { return m_origin.get(); }
 
 private:
-    RTCCertificate(double expires, Vector<DtlsFingerprint>&&, String&& pemCertificate, String&& pemPrivateKey);
+    RTCCertificate(Ref<SecurityOrigin>&&, double expires, Vector<DtlsFingerprint>&&, String&& pemCertificate, String&& pemPrivateKey);
 
+    Ref<SecurityOrigin> m_origin;
     double m_expires { 0 };
     Vector<DtlsFingerprint> m_fingerprints;
     String m_pemCertificate;
index 4a3fb57..59b145f 100644 (file)
@@ -297,18 +297,23 @@ static inline ExceptionOr<Vector<MediaEndpointConfiguration::IceServerInfo>> ice
     return WTFMove(servers);
 }
 
-static inline ExceptionOr<Vector<MediaEndpointConfiguration::CertificatePEM>> certificatesFromConfiguration(const RTCConfiguration& configuration)
+ExceptionOr<Vector<MediaEndpointConfiguration::CertificatePEM>> RTCPeerConnection::certificatesFromConfiguration(const RTCConfiguration& configuration)
 {
-    std::optional<Exception> exception;
     auto currentMilliSeconds = WallTime::now().secondsSinceEpoch().milliseconds();
-    auto result = WTF::map(configuration.certificates, [&](const auto& certificate) {
-        if (!exception && currentMilliSeconds > certificate->expires())
-            exception = Exception { InvalidAccessError, "Certificate has expired"_s };
-        return MediaEndpointConfiguration::CertificatePEM { certificate->pemCertificate(), certificate->pemPrivateKey(), };
-    });
-    if (exception)
-        return WTFMove(*exception);
-    return WTFMove(result);
+    auto& origin = downcast<Document>(*scriptExecutionContext()).securityOrigin();
+
+    Vector<MediaEndpointConfiguration::CertificatePEM> certificates;
+    certificates.reserveInitialCapacity(configuration.certificates.size());
+    for (auto& certificate : configuration.certificates) {
+        if (!originsMatch(origin, certificate->origin()))
+            return Exception { InvalidAccessError, "Certificate does not have a valid origin" };
+
+        if (currentMilliSeconds > certificate->expires())
+            return Exception { InvalidAccessError, "Certificate has expired"_s };
+
+        certificates.uncheckedAppend(MediaEndpointConfiguration::CertificatePEM { certificate->pemCertificate(), certificate->pemPrivateKey(), });
+    }
+    return WTFMove(certificates);
 }
 
 ExceptionOr<void> RTCPeerConnection::initializeConfiguration(RTCConfiguration&& configuration)
index c993b0f..327aebc 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "ActiveDOMObject.h"
 #include "EventTarget.h"
+#include "MediaEndpointConfiguration.h"
 #include "MediaStream.h"
 #include "RTCConfiguration.h"
 #include "RTCDataChannel.h"
@@ -201,6 +202,8 @@ private:
     bool doClose();
     void doStop();
 
+    ExceptionOr<Vector<MediaEndpointConfiguration::CertificatePEM>> certificatesFromConfiguration(const RTCConfiguration&);
+
     bool m_isStopped { false };
     RTCSignalingState m_signalingState { RTCSignalingState::Stable };
     RTCIceGatheringState m_iceGatheringState { RTCIceGatheringState::New };
index 3a705e6..8a088be 100644 (file)
@@ -47,8 +47,9 @@ static inline String fromStdString(const std::string& value)
 
 class RTCCertificateGeneratorCallback : public ThreadSafeRefCounted<RTCCertificateGeneratorCallback, WTF::DestructionThread::Main>, public rtc::RTCCertificateGeneratorCallback {
 public:
-    explicit RTCCertificateGeneratorCallback(DOMPromiseDeferred<IDLInterface<RTCCertificate>>&& promise)
-        : m_promise(WTFMove(promise))
+    RTCCertificateGeneratorCallback(Ref<SecurityOrigin>&& origin, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&& promise)
+        : m_origin(WTFMove(origin))
+        , m_promise(WTFMove(promise))
     {
     }
 
@@ -63,7 +64,7 @@ public:
 private:
     void OnSuccess(const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) final
     {
-        callOnMainThread([promise = WTFMove(m_promise.value()), certificate]() mutable {
+        callOnMainThread([origin = m_origin.releaseNonNull(), promise = WTFMove(m_promise.value()), certificate]() mutable {
             Vector<RTCCertificate::DtlsFingerprint> fingerprints;
             auto stats = certificate->ssl_certificate().GetStats();
             auto* info = stats.get();
@@ -74,7 +75,7 @@ private:
             };
 
             auto pem = certificate->ToPEM();
-            promise.resolve(RTCCertificate::create(certificate->Expires(), WTFMove(fingerprints), fromStdString(pem.certificate()), fromStdString(pem.private_key())));
+            promise.resolve(RTCCertificate::create(WTFMove(origin), certificate->Expires(), WTFMove(fingerprints), fromStdString(pem.certificate()), fromStdString(pem.private_key())));
         });
     }
 
@@ -85,6 +86,7 @@ private:
         });
     }
 
+    RefPtr<SecurityOrigin> m_origin;
     std::optional<DOMPromiseDeferred<IDLInterface<RTCCertificate>>> m_promise;
 };
 
@@ -102,9 +104,9 @@ static inline rtc::KeyParams keyParamsFromCertificateType(const PeerConnectionBa
     RELEASE_ASSERT_NOT_REACHED();
 }
 
-void generateCertificate(LibWebRTCProvider& provider, const PeerConnectionBackend::CertificateInformation& info, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&& promise)
+void generateCertificate(Ref<SecurityOrigin>&& origin, LibWebRTCProvider& provider, const PeerConnectionBackend::CertificateInformation& info, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&& promise)
 {
-    rtc::scoped_refptr<RTCCertificateGeneratorCallback> callback(new RTCCertificateGeneratorCallback(WTFMove(promise)));
+    rtc::scoped_refptr<RTCCertificateGeneratorCallback> callback(new RTCCertificateGeneratorCallback(WTFMove(origin), WTFMove(promise)));
 
     absl::optional<uint64_t> expiresMs;
     if (info.expires)
index 417c32e..a2b9f38 100644 (file)
 namespace WebCore {
 
 class LibWebRTCProvider;
+class SecurityOrigin;
 
 namespace LibWebRTCCertificateGenerator {
 
-void generateCertificate(LibWebRTCProvider&, const PeerConnectionBackend::CertificateInformation&, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&&);
+void generateCertificate(Ref<SecurityOrigin>&&, LibWebRTCProvider&, const PeerConnectionBackend::CertificateInformation&, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&&);
 
 } // namespace LibWebRTCCertificateGenerator
 
index af7ab3a..1a8d193 100644 (file)
@@ -50,6 +50,7 @@
 #include "JSImageData.h"
 #include "JSMessagePort.h"
 #include "JSNavigator.h"
+#include "JSRTCCertificate.h"
 #include "ScriptExecutionContext.h"
 #include "ScriptState.h"
 #include "SharedBuffer.h"
@@ -161,6 +162,9 @@ enum SerializationTag {
     DOMMatrixTag = 41,
     DOMQuadTag = 42,
     ImageBitmapTransferTag = 43,
+#if ENABLE(WEB_RTC)
+    RTCCertificateTag = 44,
+#endif
     ErrorTag = 255
 };
 
@@ -342,6 +346,12 @@ static const unsigned StringDataIs8BitFlag = 0x80000000;
  *    | DOMMatrix
  *    | DOMQuad
  *    | ImageBitmapTransferTag <value:uint32_t>
+ *    | RTCCertificateTag
+ *
+ * Inside certificate, data is serialized in this format as per spec:
+ *
+ * <expires:double> <certificate:StringData> <origin:StringData> <keyingMaterial:StringData>
+ * We also add fingerprints to make sure we expose to JavaScript the same information.
  *
  * Inside wrapped crypto key, data is serialized in this format:
  *
@@ -1052,7 +1062,21 @@ private:
                 return true;
             }
 #endif
-
+#if ENABLE(WEB_RTC)
+            if (auto* rtcCertificate = JSRTCCertificate::toWrapped(vm, obj)) {
+                write(RTCCertificateTag);
+                write(rtcCertificate->expires());
+                write(rtcCertificate->pemCertificate());
+                write(rtcCertificate->origin().toString());
+                write(rtcCertificate->pemPrivateKey());
+                write(static_cast<unsigned>(rtcCertificate->getFingerprints().size()));
+                for (const auto& fingerprint : rtcCertificate->getFingerprints()) {
+                    write(fingerprint.algorithm);
+                    write(fingerprint.value);
+                }
+                return true;
+            }
+#endif
 #if ENABLE(WEBASSEMBLY)
             if (JSWebAssemblyModule* module = jsDynamicCast<JSWebAssemblyModule*>(vm, obj)) {
                 if (m_context != SerializationContext::WorkerPostMessage && m_context != SerializationContext::WindowPostMessage)
@@ -1743,6 +1767,7 @@ private:
             return m_jsString;
         }
         const String& string() { return m_string; }
+        String takeString() { return WTFMove(m_string); }
 
     private:
         String m_string;
@@ -2649,6 +2674,50 @@ private:
         return getJSValue(bitmap);
     }
 
+#if ENABLE(WEB_RTC)
+    JSValue readRTCCertificate()
+    {
+        double expires;
+        if (!read(expires)) {
+            fail();
+            return JSValue();
+        }
+        CachedStringRef certificate;
+        if (!readStringData(certificate)) {
+            fail();
+            return JSValue();
+        }
+        CachedStringRef origin;
+        if (!readStringData(origin)) {
+            fail();
+            return JSValue();
+        }
+        CachedStringRef keyedMaterial;
+        if (!readStringData(keyedMaterial)) {
+            fail();
+            return JSValue();
+        }
+        unsigned size = 0;
+        if (!read(size))
+            return JSValue();
+
+        Vector<RTCCertificate::DtlsFingerprint> fingerprints;
+        fingerprints.reserveInitialCapacity(size);
+        for (unsigned i = 0; i < size; i++) {
+            CachedStringRef algorithm;
+            if (!readStringData(algorithm))
+                return JSValue();
+            CachedStringRef value;
+            if (!readStringData(value))
+                return JSValue();
+            fingerprints.uncheckedAppend(RTCCertificate::DtlsFingerprint { algorithm->string(), value->string() });
+        }
+
+        auto rtcCertificate = RTCCertificate::create(SecurityOrigin::createFromString(origin->string()), expires, WTFMove(fingerprints), certificate->takeString(), keyedMaterial->takeString());
+        return toJSNewlyCreated(m_exec, jsCast<JSDOMGlobalObject*>(m_globalObject), WTFMove(rtcCertificate));
+    }
+#endif
+
     JSValue readTerminal()
     {
         SerializationTag tag = readTag();
@@ -2939,6 +3008,11 @@ private:
             return readDOMQuad();
         case ImageBitmapTransferTag:
             return readImageBitmap();
+#if ENABLE(WEB_RTC)
+        case RTCCertificateTag:
+            return readRTCCertificate();
+
+#endif
         default:
             m_ptr--; // Push the tag back
             return JSValue();