diff options
author | 2023-08-19 04:59:23 +0200 | |
---|---|---|
committer | 2023-08-18 19:59:23 -0700 | |
commit | c2ec47ff320fc52298d9488ebbc2d89a19a0ec8a (patch) | |
tree | f19b054ad4358b667c6fe4dd728164424e222fa4 | |
parent | 26036a390b71e079e31694d2dcf64572e30db74f (diff) | |
download | bun-c2ec47ff320fc52298d9488ebbc2d89a19a0ec8a.tar.gz bun-c2ec47ff320fc52298d9488ebbc2d89a19a0ec8a.tar.zst bun-c2ec47ff320fc52298d9488ebbc2d89a19a0ec8a.zip |
feat: add self-closing & can-have-content (#4206)
Diffstat (limited to '')
-rw-r--r-- | packages/bun-types/html-rewriter.d.ts | 8 | ||||
-rw-r--r-- | src/bun.js/api/html_rewriter.classes.ts | 6 | ||||
-rw-r--r-- | src/bun.js/api/html_rewriter.zig | 70 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 32 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 8 | ||||
-rw-r--r-- | src/deps/lol-html.zig | 10 | ||||
-rw-r--r-- | test/js/workerd/html-rewriter.test.js | 39 |
7 files changed, 147 insertions, 26 deletions
diff --git a/packages/bun-types/html-rewriter.d.ts b/packages/bun-types/html-rewriter.d.ts index 7f260fd5a..8cc4e9657 100644 --- a/packages/bun-types/html-rewriter.d.ts +++ b/packages/bun-types/html-rewriter.d.ts @@ -50,6 +50,14 @@ declare namespace HTMLRewriterTypes { tagName: string; readonly attributes: IterableIterator<string[]>; readonly removed: boolean; + /** Whether the element is explicitly self-closing, e.g. `<foo />` */ + readonly selfClosing: boolean; + /** + * Whether the element can have inner content. Returns `true` unless + * - the element is an [HTML void element](https://html.spec.whatwg.org/multipage/syntax.html#void-elements) + * - or it's self-closing in a foreign context (eg. in SVG, MathML). + */ + readonly canHaveContent: boolean; readonly namespaceURI: string; getAttribute(name: string): string | null; hasAttribute(name: string): boolean; diff --git a/src/bun.js/api/html_rewriter.classes.ts b/src/bun.js/api/html_rewriter.classes.ts index a447a0edc..f904f6f29 100644 --- a/src/bun.js/api/html_rewriter.classes.ts +++ b/src/bun.js/api/html_rewriter.classes.ts @@ -251,6 +251,12 @@ export default [ removed: { getter: "getRemoved", }, + selfClosing: { + getter: "getSelfClosing", + }, + canHaveContent: { + getter: "getCanHaveContent", + }, namespaceURI: { getter: "getNamespaceURI", cache: true, diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index 0c1cbebab..fd0b5dd9e 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -950,7 +950,7 @@ pub const TextChunk = struct { pub usingnamespace JSC.Codegen.JSTextChunk; - fn contentHandler(this: *TextChunk, comptime Callback: (fn (*LOLHTML.TextChunk, []const u8, bool) LOLHTML.Error!void), callFrame: *JSC.CallFrame, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { + fn contentHandler(this: *TextChunk, comptime Callback: (fn (*LOLHTML.TextChunk, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { if (this.text_chunk == null) return JSC.JSValue.jsUndefined(); var content_slice = content.toSlice(bun.default_allocator); @@ -962,7 +962,7 @@ pub const TextChunk = struct { contentOptions != null and contentOptions.?.html, ) catch return throwLOLHTMLError(globalObject); - return callFrame.this(); + return thisObject; } pub fn before_( @@ -972,7 +972,7 @@ pub const TextChunk = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.TextChunk.before, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.TextChunk.before, callFrame.this(), globalObject, content, contentOptions); } pub fn after_( @@ -982,7 +982,7 @@ pub const TextChunk = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.TextChunk.after, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.TextChunk.after, callFrame.this(), globalObject, content, contentOptions); } pub fn replace_( @@ -992,7 +992,7 @@ pub const TextChunk = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.TextChunk.replace, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.TextChunk.replace, callFrame.this(), globalObject, content, contentOptions); } pub const before = JSC.wrapInstanceMethod(TextChunk, "before_", false); @@ -1093,7 +1093,7 @@ pub const DocEnd = struct { pub usingnamespace JSC.Codegen.JSDocEnd; - fn contentHandler(this: *DocEnd, comptime Callback: (fn (*LOLHTML.DocEnd, []const u8, bool) LOLHTML.Error!void), callFrame: *JSC.CallFrame, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { + fn contentHandler(this: *DocEnd, comptime Callback: (fn (*LOLHTML.DocEnd, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { if (this.doc_end == null) return JSValue.jsNull(); @@ -1106,7 +1106,7 @@ pub const DocEnd = struct { contentOptions != null and contentOptions.?.html, ) catch return throwLOLHTMLError(globalObject); - return callFrame.this(); + return thisObject; } pub fn append_( @@ -1116,7 +1116,7 @@ pub const DocEnd = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.DocEnd.append, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.DocEnd.append, callFrame.this(), globalObject, content, contentOptions); } pub const append = JSC.wrapInstanceMethod(DocEnd, "append_", false); @@ -1132,7 +1132,7 @@ pub const Comment = struct { pub usingnamespace JSC.Codegen.JSComment; - fn contentHandler(this: *Comment, comptime Callback: (fn (*LOLHTML.Comment, []const u8, bool) LOLHTML.Error!void), callFrame: *JSC.CallFrame, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { + fn contentHandler(this: *Comment, comptime Callback: (fn (*LOLHTML.Comment, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { if (this.comment == null) return JSValue.jsNull(); var content_slice = content.toSlice(bun.default_allocator); @@ -1144,7 +1144,7 @@ pub const Comment = struct { contentOptions != null and contentOptions.?.html, ) catch return throwLOLHTMLError(globalObject); - return callFrame.this(); + return thisObject; } pub fn before_( @@ -1154,7 +1154,7 @@ pub const Comment = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.Comment.before, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.Comment.before, callFrame.this(), globalObject, content, contentOptions); } pub fn after_( @@ -1164,7 +1164,7 @@ pub const Comment = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.Comment.after, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.Comment.after, callFrame.this(), globalObject, content, contentOptions); } pub fn replace_( @@ -1174,7 +1174,7 @@ pub const Comment = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.Comment.replace, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.Comment.replace, callFrame.this(), globalObject, content, contentOptions); } pub const before = JSC.wrapInstanceMethod(Comment, "before_", false); @@ -1253,7 +1253,7 @@ pub const EndTag = struct { pub usingnamespace JSC.Codegen.JSEndTag; - fn contentHandler(this: *EndTag, comptime Callback: (fn (*LOLHTML.EndTag, []const u8, bool) LOLHTML.Error!void), callFrame: *JSC.CallFrame, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { + fn contentHandler(this: *EndTag, comptime Callback: (fn (*LOLHTML.EndTag, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { if (this.end_tag == null) return JSValue.jsNull(); @@ -1266,7 +1266,7 @@ pub const EndTag = struct { contentOptions != null and contentOptions.?.html, ) catch return throwLOLHTMLError(globalObject); - return callFrame.this(); + return thisObject; } pub fn before_( @@ -1276,7 +1276,7 @@ pub const EndTag = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.EndTag.before, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.EndTag.before, callFrame.this(), globalObject, content, contentOptions); } pub fn after_( @@ -1286,7 +1286,7 @@ pub const EndTag = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.EndTag.after, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.EndTag.after, callFrame.this(), globalObject, content, contentOptions); } pub fn replace_( @@ -1296,7 +1296,7 @@ pub const EndTag = struct { content: ZigString, contentOptions: ?ContentOptions, ) JSValue { - return this.contentHandler(LOLHTML.EndTag.replace, callFrame, globalObject, content, contentOptions); + return this.contentHandler(LOLHTML.EndTag.replace, callFrame.this(), globalObject, content, contentOptions); } pub const before = JSC.wrapInstanceMethod(EndTag, "before_", false); @@ -1481,7 +1481,7 @@ pub const Element = struct { pub const setAttribute = JSC.wrapInstanceMethod(Element, "setAttribute_", false); pub const removeAttribute = JSC.wrapInstanceMethod(Element, "removeAttribute_", false); - fn contentHandler(this: *Element, comptime Callback: (fn (*LOLHTML.Element, []const u8, bool) LOLHTML.Error!void), callFrame: *JSC.CallFrame, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { + fn contentHandler(this: *Element, comptime Callback: (fn (*LOLHTML.Element, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { if (this.element == null) return JSValue.jsUndefined(); @@ -1494,7 +1494,7 @@ pub const Element = struct { contentOptions != null and contentOptions.?.html, ) catch return throwLOLHTMLError(globalObject); - return callFrame.this(); + return thisObject; } /// Inserts content before the element. @@ -1502,7 +1502,7 @@ pub const Element = struct { return contentHandler( this, LOLHTML.Element.before, - callFrame, + callFrame.this(), globalObject, content, contentOptions, @@ -1514,7 +1514,7 @@ pub const Element = struct { return contentHandler( this, LOLHTML.Element.after, - callFrame, + callFrame.this(), globalObject, content, contentOptions, @@ -1526,7 +1526,7 @@ pub const Element = struct { return contentHandler( this, LOLHTML.Element.prepend, - callFrame, + callFrame.this(), globalObject, content, contentOptions, @@ -1538,7 +1538,7 @@ pub const Element = struct { return contentHandler( this, LOLHTML.Element.append, - callFrame, + callFrame.this(), globalObject, content, contentOptions, @@ -1550,7 +1550,7 @@ pub const Element = struct { return contentHandler( this, LOLHTML.Element.replace, - callFrame, + callFrame.this(), globalObject, content, contentOptions, @@ -1562,7 +1562,7 @@ pub const Element = struct { return contentHandler( this, LOLHTML.Element.setInnerContent, - callFrame, + callFrame.this(), globalObject, content, contentOptions, @@ -1636,6 +1636,24 @@ pub const Element = struct { return JSValue.jsBoolean(this.element.?.isRemoved()); } + pub fn getSelfClosing( + this: *Element, + _: *JSGlobalObject, + ) callconv(.C) JSValue { + if (this.element == null) + return JSValue.jsUndefined(); + return JSValue.jsBoolean(this.element.?.isSelfClosing()); + } + + pub fn getCanHaveContent( + this: *Element, + _: *JSGlobalObject, + ) callconv(.C) JSValue { + if (this.element == null) + return JSValue.jsUndefined(); + return JSValue.jsBoolean(this.element.?.canHaveContent()); + } + pub fn getNamespaceURI( this: *Element, globalObject: *JSGlobalObject, diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 066732db9..0b1e1702b 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -6054,6 +6054,9 @@ JSC_DECLARE_CUSTOM_GETTER(ElementPrototype__attributesGetterWrap); extern "C" EncodedJSValue ElementPrototype__before(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ElementPrototype__beforeCallback); +extern "C" JSC::EncodedJSValue ElementPrototype__getCanHaveContent(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); +JSC_DECLARE_CUSTOM_GETTER(ElementPrototype__canHaveContentGetterWrap); + extern "C" EncodedJSValue ElementPrototype__getAttribute(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ElementPrototype__getAttributeCallback); @@ -6084,6 +6087,9 @@ JSC_DECLARE_CUSTOM_GETTER(ElementPrototype__removedGetterWrap); extern "C" EncodedJSValue ElementPrototype__replace(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ElementPrototype__replaceCallback); +extern "C" JSC::EncodedJSValue ElementPrototype__getSelfClosing(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); +JSC_DECLARE_CUSTOM_GETTER(ElementPrototype__selfClosingGetterWrap); + extern "C" EncodedJSValue ElementPrototype__setAttribute(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ElementPrototype__setAttributeCallback); @@ -6103,6 +6109,7 @@ static const HashTableValue JSElementPrototypeTableValues[] = { { "append"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__appendCallback, 1 } }, { "attributes"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ElementPrototype__attributesGetterWrap, 0 } }, { "before"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__beforeCallback, 1 } }, + { "canHaveContent"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ElementPrototype__canHaveContentGetterWrap, 0 } }, { "getAttribute"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__getAttributeCallback, 1 } }, { "hasAttribute"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__hasAttributeCallback, 1 } }, { "namespaceURI"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ElementPrototype__namespaceURIGetterWrap, 0 } }, @@ -6113,6 +6120,7 @@ static const HashTableValue JSElementPrototypeTableValues[] = { { "removeAttribute"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__removeAttributeCallback, 1 } }, { "removed"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ElementPrototype__removedGetterWrap, 0 } }, { "replace"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__replaceCallback, 1 } }, + { "selfClosing"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ElementPrototype__selfClosingGetterWrap, 0 } }, { "setAttribute"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__setAttributeCallback, 2 } }, { "setInnerContent"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ElementPrototype__setInnerContentCallback, 1 } }, { "tagName"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ElementPrototype__tagNameGetterWrap, ElementPrototype__tagNameSetterWrap } } @@ -6228,6 +6236,18 @@ JSC_DEFINE_HOST_FUNCTION(ElementPrototype__beforeCallback, (JSGlobalObject * lex return ElementPrototype__before(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_CUSTOM_GETTER(ElementPrototype__canHaveContentGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = lexicalGlobalObject->vm(); + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSElement* thisObject = jsCast<JSElement*>(JSValue::decode(thisValue)); + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + JSC::EncodedJSValue result = ElementPrototype__getCanHaveContent(thisObject->wrapped(), globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + RELEASE_AND_RETURN(throwScope, result); +} + JSC_DEFINE_HOST_FUNCTION(ElementPrototype__getAttributeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -6495,6 +6515,18 @@ JSC_DEFINE_HOST_FUNCTION(ElementPrototype__replaceCallback, (JSGlobalObject * le return ElementPrototype__replace(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_CUSTOM_GETTER(ElementPrototype__selfClosingGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = lexicalGlobalObject->vm(); + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSElement* thisObject = jsCast<JSElement*>(JSValue::decode(thisValue)); + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + JSC::EncodedJSValue result = ElementPrototype__getSelfClosing(thisObject->wrapped(), globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + RELEASE_AND_RETURN(throwScope, result); +} + JSC_DEFINE_HOST_FUNCTION(ElementPrototype__setAttributeCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 70cf4728d..bf1942fd6 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -1824,6 +1824,9 @@ pub const JSElement = struct { if (@TypeOf(Element.before) != CallbackType) @compileLog("Expected Element.before to be a callback but received " ++ @typeName(@TypeOf(Element.before))); + if (@TypeOf(Element.getCanHaveContent) != GetterType) + @compileLog("Expected Element.getCanHaveContent to be a getter"); + if (@TypeOf(Element.getAttribute) != CallbackType) @compileLog("Expected Element.getAttribute to be a callback but received " ++ @typeName(@TypeOf(Element.getAttribute))); if (@TypeOf(Element.hasAttribute) != CallbackType) @@ -1846,6 +1849,9 @@ pub const JSElement = struct { if (@TypeOf(Element.replace) != CallbackType) @compileLog("Expected Element.replace to be a callback but received " ++ @typeName(@TypeOf(Element.replace))); + if (@TypeOf(Element.getSelfClosing) != GetterType) + @compileLog("Expected Element.getSelfClosing to be a getter"); + if (@TypeOf(Element.setAttribute) != CallbackType) @compileLog("Expected Element.setAttribute to be a callback but received " ++ @typeName(@TypeOf(Element.setAttribute))); if (@TypeOf(Element.setInnerContent) != CallbackType) @@ -1862,8 +1868,10 @@ pub const JSElement = struct { @export(Element.finalize, .{ .name = "ElementClass__finalize" }); @export(Element.getAttribute, .{ .name = "ElementPrototype__getAttribute" }); @export(Element.getAttributes, .{ .name = "ElementPrototype__getAttributes" }); + @export(Element.getCanHaveContent, .{ .name = "ElementPrototype__getCanHaveContent" }); @export(Element.getNamespaceURI, .{ .name = "ElementPrototype__getNamespaceURI" }); @export(Element.getRemoved, .{ .name = "ElementPrototype__getRemoved" }); + @export(Element.getSelfClosing, .{ .name = "ElementPrototype__getSelfClosing" }); @export(Element.getTagName, .{ .name = "ElementPrototype__getTagName" }); @export(Element.hasAttribute, .{ .name = "ElementPrototype__hasAttribute" }); @export(Element.onEndTag, .{ .name = "ElementPrototype__onEndTag" }); diff --git a/src/deps/lol-html.zig b/src/deps/lol-html.zig index cd183ee5f..9a9137856 100644 --- a/src/deps/lol-html.zig +++ b/src/deps/lol-html.zig @@ -391,6 +391,8 @@ pub const Element = opaque { 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_is_self_closing(element: *const Element) bool; + extern fn lol_html_element_can_have_content(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_add_end_tag_handler(element: *Element, end_tag_handler: lol_html_end_tag_handler_t, user_data: ?*anyopaque) c_int; @@ -493,6 +495,14 @@ pub const Element = opaque { auto_disable(); return lol_html_element_is_removed(element); } + pub fn isSelfClosing(element: *const Element) bool { + auto_disable(); + return lol_html_element_is_self_closing(element); + } + pub fn canHaveContent(element: *const Element) bool { + auto_disable(); + return lol_html_element_can_have_content(element); + } pub fn setUserData(element: *const Element, user_data: ?*anyopaque) void { auto_disable(); lol_html_element_user_data_set(element, user_data); diff --git a/test/js/workerd/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js index 44961df3b..58411209a 100644 --- a/test/js/workerd/html-rewriter.test.js +++ b/test/js/workerd/html-rewriter.test.js @@ -315,6 +315,45 @@ describe("HTMLRewriter", () => { expect(lastInTextNode).toBeBoolean(); }); + + it("it supports selfClosing", async () => { + const selfClosing = {} + await new HTMLRewriter() + .on("*", { + element(el) { + selfClosing[el.tagName] = el.selfClosing; + }, + }) + + .transform(new Response("<p>Lorem ipsum!<br></p><div />")) + .text(); + + expect(selfClosing).toEqual({ + p: false, + br: false, + div: true, + }); + }); + + it("it supports canHaveContent", async () => { + const canHaveContent = {} + await new HTMLRewriter() + .on("*", { + element(el) { + canHaveContent[el.tagName] = el.canHaveContent; + }, + }) + .transform(new Response("<p>Lorem ipsum!<br></p><div /><svg><circle /></svg>")) + .text(); + + expect(canHaveContent).toEqual({ + p: true, + br: false, + div: true, + svg: true, + circle: false, + }); + }); }); // By not segfaulting, this test passes |