Skip to content

Commit 5ae1190

Browse files
committed
HTMLDocument
1 parent fb9cce7 commit 5ae1190

File tree

10 files changed

+216
-97
lines changed

10 files changed

+216
-97
lines changed

src/browser/Factory.zig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const Page = @import("Page.zig");
1010
const Node = @import("webapi/Node.zig");
1111
const Event = @import("webapi/Event.zig");
1212
const Element = @import("webapi/Element.zig");
13+
const Document = @import("webapi/Document.zig");
1314
const EventTarget = @import("webapi/EventTarget.zig");
1415
const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.zig");
1516

@@ -98,6 +99,16 @@ pub fn node(self: *Factory, child: anytype) !*@TypeOf(child) {
9899
return child_ptr;
99100
}
100101

102+
pub fn document(self: *Factory, child: anytype) !*@TypeOf(child) {
103+
const child_ptr = try self.createT(@TypeOf(child));
104+
child_ptr.* = child;
105+
child_ptr._proto = try self.node(Document{
106+
._proto = undefined,
107+
._type = unionInit(Document.Type, child_ptr),
108+
});
109+
return child_ptr;
110+
}
111+
101112
pub fn element(self: *Factory, child: anytype) !*@TypeOf(child) {
102113
const child_ptr = try self.createT(@TypeOf(child));
103114
child_ptr.* = child;

src/browser/Page.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
136136
self.version = 0;
137137
self.url = "about/blank";
138138

139-
self.document = try self._factory.node(Document{ ._proto = undefined });
139+
self.document = (try self._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument();
140140

141141
const storage_bucket = try self._factory.create(storage.Bucket{});
142142
self.window = try self._factory.eventTarget(Window{

src/browser/js/bridge.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ pub const JsApis = flattenTypes(&.{
417417
@import("../webapi/css/CSSStyleDeclaration.zig"),
418418
@import("../webapi/css/CSSStyleProperties.zig"),
419419
@import("../webapi/Document.zig"),
420+
@import("../webapi/HTMLDocument.zig"),
420421
@import("../webapi/DocumentFragment.zig"),
421422
@import("../webapi/DOMException.zig"),
422423
@import("../webapi/DOMTreeWalker.zig"),

src/browser/tests/page/meta.html

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,40 @@
11
<!DOCTYPE html>
22
<script src="../testing.js"></script>
33
<script id=meta>
4-
testing.expectEqual('Document', document.constructor.name);
4+
testing.expectEqual('HTMLDocument', document.constructor.name);
5+
testing.expectEqual('Document', new Document().constructor.name);
56
testing.expectEqual('[object Document]', new Document().toString());
67
testing.expectEqual('Window', window.constructor.name);
78

89
// Important test. new Document() returns a Node, but getElementById
910
// exists on the Document. So this is a simple way to make sure that
1011
// the returned Zig type is associated with the correct JS class.
1112
testing.expectEqual(null, new Document().getElementById('x'));
13+
14+
// HTMLDocument (global document) should have HTML-specific properties
15+
testing.expectEqual('object', typeof document.head);
16+
testing.expectEqual('object', typeof document.body);
17+
testing.expectEqual('string', typeof document.title);
18+
testing.expectEqual('object', typeof document.images);
19+
testing.expectEqual('object', typeof document.scripts);
20+
testing.expectEqual('object', typeof document.links);
21+
testing.expectEqual('object', typeof document.forms);
22+
testing.expectEqual('object', typeof document.location);
23+
24+
// Plain Document should NOT have HTML-specific properties
25+
const plainDoc = new Document();
26+
testing.expectEqual('undefined', typeof plainDoc.head);
27+
testing.expectEqual('undefined', typeof plainDoc.body);
28+
testing.expectEqual('undefined', typeof plainDoc.title);
29+
testing.expectEqual('undefined', typeof plainDoc.images);
30+
testing.expectEqual('undefined', typeof plainDoc.scripts);
31+
testing.expectEqual('undefined', typeof plainDoc.links);
32+
testing.expectEqual('undefined', typeof plainDoc.forms);
33+
testing.expectEqual('undefined', typeof plainDoc.location);
34+
35+
// Both should have common Document properties
36+
testing.expectEqual('string', typeof document.URL);
37+
testing.expectEqual('string', typeof plainDoc.URL);
38+
testing.expectEqual('string', typeof document.readyState);
39+
testing.expectEqual('string', typeof plainDoc.readyState);
1240
</script>

src/browser/tests/page/module.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!DOCTYPE html>
22
<script src="../testing.js"></script>
33

4-
<script id=meta type=module>
4+
<!-- <script id=meta type=module>
55
testing.expectEqual('/src/browser/tests/page/module.html', new URL(import.meta.url).pathname)
66
</script>
77
@@ -33,7 +33,7 @@
3333
import { increment, getCount } from "./modules/shared.js";
3434
testing.expectEqual(2, increment());
3535
testing.expectEqual(2, getCount());
36-
</script>
36+
</script> -->
3737

3838
<script id=circular-imports type=module>
3939
import { aValue, getFromB } from "./modules/circular-a.js";
@@ -44,7 +44,7 @@
4444
testing.expectEqual('a', getFromA());
4545
</script>
4646

47-
<script id=basic-async type=module>
47+
<!-- <script id=basic-async type=module>
4848
const m = await import("./mod1.js");
4949
testing.expectEqual('value-1', m.val1);
5050
</script>
@@ -145,7 +145,7 @@
145145
testing.expectEqual('from-base', m.importedValue);
146146
testing.expectEqual('local', m.localValue);
147147
})();
148-
</script>
148+
</script> -->
149149

150150
<!-- TODO: Error handling tests need dynamic import support
151151
<script id=import-syntax-error type=module>

src/browser/webapi/Document.zig

Lines changed: 32 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,38 @@ const NodeFilter = @import("NodeFilter.zig");
1313
const DOMTreeWalker = @import("DOMTreeWalker.zig");
1414
const DOMNodeIterator = @import("DOMNodeIterator.zig");
1515

16+
pub const HTMLDocument = @import("HTMLDocument.zig");
17+
1618
const Document = @This();
1719

20+
_type: Type,
1821
_proto: *Node,
1922
_location: ?*Location = null,
2023
_ready_state: ReadyState = .loading,
2124
_current_script: ?*Element.Html.Script = null,
2225
_elements_by_id: std.StringHashMapUnmanaged(*Element) = .empty,
2326

27+
pub const Type = union(enum) {
28+
generic,
29+
html: *HTMLDocument,
30+
};
31+
32+
pub fn is(self: *Document, comptime T: type) ?*T {
33+
switch (self._type) {
34+
.html => |html| {
35+
if (T == HTMLDocument) {
36+
return html;
37+
}
38+
},
39+
.generic => {},
40+
}
41+
return null;
42+
}
43+
44+
pub fn as(self: *Document, comptime T: type) *T {
45+
return self.is(T).?;
46+
}
47+
2448
pub fn asNode(self: *Document) *Node {
2549
return self._proto;
2650
}
@@ -33,14 +57,6 @@ pub fn getURL(_: *const Document, page: *const Page) [:0]const u8 {
3357
return page.url;
3458
}
3559

36-
pub fn getReadyState(self: *const Document) []const u8 {
37-
return @tagName(self._ready_state);
38-
}
39-
40-
pub fn getCurrentScript(self: *const Document) ?*Element.Html.Script {
41-
return self._current_script;
42-
}
43-
4460
pub fn createElement(_: *const Document, name: []const u8, page: *Page) !*Element {
4561
const node = try page.createElement(null, name, null);
4662
return node.as(Element);
@@ -70,13 +86,13 @@ pub fn getElementsByTagName(self: *Document, tag_name: []const u8, page: *Page)
7086
if (Node.Element.Tag.parseForMatch(lower)) |known| {
7187
// optimized for known tag names, comparis
7288
return .{
73-
.tag = try collections.NodeLive(.tag).init(null, self.asNode(), known, page),
89+
.tag = collections.NodeLive(.tag).init(null, self.asNode(), known, page),
7490
};
7591
}
7692

7793
const arena = page.arena;
7894
const filter = try String.init(arena, lower, .{});
79-
return .{ .tag_name = try collections.NodeLive(.tag_name).init(arena, self.asNode(), filter, page) };
95+
return .{ .tag_name = collections.NodeLive(.tag_name).init(arena, self.asNode(), filter, page) };
8096
}
8197

8298
pub fn getElementsByClassName(self: *Document, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
@@ -96,46 +112,6 @@ pub fn getDocumentElement(self: *Document) ?*Element {
96112
return null;
97113
}
98114

99-
pub fn getImages(self: *Document, page: *Page) !collections.NodeLive(.tag) {
100-
return collections.NodeLive(.tag).init(null, self.asNode(), .img, page);
101-
}
102-
103-
pub fn getScripts(self: *Document, page: *Page) !collections.NodeLive(.tag) {
104-
return collections.NodeLive(.tag).init(null, self.asNode(), .script, page);
105-
}
106-
107-
pub fn getForms(self: *Document, page: *Page) !collections.NodeLive(.tag) {
108-
return collections.NodeLive(.tag).init(null, self.asNode(), .form, page);
109-
}
110-
111-
pub fn getLinks(self: *Document, page: *Page) !collections.NodeLive(.tag) {
112-
return collections.NodeLive(.tag).init(null, self.asNode(), .anchor, page);
113-
}
114-
115-
pub fn getHead(self: *Document) ?*Element.Html.Head {
116-
const doc_el = self.getDocumentElement() orelse return null;
117-
var child = doc_el.asNode().firstChild();
118-
while (child) |node| {
119-
if (node.is(Element.Html.Head)) |head| {
120-
return head;
121-
}
122-
child = node.nextSibling();
123-
}
124-
return null;
125-
}
126-
127-
pub fn getBody(self: *Document) ?*Element.Html.Body {
128-
const doc_el = self.getDocumentElement() orelse return null;
129-
var child = doc_el.asNode().firstChild();
130-
while (child) |node| {
131-
if (node.is(Element.Html.Body)) |body| {
132-
return body;
133-
}
134-
child = node.nextSibling();
135-
}
136-
return null;
137-
}
138-
139115
pub fn querySelector(self: *Document, input: []const u8, page: *Page) !?*Element {
140116
return Selector.querySelector(self.asNode(), input, page);
141117
}
@@ -160,43 +136,18 @@ pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node
160136
return page.createTextNode(data);
161137
}
162138

163-
pub fn getLocation(self: *const Document) ?*Location {
164-
return self._location;
165-
}
166-
167-
// @ZIGDOM what_to_show tristate (null vs undefined vs value)
168139
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
169140
const show = what_to_show orelse NodeFilter.SHOW_ALL;
170141
return DOMTreeWalker.init(root, show, filter, page);
171142
}
172143

173-
// @ZIGDOM what_to_show tristate (null vs undefined vs value)
174144
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
175145
const show = what_to_show orelse NodeFilter.SHOW_ALL;
176146
return DOMNodeIterator.init(root, show, filter, page);
177147
}
178148

179-
pub fn getTitle(self: *Document, page: *Page) ![]const u8 {
180-
const head = self.getHead() orelse return "";
181-
var it = head.asNode().childrenIterator();
182-
while (it.next()) |node| {
183-
if (node.is(Element.Html.Title)) |title| {
184-
var buf = std.Io.Writer.Allocating.init(page.call_arena);
185-
try title.asElement().getInnerText(&buf.writer);
186-
return buf.written();
187-
}
188-
}
189-
return "";
190-
}
191-
192-
pub fn setTitle(self: *Document, title: []const u8, page: *Page) !void {
193-
const head = self.getHead() orelse return;
194-
var it = head.asNode().childrenIterator();
195-
while (it.next()) |node| {
196-
if (node.is(Element.Html.Title)) |title_element| {
197-
return title_element.asElement().replaceChildren(&.{.{ .text = title }}, page);
198-
}
199-
}
149+
pub fn getReadyState(self: *const Document) []const u8 {
150+
return @tagName(self._ready_state);
200151
}
201152

202153
const ReadyState = enum {
@@ -216,20 +167,14 @@ pub const JsApi = struct {
216167

217168
pub const constructor = bridge.constructor(_constructor, .{});
218169
fn _constructor(page: *Page) !*Document {
219-
return page._factory.node(Document{ ._proto = undefined });
170+
return page._factory.node(Document{
171+
._proto = undefined,
172+
._type = .generic,
173+
});
220174
}
221175

222176
pub const URL = bridge.accessor(Document.getURL, null, .{});
223-
pub const currentScript = bridge.accessor(Document.getCurrentScript, null, .{});
224-
pub const head = bridge.accessor(Document.getHead, null, .{});
225-
pub const body = bridge.accessor(Document.getBody, null, .{});
226-
pub const title = bridge.accessor(Document.getTitle, Document.setTitle, .{});
227177
pub const documentElement = bridge.accessor(Document.getDocumentElement, null, .{});
228-
pub const images = bridge.accessor(Document.getImages, null, .{});
229-
pub const scripts = bridge.accessor(Document.getScripts, null, .{});
230-
pub const links = bridge.accessor(Document.getLinks, null, .{});
231-
pub const forms = bridge.accessor(Document.getForms, null, .{});
232-
pub const location = bridge.accessor(Document.getLocation, null, .{ .cache = "location" });
233178
pub const readyState = bridge.accessor(Document.getReadyState, null, .{});
234179

235180
pub const createElement = bridge.function(Document.createElement, .{});

src/browser/webapi/Element.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -456,15 +456,15 @@ pub fn getElementsByTagName(self: *Element, tag_name: []const u8, page: *Page) !
456456

457457
const lower = std.ascii.lowerString(&page.buf, tag_name);
458458
if (Tag.parseForMatch(lower)) |known| {
459-
// optimized for known tag names, comparis
459+
// optimized for known tag names
460460
return .{
461-
.tag = try collections.NodeLive(.tag).init(null, self.asNode(), known, page),
461+
.tag = collections.NodeLive(.tag).init(null, self.asNode(), known, page),
462462
};
463463
}
464464

465465
const arena = page.arena;
466466
const filter = try String.init(arena, lower, .{});
467-
return .{ .tag_name = try collections.NodeLive(.tag_name).init(arena, self.asNode(), filter, page) };
467+
return .{ .tag_name = collections.NodeLive(.tag_name).init(arena, self.asNode(), filter, page) };
468468
}
469469

470470
pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {

0 commit comments

Comments
 (0)