diff options
Diffstat (limited to 'src/javascript/jsc/api/html_rewriter.zig')
-rw-r--r-- | src/javascript/jsc/api/html_rewriter.zig | 1886 |
1 files changed, 0 insertions, 1886 deletions
diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig deleted file mode 100644 index 302af6aac..000000000 --- a/src/javascript/jsc/api/html_rewriter.zig +++ /dev/null @@ -1,1886 +0,0 @@ -const std = @import("std"); -const Api = @import("../../../api/schema.zig").Api; -const FilesystemRouter = @import("../../../router.zig"); -const http = @import("../../../http.zig"); -const JavaScript = @import("../javascript.zig"); -const QueryStringMap = @import("../../../url.zig").QueryStringMap; -const CombinedScanner = @import("../../../url.zig").CombinedScanner; -const bun = @import("../../../global.zig"); -const string = bun.string; -const JSC = @import("../../../jsc.zig"); -const js = JSC.C; -const WebCore = @import("../webcore/response.zig"); -const Router = @This(); -const Bundler = @import("../../../bundler.zig"); -const VirtualMachine = JavaScript.VirtualMachine; -const ScriptSrcStream = std.io.FixedBufferStream([]u8); -const ZigString = JSC.ZigString; -const Fs = @import("../../../fs.zig"); -const Base = @import("../base.zig"); -const getAllocator = Base.getAllocator; -const JSObject = JSC.JSObject; -const JSError = Base.JSError; -const JSValue = JSC.JSValue; -const JSGlobalObject = JSC.JSGlobalObject; -const strings = @import("strings"); -const NewClass = Base.NewClass; -const To = Base.To; -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); -pub 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 { - builder: *LOLHTML.HTMLRewriter.Builder, - context: LOLHTMLContext, - - pub const Constructor = JSC.NewConstructor(HTMLRewriter, .{ .constructor = constructor }, .{}); - - pub const Class = NewClass( - HTMLRewriter, - .{ .name = "HTMLRewriter" }, - .{ - .finalize = finalize, - .on = .{ - .rfn = wrap(HTMLRewriter, "on"), - }, - .onDocument = .{ - .rfn = wrap(HTMLRewriter, "onDocument"), - }, - .transform = .{ - .rfn = wrap(HTMLRewriter, "transform"), - }, - }, - .{}, - ); - - pub fn constructor( - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - var rewriter = bun.default_allocator.create(HTMLRewriter) catch unreachable; - rewriter.* = HTMLRewriter{ - .builder = LOLHTML.HTMLRewriter.Builder.init(), - .context = .{}, - }; - return HTMLRewriter.Class.make(ctx, rewriter); - } - - pub fn on( - this: *HTMLRewriter, - global: *JSGlobalObject, - selector_name: ZigString, - thisObject: JSC.C.JSObjectRef, - listener: JSValue, - exception: JSC.C.ExceptionRef, - ) JSValue { - 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, - listener: JSValue, - thisObject: JSC.C.JSObjectRef, - exception: JSC.C.ExceptionRef, - ) JSValue { - 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) void { - this.finalizeWithoutDestroy(); - bun.default_allocator.destroy(this); - } - - pub fn finalizeWithoutDestroy(this: *HTMLRewriter) void { - this.context.deinit(bun.default_allocator); - } - - pub fn beginTransform(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue { - const new_context = this.context; - this.context = .{}; - return BufferOutputSink.init(new_context, global, response, this.builder); - } - - pub fn returnEmptyResponse(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue { - var result = bun.default_allocator.create(Response) catch unreachable; - - response.cloneInto(result, getAllocator(global.ref()), global); - this.finalizeWithoutDestroy(); - return JSValue.fromRef(Response.makeMaybePooled(global.ref(), result)); - } - - pub fn transform(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue { - var input = response.body.slice(); - - if (input.len == 0 and !(response.body.value == .Blob and response.body.value.Blob.needsToReadFile())) { - return this.returnEmptyResponse(global, response); - } - - return this.beginTransform(global, response); - } - - pub const HTMLRewriterLoader = struct { - rewriter: *LOLHTML.HTMLRewriter, - finalized: bool = false, - context: LOLHTMLContext, - chunk_size: usize = 0, - failed: bool = false, - output: JSC.WebCore.Sink, - signal: JSC.WebCore.Signal = .{}, - backpressure: std.fifo.LinearFifo(u8, .Dynamic) = std.fifo.LinearFifo(u8, .Dynamic).init(bun.default_allocator), - - pub fn finalize(this: *HTMLRewriterLoader) void { - if (this.finalized) return; - this.rewriter.deinit(); - this.backpressure.deinit(); - this.backpressure = std.fifo.LinearFifo(u8, .Dynamic).init(bun.default_allocator); - this.finalized = true; - } - - pub fn fail(this: *HTMLRewriterLoader, err: JSC.Node.Syscall.Error) void { - this.signal.close(err); - this.output.end(err); - this.failed = true; - this.finalize(); - } - - pub fn connect(this: *HTMLRewriterLoader, signal: JSC.WebCore.Signal) void { - this.signal = signal; - } - - pub fn writeToDestination(this: *HTMLRewriterLoader, bytes: []const u8) void { - if (this.backpressure.count > 0) { - this.backpressure.write(bytes) catch { - this.fail(JSC.Node.Syscall.Error.oom); - this.finalize(); - }; - return; - } - - const write_result = this.output.write(.{ .temporary = bun.ByteList.init(bytes) }); - - switch (write_result) { - .err => |err| { - this.fail(err); - }, - .owned_and_done, .temporary_and_done, .into_array_and_done => { - this.done(); - }, - .pending => |pending| { - pending.applyBackpressure(bun.default_allocator, &this.output, pending, bytes); - }, - .into_array, .owned, .temporary => { - this.signal.ready(if (this.chunk_size > 0) this.chunk_size else null, null); - }, - } - } - - pub fn done( - this: *HTMLRewriterLoader, - ) void { - this.output.end(null); - this.signal.close(null); - this.finalize(); - } - - pub fn setup( - this: *HTMLRewriterLoader, - builder: *LOLHTML.HTMLRewriter.Builder, - context: LOLHTMLContext, - size_hint: ?usize, - output: JSC.WebCore.Sink, - ) ?[]const u8 { - for (context.document_handlers.items) |doc| { - doc.ctx = this; - } - for (context.element_handlers.items) |doc| { - doc.ctx = this; - } - - const chunk_size = @maximum(size_hint orelse 16384, 1024); - this.rewriter = builder.build( - .UTF8, - .{ - .preallocated_parsing_buffer_size = chunk_size, - .max_allowed_memory_usage = std.math.maxInt(u32), - }, - false, - HTMLRewriterLoader, - this, - HTMLRewriterLoader.writeToDestination, - HTMLRewriterLoader.done, - ) catch { - output.end(); - return LOLHTML.HTMLString.lastError().slice(); - }; - - this.chunk_size = chunk_size; - this.context = context; - this.output = output; - - return null; - } - - pub fn sink(this: *HTMLRewriterLoader) JSC.WebCore.Sink { - return JSC.WebCore.Sink.init(this); - } - - fn writeBytes(this: *HTMLRewriterLoader, bytes: bun.ByteList, comptime deinit_: bool) ?JSC.Node.Syscall.Error { - this.rewriter.write(bytes.slice()) catch { - return JSC.Node.Syscall.Error{ - .errno = 1, - // TODO: make this a union - .path = bun.default_allocator.dupe(u8, LOLHTML.HTMLString.lastError().slice()) catch unreachable, - }; - }; - if (comptime deinit_) bytes.listManaged(bun.default_allocator).deinit(); - return null; - } - - pub fn write(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable { - switch (data) { - .owned => |bytes| { - if (this.writeBytes(bytes, true)) |err| { - return .{ .err = err }; - } - return .{ .owned = bytes.len }; - }, - .owned_and_done => |bytes| { - if (this.writeBytes(bytes, true)) |err| { - return .{ .err = err }; - } - return .{ .owned_and_done = bytes.len }; - }, - .temporary_and_done => |bytes| { - if (this.writeBytes(bytes, false)) |err| { - return .{ .err = err }; - } - return .{ .temporary_and_done = bytes.len }; - }, - .temporary => |bytes| { - if (this.writeBytes(bytes, false)) |err| { - return .{ .err = err }; - } - return .{ .temporary = bytes.len }; - }, - else => unreachable, - } - } - - pub fn writeUTF16(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable { - return JSC.WebCore.Sink.UTF8Fallback.writeUTF16(HTMLRewriterLoader, this, data, write); - } - - pub fn writeLatin1(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable { - return JSC.WebCore.Sink.UTF8Fallback.writeLatin1(HTMLRewriterLoader, this, data, write); - } - }; - - pub const BufferOutputSink = struct { - global: *JSGlobalObject, - bytes: bun.MutableString, - rewriter: *LOLHTML.HTMLRewriter, - context: LOLHTMLContext, - response: *Response, - input: JSC.WebCore.Blob = undefined, - pub fn init(context: LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSValue { - var result = bun.default_allocator.create(Response) catch unreachable; - var sink = bun.default_allocator.create(BufferOutputSink) catch unreachable; - sink.* = BufferOutputSink{ - .global = global, - .bytes = bun.MutableString.initEmpty(bun.default_allocator), - .rewriter = undefined, - .context = context, - .response = result, - }; - - for (sink.context.document_handlers.items) |doc| { - doc.ctx = sink; - } - for (sink.context.element_handlers.items) |doc| { - doc.ctx = sink; - } - - sink.rewriter = builder.build( - .UTF8, - .{ - .preallocated_parsing_buffer_size = @maximum(original.body.len(), 1024), - .max_allowed_memory_usage = std.math.maxInt(u32), - }, - false, - BufferOutputSink, - sink, - BufferOutputSink.write, - BufferOutputSink.done, - ) catch { - sink.deinit(); - bun.default_allocator.destroy(result); - - return throwLOLHTMLError(global); - }; - - result.* = Response{ - .allocator = bun.default_allocator, - .body = .{ - .init = .{ - .status_code = 200, - }, - .value = .{ - .Locked = .{ - .global = global, - .task = sink, - }, - }, - }, - }; - - result.body.init.headers = original.body.init.headers; - result.body.init.method = original.body.init.method; - result.body.init.status_code = original.body.init.status_code; - - result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable; - result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable; - - var input: JSC.WebCore.Blob = original.body.value.use(); - - const is_pending = input.needsToReadFile(); - defer if (!is_pending) input.detach(); - - if (is_pending) { - input.doReadFileInternal(*BufferOutputSink, sink, onFinishedLoading, global); - } else if (sink.runOutputSink(input.sharedView(), false, false)) |error_value| { - return error_value; - } - - // Hold off on cloning until we're actually done. - - return JSC.JSValue.fromRef( - Response.makeMaybePooled(sink.global.ref(), sink.response), - ); - } - - pub fn onFinishedLoading(sink: *BufferOutputSink, bytes: JSC.WebCore.Blob.Store.ReadFile.ResultType) void { - switch (bytes) { - .err => |err| { - if (sink.response.body.value == .Locked and @ptrToInt(sink.response.body.value.Locked.task) == @ptrToInt(sink) and - sink.response.body.value.Locked.promise == null) - { - sink.response.body.value = .{ .Empty = .{} }; - // is there a pending promise? - // we will need to reject it - } else if (sink.response.body.value == .Locked and @ptrToInt(sink.response.body.value.Locked.task) == @ptrToInt(sink) and - sink.response.body.value.Locked.promise != null) - { - sink.response.body.value.Locked.callback = null; - sink.response.body.value.Locked.task = null; - } - - sink.response.body.value.toErrorInstance(err.toErrorInstance(sink.global), sink.global); - sink.rewriter.end() catch {}; - sink.deinit(); - return; - }, - .result => |data| { - _ = sink.runOutputSink(data.buf, true, data.is_temporary); - }, - } - } - - pub fn runOutputSink( - sink: *BufferOutputSink, - bytes: []const u8, - is_async: bool, - free_bytes_on_end: bool, - ) ?JSValue { - defer if (free_bytes_on_end) - bun.default_allocator.free(bun.constStrToU8(bytes)); - - sink.bytes.growBy(bytes.len) catch unreachable; - var global = sink.global; - var response = sink.response; - - sink.rewriter.write(bytes) catch { - sink.deinit(); - bun.default_allocator.destroy(sink); - - if (is_async) { - response.body.value.toErrorInstance(throwLOLHTMLError(global), global); - - return null; - } else { - return throwLOLHTMLError(global); - } - }; - - sink.rewriter.end() catch { - if (!is_async) response.finalize(); - sink.response = undefined; - sink.deinit(); - - if (is_async) { - response.body.value.toErrorInstance(throwLOLHTMLError(global), global); - return null; - } else { - return throwLOLHTMLError(global); - } - }; - - return null; - } - - pub const Sync = enum { suspended, pending, done }; - - pub fn done(this: *BufferOutputSink) void { - var prev_value = this.response.body.value; - var bytes = this.bytes.toOwnedSliceLeaky(); - this.response.body.value = .{ - .Blob = JSC.WebCore.Blob.init(bytes, this.bytes.allocator, this.global), - }; - prev_value.resolve( - &this.response.body.value, - this.global, - ); - } - - 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 StreamOutputSink = struct { - // global: *JSGlobalObject, - // rewriter: *LOLHTML.HTMLRewriter, - // context: LOLHTMLContext, - // response: *Response, - // input: JSC.WebCore.Blob = undefined, - // pub fn init(context: LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSValue { - // var result = bun.default_allocator.create(Response) catch unreachable; - // var sink = bun.default_allocator.create(StreamOutputSink) catch unreachable; - // sink.* = StreamOutputSink{ - // .global = global, - // .rewriter = undefined, - // .context = context, - // .response = result, - // }; - - // for (sink.context.document_handlers.items) |doc| { - // doc.ctx = sink; - // } - // for (sink.context.element_handlers.items) |doc| { - // doc.ctx = sink; - // } - - // sink.rewriter = builder.build( - // .UTF8, - // .{ - // .preallocated_parsing_buffer_size = @maximum(original.body.len(), 1024), - // .max_allowed_memory_usage = std.math.maxInt(u32), - // }, - // false, - // StreamOutputSink, - // sink, - // StreamOutputSink.write, - // StreamOutputSink.done, - // ) catch { - // sink.deinit(); - // bun.default_allocator.destroy(result); - - // return throwLOLHTMLError(global); - // }; - - // result.* = Response{ - // .allocator = bun.default_allocator, - // .body = .{ - // .init = .{ - // .status_code = 200, - // }, - // .value = .{ - // .Locked = .{ - // .global = global, - // .task = sink, - // }, - // }, - // }, - // }; - - // result.body.init.headers = original.body.init.headers; - // result.body.init.method = original.body.init.method; - // result.body.init.status_code = original.body.init.status_code; - - // result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable; - // result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable; - - // var input: JSC.WebCore.Blob = original.body.value.use(); - - // const is_pending = input.needsToReadFile(); - // defer if (!is_pending) input.detach(); - - // if (is_pending) { - // input.doReadFileInternal(*StreamOutputSink, sink, onFinishedLoading, global); - // } else if (sink.runOutputSink(input.sharedView(), false, false)) |error_value| { - // return error_value; - // } - - // // Hold off on cloning until we're actually done. - - // return JSC.JSValue.fromRef( - // Response.makeMaybePooled(sink.global.ref(), sink.response), - // ); - // } - - // pub fn runOutputSink( - // sink: *StreamOutputSink, - // bytes: []const u8, - // is_async: bool, - // free_bytes_on_end: bool, - // ) ?JSValue { - // defer if (free_bytes_on_end) - // bun.default_allocator.free(bun.constStrToU8(bytes)); - - // return null; - // } - - // pub const Sync = enum { suspended, pending, done }; - - // pub fn done(this: *StreamOutputSink) void { - // var prev_value = this.response.body.value; - // var bytes = this.bytes.toOwnedSliceLeaky(); - // this.response.body.value = .{ - // .Blob = JSC.WebCore.Blob.init(bytes, this.bytes.allocator, this.global), - // }; - // prev_value.resolve( - // &this.response.body.value, - // this.global, - // ); - // } - - // pub fn write(this: *StreamOutputSink, bytes: []const u8) void { - // this.bytes.append(bytes) catch unreachable; - // } - - // pub fn deinit(this: *StreamOutputSink) void { - // this.bytes.deinit(); - - // this.context.deinit(bun.default_allocator); - // } - // }; -}; - -const DocumentHandler = struct { - onDocTypeCallback: ?JSValue = null, - onCommentCallback: ?JSValue = null, - onTextCallback: ?JSValue = null, - onEndCallback: ?JSValue = null, - thisObject: JSValue, - global: *JSGlobalObject, - ctx: ?*HTMLRewriter.BufferOutputSink = null, - - 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 { - if (comptime JSC.is_bindgen) - unreachable; - 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 - 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, - ); - var promise_: ?*JSC.JSInternalPromise = null; - while (!result.isUndefinedOrNull()) { - if (result.isError() or result.isAggregateError(this.global)) { - @field(zig_element, field_name) = null; - return true; - } - - var promise = promise_ orelse JSC.JSInternalPromise.resolvedPromise(this.global, result); - promise_ = promise; - JavaScript.VirtualMachine.vm.event_loop.waitForPromise(promise); - - switch (promise.status(this.global.vm())) { - JSC.JSPromise.Status.Pending => unreachable, - JSC.JSPromise.Status.Rejected => { - JavaScript.VirtualMachine.vm.defaultErrorHandler(promise.result(this.global.vm()), null); - @field(zig_element, field_name) = null; - return false; - }, - JSC.JSPromise.Status.Fulfilled => { - result = promise.result(this.global.vm()); - break; - }, - } - - break; - } - @field(zig_element, field_name) = null; - return false; - } - }.callback; -} - -const ElementHandler = struct { - onElementCallback: ?JSValue = null, - onCommentCallback: ?JSValue = null, - onTextCallback: ?JSValue = null, - thisObject: JSValue, - global: *JSGlobalObject, - ctx: ?*HTMLRewriter.BufferOutputSink = null, - - 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, "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; - } - - 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 fn onElement(this: *ElementHandler, value: *LOLHTML.Element) bool { - return HandlerCallback( - ElementHandler, - Element, - LOLHTML.Element, - "element", - "onElementCallback", - )(this, value); - } - - pub const onComment = HandlerCallback( - ElementHandler, - Comment, - LOLHTML.Comment, - "comment", - "onCommentCallback", - ); - - pub const onText = HandlerCallback( - ElementHandler, - TextChunk, - LOLHTML.TextChunk, - "text_chunk", - "onTextCallback", - ); -}; - -pub const ContentOptions = struct { - html: bool = false, -}; - -const getterWrap = JSC.getterWrap; -const setterWrap = JSC.setterWrap; -const wrap = JSC.wrapAsync; - -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(); -} - -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"), - }, - .finalize = finalize, - }, - .{ - .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 fn finalize(this: *TextChunk) void { - this.text_chunk = null; - bun.default_allocator.destroy(this); - } -}; - -pub const DocType = struct { - doctype: ?*LOLHTML.DocType = null, - - pub fn finalize(this: *DocType) void { - this.doctype = null; - bun.default_allocator.destroy(this); - } - - pub const Class = NewClass( - DocType, - .{ - .name = "DocType", - }, - .{ - .finalize = finalize, - }, - .{ - .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 fn finalize(this: *DocEnd) void { - this.doc_end = null; - bun.default_allocator.destroy(this); - } - - pub const Class = NewClass( - DocEnd, - .{ .name = "DocEnd" }, - .{ - .finalize = finalize, - .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 fn finalize(this: *Comment) void { - this.comment = null; - bun.default_allocator.destroy(this); - } - - 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"), - }, - .finalize = finalize, - }, - .{ - .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 fn finalize(this: *EndTag) void { - this.end_tag = null; - bun.default_allocator.destroy(this); - } - - 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"), - }, - .finalize = finalize, - }, - .{ - .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, - - const attribute_iterator_path: string = "file:///bun-vfs/lolhtml/AttributeIterator.js"; - const attribute_iterator_code: string = - \\"use strict"; - \\ - \\class AttributeIterator { - \\ constructor(internal) { - \\ this.#iterator = internal; - \\ } - \\ - \\ #iterator; - \\ - \\ [Symbol.iterator]() { - \\ return this; - \\ } - \\ - \\ next() { - \\ if (this.#iterator === null) - \\ return {done: true}; - \\ var value = this.#iterator.next(); - \\ if (!value) { - \\ this.#iterator = null; - \\ return {done: true}; - \\ } - \\ return {done: false, value: value}; - \\ } - \\} - \\ - \\return new AttributeIterator(internal1); - ; - threadlocal var attribute_iterator_class: JSC.C.JSObjectRef = undefined; - threadlocal var attribute_iterator_loaded: bool = false; - - pub fn getAttributeIteratorJSClass(global: *JSGlobalObject) JSValue { - if (attribute_iterator_loaded) - return JSC.JSValue.fromRef(attribute_iterator_class); - attribute_iterator_loaded = true; - var exception_ptr: ?[*]JSC.JSValueRef = null; - var name = JSC.C.JSStringCreateStatic("AttributeIteratorGetter", "AttributeIteratorGetter".len); - var param_name = JSC.C.JSStringCreateStatic("internal1", "internal1".len); - var attribute_iterator_class_ = JSC.C.JSObjectMakeFunction( - global.ref(), - name, - 1, - &[_]JSC.C.JSStringRef{param_name}, - JSC.C.JSStringCreateStatic(attribute_iterator_code.ptr, attribute_iterator_code.len), - JSC.C.JSStringCreateStatic(attribute_iterator_path.ptr, attribute_iterator_path.len), - 0, - exception_ptr, - ); - JSC.C.JSValueProtect(global.ref(), attribute_iterator_class_); - attribute_iterator_class = attribute_iterator_class_; - return JSC.JSValue.fromRef(attribute_iterator_class); - } - - pub fn finalize(this: *AttributeIterator) void { - if (this.iterator) |iter| { - iter.deinit(); - this.iterator = null; - } - bun.default_allocator.destroy(this); - } - - pub const Class = NewClass( - AttributeIterator, - .{ .name = "AttributeIterator" }, - .{ - .next = .{ - .rfn = wrap(AttributeIterator, "next"), - }, - .finalize = finalize, - }, - .{}, - ); - - 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.jsNull(); - } - - var attribute = this.iterator.?.next() orelse { - this.iterator.?.deinit(); - this.iterator = null; - return JSC.JSValue.jsNull(); - }; - - // TODO: don't clone here - const value = attribute.value(); - const name = attribute.name(); - defer name.deinit(); - defer value.deinit(); - - var strs = [2]ZigString{ - ZigString.init(name.slice()), - ZigString.init(value.slice()), - }; - - var valid_strs: []ZigString = strs[0..2]; - - var array = JSC.JSValue.createStringArray( - globalObject, - valid_strs.ptr, - valid_strs.len, - true, - ); - - return array; - } -}; -pub const Element = struct { - element: ?*LOLHTML.Element = null, - - pub const Class = NewClass( - Element, - .{ .name = "Element" }, - .{ - .getAttribute = .{ - .rfn = wrap(Element, "getAttribute"), - }, - .hasAttribute = .{ - .rfn = wrap(Element, "hasAttribute"), - }, - .setAttribute = .{ - .rfn = wrap(Element, "setAttribute"), - }, - .removeAttribute = .{ - .rfn = wrap(Element, "removeAttribute"), - }, - .before = .{ - .rfn = wrap(Element, "before"), - }, - .after = .{ - .rfn = wrap(Element, "after"), - }, - .prepend = .{ - .rfn = wrap(Element, "prepend"), - }, - .append = .{ - .rfn = wrap(Element, "append"), - }, - .replace = .{ - .rfn = wrap(Element, "replace"), - }, - .setInnerContent = .{ - .rfn = wrap(Element, "setInnerContent"), - }, - .remove = .{ - .rfn = wrap(Element, "remove"), - }, - .removeAndKeepContent = .{ - .rfn = wrap(Element, "removeAndKeepContent"), - }, - .onEndTag = .{ - .rfn = wrap(Element, "onEndTag"), - }, - .finalize = finalize, - }, - .{ - .tagName = .{ - .get = getterWrap(Element, "getTagName"), - .set = setterWrap(Element, "setTagName"), - }, - .removed = .{ - .get = getterWrap(Element, "getRemoved"), - }, - .namespaceURI = .{ - .get = getterWrap(Element, "getNamespaceURI"), - }, - .attributes = .{ - .get = getterWrap(Element, "getAttributes"), - }, - }, - ); - - pub fn finalize(this: *Element) void { - this.element = null; - bun.default_allocator.destroy(this); - } - - 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 { - 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, 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, 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, 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, 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, thisObject: js.JSObjectRef, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { - return contentHandler( - this, - LOLHTML.Element.after, - thisObject, - globalObject, - content, - contentOptions, - ); - } - - /// 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, 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, 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, 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, thisObject: js.JSObjectRef) JSValue { - if (this.element == null) - return JSValue.jsUndefined(); - - 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, thisObject: js.JSObjectRef) JSValue { - if (this.element == null) - return JSValue.jsUndefined(); - - this.element.?.removeAndKeepContent(); - return JSValue.fromRef(thisObject); - } - - pub fn getTagName(this: *Element, globalObject: *JSGlobalObject) JSValue { - if (this.element == null) - return JSValue.jsUndefined(); - - return htmlStringValue(this.element.?.tagName(), globalObject); - } - - 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(); - }; - } - - 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 { - if (this.element == null) - return JSValue.jsUndefined(); - - return ZigString.init(std.mem.span(this.element.?.namespaceURI())).toValue(globalObject); - } - - pub fn getAttributes(this: *Element, globalObject: *JSGlobalObject) JSValue { - if (this.element == null) - return JSValue.jsUndefined(); - - var iter = this.element.?.attributes() orelse return throwLOLHTMLError(globalObject); - var attr_iter = bun.default_allocator.create(AttributeIterator) catch unreachable; - attr_iter.* = .{ .iterator = iter }; - var attr = AttributeIterator.Class.make(globalObject.ref(), attr_iter); - JSC.C.JSValueProtect(globalObject.ref(), attr); - defer JSC.C.JSValueUnprotect(globalObject.ref(), attr); - return JSC.JSValue.fromRef( - JSC.C.JSObjectCallAsFunction( - globalObject.ref(), - AttributeIterator.getAttributeIteratorJSClass(globalObject).asObjectRef(), - null, - 1, - @ptrCast([*]JSC.C.JSObjectRef, &attr), - null, - ), - ); - } -}; |