Remove the generic nature of Env and most of the JS classes

Back in the zig-js-runtime days, globals were used for the state and webapi
declarations. This caused problems largely because it was done across
compilation units (using @import("root")...).

The generic Env(S, WebApi) was used to solve these problems, while still making
it work for different States and WebApis.

This change removes the generics and hard-codes the *Page as the state and
only supports our WebApis for the class declarations.

To accommodate this change, the runtime/*tests* have been removed. I don't
consider this a huge loss - whatever behavior these were testing, already
exists in the browser/**/*.zig web api.

As we write more complex/complete WebApis, we're seeing more and more cases
that need to rely on js objects directly (JsObject, Function, Promises, etc...).
The goal is to make these easier to use. Rather than using Env.JsObject, you
now import "js.zig" and use js.JsObject (TODO: rename JsObject to Object).
Everything is just a plain Zig struct, rather than being nested in a generic.

After this change, I plan on:

1 - Renaming the js objects, JsObject -> Object. These should be referenced in
    the webapi as js.Object, js.This, ...

2 - Splitting the code across multiple files (Env.zig, Context.zig,
    Caller.zig, ...)
This commit is contained in:
Karl Seguin
2025-10-01 16:11:44 +08:00
parent ab18c90b36
commit 32226297ab
59 changed files with 4574 additions and 5684 deletions

View File

@@ -4,7 +4,7 @@ const Allocator = std.mem.Allocator;
const log = @import("log.zig");
const Http = @import("http/Http.zig");
const Platform = @import("runtime/js.zig").Platform;
const Platform = @import("browser/js/js.zig").Platform;
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
const Notification = @import("notification.zig").Notification;

View File

