From dffe215fbb4a58ba4e85d0b03d9e0d96db29c202 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Fri, 13 May 2022 14:28:05 +0200 Subject: [PATCH] feat(ext/web): implement static `Response.json` (#14566) This commit adds support for the static `Response.json` method. --- ext/fetch/23_response.js | 119 +++++++++++++++++++++++++++---------- ext/web/00_infra.js | 34 +++++++---- ext/web/internal.d.ts | 1 + test_util/wpt | 2 +- tools/wpt/expectation.json | 4 +- 5 files changed, 116 insertions(+), 44 deletions(-) diff --git a/ext/fetch/23_response.js b/ext/fetch/23_response.js index f1f6fe1dda992e..8d87944b651a31 100644 --- a/ext/fetch/23_response.js +++ b/ext/fetch/23_response.js @@ -15,7 +15,8 @@ const { isProxy } = Deno.core; const webidl = window.__bootstrap.webidl; const consoleInternal = window.__bootstrap.console; - const { HTTP_TAB_OR_SPACE, regexMatcher } = window.__bootstrap.infra; + const { HTTP_TAB_OR_SPACE, regexMatcher, serializeJSValueToJSONString } = + window.__bootstrap.infra; const { extractBody, mixinBody } = window.__bootstrap.fetchBody; const { getLocationHref } = window.__bootstrap.location; const { extractMimeType } = window.__bootstrap.mimesniff; @@ -157,6 +158,56 @@ return resp; } + /** + * https://fetch.spec.whatwg.org#initialize-a-response + * @param {Response} response + * @param {ResponseInit} init + * @param {{ body: __bootstrap.fetchBody.InnerBody, contentType: string | null } | null} bodyWithType + */ + function initializeAResponse(response, init, bodyWithType) { + // 1. + if ((init.status < 200 || init.status > 599) && init.status != 101) { + throw new RangeError( + `The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`, + ); + } + + // 2. + if ( + init.statusText && + !RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText) + ) { + throw new TypeError("Status text is not valid."); + } + + // 3. + response[_response].status = init.status; + + // 4. + response[_response].statusMessage = init.statusText; + + // 5. + /** @type {__bootstrap.headers.Headers} */ + const headers = response[_headers]; + if (init.headers) { + fillHeaders(headers, init.headers); + } + + // 6. + if (bodyWithType !== null) { + if (nullBodyStatus(response[_response].status)) { + throw new TypeError( + "Response with null body status cannot have body", + ); + } + const { body, contentType } = bodyWithType; + response[_response].body = body; + if (contentType !== null && !headers.has("content-type")) { + headers.append("Content-Type", contentType); + } + } + } + class Response { get [_mimeType]() { const values = getDecodeSplitHeader( @@ -217,6 +268,32 @@ return response; } + /** + * @param {any} data + * @param {ResponseInit} init + * @returns {Response} + */ + static json(data, init = {}) { + const prefix = "Failed to call 'Response.json'"; + data = webidl.converters.any(data); + init = webidl.converters["ResponseInit_fast"](init, { + prefix, + context: "Argument 2", + }); + + const str = serializeJSValueToJSONString(data); + const res = extractBody(str); + res.contentType = "application/json"; + const response = webidl.createBranded(Response); + response[_response] = newInnerResponse(); + response[_headers] = headersFromHeaderList( + response[_response].headerList, + "response", + ); + initializeAResponse(response, init, res); + return response; + } + /** * @param {BodyInit | null} body * @param {ResponseInit} init @@ -232,40 +309,18 @@ context: "Argument 2", }); - if ((init.status < 200 || init.status > 599) && init.status != 101) { - throw new RangeError( - `The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`, - ); - } - - if ( - init.statusText && - !RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText) - ) { - throw new TypeError("Status text is not valid."); - } + this[_response] = newInnerResponse(); + this[_headers] = headersFromHeaderList( + this[_response].headerList, + "response", + ); - this[webidl.brand] = webidl.brand; - const response = newInnerResponse(init.status, init.statusText); - /** @type {InnerResponse} */ - this[_response] = response; - /** @type {Headers} */ - this[_headers] = headersFromHeaderList(response.headerList, "response"); - if (init.headers) { - fillHeaders(this[_headers], init.headers); - } + let bodyWithType = null; if (body !== null) { - if (nullBodyStatus(response.status)) { - throw new TypeError( - "Response with null body status cannot have body", - ); - } - const res = extractBody(body); - response.body = res.body; - if (res.contentType !== null && !this[_headers].has("content-type")) { - this[_headers].append("Content-Type", res.contentType); - } + bodyWithType = extractBody(body); } + initializeAResponse(this, init, bodyWithType); + this[webidl.brand] = webidl.brand; } /** diff --git a/ext/web/00_infra.js b/ext/web/00_infra.js index f46316bfed7e93..bf931f8c7f2e9f 100644 --- a/ext/web/00_infra.js +++ b/ext/web/00_infra.js @@ -11,23 +11,24 @@ ((window) => { const core = Deno.core; const { - Error, - RegExp, + ArrayPrototypeJoin, ArrayPrototypeMap, - StringPrototypeCharCodeAt, + Error, + JSONStringify, NumberPrototypeToString, - StringPrototypePadStart, - TypeError, - ArrayPrototypeJoin, + RegExp, SafeArrayIterator, + String, StringPrototypeCharAt, + StringPrototypeCharCodeAt, StringPrototypeMatch, - StringPrototypeSlice, - String, + StringPrototypePadStart, StringPrototypeReplace, - StringPrototypeToUpperCase, - StringPrototypeToLowerCase, + StringPrototypeSlice, StringPrototypeSubstring, + StringPrototypeToLowerCase, + StringPrototypeToUpperCase, + TypeError, } = window.__bootstrap.primordials; const ASCII_DIGIT = ["\u0030-\u0039"]; @@ -294,6 +295,18 @@ } } + /** + * @param {unknown} value + * @returns {string} + */ + function serializeJSValueToJSONString(value) { + const result = JSONStringify(value); + if (result === undefined) { + throw new TypeError("Value is not JSON serializable."); + } + return result; + } + window.__bootstrap.infra = { collectSequenceOfCodepoints, ASCII_DIGIT, @@ -320,5 +333,6 @@ forgivingBase64Decode, AssertionError, assert, + serializeJSValueToJSONString, }; })(globalThis); diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts index 8e580495b16ad2..04309a77ee63bd 100644 --- a/ext/web/internal.d.ts +++ b/ext/web/internal.d.ts @@ -45,6 +45,7 @@ declare namespace globalThis { }; forgivingBase64Encode(data: Uint8Array): string; forgivingBase64Decode(data: string): Uint8Array; + serializeJSValueToJSONString(value: unknown): string; }; declare var domException: { diff --git a/test_util/wpt b/test_util/wpt index 114d0fd7e438e6..1a8281d7aa0eed 160000 --- a/test_util/wpt +++ b/test_util/wpt @@ -1 +1 @@ -Subproject commit 114d0fd7e438e64905a5673550385dd690ccb763 +Subproject commit 1a8281d7aa0eed050c6d8c151a602ce43dd55406 diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index a5859d0aa5d44a..e71a48e2f391e1 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -3215,7 +3215,9 @@ "response-consume-stream.any.html": true, "response-consume-stream.any.worker.html": true, "response-init-contenttype.any.html": true, - "response-init-contenttype.any.worker.html": true + "response-init-contenttype.any.worker.html": true, + "response-static-json.any.html": true, + "response-static-json.any.worker.html": true }, "body": { "formdata.any.html": [