Skip to content

Commit 23dafb0

Browse files
Merge pull request #216 from lightpanda-io/usrctx
Add user context
2 parents c1b73df + 9ac46ea commit 23dafb0

19 files changed

+240
-42
lines changed

src/apiweb.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ pub const Interfaces = generate.Tuple(.{
3939
Storage.Interfaces,
4040
URL.Interfaces,
4141
});
42+
43+
pub const UserContext = @import("user_context.zig").UserContext;

src/browser/browser.zig

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const storage = @import("../storage/storage.zig");
3939

4040
const FetchResult = std.http.Client.FetchResult;
4141

42+
const UserContext = @import("../user_context.zig").UserContext;
43+
const HttpClient = @import("../async/Client.zig");
44+
4245
const log = std.log.scoped(.browser);
4346

4447
// Browser is an instance of the browser.
@@ -92,6 +95,7 @@ pub const Session = struct {
9295
// TODO move the shed to the browser?
9396
storageShed: storage.Shed,
9497
page: ?*Page = null,
98+
httpClient: HttpClient,
9599

96100
jstypes: [Types.len]usize = undefined,
97101

@@ -105,9 +109,11 @@ pub const Session = struct {
105109
.loader = Loader.init(alloc),
106110
.loop = try Loop.init(alloc),
107111
.storageShed = storage.Shed.init(alloc),
112+
.httpClient = undefined,
108113
};
109114

110-
self.env = try Env.init(self.arena.allocator(), &self.loop);
115+
self.env = try Env.init(self.arena.allocator(), &self.loop, null);
116+
self.httpClient = .{ .allocator = alloc, .loop = &self.loop };
111117
try self.env.load(&self.jstypes);
112118

113119
return self;
@@ -122,6 +128,7 @@ pub const Session = struct {
122128
self.loader.deinit();
123129
self.loop.deinit();
124130
self.storageShed.deinit();
131+
self.httpClient.deinit();
125132
self.alloc.destroy(self);
126133
}
127134

@@ -289,6 +296,12 @@ pub const Page = struct {
289296
log.debug("start js env", .{});
290297
try self.session.env.start(alloc);
291298

299+
// replace the user context document with the new one.
300+
try self.session.env.setUserContext(.{
301+
.document = html_doc,
302+
.httpClient = &self.session.httpClient,
303+
});
304+
292305
// add global objects
293306
log.debug("setup global env", .{});
294307
try self.session.env.bindGlobal(&self.session.window);

src/dom/comment.zig

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,45 @@
1515
//
1616
// You should have received a copy of the GNU Affero General Public License
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
const std = @import("std");
1819

1920
const parser = @import("../netsurf.zig");
2021

22+
const jsruntime = @import("jsruntime");
23+
const Case = jsruntime.test_utils.Case;
24+
const checkCases = jsruntime.test_utils.checkCases;
25+
2126
const CharacterData = @import("character_data.zig").CharacterData;
2227

28+
const UserContext = @import("../user_context.zig").UserContext;
29+
30+
// https://dom.spec.whatwg.org/#interface-comment
2331
pub const Comment = struct {
2432
pub const Self = parser.Comment;
2533
pub const prototype = *CharacterData;
2634
pub const mem_guarantied = true;
35+
36+
pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Comment {
37+
return parser.documentCreateComment(
38+
parser.documentHTMLToDocument(userctx.document),
39+
data orelse "",
40+
);
41+
}
2742
};
43+
44+
// Tests
45+
// -----
46+
47+
pub fn testExecFn(
48+
_: std.mem.Allocator,
49+
js_env: *jsruntime.Env,
50+
) anyerror!void {
51+
var constructor = [_]Case{
52+
.{ .src = "let comment = new Comment('foo')", .ex = "undefined" },
53+
.{ .src = "comment.data", .ex = "foo" },
54+
55+
.{ .src = "let emptycomment = new Comment()", .ex = "undefined" },
56+
.{ .src = "emptycomment.data", .ex = "" },
57+
};
58+
try checkCases(js_env, &constructor);
59+
}

src/dom/document.zig

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,26 @@ const DocumentType = @import("document_type.zig").DocumentType;
4040
const DocumentFragment = @import("document_fragment.zig").DocumentFragment;
4141
const DOMImplementation = @import("implementation.zig").DOMImplementation;
4242

43+
const UserContext = @import("../user_context.zig").UserContext;
44+
4345
// WEB IDL https://dom.spec.whatwg.org/#document
4446
pub const Document = struct {
4547
pub const Self = parser.Document;
4648
pub const prototype = *Node;
4749
pub const mem_guarantied = true;
4850

49-
pub fn constructor() !*parser.Document {
50-
return try parser.domImplementationCreateHTMLDocument(null);
51+
pub fn constructor(userctx: UserContext) !*parser.DocumentHTML {
52+
const doc = try parser.documentCreateDocument(
53+
try parser.documentHTMLGetTitle(userctx.document),
54+
);
55+
56+
// we have to work w/ document instead of html document.
57+
const ddoc = parser.documentHTMLToDocument(doc);
58+
const ccur = parser.documentHTMLToDocument(userctx.document);
59+
try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur));
60+
try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur));
61+
62+
return doc;
5163
}
5264