@@ -18,10 +18,10 @@
const std = @import("std");
const js = @import("js/js.zig");
const log = @import("../log.zig");
const parser = @import("netsurf.zig");
const Env = @import("env.zig").Env;
const Page = @import("page.zig").Page;
const DataURI = @import("DataURI.zig");
const Http = @import("../http/Http.zig");
@@ -627,7 +627,7 @@ const Script = struct {
const Callback = union(enum) {
string: []const u8,
function: Env.Function,
function: js.Function,
};
const Source = union(enum) {
@@ -664,7 +664,7 @@ const Script = struct {
});
const js_context = page.main_context;
var try_catch: Env.TryCatch = undefined;
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();
@@ -706,7 +706,7 @@ const Script = struct {
switch (callback) {
.string => |str| {
var try_catch: Env.TryCatch = undefined;
var try_catch: js.TryCatch = undefined;
try_catch.init(page.main_context);
defer try_catch.deinit();
@@ -728,7 +728,7 @@ const Script = struct {
};
defer parser.eventDestroy(loadevt);
var result: Env.Function.Result = undefined;
var result: js.Function.Result = undefined;
const iface = Event.toInterface(loadevt);
f.tryCall(void, .{iface}, &result) catch {
log.warn(.user_script, "script callback", .{

View File

@@ -26,7 +26,7 @@
// this quickly proved necessary, since different fields are needed on the same
// data at different levels of the prototype chain. This isn't memory efficient.
const Env = @import("env.zig").Env;
const js = @import("js/js.zig");
const parser = @import("netsurf.zig");
const DataSet = @import("html/DataSet.zig");
const ShadowRoot = @import("dom/shadow_root.zig").ShadowRoot;
@@ -34,8 +34,8 @@ const StyleSheet = @import("cssom/StyleSheet.zig");
const CSSStyleDeclaration = @import("cssom/CSSStyleDeclaration.zig");
// for HTMLScript (but probably needs to be added to more)
onload: ?Env.Function = null,
onerror: ?Env.Function = null,
onload: ?js.Function = null,
onerror: ?js.Function = null,
// for HTMLElement
style: CSSStyleDeclaration = .empty,
@@ -53,7 +53,7 @@ style_sheet: ?*StyleSheet = null,
// for dom/document
active_element: ?*parser.Element = null,
adopted_style_sheets: ?Env.JsObject = null,
adopted_style_sheets: ?js.JsObject = null,
// for HTMLSelectElement
// By default, if no option is explicitly selected, the first option should

View File

@@ -21,8 +21,8 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const js = @import("js/js.zig");
const State = @import("State.zig");
const Env = @import("env.zig").Env;
const App = @import("../app.zig").App;
const Session = @import("session.zig").Session;
const Notification = @import("../notification.zig").Notification;
@@ -34,7 +34,7 @@ const HttpClient = @import("../http/Client.zig");
// You can create multiple browser instances.
// A browser contains only one session.
pub const Browser = struct {
env: *Env,
env: *js.Env,
app: *App,
session: ?Session,
allocator: Allocator,
@@ -48,7 +48,7 @@ pub const Browser = struct {
pub fn init(app: *App) !Browser {
const allocator = app.allocator;
const env = try Env.init(allocator, &app.platform, .{});
const env = try js.Env.init(allocator, &app.platform, .{});
errdefer env.deinit();
const notification = try Notification.init(allocator, app.notification);

View File

@@ -20,47 +20,47 @@ const std = @import("std");
const builtin = @import("builtin");
const log = @import("../../log.zig");
const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const JsObject = @import("../env.zig").Env.JsObject;
pub const Console = struct {
// TODO: configurable writer
timers: std.StringHashMapUnmanaged(u32) = .{},
counts: std.StringHashMapUnmanaged(u32) = .{},
pub fn _lp(values: []JsObject, page: *Page) !void {
pub fn _lp(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.fatal(.console, "lightpanda", .{ .args = try serializeValues(values, page) });
}
pub fn _log(values: []JsObject, page: *Page) !void {
pub fn _log(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.info(.console, "info", .{ .args = try serializeValues(values, page) });
}
pub fn _info(values: []JsObject, page: *Page) !void {
pub fn _info(values: []js.JsObject, page: *Page) !void {
return _log(values, page);
}
pub fn _debug(values: []JsObject, page: *Page) !void {
pub fn _debug(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.debug(.console, "debug", .{ .args = try serializeValues(values, page) });
}
pub fn _warn(values: []JsObject, page: *Page) !void {
pub fn _warn(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.warn(.console, "warn", .{ .args = try serializeValues(values, page) });
}
pub fn _error(values: []JsObject, page: *Page) !void {
pub fn _error(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
@@ -132,7 +132,7 @@ pub const Console = struct {
log.warn(.console, "timer stop", .{ .label = label, .elapsed = elapsed - kv.value });
}
pub fn _assert(assertion: JsObject, values: []JsObject, page: *Page) !void {
pub fn _assert(assertion: js.JsObject, values: []js.JsObject, page: *Page) !void {
if (assertion.isTruthy()) {
return;
}
@@ -143,7 +143,7 @@ pub const Console = struct {
log.info(.console, "assertion failed", .{ .values = serialized_values });
}
fn serializeValues(values: []JsObject, page: *Page) ![]const u8 {
fn serializeValues(values: []js.JsObject, page: *Page) ![]const u8 {
if (values.len == 0) {
return "";
}

View File

@@ -17,14 +17,14 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const uuidv4 = @import("../../id.zig").uuidv4;
// https://w3c.github.io/webcrypto/#crypto-interface
pub const Crypto = struct {
_not_empty: bool = true,
pub fn _getRandomValues(_: *const Crypto, js_obj: Env.JsObject) !Env.JsObject {
pub fn _getRandomValues(_: *const Crypto, js_obj: js.JsObject) !js.JsObject {
var into = try js_obj.toZig(Crypto, "getRandomValues", RandomValues);
const buf = into.asBuffer();
if (buf.len > 65_536) {

View File

@@ -18,7 +18,7 @@
const std = @import("std");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const StyleSheet = @import("StyleSheet.zig");
const CSSRuleList = @import("CSSRuleList.zig");
@@ -73,7 +73,7 @@ pub fn _deleteRule(self: *CSSStyleSheet, index: usize) !void {
_ = self.css_rules.list.orderedRemove(index);
}
pub fn _replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !Env.Promise {
pub fn _replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !js.Promise {
_ = self;
_ = text;
// TODO: clear self.css_rules

View File

@@ -18,19 +18,17 @@
const std = @import("std");
const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const JsObject = @import("../env.zig").JsObject;
const Promise = @import("../env.zig").Promise;
const PromiseResolver = @import("../env.zig").PromiseResolver;
const Animation = @This();
effect: ?JsObject,
timeline: ?JsObject,
ready_resolver: ?PromiseResolver,
finished_resolver: ?PromiseResolver,
effect: ?js.JsObject,
timeline: ?js.JsObject,
ready_resolver: ?js.PromiseResolver,
finished_resolver: ?js.PromiseResolver,
pub fn constructor(effect: ?JsObject, timeline: ?JsObject) !Animation {
pub fn constructor(effect: ?js.JsObject, timeline: ?js.JsObject) !Animation {
return .{
.effect = if (effect) |eo| try eo.persist() else null,
.timeline = if (timeline) |to| try to.persist() else null,
@@ -49,7 +47,7 @@ pub fn get_pending(self: *const Animation) bool {
return false;
}
pub fn get_finished(self: *Animation, page: *Page) !Promise {
pub fn get_finished(self: *Animation, page: *Page) !js.Promise {
if (self.finished_resolver == null) {
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(self);
@@ -58,7 +56,7 @@ pub fn get_finished(self: *Animation, page: *Page) !Promise {
return self.finished_resolver.?.promise();
}
pub fn get_ready(self: *Animation, page: *Page) !Promise {
pub fn get_ready(self: *Animation, page: *Page) !js.Promise {
// never resolved, because we're always "finished"
if (self.ready_resolver == null) {
const resolver = page.main_context.createPromiseResolver();
@@ -67,19 +65,19 @@ pub fn get_ready(self: *Animation, page: *Page) !Promise {
return self.ready_resolver.?.promise();
}
pub fn get_effect(self: *const Animation) ?JsObject {
pub fn get_effect(self: *const Animation) ?js.JsObject {
return self.effect;
}
pub fn set_effect(self: *Animation, effect: JsObject) !void {
pub fn set_effect(self: *Animation, effect: js.JsObject) !void {
self.effect = try effect.persist();
}
pub fn get_timeline(self: *const Animation) ?JsObject {
pub fn get_timeline(self: *const Animation) ?js.JsObject {
return self.timeline;
}
pub fn set_timeline(self: *Animation, timeline: JsObject) !void {
pub fn set_timeline(self: *Animation, timeline: js.JsObject) !void {
self.timeline = try timeline.persist();
}

View File

@@ -20,13 +20,11 @@ const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
const EventHandler = @import("../events/event.zig").EventHandler;
const JsObject = Env.JsObject;
const Function = Env.Function;
const Allocator = std.mem.Allocator;
const MAX_QUEUE_SIZE = 10;
@@ -72,22 +70,22 @@ pub const MessagePort = struct {
pair: *MessagePort,
closed: bool = false,
started: bool = false,
onmessage_cbk: ?Function = null,
onmessageerror_cbk: ?Function = null,
onmessage_cbk: ?js.Function = null,
onmessageerror_cbk: ?js.Function = null,
// This is the queue of messages to dispatch to THIS MessagePort when the
// MessagePort is started.
queue: std.ArrayListUnmanaged(JsObject) = .empty,
queue: std.ArrayListUnmanaged(js.JsObject) = .empty,
pub const PostMessageOption = union(enum) {
transfer: JsObject,
transfer: js.JsObject,
options: Opts,
pub const Opts = struct {
transfer: JsObject,
transfer: js.JsObject,
};
};
pub fn _postMessage(self: *MessagePort, obj: JsObject, opts_: ?PostMessageOption, page: *Page) !void {
pub fn _postMessage(self: *MessagePort, obj: js.JsObject, opts_: ?PostMessageOption, page: *Page) !void {
if (self.closed) {
return;
}
@@ -124,10 +122,10 @@ pub const MessagePort = struct {
self.pair.closed = true;
}
pub fn get_onmessage(self: *MessagePort) ?Function {
pub fn get_onmessage(self: *MessagePort) ?js.Function {
return self.onmessage_cbk;
}
pub fn get_onmessageerror(self: *MessagePort) ?Function {
pub fn get_onmessageerror(self: *MessagePort) ?js.Function {
return self.onmessageerror_cbk;
}
@@ -152,7 +150,7 @@ pub const MessagePort = struct {
// called from our pair. If port1.postMessage("x") is called, then this
// will be called on port2.
fn dispatchOrQueue(self: *MessagePort, obj: JsObject, arena: Allocator) !void {
fn dispatchOrQueue(self: *MessagePort, obj: js.JsObject, arena: Allocator) !void {
// our pair should have checked this already
std.debug.assert(self.closed == false);
@@ -167,7 +165,7 @@ pub const MessagePort = struct {
return self.queue.append(arena, try obj.persist());
}
fn dispatch(self: *MessagePort, obj: JsObject) !void {
fn dispatch(self: *MessagePort, obj: js.JsObject) !void {
// obj is already persisted, don't use `MessageEvent.constructor`, but
// go directly to `init`, which assumes persisted objects.
var evt = try MessageEvent.init(.{ .data = obj });
@@ -182,7 +180,7 @@ pub const MessagePort = struct {
alloc: Allocator,
typ: []const u8,
listener: EventHandler.Listener,
) !?Function {
) !?js.Function {
const target = @as(*parser.EventTarget, @ptrCast(self));
const eh = (try EventHandler.register(alloc, target, typ, listener, null)) orelse unreachable;
return eh.callback;
@@ -207,12 +205,12 @@ pub const MessageEvent = struct {
pub const union_make_copy = true;
proto: parser.Event,
data: ?JsObject,
data: ?js.JsObject,
// You would think if port1 sends to port2, the source would be port2
// (which is how I read the documentation), but it appears to always be
// null. It can always be set explicitly via the constructor;
source: ?JsObject,
source: ?js.JsObject,
origin: []const u8,
@@ -226,8 +224,8 @@ pub const MessageEvent = struct {
ports: []*MessagePort,
const Options = struct {
data: ?JsObject = null,
source: ?JsObject = null,
data: ?js.JsObject = null,
source: ?js.JsObject = null,
origin: []const u8 = "",
lastEventId: []const u8 = "",
ports: []*MessagePort = &.{},
@@ -243,7 +241,7 @@ pub const MessageEvent = struct {
});
}
// This is like "constructor", but it assumes JsObjects have already been
// This is like "constructor", but it assumes js.JsObjects have already been
// persisted. Necessary because this `new MessageEvent()` can be called
// directly from JS OR from a port.postMessage. In the latter case, data
// may have already been persisted (as it might need to be queued);
@@ -263,7 +261,7 @@ pub const MessageEvent = struct {
};
}
pub fn get_data(self: *const MessageEvent) !?JsObject {
pub fn get_data(self: *const MessageEvent) !?js.JsObject {
return self.data;
}
@@ -271,7 +269,7 @@ pub const MessageEvent = struct {
return self.origin;
}
pub fn get_source(self: *const MessageEvent) ?JsObject {
pub fn get_source(self: *const MessageEvent) ?js.JsObject {
return self.source;
}

View File

@@ -18,6 +18,7 @@
const std = @import("std");
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page;
@@ -37,8 +38,6 @@ const Range = @import("range.zig").Range;
const CustomEvent = @import("../events/custom_event.zig").CustomEvent;
const Env = @import("../env.zig").Env;
const DOMImplementation = @import("implementation.zig").DOMImplementation;
// WEB IDL https://dom.spec.whatwg.org/#document
@@ -155,13 +154,13 @@ pub const Document = struct {
// the spec changed to return an HTMLCollection instead.
// That's why we reimplemented getElementsByTagName by using an
// HTMLCollection in zig here.
pub fn _getElementsByTagName(self: *parser.Document, tag_name: Env.String) !collection.HTMLCollection {
pub fn _getElementsByTagName(self: *parser.Document, tag_name: js.String) !collection.HTMLCollection {
return collection.HTMLCollectionByTagName(parser.documentToNode(self), tag_name.string, .{
.include_root = true,
});
}
pub fn _getElementsByClassName(self: *parser.Document, class_names: Env.String) !collection.HTMLCollection {
pub fn _getElementsByClassName(self: *parser.Document, class_names: js.String) !collection.HTMLCollection {
return collection.HTMLCollectionByClassName(parser.documentToNode(self), class_names.string, .{
.include_root = true,
});
@@ -299,7 +298,7 @@ pub const Document = struct {
return &.{};
}
pub fn get_adoptedStyleSheets(self: *parser.Document, page: *Page) !Env.JsObject {
pub fn get_adoptedStyleSheets(self: *parser.Document, page: *Page) !js.JsObject {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
if (state.adopted_style_sheets) |obj| {
return obj;
@@ -310,7 +309,7 @@ pub const Document = struct {
return obj;
}
pub fn set_adoptedStyleSheets(self: *parser.Document, sheets: Env.JsObject, page: *Page) !void {
pub fn set_adoptedStyleSheets(self: *parser.Document, sheets: js.JsObject, page: *Page) !void {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
state.adopted_style_sheets = try sheets.persist();
}

View File

@@ -18,8 +18,8 @@
const std = @import("std");
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const css = @import("css.zig");
@@ -34,7 +34,6 @@ const HTMLElem = @import("../html/elements.zig");
const ShadowRoot = @import("../dom/shadow_root.zig").ShadowRoot;
const Animation = @import("Animation.zig");
const JsObject = @import("../env.zig").JsObject;
pub const Union = @import("../html/elements.zig").Union;
@@ -436,7 +435,7 @@ pub const Element = struct {
return try parser.elementRemoveAttributeNode(self, attr);
}
pub fn _getElementsByTagName(self: *parser.Element, tag_name: Env.String) !collection.HTMLCollection {
pub fn _getElementsByTagName(self: *parser.Element, tag_name: js.String) !collection.HTMLCollection {
return collection.HTMLCollectionByTagName(
parser.elementToNode(self),
tag_name.string,
@@ -444,7 +443,7 @@ pub const Element = struct {
);
}
pub fn _getElementsByClassName(self: *parser.Element, class_names: Env.String) !collection.HTMLCollection {
pub fn _getElementsByClassName(self: *parser.Element, class_names: js.String) !collection.HTMLCollection {
return try collection.HTMLCollectionByClassName(
parser.elementToNode(self),
class_names.string,
@@ -661,7 +660,7 @@ pub const Element = struct {
return sr;
}
pub fn _animate(self: *parser.Element, effect: JsObject, opts: JsObject) !Animation {
pub fn _animate(self: *parser.Element, effect: js.JsObject, opts: js.JsObject) !Animation {
_ = self;
_ = opts;
return Animation.constructor(effect, null);

View File

@@ -23,7 +23,6 @@ const parser = @import("../netsurf.zig");
const Element = @import("element.zig").Element;
const Union = @import("element.zig").Union;
const JsThis = @import("../env.zig").JsThis;
const Walker = @import("walker.zig").Walker;
const Matcher = union(enum) {

View File

@@ -18,11 +18,11 @@
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const Element = @import("element.zig").Element;
pub const Interfaces = .{
@@ -40,14 +40,14 @@ pub const Interfaces = .{
// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver
pub const IntersectionObserver = struct {
page: *Page,
callback: Env.Function,
callback: js.Function,
options: IntersectionObserverOptions,
observed_entries: std.ArrayListUnmanaged(IntersectionObserverEntry),
// new IntersectionObserver(callback)
// new IntersectionObserver(callback, options) [not supported yet]
pub fn constructor(callback: Env.Function, options_: ?IntersectionObserverOptions, page: *Page) !IntersectionObserver {
pub fn constructor(callback: js.Function, options_: ?IntersectionObserverOptions, page: *Page) !IntersectionObserver {
var options = IntersectionObserverOptions{
.root = parser.documentToNode(parser.documentHTMLToDocument(page.window.document)),
.rootMargin = "0px 0px 0px 0px",
@@ -84,7 +84,7 @@ pub const IntersectionObserver = struct {
.options = &self.options,
});
var result: Env.Function.Result = undefined;
var result: js.Function.Result = undefined;
self.callback.tryCall(void, .{self.observed_entries.items}, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,

View File

@@ -18,11 +18,11 @@
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const NodeList = @import("nodelist.zig").NodeList;
pub const Interfaces = .{
@@ -35,7 +35,7 @@ const Walker = @import("../dom/walker.zig").WalkerChildren;
// WEB IDL https://dom.spec.whatwg.org/#interface-mutationobserver
pub const MutationObserver = struct {
page: *Page,
cbk: Env.Function,
cbk: js.Function,
scheduled: bool,
observers: std.ArrayListUnmanaged(*Observer),
@@ -43,7 +43,7 @@ pub const MutationObserver = struct {
// execute our callback with it.
observed: std.ArrayListUnmanaged(MutationRecord),
pub fn constructor(cbk: Env.Function, page: *Page) !MutationObserver {
pub fn constructor(cbk: js.Function, page: *Page) !MutationObserver {
return .{
.cbk = cbk,
.page = page,
@@ -122,7 +122,7 @@ pub const MutationObserver = struct {
defer self.observed.clearRetainingCapacity();
var result: Env.Function.Result = undefined;
var result: js.Function.Result = undefined;
self.cbk.tryCallWithThis(void, self, .{records}, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const generate = @import("../../runtime/generate.zig");
const generate = @import("../js/generate.zig");
const Page = @import("../page.zig").Page;
const EventTarget = @import("event_target.zig").EventTarget;

View File

@@ -17,8 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const Node = @import("node.zig").Node;
pub const NodeFilter = struct {
@@ -43,7 +43,7 @@ pub const NodeFilter = struct {
const VerifyResult = enum { accept, skip, reject };
pub fn verify(what_to_show: u32, filter: ?Env.Function, node: *parser.Node) !VerifyResult {
pub fn verify(what_to_show: u32, filter: ?js.Function, node: *parser.Node) !VerifyResult {
const node_type = parser.nodeType(node);
// Verify that we can show this node type.

View File

@@ -18,8 +18,8 @@
const std = @import("std");
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const NodeFilter = @import("node_filter.zig");
const Node = @import("node.zig").Node;
const NodeUnion = @import("node.zig").Union;
@@ -37,7 +37,7 @@ pub const NodeIterator = struct {
reference_node: *parser.Node,
what_to_show: u32,
filter: ?NodeIteratorOpts,
filter_func: ?Env.Function,
filter_func: ?js.Function,
pointer_before_current: bool = true,
// used to track / block recursive filters
is_in_callback: bool = false,
@@ -45,15 +45,15 @@ pub const NodeIterator = struct {
// One of the few cases where null and undefined resolve to different default.
// We need the raw JsObject so that we can probe the tri state:
// null, undefined or i32.
pub const WhatToShow = Env.JsObject;
pub const WhatToShow = js.JsObject;
pub const NodeIteratorOpts = union(enum) {
function: Env.Function,
object: struct { acceptNode: Env.Function },
function: js.Function,
object: struct { acceptNode: js.Function },
};
pub fn init(node: *parser.Node, what_to_show_: ?WhatToShow, filter: ?NodeIteratorOpts) !NodeIterator {
var filter_func: ?Env.Function = null;
var filter_func: ?js.Function = null;
if (filter) |f| {
filter_func = switch (f) {
.function => |func| func,

View File

@@ -19,11 +19,10 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const JsThis = @import("../env.zig").JsThis;
const Function = @import("../env.zig").Function;
const NodeUnion = @import("node.zig").Union;
const Node = @import("node.zig").Node;
@@ -148,10 +147,10 @@ pub const NodeList = struct {
// };
// }
pub fn _forEach(self: *NodeList, cbk: Function) !void { // TODO handle thisArg
pub fn _forEach(self: *NodeList, cbk: js.Function) !void { // TODO handle thisArg
for (self.nodes.items, 0..) |n, i| {
const ii: u32 = @intCast(i);
var result: Function.Result = undefined;
var result: js.Function.Result = undefined;
cbk.tryCall(void, .{ n, ii, self }, &result) catch {
log.debug(.user_script, "forEach callback", .{ .err = result.exception, .stack = result.stack });
};
@@ -175,7 +174,7 @@ pub const NodeList = struct {
}
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
pub fn postAttach(self: *NodeList, js_this: JsThis) !void {
pub fn postAttach(self: *NodeList, js_this: js.JsThis) !void {
const len = self.get_length();
for (0..len) |i| {
const node = try self._item(@intCast(i)) orelse unreachable;

View File

@@ -18,9 +18,9 @@
const std = @import("std");
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const EventTarget = @import("../dom/event_target.zig").EventTarget;
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const milliTimestamp = @import("../../datetime.zig").milliTimestamp;
@@ -61,7 +61,7 @@ pub const Performance = struct {
return milliTimestamp() - self.time_origin;
}
pub fn _mark(_: *Performance, name: Env.String, _options: ?PerformanceMark.Options, page: *Page) !PerformanceMark {
pub fn _mark(_: *Performance, name: js.String, _options: ?PerformanceMark.Options, page: *Page) !PerformanceMark {
const mark: PerformanceMark = try .constructor(name, _options, page);
// TODO: Should store this in an entries list
return mark;
@@ -148,14 +148,14 @@ pub const PerformanceMark = struct {
pub const prototype = *PerformanceEntry;
proto: PerformanceEntry,
detail: ?Env.JsObject,
detail: ?js.JsObject,
const Options = struct {
detail: ?Env.JsObject = null,
detail: ?js.JsObject = null,
startTime: ?f64 = null,
};
pub fn constructor(name: Env.String, _options: ?Options, page: *Page) !PerformanceMark {
pub fn constructor(name: js.String, _options: ?Options, page: *Page) !PerformanceMark {
const perf = &page.window.performance;
const options = _options orelse Options{};
@@ -171,7 +171,7 @@ pub const PerformanceMark = struct {
return .{ .proto = proto, .detail = detail };
}
pub fn get_detail(self: *const PerformanceMark) ?Env.JsObject {
pub fn get_detail(self: *const PerformanceMark) ?js.JsObject {
return self.detail;
}
};

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const PerformanceEntry = @import("performance.zig").PerformanceEntry;
@@ -25,7 +25,7 @@ const PerformanceEntry = @import("performance.zig").PerformanceEntry;
pub const PerformanceObserver = struct {
pub const _supportedEntryTypes = [0][]const u8{};
pub fn constructor(cbk: Env.Function) PerformanceObserver {
pub fn constructor(cbk: js.Function) PerformanceObserver {
_ = cbk;
return .{};
}

View File

@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
pub const Interfaces = .{
@@ -25,7 +25,7 @@ pub const Interfaces = .{
// WEB IDL https://drafts.csswg.org/resize-observer/#resize-observer-interface
pub const ResizeObserver = struct {
pub fn constructor(cbk: Env.Function) ResizeObserver {
pub fn constructor(cbk: js.Function) ResizeObserver {
_ = cbk;
return .{};
}

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const dump = @import("../dump.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const js = @import(".././js/js.zig");
const Page = @import("../page.zig").Page;
const Node = @import("node.zig").Node;
const Element = @import("element.zig").Element;
@@ -34,7 +34,7 @@ pub const ShadowRoot = struct {
mode: Mode,
host: *parser.Element,
proto: *parser.DocumentFragment,
adopted_style_sheets: ?Env.JsObject = null,
adopted_style_sheets: ?js.JsObject = null,
pub const Mode = enum {
open,
@@ -45,7 +45,7 @@ pub const ShadowRoot = struct {
return Element.toInterface(self.host);
}
pub fn get_adoptedStyleSheets(self: *ShadowRoot, page: *Page) !Env.JsObject {
pub fn get_adoptedStyleSheets(self: *ShadowRoot, page: *Page) !js.JsObject {
if (self.adopted_style_sheets) |obj| {
return obj;
}
@@ -55,7 +55,7 @@ pub const ShadowRoot = struct {
return obj;
}
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: Env.JsObject) !void {
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: js.JsObject) !void {
self.adopted_style_sheets = try sheets.persist();
}

View File

@@ -18,12 +18,11 @@
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const iterator = @import("../iterator/iterator.zig");
const Function = @import("../env.zig").Function;
const JsObject = @import("../env.zig").JsObject;
const DOMException = @import("exceptions.zig").DOMException;
pub const Interfaces = .{
@@ -137,10 +136,10 @@ pub const DOMTokenList = struct {
}
// TODO handle thisArg
pub fn _forEach(self: *parser.TokenList, cbk: Function, this_arg: JsObject) !void {
pub fn _forEach(self: *parser.TokenList, cbk: js.Function, this_arg: js.JsObject) !void {
var entries = _entries(self);
while (try entries._next()) |entry| {
var result: Function.Result = undefined;
var result: js.Function.Result = undefined;
cbk.tryCallWithThis(void, this_arg, .{ entry.@"1", entry.@"0", self }, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,

View File

@@ -17,10 +17,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const NodeFilter = @import("node_filter.zig");
const Env = @import("../env.zig").Env;
const Node = @import("node.zig").Node;
const NodeUnion = @import("node.zig").Union;
@@ -30,20 +30,20 @@ pub const TreeWalker = struct {
current_node: *parser.Node,
what_to_show: u32,
filter: ?TreeWalkerOpts,
filter_func: ?Env.Function,
filter_func: ?js.Function,
// One of the few cases where null and undefined resolve to different default.
// We need the raw JsObject so that we can probe the tri state:
// null, undefined or i32.
pub const WhatToShow = Env.JsObject;
pub const WhatToShow = js.JsObject;
pub const TreeWalkerOpts = union(enum) {
function: Env.Function,
object: struct { acceptNode: Env.Function },
function: js.Function,
object: struct { acceptNode: js.Function },
};
pub fn init(node: *parser.Node, what_to_show_: ?WhatToShow, filter: ?TreeWalkerOpts) !TreeWalker {
var filter_func: ?Env.Function = null;
var filter_func: ?js.Function = null;
if (filter) |f| {
filter_func = switch (f) {

View File

@@ -19,7 +19,6 @@
const std = @import("std");
const log = @import("../../log.zig");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
// https://encoding.spec.whatwg.org/#interface-textdecoder

View File

@@ -18,7 +18,7 @@
const std = @import("std");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
// https://encoding.spec.whatwg.org/#interface-textencoder
const TextEncoder = @This();
@@ -31,7 +31,7 @@ pub fn get_encoding(_: *const TextEncoder) []const u8 {
return "utf-8";
}
pub fn _encode(_: *const TextEncoder, v: []const u8) !Env.TypedArray(u8) {
pub fn _encode(_: *const TextEncoder, v: []const u8) !js.TypedArray(u8) {
// Ensure the input is a valid utf-8
// It seems chrome accepts invalid utf-8 sequence.
//

View File

@@ -1,51 +0,0 @@
const std = @import("std");
const Page = @import("page.zig").Page;
const js = @import("../runtime/js.zig");
const generate = @import("../runtime/generate.zig");
const WebApis = struct {
// Wrapped like this for debug ergonomics.
// When we create our Env, a few lines down, we define it as:
// pub const Env = js.Env(*Page, WebApis);
//
// If there's a compile time error witht he Env, it's type will be readable,
// i.e.: runtime.js.Env(*browser.env.Page, browser.env.WebApis)
//
// But if we didn't wrap it in the struct, like we once didn't, and defined
// env as:
// pub const Env = js.Env(*Page, Interfaces);
//
// Because Interfaces is an anynoumous type, it doesn't have a friendly name
// and errors would be something like:
// runtime.js.Env(*browser.Page, .{...A HUNDRED TYPES...})
pub const Interfaces = generate.Tuple(.{
@import("crypto/crypto.zig").Crypto,
@import("console/console.zig").Console,
@import("css/css.zig").Interfaces,
@import("cssom/cssom.zig").Interfaces,
@import("dom/dom.zig").Interfaces,
@import("dom/shadow_root.zig").ShadowRoot,
@import("encoding/encoding.zig").Interfaces,
@import("events/event.zig").Interfaces,
@import("html/html.zig").Interfaces,
@import("iterator/iterator.zig").Interfaces,
@import("storage/storage.zig").Interfaces,
@import("url/url.zig").Interfaces,
@import("xhr/xhr.zig").Interfaces,
@import("xhr/form_data.zig").Interfaces,
@import("xhr/File.zig"),
@import("xmlserializer/xmlserializer.zig").Interfaces,
@import("fetch/fetch.zig").Interfaces,
@import("streams/streams.zig").Interfaces,
});
};
pub const JsThis = Env.JsThis;
pub const JsObject = Env.JsObject;
pub const Function = Env.Function;
pub const Promise = Env.Promise;
pub const PromiseResolver = Env.PromiseResolver;
pub const Env = js.Env(*Page, WebApis);
pub const Global = @import("html/window.zig").Window;

View File

@@ -16,9 +16,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Event = @import("event.zig").Event;
const JsObject = @import("../env.zig").JsObject;
const netsurf = @import("../netsurf.zig");
// https://dom.spec.whatwg.org/#interface-customevent
@@ -27,13 +28,13 @@ pub const CustomEvent = struct {
pub const union_make_copy = true;
proto: parser.Event,
detail: ?JsObject,
detail: ?js.JsObject,
const CustomEventInit = struct {
bubbles: bool = false,
cancelable: bool = false,
composed: bool = false,
detail: ?JsObject = null,
detail: ?js.JsObject = null,
};
pub fn constructor(event_type: []const u8, opts_: ?CustomEventInit) !CustomEvent {
@@ -53,7 +54,7 @@ pub const CustomEvent = struct {
};
}
pub fn get_detail(self: *CustomEvent) ?JsObject {
pub fn get_detail(self: *CustomEvent) ?js.JsObject {
return self.detail;
}
@@ -64,7 +65,7 @@ pub const CustomEvent = struct {
event_type: []const u8,
can_bubble: bool,
cancelable: bool,
maybe_detail: ?JsObject,
maybe_detail: ?js.JsObject,
) !void {
// This function can only be called after the constructor has called.
// So we assume proto is initialized already by constructor.

View File

@@ -21,7 +21,7 @@ const Allocator = std.mem.Allocator;
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const generate = @import("../../runtime/generate.zig");
const generate = @import("../js/generate.zig");
const Page = @import("../page.zig").Page;
const Node = @import("../dom/node.zig").Node;
@@ -219,18 +219,17 @@ pub const Event = struct {
pub const EventHandler = struct {
once: bool,
capture: bool,
callback: Function,
callback: js.Function,
node: parser.EventNode,
listener: *parser.EventListener,
const Env = @import("../env.zig").Env;
const Function = Env.Function;
const js = @import("../js/js.zig");
pub const Listener = union(enum) {
function: Function,
object: Env.JsObject,
function: js.Function,
object: js.JsObject,
pub fn callback(self: Listener, target: *parser.EventTarget) !?Function {
pub fn callback(self: Listener, target: *parser.EventTarget) !?js.Function {
return switch (self) {
.function => |func| try func.withThis(target),
.object => |obj| blk: {
@@ -331,7 +330,7 @@ pub const EventHandler = struct {
fn handle(node: *parser.EventNode, event: *parser.Event) void {
const ievent = Event.toInterface(event);
const self: *EventHandler = @fieldParentPtr("node", node);
var result: Function.Result = undefined;
var result: js.Function.Result = undefined;
self.callback.tryCall(void, .{ievent}, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const URL = @import("../../url.zig").URL;
const Page = @import("../page.zig").Page;
@@ -24,8 +25,6 @@ const Page = @import("../page.zig").Page;
const iterator = @import("../iterator/iterator.zig");
const v8 = @import("v8");
const Env = @import("../env.zig").Env;
// https://developer.mozilla.org/en-US/docs/Web/API/Headers
const Headers = @This();
@@ -69,7 +68,7 @@ pub const HeadersInit = union(enum) {
// Headers
headers: *Headers,
// Mappings
object: Env.JsObject,
object: js.JsObject,
};
pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers {
@@ -159,7 +158,7 @@ pub fn _entries(self: *const Headers) HeadersEntryIterable {
};
}
pub fn _forEach(self: *Headers, callback_fn: Env.Function, this_arg: ?Env.JsObject) !void {
pub fn _forEach(self: *Headers, callback_fn: js.Function, this_arg: ?js.JsObject) !void {
var iter = self.headers.iterator();
const cb = if (this_arg) |this| try callback_fn.withThis(this) else callback_fn;

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const URL = @import("../../url.zig").URL;
@@ -27,7 +28,6 @@ const Http = @import("../../http/Http.zig");
const ReadableStream = @import("../streams/ReadableStream.zig");
const v8 = @import("v8");
const Env = @import("../env.zig").Env;
const Headers = @import("Headers.zig");
const HeadersInit = @import("Headers.zig").HeadersInit;
@@ -241,7 +241,7 @@ pub fn _clone(self: *Request) !Request {
};
}
pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
pub fn _bytes(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
@@ -253,7 +253,7 @@ pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
pub fn _json(self: *Response, page: *Page) !Env.Promise {
pub fn _json(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
@@ -280,7 +280,7 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
pub fn _text(self: *Response, page: *Page) !Env.Promise {
pub fn _text(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const v8 = @import("v8");
@@ -29,7 +30,6 @@ const ReadableStream = @import("../streams/ReadableStream.zig");
const Headers = @import("Headers.zig");
const HeadersInit = @import("Headers.zig").HeadersInit;
const Env = @import("../env.zig").Env;
const Mime = @import("../mime.zig").Mime;
const Page = @import("../page.zig").Page;
@@ -165,12 +165,12 @@ pub fn _clone(self: *const Response) !Response {
};
}
pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
pub fn _bytes(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
const resolver = Env.PromiseResolver{
const resolver = js.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
@@ -180,7 +180,7 @@ pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
pub fn _json(self: *Response, page: *Page) !Env.Promise {
pub fn _json(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
@@ -207,7 +207,7 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
pub fn _text(self: *Response, page: *Page) !Env.Promise {
pub fn _text(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}

View File

@@ -19,7 +19,7 @@
const std = @import("std");
const log = @import("../../log.zig");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const Http = @import("../../http/Http.zig");
@@ -45,7 +45,7 @@ pub const Interfaces = .{
pub const FetchContext = struct {
page: *Page,
arena: std.mem.Allocator,
promise_resolver: Env.PersistentPromiseResolver,
promise_resolver: js.PersistentPromiseResolver,
method: Http.Method,
url: []const u8,
@@ -111,7 +111,7 @@ pub const FetchContext = struct {
};
// https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promise {
pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !js.Promise {
const arena = page.arena;
const req = try Request.constructor(input, options, page);

View File

@@ -17,9 +17,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
@@ -113,7 +113,7 @@ pub const AbortSignal = struct {
}
const ThrowIfAborted = union(enum) {
exception: Env.Exception,
exception: js.Exception,
undefined: void,
};
pub fn _throwIfAborted(self: *const AbortSignal, page: *Page) ThrowIfAborted {

View File

@@ -17,7 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const Allocator = std.mem.Allocator;
@@ -26,7 +27,7 @@ const DataSet = @This();
element: *parser.Element,
pub fn named_get(self: *const DataSet, name: []const u8, _: *bool, page: *Page) !Env.UndefinedOr([]const u8) {
pub fn named_get(self: *const DataSet, name: []const u8, _: *bool, page: *Page) !js.UndefinedOr([]const u8) {
const normalized_name = try normalize(page.call_arena, name);
if (try parser.elementGetAttribute(self.element, normalized_name)) |value| {
return .{ .value = value };

View File

@@ -19,7 +19,7 @@
const std = @import("std");
const log = @import("../../log.zig");
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface
@@ -67,11 +67,11 @@ pub fn set_scrollRestoration(self: *History, mode: []const u8) void {
self.scroll_restoration = ScrollRestorationMode.fromString(mode) orelse self.scroll_restoration;
}
pub fn get_state(self: *History, page: *Page) !?Env.Value {
pub fn get_state(self: *History, page: *Page) !?js.Value {
if (self.current) |curr| {
const entry = self.stack.items[curr];
if (entry.state) |state| {
const value = try Env.Value.fromJson(page.main_context, state);
const value = try js.Value.fromJson(page.main_context, state);
return value;
} else {
return null;
@@ -113,7 +113,7 @@ fn _dispatchPopStateEvent(state: ?[]const u8, page: *Page) !void {
);
}
pub fn _pushState(self: *History, state: Env.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
pub fn _pushState(self: *History, state: js.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
const arena = page.session.arena;
const json = try state.toJson(arena);
@@ -123,7 +123,7 @@ pub fn _pushState(self: *History, state: Env.JsObject, _: ?[]const u8, _url: ?[]
self.current = self.stack.items.len - 1;
}
pub fn _replaceState(self: *History, state: Env.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
pub fn _replaceState(self: *History, state: js.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
const arena = page.session.arena;
if (self.current) |curr| {
@@ -199,9 +199,9 @@ pub const PopStateEvent = struct {
// `hasUAVisualTransition` is not implemented. It isn't baseline so this is okay.
pub fn get_state(self: *const PopStateEvent, page: *Page) !?Env.Value {
pub fn get_state(self: *const PopStateEvent, page: *Page) !?js.Value {
if (self.state) |state| {
const value = try Env.Value.fromJson(page.main_context, state);
const value = try js.Value.fromJson(page.main_context, state);
return value;
} else {
return null;

View File

@@ -18,9 +18,9 @@
const std = @import("std");
const log = @import("../../log.zig");
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const generate = @import("../../runtime/generate.zig");
const Env = @import("../env.zig").Env;
const generate = @import("../js/generate.zig");
const Page = @import("../page.zig").Page;
const urlStitch = @import("../../url.zig").URL.stitch;
@@ -1000,22 +1000,22 @@ pub const HTMLScriptElement = struct {
);
}
pub fn get_onload(self: *parser.Script, page: *Page) !?Env.Function {
pub fn get_onload(self: *parser.Script, page: *Page) !?js.Function {
const state = page.getNodeState(@ptrCast(@alignCast(self))) orelse return null;
return state.onload;
}
pub fn set_onload(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
pub fn set_onload(self: *parser.Script, function: ?js.Function, page: *Page) !void {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
state.onload = function;
}
pub fn get_onerror(self: *parser.Script, page: *Page) !?Env.Function {
pub fn get_onerror(self: *parser.Script, page: *Page) !?js.Function {
const state = page.getNodeState(@ptrCast(@alignCast(self))) orelse return null;
return state.onerror;
}
pub fn set_onerror(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
pub fn set_onerror(self: *parser.Script, function: ?js.Function, page: *Page) !void {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
state.onerror = function;
}

View File

@@ -15,7 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Env = @import("../env.zig").Env;
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
// https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
@@ -28,14 +28,14 @@ pub const ErrorEvent = struct {
filename: []const u8,
lineno: i32,
colno: i32,
@"error": ?Env.JsObject,
@"error": ?js.JsObject,
const ErrorEventInit = struct {
message: []const u8 = "",
filename: []const u8 = "",
lineno: i32 = 0,
colno: i32 = 0,
@"error": ?Env.JsObject = null,
@"error": ?js.JsObject = null,
};
pub fn constructor(event_type: []const u8, opts: ?ErrorEventInit) !ErrorEvent {
@@ -72,7 +72,7 @@ pub const ErrorEvent = struct {
return self.colno;
}
pub fn get_error(self: *const ErrorEvent) Env.UndefinedOr(Env.JsObject) {
pub fn get_error(self: *const ErrorEvent) js.UndefinedOr(js.JsObject) {
if (self.@"error") |e| {
return .{ .value = e };
}

View File

@@ -16,8 +16,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Function = @import("../env.zig").Function;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
// https://drafts.csswg.org/cssom-view/#the-mediaquerylist-interface
@@ -39,7 +39,7 @@ pub const MediaQueryList = struct {
return self.media;
}
pub fn _addListener(_: *const MediaQueryList, _: Function) void {}
pub fn _addListener(_: *const MediaQueryList, _: js.Function) void {}
pub fn _removeListener(_: *const MediaQueryList, _: Function) void {}
pub fn _removeListener(_: *const MediaQueryList, _: js.Function) void {}
};

View File

@@ -18,9 +18,9 @@
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const Navigator = @import("navigator.zig").Navigator;
@@ -37,8 +37,6 @@ const domcss = @import("../dom/css.zig");
const Css = @import("../css/css.zig").Css;
const EventHandler = @import("../events/event.zig").EventHandler;
const Function = Env.Function;
const v8 = @import("v8");
const Request = @import("../fetch/Request.zig");
const fetchFn = @import("../fetch/fetch.zig").fetch;
@@ -70,7 +68,7 @@ pub const Window = struct {
css: Css = .{},
scroll_x: u32 = 0,
scroll_y: u32 = 0,
onload_callback: ?Function = null,
onload_callback: ?js.Function = null,
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
var fbs = std.io.fixedBufferStream("");
@@ -101,12 +99,12 @@ pub const Window = struct {
self.storage_shelf = shelf;
}
pub fn _fetch(_: *Window, input: Request.RequestInput, options: ?Request.RequestInit, page: *Page) !Env.Promise {
pub fn _fetch(_: *Window, input: Request.RequestInput, options: ?Request.RequestInit, page: *Page) !js.Promise {
return fetchFn(input, options, page);
}
/// Returns `onload_callback`.
pub fn get_onload(self: *const Window) ?Function {
pub fn get_onload(self: *const Window) ?js.Function {
return self.onload_callback;
}
@@ -258,7 +256,7 @@ pub const Window = struct {
return &self.css;
}
pub fn _requestAnimationFrame(self: *Window, cbk: Function, page: *Page) !u32 {
pub fn _requestAnimationFrame(self: *Window, cbk: js.Function, page: *Page) !u32 {
return self.createTimeout(cbk, 5, page, .{
.animation_frame = true,
.name = "animationFrame",
@@ -270,11 +268,11 @@ pub const Window = struct {
_ = self.timers.remove(id);
}
pub fn _setTimeout(self: *Window, cbk: Function, delay: ?u32, params: []Env.JsObject, page: *Page) !u32 {
pub fn _setTimeout(self: *Window, cbk: js.Function, delay: ?u32, params: []js.JsObject, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{ .args = params, .name = "setTimeout" });
}
pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, params: []Env.JsObject, page: *Page) !u32 {
pub fn _setInterval(self: *Window, cbk: js.Function, delay: ?u32, params: []js.JsObject, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{ .repeat = true, .args = params, .name = "setInterval" });
}
@@ -286,11 +284,11 @@ pub const Window = struct {
_ = self.timers.remove(id);
}
pub fn _queueMicrotask(self: *Window, cbk: Function, page: *Page) !u32 {
pub fn _queueMicrotask(self: *Window, cbk: js.Function, page: *Page) !u32 {
return self.createTimeout(cbk, 0, page, .{ .name = "queueMicrotask" });
}
pub fn _setImmediate(self: *Window, cbk: Function, page: *Page) !u32 {
pub fn _setImmediate(self: *Window, cbk: js.Function, page: *Page) !u32 {
return self.createTimeout(cbk, 0, page, .{ .name = "setImmediate" });
}
@@ -298,7 +296,7 @@ pub const Window = struct {
_ = self.timers.remove(id);
}
pub fn _matchMedia(_: *const Window, media: Env.String) !MediaQueryList {
pub fn _matchMedia(_: *const Window, media: js.String) !MediaQueryList {
return .{
.matches = false, // TODO?
.media = media.string,
@@ -322,12 +320,12 @@ pub const Window = struct {
const CreateTimeoutOpts = struct {
name: []const u8,
args: []Env.JsObject = &.{},
args: []js.JsObject = &.{},
repeat: bool = false,
animation_frame: bool = false,
low_priority: bool = false,
};
fn createTimeout(self: *Window, cbk: Function, delay_: ?u32, page: *Page, opts: CreateTimeoutOpts) !u32 {
fn createTimeout(self: *Window, cbk: js.Function, delay_: ?u32, page: *Page, opts: CreateTimeoutOpts) !u32 {
const delay = delay_ orelse 0;
if (self.timers.count() > 512) {
return error.TooManyTimeout;
@@ -347,9 +345,9 @@ pub const Window = struct {
errdefer _ = self.timers.remove(timer_id);
const args = opts.args;
var persisted_args: []Env.JsObject = &.{};
var persisted_args: []js.JsObject = &.{};
if (args.len > 0) {
persisted_args = try page.arena.alloc(Env.JsObject, args.len);
persisted_args = try page.arena.alloc(js.JsObject, args.len);
for (args, persisted_args) |a, *ca| {
ca.* = try a.persist();
}
@@ -476,13 +474,13 @@ const TimerCallback = struct {
repeat: ?u32,
// The JavaScript callback to execute
cbk: Function,
cbk: js.Function,
animation_frame: bool = false,
window: *Window,
args: []Env.JsObject = &.{},
args: []js.JsObject = &.{},
fn run(ctx: *anyopaque) ?u32 {
const self: *TimerCallback = @ptrCast(@alignCast(ctx));
@@ -496,7 +494,7 @@ const TimerCallback = struct {
return null;
}
var result: Function.Result = undefined;
var result: js.Function.Result = undefined;
var call: anyerror!void = undefined;
if (self.animation_frame) {

View File

@@ -190,7 +190,7 @@ test "generate: Union" {
const value = Union(.{ Astruct, Bstruct, .{Cstruct} });
const ti = @typeInfo(value).@"union";
try std.testing.expectEqual(3, ti.fields.len);
try std.testing.expectEqualStrings("*runtime.generate.test.generate: Union.Astruct.Other", @typeName(ti.fields[0].type));
try std.testing.expectEqualStrings("*browser.js.generate.test.generate: Union.Astruct.Other", @typeName(ti.fields[0].type));
try std.testing.expectEqualStrings(ti.fields[0].name, "Astruct");
try std.testing.expectEqual(*Bstruct, ti.fields[1].type);
try std.testing.expectEqualStrings(ti.fields[1].name, "Bstruct");

4331
src/browser/js/js.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,6 @@ const Allocator = std.mem.Allocator;
const Dump = @import("dump.zig");
const State = @import("State.zig");
const Env = @import("env.zig").Env;
const Mime = @import("mime.zig").Mime;
const Session = @import("session.zig").Session;
const Renderer = @import("renderer.zig").Renderer;
@@ -35,6 +34,7 @@ const ScriptManager = @import("ScriptManager.zig");
const SlotChangeMonitor = @import("SlotChangeMonitor.zig");
const HTMLDocument = @import("html/document.zig").HTMLDocument;
const js = @import("js/js.zig");
const URL = @import("../url.zig").URL;
const log = @import("../log.zig");
@@ -74,7 +74,7 @@ pub const Page = struct {
// Our JavaScript context for this specific page. This is what we use to
// execute any JavaScript
main_context: *Env.JsContext,
main_context: *js.JsContext,
// indicates intention to navigate to another page on the next loop execution.
delayed_navigation: bool = false,
@@ -143,7 +143,7 @@ pub const Page = struct {
.main_context = undefined,
};
self.main_context = try session.executor.createJsContext(&self.window, self, &self.script_manager, true, Env.GlobalMissingCallback.init(&self.polyfill_loader));
self.main_context = try session.executor.createJsContext(&self.window, self, &self.script_manager, true, js.GlobalMissingCallback.init(&self.polyfill_loader));
try polyfill.preload(self.arena, self.main_context);
try self.scheduler.add(self, runMicrotasks, 5, .{ .name = "page.microtasks" });
@@ -276,7 +276,7 @@ pub const Page = struct {
var timer = try std.time.Timer.start();
var ms_remaining = wait_ms;
var try_catch: Env.TryCatch = undefined;
var try_catch: js.TryCatch = undefined;
try_catch.init(self.main_context);
defer try_catch.deinit();

View File

@@ -19,9 +19,9 @@
const std = @import("std");
const builtin = @import("builtin");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const Allocator = std.mem.Allocator;
const Env = @import("../env.zig").Env;
pub const Loader = struct {
state: enum { empty, loading } = .empty,
@@ -30,8 +30,8 @@ pub const Loader = struct {
webcomponents: bool = false,
} = .{},
fn load(self: *Loader, comptime name: []const u8, source: []const u8, js_context: *Env.JsContext) void {
var try_catch: Env.TryCatch = undefined;
fn load(self: *Loader, comptime name: []const u8, source: []const u8, js_context: *js.JsContext) void {
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();
@@ -49,7 +49,7 @@ pub const Loader = struct {
@field(self.done, name) = true;
}
pub fn missing(self: *Loader, name: []const u8, js_context: *Env.JsContext) bool {
pub fn missing(self: *Loader, name: []const u8, js_context: *js.JsContext) bool {
// Avoid recursive calls during polyfill loading.
if (self.state == .loading) {
return false;
@@ -82,8 +82,8 @@ pub const Loader = struct {
}
};
pub fn preload(allocator: Allocator, js_context: *Env.JsContext) !void {
var try_catch: Env.TryCatch = undefined;
pub fn preload(allocator: Allocator, js_context: *js.JsContext) !void {
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const Env = @import("env.zig").Env;
const js = @import("js/js.zig");
const Page = @import("page.zig").Page;
const Browser = @import("browser.zig").Browser;
const NavigateOpts = @import("page.zig").NavigateOpts;
@@ -50,7 +50,7 @@ pub const Session = struct {
// page and start another.
transfer_arena: Allocator,
executor: Env.ExecutionWorld,
executor: js.ExecutionWorld,
storage_shed: storage.Shed,
cookie_jar: storage.CookieJar,

View File

@@ -17,10 +17,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const Allocator = std.mem.Allocator;
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const ReadableStream = @This();
@@ -31,26 +31,26 @@ const State = union(enum) {
readable,
closed: ?[]const u8,
cancelled: ?[]const u8,
errored: Env.JsObject,
errored: js.JsObject,
};
// This promise resolves when a stream is canceled.
cancel_resolver: Env.PersistentPromiseResolver,
closed_resolver: Env.PersistentPromiseResolver,
reader_resolver: ?Env.PersistentPromiseResolver = null,
cancel_resolver: js.PersistentPromiseResolver,
closed_resolver: js.PersistentPromiseResolver,
reader_resolver: ?js.PersistentPromiseResolver = null,
locked: bool = false,
state: State = .readable,
cancel_fn: ?Env.Function = null,
pull_fn: ?Env.Function = null,
cancel_fn: ?js.Function = null,
pull_fn: ?js.Function = null,
strategy: QueueingStrategy,
queue: std.ArrayListUnmanaged(Chunk) = .empty,
pub const Chunk = union(enum) {
// the order matters, sorry.
uint8array: Env.TypedArray(u8),
uint8array: js.TypedArray(u8),
string: []const u8,
pub fn dupe(self: Chunk, allocator: Allocator) !Chunk {
@@ -91,14 +91,14 @@ pub const ReadableStreamReadResult = struct {
};
const UnderlyingSource = struct {
start: ?Env.Function = null,
pull: ?Env.Function = null,
cancel: ?Env.Function = null,
start: ?js.Function = null,
pull: ?js.Function = null,
cancel: ?js.Function = null,
type: ?[]const u8 = null,
};
const QueueingStrategy = struct {
size: ?Env.Function = null,
size: ?js.Function = null,
high_water_mark: u32 = 1,
};
@@ -146,7 +146,7 @@ pub fn get_locked(self: *const ReadableStream) bool {
return self.locked;
}
pub fn _cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !Env.Promise {
pub fn _cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promise {
if (self.locked) {
return error.TypeError;
}

View File

@@ -17,10 +17,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const ReadableStream = @import("./ReadableStream.zig");
const ReadableStreamReadResult = @import("./ReadableStream.zig").ReadableStreamReadResult;
@@ -69,7 +69,7 @@ pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: ReadableStream.Ch
try self.stream.pullIf();
}
pub fn _error(self: *ReadableStreamDefaultController, err: Env.JsObject) !void {
pub fn _error(self: *ReadableStreamDefaultController, err: js.JsObject) !void {
self.stream.state = .{ .errored = err };
if (self.stream.reader_resolver) |*rr| {

View File

@@ -18,8 +18,8 @@
const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const ReadableStream = @import("./ReadableStream.zig");
const ReadableStreamReadResult = @import("./ReadableStream.zig").ReadableStreamReadResult;
@@ -32,15 +32,15 @@ pub fn constructor(stream: *ReadableStream) ReadableStreamDefaultReader {
return .{ .stream = stream };
}
pub fn get_closed(self: *const ReadableStreamDefaultReader) Env.Promise {
pub fn get_closed(self: *const ReadableStreamDefaultReader) js.Promise {
return self.stream.closed_resolver.promise();
}
pub fn _cancel(self: *ReadableStreamDefaultReader, reason: ?[]const u8, page: *Page) !Env.Promise {
pub fn _cancel(self: *ReadableStreamDefaultReader, reason: ?[]const u8, page: *Page) !js.Promise {
return try self.stream._cancel(reason, page);
}
pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise {
pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !js.Promise {
const stream = self.stream;
switch (stream.state) {

View File

@@ -19,8 +19,8 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const FormData = @import("../xhr/form_data.zig").FormData;
@@ -261,7 +261,7 @@ pub const URLSearchParams = struct {
const URLSearchParamsOpts = union(enum) {
qs: []const u8,
form_data: *const FormData,
js_obj: Env.JsObject,
js_obj: js.JsObject,
};
pub fn constructor(opts_: ?URLSearchParamsOpts, page: *Page) !URLSearchParams {
const opts = opts_ orelse return .{ .entries = .{} };

View File

@@ -17,9 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Env = @import("../env.zig").Env;
const Function = Env.Function;
const js = @import("../js/js.zig");
const EventTarget = @import("../dom/event_target.zig").EventTarget;
const EventHandler = @import("../events/event.zig").EventHandler;
@@ -33,20 +31,20 @@ pub const XMLHttpRequestEventTarget = struct {
// Extend libdom event target for pure zig struct.
base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .xhr },
onloadstart_cbk: ?Function = null,
onprogress_cbk: ?Function = null,
onabort_cbk: ?Function = null,
onload_cbk: ?Function = null,
ontimeout_cbk: ?Function = null,
onloadend_cbk: ?Function = null,
onreadystatechange_cbk: ?Function = null,
onloadstart_cbk: ?js.Function = null,
onprogress_cbk: ?js.Function = null,
onabort_cbk: ?js.Function = null,
onload_cbk: ?js.Function = null,
ontimeout_cbk: ?js.Function = null,
onloadend_cbk: ?js.Function = null,
onreadystatechange_cbk: ?js.Function = null,
fn register(
self: *XMLHttpRequestEventTarget,
alloc: std.mem.Allocator,
typ: []const u8,
listener: EventHandler.Listener,
) !?Function {
) !?js.Function {
const target = @as(*parser.EventTarget, @ptrCast(self));
// The only time this can return null if the listener is already
@@ -69,25 +67,25 @@ pub const XMLHttpRequestEventTarget = struct {
try parser.eventTargetRemoveEventListener(et, typ, lst.?, false);
}
pub fn get_onloadstart(self: *XMLHttpRequestEventTarget) ?Function {
pub fn get_onloadstart(self: *XMLHttpRequestEventTarget) ?js.Function {
return self.onloadstart_cbk;
}
pub fn get_onprogress(self: *XMLHttpRequestEventTarget) ?Function {
pub fn get_onprogress(self: *XMLHttpRequestEventTarget) ?js.Function {
return self.onprogress_cbk;
}
pub fn get_onabort(self: *XMLHttpRequestEventTarget) ?Function {
pub fn get_onabort(self: *XMLHttpRequestEventTarget) ?js.Function {
return self.onabort_cbk;
}
pub fn get_onload(self: *XMLHttpRequestEventTarget) ?Function {
pub fn get_onload(self: *XMLHttpRequestEventTarget) ?js.Function {
return self.onload_cbk;
}
pub fn get_ontimeout(self: *XMLHttpRequestEventTarget) ?Function {
pub fn get_ontimeout(self: *XMLHttpRequestEventTarget) ?js.Function {
return self.ontimeout_cbk;
}
pub fn get_onloadend(self: *XMLHttpRequestEventTarget) ?Function {
pub fn get_onloadend(self: *XMLHttpRequestEventTarget) ?js.Function {
return self.onloadend_cbk;
}
pub fn get_onreadystatechange(self: *XMLHttpRequestEventTarget) ?Function {
pub fn get_onreadystatechange(self: *XMLHttpRequestEventTarget) ?js.Function {
return self.onreadystatechange_cbk;
}

View File

@@ -21,18 +21,17 @@ const Allocator = std.mem.Allocator;
const json = std.json;
const log = @import("../log.zig");
const js = @import("../browser/js/js.zig");
const polyfill = @import("../browser/polyfill/polyfill.zig");
const App = @import("../app.zig").App;
const Env = @import("../browser/env.zig").Env;
const Browser = @import("../browser/browser.zig").Browser;
const Session = @import("../browser/session.zig").Session;
const Page = @import("../browser/page.zig").Page;
const Inspector = @import("../browser/env.zig").Env.Inspector;
const Incrementing = @import("../id.zig").Incrementing;
const Notification = @import("../notification.zig").Notification;
const InterceptState = @import("domains/fetch.zig").InterceptState;
const polyfill = @import("../browser/polyfill/polyfill.zig");
pub const URL_BASE = "chrome://newtab/";
pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
@@ -329,7 +328,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
node_registry: Node.Registry,
node_search_list: Node.Search.List,
inspector: Inspector,
inspector: js.Inspector,
isolated_worlds: std.ArrayListUnmanaged(IsolatedWorld),
http_proxy_changed: bool = false,
@@ -661,7 +660,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
/// An object id is unique across all contexts, different object ids can refer to the same Node in different contexts.
const IsolatedWorld = struct {
name: []const u8,
executor: Env.ExecutionWorld,
executor: js.ExecutionWorld,
grant_universal_access: bool,
// Polyfill loader for the isolated world.
@@ -695,7 +694,7 @@ const IsolatedWorld = struct {
page,
null,
false,
Env.GlobalMissingCallback.init(&self.polyfill_loader),
js.GlobalMissingCallback.init(&self.polyfill_loader),
);
}

View File

@@ -19,11 +19,12 @@
const std = @import("std");
const log = @import("log.zig");
const js = @import("browser/js/js.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const App = @import("app.zig").App;
const Env = @import("browser/env.zig").Env;
const Browser = @import("browser/browser.zig").Browser;
const TestHTTPServer = @import("TestHTTPServer.zig");
@@ -123,7 +124,7 @@ fn run(
_ = page.wait(2000);
const js_context = page.main_context;
var try_catch: Env.TryCatch = undefined;
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();

File diff suppressed because it is too large Load Diff

View File

@@ -1,295 +0,0 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Allocator = std.mem.Allocator;
const MyList = struct {
items: []u8,
pub fn constructor(elem1: u8, elem2: u8, elem3: u8, state: State) MyList {
var items = state.arena.alloc(u8, 3) catch unreachable;
items[0] = elem1;
items[1] = elem2;
items[2] = elem3;
return .{ .items = items };
}
pub fn _first(self: *const MyList) u8 {
return self.items[0];
}
pub fn _symbol_iterator(self: *const MyList) IterableU8 {
return IterableU8.init(self.items);
}
};
const MyVariadic = struct {
member: u8,
pub fn constructor() MyVariadic {
return .{ .member = 0 };
}
pub fn _len(_: *const MyVariadic, variadic: []bool) u64 {
return @as(u64, variadic.len);
}
pub fn _first(_: *const MyVariadic, _: []const u8, variadic: []bool) bool {
return variadic[0];
}
pub fn _last(_: *const MyVariadic, variadic: []bool) bool {
return variadic[variadic.len - 1];
}
pub fn _empty(_: *const MyVariadic, _: []bool) bool {
return true;
}
pub fn _myListLen(_: *const MyVariadic, variadic: []*const MyList) u8 {
return @as(u8, @intCast(variadic.len));
}
pub fn _myListFirst(_: *const MyVariadic, variadic: []*const MyList) ?u8 {
if (variadic.len == 0) return null;
return variadic[0]._first();
}
};
const MyErrorUnion = struct {
pub fn constructor(is_err: bool) !MyErrorUnion {
if (is_err) return error.MyError;
return .{};
}
pub fn get_withoutError(_: *const MyErrorUnion) !u8 {
return 0;
}
pub fn get_withError(_: *const MyErrorUnion) !u8 {
return error.MyError;
}
pub fn set_withoutError(_: *const MyErrorUnion, _: bool) !void {}
pub fn set_withError(_: *const MyErrorUnion, _: bool) !void {
return error.MyError;
}
pub fn _funcWithoutError(_: *const MyErrorUnion) !void {}
pub fn _funcWithError(_: *const MyErrorUnion) !void {
return error.MyError;
}
};
pub const MyException = struct {
err: ErrorSet,
const errorNames = [_][]const u8{
"MyCustomError",
};
const errorMsgs = [_][]const u8{
"Some custom message.",
};
fn errorStrings(comptime i: usize) []const u8 {
return errorNames[0] ++ ": " ++ errorMsgs[i];
}
// interface definition
pub const ErrorSet = error{
MyCustomError,
};
pub fn init(_: Allocator, err: anyerror, _: []const u8) !MyException {
return .{ .err = @as(ErrorSet, @errorCast(err)) };
}
pub fn get_name(self: *const MyException) []const u8 {
return switch (self.err) {
ErrorSet.MyCustomError => errorNames[0],
};
}
pub fn get_message(self: *const MyException) []const u8 {
return switch (self.err) {
ErrorSet.MyCustomError => errorMsgs[0],
};
}
pub fn _toString(self: *const MyException) []const u8 {
return switch (self.err) {
ErrorSet.MyCustomError => errorStrings(0),
};
}
};
const MyTypeWithException = struct {
pub const Exception = MyException;
pub fn constructor() MyTypeWithException {
return .{};
}
pub fn _withoutError(_: *const MyTypeWithException) MyException.ErrorSet!void {}
pub fn _withError(_: *const MyTypeWithException) MyException.ErrorSet!void {
return MyException.ErrorSet.MyCustomError;
}
pub fn _superSetError(_: *const MyTypeWithException) !void {
return MyException.ErrorSet.MyCustomError;
}
pub fn _outOfMemory(_: *const MyTypeWithException) !void {
return error.OutOfMemory;
}
};
const MyUnionType = struct {
pub const Choices = union(enum) {
color: []const u8,
number: usize,
boolean: bool,
obj1: *MyList,
obj2: MyUnionType,
};
pub fn constructor() MyUnionType {
return .{};
}
pub fn _choices(_: *const MyUnionType, u: Choices) Choices {
return switch (u) {
.color => .{ .color = "nice" },
.number => |n| .{ .number = n + 10 },
.boolean => |b| .{ .boolean = !b },
.obj1 => |l| .{ .number = l.items.len },
.obj2 => .{ .color = "meta" },
};
}
};
const IterableU8 = Iterable(u8);
pub fn Iterable(comptime T: type) type {
return struct {
const Self = @This();
items: []T,
index: usize = 0,
pub fn init(items: []T) Self {
return .{ .items = items };
}
pub const Return = struct {
value: ?T,
done: bool,
};
pub fn _next(self: *Self) Return {
if (self.items.len > self.index) {
const val = self.items[self.index];
self.index += 1;
return .{ .value = val, .done = false };
} else {
return .{ .value = null, .done = true };
}
}
};
}
const State = struct {
arena: Allocator,
};
const testing = @import("testing.zig");
test "JS: complex types" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
var runner = try testing.Runner(State, void, .{
MyList,
IterableU8,
MyVariadic,
MyErrorUnion,
MyException,
MyTypeWithException,
MyUnionType,
}).init(.{ .arena = arena.allocator() }, {});
defer runner.deinit();
try runner.testCases(&.{
.{ "let myList = new MyList(1, 2, 3);", "undefined" },
.{ "myList.first();", "1" },
.{ "let iter = myList[Symbol.iterator]();", "undefined" },
.{ "iter.next().value;", "1" },
.{ "iter.next().value;", "2" },
.{ "iter.next().value;", "3" },
.{ "iter.next().done;", "true" },
.{ "let arr = Array.from(myList);", "undefined" },
.{ "arr.length;", "3" },
.{ "arr[0];", "1" },
}, .{});
try runner.testCases(&.{
.{ "let myVariadic = new MyVariadic();", "undefined" },
.{ "myVariadic.len(true, false, true)", "3" },
.{ "myVariadic.first('a_str', true, false, true, false)", "true" },
.{ "myVariadic.last(true, false)", "false" },
.{ "myVariadic.empty()", "true" },
.{ "myVariadic.myListLen(myList)", "1" },
.{ "myVariadic.myListFirst(myList)", "1" },
}, .{});
try runner.testCases(&.{
.{ "var myErrorCstr = ''; try {new MyErrorUnion(true)} catch (error) {myErrorCstr = error}; myErrorCstr", "Error: MyError" },
.{ "let myErrorUnion = new MyErrorUnion(false);", "undefined" },
.{ "myErrorUnion.withoutError", "0" },
.{ "var myErrorGetter = ''; try {myErrorUnion.withError} catch (error) {myErrorGetter = error}; myErrorGetter", "Error: MyError" },
.{ "myErrorUnion.withoutError = true", "true" },
.{ "var myErrorSetter = ''; try {myErrorUnion.withError = true} catch (error) {myErrorSetter = error}; myErrorSetter", "Error: MyError" },
.{ "myErrorUnion.funcWithoutError()", "undefined" },
.{ "var myErrorFunc = ''; try {myErrorUnion.funcWithError()} catch (error) {myErrorFunc = error}; myErrorFunc", "Error: MyError" },
}, .{});
try runner.testCases(&.{
.{ "MyException.prototype.__proto__ === Error.prototype", "true" },
.{ "let myTypeWithException = new MyTypeWithException();", "undefined" },
.{ "myTypeWithException.withoutError()", "undefined" },
.{ "var myCustomError = ''; try {myTypeWithException.withError()} catch (error) {myCustomError = error}", "MyCustomError: Some custom message." },
.{ "myCustomError instanceof MyException", "true" },
.{ "myCustomError instanceof Error", "true" },
.{ "var mySuperError = ''; try {myTypeWithException.superSetError()} catch (error) {mySuperError = error}", "MyCustomError: Some custom message." },
.{ "var oomError = ''; try {myTypeWithException.outOfMemory()} catch (error) {oomError = error}; oomError", "Error: out of memory" },
}, .{});
try runner.testCases(&.{
.{ "var mut = new MyUnionType()", "undefined" },
.{ "mut.choices(3)", "13" },
.{ "mut.choices('blue')", "nice" },
.{ "mut.choices(true)", "false" },
.{ "mut.choices(false)", "true" },
.{ "mut.choices(mut)", "meta" },
.{ "mut.choices(myList)", "3" },
}, .{});
}

View File

@@ -1,275 +0,0 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const Other = struct {
val: u8,
fn init(val: u8) Other {
return .{ .val = val };
}
pub fn _val(self: *const Other) u8 {
return self.val;
}
};
pub const OtherUnion = union(enum) {
Other: Other,
Bool: bool,
};
pub const MyObject = struct {
val: bool,
pub fn constructor(do_set: bool) MyObject {
return .{
.val = do_set,
};
}
pub fn named_get(_: *const MyObject, name: []const u8, has_value: *bool) ?OtherUnion {
if (std.mem.eql(u8, name, "a")) {
has_value.* = true;
return .{ .Other = .{ .val = 4 } };
}
if (std.mem.eql(u8, name, "c")) {
has_value.* = true;
return .{ .Bool = true };
}
has_value.* = false;
return null;
}
pub fn get_val(self: *const MyObject) bool {
return self.val;
}
pub fn set_val(self: *MyObject, val: bool) void {
self.val = val;
}
};
pub const MyAPI = struct {
pub fn constructor() MyAPI {
return .{};
}
pub fn _obj(_: *const MyAPI) !MyObject {
return MyObject.constructor(true);
}
};
pub const Parent = packed struct {
parent_id: i32 = 0,
pub fn get_parent(self: *const Parent) i32 {
return self.parent_id;
}
pub fn set_parent(self: *Parent, id: i32) void {
self.parent_id = id;
}
};
pub const Middle = struct {
pub const prototype = *Parent;
middle_id: i32 = 0,
_padding_1: u8 = 0,
_padding_2: u8 = 1,
_padding_3: u8 = 2,
proto: Parent,
pub fn constructor() Middle {
return .{
.middle_id = 0,
.proto = .{ .parent_id = 0 },
};
}
pub fn get_middle(self: *const Middle) i32 {
return self.middle_id;
}
pub fn set_middle(self: *Middle, id: i32) void {
self.middle_id = id;
}
};
pub const Child = struct {
pub const prototype = *Middle;
child_id: i32 = 0,
_padding_1: u8 = 0,
proto: Middle,
pub fn constructor() Child {
return .{
.child_id = 0,
.proto = .{ .middle_id = 0, .proto = .{ .parent_id = 0 } },
};
}
pub fn get_child(self: *const Child) i32 {
return self.child_id;
}
pub fn set_child(self: *Child, id: i32) void {
self.child_id = id;
}
};
pub const MiddlePtr = packed struct {
pub const prototype = *Parent;
middle_id: i32 = 0,
_padding_1: u8 = 0,
_padding_2: u8 = 1,
_padding_3: u8 = 2,
proto: *Parent,
pub fn constructor(state: State) !MiddlePtr {
const parent = try state.arena.create(Parent);
parent.* = .{ .parent_id = 0 };
return .{
.middle_id = 0,
.proto = parent,
};
}
pub fn get_middle(self: *const MiddlePtr) i32 {
return self.middle_id;
}
pub fn set_middle(self: *MiddlePtr, id: i32) void {
self.middle_id = id;
}
};
pub const ChildPtr = packed struct {
pub const prototype = *MiddlePtr;
child_id: i32 = 0,
_padding_1: u8 = 0,
_padding_2: u8 = 1,
proto: *MiddlePtr,
pub fn constructor(state: State) !ChildPtr {
const parent = try state.arena.create(Parent);
const middle = try state.arena.create(MiddlePtr);
parent.* = .{ .parent_id = 0 };
middle.* = .{ .middle_id = 0, .proto = parent };
return .{
.child_id = 0,
.proto = middle,
};
}
pub fn get_child(self: *const ChildPtr) i32 {
return self.child_id;
}
pub fn set_child(self: *ChildPtr, id: i32) void {
self.child_id = id;
}
};
const State = struct {
arena: Allocator,
};
const testing = @import("testing.zig");
test "JS: object types" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
var runner = try testing.Runner(State, void, .{
Other,
MyObject,
MyAPI,
Parent,
Middle,
Child,
MiddlePtr,
ChildPtr,
}).init(.{ .arena = arena.allocator() }, {});
defer runner.deinit();
// v8 has 3 default "own" properties
const own_base = "3";
try runner.testCases(&.{
.{ "Object.getOwnPropertyNames(MyObject).length;", own_base },
.{ "let myObj = new MyObject(true);", "undefined" },
// check object property
.{ "myObj.a.val()", "4" },
.{ "myObj.b", "undefined" },
.{ "Object.getOwnPropertyNames(myObj).length;", "0" },
// check if setter (pointer) still works
.{ "myObj.val", "true" },
.{ "myObj.val = false", "false" },
.{ "myObj.val", "false" },
.{ "let myObj2 = new MyObject(false);", "undefined" },
.{ "myObj2.c", "true" },
}, .{});
try runner.testCases(&.{
.{ "let myAPI = new MyAPI();", "undefined" },
.{ "let myObjIndirect = myAPI.obj();", "undefined" },
// check object property
.{ "myObjIndirect.a.val()", "4" },
}, .{});
try runner.testCases(&.{
.{ "let m1 = new Middle();", null },
.{ "m1.middle = 2", null },
.{ "m1.parent = 3", null },
.{ "m1.middle", "2" },
.{ "m1.parent", "3" },
}, .{});
try runner.testCases(&.{
.{ "let c1 = new Child();", null },
.{ "c1.child = 1", null },
.{ "c1.middle = 2", null },
.{ "c1.parent = 3", null },
.{ "c1.child", "1" },
.{ "c1.middle", "2" },
.{ "c1.parent", "3" },
}, .{});
try runner.testCases(&.{
.{ "let m2 = new MiddlePtr();", null },
.{ "m2.middle = 2", null },
.{ "m2.parent = 3", null },
.{ "m2.middle", "2" },
.{ "m2.parent", "3" },
}, .{});
try runner.testCases(&.{
.{ "let c2 = new ChildPtr();", null },
.{ "c2.child = 1", null },
.{ "c2.middle = 2", null },
.{ "c2.parent = 3", null },
.{ "c2.child", "1" },
.{ "c2.middle", "2" },
.{ "c2.parent", "3" },
}, .{});
}

View File

@@ -1,352 +0,0 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// TODO: use functions instead of "fake" struct once we handle function API generation
const Runner = testing.Runner(void, void, .{Primitives});
const Env = Runner.Env;
const Primitives = struct {
pub fn constructor() Primitives {
return .{};
}
// List of bytes (string)
pub fn _checkString(_: *const Primitives, v: []u8) []u8 {
return v;
}
// Integers signed
pub fn _checkI32(_: *const Primitives, v: i32) i32 {
return v;
}
pub fn _checkI64(_: *const Primitives, v: i64) i64 {
return v;
}
// Integers unsigned
pub fn _checkU32(_: *const Primitives, v: u32) u32 {
return v;
}
pub fn _checkU64(_: *const Primitives, v: u64) u64 {
return v;
}
// Floats
pub fn _checkF32(_: *const Primitives, v: f32) f32 {
return v;
}
pub fn _checkF64(_: *const Primitives, v: f64) f64 {
return v;
}
// Bool
pub fn _checkBool(_: *const Primitives, v: bool) bool {
return v;
}
// Undefined
// TODO: there is a bug with this function
// void paramater does not work => avoid for now
// pub fn _checkUndefined(_: *const Primitives, v: void) void {
// return v;
// }
// Null
pub fn _checkNullEmpty(_: *const Primitives, v: ?u32) bool {
return (v == null);
}
pub fn _checkNullNotEmpty(_: *const Primitives, v: ?u32) bool {
return (v != null);
}
// Optionals
pub fn _checkOptional(_: *const Primitives, _: ?u8, v: u8, _: ?u8, _: ?u8) u8 {
return v;
}
pub fn _checkNonOptional(_: *const Primitives, v: u8) u8 {
return v;
}
pub fn _checkOptionalReturn(_: *const Primitives) ?bool {
return true;
}
pub fn _checkOptionalReturnNull(_: *const Primitives) ?bool {
return null;
}
pub fn _checkOptionalReturnString(_: *const Primitives) ?[]const u8 {
return "ok";
}
pub fn _echoString(_: *const Primitives, a: []const u8) []const u8 {
return a;
}
pub fn _echoStringZ(_: *const Primitives, a: [:0]const u8) []const u8 {
return a;
}
pub fn _int8(_: *const Primitives, arr: []i8) void {
for (arr) |*a| {
a.* -= @intCast(arr.len);
}
}
pub fn _uint8(_: *const Primitives, arr: []u8) void {
for (arr) |*a| {
a.* += @intCast(arr.len);
}
}
pub fn _returnEmptyUint8(_: *const Primitives) Env.TypedArray(u8) {
return .{ .values = &.{} };
}
pub fn _returnUint8(_: *const Primitives) Env.TypedArray(u8) {
return .{ .values = &.{ 10, 20, 250 } };
}
pub fn _returnInt8(_: *const Primitives) Env.TypedArray(i8) {
return .{ .values = &.{ 10, -20, -120 } };
}
pub fn _returnUint16(_: *const Primitives) Env.TypedArray(u16) {
return .{ .values = &.{ 10, 200, 2050 } };
}
pub fn _returnInt16(_: *const Primitives) Env.TypedArray(i16) {
return .{ .values = &.{ 10, -420, 0 } };
}
pub fn _returnUint32(_: *const Primitives) Env.TypedArray(u32) {
return .{ .values = &.{ 10, 2444343, 43432432 } };
}
pub fn _returnInt32(_: *const Primitives) Env.TypedArray(i32) {
return .{ .values = &.{ 10, -20, -495929123 } };
}
pub fn _returnUint64(_: *const Primitives) Env.TypedArray(u64) {
return .{ .values = &.{ 10, 495812375924, 0 } };
}
pub fn _returnInt64(_: *const Primitives) Env.TypedArray(i64) {
return .{ .values = &.{ 10, -49283838122, -2 } };
}
pub fn _returnFloat32(_: *const Primitives) Env.TypedArray(f32) {
return .{ .values = &.{ 1.1, -200.035, 0.0003 } };
}
pub fn _returnFloat64(_: *const Primitives) Env.TypedArray(f64) {
return .{ .values = &.{ 8881.22284, -4928.3838122, -0.00004 } };
}
pub fn _int16(_: *const Primitives, arr: []i16) void {
for (arr) |*a| {
a.* -= @intCast(arr.len);
}
}
pub fn _uint16(_: *const Primitives, arr: []u16) void {
for (arr) |*a| {
a.* += @intCast(arr.len);
}
}
pub fn _int32(_: *const Primitives, arr: []i32) void {
for (arr) |*a| {
a.* -= @intCast(arr.len);
}
}
pub fn _uint32(_: *const Primitives, arr: []u32) void {
for (arr) |*a| {
a.* += @intCast(arr.len);
}
}
pub fn _int64(_: *const Primitives, arr: []i64) void {
for (arr) |*a| {
a.* -= @intCast(arr.len);
}
}
pub fn _uint64(_: *const Primitives, arr: []u64) void {
for (arr) |*a| {
a.* += @intCast(arr.len);
}
}
};
const testing = @import("testing.zig");
test "JS: primitive types" {
var runner = try Runner.init({}, {});
defer runner.deinit();
// constructor
try runner.testCases(&.{
.{ "let p = new Primitives();", "undefined" },
}, .{});
// JS <> Native translation of primitive types
try runner.testCases(&.{
.{ "p.checkString('ok ascii') === 'ok ascii';", "true" },
.{ "p.checkString('ok emoji 🚀') === 'ok emoji 🚀';", "true" },
.{ "p.checkString('ok chinese 鿍') === 'ok chinese 鿍';", "true" },
// String (JS liberal cases)
.{ "p.checkString(1) === '1';", "true" },
.{ "p.checkString(null) === 'null';", "true" },
.{ "p.checkString(undefined) === 'undefined';", "true" },
// Integers
// signed
.{ "const min_i32 = -2147483648", "undefined" },
.{ "p.checkI32(min_i32) === min_i32;", "true" },
.{ "p.checkI32(min_i32-1) === min_i32-1;", "false" },
.{ "try { p.checkI32(9007199254740995n) } catch(e) { e instanceof TypeError; }", "true" },
// unsigned
.{ "const max_u32 = 4294967295", "undefined" },
.{ "p.checkU32(max_u32) === max_u32;", "true" },
.{ "p.checkU32(max_u32+1) === max_u32+1;", "false" },
// int64 (with BigInt)
.{ "const big_int = 9007199254740995n", "undefined" },
.{ "p.checkI64(big_int) === big_int", "true" },
.{ "p.checkU64(big_int) === big_int;", "true" },
.{ "p.checkI64(0) === 0;", "true" },
.{ "p.checkI64(-1) === -1;", "true" },
.{ "p.checkU64(0) === 0;", "true" },
// Floats
// use round 2 decimals for float to ensure equality
.{ "const r = function(x) {return Math.round(x * 100) / 100};", "undefined" },
.{ "const double = 10.02;", "undefined" },
.{ "r(p.checkF32(double)) === double;", "true" },
.{ "r(p.checkF64(double)) === double;", "true" },
// Bool
.{ "p.checkBool(true);", "true" },
.{ "p.checkBool(false);", "false" },
.{ "p.checkBool(0);", "false" },
.{ "p.checkBool(1);", "true" },
// Bool (JS liberal cases)
.{ "p.checkBool(null);", "false" },
.{ "p.checkBool(undefined);", "false" },
// Undefined
// see TODO on Primitives.checkUndefined
// .{ "p.checkUndefined(undefined) === undefined;", "true" },
// Null
.{ "p.checkNullEmpty(null);", "true" },
.{ "p.checkNullEmpty(undefined);", "true" },
.{ "p.checkNullNotEmpty(1);", "true" },
// Optional
.{ "p.checkOptional(null, 3);", "3" },
.{ "p.checkNonOptional();", "TypeError" },
.{ "p.checkOptionalReturn() === true;", "true" },
.{ "p.checkOptionalReturnNull() === null;", "true" },
.{ "p.checkOptionalReturnString() === 'ok';", "true" },
// strings
.{ "p.echoString('over 9000!');", "over 9000!" },
.{ "p.echoStringZ('Teg');", "Teg" },
}, .{});
// typed arrays
try runner.testCases(&.{
.{ "let empty_arr = new Int8Array([]);", "undefined" },
.{ "p.int8(empty_arr)", "undefined" },
.{ "empty_arr;", "" },
.{ "let arr_i8 = new Int8Array([-10, -20, -30]);", "undefined" },
.{ "p.int8(arr_i8)", "undefined" },
.{ "arr_i8;", "-13,-23,-33" },
.{ "let arr_u8 = new Uint8Array([10, 20, 30]);", "undefined" },
.{ "p.uint8(arr_u8)", "undefined" },
.{ "arr_u8;", "13,23,33" },
.{ "let arr_i16 = new Int16Array([-1000, -2000, -3000]);", "undefined" },
.{ "p.int16(arr_i16)", "undefined" },
.{ "arr_i16;", "-1003,-2003,-3003" },
.{ "let arr_u16 = new Uint16Array([1000, 2000, 3000]);", "undefined" },
.{ "p.uint16(arr_u16)", "undefined" },
.{ "arr_u16;", "1003,2003,3003" },
.{ "let arr_i32 = new Int32Array([-1000000, -2000000, -3000000]);", "undefined" },
.{ "p.int32(arr_i32)", "undefined" },
.{ "arr_i32;", "-1000003,-2000003,-3000003" },
.{ "let arr_u32 = new Uint32Array([1000000, 2000000, 3000000]);", "undefined" },
.{ "p.uint32(arr_u32)", "undefined" },
.{ "arr_u32;", "1000003,2000003,3000003" },
.{ "let arr_i64 = new BigInt64Array([-1000000000n, -2000000000n, -3000000000n]);", "undefined" },
.{ "p.int64(arr_i64)", "undefined" },
.{ "arr_i64;", "-1000000003,-2000000003,-3000000003" },
.{ "let arr_u64 = new BigUint64Array([1000000000n, 2000000000n, 3000000000n]);", "undefined" },
.{ "p.uint64(arr_u64)", "undefined" },
.{ "arr_u64;", "1000000003,2000000003,3000000003" },
.{ "try { p.int8(arr_u8) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.intu8(arr_i8) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.intu8(arr_u32) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.int16(arr_u8) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.intu16(arr_i16) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.int16(arr_i64) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.int32(arr_u32) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.intu32(arr_i32) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.intu32(arr_u32) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.int64(arr_u64) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.intu64(arr_i64) } catch(e) { e instanceof TypeError; }", "true" },
.{ "try { p.intu64(arr_u32) } catch(e) { e instanceof TypeError; }", "true" },
.{ "p.returnEmptyUint8()", "" },
.{ "p.returnUint8()", "10,20,250" },
.{ "p.returnInt8()", "10,-20,-120" },
.{ "p.returnUint16()", "10,200,2050" },
.{ "p.returnInt16()", "10,-420,0" },
.{ "p.returnUint32()", "10,2444343,43432432" },
.{ "p.returnInt32()", "10,-20,-495929123" },
.{ "p.returnUint64()", "10,495812375924,0" },
.{ "p.returnInt64()", "10,-49283838122,-2" },
.{ "p.returnFloat32()", "1.100000023841858,-200.03500366210938,0.0003000000142492354" },
.{ "p.returnFloat64()", "8881.22284,-4928.3838122,-0.00004" },
}, .{});
try runner.testCases(&.{
.{ "'foo\\\\:bar'", "foo\\:bar" },
}, .{});
}

View File

@@ -1,110 +0,0 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const base = @import("../testing.zig");
const generate = @import("generate.zig");
pub const allocator = std.testing.allocator;
// Very similar to the JSRunner in src/testing.zig, but it isn't tied to the
// browser.Env or the *Page state
pub fn Runner(comptime State: type, comptime Global: type, comptime types: anytype) type {
const AdjustedTypes = if (Global == void) generate.Tuple(.{ types, DefaultGlobal }) else types;
return struct {
env: *Env,
js_context: *Env.JsContext,
executor: Env.ExecutionWorld,
pub const Env = js.Env(State, struct {
pub const Interfaces = AdjustedTypes;
});
const Self = @This();
pub fn init(state: State, global: Global) !*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.env = try Env.init(allocator, &base.test_app.platform, .{});
errdefer self.env.deinit();
self.executor = try self.env.newExecutionWorld();
errdefer self.executor.deinit();
self.js_context = try self.executor.createJsContext(
if (Global == void) &default_global else global,
state,
null,
true,
null,
);
return self;
}
pub fn deinit(self: *Self) void {
self.executor.deinit();
self.env.deinit();
allocator.destroy(self);
}
const RunOpts = struct {};
pub const Case = std.meta.Tuple(&.{ []const u8, ?[]const u8 });
pub fn testCases(self: *Self, cases: []const Case, _: RunOpts) !void {
for (cases, 0..) |case, i| {
var try_catch: Env.TryCatch = undefined;
try_catch.init(self.js_context);
defer try_catch.deinit();
const value = self.js_context.exec(case.@"0", null) catch |err| {
if (try try_catch.err(allocator)) |msg| {
defer allocator.free(msg);
if (isExpectedTypeError(case.@"1", msg)) {
continue;
}
std.debug.print("{s}\n\nCase: {d}\n{s}\n", .{ msg, i + 1, case.@"0" });
}
return err;
};
if (case.@"1") |expected| {
const actual = try value.toString(allocator);
defer allocator.free(actual);
if (std.mem.eql(u8, expected, actual) == false) {
std.debug.print("Expected:\n{s}\n\nGot:\n{s}\n\nCase: {d}\n{s}\n", .{ expected, actual, i + 1, case.@"0" });
return error.UnexpectedResult;
}
}
}
}
};
}
fn isExpectedTypeError(expected_: ?[]const u8, msg: []const u8) bool {
const expected = expected_ orelse return false;
if (!std.mem.eql(u8, expected, "TypeError")) {
return false;
}
return std.mem.startsWith(u8, msg, "TypeError: ");
}
var default_global = DefaultGlobal{};
const DefaultGlobal = struct {};

View File

@@ -37,7 +37,7 @@ pub fn reset() void {
}
const App = @import("app.zig").App;
const Env = @import("browser/env.zig").Env;
const js = @import("browser/js/js.zig");
const Browser = @import("browser/browser.zig").Browser;
const Session = @import("browser/session.zig").Session;
const parser = @import("browser/netsurf.zig");
@@ -396,7 +396,7 @@ pub fn htmlRunner(file: []const u8) !void {
page.arena = @import("root").tracking_allocator;
const js_context = page.main_context;
var try_catch: Env.TryCatch = undefined;
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();