+ +
+And
+ +diff --git a/.gitignore b/.gitignore index f78e00fb..a5e9840f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ zig-cache zig-out /vendor/lexbor/ +/vendor/netsurf/build/ +/vendor/netsurf/lib/ +/vendor/netsurf/include/ +/vendor/libiconv/ diff --git a/.gitmodules b/.gitmodules index 26181de6..622ee626 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,18 @@ [submodule "vendor/lexbor-src"] path = vendor/lexbor-src url = https://github.com/lexbor/lexbor +[submodule "vendor/netsurf/libwapcaplet"] + path = vendor/netsurf/libwapcaplet + url = https://source.netsurf-browser.org/libwapcaplet.git +[submodule "vendor/netsurf/libparserutils"] + path = vendor/netsurf/libparserutils + url = https://source.netsurf-browser.org/libparserutils.git +[submodule "vendor/netsurf/libdom"] + path = vendor/netsurf/libdom + url = https://source.netsurf-browser.org/libdom.git +[submodule "vendor/netsurf/share/netsurf-buildsystem"] + path = vendor/netsurf/share/netsurf-buildsystem + url = https://source.netsurf-browser.org/buildsystem.git +[submodule "vendor/netsurf/libhubbub"] + path = vendor/netsurf/libhubbub + url = https://source.netsurf-browser.org/libhubbub.git diff --git a/Makefile b/Makefile index fc05b195..0b1cba85 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ # --------- ZIG := zig +BC := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) # Infos # ----- @@ -54,16 +55,85 @@ test: # Install and build required dependencies commands # ------------ .PHONY: install-submodule -.PHONY: install-lexbor install-jsruntime install-jsruntime-dev +.PHONY: install-lexbor install-jsruntime install-jsruntime-dev install-libiconv +.PHONY: install-netsurf clean-netsurf test-netsurf .PHONY: install-dev install ## Install and build dependencies for release -install: install-submodule install-lexbor install-jsruntime +install: install-submodule install-lexbor install-jsruntime install-netsurf ## Install and build dependencies for dev -install-dev: install-submodule install-lexbor install-jsruntime-dev +install-dev: install-submodule install-lexbor install-jsruntime-dev install-netsurf + +BC_NS := $(BC)vendor/netsurf +UNAME_S := $(shell uname -s) +ICONV := $(BC)vendor/libiconv +# TODO: add Linux iconv path (I guess it depends on the distro) +# TODO: this way of linking libiconv is not ideal. We should have a more generic way +# and stick to a specif version. Maybe build from source. Anyway not now. +install-netsurf: install-libiconv + @printf "\e[36mInstalling NetSurf...\e[0m\n" && \ + ls $(ICONV) 1> /dev/null || (printf "\e[33mERROR: you need to install libiconv in your system (on MacOS on with Homebrew)\e[0m\n"; exit 1;) && \ + export PREFIX=$(BC_NS) && \ + export OPTLDFLAGS="-L$(ICONV)/lib" && \ + export OPTCFLAGS="-I$(ICONV)/include" && \ + printf "\e[33mInstalling libwapcaplet...\e[0m\n" && \ + cd vendor/netsurf/libwapcaplet && \ + BUILDDIR=$(BC_NS)/build/libwapcaplet make install && \ + cd ../libparserutils && \ + printf "\e[33mInstalling libparserutils...\e[0m\n" && \ + BUILDDIR=$(BC_NS)/build/libparserutils make install && \ + cd ../libhubbub && \ + printf "\e[33mInstalling libhubbub...\e[0m\n" && \ + BUILDDIR=$(BC_NS)/build/libhubbub make install && \ + rm src/treebuilder/autogenerated-element-type.c && \ + cd ../libdom && \ + printf "\e[33mInstalling libdom...\e[0m\n" && \ + BUILDDIR=$(BC_NS)/build/libdom make install && \ + printf "\e[33mRunning libdom example...\e[0m\n" && \ + cd examples && \ + zig cc \ + -I$(ICONV)/include \ + -I$(BC_NS)/include \ + -L$(ICONV)/lib \ + -L$(BC_NS)/lib \ + -liconv \ + -ldom \ + -lhubbub \ + -lparserutils \ + -lwapcaplet \ + -o a.out \ + dom-structure-dump.c \ + $(ICONV)/lib/libiconv.a && \ + ./a.out > /dev/null && \ + rm a.out && \ + printf "\e[36mDone NetSurf $(OS)\e[0m\n" + +clean-netsurf: + @printf "\e[36mCleaning NetSurf build...\e[0m\n" && \ + cd vendor/netsurf && \ + rm -R build && \ + rm -R lib && \ + rm -R include + +test-netsurf: + @printf "\e[36mTesting NetSurf...\e[0m\n" && \ + export PREFIX=$(BC_NS) && \ + export LDFLAGS="-L$(ICONV)/lib -L$(BC_NS)/lib" && \ + export CFLAGS="-I$(ICONV)/include -I$(BC_NS)/include" && \ + cd vendor/netsurf/libdom && \ + BUILDDIR=$(BC_NS)/build/libdom make test + +install-libiconv: +ifeq ("$(wildcard vendor/libiconv/lib/libiconv.a)","") + @mkdir -p vendor/libiconv + @cd vendor/libiconv && \ + curl https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz | tar -xvzf - + @cd vendor/libiconv/libiconv-1.17 && \ + ./configure --prefix=$(BC)vendor/libiconv --enable-static && \ + make && make install +endif -## Install and build v8 engine for dev install-lexbor: @mkdir -p vendor/lexbor @cd vendor/lexbor && \ diff --git a/README.md b/README.md index 241f5be9..1bc4e01d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ For Debian/Ubuntu based Linux: sudo apt install xz-utils \ python3 ca-certificates git \ pkg-config libglib2.0-dev \ - cmake + gperf libexpat1-dev \ + cmake clang ``` For MacOS, you only need Python 3 and cmake. @@ -35,6 +36,13 @@ directory. make install-submodule ``` +### Build netsurf + +The command `make install-netsurf` will build netsurf libs used by browsercore. +``` +make install-netsurf +``` + ### Build lexbor The command `make install-lexbor` will build lexbor lib used by browsercore. diff --git a/build.zig b/build.zig index c6c75dd4..ed3f9e25 100644 --- a/build.zig +++ b/build.zig @@ -78,6 +78,7 @@ fn common( ) !void { try jsruntime_pkgs.add(step, options); linkLexbor(step); + linkNetSurf(step); } fn linkLexbor(step: *std.build.LibExeObjStep) void { @@ -86,3 +87,30 @@ fn linkLexbor(step: *std.build.LibExeObjStep) void { step.addObjectFile(.{ .path = lib_path }); step.addIncludePath(.{ .path = "vendor/lexbor-src/source" }); } + +fn linkNetSurf(step: *std.build.LibExeObjStep) void { + + // iconv + step.addObjectFile(.{ .path = "vendor/libiconv/lib/libiconv.a" }); + step.addIncludePath(.{ .path = "vendor/libiconv/include" }); + + // netsurf libs + const ns = "vendor/netsurf/"; + const libs: [4][]const u8 = .{ + "libdom", + "libhubbub", + "libparserutils", + "libwapcaplet", + }; + inline for (libs) |lib| { + step.addObjectFile(.{ .path = ns ++ "/lib/" ++ lib ++ ".a" }); + step.addIncludePath(.{ .path = ns ++ lib ++ "/src" }); + } + step.addIncludePath(.{ .path = ns ++ "/include" }); + + // wrapper + const flags = [_][]const u8{}; + const files: [1][]const u8 = .{ns ++ "wrapper/wrapper.c"}; + step.addCSourceFiles(&files, &flags); + step.addIncludePath(.{ .path = ns ++ "wrapper" }); +} diff --git a/src/dom.zig b/src/dom.zig index 6d481fe3..d0fcd42f 100644 --- a/src/dom.zig +++ b/src/dom.zig @@ -4,12 +4,13 @@ const Console = @import("jsruntime").Console; // DOM const EventTarget = @import("dom/event_target.zig").EventTarget; -const Node = @import("dom/node.zig").Node; +const N = @import("dom/node.zig"); const Element = @import("dom/element.zig").Element; const Document = @import("dom/document.zig").Document; // HTML pub const HTMLDocument = @import("html/document.zig").HTMLDocument; +const HTMLElem = @import("html/elements.zig"); const E = @import("html/elements.zig"); @@ -19,14 +20,15 @@ const interfaces = .{ // DOM EventTarget, - Node, + N.Node, + N.Types, Element, Document, // HTML HTMLDocument, - E.HTMLElement, - E.HTMLMediaElement, - E.HTMLElementsTypes, + HTMLElem.HTMLElement, + HTMLElem.HTMLMediaElement, + HTMLElem.Types, }; -pub const Interfaces = generate.TupleInst(generate.TupleT(interfaces), interfaces); +pub const Interfaces = generate.Tuple(interfaces); diff --git a/src/dom/document.zig b/src/dom/document.zig index 2d5558c3..d3622e78 100644 --- a/src/dom/document.zig +++ b/src/dom/document.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const parser = @import("../parser.zig"); +const parser = @import("../netsurf.zig"); const Node = @import("node.zig").Node; const Element = @import("element.zig").Element; @@ -15,19 +15,8 @@ pub const Document = struct { // return .{}; // } - pub fn getElementById(self: *parser.Document, elem: *parser.Element, id: []const u8) ?*parser.Element { - const collection = parser.collectionInit(self, 1); - defer parser.collectionDeinit(collection); - const case_sensitve = true; - parser.elementsByAttr(elem, collection, "id", id, case_sensitve) catch |err| { - std.debug.print("getElementById error: {s}\n", .{@errorName(err)}); - return null; - }; - if (collection.array.length == 0) { - // no results - return null; - } - return parser.collectionElement(collection, 0); + pub fn getElementById(self: *parser.Document, id: []const u8) ?*parser.Element { + return parser.documentGetElementById(self, id); } // JS funcs diff --git a/src/dom/element.zig b/src/dom/element.zig index 27e851a2..717da26a 100644 --- a/src/dom/element.zig +++ b/src/dom/element.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const parser = @import("../parser.zig"); +const parser = @import("../netsurf.zig"); const Node = @import("node.zig").Node; diff --git a/src/dom/event_target.zig b/src/dom/event_target.zig index 2e125aab..7d0fccdc 100644 --- a/src/dom/event_target.zig +++ b/src/dom/event_target.zig @@ -1,4 +1,4 @@ -const parser = @import("../parser.zig"); +const parser = @import("../netsurf.zig"); pub const EventTarget = struct { pub const Self = parser.EventTarget; diff --git a/src/dom/node.zig b/src/dom/node.zig index 6ad69380..f2daf427 100644 --- a/src/dom/node.zig +++ b/src/dom/node.zig @@ -1,28 +1,23 @@ const std = @import("std"); -const parser = @import("../parser.zig"); +const generate = @import("../generate.zig"); + +const parser = @import("../netsurf.zig"); const EventTarget = @import("event_target.zig").EventTarget; - -pub fn create_tree(node: ?*parser.Node, _: ?*anyopaque) callconv(.C) parser.Action { - if (node == null) { - return parser.ActionStop; - } - const node_type = parser.nodeType(node.?); - const node_name = parser.nodeName(node.?); - std.debug.print("type: {any}, name: {s}\n", .{ node_type, node_name }); - if (node_type == parser.NodeType.element) { - std.debug.print("yes\n", .{}); - } - return parser.ActionOk; -} +const HTMLDocument = @import("../html/document.zig").HTMLDocument; +const HTMLElem = @import("../html/elements.zig"); pub const Node = struct { pub const Self = parser.Node; pub const prototype = *EventTarget; pub const mem_guarantied = true; - - pub fn make_tree(self: *parser.Node) !void { - try parser.nodeWalk(self, create_tree); - } }; + +pub const Types = generate.Tuple(.{ + HTMLElem.Types, + HTMLDocument, +}); +const Generated = generate.Union.compile(Types); +pub const Union = Generated._union; +pub const Tags = Generated._enum; diff --git a/src/generate.zig b/src/generate.zig index 553197c9..8814eaa5 100644 --- a/src/generate.zig +++ b/src/generate.zig @@ -1,13 +1,33 @@ const std = @import("std"); const builtin = @import("builtin"); +// Utils +// ----- + +fn itoa(comptime i: u8) ![]const u8 { + var len: usize = undefined; + if (i < 10) { + len = 1; + } else if (i < 100) { + len = 2; + } else { + return error.GenerateTooMuchMembers; + } + var buf: [len]u8 = undefined; + return try std.fmt.bufPrint(buf[0..], "{d}", .{i}); +} + fn fmtName(comptime T: type) []const u8 { var it = std.mem.splitBackwards(u8, @typeName(T), "."); return it.first(); } +// Union +// ----- + // Generate a flatten tagged Union from various structs and union of structs // TODO: make this function more generic +// TODO: dedup pub const Union = struct { _enum: type, _union: type, @@ -152,38 +172,15 @@ pub const Union = struct { } }; -fn itoa(comptime i: u8) ![]u8 { - var len: usize = undefined; - if (i < 10) { - len = 1; - } else if (i < 100) { - len = 2; - } else { - return error.GenerateTooMuchMembers; - } - var buf: [len]u8 = undefined; - return try std.fmt.bufPrint(buf[0..], "{d}", .{i}); -} +// Tuple +// ----- -// Generate a flatten tuple type from various structs and tuple of structs. -// TODO: make this function more generic -pub fn TupleT(comptime tuple: anytype) type { - - // check types provided - const tuple_T = @TypeOf(tuple); - const tuple_info = @typeInfo(tuple_T); - if (tuple_info != .Struct or !tuple_info.Struct.is_tuple) { - @compileError("GenerateArgNotTuple"); - } - - const tuple_members = tuple_info.Struct.fields; - - // first iteration to get the total number of members - var members_nb = 0; - for (tuple_members) |member| { +fn tupleNb(comptime tuple: anytype) usize { + var nb = 0; + for (@typeInfo(@TypeOf(tuple)).Struct.fields) |member| { const member_T = @field(tuple, member.name); if (@TypeOf(member_T) == type) { - members_nb += 1; + nb += 1; } else { const member_info = @typeInfo(@TypeOf(member_T)); if (member_info != .Struct and !member_info.Struct.is_tuple) { @@ -194,14 +191,82 @@ pub fn TupleT(comptime tuple: anytype) type { @compileError("GenerateMemberTupleChildNotType"); } } - members_nb += member_info.Struct.fields.len; + nb += member_info.Struct.fields.len; } } + return nb; +} - // second iteration to generate the tuple type - var fields: [members_nb]std.builtin.Type.StructField = undefined; +fn tupleTypes(comptime nb: usize, comptime tuple: anytype) [nb]type { + var types: [nb]type = undefined; var done = 0; - while (done < members_nb) { + for (@typeInfo(@TypeOf(tuple)).Struct.fields) |member| { + const T = @field(tuple, member.name); + if (@TypeOf(T) == type) { + types[done] = T; + done += 1; + } else { + const info = @typeInfo(@TypeOf(T)); + for (info.Struct.fields) |field| { + types[done] = @field(T, field.name); + done += 1; + } + } + } + return types; +} + +fn isDup(comptime nb: usize, comptime list: [nb]type, comptime T: type, comptime i: usize) bool { + for (list, 0..) |item, index| { + if (i >= index) { + // check sequentially + continue; + } + if (T == item) { + return true; + } + } + return false; +} + +fn dedupIndexes(comptime nb: usize, comptime types: [nb]type) [nb]i32 { + var dedup_indexes: [nb]i32 = undefined; + for (types, 0..) |T, i| { + if (isDup(nb, types, T, i)) { + dedup_indexes[i] = -1; + } else { + dedup_indexes[i] = i; + } + } + return dedup_indexes; +} + +fn dedupNb(comptime nb: usize, comptime dedup_indexes: [nb]i32) usize { + var dedup_nb = 0; + for (dedup_indexes) |index| { + if (index != -1) { + dedup_nb += 1; + } + } + return dedup_nb; +} + +fn TupleT(comptime tuple: anytype) type { + @setEvalBranchQuota(100000); + + // logic + const nb = tupleNb(tuple); + const types = tupleTypes(nb, tuple); + const dedup_indexes = dedupIndexes(nb, types); + const dedup_nb = dedupNb(nb, dedup_indexes); + + // generate the tuple type + var fields: [dedup_nb]std.builtin.Type.StructField = undefined; + var done = 0; + for (dedup_indexes) |index| { + if (index == -1) { + continue; + } fields[done] = .{ .name = try itoa(done), .type = type, @@ -221,39 +286,36 @@ pub fn TupleT(comptime tuple: anytype) type { return @Type(std.builtin.Type{ .Struct = info }); } -// Instantiate a flatten tuple from various structs and tuple of structs -// You need to call first TupleT to generate the according type +// Create a flatten tuple from various structs and tuple of structs +// Duplicates will be removed. // TODO: make this function more generic -pub fn TupleInst(comptime T: type, comptime tuple: anytype) T { +pub fn Tuple(comptime tuple: anytype) TupleT(tuple) { // check types provided const tuple_T = @TypeOf(tuple); const tuple_info = @typeInfo(tuple_T); - const tuple_members = tuple_info.Struct.fields; + if (tuple_info != .Struct or !tuple_info.Struct.is_tuple) { + @compileError("GenerateArgNotTuple"); + } + + // generate the type + const T = TupleT(tuple); + + // logic + const nb = tupleNb(tuple); + const types = tupleTypes(nb, tuple); + const dedup_indexes = dedupIndexes(nb, types); // instantiate the tuple var t: T = undefined; var done = 0; - for (tuple_members) |member| { - const member_T = @field(tuple, member.name); - var member_info: std.builtin.Type = undefined; - if (@TypeOf(member_T) == type) { - member_info = @typeInfo(member_T); - } else { - member_info = @typeInfo(@TypeOf(member_T)); - } - var member_detail = member_info.Struct; - if (member_detail.is_tuple) { - for (member_detail.fields) |field| { - const name = try itoa(done); - @field(t, name) = @field(member_T, field.name); - done += 1; - } - } else { - const name = try itoa(done); - @field(t, name) = @field(tuple, member.name); - done += 1; + for (dedup_indexes) |index| { + if (index == -1) { + continue; } + const name = try itoa(done); + @field(t, name) = types[index]; + done += 1; } return t; } @@ -322,7 +384,7 @@ pub fn tests() !void { // Tuple from structs const tuple_structs = .{ Astruct, Bstruct }; - const tFromStructs = TupleInst(TupleT(tuple_structs), tuple_structs); + const tFromStructs = Tuple(tuple_structs); const t_from_structs = @typeInfo(@TypeOf(tFromStructs)); try std.testing.expect(t_from_structs == .Struct); try std.testing.expect(t_from_structs.Struct.is_tuple); @@ -332,7 +394,7 @@ pub fn tests() !void { // Tuple from tuple and structs const tuple_mix = .{ tFromStructs, Cstruct }; - const tFromMix = TupleInst(TupleT(tuple_mix), tuple_mix); + const tFromMix = Tuple(tuple_mix); const t_from_mix = @typeInfo(@TypeOf(tFromMix)); try std.testing.expect(t_from_mix == .Struct); try std.testing.expect(t_from_mix.Struct.is_tuple); @@ -341,5 +403,16 @@ pub fn tests() !void { try std.testing.expect(@field(tFromMix, "1") == Bstruct); try std.testing.expect(@field(tFromMix, "2") == Cstruct); + // Tuple with dedup + const tuple_dedup = .{ Cstruct, Astruct, tFromStructs, Bstruct }; + const tFromDedup = Tuple(tuple_dedup); + const t_from_dedup = @typeInfo(@TypeOf(tFromDedup)); + try std.testing.expect(t_from_dedup == .Struct); + try std.testing.expect(t_from_dedup.Struct.is_tuple); + try std.testing.expect(t_from_dedup.Struct.fields.len == 3); + try std.testing.expect(@field(tFromDedup, "0") == Cstruct); + try std.testing.expect(@field(tFromDedup, "1") == Astruct); + try std.testing.expect(@field(tFromDedup, "2") == Bstruct); + std.debug.print("Generate Tuple: OK\n", .{}); } diff --git a/src/html/document.zig b/src/html/document.zig index 929cfcd8..8738d307 100644 --- a/src/html/document.zig +++ b/src/html/document.zig @@ -1,14 +1,13 @@ const std = @import("std"); -const parser = @import("../parser.zig"); +const parser = @import("../netsurf.zig"); const jsruntime = @import("jsruntime"); const Case = jsruntime.test_utils.Case; const checkCases = jsruntime.test_utils.checkCases; const Document = @import("../dom/document.zig").Document; - -const E = @import("elements.zig"); +const HTMLElem = @import("elements.zig"); pub const HTMLDocument = struct { pub const Self = parser.DocumentHTML; @@ -22,18 +21,19 @@ pub const HTMLDocument = struct { return parser.documentHTMLBody(self); } - pub fn _getElementById(self: *parser.DocumentHTML, id: []u8) ?*parser.HTMLElement { - const body_html = parser.documentHTMLBody(self); - const body_dom = @as(*parser.Element, @ptrCast(body_html)); - const doc_dom = @as(*parser.Document, @ptrCast(self)); - const elem_dom = Document.getElementById(doc_dom, body_dom, id); - return @as(*parser.HTMLElement, @ptrCast(elem_dom)); + pub fn _getElementById(self: *parser.DocumentHTML, id: []u8) ?HTMLElem.Union { + const doc = parser.documentHTMLToDocument(self); + const elem_dom = parser.documentGetElementById(doc, id); + if (elem_dom) |elem| { + return HTMLElem.toInterface(HTMLElem.Union, elem); + } + return null; } - pub fn _createElement(self: *parser.DocumentHTML, tag_name: []const u8) E.HTMLElements { + pub fn _createElement(self: *parser.DocumentHTML, tag_name: []const u8) HTMLElem.Union { const doc_dom = parser.documentHTMLToDocument(self); const base = parser.documentCreateElement(doc_dom, tag_name); - return E.ElementToHTMLElementInterface(base); + return HTMLElem.toInterface(HTMLElem.Union, base); } }; @@ -50,13 +50,14 @@ pub fn testExecFn( .{ .src = "document.__proto__.__proto__.constructor.name", .ex = "Document" }, .{ .src = "document.__proto__.__proto__.__proto__.constructor.name", .ex = "Node" }, .{ .src = "document.__proto__.__proto__.__proto__.__proto__.constructor.name", .ex = "EventTarget" }, + .{ .src = "document.body.localName === 'body'", .ex = "true" }, }; try checkCases(js_env, &constructor); var getElementById = [_]Case{ .{ .src = "let getElementById = document.getElementById('content')", .ex = "undefined" }, - .{ .src = "getElementById.constructor.name", .ex = "HTMLElement" }, - .{ .src = "getElementById.localName", .ex = "main" }, + .{ .src = "getElementById.constructor.name", .ex = "HTMLDivElement" }, + .{ .src = "getElementById.localName", .ex = "div" }, }; try checkCases(js_env, &getElementById); diff --git a/src/html/elements.zig b/src/html/elements.zig index ba9f9653..012bec3d 100644 --- a/src/html/elements.zig +++ b/src/html/elements.zig @@ -1,4 +1,4 @@ -const parser = @import("../parser.zig"); +const parser = @import("../netsurf.zig"); const generate = @import("../generate.zig"); const Element = @import("../dom/element.zig").Element; @@ -7,12 +7,12 @@ const Element = @import("../dom/element.zig").Element; // -------------- pub const HTMLElement = struct { - pub const Self = parser.HTMLElement; + pub const Self = parser.ElementHTML; pub const prototype = *Element; pub const mem_guarantied = true; }; -pub const HTMLElementsTypes = .{ +pub const Types = .{ HTMLUnknownElement, HTMLAnchorElement, HTMLAreaElement, @@ -74,9 +74,9 @@ pub const HTMLElementsTypes = .{ HTMLUListElement, HTMLVideoElement, }; -const HTMLElementsGenerated = generate.Union.compile(HTMLElementsTypes); -pub const HTMLElements = HTMLElementsGenerated._union; -pub const HTMLElementsTags = HTMLElementsGenerated._enum; +const Generated = generate.Union.compile(Types); +pub const Union = Generated._union; +pub const Tags = Generated._enum; // Deprecated HTMLElements in Chrome (2023/03/15) // HTMLContentelement @@ -454,8 +454,8 @@ pub const HTMLVideoElement = struct { pub const mem_guarantied = true; }; -pub fn ElementToHTMLElementInterface(elem: *parser.Element) HTMLElements { - const tag = parser.nodeTag(parser.elementNode(elem)); +pub fn toInterface(comptime T: type, elem: *parser.Element) T { + const tag = parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem))); return switch (tag) { .a => .{ .HTMLAnchorElement = @as(*parser.Anchor, @ptrCast(elem)) }, .area => .{ .HTMLAreaElement = @as(*parser.Area, @ptrCast(elem)) }, diff --git a/src/parser.zig b/src/lexbor.zig similarity index 100% rename from src/parser.zig rename to src/lexbor.zig diff --git a/src/main.zig b/src/main.zig index 092ed443..fccb781b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,7 +2,7 @@ const std = @import("std"); const jsruntime = @import("jsruntime"); -const parser = @import("parser.zig"); +const parser = @import("netsurf.zig"); const DOM = @import("dom.zig"); const html_test = @import("html_test.zig").html; @@ -55,9 +55,6 @@ pub fn main() !void { defer vm.deinit(); // document - doc = parser.documentHTMLInit(); - defer parser.documentHTMLDeinit(doc); - try parser.documentHTMLParse(doc, html_test); // remove socket file of internal server // reuse_address (SO_REUSEADDR flag) does not seems to work on unix socket @@ -68,6 +65,9 @@ pub fn main() !void { return err; } }; + var f = "test.html".*; + doc = parser.documentHTMLParse(&f); + // TODO: defer doc? // alloc var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); diff --git a/src/main_shell.zig b/src/main_shell.zig index c7ce18f7..6d8b2819 100644 --- a/src/main_shell.zig +++ b/src/main_shell.zig @@ -2,7 +2,7 @@ const std = @import("std"); const jsruntime = @import("jsruntime"); -const parser = @import("parser.zig"); +const parser = @import("netsurf.zig"); const DOM = @import("dom.zig"); const html_test = @import("html_test.zig").html; @@ -32,9 +32,9 @@ pub fn main() !void { const apis = jsruntime.compile(DOM.Interfaces); // document - doc = parser.documentHTMLInit(); - defer parser.documentHTMLDeinit(doc); - try parser.documentHTMLParse(doc, html_test); + var f = "test.html".*; + doc = parser.documentHTMLParse(&f); + // TODO: defer doc? // create JS vm const vm = jsruntime.VM.init(); diff --git a/src/netsurf.zig b/src/netsurf.zig new file mode 100644 index 00000000..e0ae2e7d --- /dev/null +++ b/src/netsurf.zig @@ -0,0 +1,349 @@ +const std = @import("std"); + +const cp = @cImport({ + @cInclude("wrapper.h"); +}); + +const c = @cImport({ + @cInclude("core/node.h"); + @cInclude("core/document.h"); + @cInclude("core/element.h"); + + @cInclude("html/html_document.h"); + @cInclude("html/html_element.h"); + @cInclude("html/html_anchor_element.h"); + @cInclude("html/html_area_element.h"); + @cInclude("html/html_br_element.h"); + @cInclude("html/html_base_element.h"); + @cInclude("html/html_body_element.h"); + @cInclude("html/html_button_element.h"); + @cInclude("html/html_canvas_element.h"); + @cInclude("html/html_dlist_element.h"); + @cInclude("html/html_div_element.h"); + @cInclude("html/html_fieldset_element.h"); + @cInclude("html/html_form_element.h"); + @cInclude("html/html_frameset_element.h"); + @cInclude("html/html_hr_element.h"); + @cInclude("html/html_head_element.h"); + @cInclude("html/html_heading_element.h"); + @cInclude("html/html_html_element.h"); + @cInclude("html/html_iframe_element.h"); + @cInclude("html/html_image_element.h"); + @cInclude("html/html_input_element.h"); + @cInclude("html/html_li_element.h"); + @cInclude("html/html_label_element.h"); + @cInclude("html/html_legend_element.h"); + @cInclude("html/html_link_element.h"); + @cInclude("html/html_map_element.h"); + @cInclude("html/html_meta_element.h"); + @cInclude("html/html_mod_element.h"); + @cInclude("html/html_olist_element.h"); + @cInclude("html/html_object_element.h"); + @cInclude("html/html_opt_group_element.h"); + @cInclude("html/html_option_element.h"); + @cInclude("html/html_paragraph_element.h"); + @cInclude("html/html_pre_element.h"); + @cInclude("html/html_quote_element.h"); + @cInclude("html/html_script_element.h"); + @cInclude("html/html_select_element.h"); + @cInclude("html/html_style_element.h"); + @cInclude("html/html_table_element.h"); + @cInclude("html/html_tablecaption_element.h"); + @cInclude("html/html_tablecell_element.h"); + @cInclude("html/html_tablecol_element.h"); + @cInclude("html/html_tablerow_element.h"); + @cInclude("html/html_tablesection_element.h"); + @cInclude("html/html_text_area_element.h"); + @cInclude("html/html_title_element.h"); + @cInclude("html/html_ulist_element.h"); +}); + +// Utils +const String = c.dom_string; + +inline fn stringToData(s: *String) []const u8 { + const data = c.dom_string_data(s); + return data[0..c.dom_string_byte_length(s)]; +} + +inline fn stringFromData(data: []const u8) *String { + var s: ?*String = null; + _ = c.dom_string_create(data.ptr, data.len, &s); + return s.?; +} + +// Tag + +pub const Tag = enum(u8) { + a = c.DOM_HTML_ELEMENT_TYPE_A, + area = c.DOM_HTML_ELEMENT_TYPE_AREA, + audio = c.DOM_HTML_ELEMENT_TYPE_AUDIO, + br = c.DOM_HTML_ELEMENT_TYPE_BR, + base = c.DOM_HTML_ELEMENT_TYPE_BASE, + body = c.DOM_HTML_ELEMENT_TYPE_BODY, + button = c.DOM_HTML_ELEMENT_TYPE_BUTTON, + canvas = c.DOM_HTML_ELEMENT_TYPE_CANVAS, + dl = c.DOM_HTML_ELEMENT_TYPE_DL, + dialog = c.DOM_HTML_ELEMENT_TYPE_DIALOG, + data = c.DOM_HTML_ELEMENT_TYPE_DATA, + div = c.DOM_HTML_ELEMENT_TYPE_DIV, + embed = c.DOM_HTML_ELEMENT_TYPE_EMBED, + fieldset = c.DOM_HTML_ELEMENT_TYPE_FIELDSET, + form = c.DOM_HTML_ELEMENT_TYPE_FORM, + frameset = c.DOM_HTML_ELEMENT_TYPE_FRAMESET, + hr = c.DOM_HTML_ELEMENT_TYPE_HR, + head = c.DOM_HTML_ELEMENT_TYPE_HEAD, + h1 = c.DOM_HTML_ELEMENT_TYPE_H1, + h2 = c.DOM_HTML_ELEMENT_TYPE_H2, + h3 = c.DOM_HTML_ELEMENT_TYPE_H3, + h4 = c.DOM_HTML_ELEMENT_TYPE_H4, + h5 = c.DOM_HTML_ELEMENT_TYPE_H5, + h6 = c.DOM_HTML_ELEMENT_TYPE_H6, + html = c.DOM_HTML_ELEMENT_TYPE_HTML, + iframe = c.DOM_HTML_ELEMENT_TYPE_IFRAME, + img = c.DOM_HTML_ELEMENT_TYPE_IMG, + input = c.DOM_HTML_ELEMENT_TYPE_INPUT, + li = c.DOM_HTML_ELEMENT_TYPE_LI, + label = c.DOM_HTML_ELEMENT_TYPE_LABEL, + legend = c.DOM_HTML_ELEMENT_TYPE_LEGEND, + link = c.DOM_HTML_ELEMENT_TYPE_LINK, + map = c.DOM_HTML_ELEMENT_TYPE_MAP, + meta = c.DOM_HTML_ELEMENT_TYPE_META, + meter = c.DOM_HTML_ELEMENT_TYPE_METER, + ins = c.DOM_HTML_ELEMENT_TYPE_INS, + del = c.DOM_HTML_ELEMENT_TYPE_DEL, + ol = c.DOM_HTML_ELEMENT_TYPE_OL, + object = c.DOM_HTML_ELEMENT_TYPE_OBJECT, + optgroup = c.DOM_HTML_ELEMENT_TYPE_OPTGROUP, + option = c.DOM_HTML_ELEMENT_TYPE_OPTION, + output = c.DOM_HTML_ELEMENT_TYPE_OUTPUT, + p = c.DOM_HTML_ELEMENT_TYPE_P, + picture = c.DOM_HTML_ELEMENT_TYPE_PICTURE, + pre = c.DOM_HTML_ELEMENT_TYPE_PRE, + progress = c.DOM_HTML_ELEMENT_TYPE_PROGRESS, + blockquote = c.DOM_HTML_ELEMENT_TYPE_BLOCKQUOTE, + q = c.DOM_HTML_ELEMENT_TYPE_Q, + script = c.DOM_HTML_ELEMENT_TYPE_SCRIPT, + select = c.DOM_HTML_ELEMENT_TYPE_SELECT, + source = c.DOM_HTML_ELEMENT_TYPE_SOURCE, + span = c.DOM_HTML_ELEMENT_TYPE_SPAN, + style = c.DOM_HTML_ELEMENT_TYPE_STYLE, + table = c.DOM_HTML_ELEMENT_TYPE_TABLE, + caption = c.DOM_HTML_ELEMENT_TYPE_CAPTION, + th = c.DOM_HTML_ELEMENT_TYPE_TH, + td = c.DOM_HTML_ELEMENT_TYPE_TD, + col = c.DOM_HTML_ELEMENT_TYPE_COL, + tr = c.DOM_HTML_ELEMENT_TYPE_TR, + thead = c.DOM_HTML_ELEMENT_TYPE_THEAD, + tbody = c.DOM_HTML_ELEMENT_TYPE_TBODY, + tfoot = c.DOM_HTML_ELEMENT_TYPE_TFOOT, + template = c.DOM_HTML_ELEMENT_TYPE_TEMPLATE, + textarea = c.DOM_HTML_ELEMENT_TYPE_TEXTAREA, + time = c.DOM_HTML_ELEMENT_TYPE_TIME, + title = c.DOM_HTML_ELEMENT_TYPE_TITLE, + track = c.DOM_HTML_ELEMENT_TYPE_TRACK, + ul = c.DOM_HTML_ELEMENT_TYPE_UL, + video = c.DOM_HTML_ELEMENT_TYPE_VIDEO, + undef = c.DOM_HTML_ELEMENT_TYPE__UNKNOWN, + + pub fn all() []Tag { + comptime { + const info = @typeInfo(Tag).Enum; + comptime var l: [info.fields.len]Tag = undefined; + inline for (info.fields, 0..) |field, i| { + l[i] = @as(Tag, @enumFromInt(field.value)); + } + return &l; + } + } + + pub fn allElements() [][]const u8 { + comptime { + const tags = all(); + var names: [tags.len][]const u8 = undefined; + inline for (tags, 0..) |tag, i| { + names[i] = tag.elementName(); + } + return &names; + } + } + + fn upperName(comptime name: []const u8) []const u8 { + comptime { + var upper_name: [name.len]u8 = undefined; + for (name, 0..) |char, i| { + var to_upper = false; + if (i == 0) { + to_upper = true; + } else if (i == 1 and name.len == 2) { + to_upper = true; + } + if (to_upper) { + upper_name[i] = std.ascii.toUpper(char); + } else { + upper_name[i] = char; + } + } + return &upper_name; + } + } + + fn elementName(comptime tag: Tag) []const u8 { + return switch (tag) { + .a => "Anchor", + .dl => "DList", + .fieldset => "FieldSet", + .frameset => "FrameSet", + .h1, .h2, .h3, .h4, .h5, .h6 => "Heading", + .iframe => "IFrame", + .img => "Image", + .ins, .del => "Mod", + .ol => "OList", + .optgroup => "OptGroup", + .p => "Paragraph", + .blockquote, .q => "Quote", + .caption => "TableCaption", + .th, .td => "TableCell", + .col => "TableCol", + .tr => "TableRow", + .thead, .tbody, .tfoot => "TableSection", + .textarea => "TextArea", + .ul => "UList", + .undef => "Unknown", + else => upperName(@tagName(tag)), + }; + } +}; + +// EventTarget +pub const EventTarget = c.dom_event_target; + +// Node +pub const Node = c.dom_node_internal; + +// Element +pub const Element = c.dom_element; + +pub fn elementLocalName(elem: *Element) []const u8 { + const elem_aligned: *align(8) Element = @alignCast(elem); + const node = @as(*Node, @ptrCast(elem_aligned)); + var s: ?*String = null; + _ = c._dom_node_get_local_name(node, &s); + var s_lower: ?*String = null; + _ = c.dom_string_tolower(s, true, &s_lower); + return stringToData(s_lower.?); +} + +// ElementHTML +pub const ElementHTML = c.dom_html_element; + +pub fn elementHTMLGetTagType(elem_html: *ElementHTML) Tag { + var tag_type: c.dom_html_element_type = undefined; + _ = c._dom_html_element_get_tag_type(elem_html, &tag_type); + return @as(Tag, @enumFromInt(tag_type)); +} + +// ElementsHTML + +pub const MediaElement = struct { base: c.dom_html_element }; + +pub const Unknown = struct { base: c.dom_html_element }; +pub const Anchor = c.dom_html_anchor_element; +pub const Area = c.dom_html_area_element; +pub const Audio = struct { base: c.dom_html_element }; +pub const BR = c.dom_html_br_element; +pub const Base = c.dom_html_base_element; +pub const Body = c.dom_html_body_element; +pub const Button = c.dom_html_button_element; +pub const Canvas = c.dom_html_canvas_element; +pub const DList = c.dom_html_dlist_element; +pub const Data = struct { base: c.dom_html_element }; +pub const Dialog = struct { base: c.dom_html_element }; +pub const Div = c.dom_html_div_element; +pub const Embed = struct { base: c.dom_html_element }; +pub const FieldSet = c.dom_html_field_set_element; +pub const Form = c.dom_html_form_element; +pub const FrameSet = c.dom_html_frame_set_element; +pub const HR = c.dom_html_hr_element; +pub const Head = c.dom_html_head_element; +pub const Heading = c.dom_html_heading_element; +pub const Html = c.dom_html_html_element; +pub const IFrame = c.dom_html_iframe_element; +pub const Image = c.dom_html_image_element; +pub const Input = c.dom_html_input_element; +pub const LI = c.dom_html_li_element; +pub const Label = c.dom_html_label_element; +pub const Legend = c.dom_html_legend_element; +pub const Link = c.dom_html_link_element; +pub const Map = c.dom_html_map_element; +pub const Meta = c.dom_html_meta_element; +pub const Meter = struct { base: c.dom_html_element }; +pub const Mod = c.dom_html_mod_element; +pub const OList = c.dom_html_olist_element; +pub const Object = c.dom_html_object_element; +pub const OptGroup = c.dom_html_opt_group_element; +pub const Option = c.dom_html_option_element; +pub const Output = struct { base: c.dom_html_element }; +pub const Paragraph = c.dom_html_paragraph_element; +pub const Picture = struct { base: c.dom_html_element }; +pub const Pre = c.dom_html_pre_element; +pub const Progress = struct { base: c.dom_html_element }; +pub const Quote = c.dom_html_quote_element; +pub const Script = c.dom_html_script_element; +pub const Select = c.dom_html_select_element; +pub const Source = struct { base: c.dom_html_element }; +pub const Span = struct { base: c.dom_html_element }; +pub const Style = c.dom_html_style_element; +pub const Table = c.dom_html_table_element; +pub const TableCaption = c.dom_html_table_caption_element; +pub const TableCell = c.dom_html_table_cell_element; +pub const TableCol = c.dom_html_table_col_element; +pub const TableRow = c.dom_html_table_row_element; +pub const TableSection = c.dom_html_table_section_element; +pub const Template = struct { base: c.dom_html_element }; +pub const TextArea = c.dom_html_text_area_element; +pub const Time = struct { base: c.dom_html_element }; +pub const Title = c.dom_html_title_element; +pub const Track = struct { base: c.dom_html_element }; +pub const UList = c.dom_html_u_list_element; +pub const Video = struct { base: c.dom_html_element }; + +// Document +pub const Document = c.dom_document; + +pub inline fn documentGetElementById(doc: *Document, id: []const u8) ?*Element { + var elem: ?*Element = undefined; + _ = c._dom_document_get_element_by_id(doc, stringFromData(id), &elem); + return elem; +} + +pub inline fn documentCreateElement(doc: *Document, tag_name: []const u8) *Element { + var elem: ?*Element = undefined; + _ = c._dom_html_document_create_element(doc, stringFromData(tag_name), &elem); + return elem.?; +} + +// DocumentHTML +pub const DocumentHTML = c.dom_html_document; + +pub fn documentHTMLParse(filename: []u8) *DocumentHTML { + const doc = cp.wr_create_doc_dom_from_file(filename.ptr); + if (doc == null) { + @panic("error parser"); + } + const doc_aligned: *align(@alignOf((DocumentHTML))) cp.dom_document = @alignCast(doc.?); + return @as(*DocumentHTML, @ptrCast(doc_aligned)); +} + +pub inline fn documentHTMLToDocument(doc_html: *DocumentHTML) *Document { + return @as(*Document, @ptrCast(doc_html)); +} + +pub inline fn documentHTMLBody(doc_html: *DocumentHTML) ?*Body { + var body: ?*ElementHTML = undefined; + _ = c._dom_html_document_get_body(doc_html, &body); + if (body) |value| { + return @as(*Body, @ptrCast(value)); + } + return null; +} diff --git a/src/run_tests.zig b/src/run_tests.zig index ff579757..7bdff671 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -3,12 +3,10 @@ const std = @import("std"); const jsruntime = @import("jsruntime"); const generate = @import("generate.zig"); -const parser = @import("parser.zig"); +const parser = @import("netsurf.zig"); const DOM = @import("dom.zig"); const testExecFn = @import("html/document.zig").testExecFn; -const html_test = @import("html_test.zig").html; - var doc: *parser.DocumentHTML = undefined; fn testsExecFn( @@ -38,9 +36,8 @@ test { const apis = jsruntime.compile(DOM.Interfaces); // document - doc = parser.documentHTMLInit(); - defer parser.documentHTMLDeinit(doc); - try parser.documentHTMLParse(doc, html_test); + var f = "test.html".*; + doc = parser.documentHTMLParse(&f); // create JS vm const vm = jsruntime.VM.init(); diff --git a/test.html b/test.html new file mode 100644 index 00000000..cb54107e --- /dev/null +++ b/test.html @@ -0,0 +1,8 @@ +
diff --git a/vendor/jsruntime-lib b/vendor/jsruntime-lib index 978b166c..1dbd2349 160000 --- a/vendor/jsruntime-lib +++ b/vendor/jsruntime-lib @@ -1 +1 @@ -Subproject commit 978b166c651f11566df2a264fe869ea4d161c7d5 +Subproject commit 1dbd2349f980817317d54daba1ce9cb3653ab148 diff --git a/vendor/netsurf/libdom b/vendor/netsurf/libdom new file mode 160000 index 00000000..176bab60 --- /dev/null +++ b/vendor/netsurf/libdom @@ -0,0 +1 @@ +Subproject commit 176bab60eb10beb68fbbb1a6185c68eeda2a6e95 diff --git a/vendor/netsurf/libhubbub b/vendor/netsurf/libhubbub new file mode 160000 index 00000000..873ed6e2 --- /dev/null +++ b/vendor/netsurf/libhubbub @@ -0,0 +1 @@ +Subproject commit 873ed6e236f7669afd3ef44259c34addc6dc95b6 diff --git a/vendor/netsurf/libparserutils b/vendor/netsurf/libparserutils new file mode 160000 index 00000000..96cdd0ff --- /dev/null +++ b/vendor/netsurf/libparserutils @@ -0,0 +1 @@ +Subproject commit 96cdd0ff114299f520e76538ab8fde39358b87f9 diff --git a/vendor/netsurf/libwapcaplet b/vendor/netsurf/libwapcaplet new file mode 160000 index 00000000..b5e42b12 --- /dev/null +++ b/vendor/netsurf/libwapcaplet @@ -0,0 +1 @@ +Subproject commit b5e42b12211a92339b0b62cb90f1a86a397e146e diff --git a/vendor/netsurf/share/netsurf-buildsystem b/vendor/netsurf/share/netsurf-buildsystem new file mode 160000 index 00000000..b4ba781f --- /dev/null +++ b/vendor/netsurf/share/netsurf-buildsystem @@ -0,0 +1 @@ +Subproject commit b4ba781fe22f356d7c53b1674dff91323af61458 diff --git a/vendor/netsurf/wrapper/wrapper.c b/vendor/netsurf/wrapper/wrapper.c new file mode 100644 index 00000000..1f7c14bd --- /dev/null +++ b/vendor/netsurf/wrapper/wrapper.c @@ -0,0 +1,77 @@ +#include