| /* |
| * Copyright (C) 2024 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. |
| */ |
| |
| #if HAVE(WEB_TRANSPORT) |
| |
| #import "config.h" |
| |
| #import "HTTPServer.h" |
| #import "PlatformUtilities.h" |
| #import "Test.h" |
| #import "TestNavigationDelegate.h" |
| #import "TestUIDelegate.h" |
| #import "TestWKWebView.h" |
| #import "Utilities.h" |
| #import "WebTransportServer.h" |
| #import <CommonCrypto/CommonDigest.h> |
| #import <WebKit/WKPreferencesPrivate.h> |
| #import <WebKit/WKWebsiteDataStorePrivate.h> |
| #import <WebKit/_WKInternalDebugFeature.h> |
| #import <pal/spi/cocoa/NetworkSPI.h> |
| #import <wtf/SoftLinking.h> |
| #import <wtf/spi/cocoa/SecuritySPI.h> |
| #import <wtf/text/MakeString.h> |
| #import <wtf/text/StringBuilder.h> |
| |
| SOFT_LINK_FRAMEWORK(Network) |
| |
| // FIXME: Replace this soft linking with a HAVE macro once rdar://158191390 is available on all tested OS builds. |
| SOFT_LINK_MAY_FAIL(Network, nw_webtransport_options_set_allow_joining_before_ready, void, (nw_protocol_options_t options, bool allow), (options, allow)) |
| |
| // FIXME: Replace this soft linking with a HAVE macro once rdar://164265337 is available on all tested OS builds. |
| SOFT_LINK_MAY_FAIL(Network, nw_webtransport_metadata_set_local_draining, void, (nw_protocol_metadata_t metadata), (metadata)) |
| |
| // FIXME: Replace this soft linking with a HAVE macro once rdar://164514830 is available on all tested OS builds. |
| SOFT_LINK_MAY_FAIL(Network, nw_webtransport_metadata_get_session_closed, bool, (nw_protocol_metadata_t metadata), (metadata)) |
| |
| // FIXME: Replace this soft linking with a HAVE macro once rdar://164917448 is available on all tested OS builds. |
| SOFT_LINK_MAY_FAIL(Network, nw_webtransport_metadata_get_transport_mode, nw_webtransport_transport_mode_t, (nw_protocol_metadata_t metadata), (metadata)) |
| |
| // FIXME: Replace this soft linking with a HAVE macro once rdar://141886375 is available on all tested OS builds. |
| SOFT_LINK_MAY_FAIL(Network, nw_connection_abort_reads, void, (nw_connection_t connection, uint64_t error_code), (connection, error_code)) |
| SOFT_LINK_MAY_FAIL(Network, nw_connection_abort_writes, void, (nw_connection_t connection, uint64_t error_code), (connection, error_code)) |
| SOFT_LINK_MAY_FAIL(Network, nw_webtransport_metadata_set_remote_receive_error_handler, void, (nw_protocol_metadata_t metadata, nw_webtransport_receive_error_handler_t handler, dispatch_queue_t queue), (metadata, handler, queue)) |
| SOFT_LINK_MAY_FAIL(Network, nw_webtransport_metadata_set_remote_send_error_handler, void, (nw_protocol_metadata_t metadata, nw_webtransport_send_error_handler_t handler, dispatch_queue_t queue), (metadata, handler, queue)) |
| |
| namespace TestWebKitAPI { |
| |
| static void enableWebTransport(WKWebViewConfiguration *configuration) |
| { |
| auto preferences = [configuration preferences]; |
| for (_WKFeature *feature in [WKPreferences _features]) { |
| if ([feature.key isEqualToString:@"WebTransportEnabled"]) { |
| [preferences _setEnabled:YES forFeature:feature]; |
| break; |
| } |
| } |
| } |
| |
| static void validateChallenge(NSURLAuthenticationChallenge *challenge, uint16_t port) |
| { |
| EXPECT_WK_STREQ(challenge.protectionSpace.authenticationMethod, NSURLAuthenticationMethodServerTrust); |
| EXPECT_NOT_NULL(challenge.protectionSpace.serverTrust); |
| EXPECT_EQ(challenge.protectionSpace.port, port); |
| EXPECT_WK_STREQ(challenge.protectionSpace.host, "127.0.0.1"); |
| verifyCertificateAndPublicKey(challenge.protectionSpace.serverTrust); |
| } |
| |
| TEST(WebTransport, ClientBidirectional) |
| { |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| request.append('d'); |
| request.append('e'); |
| request.append('f'); |
| co_await connection.awaitableSend(WTF::move(request)); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [webView setNavigationDelegate:delegate.get()]; |
| __block bool challenged { false }; |
| __block uint16_t port = echoServer.port(); |
| delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { |
| validateChallenge(challenge, port); |
| challenged = true; |
| completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); |
| }; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let s = await t.createBidirectionalStream();" |
| " let initialReadStats = await s.readable.getStats();" |
| " let initialWriteStats = await s.writable.getStats();" |
| " let w = s.writable.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let finalWriteStats = await s.writable.getStats();" |
| " let r = s.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " let finalReadStats = await s.readable.getStats();" |
| " await w.close();" |
| " await r.cancel();" |
| " t.close();" |
| " let writableThrew = false;" |
| " let readableThrew = false;" |
| " try { await s.writable.getStats() } catch (e) { writableThrew = true }" |
| " try { await s.readable.getStats() } catch (e) { readableThrew = true }" |
| " alert('successfully read ' + new TextDecoder().decode(value)" |
| " + ', stats before: ' + initialReadStats.bytesReceived + ' ' + initialWriteStats.bytesSent" |
| " + ', stats after: ' + finalReadStats.bytesReceived + ' ' + finalWriteStats.bytesSent" |
| " + ', writable threw after closing: ' + writableThrew" |
| " + ', readable threw after closing: ' + readableThrew" |
| " );" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", |
| port]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| const char* expected = |
| "successfully read abcdef" |
| ", stats before: 0 0" |
| ", stats after: 6 3" |
| ", writable threw after closing: true" |
| ", readable threw after closing: true"; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], expected); |
| EXPECT_TRUE(challenged); |
| } |
| |
| TEST(WebTransport, Datagram) |
| { |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto datagramConnection = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Datagram); |
| auto request = co_await datagramConnection.awaitableReceiveBytes(); |
| co_await datagramConnection.awaitableSend(WTF::move(request)); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [webView setNavigationDelegate:delegate.get()]; |
| __block bool challenged { false }; |
| __block uint16_t port = echoServer.port(); |
| delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { |
| validateChallenge(challenge, port); |
| challenged = true; |
| completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); |
| }; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " var s = 'unexpected unset value';" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:1/');" |
| " await t.ready;" |
| " alert('unexpected success');" |
| " } catch (e) { s = 'abc' }" |
| " " |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " let g = t.createSendGroup();" |
| " await t.ready;" |
| " let w = t.datagrams.createWritable({ sendGroup : g }).getWriter();" |
| " await w.write(new TextEncoder().encode(s));" |
| " let r = t.datagrams.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " await r.cancel();" |
| " const groupStats = await g.getStats();" |
| " t.close();" |
| " await w.closed;" |
| " alert('successfully read ' + new TextDecoder().decode(value) + ', group sent ' + groupStats.bytesWritten + ' bytes, maxDatagramSize ' + t.datagrams.maxDatagramSize + ', reliability ' + t.reliability);" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", |
| port]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| if (!canLoadnw_webtransport_metadata_get_transport_mode()) |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc, group sent 3 bytes, maxDatagramSize 65535, reliability pending"); |
| else |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc, group sent 3 bytes, maxDatagramSize 65535, reliability supports-unreliable"); |
| EXPECT_TRUE(challenged); |
| } |
| |
| TEST(WebTransport, Unidirectional) |
| { |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| auto serverUnidirectionalStream = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Unidirectional); |
| co_await serverUnidirectionalStream.awaitableSend(WTF::move(request)); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [webView setNavigationDelegate:delegate.get()]; |
| __block bool challenged { false }; |
| __block uint16_t port = echoServer.port(); |
| delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { |
| validateChallenge(challenge, port); |
| challenged = true; |
| completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); |
| }; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let c = await t.createUnidirectionalStream();" |
| " let w = c.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " await w.close();" |
| " let sr = t.incomingUnidirectionalStreams.getReader();" |
| " let {value: s, d} = await sr.read();" |
| " let r = s.getReader();" |
| " const { value, done } = await r.read();" |
| " await r.cancel();" |
| " t.close();" |
| " alert('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", |
| port]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc"); |
| EXPECT_TRUE(challenged); |
| } |
| |
| TEST(WebTransport, ServerBidirectional) |
| { |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| auto serverBidirectionalStream = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Bidirectional); |
| co_await serverBidirectionalStream.awaitableSend(WTF::move(request)); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [webView setNavigationDelegate:delegate.get()]; |
| __block bool challenged { false }; |
| __block uint16_t port = echoServer.port(); |
| delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { |
| validateChallenge(challenge, port); |
| challenged = true; |
| completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); |
| }; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let c = await t.createBidirectionalStream();" |
| " let w = c.writable.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let sr = t.incomingBidirectionalStreams.getReader();" |
| " let {value: s, d} = await sr.read();" |
| " let r = s.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " await r.cancel();" |
| " await s.writable.getWriter().close();" |
| " t.close();" |
| " alert('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", |
| port]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc"); |
| EXPECT_TRUE(challenged); |
| } |
| |
| TEST(WebTransport, NetworkProcessCrash) |
| { |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto datagramConnection = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Datagram); |
| co_await datagramConnection.awaitableSend(@"abc"); |
| auto bidiConnection = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Bidirectional); |
| co_await bidiConnection.awaitableSend(@"abc", false); |
| auto uniConnection = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Unidirectional); |
| co_await uniConnection.awaitableSend(@"abc", false); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [webView setNavigationDelegate:delegate.get()]; |
| __block bool challenged { false }; |
| __block uint16_t port = echoServer.port(); |
| delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { |
| validateChallenge(challenge, port); |
| challenged = true; |
| completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); |
| }; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>" |
| "let session = new WebTransport('https://127.0.0.1:%d/');" |
| "let bidiStream = null;" |
| "let uniStream = null;" |
| "let incomingBidiStream = null;" |
| "let incomingUniStream = null;" |
| "let data = new TextEncoder().encode('abc');" |
| "async function setupSession() {" |
| " try {" |
| " await session.ready;" |
| " bidiStream = await session.createBidirectionalStream();" |
| " uniStream = await session.createUnidirectionalStream();" |
| " incomingBidiStream = await getIncomingBidiStream();" |
| " incomingUniStream = await getIncomingUniStream();" |
| " alert('successfully established');" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; setupSession();" |
| "async function getIncomingBidiStream() {" |
| " let reader = session.incomingBidirectionalStreams.getReader();" |
| " let {value: s, d} = await reader.read();" |
| " reader.releaseLock();" |
| " return s;" |
| "};" |
| "async function getIncomingUniStream() {" |
| " let reader = session.incomingUnidirectionalStreams.getReader();" |
| " let {value: s, d} = await reader.read();" |
| " reader.releaseLock();" |
| " return s;" |
| "};" |
| "async function readFromBidiStream() {" |
| " let reader = bidiStream.readable.getReader();" |
| " let {value: c, d} = await reader.read();" |
| " reader.releaseLock();" |
| " return c;" |
| "};" |
| "async function readFromIncomingBidiStream() {" |
| " let reader = incomingBidiStream.readable.getReader();" |
| " let {value: c, d} = await reader.read();" |
| " reader.releaseLock();" |
| " return c;" |
| "};" |
| "async function readFromIncomingUniStream() {" |
| " let reader = incomingUniStream.getReader();" |
| " let {value: c, d} = await reader.read();" |
| " reader.releaseLock();" |
| " return c;" |
| "};" |
| "async function readDatagram() {" |
| " let reader = session.datagrams.readable.getReader();" |
| " let {value: c, d} = await reader.read();" |
| " reader.releaseLock();" |
| " return c;" |
| "};" |
| "async function writeOnBidiStream() {" |
| " let writer = bidiStream.writable.getWriter();" |
| " await writer.write(data);" |
| " writer.releaseLock();" |
| " return;" |
| "};" |
| "async function writeOnUniStream() {" |
| " let writer = uniStream.getWriter();" |
| " await writer.write(data);" |
| " writer.releaseLock();" |
| " return;" |
| "};" |
| "async function writeOnIncomingBidiStream() {" |
| " let writer = incomingBidiStream.writable.getWriter();" |
| " await writer.write(data);" |
| " writer.releaseLock();" |
| " return;" |
| "};" |
| "async function writeDatagram() {" |
| " let writer = session.datagrams.createWritable().getWriter();" |
| " await writer.write(data);" |
| " writer.releaseLock();" |
| " return;" |
| "};" |
| "</script>", |
| port]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully established"); |
| EXPECT_TRUE(challenged); |
| |
| pid_t networkProcessIdentifier = [configuration.get().websiteDataStore _networkProcessIdentifier]; |
| |
| kill(networkProcessIdentifier, SIGKILL); |
| |
| NSError *error = nil; |
| |
| id obj = [webView objectByCallingAsyncFunction:@"return await session.createBidirectionalStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await session.createUnidirectionalStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await getIncomingBidiStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await getIncomingUniStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await readFromBidiStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await readFromIncomingBidiStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await readFromIncomingUniStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await readDatagram()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await writeOnBidiStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await writeOnUniStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await writeOnIncomingBidiStream()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByCallingAsyncFunction:@"return await writeDatagram()" withArguments:@{ } error:&error]; |
| EXPECT_EQ(obj, nil); |
| EXPECT_NOT_NULL(error); |
| error = nil; |
| |
| obj = [webView objectByEvaluatingJavaScript:@"session.close()"]; |
| EXPECT_EQ(obj, nil); |
| } |
| |
| TEST(WebTransport, Worker) |
| { |
| WebTransportServer transportServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| auto serverBidirectionalStream = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Bidirectional); |
| co_await serverBidirectionalStream.awaitableSend(WTF::move(request)); |
| }); |
| |
| auto mainHTML = "<script>" |
| "const worker = new Worker('worker.js');" |
| "worker.onmessage = (event) => {" |
| " alert('message from worker: ' + event.data);" |
| "};" |
| "</script>"_s; |
| |
| NSString *workerJS = [NSString stringWithFormat:@"" |
| "async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " %s" |
| " let c = await t.createBidirectionalStream();" |
| " let w = c.writable.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let sr = t.incomingBidirectionalStreams.getReader();" |
| " let {value: s, d} = await sr.read();" |
| " let r = s.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " self.postMessage('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { self.postMessage('caught ' + e); }" |
| "}; test();", transportServer.port(), canLoadnw_webtransport_options_set_allow_joining_before_ready() ? "" : "await t.ready;"]; |
| |
| HTTPServer loadingServer({ |
| { "/"_s, { mainHTML } }, |
| { "/worker.js"_s, { { { "Content-Type"_s, "text/javascript"_s } }, workerJS } } |
| }); |
| |
| RetainPtr configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| [webView loadRequest:loadingServer.request()]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "message from worker: successfully read abc"); |
| } |
| |
| TEST(WebTransport, WorkerAfterNetworkProcessCrash) |
| { |
| WebTransportServer transportServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| auto serverBidirectionalStream = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Bidirectional); |
| co_await serverBidirectionalStream.awaitableSend(WTF::move(request)); |
| }); |
| |
| auto mainHTML = "<script>" |
| "const worker = new Worker('worker.js');" |
| "worker.onmessage = (event) => {" |
| " alert('message from worker: ' + event.data);" |
| "};" |
| "</script>"_s; |
| |
| NSString *workerJS = [NSString stringWithFormat:@"" |
| "async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let c = await t.createBidirectionalStream();" |
| " let w = c.writable.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let sr = t.incomingBidirectionalStreams.getReader();" |
| " let {value: s, d} = await sr.read();" |
| " let r = s.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " self.postMessage('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { self.postMessage('caught ' + e); }" |
| "};" |
| "addEventListener('message', test);" |
| "self.postMessage('started worker');", transportServer.port()]; |
| |
| HTTPServer loadingServer({ |
| { "/"_s, { mainHTML } }, |
| { "/worker.js"_s, { { { "Content-Type"_s, "text/javascript"_s } }, workerJS } } |
| }); |
| |
| RetainPtr configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| [webView loadRequest:loadingServer.request()]; |
| [delegate waitForDidFinishNavigation]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "message from worker: started worker"); |
| kill([configuration.get().websiteDataStore _networkProcessIdentifier], SIGKILL); |
| while ([[configuration websiteDataStore] _networkProcessExists]) |
| TestWebKitAPI::Util::spinRunLoop(); |
| [webView objectByEvaluatingJavaScript:@"'wait for web process to be informed of network process termination'"]; |
| [webView evaluateJavaScript:@"worker.postMessage('start')" completionHandler:nil]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "message from worker: successfully read abc"); |
| } |
| |
| TEST(WebTransport, CreateStreamsBeforeReady) |
| { |
| if (!canLoadnw_webtransport_options_set_allow_joining_before_ready()) |
| return; |
| |
| WebTransportServer datagramServer([](ConnectionGroup group) -> ConnectionTask { |
| auto datagramConnection = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Datagram); |
| auto request = co_await datagramConnection.awaitableReceiveBytes(); |
| co_await datagramConnection.awaitableSend(WTF::move(request)); |
| }); |
| |
| WebTransportServer streamServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| co_await connection.awaitableSend(WTF::move(request)); |
| }); |
| |
| RetainPtr configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| NSString *datagramHTML = [NSString stringWithFormat:@"<script>" |
| "async function test() {" |
| " try {" |
| " const w = new WebTransport('https://127.0.0.1:%d/');" |
| " const writer = w.datagrams.createWritable().getWriter();" |
| " const reader = w.datagrams.readable.getReader();" |
| " await writer.write(new TextEncoder().encode('abc'));" |
| " const { value, done } = await reader.read();" |
| " alert('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test()" |
| "</script>", datagramServer.port()]; |
| [webView loadHTMLString:datagramHTML baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc"); |
| |
| NSString *streamHTML = [NSString stringWithFormat:@"<script>" |
| "async function test() {" |
| " try {" |
| " const w = new WebTransport('https://127.0.0.1:%d/');" |
| " let c = await w.createBidirectionalStream();" |
| " let writer = c.writable.getWriter();" |
| " await writer.write(new TextEncoder().encode('abc'));" |
| " let reader = await c.readable.getReader();" |
| " const { value, done } = await reader.read();" |
| " alert('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test()" |
| "</script>", streamServer.port()]; |
| [webView loadHTMLString:streamHTML baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc"); |
| } |
| |
| // FIXME: Re-enable this test on iOS when rdar://161858543 is resolved. |
| #if PLATFORM(MAC) |
| TEST(WebTransport, CSP) |
| #else |
| TEST(WebTransport, DISABLED_CSP) |
| #endif |
| { |
| WebTransportServer server([](ConnectionGroup group) -> ConnectionTask { |
| co_return; |
| }); |
| |
| RetainPtr configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| auto runTest = [&] (const char* allowedDestination) { |
| NSString *html = [NSString stringWithFormat:@"<script>" |
| "function setCSP(destination) {" |
| " let meta = document.createElement('meta');" |
| " meta.httpEquiv = 'Content-Security-Policy';" |
| " meta.content = 'connect-src ' + destination;" |
| " document.head.appendChild(meta);" |
| "};" |
| "async function test() {" |
| " try {" |
| " setCSP('%s');" |
| " const w = new WebTransport('https://localhost:%d/');" |
| " await w.ready;" |
| " alert('ready');" |
| " } catch (e) { alert('caught ' + e.name + ' ' + e.source + ' ' + e.streamErrorCode + ' ' + (e instanceof WebTransportError)); }" |
| "}; test()" |
| "</script>", allowedDestination, server.port()]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| return [webView _test_waitForAlert]; |
| }; |
| EXPECT_WK_STREQ(runTest("none"), "caught WebTransportError session null true"); |
| EXPECT_WK_STREQ(runTest([NSString stringWithFormat:@"https://localhost:%d", server.port()].UTF8String), "ready"); |
| } |
| |
| TEST(WebTransport, ServerCertificateHashes) |
| { |
| auto runTest = [] (uint64_t certLifetime, bool matchHash = true) { |
| NSDictionary* options = @{ |
| (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom, |
| (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, |
| (id)kSecAttrKeySizeInBits: @256, |
| }; |
| CFErrorRef error = nullptr; |
| RetainPtr privateKey = adoptCF(SecKeyCreateRandomKey((__bridge CFDictionaryRef)options, &error)); |
| EXPECT_NULL(error); |
| |
| NSArray *subject = @[]; |
| NSDictionary *parameters = @{ |
| (__bridge NSString*)kSecCertificateLifetime: @(certLifetime) |
| }; |
| RetainPtr certificate = adoptCF(SecGenerateSelfSignedCertificate((__bridge CFArrayRef)subject, (__bridge CFDictionaryRef)parameters, nullptr, privateKey.get())); |
| RetainPtr identity = adoptCF(SecIdentityCreate(kCFAllocatorDefault, certificate.get(), privateKey.get())); |
| RetainPtr certificateDER = adoptNS((__bridge NSData *)SecCertificateCopyData(certificate.get())); |
| |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| auto serverBidirectionalStream = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Bidirectional); |
| co_await serverBidirectionalStream.awaitableSend(WTF::move(request)); |
| }, adoptNS(sec_identity_create(identity.get())).get()); |
| |
| std::array<uint8_t, CC_SHA256_DIGEST_LENGTH> sha2 { }; |
| if (matchHash) |
| CC_SHA256([certificateDER bytes], [certificateDER length], sha2.data()); |
| |
| StringBuilder certificateBytes; |
| for (NSUInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { |
| if (i) |
| certificateBytes.append(", "_s); |
| certificateBytes.append(makeString((unsigned)sha2[i])); |
| } |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " const hashValue = new Uint8Array([%s]);" |
| " let t = new WebTransport('https://127.0.0.1:%d/',{serverCertificateHashes: [{algorithm: 'sha-256',value: hashValue}]});" |
| " try { await t.ready } catch (e) { alert('did not become ready') };" |
| " let c = await t.createBidirectionalStream();" |
| " let w = c.writable.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let sr = t.incomingBidirectionalStreams.getReader();" |
| " let {value: s, d} = await sr.read();" |
| " let r = s.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " alert('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", certificateBytes.toString().utf8().data(), echoServer.port()]; |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| __block bool challenged { false }; |
| delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { |
| challenged = true; |
| completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); |
| }; |
| |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| NSString *result = [webView _test_waitForAlert]; |
| EXPECT_FALSE(challenged); |
| return result; |
| }; |
| |
| constexpr uint64_t oneWeekValidity = 7 * 24 * 60 * 60; |
| EXPECT_WK_STREQ(runTest(oneWeekValidity), "successfully read abc"); |
| // FIXME: Add negative tests once rdar://161855525 is fixed. |
| } |
| |
| TEST(WebTransport, ServerConnectionTermination) |
| { |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| EXPECT_EQ(request.size(), 3u); |
| group.cancel(); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let c = await t.createUnidirectionalStream();" |
| " let w = c.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let closeInfo = await t.closed;" |
| " alert('successfully read closeInfo (' + closeInfo.closeCode + ', ' + closeInfo.reason + ')');" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", echoServer.port()]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], canLoadnw_webtransport_metadata_get_session_closed() ? "successfully read closeInfo (0, )" : "caught WebTransportError"); |
| } |
| |
| TEST(WebTransport, BackForwardCache) |
| { |
| bool serverConnectionTerminatedByClient { false }; |
| WebTransportServer echoServer([&](ConnectionGroup group) -> ConnectionTask { |
| auto datagramConnection = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Datagram); |
| auto request = co_await datagramConnection.awaitableReceiveBytes(); |
| co_await datagramConnection.awaitableSend(WTF::move(request)); |
| co_await group.awaitableFailure(); |
| serverConnectionTerminatedByClient = true; |
| }); |
| |
| NSString *mainHTML = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let w = t.datagrams.createWritable().getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let r = t.datagrams.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " alert('successfully read ' + new TextDecoder().decode(value));" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", echoServer.port()]; |
| |
| HTTPServer loadingServer({ |
| { "/"_s, { mainHTML } }, |
| { "/other"_s, { @"<script>alert('loaded')</script>" } } |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| [webView loadRequest:loadingServer.request()]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc"); |
| [webView loadRequest:loadingServer.request("/other"_s)]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "loaded"); |
| Util::run(&serverConnectionTerminatedByClient); |
| } |
| |
| TEST(WebTransport, ServerDrain) |
| { |
| if (!canLoadnw_webtransport_metadata_set_local_draining()) |
| return; |
| |
| WebTransportServer echoServer([](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| EXPECT_EQ(request.size(), 3u); |
| group.drainWebTransportSession(); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let c = await t.createUnidirectionalStream();" |
| " let w = c.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " await t.draining;" |
| " alert('successfully receieved draining');" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", echoServer.port()]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully receieved draining"); |
| } |
| |
| // FIXME: Re-enable this test once rdar://157795985 is widely available. |
| TEST(WebTransport, DISABLED_ClientStreamAborts) |
| { |
| if (!canLoadnw_webtransport_metadata_set_remote_receive_error_handler() || !canLoadnw_webtransport_metadata_set_remote_send_error_handler()) |
| return; |
| |
| bool receivedReadError = false; |
| bool receivedWriteError = false; |
| uint64_t readErrorCode = 0; |
| uint64_t writeErrorCode = 0; |
| |
| WebTransportServer echoServer([&](ConnectionGroup group) -> ConnectionTask { |
| auto connection = co_await group.receiveIncomingConnection(); |
| connection.setRemoteReceiveErrorHandler([&](uint64_t errorCode) { |
| readErrorCode = errorCode; |
| receivedReadError = true; |
| }); |
| connection.setRemoteSendErrorHandler([&](uint64_t errorCode) { |
| writeErrorCode = errorCode; |
| receivedWriteError = true; |
| }); |
| auto request = co_await connection.awaitableReceiveBytes(); |
| co_await connection.awaitableSend(WTF::move(request), false); |
| co_await group.awaitableFailure(); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let s = await t.createBidirectionalStream();" |
| " let w = s.writable.getWriter();" |
| " await w.write(new TextEncoder().encode('abc'));" |
| " let r = s.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " let received = new TextDecoder().decode(value);" |
| " await w.abort(new WebTransportError('write error', {streamErrorCode: 42}));" |
| " await r.cancel(new WebTransportError('read error', {streamErrorCode: 123}));" |
| " t.close();" |
| " alert('successfully read ' + received + ' then aborted stream');" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", echoServer.port()]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "successfully read abc then aborted stream"); |
| |
| Util::run(&receivedReadError); |
| Util::run(&receivedWriteError); |
| EXPECT_EQ(readErrorCode, 42u); |
| EXPECT_EQ(writeErrorCode, 123u); |
| } |
| |
| // FIXME: Re-enable this test once rdar://157795985 is widely available. |
| TEST(WebTransport, DISABLED_ServerStreamAborts) |
| { |
| if (!canLoadnw_connection_abort_reads() || !canLoadnw_connection_abort_writes()) |
| return; |
| |
| WebTransportServer server([](ConnectionGroup group) -> ConnectionTask { |
| auto stream = group.createWebTransportConnection(ConnectionGroup::ConnectionType::Bidirectional); |
| co_await stream.awaitableSend(@"abc", false); |
| auto echo = co_await stream.awaitableReceiveBytes(); |
| EXPECT_EQ(echo.size(), 3u); |
| |
| stream.abortReads(456); |
| stream.abortWrites(789); |
| co_await group.awaitableFailure(); |
| }); |
| |
| auto configuration = adoptNS([WKWebViewConfiguration new]); |
| enableWebTransport(configuration.get()); |
| auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); |
| auto delegate = adoptNS([TestNavigationDelegate new]); |
| [delegate allowAnyTLSCertificate]; |
| [webView setNavigationDelegate:delegate.get()]; |
| |
| NSString *html = [NSString stringWithFormat:@"" |
| "<script>async function test() {" |
| " try {" |
| " let t = new WebTransport('https://127.0.0.1:%d/');" |
| " await t.ready;" |
| " let sr = t.incomingBidirectionalStreams.getReader();" |
| " let {value: s, d} = await sr.read();" |
| " let r = s.readable.getReader();" |
| " const { value, done } = await r.read();" |
| " let received = new TextDecoder().decode(value);" |
| " let w = s.writable.getWriter();" |
| " await w.write(value);" |
| " let readError = null;" |
| " let writeError = null;" |
| " try {" |
| " await r.read();" |
| " } catch (e) {" |
| " readError = e.streamErrorCode;" |
| " }" |
| " try {" |
| " await w.write(new TextEncoder().encode('test'));" |
| " } catch (e) {" |
| " writeError = e.streamErrorCode;" |
| " }" |
| " t.close();" |
| " alert('received ' + received + ', read error: ' + readError + ', write error: ' + writeError);" |
| " } catch (e) { alert('caught ' + e); }" |
| "}; test();" |
| "</script>", server.port()]; |
| [webView loadHTMLString:html baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| EXPECT_WK_STREQ([webView _test_waitForAlert], "received abc, read error: 789, write error: 456"); |
| } |
| |
| } // namespace TestWebKitAPI |
| |
| #endif // HAVE(WEB_TRANSPORT) |