11cb0ef41Sopenharmony_ci'use strict'; 21cb0ef41Sopenharmony_ci 31cb0ef41Sopenharmony_ciconst common = require('../common'); 41cb0ef41Sopenharmony_ciconst fixtures = require('../common/fixtures'); 51cb0ef41Sopenharmony_ci 61cb0ef41Sopenharmony_ciconst assert = require('assert'); 71cb0ef41Sopenharmony_ciconst events = require('events'); 81cb0ef41Sopenharmony_ciconst fs = require('fs/promises'); 91cb0ef41Sopenharmony_ciconst { createServer } = require('http'); 101cb0ef41Sopenharmony_ci 111cb0ef41Sopenharmony_ciassert.strictEqual(typeof WebAssembly.compileStreaming, 'function'); 121cb0ef41Sopenharmony_ciassert.strictEqual(typeof WebAssembly.instantiateStreaming, 'function'); 131cb0ef41Sopenharmony_ci 141cb0ef41Sopenharmony_ciconst simpleWasmBytes = fixtures.readSync('simple.wasm'); 151cb0ef41Sopenharmony_ci 161cb0ef41Sopenharmony_ci// Sets up an HTTP server with the given response handler and calls fetch() to 171cb0ef41Sopenharmony_ci// obtain a Response from the newly created server. 181cb0ef41Sopenharmony_ciasync function testRequest(handler) { 191cb0ef41Sopenharmony_ci const server = createServer((_, res) => handler(res)).unref().listen(0); 201cb0ef41Sopenharmony_ci await events.once(server, 'listening'); 211cb0ef41Sopenharmony_ci const { port } = server.address(); 221cb0ef41Sopenharmony_ci return fetch(`http://127.0.0.1:${port}/foo.wasm`); 231cb0ef41Sopenharmony_ci} 241cb0ef41Sopenharmony_ci 251cb0ef41Sopenharmony_ci// Runs the given function both with the promise itself and as a continuation 261cb0ef41Sopenharmony_ci// of the promise. We use this to test that the API accepts not just a Response 271cb0ef41Sopenharmony_ci// but also a Promise that resolves to a Response. 281cb0ef41Sopenharmony_cifunction withPromiseAndResolved(makePromise, consume) { 291cb0ef41Sopenharmony_ci return Promise.all([ 301cb0ef41Sopenharmony_ci consume(makePromise()), 311cb0ef41Sopenharmony_ci makePromise().then(consume), 321cb0ef41Sopenharmony_ci ]); 331cb0ef41Sopenharmony_ci} 341cb0ef41Sopenharmony_ci 351cb0ef41Sopenharmony_ci// The makeResponsePromise function must return a Promise that resolves to a 361cb0ef41Sopenharmony_ci// Response. The checkResult function receives the Promise returned by 371cb0ef41Sopenharmony_ci// WebAssembly.compileStreaming and must return a Promise itself. 381cb0ef41Sopenharmony_cifunction testCompileStreaming(makeResponsePromise, checkResult) { 391cb0ef41Sopenharmony_ci return withPromiseAndResolved( 401cb0ef41Sopenharmony_ci common.mustCall(makeResponsePromise, 2), 411cb0ef41Sopenharmony_ci common.mustCall((response) => { 421cb0ef41Sopenharmony_ci return checkResult(WebAssembly.compileStreaming(response)); 431cb0ef41Sopenharmony_ci }, 2) 441cb0ef41Sopenharmony_ci ); 451cb0ef41Sopenharmony_ci} 461cb0ef41Sopenharmony_ci 471cb0ef41Sopenharmony_cifunction testCompileStreamingSuccess(makeResponsePromise) { 481cb0ef41Sopenharmony_ci return testCompileStreaming(makeResponsePromise, async (modPromise) => { 491cb0ef41Sopenharmony_ci const mod = await modPromise; 501cb0ef41Sopenharmony_ci assert.strictEqual(mod.constructor, WebAssembly.Module); 511cb0ef41Sopenharmony_ci }); 521cb0ef41Sopenharmony_ci} 531cb0ef41Sopenharmony_ci 541cb0ef41Sopenharmony_cifunction testCompileStreamingRejection(makeResponsePromise, rejection) { 551cb0ef41Sopenharmony_ci return testCompileStreaming(makeResponsePromise, (modPromise) => { 561cb0ef41Sopenharmony_ci assert.strictEqual(modPromise.constructor, Promise); 571cb0ef41Sopenharmony_ci return assert.rejects(modPromise, rejection); 581cb0ef41Sopenharmony_ci }); 591cb0ef41Sopenharmony_ci} 601cb0ef41Sopenharmony_ci 611cb0ef41Sopenharmony_cifunction testCompileStreamingSuccessUsingFetch(responseCallback) { 621cb0ef41Sopenharmony_ci return testCompileStreamingSuccess(() => testRequest(responseCallback)); 631cb0ef41Sopenharmony_ci} 641cb0ef41Sopenharmony_ci 651cb0ef41Sopenharmony_cifunction testCompileStreamingRejectionUsingFetch(responseCallback, rejection) { 661cb0ef41Sopenharmony_ci return testCompileStreamingRejection(() => testRequest(responseCallback), 671cb0ef41Sopenharmony_ci rejection); 681cb0ef41Sopenharmony_ci} 691cb0ef41Sopenharmony_ci 701cb0ef41Sopenharmony_ci(async () => { 711cb0ef41Sopenharmony_ci // A non-Response should cause a TypeError. 721cb0ef41Sopenharmony_ci for (const invalid of [undefined, null, 0, true, 'foo', {}, [], Symbol()]) { 731cb0ef41Sopenharmony_ci await withPromiseAndResolved(() => Promise.resolve(invalid), (arg) => { 741cb0ef41Sopenharmony_ci return assert.rejects(() => WebAssembly.compileStreaming(arg), { 751cb0ef41Sopenharmony_ci name: 'TypeError', 761cb0ef41Sopenharmony_ci code: 'ERR_INVALID_ARG_TYPE', 771cb0ef41Sopenharmony_ci message: /^The "source" argument .*$/ 781cb0ef41Sopenharmony_ci }); 791cb0ef41Sopenharmony_ci }); 801cb0ef41Sopenharmony_ci } 811cb0ef41Sopenharmony_ci 821cb0ef41Sopenharmony_ci // When given a Promise, any rejection should be propagated as-is. 831cb0ef41Sopenharmony_ci { 841cb0ef41Sopenharmony_ci const err = new RangeError('foo'); 851cb0ef41Sopenharmony_ci await assert.rejects(() => { 861cb0ef41Sopenharmony_ci return WebAssembly.compileStreaming(Promise.reject(err)); 871cb0ef41Sopenharmony_ci }, (actualError) => actualError === err); 881cb0ef41Sopenharmony_ci } 891cb0ef41Sopenharmony_ci 901cb0ef41Sopenharmony_ci // A valid WebAssembly file with the correct MIME type. 911cb0ef41Sopenharmony_ci await testCompileStreamingSuccessUsingFetch((res) => { 921cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm'); 931cb0ef41Sopenharmony_ci res.end(simpleWasmBytes); 941cb0ef41Sopenharmony_ci }); 951cb0ef41Sopenharmony_ci 961cb0ef41Sopenharmony_ci // The same valid WebAssembly file with the same MIME type, but using a 971cb0ef41Sopenharmony_ci // Response whose body is a Buffer instead of calling fetch(). 981cb0ef41Sopenharmony_ci await testCompileStreamingSuccess(() => { 991cb0ef41Sopenharmony_ci return Promise.resolve(new Response(simpleWasmBytes, { 1001cb0ef41Sopenharmony_ci status: 200, 1011cb0ef41Sopenharmony_ci headers: { 'Content-Type': 'application/wasm' } 1021cb0ef41Sopenharmony_ci })); 1031cb0ef41Sopenharmony_ci }); 1041cb0ef41Sopenharmony_ci 1051cb0ef41Sopenharmony_ci // The same valid WebAssembly file with the same MIME type, but using a 1061cb0ef41Sopenharmony_ci // Response whose body is a ReadableStream instead of calling fetch(). 1071cb0ef41Sopenharmony_ci await testCompileStreamingSuccess(async () => { 1081cb0ef41Sopenharmony_ci const handle = await fs.open(fixtures.path('simple.wasm')); 1091cb0ef41Sopenharmony_ci const stream = handle.readableWebStream(); 1101cb0ef41Sopenharmony_ci return Promise.resolve(new Response(stream, { 1111cb0ef41Sopenharmony_ci status: 200, 1121cb0ef41Sopenharmony_ci headers: { 'Content-Type': 'application/wasm' } 1131cb0ef41Sopenharmony_ci })); 1141cb0ef41Sopenharmony_ci }); 1151cb0ef41Sopenharmony_ci 1161cb0ef41Sopenharmony_ci // A larger valid WebAssembly file with the correct MIME type that causes the 1171cb0ef41Sopenharmony_ci // client to pass it to the compiler in many separate chunks. For this, we use 1181cb0ef41Sopenharmony_ci // the same WebAssembly file as in the previous test but insert useless custom 1191cb0ef41Sopenharmony_ci // sections into the WebAssembly module to increase the file size without 1201cb0ef41Sopenharmony_ci // changing the relevant contents. 1211cb0ef41Sopenharmony_ci await testCompileStreamingSuccessUsingFetch((res) => { 1221cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm'); 1231cb0ef41Sopenharmony_ci 1241cb0ef41Sopenharmony_ci // Send the WebAssembly magic and version first. 1251cb0ef41Sopenharmony_ci res.write(simpleWasmBytes.slice(0, 8), common.mustCall()); 1261cb0ef41Sopenharmony_ci 1271cb0ef41Sopenharmony_ci // Construct a 4KiB custom section. 1281cb0ef41Sopenharmony_ci const customSection = Buffer.concat([ 1291cb0ef41Sopenharmony_ci Buffer.from([ 1301cb0ef41Sopenharmony_ci 0, // Custom section. 1311cb0ef41Sopenharmony_ci 134, 32, // (134 & 0x7f) + 0x80 * 32 = 6 + 4096 bytes in this section. 1321cb0ef41Sopenharmony_ci 5, // The length of the following section name. 1331cb0ef41Sopenharmony_ci ]), 1341cb0ef41Sopenharmony_ci Buffer.from('?'.repeat(5)), // The section name 1351cb0ef41Sopenharmony_ci Buffer.from('\0'.repeat(4096)), // The actual section data 1361cb0ef41Sopenharmony_ci ]); 1371cb0ef41Sopenharmony_ci 1381cb0ef41Sopenharmony_ci // Now repeatedly send useless custom sections. These have no use for the 1391cb0ef41Sopenharmony_ci // WebAssembly compiler but they are syntactically valid. The client has to 1401cb0ef41Sopenharmony_ci // keep reading the stream until the very end to obtain the relevant 1411cb0ef41Sopenharmony_ci // sections within the module. This adds up to a few hundred kibibytes. 1421cb0ef41Sopenharmony_ci (function next(i) { 1431cb0ef41Sopenharmony_ci if (i < 100) { 1441cb0ef41Sopenharmony_ci while (res.write(customSection)); 1451cb0ef41Sopenharmony_ci res.once('drain', () => next(i + 1)); 1461cb0ef41Sopenharmony_ci } else { 1471cb0ef41Sopenharmony_ci // End the response body with the actual module contents. 1481cb0ef41Sopenharmony_ci res.end(simpleWasmBytes.slice(8)); 1491cb0ef41Sopenharmony_ci } 1501cb0ef41Sopenharmony_ci })(0); 1511cb0ef41Sopenharmony_ci }); 1521cb0ef41Sopenharmony_ci 1531cb0ef41Sopenharmony_ci // A valid WebAssembly file with an empty parameter in the (otherwise valid) 1541cb0ef41Sopenharmony_ci // MIME type. 1551cb0ef41Sopenharmony_ci await testCompileStreamingRejectionUsingFetch((res) => { 1561cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm;'); 1571cb0ef41Sopenharmony_ci res.end(simpleWasmBytes); 1581cb0ef41Sopenharmony_ci }, { 1591cb0ef41Sopenharmony_ci name: 'TypeError', 1601cb0ef41Sopenharmony_ci code: 'ERR_WEBASSEMBLY_RESPONSE', 1611cb0ef41Sopenharmony_ci message: 'WebAssembly response has unsupported MIME type ' + 1621cb0ef41Sopenharmony_ci "'application/wasm;'" 1631cb0ef41Sopenharmony_ci }); 1641cb0ef41Sopenharmony_ci 1651cb0ef41Sopenharmony_ci // A valid WebAssembly file with an invalid MIME type. 1661cb0ef41Sopenharmony_ci await testCompileStreamingRejectionUsingFetch((res) => { 1671cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/octet-stream'); 1681cb0ef41Sopenharmony_ci res.end(simpleWasmBytes); 1691cb0ef41Sopenharmony_ci }, { 1701cb0ef41Sopenharmony_ci name: 'TypeError', 1711cb0ef41Sopenharmony_ci code: 'ERR_WEBASSEMBLY_RESPONSE', 1721cb0ef41Sopenharmony_ci message: 'WebAssembly response has unsupported MIME type ' + 1731cb0ef41Sopenharmony_ci "'application/octet-stream'" 1741cb0ef41Sopenharmony_ci }); 1751cb0ef41Sopenharmony_ci 1761cb0ef41Sopenharmony_ci // HTTP status code indiciating an error. 1771cb0ef41Sopenharmony_ci await testCompileStreamingRejectionUsingFetch((res) => { 1781cb0ef41Sopenharmony_ci res.statusCode = 418; 1791cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm'); 1801cb0ef41Sopenharmony_ci res.end(simpleWasmBytes); 1811cb0ef41Sopenharmony_ci }, { 1821cb0ef41Sopenharmony_ci name: 'TypeError', 1831cb0ef41Sopenharmony_ci code: 'ERR_WEBASSEMBLY_RESPONSE', 1841cb0ef41Sopenharmony_ci message: /^WebAssembly response has status code 418$/ 1851cb0ef41Sopenharmony_ci }); 1861cb0ef41Sopenharmony_ci 1871cb0ef41Sopenharmony_ci // HTTP status code indiciating an error, but using a Response whose body is 1881cb0ef41Sopenharmony_ci // a Buffer instead of calling fetch(). 1891cb0ef41Sopenharmony_ci await testCompileStreamingSuccess(() => { 1901cb0ef41Sopenharmony_ci return Promise.resolve(new Response(simpleWasmBytes, { 1911cb0ef41Sopenharmony_ci status: 200, 1921cb0ef41Sopenharmony_ci headers: { 'Content-Type': 'application/wasm' } 1931cb0ef41Sopenharmony_ci })); 1941cb0ef41Sopenharmony_ci }); 1951cb0ef41Sopenharmony_ci 1961cb0ef41Sopenharmony_ci // Extra bytes after the WebAssembly file. 1971cb0ef41Sopenharmony_ci await testCompileStreamingRejectionUsingFetch((res) => { 1981cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm'); 1991cb0ef41Sopenharmony_ci res.end(Buffer.concat([simpleWasmBytes, Buffer.from('foo')])); 2001cb0ef41Sopenharmony_ci }, { 2011cb0ef41Sopenharmony_ci name: 'CompileError', 2021cb0ef41Sopenharmony_ci message: /^WebAssembly\.compileStreaming\(\): .*$/ 2031cb0ef41Sopenharmony_ci }); 2041cb0ef41Sopenharmony_ci 2051cb0ef41Sopenharmony_ci // Missing bytes at the end of the WebAssembly file. 2061cb0ef41Sopenharmony_ci await testCompileStreamingRejectionUsingFetch((res) => { 2071cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm'); 2081cb0ef41Sopenharmony_ci res.end(simpleWasmBytes.subarray(0, simpleWasmBytes.length - 3)); 2091cb0ef41Sopenharmony_ci }, { 2101cb0ef41Sopenharmony_ci name: 'CompileError', 2111cb0ef41Sopenharmony_ci message: /^WebAssembly\.compileStreaming\(\): .*$/ 2121cb0ef41Sopenharmony_ci }); 2131cb0ef41Sopenharmony_ci 2141cb0ef41Sopenharmony_ci // Incomplete HTTP response body. The TypeError might come as a surprise, but 2151cb0ef41Sopenharmony_ci // it originates from within fetch(). 2161cb0ef41Sopenharmony_ci await testCompileStreamingRejectionUsingFetch((res) => { 2171cb0ef41Sopenharmony_ci res.setHeader('Content-Length', simpleWasmBytes.length); 2181cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm'); 2191cb0ef41Sopenharmony_ci res.write(simpleWasmBytes.slice(0, 5), common.mustSucceed(() => { 2201cb0ef41Sopenharmony_ci res.destroy(); 2211cb0ef41Sopenharmony_ci })); 2221cb0ef41Sopenharmony_ci }, { 2231cb0ef41Sopenharmony_ci name: 'TypeError', 2241cb0ef41Sopenharmony_ci message: /terminated/ 2251cb0ef41Sopenharmony_ci }); 2261cb0ef41Sopenharmony_ci 2271cb0ef41Sopenharmony_ci // Test "Developer-Facing Display Conventions" described in the WebAssembly 2281cb0ef41Sopenharmony_ci // Web API specification. 2291cb0ef41Sopenharmony_ci await testCompileStreaming(() => testRequest((res) => { 2301cb0ef41Sopenharmony_ci // Respond with a WebAssembly module that only exports a single function, 2311cb0ef41Sopenharmony_ci // which only contains an 'unreachable' instruction. 2321cb0ef41Sopenharmony_ci res.setHeader('Content-Type', 'application/wasm'); 2331cb0ef41Sopenharmony_ci res.end(fixtures.readSync('crash.wasm')); 2341cb0ef41Sopenharmony_ci }), async (modPromise) => { 2351cb0ef41Sopenharmony_ci // Call the WebAssembly function and check that the error stack contains the 2361cb0ef41Sopenharmony_ci // correct "WebAssembly location" as per the specification. 2371cb0ef41Sopenharmony_ci const mod = await modPromise; 2381cb0ef41Sopenharmony_ci const instance = new WebAssembly.Instance(mod); 2391cb0ef41Sopenharmony_ci assert.throws(() => instance.exports.crash(), (err) => { 2401cb0ef41Sopenharmony_ci const stack = err.stack.split(/\n/g); 2411cb0ef41Sopenharmony_ci assert.strictEqual(stack[0], 'RuntimeError: unreachable'); 2421cb0ef41Sopenharmony_ci assert.match(stack[1], 2431cb0ef41Sopenharmony_ci /^\s*at http:\/\/127\.0\.0\.1:\d+\/foo\.wasm:wasm-function\[0\]:0x22$/); 2441cb0ef41Sopenharmony_ci return true; 2451cb0ef41Sopenharmony_ci }); 2461cb0ef41Sopenharmony_ci }); 2471cb0ef41Sopenharmony_ci})().then(common.mustCall()); 248