5365
// JS funcs
@@ -262,6 +274,13 @@ pub fn testExecFn(
262274
.{ .src = "newdoc.children.length", .ex = "0" },
263275
.{ .src = "newdoc.getElementsByTagName('*').length", .ex = "0" },
264276
.{ .src = "newdoc.getElementsByTagName('*').item(0)", .ex = "null" },
277+
.{ .src = "newdoc.inputEncoding === document.inputEncoding", .ex = "true" },
278+
.{ .src = "newdoc.documentURI === document.documentURI", .ex = "true" },
279+
.{ .src = "newdoc.URL === document.URL", .ex = "true" },
280+
.{ .src = "newdoc.compatMode === document.compatMode", .ex = "true" },
281+
.{ .src = "newdoc.characterSet === document.characterSet", .ex = "true" },
282+
.{ .src = "newdoc.charset === document.charset", .ex = "true" },
283+
.{ .src = "newdoc.contentType === document.contentType", .ex = "true" },
265284
};
266285
try checkCases(js_env, &constructor);
267286

src/dom/document_fragment.zig

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,37 @@ const std = @import("std");
2020

2121
const parser = @import("../netsurf.zig");
2222

23+
const jsruntime = @import("jsruntime");
24+
const Case = jsruntime.test_utils.Case;
25+
const checkCases = jsruntime.test_utils.checkCases;
26+
2327
const Node = @import("node.zig").Node;
2428

