diff --git a/src/browser/html/html.zig b/src/browser/html/html.zig
index ef75dde0..68c77de0 100644
--- a/src/browser/html/html.zig
+++ b/src/browser/html/html.zig
@@ -25,6 +25,7 @@ const History = @import("history.zig").History;
const Location = @import("location.zig").Location;
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
const Performance = @import("performance.zig").Performance;
+const TrustedTypes = @import("trusted_types.zig");
pub const Interfaces = .{
HTMLDocument,
@@ -38,4 +39,5 @@ pub const Interfaces = .{
Location,
MediaQueryList,
Performance,
+ TrustedTypes.Interfaces,
};
diff --git a/src/browser/html/trusted_types.zig b/src/browser/html/trusted_types.zig
new file mode 100644
index 00000000..9993bacc
--- /dev/null
+++ b/src/browser/html/trusted_types.zig
@@ -0,0 +1,149 @@
+// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
+//
+// Francis Bouvier
+// Pierre Tachoire
+//
+// 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 .
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+const Env = @import("../env.zig").Env;
+const SessionState = @import("../env.zig").SessionState;
+
+const log = std.log.scoped(.trusted_types);
+
+pub const Interfaces = .{
+ TrustedTypePolicyFactory,
+ TrustedTypePolicy,
+ TrustedTypePolicyOptions,
+ TrustedHTML,
+ TrustedScript,
+ TrustedScriptURL,
+};
+
+const TrustedHTML = struct {
+ value: []const u8,
+
+ // TODO _toJSON
+ pub fn _toString(self: *const TrustedHTML) []const u8 {
+ return self.value;
+ }
+};
+const TrustedScript = struct {
+ value: []const u8,
+
+ pub fn _toString(self: *const TrustedScript) []const u8 {
+ return self.value;
+ }
+};
+const TrustedScriptURL = struct {
+ value: []const u8,
+
+ pub fn _toString(self: *const TrustedScriptURL) []const u8 {
+ return self.value;
+ }
+};
+
+// https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory
+pub const TrustedTypePolicyFactory = struct {
+ // TBD innerHTML if set the default createHTML should be used when `element.innerHTML = userInput;` does v8 do that for us? Prob not.
+ default_policy: ?TrustedTypePolicy = null, // The default policy, set by creating a policy with the name "default".
+ created_policy_names: std.ArrayListUnmanaged([]const u8) = .empty,
+
+ pub fn _defaultPolicy(self: *TrustedTypePolicyFactory) ?TrustedTypePolicy {
+ return self.default_policy;
+ }
+
+ // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-createpolicy
+ // https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-create-a-trusted-type-policy
+ pub fn _createPolicy(self: *TrustedTypePolicyFactory, name: []const u8, options: ?TrustedTypePolicyOptions, state: *SessionState) !TrustedTypePolicy {
+ // TODO Throw TypeError if policy names are restricted by the Content Security Policy trusted-types directive and this name is not on the allowlist.
+ // TODO Throw TypeError if the name is a duplicate and the Content Security Policy trusted-types directive is not using allow-duplicates
+
+ const policy = TrustedTypePolicy{
+ .name = name,
+ .options = options orelse TrustedTypePolicyOptions{},
+ };
+
+ if (std.mem.eql(u8, name, "default")) {
+ // TBD what if default_policy is already set?
+ self.default_policy = policy;
+ }
+ try self.created_policy_names.append(state.arena, try state.arena.dupe(u8, name));
+
+ return policy;
+ }
+};
+
+pub const TrustedTypePolicyOptions = struct {
+ createHTML: ?Env.Function = null, // (str, ..args) -> str
+ createScript: ?Env.Function = null, // (str, ..args) -> str
+ createScriptURL: ?Env.Function = null, // (str, ..args) -> str
+};
+
+// https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy
+pub const TrustedTypePolicy = struct {
+ name: []const u8,
+ options: TrustedTypePolicyOptions,
+
+ pub fn get_name(self: *TrustedTypePolicy) []const u8 {
+ return self.name;
+ }
+
+ pub fn _createHTML(self: *TrustedTypePolicy, html: []const u8) !TrustedHTML {
+ // TODO handle throwIfMissing
+ const create = self.options.createHTML orelse return error.TypeError;
+
+ var result: Env.Function.Result = undefined;
+ const out = try create.tryCall([]const u8, .{html}, &result); // TODO varargs
+ return .{
+ .value = out,
+ };
+ }
+
+ pub fn _createScript(self: *TrustedTypePolicy, script: []const u8) !TrustedScript {
+ // TODO handle throwIfMissing
+ const create = self.options.createScript orelse return error.TypeError;
+
+ var result: Env.Function.Result = undefined;
+ return try create.tryCall(TrustedScript, .{script}, &result); // TODO varargs
+ }
+
+ pub fn _createScriptURL(self: *TrustedTypePolicy, url: []const u8) !TrustedScriptURL {
+ // TODO handle throwIfMissing
+ const create = self.options.createScriptURL orelse return error.TypeError;
+
+ var result: Env.Function.Result = undefined;
+ return try create.tryCall(TrustedScriptURL, .{url}, &result); // TODO varargs
+ }
+};
+
+const testing = @import("../../testing.zig");
+test "Browser.TrustedTypes" {
+ var runner = try testing.jsRunner(testing.tracking_allocator, .{});
+ defer runner.deinit();
+
+ try runner.testCases(&.{
+ .{ "trustedTypes", "[object TrustedTypePolicyFactory]" },
+ .{
+ \\ let escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
+ \\ createHTML: (string) => string.replace(/');", "<img src=x onerror=alert(1)>" },
+ }, .{});
+}
diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig
index 7d747f04..6b4e6800 100644
--- a/src/browser/html/window.zig
+++ b/src/browser/html/window.zig
@@ -31,6 +31,7 @@ const Console = @import("../console/console.zig").Console;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
const Performance = @import("performance.zig").Performance;
+const TrustedTypePolicyFactory = @import("trusted_types.zig").TrustedTypePolicyFactory;
const storage = @import("../storage/storage.zig");
@@ -58,6 +59,7 @@ pub const Window = struct {
console: Console = .{},
navigator: Navigator = .{},
performance: Performance,
+ trusted_types: TrustedTypePolicyFactory = .{},
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
var fbs = std.io.fixedBufferStream("");
@@ -154,6 +156,10 @@ pub const Window = struct {
return &self.performance;
}
+ pub fn get_trustedTypes(self: *Window) !TrustedTypePolicyFactory {
+ return self.trusted_types;
+ }
+
// Tells the browser you wish to perform an animation. It requests the browser to call a user-supplied callback function before the next repaint.
// fn callback(timestamp: f64)
// Returns the request ID, that uniquely identifies the entry in the callback list.
diff --git a/src/runtime/js.zig b/src/runtime/js.zig
index 92b96d66..83f60704 100644
--- a/src/runtime/js.zig
+++ b/src/runtime/js.zig
@@ -1254,7 +1254,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return self.tryCallWithThis(T, self.getThis(), args, result);
}
- pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !void {
+ pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !T {
var try_catch: TryCatch = undefined;
try_catch.init(self.scope);
defer try_catch.deinit();