diff options
author | 2021-07-01 05:12:15 -0700 | |
---|---|---|
committer | 2021-07-01 05:12:15 -0700 | |
commit | 88aad6aeb19f3d1d73ced59a7a5aaddc2d7408ee (patch) | |
tree | 48c5a443c72d4739f154f2e9f63187b49bb78720 | |
parent | a8536a73373794dc9748a97f0e4668d6bf708ca1 (diff) | |
download | bun-88aad6aeb19f3d1d73ced59a7a5aaddc2d7408ee.tar.gz bun-88aad6aeb19f3d1d73ced59a7a5aaddc2d7408ee.tar.zst bun-88aad6aeb19f3d1d73ced59a7a5aaddc2d7408ee.zip |
this kind of works, but there is a crash when bundling. I think its missing a Stmt.Data.Store.reset()
-rw-r--r-- | .vscode/launch.json | 7 | ||||
-rw-r--r-- | .vscode/settings.json | 0 | ||||
-rw-r--r-- | build.zig | 10 | ||||
-rw-r--r-- | demos/css-stress-test/framework.tsx | 28 | ||||
-rw-r--r-- | demos/css-stress-test/package.json | 1 | ||||
-rw-r--r-- | demos/css-stress-test/src/index.tsx | 8 | ||||
-rw-r--r-- | demos/css-stress-test/tsconfig.json | 2 | ||||
-rw-r--r-- | out.txt | 318 | ||||
-rw-r--r-- | src/api/schema.d.ts | 1 | ||||
-rw-r--r-- | src/api/schema.js | 10 | ||||
-rw-r--r-- | src/api/schema.peechy | 2 | ||||
-rw-r--r-- | src/api/schema.zig | 10 | ||||
-rw-r--r-- | src/cli.zig | 8 | ||||
-rw-r--r-- | src/http.zig | 249 | ||||
-rw-r--r-- | src/http/mime_type.zig | 6 | ||||
-rw-r--r-- | src/javascript/jsc/JavascriptCore.zig | 23 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 234 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 201 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 903 | ||||
-rw-r--r-- | src/string_immutable.zig | 9 | ||||
-rw-r--r-- | src/sync.zig | 2 |
21 files changed, 1547 insertions, 485 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json index 2c411eaed..90fd0ceed 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,8 @@ { "version": "0.2.0", "configurations": [ + + // { // "type": "lldb", // "request": "launch", @@ -65,9 +67,10 @@ "--resolve=lazy", "--outdir=public", "--serve", - "--public-url=http://localhost:9000/" + "--public-url=http://localhost:9000/", + "--framework=./framework.tsx" ], - "cwd": "${workspaceFolder}/demos/simple-react", + "cwd": "${workspaceFolder}/demos/css-stress-test", "console": "internalConsole" }, { diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/.vscode/settings.json @@ -114,11 +114,12 @@ pub fn build(b: *std.build.Builder) void { exe.setTarget(target); exe.setBuildMode(mode); b.install_path = output_dir; - + var javascript: @TypeOf(exe) = undefined; // exe.want_lto = true; if (!target.getCpuArch().isWasm()) { addPicoHTTP(exe, cwd); - var javascript = b.addExecutable("spjs", "src/main_javascript.zig"); + javascript = b.addExecutable("spjs", "src/main_javascript.zig"); + addPicoHTTP(javascript, cwd); javascript.packages = std.ArrayList(std.build.Pkg).fromOwnedSlice(std.heap.c_allocator, std.heap.c_allocator.dupe(std.build.Pkg, exe.packages.items) catch unreachable); javascript.setOutputDir(output_dir); javascript.setBuildMode(mode); @@ -131,11 +132,14 @@ pub fn build(b: *std.build.Builder) void { } javascript.strip = false; - javascript.install(); } exe.install(); + if (!target.getCpuArch().isWasm()) { + javascript.install(); + } + const run_cmd = exe.run(); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { diff --git a/demos/css-stress-test/framework.tsx b/demos/css-stress-test/framework.tsx new file mode 100644 index 000000000..0d1e5d18c --- /dev/null +++ b/demos/css-stress-test/framework.tsx @@ -0,0 +1,28 @@ +import ReactDOMServer from "react-dom/server.browser"; +import { Base } from "./src/index"; + +addEventListener("fetch", (event: FetchEvent) => { + const response = new Response(` + <!DOCTYPE html> +<html> + <head> + <link + rel="stylesheet" + crossorigin="anonymous" + href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&family=Space+Mono:wght@400;700" + /> + </head> + <body> + <link rel="stylesheet" href="./src/index.css" /> + <div id="reactroot">${ReactDOMServer.renderToString(<Base />)}</div> + + <script src="./src/index.tsx" async type="module"></script> + </body> +</html> + `); + + event.respondWith(response); +}); + +// typescript isolated modules +export {}; diff --git a/demos/css-stress-test/package.json b/demos/css-stress-test/package.json index 24b2082ee..fefc4d2b2 100644 --- a/demos/css-stress-test/package.json +++ b/demos/css-stress-test/package.json @@ -27,6 +27,7 @@ } }, "devDependencies": { + "@microsoft/fetch-event-source": "^2.0.1", "@snowpack/plugin-react-refresh": "^2.5.0", "typescript": "^4.3.4" } diff --git a/demos/css-stress-test/src/index.tsx b/demos/css-stress-test/src/index.tsx index 267691467..16855fd11 100644 --- a/demos/css-stress-test/src/index.tsx +++ b/demos/css-stress-test/src/index.tsx @@ -1,5 +1,3 @@ -import ReactDOMServer from "react-dom/server.browser"; - import { Main } from "./main"; import classNames from "classnames"; const Base = ({}) => { @@ -15,10 +13,6 @@ function startReact() { ReactDOM.render(<Base />, document.querySelector("#reactroot")); } -function ssr() { - console.log(ReactDOMServer.renderToString(<Base />)); -} - if (typeof window !== "undefined") { console.log("HERE!!"); globalThis.addEventListener("DOMContentLoaded", () => { @@ -26,8 +20,6 @@ if (typeof window !== "undefined") { }); startReact(); -} else { - ssr(); } export { Base }; diff --git a/demos/css-stress-test/tsconfig.json b/demos/css-stress-test/tsconfig.json index 401ede344..c13e650d0 100644 --- a/demos/css-stress-test/tsconfig.json +++ b/demos/css-stress-test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "esnext", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": ["dom", "dom.iterable", "esnext", "WebWorker"], "allowJs": true, "skipLibCheck": true, "strict": false, @@ -1,318 +0,0 @@ -/Users/jarredsumner/Code/esdev: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/.: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./pages/..: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/.: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/.: readFile error -- IsDir - -error: Cannot read file "/Users/jarredsumner/Code/esdev": IsDir - - - -error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo": IsDir - - - -error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/.": IsDir - - - -error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next": IsDir - - - -error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./pages/..": IsDir - - - -error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react": IsDir - - - -error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/.": IsDir - - - -error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/.": IsDir - - - -error: Cannot assign to property on import "newObj" -"use strict";exports.__esModule=true;exports.defaultHead=defaultHead;exports.default=void 0;var _react=_interopRequireWildcard(require("react"));var _sideEffect=_interopRequireDefault(require("./side-effect"));var _ampContext=require("./amp-context");var _headManagerContext=require("./head-manager-context");var _amp=require("./amp");function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function _getRequireWildcardCache(){if(typeof WeakMap!=="function")return null;var cache=new WeakMap();_getRequireWildcardCache=function(){return cache;};return cache;}function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}if(obj===null||typeof obj!=="object"&&typeof obj!=="function"){return{default:obj};}var cache=_getRequireWildcardCache();if(cache&&cache.has(obj)){return cache.get(obj);}var newObj={};var hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;if(desc&&(desc.get||desc.set)){Object.defineProperty(newObj,key,desc);}else{newObj[key]=obj[key];}}}newObj.default=obj;if(cache){cache.set(obj,newObj);}return newObj;}function defaultHead(inAmpMode=false){const head=[/*#__PURE__*/_react.default.createElement("meta",{charSet:"utf-8"})];if(!inAmpMode){head.push(/*#__PURE__*/_react.default.createElement("meta",{name:"viewport",content:"width=device-width"}));}return head;}function onlyReactElement(list,child){// React children can be "string" or "number" in this case we ignore them for backwards compat - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:1:1149 1148 - - -error: Cannot assign to property on import "metaCategories" -switch(h.type){case'title':case'base':if(tags.has(h.type)){isUnique=false;}else{tags.add(h.type);}break;case'meta':for(let i=0,len=METATYPES.length;i<len;i++){const metatype=METATYPES[i];if(!h.props.hasOwnProperty(metatype))continue;if(metatype==='charSet'){if(metaTypes.has(metatype)){isUnique=false;}else{metaTypes.add(metatype);}}else{const category=h.props[metatype];const categories=metaCategories[metatype]||new Set();if((metatype!=='name'||!hasKey)&&categories.has(category)){isUnique=false;}else{categories.add(category);metaCategories[metatype]=categories;}}}break;}return isUnique;};}/** - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:8:530 3158 - - -error: Cannot assign to property on import "newProps" -['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/** - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:12:116 3986 - - -error: Cannot assign to property on import "newProps" -['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/** - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:12:155 4025 - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/@babel": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/@babel": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/@babel/runtime": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/@babel/runtime": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/@babel/runtime": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/@babel/runtime/helpers": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/react": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/react": FileNotFound - - - -error: Expected ")" but found , -function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}} - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:89 2171 - - -error: Expected ";" but found ) -function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}} - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:120 2202 - - -error: Expected ")" but found , -function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}} - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:338 2420 - - -error: Expected ";" but found ) -function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}} - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:340 2422 - - -error: Cannot assign to property on import "d" -function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}} - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:157 2239 - - -error: Cannot assign to property on import "f" -function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}} - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:259 2341 - - -error: Cannot assign to property on import "d" -function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}} - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:357 2439 - - -error: Cannot assign to property on import "e" -exports.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(z(267,a));var e=l({},a.props),d=a.key,k=a.ref,h=a._owner;if(null!=b){void 0!==b.ref&&(k=b.ref,h=G.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var g=a.type.defaultProps;for(f in b)H.call(b,f)&&!I.hasOwnProperty(f)&&(e[f]=void 0===b[f]&&void 0!==g?g[f]:b[f])}var f=arguments.length-2;if(1===f)e.children=c;else if(1<f){g=Array(f);for(var m=0;m<f;m++)g[m]=arguments[m+2];e.children=g}return{$$typeof:n,type:a.type, - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:20:314 4973 - - -error: Cannot assign to property on import "g" -exports.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(z(267,a));var e=l({},a.props),d=a.key,k=a.ref,h=a._owner;if(null!=b){void 0!==b.ref&&(k=b.ref,h=G.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var g=a.type.defaultProps;for(f in b)H.call(b,f)&&!I.hasOwnProperty(f)&&(e[f]=void 0===b[f]&&void 0!==g?g[f]:b[f])}var f=arguments.length-2;if(1===f)e.children=c;else if(1<f){g=Array(f);for(var m=0;m<f;m++)g[m]=arguments[m+2];e.children=g}return{$$typeof:n,type:a.type, - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:20:447 5106 - - -error: Cannot assign to property on import "args" - args[_key - 1] = arguments[_key]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:184:7 5256 - - -error: Cannot assign to property on import "args" - args[_key2 - 1] = arguments[_key2]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:193:7 5499 - - -error: Cannot assign to property on import "didWarnStateUpdateForUnmountedComponent" - didWarnStateUpdateForUnmountedComponent[warningKey] = true; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:238:5 7144 - - -error: Cannot assign to property on import "didWarnAboutStringRefs" - didWarnAboutStringRefs[componentName] = true; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:622:9 19312 - - -error: Cannot assign to property on import "props" - props[propName] = config[propName]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:734:9 22825 - - -error: Cannot assign to property on import "childArray" - childArray[i] = arguments[i + 2]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:749:7 23234 - - -error: Cannot assign to property on import "props" - props[propName] = defaultProps[propName]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:767:9 23588 - - -error: Cannot assign to property on import "props" - props[propName] = defaultProps[propName]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:842:11 25862 - - -error: Cannot assign to property on import "props" - props[propName] = config[propName]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:844:11 25931 - - -error: Cannot assign to property on import "childArray" - childArray[i] = arguments[i + 2]; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:860:7 26350 - - -error: Cannot use "break" here - break; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:1790:11 55454 - - -error: Cannot assign to property on import "loggedTypeFailures" - loggedTypeFailures[error$1.message] = true; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:1932:11 60269 - - -error: Cannot assign to property on import "ownerHasKeyUseWarning" - ownerHasKeyUseWarning[currentComponentErrorInfo] = true; // Usually the current owner is the offender, but if it accepts children as a - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:2038:3 62986 - - -error: Cannot use "break" here - break; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:2169:9 67164 - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./node_modules/object-assign": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/node_modules/object-assign": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/object-assign": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/object-assign": FileNotFound - - - -error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/object-assign": FileNotFound - - - -error: Unexpected "super" -"use strict";exports.__esModule=true;exports.default=void 0;var _react=require("react");const isServer=typeof window==='undefined';class _default extends _react.Component{constructor(props){super(props);this._hasHeadManager=void 0;this.emitChange=()=>{if(this._hasHeadManager){this.props.headManager.updateHead(this.props.reduceComponentsToState([...this.props.headManager.mountedInstances],this.props));}};this._hasHeadManager=this.props.headManager&&this.props.headManager.mountedInstances;if(isServer&&this._hasHeadManager){this.props.headManager.mountedInstances.add(this);this.emitChange();}}componentDidMount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.add(this);}this.emitChange();}componentDidUpdate(){this.emitChange();}componentWillUnmount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.delete(this);}this.emitChange();}render(){return null;}}exports.default=_default; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/./side-effect.js:1:191 190 - - -error: Cannot assign to property on import "newObj" -"use strict";exports.__esModule=true;exports.defaultHead=defaultHead;exports.default=void 0;var _react=_interopRequireWildcard(require("react"));var _sideEffect=_interopRequireDefault(require("./side-effect"));var _ampContext=require("./amp-context");var _headManagerContext=require("./head-manager-context");var _amp=require("./amp");function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function _getRequireWildcardCache(){if(typeof WeakMap!=="function")return null;var cache=new WeakMap();_getRequireWildcardCache=function(){return cache;};return cache;}function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}if(obj===null||typeof obj!=="object"&&typeof obj!=="function"){return{default:obj};}var cache=_getRequireWildcardCache();if(cache&&cache.has(obj)){return cache.get(obj);}var newObj={};var hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;if(desc&&(desc.get||desc.set)){Object.defineProperty(newObj,key,desc);}else{newObj[key]=obj[key];}}}newObj.default=obj;if(cache){cache.set(obj,newObj);}return newObj;}function defaultHead(inAmpMode=false){const head=[/*#__PURE__*/_react.default.createElement("meta",{charSet:"utf-8"})];if(!inAmpMode){head.push(/*#__PURE__*/_react.default.createElement("meta",{name:"viewport",content:"width=device-width"}));}return head;}function onlyReactElement(list,child){// React children can be "string" or "number" in this case we ignore them for backwards compat - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:1:1149 1148 - - -error: Cannot assign to property on import "metaCategories" -switch(h.type){case'title':case'base':if(tags.has(h.type)){isUnique=false;}else{tags.add(h.type);}break;case'meta':for(let i=0,len=METATYPES.length;i<len;i++){const metatype=METATYPES[i];if(!h.props.hasOwnProperty(metatype))continue;if(metatype==='charSet'){if(metaTypes.has(metatype)){isUnique=false;}else{metaTypes.add(metatype);}}else{const category=h.props[metatype];const categories=metaCategories[metatype]||new Set();if((metatype!=='name'||!hasKey)&&categories.has(category)){isUnique=false;}else{categories.add(category);metaCategories[metatype]=categories;}}}break;}return isUnique;};}/** - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:8:530 3158 - - -error: Cannot assign to property on import "newProps" -['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/** - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:12:116 3986 - - -error: Cannot assign to property on import "newProps" -['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/** - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:12:155 4025 - - -error: Unexpected "super" -"use strict";exports.__esModule=true;exports.default=void 0;var _react=require("react");const isServer=typeof window==='undefined';class _default extends _react.Component{constructor(props){super(props);this._hasHeadManager=void 0;this.emitChange=()=>{if(this._hasHeadManager){this.props.headManager.updateHead(this.props.reduceComponentsToState([...this.props.headManager.mountedInstances],this.props));}};this._hasHeadManager=this.props.headManager&&this.props.headManager.mountedInstances;if(isServer&&this._hasHeadManager){this.props.headManager.mountedInstances.add(this);this.emitChange();}}componentDidMount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.add(this);}this.emitChange();}componentDidUpdate(){this.emitChange();}componentWillUnmount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.delete(this);}this.emitChange();}render(){return null;}}exports.default=_default; - -/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/./side-effect.js:1:191 190 diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index a78156e10..4b647c4db 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -256,6 +256,7 @@ type uint32 = number; only_scan_dependencies?: ScanDependencyMode; generate_node_module_bundle?: boolean; node_modules_bundle_path?: string; + javascript_framework_file?: string; } export interface FileHandle { diff --git a/src/api/schema.js b/src/api/schema.js index e48d857a6..a8241f345 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -671,6 +671,10 @@ function decodeTransformOptions(bb) { result["node_modules_bundle_path"] = bb.readString(); break; + case 22: + result["javascript_framework_file"] = bb.readString(); + break; + default: throw new Error("Attempted to parse invalid message"); } @@ -835,6 +839,12 @@ bb.writeByte(encoded); bb.writeByte(21); bb.writeString(value); } + + var value = message["javascript_framework_file"]; + if (value != null) { + bb.writeByte(22); + bb.writeString(value); + } bb.writeByte(0); } diff --git a/src/api/schema.peechy b/src/api/schema.peechy index 1da6c8c67..6f755e907 100644 --- a/src/api/schema.peechy +++ b/src/api/schema.peechy @@ -168,6 +168,8 @@ message TransformOptions { bool generate_node_module_bundle = 20; string node_modules_bundle_path = 21; + + string javascript_framework_file = 22; } struct FileHandle { diff --git a/src/api/schema.zig b/src/api/schema.zig index 0805ddfc1..23739441a 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -823,6 +823,9 @@ generate_node_module_bundle: ?bool = null, /// node_modules_bundle_path node_modules_bundle_path: ?[]const u8 = null, +/// javascript_framework_file +javascript_framework_file: ?[]const u8 = null, + pub fn decode(reader: anytype) anyerror!TransformOptions { var this = std.mem.zeroes(TransformOptions); @@ -894,6 +897,9 @@ pub fn decode(reader: anytype) anyerror!TransformOptions { 21 => { this.node_modules_bundle_path = try reader.readValue([]const u8); }, + 22 => { + this.javascript_framework_file = try reader.readValue([]const u8); +}, else => { return error.InvalidMessage; }, @@ -987,6 +993,10 @@ if (this.node_modules_bundle_path) |node_modules_bundle_path| { try writer.writeFieldID(21); try writer.writeValue(node_modules_bundle_path); } +if (this.javascript_framework_file) |javascript_framework_file| { + try writer.writeFieldID(22); + try writer.writeValue(javascript_framework_file); +} try writer.endMessage(); } diff --git a/src/cli.zig b/src/cli.zig index b89343ec0..05aa6feae 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -130,6 +130,7 @@ pub const Cli = struct { clap.parseParam("--scan Instead of bundling or transpiling, print a list of every file imported by an entry point, recursively") catch unreachable, clap.parseParam("--new-jsb Generate a new node_modules.jsb file from node_modules and entry point(s)") catch unreachable, clap.parseParam("--jsb <STR> Use a Speedy JavaScript Bundle (default: \"./node_modules.jsb\" if exists)") catch unreachable, + clap.parseParam("--framework <STR> Use a JavaScript framework (file path) with --serve") catch unreachable, // clap.parseParam("--no-jsb Use a Speedy JavaScript Bundle (default: \"./node_modules.jsb\" if exists)") catch unreachable, clap.parseParam("<POS>... Entry points to use") catch unreachable, }; @@ -183,6 +184,8 @@ pub const Cli = struct { var jsx_production = args.flag("--jsx-production"); var react_fast_refresh = false; + var javascript_framework = args.option("--framework"); + if (serve or args.flag("--new-jsb")) { react_fast_refresh = true; if (args.flag("--disable-react-fast-refresh") or jsx_production) { @@ -280,6 +283,10 @@ pub const Cli = struct { std.process.exit(1); } + if (!serve) { + javascript_framework = null; + } + return Api.TransformOptions{ .jsx = jsx, .output_dir = output_dir, @@ -307,6 +314,7 @@ pub const Cli = struct { .platform = platform, .only_scan_dependencies = if (args.flag("--scan")) Api.ScanDependencyMode.all else Api.ScanDependencyMode._none, .generate_node_module_bundle = if (args.flag("--new-jsb")) true else false, + .javascript_framework_file = javascript_framework, }; } }; diff --git a/src/http.zig b/src/http.zig index 78d9c624f..a0f899ddd 100644 --- a/src/http.zig +++ b/src/http.zig @@ -12,6 +12,7 @@ const logger = @import("logger.zig"); const Fs = @import("./fs.zig"); const Options = @import("./options.zig"); const Css = @import("css_scanner.zig"); +const NodeModuleBundle = @import("./node_module_bundle.zig").NodeModuleBundle; pub fn constStrToU8(s: string) []u8 { return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len]; @@ -31,8 +32,8 @@ const picohttp = @import("picohttp"); const Header = picohttp.Header; const Request = picohttp.Request; const Response = picohttp.Response; -const Headers = picohttp.Headers; -const MimeType = @import("http/mime_type.zig"); +pub const Headers = picohttp.Headers; +pub const MimeType = @import("http/mime_type.zig"); const Bundler = bundler.ServeBundler; const Websocket = @import("./http/websocket.zig"); const js_printer = @import("js_printer.zig"); @@ -41,6 +42,8 @@ const watcher = @import("./watcher.zig"); threadlocal var req_headers_buf: [100]picohttp.Header = undefined; threadlocal var res_headers_buf: [100]picohttp.Header = undefined; const sync = @import("./sync.zig"); +const JavaScript = @import("./javascript/jsc/JavaScript.zig"); +const js = @import("javascript/jsc/javascript.zig"); pub const Watcher = watcher.NewWatcher(*Server); @@ -189,10 +192,23 @@ pub const RequestContext = struct { watcher: *Watcher, timer: std.time.Timer, + full_url: [:0]const u8 = "", res_headers_count: usize = 0, pub const bundle_prefix = "__speedy"; + pub fn getFullURL(this: *RequestContext) [:0]const u8 { + if (this.full_url.len == 0) { + if (this.bundler.options.public_url.len > 0) { + this.full_url = std.fmt.allocPrintZ(this.allocator, "{s}{s}", .{ this.bundler.options.public_url, this.request.path }) catch unreachable; + } else { + this.full_url = this.allocator.dupeZ(u8, this.request.path) catch unreachable; + } + } + + return this.full_url; + } + pub fn header(ctx: *RequestContext, comptime name: anytype) ?Header { if (name.len < 17) { for (ctx.request.headers) |head| { @@ -246,6 +262,17 @@ pub const RequestContext = struct { try ctx.flushHeaders(); } + pub fn clearHeaders( + this: *RequestContext, + ) !void { + this.res_headers_count = 0; + } + + pub fn appendHeaderSlow(this: *RequestContext, name: string, value: string) !void { + res_headers_buf[this.res_headers_count] = picohttp.Header{ .name = name, .value = value }; + this.res_headers_count += 1; + } + threadlocal var resp_header_out_buf: [4096]u8 = undefined; pub fn flushHeaders(ctx: *RequestContext) !void { if (ctx.res_headers_count == 0) return; @@ -288,6 +315,20 @@ pub const RequestContext = struct { ctx.status = code; } + threadlocal var status_buf: [std.fmt.count("HTTP/1.1 {d} {s}\r\n", .{ 200, "OK" })]u8 = undefined; + pub fn writeStatusSlow(ctx: *RequestContext, code: u16) !void { + _ = try ctx.writeSocket( + try std.fmt.bufPrint( + &status_buf, + "HTTP/1.1 {d} {s}\r\n", + .{ code, if (code > 299) "HM" else "OK" }, + ), + SOCKET_FLAGS, + ); + + ctx.status = @truncate(HTTPStatusCode, code); + } + pub fn init( req: Request, arena: std.heap.ArenaAllocator, @@ -592,6 +633,132 @@ pub const RequestContext = struct { } }; + pub const JavaScriptHandler = struct { + ctx: RequestContext, + conn: tcp.Connection, + + pub const HandlerThread = struct { + args: Api.TransformOptions, + existing_bundle: ?*NodeModuleBundle, + log: ?*logger.Log = null, + }; + + pub const Channel = sync.Channel(*JavaScriptHandler, .{ .Static = 100 }); + pub var channel: Channel = undefined; + var has_loaded_channel = false; + pub var javascript_disabled = false; + var thread: std.Thread = undefined; + pub fn spawnThread(handler: HandlerThread) !void { + _ = try std.Thread.spawn(spawn, handler); + } + + pub fn spawn(handler: HandlerThread) void { + var _handler = handler; + _spawn(&_handler) catch {}; + } + + pub fn _spawn(handler: *HandlerThread) !void { + defer { + javascript_disabled = true; + } + + var stdout = std.io.getStdOut(); + // var stdout = std.io.bufferedWriter(stdout_file.writer()); + var stderr = std.io.getStdErr(); + // var stderr = std.io.bufferedWriter(stderr_file.writer()); + var output_source = Output.Source.init(stdout, stderr); + // defer stdout.flush() catch {}; + // defer stderr.flush() catch {}; + Output.Source.set(&output_source); + Output.enable_ansi_colors = stderr.isTty(); + js_ast.Stmt.Data.Store.create(std.heap.c_allocator); + js_ast.Expr.Data.Store.create(std.heap.c_allocator); + + defer Output.flush(); + var boot = handler.args.javascript_framework_file.?; + var vm = try JavaScript.VirtualMachine.init(std.heap.c_allocator, handler.args, handler.existing_bundle, handler.log); + defer vm.deinit(); + + var resolved_entry_point = try vm.bundler.resolver.resolve( + std.fs.path.dirname(boot).?, + boot, + .entry_point, + ); + JavaScript.VirtualMachine.instance = vm; + var exception: js.JSValueRef = null; + var load_result = try JavaScript.Module.loadFromResolveResult(vm, vm.ctx, resolved_entry_point, &exception); + + // We've already printed the exception here! + if (exception != null) { + Output.prettyErrorln( + "JavaScript VM failed to start", + .{}, + ); + Output.flush(); + + return; + } + + switch (load_result) { + .Module => { + Output.prettyln( + "Loaded JavaScript VM: \"{s}\"", + .{vm.bundler.fs.relativeTo(boot)}, + ); + Output.flush(); + }, + .Path => { + Output.prettyErrorln( + "Error loading JavaScript VM: Expected framework to be a path to a JavaScript-like file but received \"{s}\"", + .{boot}, + ); + Output.flush(); + return; + }, + } + + js_ast.Stmt.Data.Store.reset(); + js_ast.Expr.Data.Store.reset(); + + try runLoop(vm); + } + + pub fn runLoop(vm: *JavaScript.VirtualMachine) !void { + while (true) { + defer { + js_ast.Stmt.Data.Store.reset(); + js_ast.Expr.Data.Store.reset(); + } + var handler: *JavaScriptHandler = try channel.readItem(); + try JavaScript.EventListenerMixin.emitFetchEvent(vm, &handler.ctx); + } + } + + var one: [1]*JavaScriptHandler = undefined; + pub fn enqueue(ctx: *RequestContext, server: *Server) !void { + var clone = try ctx.allocator.create(JavaScriptHandler); + clone.ctx = ctx.*; + clone.conn = ctx.conn.*; + clone.ctx.conn = &clone.conn; + + if (!has_loaded_channel) { + has_loaded_channel = true; + channel = Channel.init(); + try JavaScriptHandler.spawnThread( + HandlerThread{ + .args = server.transform_options, + .existing_bundle = server.bundler.options.node_modules_bundle, + .log = &server.log, + }, + ); + } + + defer ctx.controlled = true; + one[0] = clone; + _ = try channel.write(&one); + } + }; + pub const WebsocketHandler = struct { accept_key: [28]u8 = undefined, ctx: RequestContext, @@ -943,6 +1110,22 @@ pub const RequestContext = struct { } }; + pub fn writeETag(this: *RequestContext, buffer: anytype) !bool { + const strong_etag = std.hash.Wyhash.hash(1, buffer); + const etag_content_slice = std.fmt.bufPrintIntToSlice(strong_etag_buffer[0..49], strong_etag, 16, .upper, .{}); + + this.appendHeader("ETag", etag_content_slice); + + if (this.header("If-None-Match")) |etag_header| { + if (std.mem.eql(u8, etag_content_slice, etag_header.value)) { + try this.sendNotModified(); + return true; + } + } + + return false; + } + pub fn handleWebsocket(ctx: *RequestContext) anyerror!void { ctx.controlled = true; var handler = try WebsocketHandler.addWebsocket(ctx); @@ -1228,18 +1411,8 @@ pub const RequestContext = struct { } if (FeatureFlags.strong_etags_for_built_files) { - // TODO: don't hash runtime.js - const strong_etag = std.hash.Wyhash.hash(1, buffer); - const etag_content_slice = std.fmt.bufPrintIntToSlice(strong_etag_buffer[0..49], strong_etag, 16, .upper, .{}); - - ctx.appendHeader("ETag", etag_content_slice); - - if (ctx.header("If-None-Match")) |etag_header| { - if (std.mem.eql(u8, etag_content_slice, etag_header.value)) { - try ctx.sendNotModified(); - return; - } - } + const did_send = ctx.writeETag(buffer) catch false; + if (did_send) return; } if (buffer.len == 0) { @@ -1313,6 +1486,7 @@ pub const Server = struct { bundler: Bundler, watcher: *Watcher, timer: std.time.Timer = undefined, + transform_options: Api.TransformOptions, pub fn adjustUlimit() !void { var limit = try std.os.getrlimit(.NOFILE); @@ -1465,17 +1639,25 @@ pub const Server = struct { req_ctx.keep_alive = false; } - req_ctx.handleRequest() catch |err| { - switch (err) { - error.ModuleNotFound => { - req_ctx.sendNotFound() catch {}; - }, - else => { - Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - return; - }, + if (req_ctx.url.extname.len == 0 and !RequestContext.JavaScriptHandler.javascript_disabled) { + if (server.transform_options.javascript_framework_file != null) { + RequestContext.JavaScriptHandler.enqueue(&req_ctx, server) catch unreachable; } - }; + } + + if (!req_ctx.controlled) { + req_ctx.handleRequest() catch |err| { + switch (err) { + error.ModuleNotFound => { + req_ctx.sendNotFound() catch {}; + }, + else => { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + return; + }, + } + }; + } if (!req_ctx.controlled) { const status = req_ctx.status orelse @intCast(HTTPStatusCode, 500); @@ -1503,11 +1685,30 @@ pub const Server = struct { .log = log, .bundler = undefined, .watcher = undefined, + .transform_options = options, .timer = try std.time.Timer.start(), }; server.bundler = try Bundler.init(allocator, &server.log, options, null); server.bundler.configureLinker(); + load_framework: { + if (options.javascript_framework_file) |framework_file_path| { + var framework_file = server.bundler.normalizeEntryPointPath(framework_file_path); + var resolved = server.bundler.resolver.resolve( + server.bundler.fs.top_level_dir, + framework_file, + .entry_point, + ) catch |err| { + Output.prettyError("Failed to load framework: {s}", .{@errorName(err)}); + Output.flush(); + server.transform_options.javascript_framework_file = null; + break :load_framework; + }; + + server.transform_options.javascript_framework_file = try server.allocator.dupe(u8, resolved.path_pair.primary.text); + } + } + try server.initWatcher(); try server.run(); diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig index b7139d1c0..dc8c40cc2 100644 --- a/src/http/mime_type.zig +++ b/src/http/mime_type.zig @@ -29,6 +29,8 @@ pub const other = MimeType.init("application/octet-stream", .other); pub const css = MimeType.init("text/css", .css); pub const javascript = MimeType.init("text/javascript;charset=utf-8", .javascript); pub const ico = MimeType.init("image/vnd.microsoft.icon", .image); +pub const html = MimeType.init("text/html;charset=utf-8", .html); +pub const json = MimeType.init("application/json;charset=utf-8", .json); fn init(comptime str: string, t: Category) MimeType { return MimeType{ @@ -89,11 +91,11 @@ pub fn byExtension(ext: string) MimeType { }, 4 => { return switch (Four.match(ext)) { - Four.case("json") => MimeType.init("application/json;charset=utf-8", .json), + Four.case("json") => MimeType.json, Four.case("jpeg") => MimeType.init("image/jpeg", .image), Four.case("aiff") => MimeType.init("image/png", .image), Four.case("tiff") => MimeType.init("image/tiff", .image), - Four.case("html") => MimeType.init("text/html;charset=utf-8", .html), + Four.case("html") => MimeType.html, Four.case("wasm") => MimeType.init( "application/wasm", .wasm, diff --git a/src/javascript/jsc/JavascriptCore.zig b/src/javascript/jsc/JavascriptCore.zig index 83ccaebcf..0f1a624ce 100644 --- a/src/javascript/jsc/JavascriptCore.zig +++ b/src/javascript/jsc/JavascriptCore.zig @@ -246,3 +246,26 @@ pub const OpaqueJSValue = struct_OpaqueJSValue; // https://github.com/WebKit/webkit/blob/main/Source/JavaScriptCore/API/JSStringRef.cpp#L62 pub extern fn JSStringCreateWithCharactersNoCopy(string: [*c]const JSChar, numChars: size_t) JSStringRef; const size_t = usize; + +const then_key = "then"; +var thenable_string: JSStringRef = null; +pub fn isObjectOfClassAndResolveIfNeeded(ctx: JSContextRef, obj: JSObjectRef, class: JSClassRef) ?JSObjectRef { + if (JSValueIsObjectOfClass(ctx, obj, class)) { + return obj; + } + + if (!JSValueIsObject(ctx, obj)) { + return null; + } + + if (thenable_string == null) { + thenable_string = JSStringCreateWithUTF8CString(then_key[0.. :0]); + } + + var prop = JSObjectGetPropertyForKey(ctx, obj, JSValueMakeString(ctx, thenable_string), null); + if (prop == null) { + return null; + } + + +} diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index e9bc819ff..307bf2da9 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -33,6 +33,79 @@ pub const To = struct { return function; } + pub fn Finalize( + comptime ZigContextType: type, + comptime ctxfn: fn ( + this: *ZigContextType, + object: js.JSObjectRef, + ) void, + ) type { + return struct { + pub fn rfn( + object: js.JSObjectRef, + ) callconv(.C) void { + var object_ptr_ = js.JSObjectGetPrivate(object); + if (object_ptr_ == null) return; + + return ctxfn( + @ptrCast(*ZigContextType, @alignCast(@alignOf(*ZigContextType), object_ptr_.?)), + object, + ); + } + }; + } + + pub fn Constructor( + comptime ctxfn: fn ( + ctx: js.JSContextRef, + function: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef, + ) type { + return struct { + pub fn rfn( + ctx: js.JSContextRef, + function: js.JSObjectRef, + argumentCount: usize, + arguments: [*c]const js.JSValueRef, + exception: js.ExceptionRef, + ) callconv(.C) js.JSValueRef { + return ctxfn( + ctx, + function, + if (arguments) |args| args[0..argumentCount] else &[_]js.JSValueRef{}, + exception, + ); + } + }; + } + pub fn ConstructorCallback( + comptime ctxfn: fn ( + ctx: js.JSContextRef, + function: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef, + ) type { + return struct { + pub fn rfn( + ctx: js.JSContextRef, + function: js.JSObjectRef, + argumentCount: usize, + arguments: [*c]const js.JSValueRef, + exception: js.ExceptionRef, + ) callconv(.C) js.JSValueRef { + return ctxfn( + ctx, + function, + if (arguments) |args| args[0..argumentCount] else &[_]js.JSValueRef{}, + exception, + ); + } + }; + } + pub fn Callback( comptime ZigContextType: type, comptime ctxfn: fn ( @@ -107,6 +180,18 @@ pub const Properties = struct { pub const initialize_bundled_module = "$$m"; pub const load_module_function = "$lOaDuRcOdE$"; pub const window = "window"; + pub const default = "default"; + pub const include = "include"; + + pub const GET = "GET"; + pub const PUT = "PUT"; + pub const POST = "POST"; + pub const PATCH = "PATCH"; + pub const HEAD = "HEAD"; + pub const OPTIONS = "OPTIONS"; + + pub const navigate = "navigate"; + pub const follow = "follow"; }; pub const UTF16 = struct { @@ -125,24 +210,50 @@ pub const Properties = struct { pub const initialize_bundled_module = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.initialize_bundled_module); pub const load_module_function: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.load_module_function); pub const window: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.window); + pub const default: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.default); + pub const include: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.include); + + pub const GET: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.GET); + pub const PUT: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.PUT); + pub const POST: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.POST); + pub const PATCH: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.PATCH); + pub const HEAD: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.HEAD); + pub const OPTIONS: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.OPTIONS); + + pub const navigate: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.navigate); + pub const follow: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.follow); }; pub const Refs = struct { - pub var module: js.JSStringRef = null; - pub var globalThis: js.JSStringRef = null; - pub var exports: js.JSStringRef = null; - pub var log: js.JSStringRef = null; - pub var debug: js.JSStringRef = null; - pub var info: js.JSStringRef = null; - pub var error_: js.JSStringRef = null; - pub var warn: js.JSStringRef = null; - pub var console: js.JSStringRef = null; - pub var require: js.JSStringRef = null; - pub var description: js.JSStringRef = null; - pub var name: js.JSStringRef = null; - pub var initialize_bundled_module: js.JSStringRef = null; - pub var load_module_function: js.JSStringRef = null; - pub var window: js.JSStringRef = null; + pub var module: js.JSStringRef = undefined; + pub var globalThis: js.JSStringRef = undefined; + pub var exports: js.JSStringRef = undefined; + pub var log: js.JSStringRef = undefined; + pub var debug: js.JSStringRef = undefined; + pub var info: js.JSStringRef = undefined; + pub var error_: js.JSStringRef = undefined; + pub var warn: js.JSStringRef = undefined; + pub var console: js.JSStringRef = undefined; + pub var require: js.JSStringRef = undefined; + pub var description: js.JSStringRef = undefined; + pub var name: js.JSStringRef = undefined; + pub var initialize_bundled_module: js.JSStringRef = undefined; + pub var load_module_function: js.JSStringRef = undefined; + pub var window: js.JSStringRef = undefined; + pub var default: js.JSStringRef = undefined; + pub var include: js.JSStringRef = undefined; + pub var GET: js.JSStringRef = undefined; + pub var PUT: js.JSStringRef = undefined; + pub var POST: js.JSStringRef = undefined; + pub var PATCH: js.JSStringRef = undefined; + pub var HEAD: js.JSStringRef = undefined; + pub var OPTIONS: js.JSStringRef = undefined; + + pub var empty_string_ptr = [_]u8{0}; + pub var empty_string: js.JSStringRef = undefined; + + pub var navigate: js.JSStringRef = undefined; + pub var follow: js.JSStringRef = undefined; }; pub fn init() void { @@ -160,9 +271,13 @@ pub const Properties = struct { ); } } + + Refs.empty_string = js.JSStringCreateWithUTF8CString(&Refs.empty_string_ptr); } }; +const hasSetter = std.meta.trait.hasField("set"); +const hasFinalize = std.meta.trait.hasField("finalize"); pub fn NewClass( comptime ZigType: type, comptime name: string, @@ -215,6 +330,65 @@ pub fn NewClass( }; var static_properties: [property_names.len]js.JSStaticValue = undefined; + pub var ref: js.JSClassRef = null; + pub var loaded = false; + pub var definition: js.JSClassDefinition = undefined; + const ConstructorWrapper = struct { + pub fn rfn( + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + argumentCount: usize, + arguments: [*c]const js.JSValueRef, + exception: js.ExceptionRef, + ) callconv(.C) js.JSValueRef { + return definition.callAsConstructor.?(ctx, function, argumentCount, arguments, exception); + } + }; + + pub const Constructor = ConstructorWrapper.rfn; + + pub const static_value_count = static_properties.len; + + pub fn get() callconv(.C) [*c]js.JSClassRef { + if (!loaded) { + loaded = true; + definition = define(); + ref = js.JSClassRetain(js.JSClassCreate(&definition)); + } + return &ref; + } + + pub fn RawGetter(comptime ReceiverType: type) type { + const ClassGetter = struct { + pub fn getter( + ctx: js.JSContextRef, + obj: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) callconv(.C) js.JSValueRef { + return js.JSObjectMake(ctx, get().*, null); + } + }; + + return ClassGetter; + } + + pub fn GetClass(comptime ReceiverType: type) type { + const ClassGetter = struct { + pub fn getter( + receiver: *ReceiverType, + ctx: js.JSContextRef, + obj: js.JSObjectRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSObjectMake(ctx, get().*, null); + } + }; + + return ClassGetter; + } + pub fn getPropertyCallback( ctx: js.JSContextRef, obj: js.JSObjectRef, @@ -281,7 +455,7 @@ pub fn NewClass( ), ); - var exc: js.ExceptionRef = null; + var exc: js.JSValueRef = null; switch (comptime @typeInfo(@TypeOf(@field( properties, @@ -372,15 +546,18 @@ pub fn NewClass( var count: usize = 0; inline for (function_name_literals) |function_name, i| { if (comptime strings.eqlComptime(function_names[i], "constructor")) { - def.callAsConstructor = @field(staticFunctions, function_names[i]); + def.callAsConstructor = To.JS.Constructor(@field(staticFunctions, function_names[i])).rfn; + } else if (comptime strings.eqlComptime(function_names[i], "finalize")) { + def.finalize = To.JS.Finalize(ZigType, staticFunctions.finalize).rfn; } else { - count += 1; var callback = To.JS.Callback(ZigType, @field(staticFunctions, function_names[i])).rfn; static_functions[count] = js.JSStaticFunction{ .name = (function_names[i][0.. :0]).ptr, .callAsFunction = callback, .attributes = comptime if (read_only) js.JSPropertyAttributes.kJSPropertyAttributeReadOnly else js.JSPropertyAttributes.kJSPropertyAttributeNone, }; + + count += 1; } // if (singleton) { @@ -402,7 +579,7 @@ pub fn NewClass( static_properties[i].getProperty = StaticProperty(i).getter; const field = comptime @field(properties, property_names[i]); - const hasSetter = std.meta.trait.hasField("set"); + if (comptime hasSetter(@TypeOf(field))) { static_properties[i].setProperty = StaticProperty(i).setter; } @@ -415,8 +592,6 @@ pub fn NewClass( def.className = class_name_str; // def.getProperty = getPropertyCallback; - def.finalize - return def; } }; @@ -437,20 +612,18 @@ pub fn JSError( exception.* = js.JSObjectMakeError(ctx, 1, &error_args, null); } else { var buf = std.fmt.allocPrintZ(allocator, fmt, args) catch unreachable; + defer allocator.free(buf); + var message = js.JSStringCreateWithUTF8CString(buf); defer js.JSStringRelease(message); - defer allocator.free(buf); + error_args[0] = js.JSValueMakeString(ctx, message); exception.* = js.JSObjectMakeError(ctx, 1, &error_args, null); } } pub fn getAllocator(ctx: js.JSContextRef) *std.mem.Allocator { - var global_obj = js.JSContextGetGlobalObject(ctx); - var priv = js.JSObjectGetPrivate(global_obj).?; - var global = @ptrCast(*javascript.GlobalObject, @alignCast(@alignOf(*javascript.GlobalObject), priv)); - - return global.vm.allocator; + return std.heap.c_allocator; } pub const JSStringList = std.ArrayList(js.JSStringRef); @@ -465,3 +638,10 @@ pub const ArrayBuffer = struct { typed_array_type: js.JSTypedArrayType, }; + +pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type { + return @ptrCast( + *Type, + @alignCast(@alignOf(*Type), js.JSObjectGetPrivate(obj).?), + ); +} diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 52dcfff1c..3e6d4c185 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -14,6 +14,7 @@ const http = @import("../../http.zig"); usingnamespace @import("./node_env_buf_map.zig"); usingnamespace @import("./base.zig"); +usingnamespace @import("./webcore/response.zig"); const DefaultSpeedyDefines = struct { pub const Keys = struct { @@ -138,6 +139,15 @@ pub const VirtualMachine = struct { log: *logger.Log, watcher: ?*http.Watcher = null, + event_listeners: EventListenerMixin.Map, + + pub threadlocal var instance: *VirtualMachine = undefined; + + pub fn deinit(vm: *VirtualMachine) void { + js.JSGlobalContextRelease(vm.ctx); + js.JSContextGroupRelease(vm.group); + } + pub fn init( allocator: *std.mem.Allocator, _args: Api.TransformOptions, @@ -167,6 +177,8 @@ pub const VirtualMachine = struct { .log = log, .group = group, + .event_listeners = EventListenerMixin.Map.init(allocator), + .require_cache = RequireCacheType.init(allocator), .global = global, }; @@ -191,8 +203,6 @@ pub const VirtualMachine = struct { } }; - - pub const Object = struct { ref: js.jsObjectRef, }; @@ -525,13 +535,41 @@ pub const Module = struct { size += this_size; longest_size = std.math.max(this_size, longest_size); } - var static_properties = try vm.allocator.alloc(js.JSStaticValue, bundle.bundle.modules.len + 2); - static_properties[static_properties.len - 2] = js.JSStaticValue{ + var static_properties = try vm.allocator.alloc(js.JSStaticValue, bundle.bundle.modules.len + 1 + GlobalObject.GlobalClass.static_value_count); + var copied_static_values = static_properties[bundle.bundle.modules.len..]; + copied_static_values = copied_static_values[0 .. copied_static_values.len - 1]; + + copied_static_values[0] = js.JSStaticValue{ .name = Properties.UTF8.console[0.. :0], .getProperty = getConsole, .setProperty = null, .attributes = .kJSPropertyAttributeNone, }; + + copied_static_values[1] = js.JSStaticValue{ + .name = Response.Class.definition.className, + .getProperty = Response.Class.RawGetter(NodeModuleList).getter, + .setProperty = null, + .attributes = .kJSPropertyAttributeNone, + }; + + copied_static_values[2] = js.JSStaticValue{ + .name = Headers.Class.definition.className, + .getProperty = Headers.Class.RawGetter(NodeModuleList).getter, + .setProperty = null, + .attributes = .kJSPropertyAttributeNone, + }; + + copied_static_values[3] = js.JSStaticValue{ + .name = Request.Class.definition.className, + .getProperty = Request.Class.RawGetter(NodeModuleList).getter, + .setProperty = null, + .attributes = .kJSPropertyAttributeNone, + }; + + // copied_static_values must match GlobalObject.GlobalClass.static_value_count + std.debug.assert(copied_static_values.len == 4); + static_properties[static_properties.len - 1] = std.mem.zeroes(js.JSStaticValue); var utf8 = try vm.allocator.alloc(u8, size + std.math.max(longest_size, 32)); std.mem.set(u8, utf8, 0); @@ -571,6 +609,7 @@ pub const Module = struct { var node_module_global_class_def = js.kJSClassDefinitionEmpty; node_module_global_class_def.staticValues = static_properties.ptr; node_module_global_class_def.className = node_module_global_class_name[0.. :0]; + node_module_global_class_def.staticFunctions = GlobalObject.GlobalClass.definition.staticFunctions; // node_module_global_class_def.parentClass = vm.global.global_class; var property_getters = try vm.allocator.alloc(js.JSObjectRef, bundle.bundle.modules.len); @@ -1145,6 +1184,142 @@ pub const Module = struct { pub const RequireObject = struct {}; }; +pub const EventListenerMixin = struct { + threadlocal var event_listener_names_buf: [128]u8 = undefined; + pub const List = std.ArrayList(js.JSObjectRef); + pub const Map = std.AutoHashMap(EventListenerMixin.EventType, EventListenerMixin.List); + + pub const EventType = enum { + fetch, + + pub fn match(str: string) ?EventType { + if (strings.eqlComptime(str, "fetch")) { + return EventType.fetch; + } + + return null; + } + }; + + pub fn emitFetchEventError( + request: *http.RequestContext, + comptime fmt: string, + args: anytype, + ) void { + Output.prettyErrorln(fmt, args); + request.sendInternalError(error.FetchEventError) catch {}; + } + + pub fn emitFetchEvent( + vm: *VirtualMachine, + request_context: *http.RequestContext, + ) !void { + var listeners = vm.event_listeners.get(EventType.fetch) orelse return emitFetchEventError( + request_context, + "Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?", + .{}, + ); + if (listeners.items.len == 0) return emitFetchEventError( + request_context, + "Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?", + .{}, + ); + + var exception: js.JSValueRef = null; + var fetch_event = try vm.allocator.create(FetchEvent); + fetch_event.* = FetchEvent{ + .request_context = request_context, + .request = Request{ .request_context = request_context }, + }; + + var fetch_args: [1]js.JSObjectRef = undefined; + for (listeners.items) |listener| { + fetch_args[0] = js.JSObjectMake( + vm.ctx, + FetchEvent.Class.get().*, + fetch_event, + ); + + _ = js.JSObjectCallAsFunction( + vm.ctx, + listener, + js.JSContextGetGlobalObject(vm.ctx), + 1, + &fetch_args, + &exception, + ); + if (request_context.has_called_done) { + break; + } + } + + if (exception != null) { + var message = js.JSValueToStringCopy(vm.ctx, exception, null); + defer js.JSStringRelease(message); + var buf = vm.allocator.alloc(u8, js.JSStringGetLength(message) + 1) catch unreachable; + defer vm.allocator.free(buf); + var note = buf[0 .. js.JSStringGetUTF8CString(message, buf.ptr, buf.len) - 1]; + + Output.prettyErrorln("<r><red>error<r>: <b>{s}<r>", .{note}); + Output.flush(); + + if (!request_context.has_called_done) { + request_context.sendInternalError(error.JavaScriptError) catch {}; + } + return; + } + + if (!request_context.has_called_done) { + return emitFetchEventError( + request_context, + "\"fetch\" handler never called event.respondWith()", + .{}, + ); + } + } + + pub fn addEventListener( + comptime Struct: type, + ) type { + const Handler = struct { + pub fn addListener( + ptr: *Struct, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (arguments.len == 0 or arguments.len == 1 or !js.JSValueIsString(ctx, arguments[0]) or !js.JSValueIsObject(ctx, arguments[arguments.len - 1]) or !js.JSObjectIsFunction(ctx, arguments[arguments.len - 1])) { + return js.JSValueMakeUndefined(ctx); + } + + const name_len = js.JSStringGetLength(arguments[0]); + if (name_len > event_listener_names_buf.len) { + return js.JSValueMakeUndefined(ctx); + } + + const name_used_len = js.JSStringGetUTF8CString(arguments[0], &event_listener_names_buf, event_listener_names_buf.len); + const name = event_listener_names_buf[0 .. name_used_len - 1]; + const event = EventType.match(name) orelse return js.JSValueMakeUndefined(ctx); + var entry = VirtualMachine.instance.event_listeners.getOrPut(event) catch unreachable; + + if (!entry.found_existing) { + entry.value_ptr.* = List.initCapacity(VirtualMachine.instance.allocator, 1) catch unreachable; + } + + var callback = arguments[arguments.len - 1]; + js.JSValueProtect(ctx, callback); + entry.value_ptr.append(callback) catch unreachable; + + return js.JSValueMakeUndefined(ctx); + } + }; + + return Handler; + } +}; + pub const GlobalObject = struct { ref: js.JSObjectRef = undefined, vm: *VirtualMachine, @@ -1176,9 +1351,14 @@ pub const GlobalObject = struct { pub const GlobalClass = NewClass( GlobalObject, "Global", - .{}, + .{ + .@"addEventListener" = EventListenerMixin.addEventListener(GlobalObject).addListener, + }, .{ .@"console" = getConsole, + .@"Request" = Request.Class.GetClass(GlobalObject).getter, + .@"Response" = Response.Class.GetClass(GlobalObject).getter, + .@"Headers" = Headers.Class.GetClass(GlobalObject).getter, }, false, false, @@ -1195,18 +1375,11 @@ pub const GlobalObject = struct { // js.JSValueProtect(js.JSContextGetGlobalContext(ctx), global.console); // } - return js.JSObjectMake(js.JSContextGetGlobalContext(ctx), global.console_class, global); + return js.JSObjectMake(js.JSContextGetGlobalContext(ctx), ConsoleClass.get().*, global); } pub fn boot(global: *GlobalObject) !void { - global.console_definition = ConsoleClass.define(); - global.console_class = js.JSClassRetain(js.JSClassCreate(&global.console_definition)); - - global.global_class_def = GlobalClass.define(); - global.global_class = js.JSClassRetain(js.JSClassCreate(&global.global_class_def)); - - global.ctx = js.JSGlobalContextRetain(js.JSGlobalContextCreateInGroup(global.vm.group, global.global_class)); - + global.ctx = js.JSGlobalContextRetain(js.JSGlobalContextCreateInGroup(global.vm.group, GlobalObject.GlobalClass.get().*)); std.debug.assert(js.JSObjectSetPrivate(js.JSContextGetGlobalObject(global.ctx), global)); if (!printer_buf_loaded) { diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index 580714da8..d65ac0732 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -2,7 +2,7 @@ usingnamespace @import("../base.zig"); const std = @import("std"); const Api = @import("../../../api/schema.zig").Api; const http = @import("../../../http.zig"); - +pub const JavaScript = @import("../javascript.zig"); pub const Response = struct { pub const Class = NewClass( Response, @@ -28,17 +28,6 @@ pub const Response = struct { false, ); - pub var class_definition: js.JSClassDefinition = undefined; - pub var class_ref: js.JSClassRef = undefined; - pub var loaded = false; - - pub fn load() void { - if (!loaded) { - class_definition = Class.define(); - class_ref = js.JSClassRetain(js.JSClassCreate(&class_definition)); - loaded = true; - } - } allocator: *std.mem.Allocator, body: Body, @@ -68,22 +57,50 @@ pub const Response = struct { pub fn finalize( this: *Response, - ctx: js.JSContextRef, + ctx: js.JSObjectRef, ) void { this.body.deinit(this.allocator); this.allocator.destroy(this); } + pub fn mimeType(response: *const Response, request_ctx: *const http.RequestContext) string { + if (response.body.init.headers) |headers| { + // Remember, we always lowercase it + // hopefully doesn't matter here tho + if (headers.getHeaderIndex("content-type")) |content_type| { + return headers.asStr(headers.entries.items(.value)[content_type]); + } + } + + if (request_ctx.url.extname.len > 0) { + return http.MimeType.byExtension(request_ctx.url.extname).value; + } + + switch (response.body.value) { + .Empty => { + return "text/plain"; + }, + .String => |body| { + // poor man's mimetype sniffing + if (body.len > 0 and (body[0] == '{' or body[0] == '[')) { + return http.MimeType.json.value; + } + + return http.MimeType.html.value; + }, + .ArrayBuffer => { + return "application/octet-stream"; + }, + } + } + pub fn constructor( ctx: js.JSContextRef, function: js.JSObjectRef, - arguments_len: usize, - arguments_ptr: [*c]const js.JSValueRef, + arguments: []const js.JSValueRef, exception: js.ExceptionRef, - ) callconv(.C) js.JSObjectRef { - const arguments = arguments_ptr[0..arguments_len]; - - const body = brk: { + ) js.JSObjectRef { + const body: Body = brk: { switch (arguments.len) { 0 => { break :brk Body.@"404"(ctx); @@ -102,65 +119,431 @@ pub const Response = struct { unreachable; }; - if (exception != null) { - return null; - } + // if (exception != null) { + // return null; + // } - var allocator = getAllocator(ctx); - var response = allocator.create(Response) catch return null; + var response = getAllocator(ctx).create(Response) catch return null; response.* = Response{ .body = body, - .allocator = allocator, + .allocator = getAllocator(ctx), }; - return js.JSObjectMake(ctx, class_ref, response); + return js.JSObjectMake(ctx, Response.Class.get().*, response); } }; +// https://developer.mozilla.org/en-US/docs/Web/API/Headers pub const Headers = struct { pub const Kv = struct { - key: Api.StringPointer, + name: Api.StringPointer, value: Api.StringPointer, }; pub const Entries = std.MultiArrayList(Kv); entries: Entries, buf: std.ArrayListUnmanaged(u8), allocator: *std.mem.Allocator, - used: usize = 0, + used: u32 = 0, + guard: Guard = Guard.none, + + pub fn deinit( + headers: *Headers, + ) void { + headers.buf.deinit(headers.allocator); + headers.entries.deinit(headers.allocator); + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Headers#methods + pub const JS = struct { + + // https://developer.mozilla.org/en-US/docs/Web/API/Headers/get + pub fn get( + this: *Headers, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (arguments.len == 0 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) { + return js.JSValueMakeNull(ctx); + } + + const key_len = js.JSStringGetUTF8CString(arguments[0], &header_kv_buf, header_kv_buf.len); + const key = header_kv_buf[0 .. key_len - 1]; + if (this.getHeaderIndex(key)) |index| { + var str = this.asStr(this.entries.items(.value)[index]); + var ref = js.JSStringCreateWithUTF8CString(str.ptr); + defer js.JSStringRelease(ref); + return js.JSValueMakeString(ctx, ref); + } else { + return js.JSValueMakeNull(ctx); + } + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Headers/set + // > The difference between set() and Headers.append is that if the specified header already exists and accepts multiple values + // > set() overwrites the existing value with the new one, whereas Headers.append appends the new value to the end of the set of values. + pub fn set( + this: *Headers, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) { + return js.JSValueMakeUndefined(ctx); + } + + this.putHeader(arguments[0], arguments[1], false); + return js.JSValueMakeUndefined(ctx); + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Headers/append + pub fn append( + this: *Headers, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) { + return js.JSValueMakeUndefined(ctx); + } + + this.putHeader(arguments[0], arguments[1], true); + return js.JSValueMakeUndefined(ctx); + } + pub fn delete( + this: *Headers, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (this.guard == .request or arguments.len < 1 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) { + return js.JSValueMakeUndefined(ctx); + } + + const key_len = js.JSStringGetUTF8CString(arguments[0], &header_kv_buf, header_kv_buf.len) - 1; + const key = header_kv_buf[0..key_len]; + + if (this.getHeaderIndex(key)) |header_i| { + this.entries.orderedRemove(header_i); + } + + return js.JSValueMakeUndefined(ctx); + } + pub fn entries( + this: *Headers, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + Output.prettyErrorln("<r><b>Headers.entries()<r> is not implemented yet - sorry!!", .{}); + return js.JSValueMakeNull(ctx); + } + pub fn keys( + this: *Headers, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + Output.prettyErrorln("H<r><b>eaders.keys()<r> is not implemented yet- sorry!!", .{}); + return js.JSValueMakeNull(ctx); + } + pub fn values( + this: *Headers, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + Output.prettyErrorln("<r><b>Headers.values()<r> is not implemented yet - sorry!!", .{}); + return js.JSValueMakeNull(ctx); + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers + pub fn constructor( + ctx: js.JSContextRef, + function: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + var headers = getAllocator(ctx).create(Headers) catch unreachable; + if (arguments.len > 0 and js.JSValueIsObjectOfClass(ctx, arguments[0], Headers.Class.get().*)) { + var other = castObj(arguments[0], Headers); + other.clone(headers) catch unreachable; + } else { + headers.* = Headers{ + .entries = @TypeOf(headers.entries){}, + .buf = @TypeOf(headers.buf){}, + .used = 0, + .allocator = getAllocator(ctx), + .guard = Guard.none, + }; + } + + return js.JSObjectMake(ctx, Headers.Class.get().*, headers); + } + + pub fn finalize( + this: *Headers, + ctx: js.JSObjectRef, + ) void { + this.deinit(); + } + }; + pub const Class = NewClass( + Headers, + "Headers", + .{ + .@"get" = JS.get, + .@"set" = JS.set, + .@"append" = JS.append, + .@"delete" = JS.delete, + .@"entries" = JS.entries, + .@"keys" = JS.keys, + .@"values" = JS.values, + .@"constructor" = JS.constructor, + .@"finalize" = JS.finalize, + }, + .{}, + true, + false, + ); + + // https://developer.mozilla.org/en-US/docs/Glossary/Guard + pub const Guard = enum { + immutable, + request, + @"request-no-cors", + response, + none, + }; + + // TODO: is it worth making this lazy? instead of copying all the request headers, should we just do it on get/put/iterator? + pub fn fromRequestCtx(allocator: *std.mem.Allocator, request: *http.RequestContext) !Headers { + var total_len: usize = 0; + for (request.request.headers) |header| { + total_len += header.name.len; + total_len += header.value.len; + } + // for the null bytes + total_len += request.request.headers.len * 2; + var headers = Headers{ + .allocator = allocator, + .entries = Entries{}, + .buf = std.ArrayListUnmanaged(u8){}, + }; + try headers.entries.ensureTotalCapacity(allocator, request.request.headers.len); + try headers.buf.ensureTotalCapacity(allocator, total_len); + headers.buf.expandToCapacity(); + headers.guard = Guard.request; - fn appendString(this: *Headers, str: js.JSStringRef, comptime needs_lowercase: bool) Api.StringPointer { - const ptr = Api.StringPointer{ .offset = this.used, .length = js.JSStringGetLength(str) }; + for (request.request.headers) |header| { + headers.entries.appendAssumeCapacity(Kv{ + .name = headers.appendString( + string, + header.name, + true, + true, + true, + ), + .value = headers.appendString( + string, + header.value, + true, + true, + true, + ), + }); + } + + headers.guard = Guard.immutable; + + return headers; + } + + pub fn asStr(headers: *const Headers, ptr: Api.StringPointer) []u8 { + return headers.buf.items[ptr.offset..][0..ptr.length]; + } + + threadlocal var header_kv_buf: [4096]u8 = undefined; + + pub fn putHeader(headers: *Headers, key_: js.JSStringRef, value_: js.JSStringRef, comptime append: bool) void { + const key_len = js.JSStringGetUTF8CString(key_, &header_kv_buf, header_kv_buf.len) - 1; + // TODO: make this one pass instead of two + var key = strings.trim(header_kv_buf[0..key_len], " \n\r"); + key = std.ascii.lowerString(key[0..key.len], key); + + var remainder = header_kv_buf[key.len..]; + const value_len = js.JSStringGetUTF8CString(value_, remainder.ptr, remainder.len) - 1; + var value = strings.trim(remainder[0..value_len], " \n\r"); + + if (headers.getHeaderIndex(key)) |header_i| { + const existing_value = headers.entries.items(.value)[header_i]; + + if (append) { + const end = @truncate(u32, value.len + existing_value.length + 2); + headers.buf.ensureUnusedCapacity(headers.allocator, end) catch unreachable; + headers.buf.expandToCapacity(); + var new_end = headers.buf.items[headers.used..][0 .. end - 1]; + const existing_buf = headers.asStr(existing_value); + std.mem.copy(u8, existing_buf, new_end); + new_end[existing_buf.len] = ','; + std.mem.copy(u8, new_end[existing_buf.len + 1 ..], value); + new_end.ptr[end - 1] = 0; + headers.entries.items(.value)[header_i] = Api.StringPointer{ .offset = headers.used, .length = end - 1 }; + headers.used += end; + // Can we get away with just overwriting in-place? + } else if (existing_value.length < value.len) { + std.mem.copy(u8, headers.asStr(existing_value), value); + headers.entries.items(.value)[header_i].length = @truncate(u32, value.len); + headers.asStr(headers.entries.items(.value)[header_i]).ptr[value.len] = 0; + // Otherwise, append to the buffer, and just don't bother dealing with the existing header value + // We assume that these header objects are going to be kind of short-lived. + } else { + headers.buf.ensureUnusedCapacity(headers.allocator, value.len + 1) catch unreachable; + headers.buf.expandToCapacity(); + headers.entries.items(.value)[header_i] = headers.appendString(string, value, false, true, true); + } + } else { + headers.appendHeader(key, value, false, false, true); + } + } + + pub fn getHeaderIndex(headers: *const Headers, key: string) ?u32 { + for (headers.entries.items(.name)) |name, i| { + if (name.length == key.len and strings.eqlInsensitive(key, headers.asStr(name))) { + return @truncate(u32, i); + } + } + + return null; + } + + pub fn appendHeader( + headers: *Headers, + key: string, + value: string, + comptime needs_lowercase: bool, + comptime needs_normalize: bool, + comptime append_null: bool, + ) void { + headers.buf.ensureUnusedCapacity(headers.allocator, key.len + value.len + 2) catch unreachable; + headers.buf.expandToCapacity(); + headers.entries.append( + headers.allocator, + Kv{ + .name = headers.appendString( + string, + key, + needs_lowercase, + needs_normalize, + append_null, + ), + .value = headers.appendString( + string, + key, + needs_lowercase, + needs_normalize, + append_null, + ), + }, + ) catch unreachable; + } + + fn appendString( + this: *Headers, + comptime StringType: type, + str: StringType, + comptime needs_lowercase: bool, + comptime needs_normalize: bool, + comptime append_null: bool, + ) Api.StringPointer { + var ptr = Api.StringPointer{ .offset = this.used, .length = 0 }; + ptr.length = @truncate( + u32, + switch (comptime StringType) { + js.JSStringRef => js.JSStringGetLength(str), + else => str.len, + }, + ); std.debug.assert(ptr.length > 0); + std.debug.assert(this.buf.items.len >= ptr.offset + ptr.length); - var slice = this.buf.items[ptr.offset][0..ptr.length]; - ptr.length = js.JSStringGetUTF8CString(this, slice.ptr, slice.len); - if (needs_lowercase) { + var slice = this.buf.items[ptr.offset..][0..ptr.length]; + switch (comptime StringType) { + js.JSStringRef => { + ptr.length = @truncate(u32, js.JSStringGetUTF8CString(str, slice.ptr, slice.len) - 1); + }, + else => { + std.mem.copy(u8, slice, str); + }, + } + + if (comptime needs_normalize) { + slice = strings.trim(slice, " \r\n"); + } + + if (comptime needs_lowercase) { for (slice) |c, i| { slice[i] = std.ascii.toLower(c); } } + if (comptime append_null) { + slice.ptr[slice.len] = 0; + this.used += 1; + } - this.used += ptr.len; + ptr.length = @truncate(u32, slice.len); + this.used += @truncate(u32, ptr.length); return ptr; } fn appendNumber(this: *Headers, num: f64) Api.StringPointer { - const ptr = Api.StringPointer{ .offset = this.used, .length = std.fmt.count("{d}", num) }; + var ptr = Api.StringPointer{ .offset = this.used, .length = @truncate( + u32, + std.fmt.count("{d}", .{num}), + ) }; std.debug.assert(this.buf.items.len >= ptr.offset + ptr.length); - var slice = this.buf.items[ptr.offset][0..ptr.length]; - ptr.length = std.fmt.bufPrint(slice, "{d}", num) catch 0; - this.used += ptr.len; + var slice = this.buf.items[ptr.offset..][0..ptr.length]; + var buf = std.fmt.bufPrint(slice, "{d}", .{num}) catch &[_]u8{}; + ptr.length = @truncate(u32, buf.len); + this.used += ptr.length; return ptr; } - pub fn append(this: *Headers, ctx: js.JSContextRef, key: js.JSStringRef, comptime value_type: js.JSType, value: js.JSValueRef) !void { + pub fn appendInit(this: *Headers, ctx: js.JSContextRef, key: js.JSStringRef, comptime value_type: js.JSType, value: js.JSValueRef) !void { this.entries.append(this.allocator, Kv{ - .key = this.appendString(key, true), + .name = this.appendString(js.JSStringRef, key, true, true, false), .value = switch (comptime value_type) { js.JSType.kJSTypeNumber => this.appendNumber(js.JSValueToNumber(ctx, value, null)), - js.JSType.kJSTypeString => this.appendString(value, false), + js.JSType.kJSTypeString => this.appendString(js.JSStringRef, value, true, true, false), + else => unreachable, }, - }); + }) catch unreachable; + } + + pub fn clone(this: *Headers, to: *Headers) !void { + to.* = Headers{ + .entries = try this.entries.clone(this.allocator), + .buf = try @TypeOf(this.buf).initCapacity(this.allocator, this.buf.items.len), + .used = this.used, + .allocator = this.allocator, + .guard = Guard.none, + }; + to.buf.expandToCapacity(); + std.mem.copy(u8, to.buf.items, this.buf.items); } }; @@ -171,8 +554,7 @@ pub const Body = struct { pub fn deinit(this: *Body, allocator: *std.mem.Allocator) void { if (this.init.headers) |headers| { - headers.buf.deinit(headers.allocator); - headers.entries.deinit(headers.allocator); + headers.deinit(); } switch (this.value) { @@ -218,7 +600,7 @@ pub const Body = struct { var estimated_buffer_len: usize = 0; var j: usize = 0; while (j < total_header_count) : (j += 1) { - var key_ref = js.JSPropertyNameArrayGetNameAtIndex(j); + var key_ref = js.JSPropertyNameArrayGetNameAtIndex(header_keys, j); var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null); switch (js.JSValueGetType(ctx, value_ref)) { @@ -231,6 +613,7 @@ pub const Body = struct { } }, js.JSType.kJSTypeString => { + const key_len = js.JSStringGetLength(key_ref); const value_len = js.JSStringGetLength(value_ref); if (key_len > 0 and value_len > 0) { real_header_count += 1; @@ -245,24 +628,25 @@ pub const Body = struct { j = 0; var headers = Headers{ - .buf = try std.ArrayList(u8).initCapacity(allocator, estimated_buffer_len), - .entries = std.mem.zeroes(Headers.Entries), + .allocator = allocator, + .buf = try std.ArrayListUnmanaged(u8).initCapacity(allocator, estimated_buffer_len), + .entries = Headers.Entries{}, }; errdefer headers.deinit(); try headers.entries.ensureTotalCapacity(allocator, real_header_count); while (j < total_header_count) : (j += 1) { - var key_ref = js.JSPropertyNameArrayGetNameAtIndex(j); + var key_ref = js.JSPropertyNameArrayGetNameAtIndex(header_keys, j); var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null); switch (js.JSValueGetType(ctx, value_ref)) { js.JSType.kJSTypeNumber => { if (js.JSStringGetLength(key_ref) == 0) continue; - try headers.append(ctx, key_ref, .kJSTypeNumber, value_ref); + try headers.appendInit(ctx, key_ref, .kJSTypeNumber, value_ref); }, js.JSType.kJSTypeString => { if (js.JSStringGetLength(value_ref) == 0 or js.JSStringGetLength(key_ref) == 0) continue; - try headers.append(ctx, key_ref, .kJSTypeString, value_ref); + try headers.appendInit(ctx, key_ref, .kJSTypeString, value_ref); }, else => {}, } @@ -276,7 +660,7 @@ pub const Body = struct { }, "statusCode".len => { if (js.JSStringIsEqualToUTF8CString(property_name_ref, "statusCode")) { - var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null); + var value_ref = js.JSObjectGetProperty(ctx, init_ref, property_name_ref, null); var exception: js.JSValueRef = null; const number = js.JSValueToNumber(ctx, value_ref, &exception); if (exception != null or !std.math.isFinite(number)) continue; @@ -300,6 +684,20 @@ pub const Body = struct { String, Empty, }; + + pub fn length(value: *const Value) usize { + switch (value.*) { + .ArrayBuffer => |buf| { + return buf.ptr[buf.offset..buf.byte_len].len; + }, + .String => |str| { + return str.len; + }, + .Empty => { + return 0; + }, + } + } }; pub fn @"404"(ctx: js.JSContextRef) Body { @@ -309,12 +707,24 @@ pub const Body = struct { }, .value = .{ .Empty = 0 } }; } - pub fn extract(ctx: js.JSContextRef, body_ref: js.JSObjectRef, exception: ExceptionValueRef) Body { - return extractBody(ctx, body_ref, false, null); + pub fn extract(ctx: js.JSContextRef, body_ref: js.JSObjectRef, exception: js.ExceptionRef) Body { + return extractBody( + ctx, + body_ref, + false, + null, + exception, + ); } - pub fn extractWithInit(ctx: js.JSContextRef, body_ref: js.JSObjectRef, init_ref: js.JSValueRef, exception: ExceptionValueRef) Body { - return extractBody(ctx, body_ref, true, init_ref); + pub fn extractWithInit(ctx: js.JSContextRef, body_ref: js.JSObjectRef, init_ref: js.JSValueRef, exception: js.ExceptionRef) Body { + return extractBody( + ctx, + body_ref, + true, + init_ref, + exception, + ); } // https://github.com/WebKit/webkit/blob/main/Source/WebCore/Modules/fetch/FetchBody.cpp#L45 @@ -323,49 +733,58 @@ pub const Body = struct { body_ref: js.JSObjectRef, comptime has_init: bool, init_ref: js.JSValueRef, - exception: ExceptionValueRef, + exception: js.ExceptionRef, ) Body { var body = Body{ .init = Init{ .headers = null, .status_code = 200 }, .value = .{ .Empty = 0 } }; switch (js.JSValueGetType(ctx, body_ref)) { - js.kJSTypeString => { - if (exception == null) { - var allocator = getAllocator(ctx); - - if (has_init) { - body.init = Init.init(allocator, ctx, init_ref.?) catch unreachable; - } - const len = js.JSStringGetLength(body_ref); - if (len == 0) { - body.value = .{ .String = "" }; - return body; - } + .kJSTypeString => { + var allocator = getAllocator(ctx); - var str = try allocator.alloc(u8, len); + if (comptime has_init) { + if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| { + if (maybeInit) |init_| { + body.init = init_; + } + } else |err| {} + } - body.value = Value{ .String = str[0..js.JSStringGetUTF8CString(body_ref, str.ptr, len)] }; + var str_ref = js.JSValueToStringCopy(ctx, body_ref, exception); + defer js.JSStringRelease(str_ref); + const len = js.JSStringGetMaximumUTF8CStringSize(str_ref); + if (len == 0) { + body.value = .{ .String = "" }; return body; } + + var str = allocator.alloc(u8, len + 1) catch unreachable; + + body.value = Value{ .String = str[0 .. js.JSStringGetUTF8CString(str_ref, str.ptr, len) - 1] }; + return body; }, - js.kJSTypeObject => { + .kJSTypeObject => { const typed_array = js.JSValueGetTypedArrayType(ctx, body_ref, exception); switch (typed_array) { js.JSTypedArrayType.kJSTypedArrayTypeNone => {}, else => { const buffer = ArrayBuffer{ - .ptr = js.JSObjectGetTypedArrayBytesPtr(ctx, body_ref, exception), - .offset = js.JSObjectGetTypedArrayByteOffset(ctx, body_ref, exception), - .len = js.JSObjectGetTypedArrayLength(ctx, body_ref, exception), - .byte_len = js.JSObjectGetTypedArrayLength(ctx, body_ref, exception), + .ptr = @ptrCast([*]u8, js.JSObjectGetTypedArrayBytesPtr(ctx, body_ref.?, exception).?), + .offset = @truncate(u32, js.JSObjectGetTypedArrayByteOffset(ctx, body_ref.?, exception)), + .len = @truncate(u32, js.JSObjectGetTypedArrayLength(ctx, body_ref.?, exception)), + .byte_len = @truncate(u32, js.JSObjectGetTypedArrayLength(ctx, body_ref.?, exception)), .typed_array_type = typed_array, }; - if (exception == null) { - if (has_init) { - body.init = Init.init(allocator, ctx, init_ref.?) catch unreachable; - } - body.value = Value{ .ArrayBuffer = buffer }; - return body; + var allocator = getAllocator(ctx); + + if (comptime has_init) { + if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| { + if (maybeInit) |init_| { + body.init = init_; + } + } else |err| {} } + body.value = Value{ .ArrayBuffer = buffer }; + return body; }, } }, @@ -373,27 +792,341 @@ pub const Body = struct { } if (exception == null) { - JSError(getAllocator(allocator), "Body must be a string or a TypedArray (for now)", .{}, ctx, exception); + JSError(getAllocator(ctx), "Body must be a string or a TypedArray (for now)", .{}, ctx, exception); } - return null; + return body; } }; +// https://developer.mozilla.org/en-US/docs/Web/API/Request +pub const Request = struct { + request_context: *http.RequestContext, + url_string_ref: js.JSStringRef = null, + headers: ?Headers = null, + + pub const Class = NewClass( + Request, + "Request", + .{}, + .{ + .@"cache" = .{ + .@"get" = getCache, + .@"ro" = true, + }, + .@"credentials" = .{ + .@"get" = getCredentials, + .@"ro" = true, + }, + .@"destination" = .{ + .@"get" = getDestination, + .@"ro" = true, + }, + .@"headers" = .{ + .@"get" = getHeaders, + .@"ro" = true, + }, + .@"integrity" = .{ + .@"get" = getIntegrity, + .@"ro" = true, + }, + .@"method" = .{ + .@"get" = getMethod, + .@"ro" = true, + }, + .@"mode" = .{ + .@"get" = getMode, + .@"ro" = true, + }, + .@"redirect" = .{ + .@"get" = getRedirect, + .@"ro" = true, + }, + .@"referrer" = .{ + .@"get" = getReferrer, + .@"ro" = true, + }, + .@"referrerPolicy" = .{ + .@"get" = getReferrerPolicy, + .@"ro" = true, + }, + .@"url" = .{ + .@"get" = getUrl, + .@"ro" = true, + }, + }, + true, + false, + ); + + pub fn getCache( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.default); + } + pub fn getCredentials( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.include); + } + pub fn getDestination( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.empty_string); + } + pub fn getHeaders( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (this.headers == null) { + this.headers = Headers.fromRequestCtx(getAllocator(ctx), this.request_context) catch unreachable; + } + + return js.JSObjectMake(ctx, Headers.Class.get().*, &this.headers.?); + } + pub fn getIntegrity( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.empty_string); + } + pub fn getMethod( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + const string_ref = switch (this.request_context.method) { + .GET => Properties.Refs.GET, + .HEAD => Properties.Refs.HEAD, + .PATCH => Properties.Refs.PATCH, + .PUT => Properties.Refs.PUT, + .POST => Properties.Refs.POST, + .OPTIONS => Properties.Refs.OPTIONS, + else => Properties.Refs.empty_string, + }; + return js.JSValueMakeString(ctx, string_ref); + } + + pub fn getMode( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.navigate); + } + pub fn getRedirect( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.follow); + } + pub fn getReferrer( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.empty_string); + } + pub fn getReferrerPolicy( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeString(ctx, Properties.Refs.empty_string); + } + pub fn getUrl( + this: *Request, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (this.url_string_ref == null) { + this.url_string_ref = js.JSStringCreateWithUTF8CString(this.request_context.getFullURL()); + } + + return js.JSValueMakeString(ctx, this.url_string_ref); + } +}; + +// https://github.com/WebKit/WebKit/blob/main/Source/WebCore/workers/service/FetchEvent.h pub const FetchEvent = struct { started_waiting_at: u64 = 0, response: ?*Response = null, request_context: *http.RequestContext, + request: Request, pub const Class = NewClass( FetchEvent, "FetchEvent", .{ .@"respondWith" = respondWith, .@"waitUntil" = waitUntil }, .{ - .@"client" = getClient, - .@"request" = getRequest, + .@"client" = .{ .@"get" = getClient, .ro = true }, + .@"request" = .{ .@"get" = getRequest, .ro = true }, }, true, false, ); + + pub fn getClient( + this: *FetchEvent, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + Output.prettyErrorln("FetchEvent.client is not implemented yet - sorry!!", .{}); + Output.flush(); + return js.JSValueMakeUndefined(ctx); + } + pub fn getRequest( + this: *FetchEvent, + ctx: js.JSContextRef, + thisObject: js.JSObjectRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSObjectMake(ctx, Request.Class.get().*, &this.request); + } + + // https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith + pub fn respondWith( + this: *FetchEvent, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + if (this.request_context.has_called_done) return js.JSValueMakeUndefined(ctx); + + // A Response or a Promise that resolves to a Response. Otherwise, a network error is returned to Fetch. + if (arguments.len == 0 or !Response.Class.loaded) { + JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception); + this.request_context.sendInternalError(error.respondWithWasEmpty) catch {}; + return js.JSValueMakeUndefined(ctx); + } + + var arg = arguments[0]; + if (!js.JSValueIsObjectOfClass(ctx, arg, Response.Class.ref)) { + JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception); + this.request_context.sendInternalError(error.respondWithWasEmpty) catch {}; + return js.JSValueMakeUndefined(ctx); + } + + var ptr = js.JSObjectGetPrivate(arg); + if (ptr == null) { + JSError(getAllocator(ctx), "event.respondWith()'s Response object was invalid. This may be an internal error.", .{}, ctx, exception); + this.request_context.sendInternalError(error.respondWithWasInvalid) catch {}; + return js.JSValueMakeUndefined(ctx); + } + + var response = @ptrCast(*Response, @alignCast(@alignOf(*Response), ptr.?)); + + var needs_mime_type = true; + var content_length: ?usize = null; + if (response.body.init.headers) |*headers| { + this.request_context.clearHeaders() catch {}; + var i: usize = 0; + while (i < headers.entries.len) : (i += 1) { + var header = headers.entries.get(i); + const name = headers.asStr(header.name); + if (strings.eqlComptime(name, "content-type")) { + needs_mime_type = false; + } + + if (strings.eqlComptime(name, "content-length")) { + content_length = std.fmt.parseInt(usize, headers.asStr(header.value), 10) catch null; + continue; + } + + this.request_context.appendHeaderSlow( + name, + headers.asStr(header.value), + ) catch unreachable; + } + } + + if (needs_mime_type) { + this.request_context.appendHeader("Content-Type", response.mimeType(this.request_context)); + } + + const content_length_ = content_length orelse response.body.value.length(); + + if (content_length_ == 0) { + this.request_context.sendNoContent() catch return js.JSValueMakeUndefined(ctx); + return js.JSValueMakeUndefined(ctx); + } + + if (FeatureFlags.strong_etags_for_built_files) { + switch (response.body.value) { + .ArrayBuffer => |buf| { + const did_send = this.request_context.writeETag(buf.ptr[buf.offset..buf.byte_len]) catch false; + if (did_send) return js.JSValueMakeUndefined(ctx); + }, + .String => |str| { + const did_send = this.request_context.writeETag(str) catch false; + if (did_send) return js.JSValueMakeUndefined(ctx); + }, + else => unreachable, + } + } + + defer this.request_context.done(); + + this.request_context.writeStatusSlow(response.body.init.status_code) catch return js.JSValueMakeUndefined(ctx); + this.request_context.prepareToSendBody(content_length_, false) catch return js.JSValueMakeUndefined(ctx); + + switch (response.body.value) { + .ArrayBuffer => |buf| { + this.request_context.writeBodyBuf(buf.ptr[buf.offset..buf.byte_len]) catch return js.JSValueMakeUndefined(ctx); + }, + .String => |str| { + this.request_context.writeBodyBuf(str) catch return js.JSValueMakeUndefined(ctx); + }, + else => unreachable, + } + + return js.JSValueMakeUndefined(ctx); + } + + pub fn waitUntil( + this: *FetchEvent, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return js.JSValueMakeUndefined(ctx); + } }; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 03382e7a2..b65b142bf 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -367,6 +367,15 @@ pub fn containsNonBmpCodePoint(text: string) bool { return false; } +// this is std.mem.trim except it doesn't forcibly change the slice to be const +pub fn trim(slice: anytype, values_to_strip: []const u8) @TypeOf(slice) { + var begin: usize = 0; + var end: usize = slice.len; + while (begin < end and std.mem.indexOfScalar(u8, values_to_strip, slice[begin]) != null) : (begin += 1) {} + while (end > begin and std.mem.indexOfScalar(u8, values_to_strip, slice[end - 1]) != null) : (end -= 1) {} + return slice[begin..end]; +} + pub fn containsNonBmpCodePointUTF16(_text: JavascriptString) bool { const n = _text.len; if (n > 0) { diff --git a/src/sync.zig b/src/sync.zig index c43c227db..8b608757c 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -465,7 +465,7 @@ pub fn Channel( if (self.is_closed) return error.Closed; - self.buffer.writeItem(item) catch |err| { + self.buffer.write(items) catch |err| { if (buffer_type == .Dynamic) return err; break :blk false; |