29+
const UserContext = @import("../user_context.zig").UserContext;
30+
2531
// WEB IDL https://dom.spec.whatwg.org/#documentfragment
2632
pub const DocumentFragment = struct {
2733
pub const Self = parser.DocumentFragment;
2834
pub const prototype = *Node;
2935
pub const mem_guarantied = true;
3036

31-
// TODO add constructor, but I need to associate the new DocumentFragment
32-
// with the current document global object...
33-
// > The new DocumentFragment() constructor steps are to set this’s node
34-
// > document to current global object’s associated Document.
35-
// https://dom.spec.whatwg.org/#dom-documentfragment-documentfragment
36-
pub fn constructor() !*parser.DocumentFragment {
37-
return error.NotImplemented;
37+
pub fn constructor(userctx: UserContext) !*parser.DocumentFragment {
38+
return parser.documentCreateDocumentFragment(
39+
parser.documentHTMLToDocument(userctx.document),
40+
);
3841
}
3942
};
43+
44+
// Tests
45+
// -----
46+
47+
pub fn testExecFn(
48+
_: std.mem.Allocator,
49+
js_env: *jsruntime.Env,
50+
) anyerror!void {
51+
var constructor = [_]Case{
52+
.{ .src = "const dc = new DocumentFragment()", .ex = "undefined" },
53+
.{ .src = "dc.constructor.name", .ex = "DocumentFragment" },
54+
};
55+
try checkCases(js_env, &constructor);
56+
}

src/dom/implementation.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub const DOMImplementation = struct {
7575
return try parser.domImplementationCreateDocument(cnamespace, cqname, doctype);
7676
}
7777

78-
pub fn _createHTMLDocument(_: *DOMImplementation, title: ?[]const u8) !*parser.Document {
78+
pub fn _createHTMLDocument(_: *DOMImplementation, title: ?[]const u8) !*parser.DocumentHTML {
7979
return try parser.domImplementationCreateHTMLDocument(title);
8080
}
8181

@@ -95,7 +95,8 @@ pub fn testExecFn(
9595
) anyerror!void {
9696
var getImplementation = [_]Case{
9797
.{ .src = "let impl = document.implementation", .ex = "undefined" },
98-
.{ .src = "impl.createHTMLDocument();", .ex = "[object Document]" },
98+
.{ .src = "impl.createHTMLDocument();", .ex = "[object HTMLDocument]" },
99+
.{ .src = "impl.createHTMLDocument('foo');", .ex = "[object HTMLDocument]" },
99100
.{ .src = "impl.createDocument(null, 'foo');", .ex = "[object Document]" },
100101
.{ .src = "impl.createDocumentType('foo', 'bar', 'baz')", .ex = "[object DocumentType]" },
101102
.{ .src = "impl.hasFeature()", .ex = "true" },

src/dom/node.zig

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,30 @@ pub const Node = struct {
277277
return try Node.toInterface(res);
278278
}
279279

280+
// Check if the hierarchy node tree constraints are respected.
281+
// For now, it checks only if new nodes are not self.
282+
// TODO implements the others contraints.
283+
// see https://dom.spec.whatwg.org/#concept-node-tree
284+
pub fn hierarchy(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !bool {
285+
if (nodes == null) return true;
286+
if (nodes.?.slice.len == 0) return true;
287+
288+
for (nodes.?.slice) |node| if (self == node) return false;
289+
290+
return true;
291+
}
292+
280293
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
281294
// function must accept either node or string.
282295
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
283296
pub fn prepend(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
284297
if (nodes == null) return;
285298
if (nodes.?.slice.len == 0) return;
286-
const first = try parser.nodeFirstChild(self);
287299

300+
// check hierarchy
301+
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
302+
303+
const first = try parser.nodeFirstChild(self);
288304
if (first == null) {
289305
for (nodes.?.slice) |node| {
290306
_ = try parser.nodeAppendChild(self, node);
@@ -303,6 +319,10 @@ pub const Node = struct {
303319
pub fn append(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
304320
if (nodes == null) return;
305321
if (nodes.?.slice.len == 0) return;
322+
323+
// check hierarchy
324+
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
325+
306326
for (nodes.?.slice) |node| {
307327
_ = try parser.nodeAppendChild(self, node);
308328
}
@@ -312,12 +332,15 @@ pub const Node = struct {
312332
// function must accept either node or string.
313333
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
314334
pub fn replaceChildren(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
315-
// remove existing children
316-
try removeChildren(self);
317-
318335
if (nodes == null) return;
319336
if (nodes.?.slice.len == 0) return;
320337

338+
// check hierarchy
339+
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
340+
341+
// remove existing children
342+
try removeChildren(self);
343+
321344
// add new children
322345
for (nodes.?.slice) |node| {
323346
_ = try parser.nodeAppendChild(self, node);

src/dom/text.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const parser = @import("../netsurf.zig");
2828
const CharacterData = @import("character_data.zig").CharacterData;
2929
const CDATASection = @import("cdata_section.zig").CDATASection;
3030

31+
const UserContext = @import("../user_context.zig").UserContext;
32+
3133
// Text interfaces
3234
pub const Interfaces = generate.Tuple(.{
3335
CDATASection,
@@ -38,6 +40,13 @@ pub const Text = struct {
3840
pub const prototype = *CharacterData;
3941
pub const mem_guarantied = true;
4042

43+
pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Text {
44+
return parser.documentCreateTextNode(
45+
parser.documentHTMLToDocument(userctx.document),
46+
data orelse "",
47+
);
48+
}
49+
4150
// JS funcs
4251
// --------
4352

@@ -62,6 +71,15 @@ pub fn testExecFn(
6271
_: std.mem.Allocator,
6372
js_env: *jsruntime.Env,
6473
) anyerror!void {
74+
var constructor = [_]Case{
75+
.{ .src = "let t = new Text('foo')", .ex = "undefined" },
76+
.{ .src = "t.data", .ex = "foo" },
77+
78+
.{ .src = "let emptyt = new Text()", .ex = "undefined" },
79+
.{ .src = "emptyt.data", .ex = "" },
80+
};
81+
try checkCases(js_env, &constructor);
82+
6583
var get_whole_text = [_]Case{
6684
.{ .src = "let text = document.getElementById('link').firstChild", .ex = "undefined" },
6785
.{ .src = "text.wholeText === 'OK'", .ex = "true" },

src/main.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const apiweb = @import("apiweb.zig");
2525
const Window = @import("html/window.zig").Window;
2626

2727
pub const Types = jsruntime.reflect(apiweb.Interfaces);
28+
pub const UserContext = apiweb.UserContext;
2829

2930
const socket_path = "/tmp/browsercore-server.sock";
3031

@@ -103,5 +104,5 @@ pub fn main() !void {
103104
try server.listen(addr);
104105
std.debug.print("Listening on: {s}...\n", .{socket_path});
105106

106-
try jsruntime.loadEnv(&arena, execJS);
107+
try jsruntime.loadEnv(&arena, null, execJS);
107108
}

src/main_get.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const jsruntime = @import("jsruntime");
2323
const apiweb = @import("apiweb.zig");
2424

2525
pub const Types = jsruntime.reflect(apiweb.Interfaces);
26+
pub const UserContext = apiweb.UserContext;
2627

2728
pub const std_options = struct {
2829
pub const log_level = .debug;

0 commit comments

Comments
 (0)