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