aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--.vscode/settings.json1
-rw-r--r--Makefile4
-rw-r--r--build.zig7
-rw-r--r--integration/bunjs-only-snippets/html-rewriter.test.js228
m---------src/deps/lol-html0
-rw-r--r--src/deps/lol-html.zig705
-rw-r--r--src/javascript/jsc/api/html_rewriter.zig1340
-rw-r--r--src/javascript/jsc/base.zig12
-rw-r--r--src/javascript/jsc/bindings/BunStream.cpp2
-rw-r--r--src/javascript/jsc/bindings/Path.cpp2
-rw-r--r--src/javascript/jsc/bindings/bindings.cpp16
-rw-r--r--src/javascript/jsc/bindings/bindings.zig47
-rw-r--r--src/javascript/jsc/bindings/exports.zig2
-rw-r--r--src/javascript/jsc/bindings/headers-cpp.h2
-rw-r--r--src/javascript/jsc/bindings/headers.h7
-rw-r--r--src/javascript/jsc/bindings/headers.zig1
-rw-r--r--src/javascript/jsc/bindings/helpers.h2
-rw-r--r--src/javascript/jsc/javascript.zig4
-rw-r--r--src/javascript/jsc/node/types.zig8
-rw-r--r--src/jsc.zig6
21 files changed, 2249 insertions, 150 deletions
diff --git a/.gitmodules b/.gitmodules
index 9dd25dae9..61f62fbfb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -29,3 +29,6 @@
path = src/deps/libbacktrace
url = https://github.com/ianlancetaylor/libbacktrace
ignore = dirty
+[submodule "src/deps/lol-html"]
+ path = src/deps/lol-html
+ url = https://github.com/cloudflare/lol-html
diff --git a/.vscode/settings.json b/.vscode/settings.json
index bd7699e02..dcd7498a0 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -41,6 +41,7 @@
"src/deps/boringssl": true,
"src/deps/openssl": true,
"src/deps/zlib": true,
+ "src/deps/lol-html": true,
"integration/snippets/package-json-exports/_node_modules_copy": true
},
"C_Cpp.files.exclude": {
diff --git a/Makefile b/Makefile
index a2fbaf826..d428e627b 100644
--- a/Makefile
+++ b/Makefile
@@ -266,6 +266,7 @@ ARCHIVE_FILES_WITHOUT_LIBCRYPTO = $(MIMALLOC_FILE_PATH) \
$(BUN_DEPS_OUT_DIR)/libarchive.a \
$(BUN_DEPS_OUT_DIR)/libssl.a \
$(BUN_DEPS_OUT_DIR)/picohttpparser.o \
+ $(BUN_DEPS_OUT_DIR)/liblolhtml.a
ARCHIVE_FILES = $(ARCHIVE_FILES_WITHOUT_LIBCRYPTO) $(BUN_DEPS_OUT_DIR)/libcrypto.boring.a
@@ -312,6 +313,9 @@ bun:
vendor-without-check: api analytics node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp zlib boringssl libarchive libbacktrace
+lolhtml:
+ cd $(BUN_DEPS_DIR)/lol-html/ && cd $(BUN_DEPS_DIR)/lol-html/c-api && cargo build --release && cp target/release/liblolhtml.a $(BUN_DEPS_OUT_DIR)
+
boringssl-build:
cd $(BUN_DEPS_DIR)/boringssl && mkdir -p build && cd build && CFLAGS="$(CFLAGS)" cmake $(CMAKE_FLAGS) -GNinja .. && ninja
diff --git a/build.zig b/build.zig
index 4d948d561..3cdea2678 100644
--- a/build.zig
+++ b/build.zig
@@ -79,6 +79,11 @@ fn addInternalPackages(step: *std.build.LibExeObjStep, _: std.mem.Allocator, tar
.path = pkgPath("src/io/io_stub.zig"),
};
+ var lol_html: std.build.Pkg = .{
+ .name = "lolhtml",
+ .path = pkgPath("src/deps/lol-html.zig"),
+ };
+
var io = if (target.isDarwin())
io_darwin
else if (target.isLinux())
@@ -156,6 +161,7 @@ fn addInternalPackages(step: *std.build.LibExeObjStep, _: std.mem.Allocator, tar
step.addPackage(javascript_core);
step.addPackage(crash_reporter);
step.addPackage(datetime);
+ step.addPackage(lol_html);
}
var output_dir: []const u8 = "";
fn panicIfNotFound(comptime filepath: []const u8) []const u8 {
@@ -512,6 +518,7 @@ pub fn linkObjectFiles(b: *std.build.Builder, obj: *std.build.LibExeObjStep, tar
.{ "libWTF.a", "libWTF.a" },
.{ "libbmalloc.a", "libbmalloc.a" },
.{ "libbacktrace.a", "libbacktrace.a" },
+ .{ "liblolhtml.a", "liblolhtml.a" },
});
for (dirs_to_search.slice()) |deps_path| {
diff --git a/integration/bunjs-only-snippets/html-rewriter.test.js b/integration/bunjs-only-snippets/html-rewriter.test.js
index cd4fad746..77c14e479 100644
--- a/integration/bunjs-only-snippets/html-rewriter.test.js
+++ b/integration/bunjs-only-snippets/html-rewriter.test.js
@@ -1,8 +1,232 @@
import { describe, it, expect } from "bun:test";
describe("HTMLRewriter", () => {
- it("exists globally", () => {
+ it("exists globally", async () => {
expect(typeof HTMLRewriter).toBe("function");
- console.log(HTMLRewriter.name);
+ expect(typeof HTMLRewriter.constructor).toBe("function");
+ });
+ it("supports element handlers", async () => {
+ var rewriter = new HTMLRewriter();
+ rewriter.on("div", {
+ element(element) {
+ element.setInnerContent("<blink>it worked!</blink>", { html: true });
+ },
+ });
+ var input = new Response("<div>hello</div>");
+ var output = rewriter.transform(input);
+ expect(await output.text()).toBe("<div><blink>it worked!</blink></div>");
+ });
+
+ it("handles element specific mutations", async () => {
+ // prepend/append
+ let res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.prepend("<span>prepend</span>");
+ element.prepend("<span>prepend html</span>", { html: true });
+ element.append("<span>append</span>");
+ element.append("<span>append html</span>", { html: true });
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe(
+ [
+ "<p>",
+ "<span>prepend html</span>",
+ "&lt;span&gt;prepend&lt;/span&gt;",
+ "test",
+ "&lt;span&gt;append&lt;/span&gt;",
+ "<span>append html</span>",
+ "</p>",
+ ].join("")
+ );
+
+ // setInnerContent
+ res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.setInnerContent("<span>replace</span>");
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("<p>&lt;span&gt;replace&lt;/span&gt;</p>");
+ res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.setInnerContent("<span>replace</span>", { html: true });
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("<p><span>replace</span></p>");
+
+ // removeAndKeepContent
+ res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.removeAndKeepContent();
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("test");
+ });
+
+ it("handles element class properties", async () => {
+ class Handler {
+ constructor(content) {
+ this.content = content;
+ }
+
+ // noinspection JSUnusedGlobalSymbols
+ element(element) {
+ element.setInnerContent(this.content);
+ }
+ }
+ const res = new HTMLRewriter()
+ .on("p", new Handler("new"))
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("<p>new</p>");
+ });
+
+ const commentsMutationsInput = "<p><!--test--></p>";
+ const commentsMutationsExpected = {
+ beforeAfter: [
+ "<p>",
+ "&lt;span&gt;before&lt;/span&gt;",
+ "<span>before html</span>",
+ "<!--test-->",
+ "<span>after html</span>",
+ "&lt;span&gt;after&lt;/span&gt;",
+ "</p>",
+ ].join(""),
+ replace: "<p>&lt;span&gt;replace&lt;/span&gt;</p>",
+ replaceHtml: "<p><span>replace</span></p>",
+ remove: "<p></p>",
+ };
+
+ const commentPropertiesMacro = async (t, func) => {
+ const res = func(new HTMLRewriter(), (comment) => {
+ expect(comment.removed).toBe(false);
+ expect(comment.text).toBe("test");
+ comment.text = "new";
+ }).transform(new Response("<p><!--test--></p>"));
+ t.is(await res.text(), "<p><!--new--></p>");
+ };
+ test(
+ "HTMLRewriter: handles comment properties",
+ commentPropertiesMacro,
+ (rw, comments) => rw.on("p", { comments })
+ );
+ test(
+ "HTMLRewriter: handles comment mutations",
+ mutationsMacro,
+ (rw, comments) => rw.on("p", { comments }),
+ commentsMutationsInput,
+ commentsMutationsExpected
+ );
+
+ it("selector tests", async () => {
+ const checkSelector = async (selector, input, expected) => {
+ const res = new HTMLRewriter()
+ .on(selector, {
+ element(element) {
+ element.setInnerContent("new");
+ },
+ })
+ .transform(new Response(input));
+ expect(await res.text()).toBe(expected);
+ };
+
+ await checkSelector("*", "<h1>1</h1><p>2</p>", "<h1>new</h1><p>new</p>");
+ await checkSelector("p", "<h1>1</h1><p>2</p>", "<h1>1</h1><p>new</p>");
+ await checkSelector(
+ "p:nth-child(2)",
+ "<div><p>1</p><p>2</p><p>3</p></div>",
+ "<div><p>1</p><p>new</p><p>3</p></div>"
+ );
+ await checkSelector(
+ "p:first-child",
+ "<div><p>1</p><p>2</p><p>3</p></div>",
+ "<div><p>new</p><p>2</p><p>3</p></div>"
+ );
+ await checkSelector(
+ "p:nth-of-type(2)",
+ "<div><p>1</p><h1>2</h1><p>3</p><h1>4</h1><p>5</p></div>",
+ "<div><p>1</p><h1>2</h1><p>new</p><h1>4</h1><p>5</p></div>"
+ );
+ await checkSelector(
+ "p:first-of-type",
+ "<div><h1>1</h1><p>2</p><p>3</p></div>",
+ "<div><h1>1</h1><p>new</p><p>3</p></div>"
+ );
+ await checkSelector(
+ "p:not(:first-child)",
+ "<div><p>1</p><p>2</p><p>3</p></div>",
+ "<div><p>1</p><p>new</p><p>new</p></div>"
+ );
+ await checkSelector(
+ "p.red",
+ '<p class="red">1</p><p>2</p>',
+ '<p class="red">new</p><p>2</p>'
+ );
+ await checkSelector(
+ "h1#header",
+ '<h1 id="header">1</h1><h1>2</h1>',
+ '<h1 id="header">new</h1><h1>2</h1>'
+ );
+ await checkSelector(
+ "p[data-test]",
+ "<p data-test>1</p><p>2</p>",
+ "<p data-test>new</p><p>2</p>"
+ );
+ await checkSelector(
+ 'p[data-test="one"]',
+ '<p data-test="one">1</p><p data-test="two">2</p>',
+ '<p data-test="one">new</p><p data-test="two">2</p>'
+ );
+ await checkSelector(
+ 'p[data-test="one" i]',
+ '<p data-test="one">1</p><p data-test="OnE">2</p><p data-test="two">3</p>',
+ '<p data-test="one">new</p><p data-test="OnE">new</p><p data-test="two">3</p>'
+ );
+ await checkSelector(
+ 'p[data-test="one" s]',
+ '<p data-test="one">1</p><p data-test="OnE">2</p><p data-test="two">3</p>',
+ '<p data-test="one">new</p><p data-test="OnE">2</p><p data-test="two">3</p>'
+ );
+ await checkSelector(
+ 'p[data-test~="two"]',
+ '<p data-test="one two three">1</p><p data-test="one two">2</p><p data-test="one">3</p>',
+ '<p data-test="one two three">new</p><p data-test="one two">new</p><p data-test="one">3</p>'
+ );
+ await checkSelector(
+ 'p[data-test^="a"]',
+ '<p data-test="a1">1</p><p data-test="a2">2</p><p data-test="b1">3</p>',
+ '<p data-test="a1">new</p><p data-test="a2">new</p><p data-test="b1">3</p>'
+ );
+ await checkSelector(
+ 'p[data-test$="1"]',
+ '<p data-test="a1">1</p><p data-test="a2">2</p><p data-test="b1">3</p>',
+ '<p data-test="a1">new</p><p data-test="a2">2</p><p data-test="b1">new</p>'
+ );
+ await checkSelector(
+ 'p[data-test*="b"]',
+ '<p data-test="abc">1</p><p data-test="ab">2</p><p data-test="a">3</p>',
+ '<p data-test="abc">new</p><p data-test="ab">new</p><p data-test="a">3</p>'
+ );
+ await checkSelector(
+ 'p[data-test|="a"]',
+ '<p data-test="a">1</p><p data-test="a-1">2</p><p data-test="a2">3</p>',
+ '<p data-test="a">new</p><p data-test="a-1">new</p><p data-test="a2">3</p>'
+ );
+ await checkSelector(
+ "div span",
+ "<div><h1><span>1</span></h1><span>2</span><b>3</b></div>",
+ "<div><h1><span>new</span></h1><span>new</span><b>3</b></div>"
+ );
+ await checkSelector(
+ "div > span",
+ "<div><h1><span>1</span></h1><span>2</span><b>3</b></div>",
+ "<div><h1><span>1</span></h1><span>new</span><b>3</b></div>"
+ );
});
});
diff --git a/src/deps/lol-html b/src/deps/lol-html
new file mode 160000
+Subproject 2eed349dcdfa4ff5c19fe7c6e501cfd68760103
diff --git a/src/deps/lol-html.zig b/src/deps/lol-html.zig
new file mode 100644
index 000000000..79cbceb75
--- /dev/null
+++ b/src/deps/lol-html.zig
@@ -0,0 +1,705 @@
+pub const Error = error{Fail};
+const std = @import("std");
+
+pub const MemorySettings = extern struct {
+ preallocated_parsing_buffer_size: usize,
+ max_allowed_memory_usage: usize,
+};
+
+pub const HTMLRewriter = opaque {
+ extern fn lol_html_rewriter_write(rewriter: *HTMLRewriter, chunk: [*]const u8, chunk_len: usize) c_int;
+ extern fn lol_html_rewriter_end(rewriter: *HTMLRewriter) c_int;
+ extern fn lol_html_rewriter_free(rewriter: *HTMLRewriter) void;
+
+ pub fn write(rewriter: *HTMLRewriter, chunk: []const u8) Error!void {
+ if (rewriter.lol_html_rewriter_write(chunk.ptr, chunk.len) < 0)
+ return error.Fail;
+ }
+
+ /// Completes rewriting and flushes the remaining output.
+ ///
+ /// Returns 0 in case of success and -1 otherwise. The actual error message
+ /// can be obtained using `lol_html_take_last_error` function.
+ ///
+ /// WARNING: after calling this function, further attempts to use the rewriter
+ /// (other than `lol_html_rewriter_free`) will cause a thread panic.
+ pub fn end(rewriter: *HTMLRewriter) Error!void {
+ if (rewriter.lol_html_rewriter_end() < 0)
+ return error.Fail;
+ }
+
+ pub fn deinit(this: *HTMLRewriter) void {
+ this.lol_html_rewriter_free();
+ }
+
+ pub const Builder = opaque {
+ extern fn lol_html_rewriter_builder_new() *HTMLRewriter.Builder;
+ extern fn lol_html_rewriter_builder_add_element_content_handlers(
+ builder: *HTMLRewriter.Builder,
+ selector: *const HTMLSelector,
+ element_handler: ?lol_html_element_handler_t,
+ element_handler_user_data: ?*anyopaque,
+ comment_handler: ?lol_html_comment_handler_t,
+ comment_handler_user_data: ?*anyopaque,
+ text_handler: ?lol_html_text_handler_handler_t,
+ text_handler_user_data: ?*anyopaque,
+ ) c_int;
+ extern fn lol_html_rewriter_builder_free(builder: *HTMLRewriter.Builder) void;
+ extern fn lol_html_rewriter_build(
+ builder: *HTMLRewriter.Builder,
+ encoding: [*]const u8,
+ encoding_len: usize,
+ memory_settings: MemorySettings,
+ output_sink: ?fn ([*]const u8, usize, *anyopaque) callconv(.C) void,
+ output_sink_user_data: *anyopaque,
+ strict: bool,
+ ) ?*HTMLRewriter;
+ extern fn unstable_lol_html_rewriter_build_with_esi_tags(
+ builder: *HTMLRewriter.Builder,
+ encoding: [*]const u8,
+ encoding_len: usize,
+ memory_settings: MemorySettings,
+ output_sink: ?fn ([*]const u8, usize, *anyopaque) callconv(.C) void,
+ output_sink_user_data: *anyopaque,
+ strict: bool,
+ ) ?*HTMLRewriter;
+
+ extern fn lol_html_rewriter_builder_add_document_content_handlers(
+ builder: *HTMLRewriter.Builder,
+ doctype_handler: ?DirectiveFunctionType(DocType),
+ doctype_handler_user_data: ?*anyopaque,
+ comment_handler: ?lol_html_comment_handler_t,
+ comment_handler_user_data: ?*anyopaque,
+ text_handler: ?lol_html_text_handler_handler_t,
+ text_handler_user_data: ?*anyopaque,
+ doc_end_handler: ?lol_html_doc_end_handler_t,
+ doc_end_user_data: ?*anyopaque,
+ ) c_int;
+
+ pub const init = lol_html_rewriter_builder_new;
+
+ /// Adds document-level content handlers to the builder.
+ ///
+ /// If a particular handler is not required then NULL can be passed
+ /// instead. Don't use stub handlers in this case as this affects
+ /// performance - rewriter skips parsing of the content that doesn't
+ /// need to be processed.
+ ///
+ /// Each handler can optionally have associated user data which will be
+ /// passed to the handler on each invocation along with the rewritable
+ /// unit argument.
+ ///
+ /// If any of handlers return LOL_HTML_STOP directive then rewriting
+ /// stops immediately and `write()` or `end()` of the rewriter methods
+ /// return an error code.
+ ///
+ /// WARNING: Pointers passed to handlers are valid only during the
+ /// handler execution. So they should never be leaked outside of handlers.
+ pub fn addDocumentContentHandlers(
+ builder: *HTMLRewriter.Builder,
+ comptime DocTypeHandler: type,
+ comptime doctype_handler: ?DirectiveFunctionTypeForHandler(DocType, DocTypeHandler),
+ doctype_handler_data: ?*DocTypeHandler,
+ comptime CommentHandler: type,
+ comptime comment_handler: ?DirectiveFunctionTypeForHandler(Comment, CommentHandler),
+ comment_handler_data: ?*CommentHandler,
+ comptime TextChunkHandler: type,
+ comptime text_chunk_handler: ?DirectiveFunctionTypeForHandler(TextChunk, TextChunkHandler),
+ text_chunk_handler_data: ?*TextChunkHandler,
+ comptime DocEndHandler: type,
+ comptime end_tag_handler: ?DirectiveFunctionTypeForHandler(DocEnd, DocEndHandler),
+ end_tag_handler_data: ?*DocEndHandler,
+ ) Error!void {
+ return switch (builder.lol_html_rewriter_builder_add_document_content_handlers(
+ if (doctype_handler_data != null)
+ DirectiveHandler(DocType, DocTypeHandler, doctype_handler.?)
+ else
+ null,
+ doctype_handler_data,
+ if (comment_handler_data != null)
+ DirectiveHandler(Comment, CommentHandler, comment_handler.?)
+ else
+ null,
+ comment_handler_data,
+ if (text_chunk_handler_data != null)
+ DirectiveHandler(TextChunk, TextChunkHandler, text_chunk_handler.?)
+ else
+ null,
+ text_chunk_handler_data,
+ if (end_tag_handler_data != null)
+ DirectiveHandler(DocEnd, DocEndHandler, end_tag_handler.?)
+ else
+ null,
+ end_tag_handler_data,
+ )) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ /// Adds element content handlers to the builder for the
+ /// given CSS selector.
+ ///
+ /// Selector should be a valid UTF8-string.
+ ///
+ /// If a particular handler is not required then NULL can be passed
+ /// instead. Don't use stub handlers in this case as this affects
+ /// performance - rewriter skips parsing of the content that doesn't
+ /// need to be processed.
+ ///
+ /// Each handler can optionally have associated user data which will be
+ /// passed to the handler on each invocation along with the rewritable
+ /// unit argument.
+ ///
+ /// If any of handlers return LOL_HTML_STOP directive then rewriting
+ /// stops immediately and `write()` or `end()` of the rewriter methods
+ /// return an error code.
+ ///
+ /// Returns 0 in case of success and -1 otherwise. The actual error message
+ /// can be obtained using `lol_html_take_last_error` function.
+ ///
+ /// WARNING: Pointers passed to handlers are valid only during the
+ /// handler execution. So they should never be leaked outside of handlers.
+ pub fn addElementContentHandlers(
+ builder: *HTMLRewriter.Builder,
+ selector: *HTMLSelector,
+ comptime ElementHandler: type,
+ comptime element_handler: ?DirectiveFunctionTypeForHandler(Element, ElementHandler),
+ element_handler_data: ?*ElementHandler,
+ comptime CommentHandler: type,
+ comptime comment_handler: ?DirectiveFunctionTypeForHandler(Comment, CommentHandler),
+ comment_handler_data: ?*CommentHandler,
+ comptime TextChunkHandler: type,
+ comptime text_chunk_handler: ?DirectiveFunctionTypeForHandler(TextChunk, TextChunkHandler),
+ text_chunk_handler_data: ?*TextChunkHandler,
+ ) Error!void {
+ return switch (builder.lol_html_rewriter_builder_add_element_content_handlers(
+ selector,
+ if (element_handler_data != null)
+ DirectiveHandler(Element, ElementHandler, element_handler.?)
+ else
+ null,
+ element_handler_data,
+ if (comment_handler_data != null)
+ DirectiveHandler(Comment, CommentHandler, comment_handler.?)
+ else
+ null,
+ comment_handler_data,
+ if (text_chunk_handler_data != null)
+ DirectiveHandler(TextChunk, TextChunkHandler, text_chunk_handler.?)
+ else
+ null,
+ text_chunk_handler_data,
+ )) {
+ -1 => error.Fail,
+ 0 => void{},
+ else => unreachable,
+ };
+ }
+
+ pub fn build(
+ builder: *HTMLRewriter.Builder,
+ encoding: Encoding,
+ memory_settings: MemorySettings,
+ strict: bool,
+ comptime OutputSink: type,
+ output_sink: *OutputSink,
+ comptime Writer: (fn (*OutputSink, bytes: []const u8) void),
+ comptime Done: (fn (*OutputSink) void),
+ ) Error!*HTMLRewriter {
+ const encoding_ = Encoding.label.getAssertContains(encoding);
+ return builder.lol_html_rewriter_build(
+ encoding_.ptr,
+ encoding_.len,
+ memory_settings,
+ OutputSinkFunction(OutputSink, Writer, Done),
+ output_sink,
+ strict,
+ ) orelse return error.Fail;
+ }
+
+ fn OutputSinkFunction(
+ comptime OutputSinkType: type,
+ comptime Writer: (fn (*OutputSinkType, bytes: []const u8) void),
+ comptime Done: (fn (*OutputSinkType) void),
+ ) (fn ([*]const u8, usize, *anyopaque) callconv(.C) void) {
+ return struct {
+ fn writeChunk(ptr: [*]const u8, len: usize, user_data: *anyopaque) callconv(.C) void {
+ @setRuntimeSafety(false);
+ var this = @ptrCast(*OutputSinkType, @alignCast(@alignOf(*OutputSinkType), user_data));
+ switch (len) {
+ 0 => Done(this),
+ else => Writer(this, ptr[0..len]),
+ }
+ }
+ }.writeChunk;
+ }
+ };
+};
+
+pub const HTMLSelector = opaque {
+ extern fn lol_html_selector_parse(selector: [*]const u8, selector_len: usize) ?*HTMLSelector;
+ extern fn lol_html_selector_free(selector: *HTMLSelector) void;
+
+ /// Frees the memory held by the parsed selector object.
+ pub fn deinit(selector: *HTMLSelector) void {
+ selector.lol_html_selector_free();
+ }
+
+ /// Parses given CSS selector string.
+ ///
+ /// Returns NULL if parsing error occurs. The actual error message
+ /// can be obtained using `lol_html_take_last_error` function.
+ ///
+ /// WARNING: Selector SHOULD NOT be deallocated if there are any active rewriter
+ /// builders that accepted it as an argument to `lol_html_rewriter_builder_add_element_content_handlers()`
+ /// method. Deallocate all dependant rewriter builders first and then
+ /// use `lol_html_selector_free` function to free the selector.
+ pub fn parse(selector: []const u8) Error!*HTMLSelector {
+ if (lol_html_selector_parse(selector.ptr, selector.len)) |ptr|
+ return ptr
+ else
+ return error.Fail;
+ }
+};
+pub const TextChunk = opaque {
+ extern fn lol_html_text_chunk_content_get(chunk: *const TextChunk) TextChunk.Content;
+ extern fn lol_html_text_chunk_is_last_in_text_node(chunk: *const TextChunk) bool;
+ extern fn lol_html_text_chunk_before(chunk: *TextChunk, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_text_chunk_after(chunk: *TextChunk, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_text_chunk_replace(chunk: *TextChunk, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_text_chunk_remove(chunk: *TextChunk) void;
+ extern fn lol_html_text_chunk_is_removed(chunk: *const TextChunk) bool;
+ extern fn lol_html_text_chunk_user_data_set(chunk: *const TextChunk, user_data: ?*anyopaque) void;
+ extern fn lol_html_text_chunk_user_data_get(chunk: *const TextChunk) ?*anyopaque;
+
+ pub const Content = extern struct {
+ ptr: [*]const u8,
+ len: usize,
+
+ pub fn slice(this: Content) []const u8 {
+ return this.ptr[0..this.len];
+ }
+ };
+
+ pub fn getContent(this: *const TextChunk) TextChunk.Content {
+ return this.lol_html_text_chunk_content_get();
+ }
+ pub fn isLastInTextNode(this: *const TextChunk) bool {
+ return this.lol_html_text_chunk_is_last_in_text_node();
+ }
+ /// Inserts the content string before the text chunk either as raw text or as HTML.
+ ///
+ /// Content should be a valid UTF8-string.
+ ///
+ /// Returns 0 in case of success and -1 otherwise. The actual error message
+ /// can be obtained using `lol_html_take_last_error` function.
+ pub fn before(this: *TextChunk, content: []const u8, is_html: bool) Error!void {
+ if (this.lol_html_text_chunk_before(content.ptr, content.len, is_html) < 0)
+ return error.Fail;
+ }
+ /// Inserts the content string after the text chunk either as raw text or as HTML.
+ ///
+ /// Content should be a valid UTF8-string.
+ ///
+ /// Returns 0 in case of success and -1 otherwise. The actual error message
+ /// can be obtained using `lol_html_take_last_error` function.
+ pub fn after(this: *TextChunk, content: []const u8, is_html: bool) Error!void {
+ if (this.lol_html_text_chunk_after(content.ptr, content.len, is_html) < 0)
+ return error.Fail;
+ }
+ // Replace the text chunk with the content of the string which is interpreted
+ // either as raw text or as HTML.
+ //
+ // Content should be a valid UTF8-string.
+ //
+ // Returns 0 in case of success and -1 otherwise. The actual error message
+ // can be obtained using `lol_html_take_last_error` function.
+ pub fn replace(this: *TextChunk, content: []const u8, is_html: bool) Error!void {
+ if (this.lol_html_text_chunk_replace(content.ptr, content.len, is_html) < 0)
+ return error.Fail;
+ }
+ /// Removes the text chunk.
+ pub fn remove(this: *TextChunk) void {
+ return this.lol_html_text_chunk_remove();
+ }
+ pub fn isRemoved(this: *const TextChunk) bool {
+ return this.lol_html_text_chunk_is_removed();
+ }
+ pub fn setUserData(this: *const TextChunk, comptime Type: type, value: ?*Type) void {
+ return this.lol_html_text_chunk_user_data_set(value);
+ }
+ pub fn getUserData(this: *const TextChunk, comptime Type: type) ?*Type {
+ return @ptrCast(?*Type, @alignCast(@alignOf(?*Type), this.lol_html_text_chunk_user_data_get()));
+ }
+};
+pub const Element = opaque {
+ extern fn lol_html_element_get_attribute(element: *const Element, name: [*]const u8, name_len: usize) HTMLString;
+ extern fn lol_html_element_has_attribute(element: *const Element, name: [*]const u8, name_len: usize) c_int;
+ extern fn lol_html_element_set_attribute(element: *Element, name: [*]const u8, name_len: usize, value: [*]const u8, value_len: usize) c_int;
+ extern fn lol_html_element_remove_attribute(element: *Element, name: [*]const u8, name_len: usize) c_int;
+ extern fn lol_html_element_before(element: *Element, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_element_prepend(element: *Element, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_element_append(element: *Element, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_element_after(element: *Element, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_element_set_inner_content(element: *Element, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_element_replace(element: *Element, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_element_remove(element: *const Element) void;
+ extern fn lol_html_element_remove_and_keep_content(element: *const Element) void;
+ extern fn lol_html_element_is_removed(element: *const Element) bool;
+ extern fn lol_html_element_user_data_set(element: *const Element, user_data: ?*anyopaque) void;
+ extern fn lol_html_element_user_data_get(element: *const Element) ?*anyopaque;
+ extern fn lol_html_element_on_end_tag(element: *Element, end_tag_handler: lol_html_end_tag_handler_t, user_data: ?*anyopaque) c_int;
+
+ pub fn getAttribute(element: *const Element, name: []const u8) HTMLString {
+ return lol_html_element_get_attribute(element, name.ptr, name.len);
+ }
+ pub fn hasAttribute(element: *const Element, name: []const u8) Error!bool {
+ return switch (lol_html_element_has_attribute(element, name.ptr, name.len)) {
+ 0 => false,
+ 1 => true,
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn setAttribute(element: *Element, name: []const u8, value: []const u8) Error!void {
+ return switch (lol_html_element_set_attribute(element, name.ptr, name.len, value.ptr, value.len)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn removeAttribute(element: *Element, name: []const u8) Error!void {
+ return switch (lol_html_element_remove_attribute(element, name.ptr, name.len)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn before(element: *Element, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_element_before(element, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn prepend(element: *Element, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_element_prepend(element, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn append(element: *Element, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_element_append(element, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn after(element: *Element, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_element_after(element, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn setInnerContent(element: *Element, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_element_set_inner_content(element, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ /// Replaces the element with the provided text or HTML content.
+ ///
+ /// Content should be a valid UTF8-string.
+ ///
+ /// Returns 0 in case of success and -1 otherwise. The actual error message
+ /// can be obtained using `lol_html_take_last_error` function.
+ pub fn replace(element: *Element, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_element_replace(element, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn remove(element: *const Element) void {
+ lol_html_element_remove(element);
+ }
+ // Removes the element, but leaves its inner content intact.
+ pub fn removeAndKeepContent(element: *const Element) void {
+ lol_html_element_remove_and_keep_content(element);
+ }
+ pub fn isRemoved(element: *const Element) bool {
+ return lol_html_element_is_removed(element);
+ }
+ pub fn setUserData(element: *const Element, user_data: ?*anyopaque) void {
+ lol_html_element_user_data_set(element, user_data);
+ }
+ pub fn getUserData(element: *const Element, comptime Type: type) ?*Type {
+ return @ptrCast(?*Element, @alignCast(@alignOf(?*Element), lol_html_element_user_data_get(element)));
+ }
+ pub fn onEndTag(element: *Element, end_tag_handler: lol_html_end_tag_handler_t, user_data: ?*anyopaque) Error!void {
+ return switch (lol_html_element_on_end_tag(element, end_tag_handler, user_data)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ extern fn lol_html_element_tag_name_get(element: *const Element) HTMLString;
+ extern fn lol_html_element_tag_name_set(element: *Element, name: [*]const u8, name_len: usize) c_int;
+ extern fn lol_html_element_namespace_uri_get(element: *const Element) [*:0]const u8;
+ extern fn lol_html_attributes_iterator_get(element: *const Element) ?*Attribute.Iterator;
+
+ pub fn tagName(element: *const Element) HTMLString {
+ return lol_html_element_tag_name_get(element);
+ }
+
+ pub fn setTagName(element: *Element, name: []const u8) Error!void {
+ return switch (lol_html_element_tag_name_set(element, name.ptr, name.len)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ pub fn namespaceURI(element: *const Element) [*:0]const u8 {
+ return lol_html_element_namespace_uri_get(element);
+ }
+
+ pub fn attributes(element: *const Element) ?*Attribute.Iterator {
+ return lol_html_attributes_iterator_get(element);
+ }
+};
+
+pub const HTMLString = extern struct {
+ ptr: [*]const u8,
+ len: usize,
+
+ extern fn lol_html_str_free(str: HTMLString) void;
+ pub fn deinit(this: HTMLString) void {
+ // if (this.len > 0) {
+ lol_html_str_free(this);
+ // }
+ }
+
+ pub extern fn lol_html_take_last_error(...) HTMLString;
+
+ pub fn lastError() HTMLString {
+ return lol_html_take_last_error();
+ }
+
+ pub fn slice(this: HTMLString) []const u8 {
+ @setRuntimeSafety(false);
+ return this.ptr[0..this.len];
+ }
+};
+
+pub const EndTag = opaque {
+ extern fn lol_html_end_tag_before(end_tag: *EndTag, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_end_tag_after(end_tag: *EndTag, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_end_tag_remove(end_tag: *EndTag) void;
+ extern fn lol_html_end_tag_name_get(end_tag: *const EndTag) HTMLString;
+ extern fn lol_html_end_tag_name_set(end_tag: *EndTag, name: [*]const u8, name_len: usize) c_int;
+
+ pub fn before(end_tag: *EndTag, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_end_tag_before(end_tag, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ pub fn after(end_tag: *EndTag, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_end_tag_after(end_tag, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+ pub fn remove(end_tag: *EndTag) void {
+ lol_html_end_tag_remove(end_tag);
+ }
+
+ pub fn getName(end_tag: *const EndTag) HTMLString {
+ return lol_html_end_tag_name_get(end_tag);
+ }
+
+ pub fn setName(end_tag: *EndTag, name: []const u8) Error!void {
+ return switch (lol_html_end_tag_name_set(end_tag, name.ptr, name.len)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+};
+
+pub const Attribute = opaque {
+ extern fn lol_html_attribute_name_get(attribute: *const Attribute) HTMLString;
+ extern fn lol_html_attribute_value_get(attribute: *const Attribute) HTMLString;
+ pub fn name(this: *const Attribute) HTMLString {
+ return this.lol_html_attribute_name_get();
+ }
+ pub fn value(this: *const Attribute) HTMLString {
+ return this.lol_html_attribute_value_get();
+ }
+
+ pub const Iterator = opaque {
+ extern fn lol_html_attributes_iterator_free(iterator: *Attribute.Iterator) void;
+ extern fn lol_html_attributes_iterator_next(iterator: *Attribute.Iterator) ?*const Attribute;
+
+ pub fn next(this: *Iterator) ?*const Attribute {
+ return lol_html_attributes_iterator_next(this);
+ }
+
+ pub fn deinit(this: *Iterator) void {
+ lol_html_attributes_iterator_free(this);
+ }
+ };
+};
+
+pub const Comment = opaque {
+ extern fn lol_html_comment_text_get(comment: *const Comment) HTMLString;
+ extern fn lol_html_comment_text_set(comment: *Comment, text: [*]const u8, text_len: usize) c_int;
+ extern fn lol_html_comment_before(comment: *Comment, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_comment_after(comment: *Comment, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_comment_replace(comment: *Comment, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+ extern fn lol_html_comment_remove(comment: *Comment) void;
+ extern fn lol_html_comment_is_removed(comment: *const Comment) bool;
+ extern fn lol_html_comment_user_data_set(comment: *const Comment, user_data: ?*anyopaque) void;
+ extern fn lol_html_comment_user_data_get(comment: *const Comment) ?*anyopaque;
+
+ pub fn getText(comment: *const Comment) HTMLString {
+ return lol_html_comment_text_get(comment);
+ }
+
+ pub fn setText(comment: *Comment, text: []const u8) Error!void {
+ return switch (lol_html_comment_text_set(comment, text.ptr, text.len)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ pub fn before(comment: *Comment, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_comment_before(comment, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ pub fn replace(comment: *Comment, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_comment_before(comment, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ pub fn after(comment: *Comment, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_comment_after(comment, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+
+ pub const isRemoved = lol_html_comment_is_removed;
+ pub const remove = lol_html_comment_remove;
+};
+
+pub const Directive = enum(c_uint) {
+ @"stop" = 0,
+ @"continue" = 1,
+};
+pub const lol_html_comment_handler_t = fn (*Comment, ?*anyopaque) callconv(.C) Directive;
+pub const lol_html_text_handler_handler_t = fn (*TextChunk, ?*anyopaque) callconv(.C) Directive;
+pub const lol_html_element_handler_t = fn (*Element, ?*anyopaque) callconv(.C) Directive;
+pub const lol_html_doc_end_handler_t = fn (*DocEnd, ?*anyopaque) callconv(.C) Directive;
+pub const lol_html_end_tag_handler_t = fn (*EndTag, ?*anyopaque) callconv(.C) Directive;
+pub const DocEnd = opaque {
+ extern fn lol_html_doc_end_append(doc_end: ?*DocEnd, content: [*]const u8, content_len: usize, is_html: bool) c_int;
+
+ pub fn append(this: *DocEnd, content: []const u8, is_html: bool) Error!void {
+ return switch (lol_html_doc_end_append(this, content.ptr, content.len, is_html)) {
+ 0 => void{},
+ -1 => error.Fail,
+ else => unreachable,
+ };
+ }
+};
+
+fn DirectiveFunctionType(comptime Container: type) type {
+ return fn (*Container, ?*anyopaque) callconv(.C) Directive;
+}
+
+fn DirectiveFunctionTypeForHandler(comptime Container: type, comptime UserDataType: type) type {
+ return fn (*UserDataType, *Container) bool;
+}
+
+fn DocTypeHandlerCallback(comptime UserDataType: type) type {
+ return fn (*DocType, *UserDataType) bool;
+}
+
+pub fn DirectiveHandler(comptime Container: type, comptime UserDataType: type, comptime Callback: (fn (this: *UserDataType, container: *Container) bool)) DirectiveFunctionType(Container) {
+ return struct {
+ pub fn callback(this: *Container, user_data: ?*anyopaque) callconv(.C) Directive {
+ return @intToEnum(
+ Directive,
+ @as(
+ c_uint,
+ @boolToInt(
+ Callback(
+ @ptrCast(
+ *UserDataType,
+ @alignCast(
+ @alignOf(*UserDataType),
+ user_data.?,
+ ),
+ ),
+ this,
+ ),
+ ),
+ ),
+ );
+ }
+ }.callback;
+}
+
+pub const DocType = opaque {
+ extern fn lol_html_doctype_name_get(doctype: *const DocType) HTMLString;
+ extern fn lol_html_doctype_public_id_get(doctype: *const DocType) HTMLString;
+ extern fn lol_html_doctype_system_id_get(doctype: *const DocType) HTMLString;
+ extern fn lol_html_doctype_user_data_set(doctype: *const DocType, user_data: ?*anyopaque) void;
+ extern fn lol_html_doctype_user_data_get(doctype: *const DocType) ?*anyopaque;
+
+ pub const Callback = fn (*DocType, ?*anyopaque) callconv(.C) Directive;
+
+ pub fn getName(this: *const DocType) HTMLString {
+ return this.lol_html_doctype_name_get();
+ }
+ pub fn getPublicId(this: *const DocType) HTMLString {
+ return this.lol_html_doctype_public_id_get();
+ }
+ pub fn getSystemId(this: *const DocType) HTMLString {
+ return this.lol_html_doctype_system_id_get();
+ }
+};
+
+pub const Encoding = enum {
+ UTF8,
+ UTF16,
+
+ const Label = std.enums.EnumMap(Encoding, []const u8);
+ pub const label: Label = brk: {
+ var labels = Label{};
+ labels.put(.UTF8, "UTF-8");
+ labels.put(.UTF16, "UTF-16");
+
+ break :brk labels;
+ };
+};
diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig
index 3a2bc1cc3..3a811b2c1 100644
--- a/src/javascript/jsc/api/html_rewriter.zig
+++ b/src/javascript/jsc/api/html_rewriter.zig
@@ -29,10 +29,37 @@ const Request = WebCore.Request;
const d = Base.d;
const FetchEvent = WebCore.FetchEvent;
const Response = WebCore.Response;
+const LOLHTML = @import("lolhtml");
+const SelectorMap = std.ArrayListUnmanaged(*LOLHTML.HTMLSelector);
+const LOLHTMLContext = struct {
+ selectors: SelectorMap = .{},
+ element_handlers: std.ArrayListUnmanaged(*ElementHandler) = .{},
+ document_handlers: std.ArrayListUnmanaged(*DocumentHandler) = .{},
+
+ pub fn deinit(this: *LOLHTMLContext, allocator: std.mem.Allocator) void {
+ for (this.selectors.items) |selector| {
+ selector.deinit();
+ }
+ this.selectors.deinit(allocator);
+ this.selectors = .{};
+
+ for (this.element_handlers.items) |handler| {
+ handler.deinit();
+ }
+ this.element_handlers.deinit(allocator);
+ this.element_handlers = .{};
+
+ for (this.document_handlers.items) |handler| {
+ handler.deinit();
+ }
+ this.document_handlers.deinit(allocator);
+ this.document_handlers = .{};
+ }
+};
pub const HTMLRewriter = struct {
- listeners: *anyopaque,
- built: bool = false,
+ builder: *LOLHTML.HTMLRewriter.Builder,
+ context: LOLHTMLContext,
pub const Class = NewClass(
HTMLRewriter,
@@ -59,42 +86,480 @@ pub const HTMLRewriter = struct {
_: js.ExceptionRef,
) js.JSObjectRef {
var rewriter = bun.default_allocator.create(HTMLRewriter) catch unreachable;
- rewriter.* = HTMLRewriter{ .listeners = undefined };
+ rewriter.* = HTMLRewriter{
+ .builder = LOLHTML.HTMLRewriter.Builder.init(),
+ .context = .{},
+ };
return HTMLRewriter.Class.make(ctx, rewriter);
}
pub fn on(
this: *HTMLRewriter,
global: *JSGlobalObject,
- event: ZigString,
+ selector_name: ZigString,
+ thisObject: JSC.C.JSObjectRef,
listener: JSValue,
+ exception: JSC.C.ExceptionRef,
) JSValue {
- _ = this;
- _ = global;
- _ = event;
- _ = listener;
- return undefined;
+ var selector_slice = std.fmt.allocPrint(bun.default_allocator, "{}", .{selector_name}) catch unreachable;
+
+ var selector = LOLHTML.HTMLSelector.parse(selector_slice) catch
+ return throwLOLHTMLError(global);
+ var handler_ = ElementHandler.init(global, listener, exception);
+ if (exception.* != null) {
+ selector.deinit();
+ return JSValue.fromRef(exception.*);
+ }
+ var handler = getAllocator(global.ref()).create(ElementHandler) catch unreachable;
+ handler.* = handler_;
+
+ this.builder.addElementContentHandlers(
+ selector,
+
+ ElementHandler,
+ ElementHandler.onElement,
+ if (handler.onElementCallback != null)
+ handler
+ else
+ null,
+
+ ElementHandler,
+ ElementHandler.onComment,
+ if (handler.onCommentCallback != null)
+ handler
+ else
+ null,
+
+ ElementHandler,
+ ElementHandler.onText,
+ if (handler.onTextCallback != null)
+ handler
+ else
+ null,
+ ) catch {
+ selector.deinit();
+ return throwLOLHTMLError(global);
+ };
+
+ this.context.selectors.append(bun.default_allocator, selector) catch unreachable;
+ this.context.element_handlers.append(bun.default_allocator, handler) catch unreachable;
+ return JSValue.fromRef(thisObject);
}
pub fn onDocument(
this: *HTMLRewriter,
global: *JSGlobalObject,
- event: ZigString,
listener: JSValue,
+ thisObject: JSC.C.JSObjectRef,
+ exception: JSC.C.ExceptionRef,
) JSValue {
- _ = this;
- _ = global;
- _ = event;
- _ = listener;
- return undefined;
+ var handler_ = DocumentHandler.init(global, listener, exception);
+ if (exception.* != null) {
+ return JSValue.fromRef(exception.*);
+ }
+
+ var handler = getAllocator(global.ref()).create(DocumentHandler) catch unreachable;
+ handler.* = handler_;
+
+ this.builder.addDocumentContentHandlers(
+ DocumentHandler,
+ DocumentHandler.onDocType,
+ if (handler.onDocTypeCallback != null)
+ handler
+ else
+ null,
+
+ DocumentHandler,
+ DocumentHandler.onComment,
+ if (handler.onCommentCallback != null)
+ handler
+ else
+ null,
+
+ DocumentHandler,
+ DocumentHandler.onText,
+ if (handler.onTextCallback != null)
+ handler
+ else
+ null,
+
+ DocumentHandler,
+ DocumentHandler.onEnd,
+ if (handler.onEndCallback != null)
+ handler
+ else
+ null,
+ ) catch {
+ return throwLOLHTMLError(global);
+ };
+
+ this.context.document_handlers.append(bun.default_allocator, handler) catch unreachable;
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn finalize(this: *HTMLRewriter) JSValue {
+ this.finalizeWithoutDestroy();
+ bun.default_allocator.destroy(this);
+ }
+
+ pub fn finalizeWithoutDestroy(this: *HTMLRewriter) void {
+ this.context.deinit(bun.default_allocator);
}
pub fn transform(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue {
- _ = this;
- _ = global;
- _ = response;
- return undefined;
+ var input = response.body.slice();
+ var result = bun.default_allocator.create(Response) catch unreachable;
+
+ if (input.len == 0) {
+ response.cloneInto(result, getAllocator(global.ref()));
+ this.finalizeWithoutDestroy();
+ return JSValue.fromRef(Response.Class.make(global.ref(), result));
+ }
+
+ var sink = bun.default_allocator.create(BufferOutputSink) catch unreachable;
+ sink.* = BufferOutputSink{
+ .bytes = bun.MutableString.initEmpty(bun.default_allocator),
+ .global = global,
+ .context = this.context,
+ .rewriter = undefined,
+ };
+ this.context = .{};
+
+ sink.rewriter = this.builder.build(
+ .UTF8,
+ .{
+ .preallocated_parsing_buffer_size = input.len,
+ .max_allowed_memory_usage = std.math.maxInt(u32),
+ },
+ false,
+ BufferOutputSink,
+ sink,
+ BufferOutputSink.write,
+ BufferOutputSink.done,
+ ) catch {
+ this.finalizeWithoutDestroy();
+ sink.deinit();
+ bun.default_allocator.destroy(sink);
+ bun.default_allocator.destroy(result);
+
+ return throwLOLHTMLError(global);
+ };
+
+ sink.rewriter.write(input) catch {
+ sink.deinit();
+ bun.default_allocator.destroy(sink);
+
+ return throwLOLHTMLError(global);
+ };
+
+ sink.rewriter.end() catch {
+ sink.deinit();
+ bun.default_allocator.destroy(sink);
+
+ return throwLOLHTMLError(global);
+ };
+ sink.rewriter.deinit();
+
+ result.body = JSC.WebCore.Body.@"200"(global.ref());
+ result.body.init = response.body.init.clone(bun.default_allocator);
+ result.body.value = .{
+ .String = sink.bytes.toOwnedSlice(),
+ };
+
+ if (result.body.init.headers) |*headers| {
+ headers.putHeaderNumber("content-length", @truncate(u32, result.body.value.String.len), false);
+ }
+
+ response.body.deinit(response.allocator);
+ response.body = JSC.WebCore.Body.@"200"(global.ref());
+
+ return JSValue.fromRef(Response.Class.make(global.ref(), result));
+ }
+
+ pub const BufferOutputSink = struct {
+ global: *JSGlobalObject,
+ bytes: bun.MutableString,
+ rewriter: *LOLHTML.HTMLRewriter,
+ context: LOLHTMLContext,
+
+ pub fn done(this: *BufferOutputSink) void {
+ _ = this;
+ }
+
+ pub fn write(this: *BufferOutputSink, bytes: []const u8) void {
+ this.bytes.append(bytes) catch unreachable;
+ }
+
+ pub fn deinit(this: *BufferOutputSink) void {
+ this.bytes.deinit();
+
+ this.context.deinit(bun.default_allocator);
+ }
+ };
+
+ pub const Processor = struct {
+ selectors: std.ArrayList(*LOLHTML.HTMLSelector),
+ };
+};
+
+const DocumentHandler = struct {
+ onDocTypeCallback: ?JSValue = null,
+ onCommentCallback: ?JSValue = null,
+ onTextCallback: ?JSValue = null,
+ onEndCallback: ?JSValue = null,
+ thisObject: JSValue,
+ global: *JSGlobalObject,
+
+ pub const onDocType = HandlerCallback(
+ DocumentHandler,
+ DocType,
+ LOLHTML.DocType,
+ "doctype",
+ "onDocTypeCallback",
+ );
+ pub const onComment = HandlerCallback(
+ DocumentHandler,
+ Comment,
+ LOLHTML.Comment,
+ "comment",
+ "onCommentCallback",
+ );
+ pub const onText = HandlerCallback(
+ DocumentHandler,
+ TextChunk,
+ LOLHTML.TextChunk,
+ "text_chunk",
+ "onTextCallback",
+ );
+ pub const onEnd = HandlerCallback(
+ DocumentHandler,
+ DocEnd,
+ LOLHTML.DocEnd,
+ "doc_end",
+ "onEndCallback",
+ );
+
+ pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) DocumentHandler {
+ var handler = DocumentHandler{
+ .thisObject = thisObject,
+ .global = global,
+ };
+
+ switch (thisObject.jsType()) {
+ .Object, .ProxyObject, .Cell, .FinalObject => {},
+ else => |kind| {
+ JSC.throwInvalidArguments(
+ "Expected object but received {s}",
+ .{std.mem.span(@tagName(kind))},
+ global.ref(),
+ exception,
+ );
+ return undefined;
+ },
+ }
+
+ if (thisObject.get(global, "doctype")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("doctype must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onDocTypeCallback = val;
+ }
+
+ if (thisObject.get(global, "comments")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("comments must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onCommentCallback = val;
+ }
+
+ if (thisObject.get(global, "text")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("text must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onTextCallback = val;
+ }
+
+ if (thisObject.get(global, "end")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("end must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onEndCallback = val;
+ }
+
+ JSC.C.JSValueProtect(global.ref(), thisObject.asObjectRef());
+ return handler;
+ }
+
+ pub fn deinit(this: *DocumentHandler) void {
+ if (this.onDocTypeCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onDocTypeCallback = null;
+ }
+
+ if (this.onCommentCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onCommentCallback = null;
+ }
+
+ if (this.onTextCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onTextCallback = null;
+ }
+
+ if (this.onEndCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onEndCallback = null;
+ }
+
+ JSC.C.JSValueUnprotect(this.global.ref(), this.thisObject.asObjectRef());
+ }
+};
+
+fn HandlerCallback(
+ comptime HandlerType: type,
+ comptime ZigType: type,
+ comptime LOLHTMLType: type,
+ comptime field_name: string,
+ comptime callback_name: string,
+) (fn (*HandlerType, *LOLHTMLType) bool) {
+ return struct {
+ pub fn callback(this: *HandlerType, value: *LOLHTMLType) bool {
+ var zig_element = bun.default_allocator.create(ZigType) catch unreachable;
+ @field(zig_element, field_name) = value;
+ // At the end of this scope, the value is no longer valid
+ defer {
+ @field(zig_element, field_name) = null;
+ }
+ var args = [1]JSC.C.JSObjectRef{
+ ZigType.Class.make(this.global.ref(), zig_element),
+ };
+ var result = JSC.C.JSObjectCallAsFunctionReturnValue(
+ this.global.ref(),
+ @field(this, callback_name).?.asObjectRef(),
+ if (comptime @hasField(HandlerType, "thisObject"))
+ @field(this, "thisObject").asObjectRef()
+ else
+ null,
+ 1,
+ &args,
+ );
+
+ if (result.isError() or result.isAggregateError(this.global)) {
+ return true;
+ }
+
+ return false;
+ }
+ }.callback;
+}
+
+const ElementHandler = struct {
+ onElementCallback: ?JSValue = null,
+ onCommentCallback: ?JSValue = null,
+ onTextCallback: ?JSValue = null,
+ thisObject: JSValue,
+ global: *JSGlobalObject,
+
+ pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) ElementHandler {
+ var handler = ElementHandler{
+ .thisObject = thisObject,
+ .global = global,
+ };
+
+ switch (thisObject.jsType()) {
+ .Object, .ProxyObject, .Cell, .FinalObject => {},
+ else => |kind| {
+ JSC.throwInvalidArguments(
+ "Expected object but received {s}",
+ .{std.mem.span(@tagName(kind))},
+ global.ref(),
+ exception,
+ );
+ return undefined;
+ },
+ }
+
+ if (thisObject.get(global, "element")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("element must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onElementCallback = val;
+ }
+
+ if (thisObject.get(global, "comment")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("comment must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onCommentCallback = val;
+ }
+
+ if (thisObject.get(global, "text")) |val| {
+ if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
+ JSC.throwInvalidArguments("text must be a function", .{}, global.ref(), exception);
+ return undefined;
+ }
+ JSC.C.JSValueProtect(global.ref(), val.asObjectRef());
+ handler.onTextCallback = val;
+ }
+
+ JSC.C.JSValueProtect(global.ref(), thisObject.asObjectRef());
+ return handler;
+ }
+
+ pub fn deinit(this: *ElementHandler) void {
+ if (this.onElementCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onElementCallback = null;
+ }
+
+ if (this.onCommentCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onCommentCallback = null;
+ }
+
+ if (this.onTextCallback) |cb| {
+ JSC.C.JSValueUnprotect(this.global.ref(), cb.asObjectRef());
+ this.onTextCallback = null;
+ }
+
+ JSC.C.JSValueUnprotect(this.global.ref(), this.thisObject.asObjectRef());
}
+
+ pub const onElement = HandlerCallback(
+ ElementHandler,
+ Element,
+ LOLHTML.Element,
+ "element",
+ "onElementCallback",
+ );
+
+ pub const onComment = HandlerCallback(
+ ElementHandler,
+ Comment,
+ LOLHTML.Comment,
+ "comment",
+ "onCommentCallback",
+ );
+
+ pub const onText = HandlerCallback(
+ ElementHandler,
+ TextChunk,
+ LOLHTML.TextChunk,
+ "text_chunk",
+ "onTextCallback",
+ );
};
const ContentOptions = struct {
@@ -122,6 +587,22 @@ fn GetterType(comptime Container: type) type {
) js.JSObjectRef;
}
+fn SetterType(comptime Container: type) type {
+ return fn (
+ this: *Container,
+ ctx: js.JSContextRef,
+ obj: js.JSObjectRef,
+ prop: js.JSStringRef,
+ value: js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) bool;
+}
+
+pub fn free_html_writer_string(_: ?*anyopaque, ptr: ?*anyopaque, len: usize) callconv(.C) void {
+ var str = LOLHTML.HTMLString{ .ptr = bun.cast([*]const u8, ptr.?), .len = len };
+ str.deinit();
+}
+
pub fn wrap(comptime Container: type, comptime name: string) MethodType(Container) {
return struct {
const FunctionType = @TypeOf(@field(Container, name));
@@ -131,7 +612,7 @@ pub fn wrap(comptime Container: type, comptime name: string) MethodType(Containe
this: *Container,
ctx: js.JSContextRef,
_: js.JSObjectRef,
- _: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSObjectRef {
@@ -140,9 +621,9 @@ pub fn wrap(comptime Container: type, comptime name: string) MethodType(Containe
comptime var i: usize = 0;
inline while (i < FunctionTypeInfo.args.len) : (i += 1) {
- const ArgType = FunctionTypeInfo.args[i].arg_type.?;
+ const ArgType = comptime FunctionTypeInfo.args[i].arg_type.?;
- switch (ArgType) {
+ switch (comptime ArgType) {
*Container => {
args[i] = this;
},
@@ -150,17 +631,21 @@ pub fn wrap(comptime Container: type, comptime name: string) MethodType(Containe
args[i] = ctx.ptr();
},
ZigString => {
- var arg: ?JSC.JSValue = iter.next();
- args[i] = (arg orelse {
+ var string_value = iter.nextEat() orelse {
+ JSC.throwInvalidArguments("Missing argument", .{}, ctx, exception);
+ return null;
+ };
+
+ if (!string_value.isString()) {
JSC.throwInvalidArguments("Expected string", .{}, ctx, exception);
return null;
- }).getZigString(ctx.ptr());
+ }
+
+ args[i] = string_value.getZigString(ctx.ptr());
},
?ContentOptions => {
- var arg: ?JSC.JSValue = iter.next();
-
- if (arg) |content_arg| {
- if (content_arg.get("html")) |html_val| {
+ if (iter.nextEat()) |content_arg| {
+ if (content_arg.get(ctx.ptr(), "html")) |html_val| {
args[i] = .{ .html = html_val.toBoolean() };
}
} else {
@@ -168,9 +653,7 @@ pub fn wrap(comptime Container: type, comptime name: string) MethodType(Containe
}
},
*Response => {
- var arg: ?JSC.JSValue = iter.next();
-
- args[i] = (arg orelse {
+ args[i] = (iter.nextEat() orelse {
JSC.throwInvalidArguments("Missing Response object", .{}, ctx, exception);
return null;
}).as(Response) orelse {
@@ -178,15 +661,24 @@ pub fn wrap(comptime Container: type, comptime name: string) MethodType(Containe
return null;
};
},
+ js.JSObjectRef => {
+ args[i] = thisObject;
+ if (!JSValue.fromRef(thisObject).isCell() or !JSValue.fromRef(thisObject).isObject()) {
+ JSC.throwInvalidArguments("Expected object", .{}, ctx, exception);
+ return null;
+ }
+ },
+ js.ExceptionRef => {
+ args[i] = exception;
+ },
JSValue => {
- var arg: ?JSC.JSValue = iter.next();
-
- args[i] = arg orelse {
+ const val = iter.nextEat() orelse {
JSC.throwInvalidArguments("Missing argument", .{}, ctx, exception);
return null;
};
+ args[i] = val;
},
- else => @compileError("Unexpected Type" ++ @typeName(ArgType)),
+ else => @compileError("Unexpected Type " ++ @typeName(ArgType)),
}
}
@@ -224,9 +716,521 @@ pub fn getterWrap(comptime Container: type, comptime name: string) GetterType(Co
}.callback;
}
+pub fn setterWrap(comptime Container: type, comptime name: string) SetterType(Container) {
+ return struct {
+ const FunctionType = @TypeOf(@field(Container, name));
+ const FunctionTypeInfo: std.builtin.TypeInfo.Fn = @typeInfo(FunctionType).Fn;
+
+ pub fn callback(
+ this: *Container,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ value: js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) bool {
+ @call(.{}, @field(Container, name), .{ this, JSC.JSValue.fromRef(value), exception, ctx.ptr() });
+ return exception.* == null;
+ }
+ }.callback;
+}
+
+fn throwLOLHTMLError(global: *JSGlobalObject) JSValue {
+ var err = LOLHTML.HTMLString.lastError();
+ return ZigString.init(err.slice()).toErrorInstance(global);
+}
+
+fn htmlStringValue(input: LOLHTML.HTMLString, globalObject: *JSGlobalObject) JSValue {
+ var str = ZigString.init(
+ input.slice(),
+ );
+ str.detectEncoding();
+
+ return str.toExternalValueWithCallback(
+ globalObject,
+ free_html_writer_string,
+ );
+}
+
+pub const TextChunk = struct {
+ text_chunk: ?*LOLHTML.TextChunk = null,
+
+ pub const Class = NewClass(
+ TextChunk,
+ .{ .name = "TextChunk" },
+ .{
+ .before = .{
+ .rfn = wrap(TextChunk, "before"),
+ },
+ .after = .{
+ .rfn = wrap(TextChunk, "after"),
+ },
+
+ .replace = .{
+ .rfn = wrap(TextChunk, "replace"),
+ },
+
+ .remove = .{
+ .rfn = wrap(TextChunk, "remove"),
+ },
+ },
+ .{
+ .removed = .{
+ .get = getterWrap(TextChunk, "removed"),
+ },
+ .text = .{
+ .get = getterWrap(TextChunk, "getText"),
+ },
+ },
+ );
+
+ fn contentHandler(this: *TextChunk, comptime Callback: (fn (*LOLHTML.TextChunk, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.text_chunk == null)
+ return JSC.JSValue.jsUndefined();
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.text_chunk.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn before(
+ this: *TextChunk,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.TextChunk.before, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn after(
+ this: *TextChunk,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.TextChunk.after, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn replace(
+ this: *TextChunk,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.TextChunk.replace, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn remove(this: *TextChunk, thisObject: js.JSObjectRef) JSValue {
+ if (this.text_chunk == null)
+ return JSC.JSValue.jsUndefined();
+ this.text_chunk.?.remove();
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn getText(this: *TextChunk, global: *JSGlobalObject) JSValue {
+ if (this.text_chunk == null)
+ return JSC.JSValue.jsUndefined();
+ return ZigString.init(this.text_chunk.?.getContent().slice()).withEncoding().toValue(global);
+ }
+
+ pub fn removed(this: *TextChunk, _: *JSGlobalObject) JSValue {
+ return JSC.JSValue.jsBoolean(this.text_chunk.?.isRemoved());
+ }
+};
+
+pub const DocType = struct {
+ doctype: ?*LOLHTML.DocType = null,
+
+ pub const Class = NewClass(
+ DocType,
+ .{
+ .name = "DocType",
+ },
+ .{},
+ .{
+ .name = .{
+ .get = getterWrap(DocType, "name"),
+ },
+ .systemId = .{
+ .get = getterWrap(DocType, "systemId"),
+ },
+
+ .publicId = .{
+ .get = getterWrap(DocType, "publicId"),
+ },
+ },
+ );
+
+ /// The doctype name.
+ pub fn name(this: *DocType, global: *JSGlobalObject) JSValue {
+ if (this.doctype == null)
+ return JSC.JSValue.jsUndefined();
+ const str = this.doctype.?.getName().slice();
+ if (str.len == 0)
+ return JSValue.jsNull();
+ return ZigString.init(str).toValue(global);
+ }
+
+ pub fn systemId(this: *DocType, global: *JSGlobalObject) JSValue {
+ if (this.doctype == null)
+ return JSC.JSValue.jsUndefined();
+
+ const str = this.doctype.?.getSystemId().slice();
+ if (str.len == 0)
+ return JSValue.jsNull();
+ return ZigString.init(str).toValue(global);
+ }
+
+ pub fn publicId(this: *DocType, global: *JSGlobalObject) JSValue {
+ if (this.doctype == null)
+ return JSC.JSValue.jsUndefined();
+
+ const str = this.doctype.?.getPublicId().slice();
+ if (str.len == 0)
+ return JSValue.jsNull();
+ return ZigString.init(str).toValue(global);
+ }
+};
+
+pub const DocEnd = struct {
+ doc_end: ?*LOLHTML.DocEnd,
+
+ pub const Class = NewClass(
+ DocEnd,
+ .{ .name = "DocEnd" },
+ .{
+ .append = .{
+ .rfn = wrap(DocEnd, "append"),
+ },
+ },
+ .{},
+ );
+
+ fn contentHandler(this: *DocEnd, comptime Callback: (fn (*LOLHTML.DocEnd, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.doc_end == null)
+ return JSValue.jsNull();
+
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.doc_end.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn append(
+ this: *DocEnd,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.DocEnd.append, thisObject, globalObject, content, contentOptions);
+ }
+};
+
+pub const Comment = struct {
+ comment: ?*LOLHTML.Comment = null,
+
+ pub const Class = NewClass(
+ Comment,
+ .{ .name = "Comment" },
+ .{
+ .before = .{
+ .rfn = wrap(Comment, "before"),
+ },
+ .after = .{
+ .rfn = wrap(Comment, "after"),
+ },
+
+ .replace = .{
+ .rfn = wrap(Comment, "replace"),
+ },
+
+ .remove = .{
+ .rfn = wrap(Comment, "remove"),
+ },
+ },
+ .{
+ .removed = .{
+ .get = getterWrap(Comment, "removed"),
+ },
+ .text = .{
+ .get = getterWrap(Comment, "getText"),
+ .set = setterWrap(Comment, "setText"),
+ },
+ },
+ );
+
+ fn contentHandler(this: *Comment, comptime Callback: (fn (*LOLHTML.Comment, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.comment == null)
+ return JSValue.jsNull();
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.comment.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn before(
+ this: *Comment,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.Comment.before, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn after(
+ this: *Comment,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.Comment.after, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn replace(
+ this: *Comment,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.Comment.replace, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn remove(this: *Comment, thisObject: js.JSObjectRef) JSValue {
+ if (this.comment == null)
+ return JSValue.jsNull();
+ this.comment.?.remove();
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn getText(this: *Comment, global: *JSGlobalObject) JSValue {
+ if (this.comment == null)
+ return JSValue.jsNull();
+ return ZigString.init(this.comment.?.getText().slice()).withEncoding().toValue(global);
+ }
+
+ pub fn setText(
+ this: *Comment,
+ value: JSValue,
+ exception: JSC.C.ExceptionRef,
+ global: *JSGlobalObject,
+ ) void {
+ if (this.comment == null)
+ return;
+ var text = value.toSlice(global, bun.default_allocator);
+ defer text.deinit();
+ this.comment.?.setText(text.slice()) catch {
+ exception.* = throwLOLHTMLError(global).asObjectRef();
+ };
+ }
+
+ pub fn removed(this: *Comment, _: *JSGlobalObject) JSValue {
+ if (this.comment == null)
+ return JSC.JSValue.jsUndefined();
+ return JSC.JSValue.jsBoolean(this.comment.?.isRemoved());
+ }
+};
+
+pub const EndTag = struct {
+ end_tag: ?*LOLHTML.EndTag,
+
+ pub const Handler = struct {
+ callback: ?JSC.JSValue,
+ global: *JSGlobalObject,
+
+ pub const onEndTag = HandlerCallback(
+ Handler,
+ EndTag,
+ LOLHTML.EndTag,
+ "end_tag",
+ "callback",
+ );
+
+ pub const onEndTagHandler = LOLHTML.DirectiveHandler(LOLHTML.EndTag, Handler, onEndTag);
+ };
+
+ pub const Class = NewClass(
+ EndTag,
+ .{ .name = "EndTag" },
+ .{
+ .before = .{
+ .rfn = wrap(EndTag, "before"),
+ },
+ .after = .{
+ .rfn = wrap(EndTag, "after"),
+ },
+
+ .remove = .{
+ .rfn = wrap(EndTag, "remove"),
+ },
+ },
+ .{
+ .name = .{
+ .get = getterWrap(EndTag, "getName"),
+ .set = setterWrap(EndTag, "setName"),
+ },
+ },
+ );
+
+ fn contentHandler(this: *EndTag, comptime Callback: (fn (*LOLHTML.EndTag, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.end_tag == null)
+ return JSValue.jsNull();
+
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.end_tag.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn before(
+ this: *EndTag,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.EndTag.before, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn after(
+ this: *EndTag,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.EndTag.after, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn replace(
+ this: *EndTag,
+ thisObject: js.JSObjectRef,
+ globalObject: *JSGlobalObject,
+ content: ZigString,
+ contentOptions: ?ContentOptions,
+ ) JSValue {
+ return this.contentHandler(LOLHTML.EndTag.replace, thisObject, globalObject, content, contentOptions);
+ }
+
+ pub fn remove(this: *EndTag, thisObject: js.JSObjectRef) JSValue {
+ if (this.end_tag == null)
+ return JSC.JSValue.jsUndefined();
+
+ this.end_tag.?.remove();
+ return JSValue.fromRef(thisObject);
+ }
+
+ pub fn getName(this: *EndTag, global: *JSGlobalObject) JSValue {
+ if (this.end_tag == null)
+ return JSC.JSValue.jsUndefined();
+
+ return ZigString.init(this.end_tag.?.getName().slice()).withEncoding().toValue(global);
+ }
+
+ pub fn setName(
+ this: *EndTag,
+ value: JSValue,
+ exception: JSC.C.ExceptionRef,
+ global: *JSGlobalObject,
+ ) void {
+ if (this.end_tag == null)
+ return;
+ var text = value.toSlice(global, bun.default_allocator);
+ defer text.deinit();
+ this.end_tag.?.setName(text.slice()) catch {
+ exception.* = throwLOLHTMLError(global).asObjectRef();
+ };
+ }
+};
+
+pub const AttributeIterator = struct {
+ iterator: ?*LOLHTML.Attribute.Iterator = null,
+
+ pub const Class = NewClass(
+ AttributeIterator,
+ .{ .name = "AttributeIterator" },
+ .{
+ .next = .{
+ .rfn = wrap(AttributeIterator, "next"),
+ },
+ },
+ .{},
+ );
+
+ const value_ = ZigString.init("value");
+ const done_ = ZigString.init("done");
+ pub fn next(
+ this: *AttributeIterator,
+ globalObject: *JSGlobalObject,
+ ) JSValue {
+ if (this.iterator == null) {
+ return JSC.JSValue.createObject2(
+ globalObject,
+ &value_,
+ &done_,
+ JSC.JSValue.jsUndefined(),
+ JSC.JSValue.jsBoolean(true),
+ );
+ }
+
+ var attribute = this.iterator.?.next() orelse {
+ this.iterator.?.deinit();
+ this.iterator = null;
+ return JSC.JSValue.createObject2(
+ globalObject,
+ &value_,
+ &done_,
+ JSC.JSValue.jsUndefined(),
+ JSC.JSValue.jsBoolean(true),
+ );
+ };
+
+ var strs = [2]ZigString{
+ htmlStringValue(attribute.name().slice(), globalObject),
+ htmlStringValue(attribute.value().slice(), globalObject),
+ };
+
+ return JSC.JSValue.createObject2(
+ globalObject,
+ &value_,
+ &done_,
+ JSC.JSValue.createStringArray(globalObject, &strs, 2, false),
+ JSC.JSValue.jsBoolean(false),
+ );
+ }
+};
pub const Element = struct {
- global: *JSGlobalObject,
- removed: bool = false,
+ element: ?*LOLHTML.Element = null,
pub const Class = NewClass(
Element,
@@ -268,143 +1272,245 @@ pub const Element = struct {
.removeAndKeepContent = .{
.rfn = wrap(Element, "removeAndKeepContent"),
},
+ .onEndTag = .{
+ .rfn = wrap(Element, "onEndTag"),
+ },
},
.{
.tagName = .{
.get = getterWrap(Element, "getTagName"),
+ .set = setterWrap(Element, "setTagName"),
},
.removed = .{
.get = getterWrap(Element, "getRemoved"),
},
- .namespaceUri = .{
+ .namespaceURI = .{
.get = getterWrap(Element, "getNamespaceURI"),
},
},
);
+ pub fn onEndTag(
+ this: *Element,
+ globalObject: *JSGlobalObject,
+ function: JSValue,
+ thisObject: JSC.C.JSObjectRef,
+ ) JSValue {
+ if (this.element == null)
+ return JSValue.jsNull();
+ if (function.isUndefinedOrNull() or !function.isCallable(globalObject.vm())) {
+ return ZigString.init("Expected a function").withEncoding().toValue(globalObject);
+ }
+
+ var end_tag_handler = bun.default_allocator.create(EndTag.Handler) catch unreachable;
+ end_tag_handler.* = .{ .global = globalObject, .callback = function };
+
+ this.element.?.onEndTag(EndTag.Handler.onEndTagHandler, end_tag_handler) catch {
+ bun.default_allocator.destroy(end_tag_handler);
+ return throwLOLHTMLError(globalObject);
+ };
+
+ JSC.C.JSValueProtect(globalObject.ref(), function.asObjectRef());
+ return JSValue.fromRef(thisObject);
+ }
+
// // fn wrap(comptime name: string)
/// Returns the value for a given attribute name: ZigString on the element, or null if it is not found.
pub fn getAttribute(this: *Element, globalObject: *JSGlobalObject, name: ZigString) JSValue {
- _ = this;
- _ = name;
- _ = globalObject;
- return undefined;
+ if (this.element == null)
+ return JSValue.jsNull();
+
+ var slice = name.toSlice(bun.default_allocator);
+ defer slice.deinit();
+ var attr = this.element.?.getAttribute(slice.slice()).slice();
+
+ if (attr.len == 0)
+ return JSC.JSValue.jsNull();
+
+ var str = ZigString.init(
+ attr,
+ );
+
+ return str.toExternalValueWithCallback(
+ globalObject,
+ free_html_writer_string,
+ );
}
- /// Returns a boolean indicating whether an attribute exists on the element.
- pub fn hasAttribute(this: *Element, globalObject: *JSGlobalObject, name: ZigString) JSValue {
- _ = this;
- _ = name;
- _ = globalObject;
- return undefined;
+ /// Returns a boolean indicating whether an attribute exists on the element.
+ pub fn hasAttribute(this: *Element, global: *JSGlobalObject, name: ZigString) JSValue {
+ if (this.element == null)
+ return JSValue.jsBoolean(false);
+
+ var slice = name.toSlice(bun.default_allocator);
+ defer slice.deinit();
+ return JSValue.jsBoolean(this.element.?.hasAttribute(slice.slice()) catch return throwLOLHTMLError(global));
}
- /// Sets an attribute to a provided value, creating the attribute if it does not exist.
- pub fn setAttribute(this: *Element, globalObject: *JSGlobalObject, name: ZigString, value: ZigString) JSValue {
- _ = this;
- _ = name;
- _ = globalObject;
- _ = value;
- return undefined;
+ /// Sets an attribute to a provided value, creating the attribute if it does not exist.
+ pub fn setAttribute(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, name_: ZigString, value_: ZigString) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ var name_slice = name_.toSlice(bun.default_allocator);
+ defer name_slice.deinit();
+
+ var value_slice = value_.toSlice(bun.default_allocator);
+ defer value_slice.deinit();
+ this.element.?.setAttribute(name_slice.slice(), value_slice.slice()) catch return throwLOLHTMLError(globalObject);
+ return JSValue.fromRef(thisObject);
}
/// Removes the attribute.
- pub fn removeAttribute(this: *Element, globalObject: *JSGlobalObject, name: ZigString) JSValue {
- _ = this;
- _ = name;
- _ = globalObject;
- return undefined;
+ pub fn removeAttribute(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, name: ZigString) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ var name_slice = name.toSlice(bun.default_allocator);
+ defer name_slice.deinit();
+
+ this.element.?.removeAttribute(
+ name_slice.slice(),
+ ) catch return throwLOLHTMLError(globalObject);
+ return JSValue.fromRef(thisObject);
+ }
+
+ fn contentHandler(this: *Element, comptime Callback: (fn (*LOLHTML.Element, []const u8, bool) LOLHTML.Error!void), thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+
+ var content_slice = content.toSlice(bun.default_allocator);
+ defer content_slice.deinit();
+
+ Callback(
+ this.element.?,
+ content_slice.slice(),
+ contentOptions != null and contentOptions.?.html,
+ ) catch return throwLOLHTMLError(globalObject);
+
+ return JSValue.fromRef(thisObject);
}
/// Inserts content before the element.
- pub fn before(this: *Element, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
- _ = this;
- _ = content;
- _ = globalObject;
- _ = contentOptions;
- return undefined;
+ pub fn before(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.before,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
}
/// Inserts content right after the element.
- pub fn after(this: *Element, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
- _ = this;
- _ = content;
- _ = globalObject;
- _ = contentOptions;
- return undefined;
+ pub fn after(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.after,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
}
- /// nserts content right after the start tag of the element.
- pub fn prepend(this: *Element, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
- _ = this;
- _ = content;
- _ = globalObject;
- _ = contentOptions;
- return undefined;
+ /// Inserts content right after the start tag of the element.
+ pub fn prepend(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.prepend,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
}
/// Inserts content right before the end tag of the element.
- pub fn append(this: *Element, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
- _ = this;
- _ = content;
- _ = globalObject;
- _ = contentOptions;
- return undefined;
+ pub fn append(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.append,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
}
- /// Removes the element and inserts content in place of it.
- pub fn replace(this: *Element, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
- _ = this;
- _ = content;
- _ = globalObject;
- _ = contentOptions;
- return undefined;
+ /// Removes the element and inserts content in place of it.
+ pub fn replace(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.replace,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
}
/// Replaces content of the element.
- pub fn setInnerContent(this: *Element, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
- _ = this;
- _ = content;
- _ = globalObject;
- _ = contentOptions;
- return undefined;
+ pub fn setInnerContent(this: *Element, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue {
+ return contentHandler(
+ this,
+ LOLHTML.Element.setInnerContent,
+ thisObject,
+ globalObject,
+ content,
+ contentOptions,
+ );
}
/// Removes the element with all its content.
- pub fn remove(this: *Element, globalObject: *JSGlobalObject) JSValue {
- _ = this;
+ pub fn remove(this: *Element, thisObject: js.JSObjectRef) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
- _ = globalObject;
- return undefined;
+ this.element.?.remove();
+ return JSValue.fromRef(thisObject);
}
/// Removes the start tag and end tag of the element but keeps its inner content intact.
- pub fn removeAndKeepContent(this: *Element, globalObject: *JSGlobalObject) JSValue {
- _ = this;
+ pub fn removeAndKeepContent(this: *Element, thisObject: js.JSObjectRef) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
- _ = globalObject;
- return undefined;
+ this.element.?.removeAndKeepContent();
+ return JSValue.fromRef(thisObject);
}
pub fn getTagName(this: *Element, globalObject: *JSGlobalObject) JSValue {
- _ = this;
+ if (this.element == null)
+ return JSValue.jsUndefined();
- _ = globalObject;
- return undefined;
+ return htmlStringValue(this.element.?.tagName(), globalObject);
}
- pub fn getRemoved(this: *Element, globalObject: *JSGlobalObject) JSValue {
- _ = this;
+ pub fn setTagName(this: *Element, value: JSValue, exception: JSC.C.ExceptionRef, global: *JSGlobalObject) void {
+ if (this.element == null)
+ return;
+
+ var text = value.toSlice(global, bun.default_allocator);
+ defer text.deinit();
+
+ this.element.?.setTagName(text.slice()) catch {
+ exception.* = throwLOLHTMLError(global).asObjectRef();
+ };
+ }
- _ = globalObject;
- return undefined;
+ pub fn getRemoved(this: *Element, _: *JSGlobalObject) JSValue {
+ if (this.element == null)
+ return JSValue.jsUndefined();
+ return JSC.JSValue.jsBoolean(this.element.?.isRemoved());
}
pub fn getNamespaceURI(this: *Element, globalObject: *JSGlobalObject) JSValue {
- _ = this;
+ if (this.element == null)
+ return JSValue.jsUndefined();
- _ = globalObject;
- return undefined;
+ return ZigString.init(std.mem.span(this.element.?.namespaceURI())).toValue(globalObject);
}
};
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index a39058e45..cfdc6c110 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -1859,6 +1859,12 @@ const TextDecoder = WebCore.TextDecoder;
const TimeoutTask = JSC.BunTimer.Timeout.TimeoutTask;
const HTMLRewriter = JSC.Cloudflare.HTMLRewriter;
const Element = JSC.Cloudflare.Element;
+const Comment = JSC.Cloudflare.Comment;
+const TextChunk = JSC.Cloudflare.TextChunk;
+const DocType = JSC.Cloudflare.DocType;
+const EndTag = JSC.Cloudflare.EndTag;
+const DocEnd = JSC.Cloudflare.DocEnd;
+const AttributeIterator = JSC.Cloudflare.AttributeIterator;
pub const JSPrivateDataPtr = TaggedPointerUnion(.{
ResolveError,
@@ -1886,6 +1892,12 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
TimeoutTask,
HTMLRewriter,
Element,
+ Comment,
+ TextChunk,
+ DocType,
+ EndTag,
+ DocEnd,
+ AttributeIterator,
});
pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type {
diff --git a/src/javascript/jsc/bindings/BunStream.cpp b/src/javascript/jsc/bindings/BunStream.cpp
index 436c4d2dd..2214454bf 100644
--- a/src/javascript/jsc/bindings/BunStream.cpp
+++ b/src/javascript/jsc/bindings/BunStream.cpp
@@ -74,7 +74,7 @@ static WritableEvent getWritableEvent(const WTF::String& eventName)
} \
} \
JSC::JSValue result = JSC::JSValue::decode( \
- ZigFunction(thisObject->state, globalObject, arguments.data(), argCount) \
+ ZigFunction(thisObject->state, globalObject, reinterpret_cast<JSC__JSValue*>(arguments.data()), argCount) \
); \
JSC::JSObject *obj = result.getObject(); \
if (UNLIKELY(obj != nullptr && obj->isErrorInstance())) { \
diff --git a/src/javascript/jsc/bindings/Path.cpp b/src/javascript/jsc/bindings/Path.cpp
index 09ea7ecf6..460cbebd3 100644
--- a/src/javascript/jsc/bindings/Path.cpp
+++ b/src/javascript/jsc/bindings/Path.cpp
@@ -39,7 +39,7 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers;
auto clientData = Bun::clientData(vm); \
auto isWindows = thisObject->get(globalObject, clientData->builtinNames().isWindowsPrivateName()); \
JSC::JSValue result = JSC::JSValue::decode( \
- ZigFunction(globalObject, isWindows.asBoolean(), arguments.data(), argCount) \
+ ZigFunction(globalObject, isWindows.asBoolean(), reinterpret_cast<JSC__JSValue*>(arguments.data()), argCount) \
); \
JSC::JSObject *obj = result.getObject(); \
if (UNLIKELY(obj != nullptr && obj->isErrorInstance())) { \
diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp
index cc174cbda..40baf6c01 100644
--- a/src/javascript/jsc/bindings/bindings.cpp
+++ b/src/javascript/jsc/bindings/bindings.cpp
@@ -784,6 +784,22 @@ void JSC__JSValue__toZigString(JSC__JSValue JSValue0, ZigString* arg1, JSC__JSGl
arg1->len = str.length();
}
+JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__JSGlobalObject* arg1, void (*ArgFn2)(void* arg2, void* arg0, size_t arg1))
+{
+
+ ZigString str
+ = *arg0;
+ if (Zig::isTaggedUTF16Ptr(str.ptr)) {
+ return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
+ arg1->vm(),
+ WTF::String(ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, ArgFn2)))));
+ } else {
+ return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
+ arg1->vm(),
+ WTF::String(ExternalStringImpl::create(reinterpret_cast<const LChar*>(Zig::untag(str.ptr)), str.len, ArgFn2)))));
+ }
+}
+
JSC__JSValue ZigString__toErrorInstance(const ZigString* str, JSC__JSGlobalObject* globalObject)
{
return JSC::JSValue::encode(Zig::getErrorInstance(str, globalObject));
diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig
index f93589183..6215d9e4a 100644
--- a/src/javascript/jsc/bindings/bindings.zig
+++ b/src/javascript/jsc/bindings/bindings.zig
@@ -280,6 +280,14 @@ pub const ZigString = extern struct {
return shim.cppFn("toExternalValue", .{ this, global });
}
+ pub fn toExternalValueWithCallback(
+ this: *const ZigString,
+ global: *JSGlobalObject,
+ callback: fn (ctx: ?*anyopaque, ptr: ?*anyopaque, len: usize) callconv(.C) void,
+ ) JSValue {
+ return shim.cppFn("toExternalValueWithCallback", .{ this, global, callback });
+ }
+
pub fn to16BitValue(this: *const ZigString, global: *JSGlobalObject) JSValue {
return shim.cppFn("to16BitValue", .{ this, global });
}
@@ -316,6 +324,7 @@ pub const ZigString = extern struct {
"toValueGC",
"toErrorInstance",
"toExternalU16",
+ "toExternalValueWithCallback",
};
};
@@ -1573,12 +1582,12 @@ pub const String = extern struct {
};
};
-pub const JSValue = enum(i64) {
+pub const JSValue = enum(u64) {
_,
pub const shim = Shimmer("JSC", "JSValue", @This());
pub const is_pointer = false;
- pub const Type = i64;
+ pub const Type = u64;
const cppFn = shim.cppFn;
pub const include = "<JavaScriptCore/JSValue.h>";
@@ -1836,22 +1845,18 @@ pub const JSValue = enum(i64) {
return cppFn("getErrorsProperty", .{ this, globalObject });
}
- pub fn jsNumberWithType(comptime Number: type, number: Type) JSValue {
- return switch (Number) {
- f64 => @call(.{ .modifier = .always_inline }, jsNumberFromDouble, .{number}),
- u8 => @call(.{ .modifier = .always_inline }, jsNumberFromChar, .{number}),
- u16 => @call(.{ .modifier = .always_inline }, jsNumberFromInt32, .{@intCast(i32, number)}),
- i32 => @call(.{ .modifier = .always_inline }, jsNumberFromInt32, .{@truncate(i32, number)}),
- c_int, i64 => if (number > std.math.maxInt(i32))
- jsNumberFromInt64(@truncate(i64, number))
- else
- jsNumberFromInt32(@intCast(i32, number)),
-
- c_uint, u32 => if (number < std.math.maxInt(i32))
- jsNumberFromInt32(@intCast(i32, number))
- else
- jsNumberFromUint64(@intCast(u64, number)),
-
+ pub fn jsNumberWithType(comptime Number: type, number: Number) JSValue {
+ return switch (comptime Number) {
+ JSValue => number,
+ f64 => jsNumberFromDouble(number),
+ u8 => jsNumberFromChar(number),
+ u16 => jsNumberFromInt32(@intCast(i32, number)),
+ i32 => jsNumberFromInt32(@intCast(i32, number)),
+ c_int => jsNumberFromInt32(@intCast(i32, number)),
+ i64 => jsNumberFromInt64(@intCast(i64, number)),
+ c_uint => jsNumberFromUint64(@intCast(u64, number)),
+ u64 => jsNumberFromUint64(@intCast(u64, number)),
+ u32 => jsNumberFromInt32(@intCast(i32, number)),
else => @compileError("Type transformation missing for number of type: " ++ @typeName(Number)),
};
}
@@ -2226,11 +2231,11 @@ pub const JSValue = enum(i64) {
}
pub inline fn asRef(this: JSValue) C_API.JSValueRef {
- return @intToPtr(C_API.JSValueRef, @bitCast(usize, @enumToInt(this)));
+ return @intToPtr(C_API.JSValueRef, @intCast(usize, @enumToInt(this)));
}
pub inline fn fromRef(this: C_API.JSValueRef) JSValue {
- return @intToEnum(JSValue, @bitCast(i64, @ptrToInt(this)));
+ return @intToEnum(JSValue, @ptrToInt(this));
}
pub inline fn asObjectRef(this: JSValue) C_API.JSObjectRef {
@@ -2238,7 +2243,7 @@ pub const JSValue = enum(i64) {
}
pub inline fn asVoid(this: JSValue) *anyopaque {
- return @intToPtr(*anyopaque, @bitCast(u64, @enumToInt(this)));
+ return @intToPtr(*anyopaque, @enumToInt(this));
}
pub const Extern = [_][]const u8{ "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "getReadableStreamState", "getWritableStreamState", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" };
diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig
index 0a0a045a3..6dfcb82d4 100644
--- a/src/javascript/jsc/bindings/exports.zig
+++ b/src/javascript/jsc/bindings/exports.zig
@@ -1104,7 +1104,7 @@ pub const ZigConsoleClient = struct {
// For detecting circular references
pub const Visited = struct {
const ObjectPool = @import("../../../pool.zig").ObjectPool;
- pub const Map = std.AutoHashMap(i64, void);
+ pub const Map = std.AutoHashMap(JSValue.Type, void);
pub const Pool = ObjectPool(
Map,
struct {
diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h
index 2935db6a5..e3c04d000 100644
--- a/src/javascript/jsc/bindings/headers-cpp.h
+++ b/src/javascript/jsc/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1646833921
+//-- AUTOGENERATED FILE -- 1647061500
// clang-format off
#pragma once
diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h
index ffac8456e..e87cb4377 100644
--- a/src/javascript/jsc/bindings/headers.h
+++ b/src/javascript/jsc/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format: off
-//-- AUTOGENERATED FILE -- 1646833921
+//-- AUTOGENERATED FILE -- 1647061500
#pragma once
#include <stddef.h>
@@ -118,10 +118,10 @@ typedef void* JSClassRef;
typedef struct JSC__AsyncFunctionPrototype JSC__AsyncFunctionPrototype; // JSC::AsyncFunctionPrototype
typedef ZigException ZigException;
typedef bJSC__SourceCode JSC__SourceCode; // JSC::SourceCode
+ typedef uint64_t JSC__JSValue;
typedef struct JSC__BigIntPrototype JSC__BigIntPrototype; // JSC::BigIntPrototype
typedef struct JSC__GeneratorFunctionPrototype JSC__GeneratorFunctionPrototype; // JSC::GeneratorFunctionPrototype
typedef ZigString ZigString;
- typedef int64_t JSC__JSValue;
typedef struct JSC__FunctionPrototype JSC__FunctionPrototype; // JSC::FunctionPrototype
typedef bInspector__ScriptArguments Inspector__ScriptArguments; // Inspector::ScriptArguments
typedef bJSC__Exception JSC__Exception; // JSC::Exception
@@ -191,8 +191,8 @@ typedef void* JSClassRef;
typedef JSClassRef JSClassRef;
typedef Bun__ArrayBuffer Bun__ArrayBuffer;
typedef ZigException ZigException;
+ typedef uint64_t JSC__JSValue;
typedef ZigString ZigString;
- typedef int64_t JSC__JSValue;
using JSC__JSCell = JSC::JSCell;
using JSC__Exception = JSC::Exception;
using JSC__JSPromisePrototype = JSC::JSPromisePrototype;
@@ -250,6 +250,7 @@ CPP_DECL JSC__JSValue ZigString__to16BitValue(const ZigString* arg0, JSC__JSGlob
CPP_DECL JSC__JSValue ZigString__toErrorInstance(const ZigString* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL JSC__JSValue ZigString__toExternalU16(const uint16_t* arg0, size_t arg1, JSC__JSGlobalObject* arg2);
CPP_DECL JSC__JSValue ZigString__toExternalValue(const ZigString* arg0, JSC__JSGlobalObject* arg1);
+CPP_DECL JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__JSGlobalObject* arg1, void (* ArgFn2)(void* arg0, void* arg1, size_t arg2));
CPP_DECL JSC__JSValue ZigString__toValue(const ZigString* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL JSC__JSValue ZigString__toValueGC(const ZigString* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0, JSC__JSGlobalObject* arg1);
diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig
index 7fdca59d9..90a3a46ea 100644
--- a/src/javascript/jsc/bindings/headers.zig
+++ b/src/javascript/jsc/bindings/headers.zig
@@ -140,6 +140,7 @@ pub extern fn ZigString__to16BitValue(arg0: [*c]const ZigString, arg1: [*c]JSC__
pub extern fn ZigString__toErrorInstance(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
pub extern fn ZigString__toExternalU16(arg0: [*c]const u16, arg1: usize, arg2: [*c]JSC__JSGlobalObject) JSC__JSValue;
pub extern fn ZigString__toExternalValue(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
+pub extern fn ZigString__toExternalValueWithCallback(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject, ArgFn2: ?fn (?*anyopaque, ?*anyopaque, usize) callconv(.C) void) JSC__JSValue;
pub extern fn ZigString__toValue(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
pub extern fn ZigString__toValueGC(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
pub extern fn SystemError__toErrorInstance(arg0: [*c]const SystemError, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
diff --git a/src/javascript/jsc/bindings/helpers.h b/src/javascript/jsc/bindings/helpers.h
index 11d728ac5..9e6da5d66 100644
--- a/src/javascript/jsc/bindings/helpers.h
+++ b/src/javascript/jsc/bindings/helpers.h
@@ -111,7 +111,7 @@ static bool isTaggedExternalPtr(const unsigned char* ptr)
static const WTF::String toString(ZigString str)
{
if (str.len == 0 || str.ptr == nullptr) {
- return WTF::String();
+ return WTF::Stzring();
}
if (UNLIKELY(isTaggedUTF8Ptr(str.ptr))) {
return WTF::String::fromUTF8(untag(str.ptr), str.len);
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 7f35ef8b7..a0198ed65 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -1301,7 +1301,7 @@ pub const Bun = struct {
Timer.set(id, globalThis, callback, countdown, false) catch
return JSValue.jsUndefined();
- return JSValue.jsNumber(@intCast(i32, id));
+ return JSValue.jsNumberWithType(i32, id);
}
pub fn setInterval(
globalThis: *JSGlobalObject,
@@ -1315,7 +1315,7 @@ pub const Bun = struct {
Timer.set(id, globalThis, callback, countdown, true) catch
return JSValue.jsUndefined();
- return JSValue.jsNumber(@intCast(i32, id));
+ return JSValue.jsNumberWithType(i32, id);
}
pub fn clearTimer(id: JSValue, _: *JSGlobalObject) void {
diff --git a/src/javascript/jsc/node/types.zig b/src/javascript/jsc/node/types.zig
index f9cf5a97d..0879c11be 100644
--- a/src/javascript/jsc/node/types.zig
+++ b/src/javascript/jsc/node/types.zig
@@ -420,6 +420,14 @@ pub const ArgumentsSlice = struct {
return this.remaining[0];
}
+
+ pub fn nextEat(this: *ArgumentsSlice) ?JSC.JSValue {
+ if (this.remaining.len == 0) {
+ return null;
+ }
+ defer this.eat();
+ return this.remaining[0];
+ }
};
pub fn fileDescriptorFromJS(ctx: JSC.C.JSContextRef, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?FileDescriptor {
diff --git a/src/jsc.zig b/src/jsc.zig
index e75b0fb29..fd4c380d3 100644
--- a/src/jsc.zig
+++ b/src/jsc.zig
@@ -10,6 +10,12 @@ pub const WebCore = @import("./javascript/jsc/webcore.zig");
pub const Cloudflare = struct {
pub const HTMLRewriter = @import("./javascript/jsc/api/html_rewriter.zig").HTMLRewriter;
pub const Element = @import("./javascript/jsc/api/html_rewriter.zig").Element;
+ pub const Comment = @import("./javascript/jsc/api/html_rewriter.zig").Comment;
+ pub const TextChunk = @import("./javascript/jsc/api/html_rewriter.zig").TextChunk;
+ pub const DocType = @import("./javascript/jsc/api/html_rewriter.zig").DocType;
+ pub const DocEnd = @import("./javascript/jsc/api/html_rewriter.zig").DocEnd;
+ pub const EndTag = @import("./javascript/jsc/api/html_rewriter.zig").EndTag;
+ pub const AttributeIterator = @import("./javascript/jsc/api/html_rewriter.zig").AttributeIterator;
};
pub const Jest = @import("./javascript/jsc/test/jest.zig");
pub const API = struct {