mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-30 15:41:48 +00:00 
			
		
		
		
	Start working on HTMLSlotElement
This commit is contained in:
		 Karl Seguin
					Karl Seguin
				
			
				
					committed by
					
						 Muki Kiboigo
						Muki Kiboigo
					
				
			
			
				
	
			
			
			 Muki Kiboigo
						Muki Kiboigo
					
				
			
						parent
						
							c84634093d
						
					
				
				
					commit
					31335fc4fb
				
			| @@ -1042,84 +1042,68 @@ pub const HTMLSlotElement = struct { | ||||
|         flatten: bool = false, | ||||
|     }; | ||||
|     pub fn _assignedNodes(self: *parser.Slot, opts_: ?AssignedNodesOpts, page: *Page) ![]NodeUnion { | ||||
|         return findAssignedSlotNodes(self, opts_, false, page); | ||||
|     } | ||||
|  | ||||
|     // This should return Union, instead of NodeUnion, but we want to re-use | ||||
|     // findAssignedSlotNodes. Returning NodeUnion is fine, as long as every element | ||||
|     // within is an Element. This could be more efficient | ||||
|     pub fn _assignedElements(self: *parser.Slot, opts_: ?AssignedNodesOpts, page: *Page) ![]NodeUnion { | ||||
|         return findAssignedSlotNodes(self, opts_, true, page); | ||||
|     } | ||||
|  | ||||
|     fn findAssignedSlotNodes(self: *parser.Slot, opts_: ?AssignedNodesOpts, element_only: bool, page: *Page) ![]NodeUnion { | ||||
|         const opts = opts_ orelse AssignedNodesOpts{ .flatten = false }; | ||||
|  | ||||
|         if (opts.flatten) { | ||||
|             log.debug(.web_api, "not implemented", .{ .feature = "HTMLSlotElement flatten assignedNodes" }); | ||||
|         if (try findAssignedSlotNodes(self, opts, page)) |nodes| { | ||||
|             return nodes; | ||||
|         } | ||||
|  | ||||
|         if (!opts.flatten) { | ||||
|             return &.{}; | ||||
|         } | ||||
|  | ||||
|         const node: *parser.Node = @ptrCast(@alignCast(self)); | ||||
|         const nl = try parser.nodeGetChildNodes(node); | ||||
|         const len = try parser.nodeListLength(nl); | ||||
|         if (len == 0) { | ||||
|             return &.{}; | ||||
|         } | ||||
|  | ||||
|         // First we look for any explicitly assigned nodes (via the slot attribute) | ||||
|         { | ||||
|             const slot_name = try parser.elementGetAttribute(@ptrCast(@alignCast(self)), "name"); | ||||
|             var root = try parser.nodeGetRootNode(node); | ||||
|             if (page.getNodeState(root)) |state| { | ||||
|                 if (state.shadow_root) |sr| { | ||||
|                     root = @ptrCast(@alignCast(sr.host)); | ||||
|                 } | ||||
|         var assigned = try page.call_arena.alloc(NodeUnion, len); | ||||
|         var i: usize = 0; | ||||
|         while (true) : (i += 1) { | ||||
|             const child = try parser.nodeListItem(nl, @intCast(i)) orelse break; | ||||
|             assigned[i] = try Node.toInterface(child); | ||||
|         } | ||||
|         return assigned[0..i]; | ||||
|     } | ||||
|  | ||||
|     fn findAssignedSlotNodes(self: *parser.Slot, opts: AssignedNodesOpts, page: *Page) !?[]NodeUnion { | ||||
|         if (opts.flatten) { | ||||
|             log.warn(.web_api, "not implemented", .{ .feature = "HTMLSlotElement flatten assignedNodes" }); | ||||
|         } | ||||
|  | ||||
|         const slot_name = try parser.elementGetAttribute(@ptrCast(@alignCast(self)), "name"); | ||||
|         const node: *parser.Node = @ptrCast(@alignCast(self)); | ||||
|         var root = try parser.nodeGetRootNode(node); | ||||
|         if (page.getNodeState(root)) |state| { | ||||
|             if (state.shadow_root) |sr| { | ||||
|                 root = @ptrCast(@alignCast(sr.host)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|             var arr: std.ArrayList(NodeUnion) = .empty; | ||||
|             const w = @import("../dom/walker.zig").WalkerChildren{}; | ||||
|             var next: ?*parser.Node = null; | ||||
|             while (true) { | ||||
|                 next = try w.get_next(root, next) orelse break; | ||||
|                 if (try parser.nodeType(next.?) != .element) { | ||||
|                     if (slot_name == null and !element_only) { | ||||
|                         // default slot (with no name), takes everything | ||||
|                         try arr.append(page.call_arena, try Node.toInterface(next.?)); | ||||
|                     } | ||||
|                     continue; | ||||
|                 } | ||||
|                 const el: *parser.Element = @ptrCast(@alignCast(next.?)); | ||||
|                 const element_slot = try parser.elementGetAttribute(el, "slot"); | ||||
|  | ||||
|                 if (nullableStringsAreEqual(slot_name, element_slot)) { | ||||
|                     // either they're the same string or they are both null | ||||
|         var arr: std.ArrayList(NodeUnion) = .empty; | ||||
|         const w = @import("../dom/walker.zig").WalkerChildren{}; | ||||
|         var next: ?*parser.Node = null; | ||||
|         while (true) { | ||||
|             next = try w.get_next(root, next) orelse break; | ||||
|             if (try parser.nodeType(next.?) != .element) { | ||||
|                 if (slot_name == null) { | ||||
|                     // default slot (with no name), takes everything | ||||
|                     try arr.append(page.call_arena, try Node.toInterface(next.?)); | ||||
|                     continue; | ||||
|                 } | ||||
|                 continue; | ||||
|             } | ||||
|             if (arr.items.len > 0) { | ||||
|                 return arr.items; | ||||
|             } | ||||
|             const el: *parser.Element = @ptrCast(@alignCast(next.?)); | ||||
|             const element_slot = try parser.elementGetAttribute(el, "slot"); | ||||
|  | ||||
|             if (!opts.flatten) { | ||||
|                 return &.{}; | ||||
|             if (nullableStringsAreEqual(slot_name, element_slot)) { | ||||
|                 // either they're the same string or they are both null | ||||
|                 try arr.append(page.call_arena, try Node.toInterface(next.?)); | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Since, we have no explicitly assigned nodes and flatten == false, | ||||
|         // we'll collect the children of the slot - the defaults. | ||||
|         { | ||||
|             const nl = try parser.nodeGetChildNodes(node); | ||||
|             const len = try parser.nodeListLength(nl); | ||||
|             if (len == 0) { | ||||
|                 return &.{}; | ||||
|             } | ||||
|  | ||||
|             var assigned = try page.call_arena.alloc(NodeUnion, len); | ||||
|             var i: usize = 0; | ||||
|             while (true) : (i += 1) { | ||||
|                 const child = try parser.nodeListItem(nl, @intCast(i)) orelse break; | ||||
|                 if (!element_only or try parser.nodeType(child) == .element) { | ||||
|                     assigned[i] = try Node.toInterface(child); | ||||
|                 } | ||||
|             } | ||||
|             return assigned[0..i]; | ||||
|         } | ||||
|         return if (arr.items.len == 0) null else arr.items; | ||||
|     } | ||||
|  | ||||
|     fn nullableStringsAreEqual(a: ?[]const u8, b: ?[]const u8) bool { | ||||
| @@ -1345,6 +1329,39 @@ test "Browser: HTML.HtmlScriptElement" { | ||||
|     try testing.htmlRunner("html/script/inline_defer.html"); | ||||
| } | ||||
|  | ||||
| test "Browser: HTML.HtmlSlotElement" { | ||||
|     try testing.htmlRunner("html/slot.html"); | ||||
| test "Browser: HTML.HTMLSlotElement" { | ||||
|     try testing.htmlRunner("html/html_slot_element.html"); | ||||
| } | ||||
|  | ||||
| const Check = struct { | ||||
|     input: []const u8, | ||||
|     expected: ?[]const u8 = null, // Needed when input != expected | ||||
| }; | ||||
| const bool_valids = [_]Check{ | ||||
|     .{ .input = "true" }, | ||||
|     .{ .input = "''", .expected = "false" }, | ||||
|     .{ .input = "13.5", .expected = "true" }, | ||||
| }; | ||||
| const str_valids = [_]Check{ | ||||
|     .{ .input = "'foo'", .expected = "foo" }, | ||||
|     .{ .input = "5", .expected = "5" }, | ||||
|     .{ .input = "''", .expected = "" }, | ||||
|     .{ .input = "document", .expected = "[object HTMLDocument]" }, | ||||
| }; | ||||
|  | ||||
| // .{ "elem.type = '5'", "5" }, | ||||
| // .{ "elem.type", "text" }, | ||||
| fn testProperty( | ||||
|     arena: std.mem.Allocator, | ||||
|     runner: *testing.JsRunner, | ||||
|     elem_dot_prop: []const u8, | ||||
|     always: ?[]const u8, // Ignores checks' expected if set | ||||
|     checks: []const Check, | ||||
| ) !void { | ||||
|     for (checks) |check| { | ||||
|         try runner.testCases(&.{ | ||||
|             .{ try std.mem.concat(arena, u8, &.{ elem_dot_prop, " = ", check.input }), null }, | ||||
|             .{ elem_dot_prop, always orelse check.expected orelse check.input }, | ||||
|         }, .{}); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										66
									
								
								src/tests/html/html_slot_element.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/tests/html/html_slot_element.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| <script src="../testing.js"></script> | ||||
| <script> | ||||
|   class LightPanda extends HTMLElement { | ||||
|     constructor() { | ||||
|       super(); | ||||
|     } | ||||
|  | ||||
|     connectedCallback() { | ||||
|       const shadow = this.attachShadow({ mode: "open" }); | ||||
|  | ||||
|       const slot1 = document.createElement('slot'); | ||||
|       slot1.name = 'slot-1'; | ||||
|       shadow.appendChild(slot1); | ||||
|  | ||||
|       switch (this.getAttribute('mode')) { | ||||
|         case '1': | ||||
|           slot1.innerHTML = 'hello'; | ||||
|           break; | ||||
|         case '2': | ||||
|           const slot2 = document.createElement('slot'); | ||||
|           shadow.appendChild(slot2); | ||||
|           break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   window.customElements.define("lp-test", LightPanda); | ||||
| </script> | ||||
|  | ||||
| <lp-test id=lp1 mode=1></lp-test> | ||||
| <lp-test id=lp2 mode=0></lp-test> | ||||
| <lp-test id=lp3 mode=0>default</lp-test> | ||||
| <lp-test id=lp4 mode=1><p slot=other>default</p></lp-test> | ||||
| <lp-test id=lp5 mode=1><p slot=slot-1>default</p> xx <b slot=slot-1>other</b></lp-test> | ||||
| <lp-test id=lp6 mode=2>More <p slot=slot-1>default2</p> <span>!!</span></lp-test> | ||||
|  | ||||
| <script id=HTMLSlotElement> | ||||
|   function assertNodes(expected, actual) { | ||||
|     actual = actual.map((n) => n.id || n.textContent) | ||||
|     testing.expectEqual(expected, actual); | ||||
|   } | ||||
|  | ||||
|   for (let idx of [1, 2, 3, 4]) { | ||||
|     const lp = $(`#lp${idx}`); | ||||
|     const slot = lp.shadowRoot.querySelector('slot'); | ||||
|  | ||||
|     assertNodes([], slot.assignedNodes()); | ||||
|     assertNodes([], slot.assignedNodes({})); | ||||
|     assertNodes([], slot.assignedNodes({flatten: false})); | ||||
|     if (lp.getAttribute('mode') === '1') { | ||||
|       assertNodes(['hello'], slot.assignedNodes({flatten: true})); | ||||
|     } else { | ||||
|       assertNodes([], slot.assignedNodes({flatten: true})); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const lp5 = $('#lp5'); | ||||
|   const s5 = lp5.shadowRoot.querySelector('slot'); | ||||
|   assertNodes(['default', 'other'], s5.assignedNodes()); | ||||
|  | ||||
|   const lp6 = $('#lp6'); | ||||
|   const s6 = lp6.shadowRoot.querySelectorAll('slot'); | ||||
|   assertNodes(['default2'], s6[0].assignedNodes({})); | ||||
|   assertNodes(['default2'], s6[0].assignedNodes({flatten: true})); | ||||
|   assertNodes(['More ', ' ', '!!'], s6[1].assignedNodes({})); | ||||
|   assertNodes(['More ', ' ', '!!'], s6[1].assignedNodes({flatten: true})); | ||||
| </script> | ||||
		Reference in New Issue
	
	Block a user