Merge branch 'main' into agent

This commit is contained in:
Adrià Arrufat
2026-05-28 11:53:47 +02:00
166 changed files with 1684 additions and 5182 deletions

View File

@@ -104,10 +104,10 @@ pub fn build(b: *Build) !void {
});
check.dependOn(&check_lib.step);
// Extras (snapshot_creator, legacy_test) are off the default install to
// Extras (snapshot_creator) are off the default install to
// avoid paying for three exe compiles on every edit. Build explicitly
// with `zig build extras`.
const extras_step = b.step("extras", "Build snapshot_creator and legacy_test");
const extras_step = b.step("extras", "Build snapshot_creator");
{
// browser
@@ -187,38 +187,6 @@ pub fn build(b: *Build) !void {
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
}
{
// browser
const exe = b.addExecutable(.{
.name = "legacy_test",
.use_llvm = true,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main_legacy_test.zig"),
.target = target,
.optimize = optimize,
.sanitize_c = enable_csan,
.sanitize_thread = enable_tsan,
.imports = &.{
.{ .name = "lightpanda", .module = lightpanda_module },
},
}),
});
extras_step.dependOn(&b.addInstallArtifact(exe, .{}).step);
const exe_check = b.addLibrary(.{
.name = "legacy_test_check",
.root_module = exe.root_module,
});
check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("legacy_test", "Run the app");
run_step.dependOn(&run_cmd.step);
}
}
fn linkV8(

View File

@@ -90,8 +90,13 @@ fn handleConnection(self: *TestHTTPServer, conn: std.net.Server.Connection) !voi
};
self.handler(&req) catch |err| {
std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err });
try req.respond("server error", .{ .status = .internal_server_error });
switch (err) {
error.BrokenPipe => {},
else => {
std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err });
try req.respond("server error", .{ .status = .internal_server_error });
},
}
return;
};
}

View File

@@ -282,7 +282,13 @@ pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, frame:
._type = unionInit(AbstractRange.Type, chain.get(1)),
};
chain.setLeaf(1, child);
frame._live_ranges.append(&abstract_range._range_link);
if (abstract_range._type != .static_range) {
// StaticRanges are not live, so they don't get added to the frame's
// live_ranges list.
frame._live_ranges.append(&abstract_range._range_link);
}
return chain.get(1);
}

View File

@@ -307,6 +307,7 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, parent: ?*Frame) !void {
._event_manager = EventManager.init(arena, self),
};
self._to_load = &self._to_load_1;
self._http_owner.blob_urls = &self._blob_urls;
var screen: *Screen = undefined;
var visual_viewport: *VisualViewport = undefined;
@@ -520,11 +521,6 @@ pub fn isSameOrigin(self: *const Frame, url: [:0]const u8) bool {
return std.mem.eql(u8, URL.getHost(url), URL.getHost(current_origin));
}
/// Look up a blob URL in this frame's registry.
pub fn lookupBlobUrl(self: *Frame, url: []const u8) ?*Blob {
return self._blob_urls.get(url);
}
pub fn navigate(self: *Frame, request_url: [:0]const u8, opts: NavigateOpts) !void {
lp.assert(self._load_state == .waiting, "frame.renavigate", .{});
const session = self._session;
@@ -585,7 +581,7 @@ pub fn navigate(self: *Frame, request_url: [:0]const u8, opts: NavigateOpts) !vo
};
const parse_arena = try self.getArena(.medium, "Frame.parseBlob");
defer self.releaseArena(parse_arena);
var parser = Parser.init(parse_arena, self.document.asNode(), self);
var parser = Parser.init(parse_arena, self.document.asNode(), self, .{ .allow_declarative_shadow = true });
parser.parse(blob._slice);
} else {
self.document.injectBlank(self) catch |err| {
@@ -1196,7 +1192,7 @@ fn frameDoneCallback(ctx: *anyopaque) !void {
const parse_arena = try self.getArena(.medium, "Frame.parse");
defer self.releaseArena(parse_arena);
var parser = Parser.init(parse_arena, self.document.asNode(), self);
var parser = Parser.init(parse_arena, self.document.asNode(), self, .{ .allow_declarative_shadow = true });
switch (self._parse_state) {
.html => |*html| {
@@ -3264,7 +3260,9 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No
if (should_notify) {
if (comptime from_parser == false) {
// When the parser adds the node, nodeIsReady is only called when the
// nodeComplete() callback is executed.
// nodeComplete() callback is executed. nodeIsReady resolves the
// node's owning frame itself (only for the few node types that have
// ready work), so pass the incumbent `self`.
try self.nodeIsReady(false, child);
// Check if text was added to a script that hasn't started yet.
@@ -3573,11 +3571,20 @@ pub fn updateRangesForNodeRemoval(self: *Frame, parent: *Node, child: *Node, chi
// TODO: optimize and cleanup, this is called a lot (e.g., innerHTML = '')
pub fn parseHtmlAsChildren(self: *Frame, node: *Node, html: []const u8) !void {
return self.parseHtmlAsChildrenInner(node, html, false);
}
// setHTMLUnsafe variant: parse a fragment that may contain declarative shadow node
pub fn parseHtmlUnsafeAsChildren(self: *Frame, node: *Node, html: []const u8) !void {
return self.parseHtmlAsChildrenInner(node, html, true);
}
fn parseHtmlAsChildrenInner(self: *Frame, node: *Node, html: []const u8, allow_declarative_shadow: bool) !void {
const previous_parse_mode = self._parse_mode;
self._parse_mode = .fragment;
defer self._parse_mode = previous_parse_mode;
var parser = Parser.init(self.call_arena, node, self);
var parser = Parser.init(self.call_arena, node, self, .{ .allow_declarative_shadow = allow_declarative_shadow });
parser.parseFragment(html);
// html5ever wraps fragment output in an <html> element; unwrap so its
@@ -3615,6 +3622,15 @@ fn nodeIsReady(self: *Frame, comptime from_parser: bool, node: *Node) !void {
// we don't execute scripts added via innerHTML = '<script...';
return;
}
// A node's "ready" work (running a <script>, loading an <iframe> / <link> /
// <style>) must happen in the frame that owns the node's document — not
// necessarily `self`. When an async callback (e.g. a postMessage listener)
// running in frame A appends a node to frame B's document, `self` is the
// incumbent frame A, but the script's base URL and execution realm must come
// from B (its node document). Resolving that owner frame is a parent-chain
// walk, so we only do it once we've matched a node type that has ready work
// (the common text/element insertion does nothing here). The parser inserts
// into its own document, so from_parser always uses `self`.
if (node.is(Element.Html.Script)) |script| {
if ((comptime from_parser == false) and script._src.len == 0) {
// Script was added via JavaScript without a src attribute.
@@ -3625,23 +3641,27 @@ fn nodeIsReady(self: *Frame, comptime from_parser: bool, node: *Node) !void {
}
}
self.scriptAddedCallback(from_parser, script) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "script", .type = self._type, .url = self.url });
const frame = if (comptime from_parser) self else node.ownerFrame(self);
frame.scriptAddedCallback(from_parser, script) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "script", .type = frame._type, .url = frame.url });
return err;
};
} else if (node.is(IFrame)) |iframe| {
self.iframeAddedCallback(iframe) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "iframe", .type = self._type, .url = self.url });
const frame = if (comptime from_parser) self else node.ownerFrame(self);
frame.iframeAddedCallback(iframe) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "iframe", .type = frame._type, .url = frame.url });
return err;
};
} else if (node.is(Element.Html.Link)) |link| {
link.linkAddedCallback(self) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "link", .type = self._type });
const frame = if (comptime from_parser) self else node.ownerFrame(self);
link.linkAddedCallback(frame) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "link", .type = frame._type });
return error.LinkLoadError;
};
} else if (node.is(Element.Html.Style)) |style| {
style.styleAddedCallback(self) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "style", .type = self._type });
const frame = if (comptime from_parser) self else node.ownerFrame(self);
style.styleAddedCallback(frame) catch |err| {
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "style", .type = frame._type });
return error.StyleLoadError;
};
}
@@ -4310,5 +4330,16 @@ test "Frame: httpMetadata 404" {
defer testing.test_session.removePage();
const meta = frame.httpMetadata();
try testing.expect(meta.status != null);
try std.testing.expectEqual(@as(u16, 404), meta.status.?);
try testing.expectEqual(404, meta.status.?);
}
test "Frame: 401" {
var frame = try testing.pageTest("401", .{});
defer testing.reset();
defer frame._session.removePage();
var buf = std.Io.Writer.Allocating.init(testing.allocator);
defer buf.deinit();
try @import("dump.zig").root(frame.document, .{}, &buf.writer, frame);
try testing.expectEqual("<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body><pre>No</pre></body></html>", buf.written());
}

View File

@@ -567,6 +567,21 @@ fn requestT(self: *Client, req: Request, owner: ?*Owner) !*Transfer {
// via transfer.abort which fires error_callback and deinits. `.created`
// means no commit happened — anything else is held by an owner that
// will clean up.
// Synthetic schemes never touch the network or the layer chain — they skip
// robots/cache/interception and deliver on the next tick
if (Synthetic.isSynthetic(req.url)) {
// The 2nd transfer is the callback context. We don't actually use it,
// we're just sticking transfer in there to have something.
self.runNextTick(transfer, transfer, .{ .run = Synthetic.run }) catch |err| {
if (transfer.state == .created) {
transfer.abort(err);
}
return err;
};
return transfer;
}
self.entry_layer.request(transfer) catch |err| {
if (transfer.state == .created) {
transfer.abort(err);
@@ -577,6 +592,80 @@ fn requestT(self: *Client, req: Request, owner: ?*Owner) !*Transfer {
return transfer;
}
// Non-network URL schemes whose response is synthesized in-process rather than
// fetched, think blob data URLs.
const Synthetic = struct {
const data_url = @import("data_url.zig");
fn isSynthetic(url: []const u8) bool {
return std.mem.startsWith(u8, url, "data:") or std.mem.startsWith(u8, url, "blob:");
}
fn run(transfer: *Transfer, _: *anyopaque) void {
defer transfer.deinit();
const fulfilled = build(transfer) catch |err| {
transfer.req.error_callback(transfer.req.ctx, err);
return;
};
deliver(&transfer.req, &fulfilled) catch |err| {
transfer.req.error_callback(transfer.req.ctx, err);
};
}
fn build(transfer: *Transfer) !FulfilledResponse {
const arena = transfer.arena;
const url = transfer.req.url;
var body: []const u8 = "";
var content_type: []const u8 = "";
if (std.mem.startsWith(u8, url, "data:")) {
const parsed = try data_url.parse(arena, url);
content_type = parsed.content_type;
body = parsed.body;
} else {
// blob: — resolved against the owning frame's registry.
const owner = transfer.owner orelse return error.BlobNotFound;
const blob_urls = owner.blob_urls orelse return error.BlobNotFound;
const blob = blob_urls.get(url) orelse return error.BlobNotFound;
content_type = blob._mime;
body = blob._slice;
}
// A blob with no type yields no Content-Type header.
const headers = if (content_type.len > 0) blk: {
const h = try arena.alloc(http.Header, 1);
h[0] = .{ .name = "content-type", .value = content_type };
break :blk h;
} else &[_]http.Header{};
return .{
.url = url,
.body = body,
.status = 200,
.headers = headers,
};
}
fn deliver(req: *Request, fulfilled: *const FulfilledResponse) !void {
const response = Response.fromFulfilled(req.ctx, fulfilled);
if (req.start_callback) |cb| {
try cb(response);
}
const proceed = try req.header_callback(response);
if (!proceed) {
return error.Abort;
}
if (fulfilled.body) |b| {
if (b.len > 0) {
try req.data_callback(response, b);
}
}
try req.done_callback(req.ctx);
}
};
const SyncContext = struct {
allocator: Allocator,
completion: union(enum) {
@@ -1782,7 +1871,7 @@ pub const Transfer = struct {
log.err(.http, "getResponseCode", .{ .err = err, .source = "body callback" });
return http.writefunc_error;
};
if ((status >= 300 and status <= 399) or status == 401 or status == 407) {
if (status >= 300 and status <= 399) {
res.skip_body = true;
return @intCast(chunk_len);
}
@@ -1909,7 +1998,11 @@ pub const Owner = struct {
transfers: std.DoublyLinkedList = .{},
websockets: std.DoublyLinkedList = .{},
// The owning Frame's / WorkerGlobalScope's blob: registry,
blob_urls: ?*const std.StringHashMapUnmanaged(*Blob) = null,
const WebSocket = @import("webapi/net/WebSocket.zig");
const Blob = @import("webapi/Blob.zig");
pub fn addTransfer(self: *Owner, t: *Transfer) void {
self.transfers.append(&t.owner_node);

View File

@@ -141,12 +141,11 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
var remote_url: ?[:0]const u8 = null;
const base_url = frame.base();
if (element.getAttributeSafe(comptime .wrap("src"))) |src| {
if (try parseDataURI(arena, src)) |data_uri| {
source = .{ .@"inline" = data_uri };
} else {
remote_url = try URL.resolve(arena, base_url, src, .{ .encoding = frame.charset });
source = .{ .remote = .{} };
}
// data: and blob: srcs flow through the normal request path; HttpClient
// synthesizes the response. Execution mode (blocking vs async/defer) is
// attribute-driven, the same as any other src.
remote_url = try URL.resolve(arena, base_url, src, .{ .encoding = frame.charset });
source = .{ .remote = .{} };
} else {
var buf = std.Io.Writer.Allocating.init(arena);
try element.asNode().getChildTextContent(&buf.writer);
@@ -339,65 +338,3 @@ pub fn parseImportmap(self: *ScriptManager, script: *const Script) !void {
pub fn staticScriptsDone(self: *ScriptManager) void {
self.base.staticScriptsDone();
}
// Parses data:[<media-type>][;base64],<data>
fn parseDataURI(allocator: Allocator, src: []const u8) !?[]const u8 {
if (!std.mem.startsWith(u8, src, "data:")) {
return null;
}
const uri = src[5..];
const data_starts = std.mem.indexOfScalar(u8, uri, ',') orelse return null;
const data = uri[data_starts + 1 ..];
const unescaped = try URL.unescape(allocator, data);
const metadata = uri[0..data_starts];
if (std.mem.endsWith(u8, metadata, ";base64") == false) {
return unescaped;
}
// Forgiving base64 decode per WHATWG spec:
// https://infra.spec.whatwg.org/#forgiving-base64-decode
// Step 1: Remove all ASCII whitespace
var stripped = try std.ArrayList(u8).initCapacity(allocator, unescaped.len);
for (unescaped) |c| {
if (!std.ascii.isWhitespace(c)) {
stripped.appendAssumeCapacity(c);
}
}
const trimmed = std.mem.trimRight(u8, stripped.items, "=");
// Length % 4 == 1 is invalid
if (trimmed.len % 4 == 1) {
return error.InvalidCharacterError;
}
const decoded_size = std.base64.standard_no_pad.Decoder.calcSizeForSlice(trimmed) catch return error.InvalidCharacterError;
const buffer = try allocator.alloc(u8, decoded_size);
std.base64.standard_no_pad.Decoder.decode(buffer, trimmed) catch return error.InvalidCharacterError;
return buffer;
}
const testing = @import("../testing.zig");
test "DataURI: parse valid" {
try assertValidDataURI("data:text/javascript; charset=utf-8;base64,Zm9v", "foo");
try assertValidDataURI("data:text/javascript; charset=utf-8;,foo", "foo");
try assertValidDataURI("data:,foo", "foo");
}
test "DataURI: parse invalid" {
try assertInvalidDataURI("atad:,foo");
try assertInvalidDataURI("data:foo");
try assertInvalidDataURI("data:");
}
fn assertValidDataURI(uri: []const u8, expected: []const u8) !void {
defer testing.reset();
const data_uri = try parseDataURI(testing.arena_allocator, uri) orelse return error.TestFailed;
try testing.expectEqual(expected, data_uri);
}
fn assertInvalidDataURI(uri: []const u8) !void {
try testing.expectEqual(null, parseDataURI(undefined, uri));
}

View File

@@ -161,6 +161,8 @@ pub fn deinit(self: *Session) void {
self.cookie_jar.deinit();
self.browser.env.memoryPressureNotification(.critical);
self.storage_shed.deinit(self.browser.app.allocator);
self._console_messages.deinit();
self.arena_pool.release(self.arena);

202
src/browser/data_url.zig Normal file
View File

@@ -0,0 +1,202 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// 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/>.
// "data: URL processor" — https://fetch.spec.whatwg.org/#data-url-processor.
// The single home for data: parsing: the HttpClient synthetic-scheme path and
// ScriptManager (<script src="data:...">) both go through here.
const std = @import("std");
const URL = @import("URL.zig");
const Allocator = std.mem.Allocator;
pub const Parsed = struct {
body: []const u8,
content_type: []const u8,
};
pub fn parse(arena: Allocator, url: []const u8) !Parsed {
if (std.mem.startsWith(u8, url, "data:") == false) {
return error.InvalidDataUrl;
}
const after = url["data:".len..];
const comma = std.mem.indexOfScalarPos(u8, after, 0, ',') orelse return error.InvalidDataUrl;
var meta = std.mem.trim(u8, after[0..comma], &std.ascii.whitespace);
const encoded_body = after[comma + 1 ..];
// A trailing ";" + optional spaces + "base64" selects base64 decoding.
const is_base64 = blk: {
if (meta.len < "base64".len) break :blk false;
const tail = meta[meta.len - "base64".len ..];
if (!std.ascii.eqlIgnoreCase(tail, "base64")) break :blk false;
const head = std.mem.trimRight(u8, meta[0 .. meta.len - "base64".len], " ");
if (head.len == 0 or head[head.len - 1] != ';') break :blk false;
meta = head[0 .. head.len - 1];
break :blk true;
};
var content_type: []const u8 = meta;
if (content_type.len == 0) {
content_type = "text/plain;charset=US-ASCII";
} else if (content_type[0] == ';') {
// e.g. "data:;charset=utf-8,x" -> "text/plain;charset=utf-8"
content_type = try std.fmt.allocPrint(arena, "text/plain{s}", .{content_type});
}
const body_text = try URL.unescape(arena, encoded_body);
const body = if (is_base64) try base64Decode(arena, body_text) else body_text;
return .{ .content_type = content_type, .body = body };
}
fn base64Decode(arena: Allocator, input: []const u8) ![]u8 {
// Forgiving-base64 decode — https://infra.spec.whatwg.org/#forgiving-base64-decode.
// std's decoders reject non-canonical trailing bits (e.g. "ab"), which
// forgiving-base64 tolerates, so decode by hand after validating padding.
const buf = try arena.alloc(u8, input.len);
var n: usize = 0;
for (input) |c| switch (c) {
' ', '\t', '\n', '\r', std.ascii.control_code.ff => {},
else => {
buf[n] = c;
n += 1;
},
};
var src = buf[0..n];
// Only a multiple-of-4 length may carry (and shed) up to two "=" of padding.
if (src.len % 4 == 0) {
if (std.mem.endsWith(u8, src, "==")) {
src = src[0 .. src.len - 2];
} else if (std.mem.endsWith(u8, src, "=")) {
src = src[0 .. src.len - 1];
}
}
if (src.len % 4 == 1) return error.InvalidBase64;
// Any "=" still present is misplaced padding.
if (std.mem.indexOfScalar(u8, src, '=') != null) return error.InvalidBase64;
const out_len = src.len / 4 * 3 + switch (src.len % 4) {
0 => @as(usize, 0),
2 => 1,
3 => 2,
else => unreachable,
};
const out = try arena.alloc(u8, out_len);
var oi: usize = 0;
var i: usize = 0;
while (i + 4 <= src.len) : (i += 4) {
const a = try b64Val(src[i]);
const b = try b64Val(src[i + 1]);
const c = try b64Val(src[i + 2]);
const d = try b64Val(src[i + 3]);
out[oi] = (a << 2) | (b >> 4);
out[oi + 1] = (b << 4) | (c >> 2);
out[oi + 2] = (c << 6) | d;
oi += 3;
}
switch (src.len - i) {
0 => {},
2 => {
const a = try b64Val(src[i]);
const b = try b64Val(src[i + 1]);
out[oi] = (a << 2) | (b >> 4);
},
3 => {
const a = try b64Val(src[i]);
const b = try b64Val(src[i + 1]);
const c = try b64Val(src[i + 2]);
out[oi] = (a << 2) | (b >> 4);
out[oi + 1] = (b << 4) | (c >> 2);
},
else => unreachable,
}
return out;
}
fn b64Val(c: u8) !u8 {
return switch (c) {
'A'...'Z' => c - 'A',
'a'...'z' => c - 'a' + 26,
'0'...'9' => c - '0' + 52,
'+' => 62,
'/' => 63,
else => error.InvalidBase64,
};
}
const testing = @import("../testing.zig");
test "data_url: plain text, default content-type" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:,Hello%2C%20World");
try testing.expectString("text/plain;charset=US-ASCII", r.content_type);
try testing.expectString("Hello, World", r.body);
}
test "data_url: explicit mediatype" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:text/html,<b>hi</b>");
try testing.expectString("text/html", r.content_type);
try testing.expectString("<b>hi</b>", r.body);
}
test "data_url: base64" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:text/plain;base64,SGVsbG8=");
try testing.expectString("text/plain", r.content_type);
try testing.expectString("Hello", r.body);
}
test "data_url: base64 without padding decodes (forgiving)" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:application/octet-stream;base64,SGVsbG8");
try testing.expectString("Hello", r.body);
// 2- and 3-char unpadded tails decode (non-canonical trailing bits are ok).
try testing.expectString("i", (try parse(testing.arena_allocator, "data:;base64,ab")).body);
try testing.expectString("a", (try parse(testing.arena_allocator, "data:;base64,YR")).body);
// ASCII whitespace inside the payload is ignored.
try testing.expectString("Hello", (try parse(testing.arena_allocator, "data:;base64,SGVs bG8=")).body);
}
test "data_url: forgiving-base64 rejects misplaced/over-padding" {
defer testing.reset();
const arena = testing.arena_allocator;
try std.testing.expectError(error.InvalidBase64, parse(arena, "data:;base64,abcd=")); // len % 4 == 1
try std.testing.expectError(error.InvalidBase64, parse(arena, "data:;base64,="));
try std.testing.expectError(error.InvalidBase64, parse(arena, "data:;base64,ab=c")); // interior "="
try std.testing.expectError(error.InvalidBase64, parse(arena, "data:;base64,==")); // no data
}
test "data_url: bare charset gets text/plain prefix" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:;charset=utf-8,x");
try testing.expectString("text/plain;charset=utf-8", r.content_type);
}
test "data_url: empty body" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:text/plain,");
try testing.expectString("", r.body);
}
test "data_url: missing comma is an error" {
defer testing.reset();
try std.testing.expectError(error.InvalidDataUrl, parse(testing.arena_allocator, "data:text/plain"));
}

View File

@@ -392,13 +392,27 @@ fn evaluateModule(self: *Context, comptime want_result: bool, mod: js.Module, ur
// Some module-loading errors aren't handled by TryCatch. We need to
// get the error from the module itself.
const exception = mod.getException();
const message = blk: {
const e = mod.getException().toString() catch break :blk "???";
const e = exception.toString() catch break :blk "???";
break :blk e.toSlice() catch "???";
};
const stack = blk: {
if (comptime IS_DEBUG == false) {
// SetCaptureStackTraceForUncaughtExceptions is only set in Debug
break :blk "";
}
const stack_handle = v8.v8__Exception__GetStackTrace(exception.handle) orelse break :blk "";
var buf = std.Io.Writer.Allocating.init(self.call_arena);
js.writeStackTrace(self.isolate.handle, stack_handle, &buf.writer) catch break :blk "???";
break :blk buf.written();
};
log.warn(.js, "evaluate module", .{
.message = message,
.stack = stack,
.specifier = url,
.message = message,
});
return error.EvaluationError;
};

View File

@@ -135,6 +135,10 @@ pub fn init(app: *App, opts: InitOpts) !Env {
v8.v8__Isolate__SetFatalErrorHandler(isolate_handle, fatalCallback);
v8.v8__Isolate__SetOOMErrorHandler(isolate_handle, oomCallback);
if (comptime IS_DEBUG) {
v8.v8__Isolate__SetCaptureStackTraceForUncaughtExceptions(isolate_handle, true, 64);
}
isolate.enter();
errdefer isolate.exit();
@@ -304,11 +308,13 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
};
context.execution = .{
.js = context,
.url = &global.url,
.buf = &global.buf,
.charset = &global.charset,
.context = context,
.arena = global.arena,
.page = context.page,
.session = page.session,
.call_arena = params.call_arena,
._factory = global._factory,
._scheduler = &context.scheduler,

View File

@@ -30,11 +30,12 @@ const lp = @import("lightpanda");
const Context = @import("Context.zig");
const Scheduler = @import("Scheduler.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const Factory = @import("../Factory.zig");
const HttpClient = @import("../HttpClient.zig");
const EventManagerBase = @import("../EventManagerBase.zig");
const Blob = @import("../webapi/Blob.zig");
const Event = @import("../webapi/Event.zig");
const EventTarget = @import("../webapi/EventTarget.zig");
const Performance = @import("../webapi/Performance.zig");
@@ -44,12 +45,15 @@ const Allocator = std.mem.Allocator;
const Execution = @This();
context: *Context,
js: *Context,
// Fields named to match Page for generic code (executor._factory works for both)
buf: []u8,
arena: Allocator,
call_arena: Allocator,
page: *Page,
session: *Session,
_factory: *Factory,
_scheduler: *Scheduler,
@@ -61,7 +65,7 @@ charset: *[]const u8,
// Returns the current base URL of the global scope.
pub fn base(self: *const Execution) [:0]const u8 {
return self.context.global.base();
return self.js.global.base();
}
pub fn dupeString(self: *const Execution, value: []const u8) ![]const u8 {
@@ -72,33 +76,27 @@ pub fn dupeString(self: *const Execution, value: []const u8) ![]const u8 {
}
pub fn getArena(self: *const Execution, size_or_bucket: anytype, debug: []const u8) !Allocator {
return self.context.page.getArena(size_or_bucket, debug);
return self.page.getArena(size_or_bucket, debug);
}
pub fn releaseArena(self: *const Execution, allocator: Allocator) void {
self.context.page.releaseArena(allocator);
self.page.releaseArena(allocator);
}
pub fn headersForRequest(self: *const Execution, headers: *HttpClient.Headers) !void {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g.headersForRequest(headers),
};
}
pub fn isSameOrigin(self: *const Execution, url: [:0]const u8) bool {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g.isSameOrigin(url),
};
}
pub fn lookupBlobUrl(self: *const Execution, url: []const u8) ?*Blob {
return switch (self.context.global) {
inline else => |g| g.lookupBlobUrl(url),
};
}
pub fn makeRequest(self: *const Execution, req: HttpClient.Request) !void {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g.makeRequest(req),
};
}
@@ -108,7 +106,7 @@ pub fn makeRequest(self: *const Execution, req: HttpClient.Request) !void {
// owning scope without caring whether it's a Frame or a Worker — e.g.
// WebSocket.init appending to `.websockets`.
pub fn httpOwner(self: *const Execution) *HttpClient.Owner {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| &g._http_owner,
};
}
@@ -120,31 +118,31 @@ pub fn dispatch(
handler: anytype,
comptime opts: EventManagerBase.DispatchDirectOptions,
) !void {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g.dispatch(target, event, handler, opts),
};
}
pub fn hasDirectListeners(self: *const Execution, target: *EventTarget, typ: []const u8, handler: anytype) bool {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g.hasDirectListeners(target, typ, handler),
};
}
pub fn performance(self: *const Execution) *Performance {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g.performance(),
};
}
pub fn frameId(self: *const Execution) u32 {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g._frame_id,
};
}
pub fn loaderId(self: *const Execution) u32 {
return switch (self.context.global) {
return switch (self.js.global) {
inline else => |g| g._loader_id,
};
}

View File

@@ -23,6 +23,7 @@ const js = @import("js.zig");
const v8 = js.v8;
const log = lp.log;
const IS_DEBUG = @import("builtin").mode == .Debug;
const Function = @This();
@@ -52,6 +53,20 @@ pub fn withThis(self: *const Function, value: anytype) !Function {
pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object {
const local = self.local;
if (comptime IS_DEBUG == false) {
// This should not be possible, yet it happens. In Release, we'll log an
// error and hope for the best. In Debug, we'll let the code execute
// and v8 will crash on the null reference. This null value almost
// certainly comes from a Global that was reset and thus gets unwrapped
// to null (but I haven't figured out the flow that can cause that). This
// does indicate that we might be in some shutdown state, so just
// return an error might not be too bad.
if (@intFromPtr(self.handle) == 0) {
log.err(.browser, "newInstance on dead handle", .{});
return error.DeadFunctionHandle;
}
}
var try_catch: js.TryCatch = undefined;
try_catch.init(local);
defer try_catch.deinit();
@@ -108,6 +123,20 @@ fn _tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args
caught.* = .{};
const local = self.local;
if (comptime IS_DEBUG == false) {
// This should not be possible, yet it happens. In Release, we'll log an
// error and hope for the best. In Debug, we'll let the code execute
// and v8 will crash on the null reference. This null value almost
// certainly comes from a Global that was reset and thus gets unwrapped
// to null (but I haven't figured out the flow that can cause that). This
// does indicate that we might be in some shutdown state, so just
// return an error might not be too bad.
if (@intFromPtr(self.handle) == 0) {
log.err(.browser, "call on dead handle", .{});
return error.DeadFunctionHandle;
}
}
// When we're calling a function from within JavaScript itself, this isn't
// necessary. We're within a Caller instantiation, which will already have
// incremented the call_depth and it won't decrement it until the Caller is

View File

@@ -1342,30 +1342,19 @@ fn finalizerPtrGetter(comptime T: type, comptime FT: type) *const fn (*T) *FT {
}
pub fn stackTrace(self: *const Local) !?[]const u8 {
const isolate = self.isolate;
const isolate = self.isolate.handle;
const stack_handle = v8.v8__StackTrace__CurrentStackTrace__STATIC(isolate, 30) orelse return null;
const separator = log.separator();
var buf: std.ArrayList(u8) = .empty;
var writer = buf.writer(self.call_arena);
const stack_trace_handle = v8.v8__StackTrace__CurrentStackTrace__STATIC(isolate.handle, 30).?;
const frame_count = v8.v8__StackTrace__GetFrameCount(stack_trace_handle);
if (v8.v8__StackTrace__CurrentScriptNameOrSourceURL__STATIC(isolate.handle)) |script| {
var buf = std.Io.Writer.Allocating.init(self.call_arena);
if (v8.v8__StackTrace__CurrentScriptNameOrSourceURL__STATIC(isolate)) |script| {
const stack = js.String{ .local = self, .handle = script };
try writer.print("{s}<{f}>", .{ separator, stack });
try buf.writer.print("{s}<{f}>", .{ separator, stack });
}
for (0..@intCast(frame_count)) |i| {
const frame_handle = v8.v8__StackTrace__GetFrame(stack_trace_handle, isolate.handle, @intCast(i)).?;
if (v8.v8__StackFrame__GetFunctionName(frame_handle)) |name| {
const script = js.String{ .local = self, .handle = name };
try writer.print("{s}{f}:{d}", .{ separator, script, v8.v8__StackFrame__GetLineNumber(frame_handle) });
} else {
try writer.print("{s}<anonymous>:{d}", .{ separator, v8.v8__StackFrame__GetLineNumber(frame_handle) });
}
}
return buf.items;
try js.writeStackTrace(isolate, stack_handle, &buf.writer);
return buf.written();
}
// == Promise Helpers ==
@@ -1508,6 +1497,13 @@ pub fn newException(self: *const Local, ex: anytype) js.Exception {
};
}
pub fn getGlobal(self: *const Local) js.Object {
return .{
.local = self,
.handle = v8.v8__Context__Global(self.handle).?,
};
}
// Convert a Global (or optional Global) to a Local (or optional Local).
// Meant to be used from either frame.js.toLocal, where the context must have an
// non-null local (orelse panic), or from a LocalScope

View File

@@ -827,6 +827,7 @@ pub const PageJsApis = flattenTypes(&.{
@import("../webapi/XMLSerializer.zig"),
@import("../webapi/AbstractRange.zig"),
@import("../webapi/Range.zig"),
@import("../webapi/StaticRange.zig"),
@import("../webapi/NodeFilter.zig"),
@import("../webapi/Element.zig"),
@import("../webapi/element/DOMStringMap.zig"),

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
pub const v8 = @import("v8").c;
const string = @import("../../string.zig");
@@ -417,3 +419,19 @@ pub const FinalizerCallback = struct {
page.releaseArena(self.arena);
}
};
pub fn writeStackTrace(isolate: *v8.Isolate, stack_handle: *const v8.StackTrace, writer: *std.Io.Writer) !void {
const separator = lp.log.separator();
const frame_count = v8.v8__StackTrace__GetFrameCount(stack_handle);
for (0..@intCast(frame_count)) |i| {
const frame_handle = v8.v8__StackTrace__GetFrame(stack_handle, isolate, @intCast(i)).?;
if (v8.v8__StackFrame__GetFunctionName(frame_handle)) |name| {
var buf: [1024]u8 = undefined;
const n = v8.v8__String__WriteUtf8(name, isolate, &buf, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8);
try writer.print("{s}{s}:{d}", .{ separator, buf[0..n], v8.v8__StackFrame__GetLineNumber(frame_handle) });
} else {
try writer.print("{s}<anonymous>:{d}", .{ separator, v8.v8__StackFrame__GetLineNumber(frame_handle) });
}
}
}

View File

@@ -20,9 +20,12 @@ const std = @import("std");
const Frame = @import("Frame.zig");
const URL = @import("URL.zig");
const TreeWalker = @import("webapi/TreeWalker.zig");
const Element = @import("webapi/Element.zig");
const Node = @import("webapi/Node.zig");
const Element = @import("webapi/Element.zig");
const TreeWalker = @import("webapi/TreeWalker.zig");
const Slot = @import("webapi/element/html/Slot.zig");
const isAllWhitespace = @import("../string.zig").isAllWhitespace;
const truncateUtf8 = @import("../string.zig").truncateUtf8;
@@ -187,6 +190,10 @@ const Context = struct {
writer: *std.Io.Writer,
frame: *Frame,
// When there's a slot-attribute, we skip rendering, unless this flag has
// bet set to true.
force_slot: bool = false,
fn ensureNewline(self: *Context) !void {
if (!self.state.last_char_was_newline) {
try self.writer.writeByte('\n');
@@ -224,11 +231,37 @@ const Context = struct {
}
}
// Render a <slot>'s assigned light-DOM nodes, or its own children as
// fallback. Same as dump's dumpSlotContent.
fn renderSlotContent(self: *Context, slot: *Slot) !void {
const assigned = slot.assignedNodes(null, self.frame) catch return;
if (assigned.len == 0) {
return self.renderChildren(slot.asNode());
}
for (assigned) |node| {
// ensures that we don't skip this element when rending it.
self.force_slot = true;
try self.render(node);
}
self.force_slot = false;
}
fn renderElement(self: *Context, el: *Element) !void {
const force_slot = self.force_slot;
self.force_slot = false;
const tag = el.getTag();
if (!isVisibleElement(el)) return;
if (!force_slot) {
if (el.getAttributeSafe(comptime .wrap("slot")) != null) {
// This element has a slot attribute, and we aren't forcing slot
// rendering (i.e. this is the light-DOM), skip it.
return;
}
}
// Ensure block elements start on a new line
if (tag.isBlock() and !self.state.in_table) {
try self.ensureNewline();
@@ -396,10 +429,21 @@ const Context = struct {
}
return;
},
.slot => return self.renderSlotContent(el.as(Slot)),
else => {},
}
try self.renderChildren(el.asNode());
// Composed tree: a shadow host renders its shadow tree in place of its
// light-DOM children (light DOM is visible only through <slot>). Applies
// to open and closed roots alike. markdown is always a rendered-content
// path (cf. dump.zig's default .rendered mode), so we always pierce; the
// early-return tags above can never be valid shadow hosts, so only this
// generic path needs the check.
if (self.frame._element_shadow_roots.get(el)) |shadow| {
try self.renderChildren(shadow.asNode());
} else {
try self.renderChildren(el.asNode());
}
switch (tag) {
.pre => {
@@ -823,3 +867,80 @@ test "browser.markdown: max_bytes truncates with marker" {
try testing.expect(std.mem.endsWith(u8, out, "[truncated]\n"));
try testing.expect(out.len <= 50 + truncation_marker.len);
}
// Builds a shadow host <div>, populates its light DOM with `light` and its
// (open) shadow tree with `shadow`, then dumps the host. Declarative shadow DOM
// parsing isn't implemented, so the shadow tree is attached imperatively.
fn testMarkdownShadow(light: []const u8, shadow: []const u8, expected: []const u8) !void {
const testing = @import("../testing.zig");
const frame = try testing.test_session.createPage();
defer testing.test_session.removePage();
frame.url = "http://localhost/";
const doc = frame.window._document;
const host = try doc.createElement("div", null, frame);
if (light.len > 0) {
try frame.parseHtmlAsChildren(host.asNode(), light);
}
const sr = try host.attachShadow(comptime .wrap("open"), frame);
try frame.parseHtmlAsChildren(sr.asNode(), shadow);
var aw: std.Io.Writer.Allocating = .init(testing.allocator);
defer aw.deinit();
try dump(host.asNode(), .{}, &aw.writer, frame);
try testing.expectString(expected, aw.written());
}
test "browser.markdown: shadow content is pierced" {
try testMarkdownShadow("", "Shadow content", "Shadow content\n");
}
test "browser.markdown: slot projects assigned light DOM" {
// The slotted <span> renders at the slot's position in the shadow tree
// (inside the <h2>), and not a second time at its light-DOM position.
try testMarkdownShadow(
\\<span slot="title">Slotted</span>
,
\\<h2><slot name="title"></slot></h2>
, "\n## Slotted\n");
}
test "browser.markdown: unassigned light DOM is omitted" {
// Light DOM is visible only through a <slot>; with no matching slot the
// light <p> must not appear — only the shadow tree's content does.
try testMarkdownShadow(
\\<p>orphan</p>
,
\\<div>only shadow</div>
, "only shadow\n");
}
test "browser.markdown: slot fallback content when nothing assigned" {
try testMarkdownShadow("",
\\<slot name="x">Default text</slot>
, "Default text\n");
}
// End-to-end: a declarative shadow root (parsed via setHTMLUnsafe) is attached
// as a real shadow tree, and markdown's composed-tree piercing then renders it.
test "browser.markdown: declarative shadow DOM renders through piercing" {
const testing = @import("../testing.zig");
const frame = try testing.test_session.createPage();
defer testing.test_session.removePage();
frame.url = "http://localhost/";
const doc = frame.window._document;
const host = try doc.createElement("div", null, frame);
try host.setHTMLUnsafe(
\\<div><template shadowrootmode="open"><p>shadow content</p></template></div>
, frame);
var aw: std.Io.Writer.Allocating = .init(testing.allocator);
defer aw.deinit();
try dump(host.asNode(), .{}, &aw.writer, frame);
try testing.expectString("\nshadow content\n", aw.written());
}

View File

@@ -71,7 +71,16 @@ pending_text: ?PendingText,
// second chunk of a run, so the common case stays at one copy.
buf: std.ArrayList(u8),
pub fn init(arena: Allocator, node: *Node, frame: *Frame) Parser {
// Whether `<template shadowrootmode>` is parsed into a real shadow root.
// True for document navigation, document.write, and setHTMLUnsafe; false for
// innerHTML and DOMParser (per spec). Set from Options at init.
allow_declarative_shadow: bool = false,
pub const Options = struct {
allow_declarative_shadow: bool = false,
};
pub fn init(arena: Allocator, node: *Node, frame: *Frame, opts: Options) Parser {
return .{
.err = null,
.frame = frame,
@@ -83,6 +92,7 @@ pub fn init(arena: Allocator, node: *Node, frame: *Frame) Parser {
},
.pending_text = null,
.buf = .empty,
.allow_declarative_shadow = opts.allow_declarative_shadow,
};
}
@@ -157,6 +167,7 @@ const Error = struct {
reparent_children,
append_before_sibling,
append_based_on_parent_node,
attach_declarative_shadow,
};
};
@@ -180,6 +191,8 @@ pub fn parse(self: *Parser, html: []const u8) void {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
attachDeclarativeShadowCallback,
self.allow_declarative_shadow,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -209,6 +222,8 @@ pub fn parseWithEncoding(self: *Parser, html: []const u8, charset: []const u8) v
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
attachDeclarativeShadowCallback,
self.allow_declarative_shadow,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -235,6 +250,8 @@ pub fn parseXML(self: *Parser, xml: []const u8) void {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
attachDeclarativeShadowCallback,
false,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -261,6 +278,8 @@ pub fn parseFragment(self: *Parser, html: []const u8) void {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
attachDeclarativeShadowCallback,
self.allow_declarative_shadow,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -271,10 +290,10 @@ pub const Streaming = struct {
parser: Parser,
handle: ?*anyopaque,
pub fn init(arena: Allocator, node: *Node, frame: *Frame) Streaming {
pub fn init(arena: Allocator, node: *Node, frame: *Frame, opts: Options) Streaming {
return .{
.handle = null,
.parser = Parser.init(arena, node, frame),
.parser = Parser.init(arena, node, frame, opts),
};
}
@@ -304,6 +323,8 @@ pub const Streaming = struct {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
attachDeclarativeShadowCallback,
self.parser.allow_declarative_shadow,
) orelse return error.ParserCreationFailed;
}
@@ -499,6 +520,34 @@ fn _getTemplateContentsCallback(self: *Parser, node: *Node) !*anyopaque {
return pn;
}
// Called for `<template shadowrootmode>` when declarative shadow roots are
// allowed. Attaches a shadow root to `host` and redirects the (stack-only)
// template's contents into it, so html5ever parses the template's children
// straight into the shadow root. Returns 1 on success, 0 to tell html5ever to
// fall back to inserting the template as a normal light-DOM element.
fn attachDeclarativeShadowCallback(ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8 {
const self: *Parser = @ptrCast(@alignCast(ctx));
return self._attachDeclarativeShadowCallback(getNode(host_ref), getNode(template_ref), mode_is_open != 0) catch |err| {
self.err = .{ .err = err, .source = .attach_declarative_shadow };
return 0;
};
}
fn _attachDeclarativeShadowCallback(self: *Parser, host_node: *Node, template_node: *Node, mode_is_open: bool) !u8 {
// guaranteed by html5ever
const host = host_node.as(Element);
const mode: lp.String = if (mode_is_open) comptime .wrap("open") else comptime .wrap("closed");
const shadow = host.attachShadow(mode, self.frame) catch |err| switch (err) {
// Expected per-spec fall-backs (host can't host a shadow, or already
// has one): keep the <template> in the light DOM instead.
error.NotSupported => return 0,
else => return err,
};
const template = template_node.as(Element).is(Element.Html.Template) orelse return 0;
template._content = shadow.asDocumentFragment();
return 1;
}
fn getDataCallback(ctx: *anyopaque) callconv(.c) *anyopaque {
const pn: *ParsedNode = @ptrCast(@alignCast(ctx));
// For non-elements, data is null. But, we expect this to only ever

View File

@@ -37,6 +37,8 @@ pub extern "c" fn html5ever_parse_document(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
allow_declarative_shadow: bool,
) void;
/// Parse HTML document with encoding conversion. Converts from charset to UTF-8 before parsing.
@@ -61,6 +63,8 @@ pub extern "c" fn html5ever_parse_document_with_encoding(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
allow_declarative_shadow: bool,
) void;
pub extern "c" fn html5ever_parse_fragment(
@@ -82,6 +86,8 @@ pub extern "c" fn html5ever_parse_fragment(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
allow_declarative_shadow: bool,
) void;
pub extern "c" fn html5ever_attribute_iterator_next(ctx: *anyopaque) Nullable(Attribute);
@@ -112,6 +118,8 @@ pub extern "c" fn html5ever_streaming_parser_create(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
allow_declarative_shadow: bool,
) ?*anyopaque;
pub extern "c" fn html5ever_streaming_parser_feed(
@@ -215,6 +223,8 @@ pub extern "c" fn xml5ever_parse_document(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
allow_declarative_shadow: bool,
) void;
// General encoding api

View File

@@ -1,6 +1,20 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=replaceable>
"use strict";
// console is [Replaceable] (Console Standard): assignment must replace it
// even in strict mode — neither throwing nor silently no-op'ing, and even
// after console has already been read.
const prev = window.console;
window.console = {marker: 42};
testing.expectEqual(42, window.console.marker);
testing.expectEqual(42, console.marker);
// restore: console is now a writable data property, so this just overwrites it.
window.console = prev;
testing.expectEqual(true, window.console === prev);
</script>
<script id=entries>
{
const entries = Object.entries(console);

View File

@@ -142,6 +142,23 @@
}
</script>
<!-- document.write processes declarative shadow roots, like the parser. -->
<script id=write_declarative_shadow>
document.write('<div id="dw_dsd"><template shadowrootmode="open"><p>written shadow</p></template></div>');
testing.expectEqual(true, true);
</script>
<script id=verify_declarative_shadow>
{
const host = document.getElementById('dw_dsd');
// The shadow root survives the fragment->live-DOM splice.
testing.expectEqual(true, host.shadowRoot !== null);
testing.expectEqual('written shadow', host.shadowRoot.querySelector('p').textContent);
// The <template> was consumed, not left in light DOM.
testing.expectEqual(null, host.querySelector('template'));
}
</script>
<!-- Phase 3 Tests: document.open/close would go here -->
<!-- Note: Testing document.open/close requires async/setTimeout which doesn't -->
<!-- work well with the test isolation. The implementation is tested manually. -->

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<script src="../../../testing.js"></script>
<!--
data: <script src> resolves through the HTTP client's synthetic-scheme path
(no special-casing in ScriptManager). Execution mode is attribute-driven,
exactly like any other src: bare = blocking, defer/async = deferred.
-->
<!-- Blocking, parser-inserted: run in document order, before the check below. -->
<script src="data:text/javascript,window.__plain = 3;"></script>
<script src="data:text/javascript;base64,d2luZG93Ll9fYjY0ID0gNDI7"></script>
<script src="data:text/javascript,window.__enc%20%3D%207%3B"></script>
<script id=blocking_data_urls>
{
testing.expectEqual(3, window.__plain);
testing.expectEqual(42, window.__b64);
testing.expectEqual(7, window.__enc);
}
</script>
<!-- A deferred data: src must still run; proves mode follows the attribute. -->
<script src="data:text/javascript,window.__deferred = 'ran';" defer></script>
<script id=deferred_check>
testing.onload(() => {
testing.expectEqual('ran', window.__deferred);
});
</script>

View File

@@ -161,6 +161,32 @@
}
</script>
<script id=postMessageTransfersPorts>
{
// window.postMessage(data, targetOrigin, [port]) must deliver the transferred
// MessagePort as event.ports[0] (regression: ports used to always be empty,
// so receivers doing `e.ports[0].onmessage = ...` threw on undefined).
const channel = new MessageChannel();
let received = null;
let echoed = null;
channel.port1.onmessage = (e) => { echoed = e.data; };
const handler = (e) => {
received = e.ports[0];
received.postMessage('pong');
};
window.addEventListener('message', handler, { once: true });
window.postMessage('ping', '*', [channel.port2]);
testing.onload(() => {
testing.expectEqual(true, received instanceof MessagePort);
testing.expectEqual('pong', echoed);
});
}
</script>
<script id=messageEventOriginFromLocation>
{
let receivedOrigin = null;

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=data_url_navigation type=module>
// data: navigation flows through the HTTP client's synthetic-scheme path
// (resource_type=.document -> serveSynthetic), like any URL. The data:
// document is cross-origin (opaque origin), so we can't read its
// contentDocument — instead it reports its own parsed content back via
// postMessage, which proves serveSynthetic delivered the bytes and they
// parsed + ran.
{
const state = await testing.async();
window.addEventListener('message', (e) => state.resolve(e.data));
const f = document.createElement('iframe');
f.src = "data:text/html,<p>hello</p><script>parent.postMessage(document.querySelector('p').textContent, '*')<\/script>";
document.documentElement.appendChild(f);
await state.done((data) => {
testing.expectEqual('hello', data);
});
}
</script>

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<script src="testing.js"></script>
<script id=intl>
var te = new TextEncoder();
testing.expectEqual('utf-8', te.encoding);
testing.expectEqual([226, 130, 172], Array.from(te.encode('€')));
// this will crash if ICU isn't properly configured / ininitialized
testing.expectEqual("[object Intl.DateTimeFormat]", new Intl.DateTimeFormat().toString());
</script>

View File

@@ -1,26 +0,0 @@
<!DOCTYPE html>
<script src="testing.js"></script>
<script id=crypto>
const a = crypto.randomUUID();
const b = crypto.randomUUID();
testing.expectEqual(36, a.length);
testing.expectEqual(36, b.length);
testing.expectEqual(false, a == b)
testing.expectError('Error: QuotaExceededError', () => {
crypto.getRandomValues(new BigUint64Array(8193));
});
let r1 = new Int32Array(5)
let r2 = crypto.getRandomValues(r1)
testing.expectEqual(5, new Set(r1).size);
testing.expectEqual(5, new Set(r2).size);
testing.expectEqual(true, r1.every((v, i) => v === r2[i]));
var r3 = new Uint8Array(16);
let r4 = crypto.getRandomValues(r3);
r4[6] = 10;
testing.expectEqual(10, r4[6]);
testing.expectEqual(10, r3[6]);
</script>

View File

@@ -1,6 +0,0 @@
<!DOCTYPE html>
<script src="testing.js"></script>
<script id=support>
testing.expectEqual(true, CSS.supports('display: flex'));
testing.expectEqual(true, CSS.supports('text-decoration-style', 'blink'));
</script>

View File

@@ -1,101 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=css_style_declaration>
let style = document.createElement('div').style;
style.cssText = 'color: red; font-size: 12px; margin: 5px !important;';
testing.expectEqual(3, style.length);
testing.expectEqua;('red', style.getPropertyValue('color'));
testing.expectEqua;('12px', style.getPropertyValue('font-size'));
testing.expectEqua;('', style.getPropertyValue('unknown-property'));
testing.expectEqual('important', style.getPropertyPriority('margin'));
testing.expectEqual('', style.getPropertyPriority('color'));
testing.expectEqual('', style.getPropertyPriority('unknown-property'));
testing.expectEqual('color', style.item(0));
testing.expectEqual('font-size', style.item(1));
testing.expectEqual('margin', style.item(2));
testing.expectEqual('', style.item(3));
style.setProperty('background-color', 'blue');
testing.expectEqual('blue', style.getPropertyValue('background-color'));
testing.expectEqual(4, style.length);
style.setProperty('color', 'green');
testing.expectEqual('green', style.color);
testing.expectEqual('green', style.getPropertyValue('color'));
testing.expectEqual(4, style.length);
style.setProperty('padding', '10px', 'important');
testing.expectEqual('10px', style.getPropertyValue('padding'));
testing.expectEqual('important', style.getPropertyPriority('padding'));
style.setProperty('border', '1px solid black', 'IMPORTANT');
testing.expectEqual('important', style.getPropertyPriority('border'));
</script>
<script id=removeProperty>
testing.expectEqual('green', style.removeProperty('color'));
testing.expectEqual('', style.getPropertyValue('color'));
testing.expectEqual(5, style.length)
testing.expectEqual('', style.removeProperty('unknown-property'));
</script>
<script id=includes>
testing.expectEqual(false, style.cssText.includes('font-size: 10px;'));
testing.expectEqual(true, style.cssText.includes('font-size: 12px;'));
testing.expectEqual(true, style.cssText.includes('margin: 5px !important;'));
testing.expectEqual(true, style.cssText.includes('padding: 10px !important;'));
testing.expectEqual(true, style.cssText.includes('border: 1px solid black !important;'));
</script>
<script id=special_char">
style.cssText = 'color: purple; text-align: center;';
testing.expectEqual(2, style.length);
testing.expectEqual('purple', style.getPropertyValue('color'));
testing.expectEqual('center', style.getPropertyValue('text-align'));
testing.expectEqual('', style.getPropertyValue('font-size'));
style.setProperty('cont', 'Hello; world!');
testing.expectEqual('Hello; world!', style.getPropertyValue('cont'));
style.cssText = 'content: "Hello; world!"; background-image: url("test.png");';
testing.expectEqual('"Hello; world!"', style.getPropertyValue('content'));
testing.expectEqual('url("test.png")', style.getPropertyValue('background-image'));
</script>
<script id=cssFloat">
testing.expectEqual('', style.cssFloat);
style.cssFloat = 'left';
testing.expectEqual('left', style.cssFloat);
testing.expectEqual('left', style.getPropertyValue('float'));
style.cssFloat = 'right';
testing.expectEqual('right', style.cssFloat);
testing.expectEqual('right', style.getPropertyValue('float'));
style.cssFloat = null;
testing.expectEqual('', style.cssFloat);
testing.expectEqual('', style.getPropertyValue('float'));
</script>
<script id=misc>
style.setProperty('display', '');
testing.expectEqual('', style.getPropertyValue('display'));
// style.cssText = ' color : purple ; margin : 10px ; ';
// testing.expectEqual('purple', style.getPropertyValue('color'));
// testing.expectEqual('10px', style.getPropertyValue('margin'));
// style.setProperty('border-bottom-left-radius', '5px');
// testing.expectEqual('5px', style.getPropertyValue('border-bottom-left-radius'));
// testing.expectEqual('visible', style.visibility);
// testing.expectEqual('visible', style.getPropertyValue('visibility'));
// testing.expectEqual('10px', style.margin);
// style.margin = 'auto';
// testing.expectEqual('auto', style.margin);
</script>

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=css_stylesheet>
let css = new CSSStyleSheet()
testing.expectEqual(true, css instanceof CSSStyleSheet);
testing.expectEqual(0, css.cssRules.length);
testing.expectEqual(null, css.ownerRule);
let index1 = css.insertRule('body { color: red; }', 0);
testing.expectEqual(0, index1);
testing.expectEqual(0, css.cssRules.length);
let replaced = false;
css.replace('body{}').then(() => replaced = true);
testing.onload(() => testing.expectEqual(true, replaced));
</script>

View File

@@ -1,15 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=animation>
let a1 = document.createElement('div').animate(null, null);
testing.expectEqual('finished', a1.playState);
let cb = [];
a1.ready.then(() => { cb.push('ready') });
a1.finished.then((x) => {
cb.push('finished');
cb.push(x == a1);
});
testing.onload(() => testing.expectEqual(['finished', true], cb));
</script>

View File

@@ -1,33 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<a id="link" href="foo" class="ok">OK</a>
<script id=attribute>
let a = document.createAttributeNS('foo', 'bar');
testing.expectEqual('foo', a.namespaceURI);
testing.expectEqual(null, a.prefix);
testing.expectEqual('bar', a.localName);
testing.expectEqual('bar', a.name);
testing.expectEqual('', a.value);
// TODO: libdom has a bug here: the created attr has no parent, it
// causes a panic w/ libdom when setting the value.
//.{ "a.value = 'nok'", "nok" },
testing.expectEqual(null, a.ownerElement);
let b = document.getElementById('link').getAttributeNode('class');
testing.expectEqual('class', b.name);
testing.expectEqual('ok', b.value);
b.value = 'nok';
testing.expectEqual('nok', b.value)
b.value = null;
testing.expectEqual('null', b.value);
b.value = 'ok';
testing.expectEqual('ok', b.value);
testing.expectEqual('link', b.ownerElement.id);
</script>

View File

@@ -1,48 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<a id="link" href="foo" class="ok">OK</a>
<script id=character_data>
let link = document.getElementById('link');
let cdata = link.firstChild;
testing.expectEqual('OK', cdata.data);
cdata.data = 'OK modified';
testing.expectEqual('OK modified', cdata.data);
cdata.data = 'OK';
testing.expectEqual(2, cdata.length);
testing.expectEqual(true, cdata.nextElementSibling === null);
// create a next element
let next = document.createElement('a');
testing.expectEqual(true, link.appendChild(next, cdata) !== undefined);
testing.expectEqual(true, cdata.nextElementSibling.localName === 'a');
testing.expectEqual(true, cdata.previousElementSibling === null);
// create a prev element
let prev = document.createElement('div');
testing.expectEqual(true, link.insertBefore(prev, cdata) !== undefined);
testing.expectEqual('div', cdata.previousElementSibling.localName);
cdata.appendData(' modified');
testing.expectEqual('OK modified', cdata.data);
cdata.deleteData('OK'.length, ' modified'.length);
testing.expectEqual('OK', cdata.data)
cdata.insertData('OK'.length-1, 'modified');
testing.expectEqual('OmodifiedK', cdata.data);
cdata.replaceData('OK'.length-1, 'modified'.length, 'replaced');
testing.expectEqual('OreplacedK', cdata.data);
testing.expectEqual('replaced', cdata.substringData('OK'.length-1, 'replaced'.length));
testing.expectEqual('', cdata.substringData('OK'.length-1, 0));
testing.expectEqual('replaced', cdata.substringData('OK'.length-1, 'replaced'.length));
testing.expectEqual('', cdata.substringData('OK'.length-1, 0));
</script>

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=comment>
let comment = new Comment('foo');
testing.expectEqual('foo', comment.data);
let empty = new Comment()
testing.expectEqual('', empty.data);
</script>

View File

@@ -1,185 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="content">
<a id="a1" href="foo" class="ok">OK</a>
<p id="p1" class="ok empty">
<span id="s1"></span>
</p>
<p id="p2"> And</p>
</div>
<script id=document>
testing.expectEqual('Document', document.__proto__.__proto__.constructor.name);
testing.expectEqual('Node', document.__proto__.__proto__.__proto__.constructor.name);
testing.expectEqual('EventTarget', document.__proto__.__proto__.__proto__.__proto__.constructor.name);
let newdoc = new Document();
testing.expectEqual(null, newdoc.documentElement);
testing.expectEqual(0, newdoc.children.length);
testing.expectEqual(0, newdoc.getElementsByTagName('*').length);
testing.expectEqual(null, newdoc.getElementsByTagName('*').item(0));
testing.expectEqual(true, newdoc.inputEncoding === document.inputEncoding);
testing.expectEqual(true, newdoc.documentURI === document.documentURI);
testing.expectEqual(true, newdoc.URL === document.URL);
testing.expectEqual(true, newdoc.compatMode === document.compatMode);
testing.expectEqual(true, newdoc.characterSet === document.characterSet);
testing.expectEqual(true, newdoc.charset === document.charset);
testing.expectEqual('HTML', document.documentElement.tagName);
testing.expectEqual('UTF-8', document.characterSet);
testing.expectEqual('UTF-8', document.charset);
testing.expectEqual('UTF-8', document.inputEncoding);
testing.expectEqual('CSS1Compat', document.compatMode);
testing.expectEqual('text/html', document.contentType);
testing.expectEqual('http://localhost:9589/dom/document.html', document.documentURI);
testing.expectEqual('http://localhost:9589/dom/document.html', document.URL);
testing.expectEqual(document.body, document.activeElement);
$('#a1').focus();
testing.expectEqual($('#a1'), document.activeElement);
testing.expectEqual(0, document.styleSheets.length);
</script>
<script id=getElementById>
let divById = document.getElementById('content');
testing.expectEqual('HTMLDivElement', divById.constructor.name);
testing.expectEqual('div', divById.localName);
</script>
<script id=getElementsByTagName>
let byTagName = document.getElementsByTagName('p');
testing.expectEqual(2, byTagName.length)
testing.expectEqual('p1', byTagName.item(0).id);
testing.expectEqual('p2', byTagName.item(1).id);
let byTagNameAll = document.getElementsByTagName('*');
// If you add a script block (or change the HTML in any other way on this
// frame), this test will break. Adjust it accordingly.
testing.expectEqual(12, byTagNameAll.length);
testing.expectEqual('html', byTagNameAll.item(0).localName);
testing.expectEqual('SCRIPT', byTagNameAll.item(11).tagName);
testing.expectEqual('s1', byTagNameAll.namedItem('s1').id);
</script>
<script id=getElementByClassName>
let ok = document.getElementsByClassName('ok');
testing.expectEqual(2, ok.length);
let empty = document.getElementsByClassName('empty');
testing.expectEqual(1, empty.length);
let emptyok = document.getElementsByClassName('empty ok');
testing.expectEqual(1, emptyok.length);
</script>
<script id=createXYZ>
var v = document.createDocumentFragment();
testing.expectEqual('#document-fragment', v.nodeName);
v = document.createTextNode('foo');
testing.expectEqual('#text', v.nodeName);
v = document.createAttribute('foo');
testing.expectEqual('foo', v.nodeName);
v = document.createComment('foo');
testing.expectEqual('#comment', v.nodeName);
v.cloneNode(); // doesn't crash, (I guess that's the point??)
let pi = document.createProcessingInstruction('foo', 'bar')
testing.expectEqual('foo', pi.target);
pi.cloneNode(); // doesn't crash (I guess that's the point??)
</script>
<script id=importNode>
let nimp = document.getElementById('content');
var v = document.importNode(nimp);
testing.expectEqual('DIV', v.nodeName);
</script>
<script id=children>
testing.expectEqual(1, document.children.length);
testing.expectEqual('HTML', document.children.item(0).nodeName)
testing.expectEqual('HTML', document.firstElementChild.nodeName);
testing.expectEqual('HTML', document.lastElementChild.nodeName);
testing.expectEqual(1, document.childElementCount);
let nd = new Document();
testing.expectEqual(0, nd.children.length);
testing.expectEqual(null, nd.children.item(0));
testing.expectEqual(null, nd.firstElementChild);
testing.expectEqual(null, nd.lastElementChild);
testing.expectEqual(0, nd.childElementCount);
</script>
<!-- <script id=createElement>
let emptydoc = document.createElement('html');
emptydoc.prepend(document.createElement('html'));
let emptydoc2 = document.createElement('html');
emptydoc2.append(document.createElement('html'));
// Not sure what the above are testing, I just copied and pasted them.
// Maybe that something doesn't crash?
// Adding this so that the test runner doesn't complain;
testing.skip();
</script> -->
<script id=querySelector>
testing.expectEqual('HTML', document.querySelector('*').nodeName);
testing.expectEqual('content', document.querySelector('#content').id);
testing.expectEqual('p1', document.querySelector('#p1').id);
testing.expectEqual('a1', document.querySelector('.ok').id);
testing.expectEqual('p1', document.querySelector('a ~ p').id);
testing.expectEqual('HTML', document.querySelector(':root').nodeName);
testing.expectEqual(2, document.querySelectorAll('p').length);
testing.expectEqual([''],
Array.from(document.querySelectorAll('#content > p#p1'))
.map(row => row.querySelector('span').textContent)
);
testing.expectEqual(0, document.querySelectorAll('.\\:popover-open').length);
testing.expectEqual(0, document.querySelectorAll('.foo\\:bar').length);
</script>
<script id=adoptNode>
// this test breaks the doc structure, keep it at the end
let nadop = document.getElementById('content')
var v = document.adoptNode(nadop);
testing.expectEqual('DIV', v.nodeName);
</script>
<script id=adoptedStyleSheets>
const acss = document.adoptedStyleSheets;
testing.expectEqual(0, acss.length);
acss.push(new CSSStyleSheet());
testing.expectEqual(1, acss.length);
</script>
<script id=createEvent>
const event = document.createEvent("Event");
testing.expectEqual(true, event instanceof Event);
testing.expectEqual("", event.type);
const customEvent = document.createEvent("CustomEvent");
customEvent.initCustomEvent("hey", false, false);
testing.expectEqual(true, customEvent instanceof CustomEvent);
testing.expectEqual(true, customEvent instanceof Event);
testing.withError(
(err) => {
// TODO: Check the error type.
},
() => document.createEvent("InvalidType"),
);
</script>

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<body></body>
<script id=documentFragment>
testing.expectEqual('DocumentFragment', new DocumentFragment().constructor.name);
const dc1 = new DocumentFragment();
testing.expectEqual(true, dc1.isEqualNode(dc1))
const dc2 = new DocumentFragment();
testing.expectEqual(true, dc1.isEqualNode(dc2))
let f = document.createDocumentFragment();
let d = document.createElement('div');
testing.expectEqual(0, d.childElementCount);
d.id = 'x';
testing.expectEqual(null, $('#x'));
f.append(d);
testing.expectEqual(1, f.childElementCount)
testing.expectEqual('x', f.children[0].id);
testing.expectEqual(null, $('#x'));
document.getElementsByTagName('body')[0].append(f.cloneNode(true));
testing.expectEqual(true, $('#x') != null);
testing.expectEqual(null, document.querySelector('.hello'));
testing.expectEqual(0, document.querySelectorAll('.hello').length);
testing.expectEqual('x', document.querySelector('#x').id);
testing.expectEqual(['x'], Array.from(document.querySelectorAll('#x')).map((n) => n.id));
</script>

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=documentType>
let dt1 = document.implementation.createDocumentType('qname1', 'pid1', 'sys1');
let dt2 = document.implementation.createDocumentType('qname2', 'pid2', 'sys2');
let dt3 = document.implementation.createDocumentType('qname1', 'pid1', 'sys1');
testing.expectEqual(true, dt1.isEqualNode(dt1));
testing.expectEqual(true, dt1.isEqualNode(dt3));
testing.expectEqual(false, dt1.isEqualNode(dt2));
testing.expectEqual(false, dt2.isEqualNode(dt3));
testing.expectEqual(false, dt1.isEqualNode(document));
testing.expectEqual(false, document.isEqualNode(dt1));
</script>

View File

@@ -1,7 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=domParser>
const dp = new DOMParser();;
const parsed = dp.parseFromString('<div>abc</div>', 'text/html');
testing.expectEqual('[object HTMLDocument]', parsed.toString());
</script>

View File

@@ -1,304 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="content" dir="ltr">
<a id="link" href="foo" class="ok">OK</a>
<p id="para-empty" class="ok empty">
<span id="para-empty-child"></span>
</p>
<p id="para"> And</p>
<!--comment-->
</div>
<script id=element>
let content = document.getElementById('content');
testing.expectEqual('http://www.w3.org/1999/xhtml', content.namespaceURI);
testing.expectEqual(null, content.prefix);
testing.expectEqual('div', content.localName);
testing.expectEqual('DIV', content.tagName);
testing.expectEqual('content', content.id);
testing.expectEqual('ltr', content.dir);
content.id = 'foo';
testing.expectEqual('foo', content.id);
content.id = 'content';
testing.expectEqual('', content.className);
let p1 = document.getElementById('para-empty');
testing.expectEqual('ok empty', p1.className);
testing.expectEqual('', p1.dir);
p1.className = 'foo bar baz';
testing.expectEqual('foo bar baz', p1.className);
p1.className = 'ok empty';
testing.expectEqual(2, p1.classList.length);
</script>
<script id=closest>
const el2 = document.createElement('div');
el2.id = 'closest';
el2.className = 'ok';
testing.expectEqual(el2, el2.closest('#closest'));
testing.expectEqual(el2, el2.closest('.ok'));
testing.expectEqual(null, el2.closest('.notok'));
const sp = document.createElement('span');
el2.appendChild(sp);
testing.expectEqual(el2, sp.closest('#closest'));
</script>
<script id=attributes>
testing.expectEqual(true, content.hasAttributes());
testing.expectEqual(2, content.attributes.length);
testing.expectEqual(['id', 'dir'], content.getAttributeNames());
testing.expectEqual('content', content.getAttribute('id'));
testing.expectEqual('content', content.attributes['id'].value);
let x = '';
for (const attr of content.attributes) {
x += attr.name + '=' + attr.value + ',';
}
testing.expectEqual('id=content,dir=ltr,', x);
testing.expectEqual(false, content.hasAttribute('foo'));
testing.expectEqual(null, content.getAttribute('foo'));
content.setAttribute('foo', 'bar');
testing.expectEqual(true, content.hasAttribute('foo'));
testing.expectEqual('bar', content.getAttribute('foo'));
testing.expectEqual(['id', 'dir', 'foo'], content.getAttributeNames());
testing.expectError('InvalidCharacterError: Invalid Character', () => {
content.setAttribute('.foo', 'invalid')
});
content.setAttribute('foo', 'baz');
testing.expectEqual(true, content.hasAttribute('foo'));
testing.expectEqual('baz', content.getAttribute('foo'));
content.removeAttribute('foo');
testing.expectEqual(false, content.hasAttribute('foo'));
testing.expectEqual(null, content.getAttribute('foo'));
let b = document.getElementById('content');
testing.expectEqual(true, b.toggleAttribute('foo'));
testing.expectEqual(true, b.hasAttribute('foo'));
testing.expectEqual('', b.getAttribute('foo'));
testing.expectEqual(false, b.toggleAttribute('foo'));
testing.expectEqual(false, b.hasAttribute('foo'));
testing.expectEqual(false, document.createElement('a').hasAttributes());
const div2 = document.createElement('div');
div2.innerHTML = '<p id=1 .lit$id=9>a</p>';
testing.expectEqual('<p id="1" .lit$id="9">a</p>', div2.innerHTML);
testing.expectEqual(['id', '.lit$id'], div2.childNodes[0].getAttributeNames());
</script>
<script id=children>
testing.expectEqual(3, content.children.length);
testing.expectEqual('A', content.firstElementChild.nodeName);
testing.expectEqual('P', content.lastElementChild.nodeName);
testing.expectEqual(3, content.childElementCount);
</script>
<script id=sibling>
content.prepend(document.createTextNode('foo'));
content.append(document.createTextNode('bar'));
let d = document.getElementById('para');
testing.expectEqual('P', d.previousElementSibling.nodeName);
testing.expectEqual(null, d.nextElementSibling);
</script>
<script id=querySelector>
testing.expectEqual(null, content.querySelector('foo'));
testing.expectEqual(null, content.querySelector('#foo'));
testing.expectEqual('link', content.querySelector('#link').id);
testing.expectEqual('para', content.querySelector('#para').id);
testing.expectEqual('link', content.querySelector('*').id);
testing.expectEqual('link', content.querySelector('*').id);
testing.expectEqual(null, content.querySelector('#content'));
testing.expectEqual('para', content.querySelector('#para').id);
testing.expectEqual('link', content.querySelector('.ok').id);
testing.expectEqual('para-empty', content.querySelector('a ~ p').id);
testing.expectEqual(0, content.querySelectorAll('foo').length);
testing.expectEqual(0, content.querySelectorAll('#foo').length);
testing.expectEqual(1, content.querySelectorAll('#link').length);
testing.expectEqual('link', content.querySelectorAll('#link').item(0).id);
testing.expectEqual(1, content.querySelectorAll('#para').length);
testing.expectEqual('para', content.querySelectorAll('#para').item(0).id);
testing.expectEqual(4, content.querySelectorAll('*').length);
testing.expectEqual(2, content.querySelectorAll('p').length);
testing.expectEqual('link', content.querySelectorAll('.ok').item(0).id);
</script>
<script id=createdAttributes>
let ff = document.createAttribute('foo');
content.setAttributeNode(ff);
testing.expectEqual('foo', content.getAttributeNode('foo').name);
testing.expectEqual('foo', content.removeAttributeNode(ff).name);
testing.expectEqual(null, content.getAttributeNode('bar'));
</script>
<script id=innerHTML>
testing.expectEqual(' And', document.getElementById('para').innerHTML);
testing.expectEqual('<span id="para-empty-child"></span>', $('#para-empty').innerHTML.trim());
let h = $('#para-empty');
const prev = h.innerHTML;
h.innerHTML = '<p id="hello">hello world</p>';
testing.expectEqual('<p id="hello">hello world</p>', h.innerHTML);
testing.expectEqual('P', h.firstChild.nodeName);
testing.expectEqual('hello', h.firstChild.id);
testing.expectEqual('hello world', h.firstChild.textContent);
h.innerHTML = prev;
testing.expectEqual('<span id="para-empty-child"></span>', $('#para-empty').innerHTML.trim());
testing.expectEqual('<p id="para"> And</p>', $('#para').outerHTML);
</script>
<script id=matches>
const el = document.createElement('div');
el.id = 'matches';
el.className = 'ok';
testing.expectEqual(true, el.matches('#matches'));
testing.expectEqual(true, el.matches('.ok'));
testing.expectEqual(false, el.matches('.notok'));
</script>
<script id=scroll>
const el3 = document.createElement('div');
el3.scrollIntoViewIfNeeded();
el3.scrollIntoViewIfNeeded(false);
// doesn't throw is good enough
testing.skip();
</script>
<script id=before>
const before_container = document.createElement('div');
document.append(before_container);
const b1 = document.createElement('div');
before_container.append(b1);
const b1_a = document.createElement('p');
b1.before(b1_a, 'over 9000');
testing.expectEqual('<p></p>over 9000<div></div>', before_container.innerHTML);
</script>
<script id=after>
const after_container = document.createElement('div');
document.append(after_container);
const a1 = document.createElement('div');
after_container.append(a1);
const a1_a = document.createElement('p');
a1.after('over 9000', a1_a);
testing.expectEqual('<div></div>over 9000<p></p>', after_container.innerHTML);
</script>
<script id=getElementsByTagName>
var div1 = document.createElement('div');
div1.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
testing.expectEqual(1, div1.getElementsByTagName('a').length);
</script>
<script id=outerHTML>
let fc = document.createElement('div')
fc.innerHTML = '<script><\/script>';
testing.expectEqual('<div><script><\/script></div>', fc.outerHTML);
fc = document.createElement('div')
fc.innerHTML = '<script><\/script><p>hello</p>';
testing.expectEqual('<div><script><\/script><p>hello</p></div>', fc.outerHTML);
</script>
<script id=remove>
const rm = document.createElement('div');
testing.expectEqual([], rm.getAttributeNames());
rm.id = 'to-remove';
document.getElementsByTagName('body')[0].appendChild(rm);
$('#to-remove').remove();
testing.expectEqual(null, $('#to-remove'));
</script>
<script id=elementDir>
const divElement = document.createElement("div");
// Always initialized with empty string if `dir` attribute not provided.
testing.expectEqual("", divElement.dir);
divElement.dir = "ltr";
testing.expectEqual("ltr", divElement.dir);
divElement.dir = "rtl";
testing.expectEqual("rtl", divElement.dir);
divElement.dir = "auto";
testing.expectEqual("auto", divElement.dir);
</script>
<script id=linkRel>
const linkElement = document.createElement("link");
// A newly created link element must have it's rel set to empty string.
testing.expectEqual("", linkElement.rel);
linkElement.rel = "stylesheet";
testing.expectEqual("stylesheet", linkElement.rel);
</script>
<!-- This structure will get mutated by insertAdjacentHTML test -->
<div id="insert-adjacent-html-outer-wrapper">
<div id="insert-adjacent-html-inner-wrapper">
<span></span>
<p>content</p>
</div>
</div>
<script id=insertAdjacentHTML>
// Insert "beforeend".
const wrapper = $("#insert-adjacent-html-inner-wrapper");
wrapper.insertAdjacentHTML("beforeend", "<h1>title</h1>");
let newElement = wrapper.lastElementChild;
testing.expectEqual("H1", newElement.tagName);
testing.expectEqual("title", newElement.innerText);
// Insert "beforebegin".
wrapper.insertAdjacentHTML("beforebegin", "<h2>small title</h2>");
newElement = wrapper.previousElementSibling;
testing.expectEqual("H2", newElement.tagName);
testing.expectEqual("small title", newElement.innerText);
// Insert "afterend".
wrapper.insertAdjacentHTML("afterend", "<div id=\"afterend\">after end</div>");
newElement = wrapper.nextElementSibling;
testing.expectEqual("DIV", newElement.tagName);
testing.expectEqual("after end", newElement.innerText);
testing.expectEqual("afterend", newElement.id);
// Insert "afterbegin".
wrapper.insertAdjacentHTML("afterbegin", "<div class=\"afterbegin\">after begin</div><yy></yy>");
newElement = wrapper.firstElementChild;
testing.expectEqual("DIV", newElement.tagName);
testing.expectEqual("after begin", newElement.innerText);
testing.expectEqual("afterbegin", newElement.className);
</script>
<script id=nonBreakingSpace>
// Test non-breaking space encoding (critical for React hydration)
const div = document.createElement('div');
div.innerHTML = 'hello\xa0world';
testing.expectEqual('hello\xa0world', div.textContent);
testing.expectEqual('hello&nbsp;world', div.innerHTML);
// Test that outerHTML also encodes non-breaking spaces correctly
const p = document.createElement('p');
p.textContent = 'XAnge\xa0Privacy';
testing.expectEqual('<p>XAnge&nbsp;Privacy</p>', p.outerHTML);
</script> -->

View File

@@ -1,116 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="content"><p id=para></p></div>
<script id=eventTarget>
testing.expectEqual('[object EventTarget]', new EventTarget().toString());
let content = $('#content');
let para = $('#para');
var nb = 0;
var evt;
var phase;
var cur;
function reset() {
nb = 0;
evt = undefined;
phase = undefined;
cur = undefined;
}
function cbk(event) {
evt = event;
phase = event.eventPhase;
cur = event.currentTarget;
nb++;
}
content.addEventListener('basic', cbk);
content.dispatchEvent(new Event('basic'));
testing.expectEqual(1, nb);
testing.expectEqual(true, evt instanceof Event);
testing.expectEqual('basic', evt.type);
testing.expectEqual(2, phase);
testing.expectEqual('content', cur.getAttribute('id'));
reset();
para.dispatchEvent(new Event('basic'))
// handler is not called, no capture, not the targeno bubbling
testing.expectEqual(0, nb);
testing.expectEqual(undefined, evt);
reset();
content.addEventListener('basic', cbk);
content.dispatchEvent(new Event('basic'))
testing.expectEqual(1, nb);
reset();
content.addEventListener('basic', cbk, true);
content.dispatchEvent(new Event('basic'));
testing.expectEqual(2, nb);
reset()
content.removeEventListener('basic', cbk);
content.dispatchEvent(new Event('basic'));
testing.expectEqual(1, nb);
reset();
content.removeEventListener('basic', cbk, {capture: true});
content.dispatchEvent(new Event('basic'));
testing.expectEqual(0, nb);
reset();
content.addEventListener('capture', cbk, true);
content.dispatchEvent(new Event('capture'));
testing.expectEqual(1, nb);
testing.expectEqual(true, evt instanceof Event);
testing.expectEqual('capture', evt.type);
testing.expectEqual(2, phase);
testing.expectEqual('content', cur.getAttribute('id'));
reset();
para.dispatchEvent(new Event('capture'));
testing.expectEqual(1, nb);
testing.expectEqual(true, evt instanceof Event);
testing.expectEqual('capture', evt.type);
testing.expectEqual(1, phase);
testing.expectEqual('content', cur.getAttribute('id'));
reset();
content.addEventListener('bubbles', cbk);
content.dispatchEvent(new Event('bubbles', {bubbles: true}));
testing.expectEqual(1, nb);
testing.expectEqual(true, evt instanceof Event);
testing.expectEqual('bubbles', evt.type);
testing.expectEqual(2, phase);
testing.expectEqual('content', cur.getAttribute('id'));
reset();
para.dispatchEvent(new Event('bubbles', {bubbles: true}));
testing.expectEqual(1, nb);
testing.expectEqual(true, evt instanceof Event);
testing.expectEqual('bubbles', evt.type);
testing.expectEqual(3, phase);
testing.expectEqual('content', cur.getAttribute('id'));
const obj1 = {
calls: 0,
handleEvent: function() { this.calls += 1 }
};
content.addEventListener('he', obj1);
content.dispatchEvent(new Event('he'));
testing.expectEqual(1, obj1.calls);
content.removeEventListener('he', obj1);
content.dispatchEvent(new Event('he'));
testing.expectEqual(1, obj1.calls);
// doesn't crash on null receiver
content.addEventListener('he2', null);
content.dispatchEvent(new Event('he2'));
</script>

View File

@@ -1,39 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="content">
<a id="link" href="foo" class="ok">OK</a>
</div>
<script id=exceptions>
let content = $('#content');
let link = $('#link');
testing.withError((err) => {
testing.expectEqual(3, err.code);
testing.expectEqual('Hierarchy Error', err.message);
testing.expectEqual('HierarchyRequestError: Hierarchy Error', err.toString());
testing.expectEqual(true, err instanceof DOMException);
testing.expectEqual(true, err instanceof Error);
}, () => link.appendChild(content));
</script>
<script id=constructor>
let exc0 = new DOMException();
testing.expectEqual('Error', exc0.name);
testing.expectEqual(0, exc0.code);
testing.expectEqual('', exc0.message);
testing.expectEqual('Error', exc0.toString());
let exc1 = new DOMException('Sandwich malfunction');
testing.expectEqual('Error', exc1.name);
testing.expectEqual(0, exc1.code);
testing.expectEqual('Sandwich malfunction', exc1.message);
testing.expectEqual('Error: Sandwich malfunction', exc1.toString());
let exc2 = new DOMException('Caterpillar turned into a butterfly', 'NoModificationAllowedError');
testing.expectEqual('NoModificationAllowedError', exc2.name);
testing.expectEqual(7, exc2.code);
testing.expectEqual('Caterpillar turned into a butterfly', exc2.message);
testing.expectEqual('NoModificationAllowedError: Caterpillar turned into a butterfly', exc2.toString());
</script>

View File

@@ -1,67 +0,0 @@
<!DOCTYPE html>
<body>
<div id="content">
<a id="link" href="foo" class="ok">OK</a>
<p id="para-empty" class="ok empty">
<span id="para-empty-child"></span>
</p>
<p id="para"> And</p>
<!--comment-->
</div>
</body>
<script src="../testing.js"></script>
<script id=caseInsensitive>
const Ptags = document.getElementsByTagName('P');
testing.expectEqual(2, Ptags.length);
testing.expectEqual('p', Ptags.item(0).localName);
testing.expectEqual('p', Ptags.item(1).localName);
</script>
<script id=all>
let allTags = document.getElementsByTagName('*');
testing.expectEqual(11, allTags.length);
testing.expectEqual('html', allTags.item(0).localName);
testing.expectEqual('html', allTags.item(0).localName);
testing.expectEqual('head', allTags.item(1).localName);
testing.expectEqual('html', allTags.item(0).localName);
testing.expectEqual('body', allTags.item(2).localName);
testing.expectEqual('div', allTags.item(3).localName);
testing.expectEqual('p', allTags.item(7).localName);
testing.expectEqual('span', allTags.namedItem('para-empty-child').localName);
// array like
testing.expectEqual('html', allTags[0].localName);
testing.expectEqual('p', allTags[7].localName);
testing.expectEqual(undefined, allTags[14]);
testing.expectEqual('span', allTags['para-empty-child'].localName);
testing.expectEqual(undefined, allTags['foo']);
</script>
<script id=element>
let content = $('#content');
testing.expectEqual(4, content.getElementsByTagName('*').length);
testing.expectEqual(2, content.getElementsByTagName('p').length);
testing.expectEqual(0, content.getElementsByTagName('div').length);
testing.expectEqual(1, document.children.length);
testing.expectEqual(3, content.children.length);
</script>
<script id=liveness>
const ptags = document.getElementsByTagName('p');
testing.expectEqual(2, ptags.length);
testing.expectEqual(' And', ptags.item(1).textContent);
let p = document.createElement('p');
p.textContent = 'OK live';
// hasn't been added, still 2
testing.expectEqual(2, ptags.length);
testing.expectEqual(true, content.appendChild(p) != undefined);
testing.expectEqual(3, ptags.length);
testing.expectEqual('OK live', ptags.item(2).textContent);
testing.expectEqual(true, content.insertBefore(p, $('#para-empty')) != undefined);
testing.expectEqual('OK live', ptags.item(0).textContent);
</script>

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=implementation>
let impl = document.implementation;
testing.expectEqual("[object HTMLDocument]", impl.createHTMLDocument().toString());;
const doc = impl.createHTMLDocument('foo');
testing.expectEqual("[object HTMLDocument]", doc.toString());
testing.expectEqual("foo", doc.title);
testing.expectEqual("[object HTMLBodyElement]", doc.body.toString());
testing.expectEqual("[object XMLDocument]", impl.createDocument(null, 'foo').toString());
testing.expectEqual("[object DocumentType]", impl.createDocumentType('foo', 'bar', 'baz').toString());
testing.expectEqual(true, impl.hasFeature());
</script>

View File

@@ -1,163 +0,0 @@
<!DOCTYPE html>
<body></body>
<script src="../testing.js"></script>
<script id=intersectionObserver>
{
// never attached
let count = 0;
const div = document.createElement('div');
new IntersectionObserver((entries) => {
count += 1;
}).observe(div);
testing.onload(() => {
testing.expectEqual(0, count);
});
}
{
// not connected, but has parent
let count = 0;
const div1 = document.createElement('div');
const div2 = document.createElement('div');
new IntersectionObserver((entries) => {
console.log(entries[0]);
count += 1;
}).observe(div1);
div2.appendChild(div1);
testing.onload(() => {
testing.expectEqual(1, count);
});
}
</script>
<script id=reobserve>
{
let count = 0;
let observer = new IntersectionObserver(entries => {
count += entries.length;
});
const div1 = document.createElement('div');
document.body.appendChild(div1);
// cannot fire synchronously, must be on the next tick
testing.expectEqual(0, count);
observer.observe(div1);
testing.expectEqual(0, count);
observer.observe(div1);
observer.observe(div1);
testing.expectEqual(0, count);
testing.onload(() => {
testing.expectEqual(1, count);
});
}
</script>
<script id=unobserve>
{
let count = 0;
let observer = new IntersectionObserver(entries => {
count += entries.length;
});
const div1 = document.createElement('div');
document.body.appendChild(div1);
testing.expectEqual(0, count);
observer.observe(div1);
testing.expectEqual(0, count);
observer.observe(div1);
observer.observe(div1);
testing.expectEqual(0, count);
observer.unobserve(div1);
testing.onload(() => {
testing.expectEqual(0, count);
});
}
</script>
<script id=disconnect>
{
let count = 0;
let observer = new IntersectionObserver(entries => {
count += entries.length;
});
const div1 = document.createElement('div');
document.body.appendChild(div1);
// cannot fire synchronously, must be on the next tick
testing.expectEqual(0, count);
observer.observe(div1);
testing.expectEqual(0, count);
observer.observe(div1);
observer.observe(div1);
testing.expectEqual(0, count);
observer.disconnect();
testing.onload(() => {
testing.expectEqual(0, count);
});
}
</script>
<script id=entry>
{
let entry = null;
let observer = new IntersectionObserver(entries => {
entry = entries[0];
});
let div1 = document.createElement('div');
document.body.appendChild(div1);
new IntersectionObserver(entries => { entry = entries[0]; }).observe(div1);
testing.onload(() => {
testing.expectEqual(125, entry.boundingClientRect.x);
testing.expectEqual(1, entry.intersectionRatio);
testing.expectEqual(125, entry.intersectionRect.x);
testing.expectEqual(125, entry.intersectionRect.y);
testing.expectEqual(5, entry.intersectionRect.width);
testing.expectEqual(5, entry.intersectionRect.height);
testing.expectEqual(true, entry.isIntersecting);
testing.expectEqual(0, entry.rootBounds.x);
testing.expectEqual(0, entry.rootBounds.y);
testing.expectEqual(1920, entry.rootBounds.width);
testing.expectEqual(1080, entry.rootBounds.height);
testing.expectEqual('[object HTMLDivElement]', entry.target.toString());
});
}
</script>
<script id=timing>
{
const capture = [];
const observer = new IntersectionObserver(() => {
capture.push('callback');
});
const div = document.createElement('div');
capture.push('pre-append');
document.body.appendChild(div);
capture.push('post-append');
capture.push('pre-observe');
observer.observe(div);
capture.push('post-observe');
testing.onload(() => {
testing.expectEqual([
'pre-append',
'post-append',
'pre-observe',
'post-observe',
'callback',
], capture)
});
}
</script>

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<div id="content"></div>
<script src="../testing.js"></script>
<script id=namedNodeMap>
let a = document.getElementById('content').attributes;
testing.expectEqual(1, a.length);
testing.expectEqual('[object Attr]', a.item(0).toString());
testing.expectEqual(null, a.item(1));
testing.expectEqual('[object Attr]', a.getNamedItem('id').toString());
testing.expectEqual(null, a.getNamedItem('foo'));
testing.expectEqual('[object Attr]', a.setNamedItem(a.getNamedItem('id')).toString());
testing.expectEqual('id', a['id'].name);
testing.expectEqual('content', a['id'].value);
testing.expectEqual(undefined, a['other']);
a[0].value = 'abc123';
testing.expectEqual('abc123', a[0].value);
</script>

View File

@@ -1,219 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<!-- Test fixture -->
<div id="container">
<!-- comment1 -->
<div id="outer">
<!-- comment2 -->
<span id="inner">
<!-- comment3 -->
Text content
<!-- comment4 -->
</span>
<!-- comment5 -->
</div>
<!-- comment6 -->
</div>
<script id=nodeFilter>
testing.expectEqual(1, NodeFilter.FILTER_ACCEPT);
testing.expectEqual(2, NodeFilter.FILTER_REJECT);
testing.expectEqual(3, NodeFilter.FILTER_SKIP);
testing.expectEqual(4294967295, NodeFilter.SHOW_ALL);
testing.expectEqual(129, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT);
</script>
<script id=treeWalkerComments>
{
const container = $('#container');
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_COMMENT,
null,
false
);
const comments = [];
let node;
while (node = walker.nextNode()) {
comments.push(node.data.trim());
}
// Should find all 6 comments, including those nested inside elements
testing.expectEqual(6, comments.length);
testing.expectEqual('comment1', comments[0]);
testing.expectEqual('comment2', comments[1]);
testing.expectEqual('comment3', comments[2]);
testing.expectEqual('comment4', comments[3]);
testing.expectEqual('comment5', comments[4]);
testing.expectEqual('comment6', comments[5]);
}
</script>
<script id=treeWalkerElements>
{
const container = $('#container');
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_ELEMENT,
null,
false
);
const elements = [];
let node;
while (node = walker.nextNode()) {
if (node.id) {
elements.push(node.id);
}
}
// Should find the 2 nested elements (outer and inner)
testing.expectEqual(2, elements.length);
testing.expectEqual('outer', elements[0]);
testing.expectEqual('inner', elements[1]);
}
</script>
<script id=treeWalkerAll>
{
const container = $('#container');
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_ALL,
null,
false
);
let commentCount = 0;
let elementCount = 0;
let textCount = 0;
let node;
while (node = walker.nextNode()) {
if (node.nodeType === 8) commentCount++; // Comment
else if (node.nodeType === 1) elementCount++; // Element
else if (node.nodeType === 3) textCount++; // Text
}
testing.expectEqual(6, commentCount);
testing.expectEqual(2, elementCount);
testing.expectEqual(true, textCount > 0);
}
</script>
<script id=treeWalkerCombined>
{
const container = $('#container');
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,
null,
false
);
let commentCount = 0;
let elementCount = 0;
let node;
while (node = walker.nextNode()) {
if (node.nodeType === 8) commentCount++; // Comment
else if (node.nodeType === 1) elementCount++; // Element
}
// Should find 6 comments and 2 elements, but no text nodes
testing.expectEqual(6, commentCount);
testing.expectEqual(2, elementCount);
}
</script>
<script id=treeWalkerCustomFilter>
{
const container = $('#container');
// Filter that accepts only elements with id
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_ELEMENT,
{
acceptNode: function(node) {
return node.id ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
},
false
);
const elements = [];
let node;
while (node = walker.nextNode()) {
elements.push(node.id);
}
// Should find only elements with id (outer and inner)
testing.expectEqual(2, elements.length);
testing.expectEqual('outer', elements[0]);
testing.expectEqual('inner', elements[1]);
}
</script>
<script id=nodeIteratorComments>
{
const container = $('#container');
const iterator = document.createNodeIterator(
container,
NodeFilter.SHOW_COMMENT,
null,
false
);
const comments = [];
let node;
while (node = iterator.nextNode()) {
comments.push(node.data.trim());
}
// Should find all 6 comments, including those nested inside elements
testing.expectEqual(6, comments.length);
testing.expectEqual('comment1', comments[0]);
testing.expectEqual('comment2', comments[1]);
testing.expectEqual('comment3', comments[2]);
testing.expectEqual('comment4', comments[3]);
testing.expectEqual('comment5', comments[4]);
testing.expectEqual('comment6', comments[5]);
}
</script>
<script id=reactLikeScenario>
{
// Test a React-like scenario with comment markers
const div = document.createElement('div');
div.innerHTML = `
<a href="/">
<!--$-->
<svg viewBox="0 0 10 10">
<path d="M0,0 L10,10" />
</svg>
<!--/$-->
</a>
`;
const walker = document.createTreeWalker(
div,
NodeFilter.SHOW_COMMENT,
null,
false
);
const comments = [];
let node;
while (node = walker.nextNode()) {
comments.push(node.data);
}
// Should find both React markers even though they're nested inside <a>
testing.expectEqual(2, comments.length);
testing.expectEqual('$', comments[0]);
testing.expectEqual('/$', comments[1]);
}
</script>

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<div id="content">
<a id="link" href="foo" class="ok">OK</a>
<p id="para-empty" class="ok empty">
<span id="para-empty-child"></span>
</p>
<p id="para"> And</p>
<!--comment-->
</div>
<script src="../testing.js"></script>
<script id=nodeList>
let list = document.getElementById('content').childNodes;
testing.expectEqual(9, list.length);
testing.expectEqual('Text', list[0].__proto__.constructor.name);
let i = 0;
list.forEach(function (n, idx) { i += idx; });
testing.expectEqual(36, i);
</script>

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<div id="target-container">
<p id="reference-node">
I am the original reference node.
</p>
</div>
<script src="../testing.js"></script>
<script id=nodeOwner>
const parser = new DOMParser();
const newDoc = parser.parseFromString('<div id="new-node"><p>Hey</p><span>Marked</span></div>', 'text/html');
const newNode = newDoc.getElementById('new-node');
const parent = $('#target-container');
const referenceNode = $('#reference-node');
parent.insertBefore(newNode, referenceNode);
const k = $('#new-node');
const ptag = k.querySelector('p');
const spanTag = k.querySelector('span');
const anotherDoc = parser.parseFromString('<div id="another-new-node"></div>', 'text/html');
const anotherNewNode = anotherDoc.getElementById('another-new-node');
testing.expectEqual('[object HTMLDivElement]', parent.appendChild(anotherNewNode).toString());
testing.expectEqual(newNode.ownerDocument, parent.ownerDocument);
testing.expectEqual(anotherNewNode.ownerDocument, parent.ownerDocument);
testing.expectEqual('P', newNode.firstChild.nodeName);
testing.expectEqual(parent.ownerDocument, ptag.ownerDocument);
testing.expectEqual(parent.ownerDocument, spanTag.ownerDocument);
testing.expectEqual(true, parent.contains(newNode));
testing.expectEqual(true, parent.contains(anotherNewNode));
testing.expectEqual(false, anotherDoc.contains(anotherNewNode));
testing.expectEqual(false, newDoc.contains(newNode));
</script>

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=performance>
let performance = window.performance;
testing.expectEqual(true, performance instanceof Performance);
let mark1 = performance.mark("start");
testing.expectEqual(true, mark1 instanceof PerformanceMark);
testing.expectEqual('start', mark1.name);
testing.expectEqual('mark', mark1.entryType);
testing.expectEqual(0, mark1.duration);
testing.expectEqual(null, mark1.detail);
let mark2 = performance.mark("start", {startTime: 32939393.9});
testing.expectEqual(32939393.9, mark2.startTime);
</script>

View File

@@ -1,5 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=performanceObserver>
testing.expectEqual(0, PerformanceObserver.supportedEntryTypes.length);
</script>

View File

@@ -1,22 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=processingInstruction>
let pi = document.createProcessingInstruction('foo', 'bar');
testing.expectEqual('foo', pi.target);
testing.expectEqual('bar', pi.data);
pi.data = 'foo';
testing.expectEqual('foo', pi.data);
let pi2 = pi.cloneNode();
testing.expectEqual(7, pi2.nodeType);
let pi11 = document.createProcessingInstruction('target1', 'data1');
let pi12 = document.createProcessingInstruction('target2', 'data2');
let pi13 = document.createProcessingInstruction('target1', 'data1');
testing.expectEqual(true, pi11.isEqualNode(pi11));
testing.expectEqual(true, pi11.isEqualNode(pi13));
testing.expectEqual(false, pi11.isEqualNode(pi12));
testing.expectEqual(false, pi12.isEqualNode(pi13));
testing.expectEqual(false, pi11.isEqualNode(document));
testing.expectEqual(false, document.isEqualNode(pi11));
</script>

View File

@@ -1,41 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=content><!-- hello world --><p>over 9000</p></div>
<script id="constructor">
let range = new Range();
testing.expectEqual(true, range instanceof Range);
testing.expectEqual(true, range instanceof AbstractRange);
// Test initial state - collapsed range
testing.expectEqual(true, range.collapsed);
testing.expectEqual(0, range.startOffset);
testing.expectEqual(0, range.endOffset);
testing.expectEqual(true, range.startContainer instanceof HTMLDocument);
testing.expectEqual(true, range.endContainer instanceof HTMLDocument);
</script>
<script id="createRange">
let docRange = document.createRange();
testing.expectEqual(true, docRange instanceof Range);
testing.expectEqual(true, docRange.collapsed);
</script>
<script id=textRange>
const container = $('#content');
const commentNode = container.childNodes[0];
testing.expectEqual(' hello world ', commentNode.nodeValue);
const textRange = document.createRange();
textRange.selectNodeContents(commentNode);
testing.expectEqual(0, textRange.startOffset);
testing.expectEqual(13, textRange.endOffset); // length of comment
</script>
<script id=nodeRange>
const nodeRange = document.createRange();
nodeRange.selectNodeContents(container);
testing.expectEqual(0, nodeRange.startOffset);
testing.expectEqual(2, nodeRange.endOffset); // length of container.childNodes
</script>

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<a id="link" href="foo" class="ok">OK</a>
<script src="../testing.js"></script>
<script id=text>
let t = new Text('foo');
testing.expectEqual('foo', t.data);
let emptyt = new Text();
testing.expectEqual('', emptyt.data);
let text = $('#link').firstChild;
testing.expectEqual('OK', text.wholeText);
text.data = 'OK modified';
let split = text.splitText('OK'.length);
testing.expectEqual(' modified', split.data);
testing.expectEqual('OK', text.data);
</script>

View File

@@ -1,64 +0,0 @@
<!DOCTYPE html>
<p id="para-empty" class="ok empty">
<script src="../testing.js"></script>
<script id=tokenList>
let gs = $('#para-empty');
let cl = gs.classList;
testing.expectEqual('ok empty', gs.className);
testing.expectEqual('ok empty', cl.value);
testing.expectEqual(2, cl.length);
gs.className = 'foo bar baz';
testing.expectEqual('foo bar baz', gs.className);
testing.expectEqual(3, cl.length);
gs.className = 'ok empty';
testing.expectEqual(2, cl.length);
let cl2 = gs.classList;
testing.expectEqual(2, cl2.length);
testing.expectEqual('ok', cl2.item(0));
testing.expectEqual('empty', cl2.item(1));
testing.expectEqual(true, cl2.contains('ok'));
testing.expectEqual(false, cl2.contains('nok'));
cl2.add('foo', 'bar', 'baz');
testing.expectEqual(5, cl2.length);
cl2.remove('foo', 'bar', 'baz');
testing.expectEqual(2, cl2.length);
let cl3 = gs.classList;
testing.expectEqual(false, cl3.toggle('ok'));
testing.expectEqual(true, cl3.toggle('ok'));
testing.expectEqual(2, cl3.length);
let cl4 = gs.classList;
testing.expectEqual(true, cl4.replace('ok', 'nok'));
testing.expectEqual("empty nok", cl4.value);
testing.expectEqual(true, cl4.replace('nok', 'ok'));
testing.expectEqual("empty ok", cl4.value);
let cl5 = gs.classList;
let keys = [...cl5.keys()];
testing.expectEqual(2, keys.length);
testing.expectEqual(0, keys[0]);
testing.expectEqual(1, keys[1]);
let values = [...cl5.values()];
testing.expectEqual(2, values.length);
testing.expectEqual('empty', values[0]);
testing.expectEqual('ok', values[1]);
let entries = [...cl5.entries()];
testing.expectEqual(2, entries.length);
testing.expectEqual([0, 'empty'], entries[0]);
testing.expectEqual([1, 'ok'], entries[1]);
let cl6 = gs.classList;
cl6.value = 'a b ccc';
testing.expectEqual('a b ccc', cl6.value);
testing.expectEqual('a b ccc', cl6.toString());
</script>

View File

@@ -1,60 +0,0 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<script src="../testing.js"></script>
<script id=decoder>
let d1 = new TextDecoder();
testing.expectEqual('utf-8', d1.encoding);
testing.expectEqual(false, d1.fatal);
testing.expectEqual(false, d1.ignoreBOM);
testing.expectEqual('', d1.decode());
testing.expectEqual('𠮷', d1.decode(new Uint8Array([240, 160, 174, 183])));
testing.expectEqual('𠮷', d1.decode(new Uint8Array([0xEF, 0xBB, 0xBF, 240, 160, 174, 183])));
testing.expectEqual('<27>2', d1.decode(new Uint8Array([249, 50])));
{
const buffer = new ArrayBuffer(4);
const ints = new Uint8Array(buffer)
ints[0] = 240;
ints[1] = 160;
ints[2] = 174;
ints[3] = 183;
testing.expectEqual('𠮷', d1.decode(buffer));
}
{
const buffer = new ArrayBuffer(4);
const dv = new DataView(buffer);
dv.setUint8(0, 240);
dv.setUint8(1, 160);
dv.setUint8(2, 174);
dv.setUint8(3, 183);
testing.expectEqual('𠮷', d1.decode(dv));
}
let d2 = new TextDecoder('utf8', {fatal: true})
testing.expectError('Error: InvalidUtf8', () => {
let data = new Uint8Array([240, 240, 160, 174, 183]);
d2.decode(data);
});
</script>
<script id=stream>
let d3 = new TextDecoder();
testing.expectEqual('', d2.decode(new Uint8Array([226, 153]), { stream: true }));
testing.expectEqual('♥', d2.decode(new Uint8Array([165]), { stream: true }));
</script>
<script id=slice>
const buf1 = new ArrayBuffer(7);
const arr1 = new Uint8Array(buf1)
arr1[0] = 80;
arr1[1] = 81;
arr1[2] = 82;
arr1[3] = 83;
arr1[4] = 84;
arr1[5] = 85;
arr1[6] = 86;
testing.expectEqual('RST', d3.decode(new Uint8Array(buf1, 2, 3)));
</script>

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=encoder>
var encoder = new TextEncoder();
testing.expectEqual('utf-8', encoder.encoding);
testing.expectEqual([226, 130, 172], Array.from(encoder.encode('€')));
// Invalid utf-8 sequence.
// Browsers give a different result for this, they decode it to:
// 50, 50, 54, 44, 52, 48, 44, 49, 54, 49
testing.expectError('Error: InvalidUtf8', () => {
encoder.encode(new Uint8Array([0xE2,0x28,0xA1]));
});
</script>

View File

@@ -1,36 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=noNata>
{
let event = new CompositionEvent("test", {});
testing.expectEqual(true, event instanceof CompositionEvent);
testing.expectEqual(true, event instanceof Event);
testing.expectEqual("test", event.type);
testing.expectEqual("", event.data);
}
</script>
<script id=withData>
{
let event = new CompositionEvent("test2", {data: "over 9000!"});
testing.expectEqual("test2", event.type);
testing.expectEqual("over 9000!", event.data);
}
</script>
<script id=dispatch>
{
let called = 0;
document.addEventListener('CE', (e) => {
testing.expectEqual('test-data', e.data);
testing.expectEqual(true, e instanceof CompositionEvent);
called += 1
});
document.dispatchEvent(new CompositionEvent('CE', {data: 'test-data'}));
testing.expectEqual(1, called);
}
</script>

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=custom>
let capture = null;
const el = document.createElement('div');
el.addEventListener('c1', (e) => { capture = 'c1-' + new String(e.detail)});
el.addEventListener('c2', (e) => { capture = 'c2-' + new String(e.detail.over)});
el.dispatchEvent(new CustomEvent('c1'));
testing.expectEqual("c1-null", capture);
el.dispatchEvent(new CustomEvent('c1', {detail: '123'}));
testing.expectEqual("c1-123", capture);
el.dispatchEvent(new CustomEvent('c2', {detail: {over: 9000}}));
testing.expectEqual("c2-9000", capture);
let window_calls = 0;
window.addEventListener('c1', () => {
window_calls += 1;
});
el.dispatchEvent(new CustomEvent('c1', {bubbles: true}));
testing.expectEqual(1, window_calls);
</script>

View File

@@ -1,139 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=content>
<p id="para"></p>
</div>
<script id=dispatch>
const startTime = new Event('x').timeStamp;
let content = $('#content');
// let para = document.getElementById('para');
var nb = 0;
var evt = null;
const incrementCallback = function(e) {
evt = e;
nb += 1;
e.preventDefault();
}
content.addEventListener('dispatch', incrementCallback);
content.dispatchEvent(new Event('dispatch', {bubbles: true, cancelable: true}));
testing.expectEqual(1, nb);
testing.expectEqual(content, evt.target);
testing.expectEqual(true, evt.bubbles);
testing.expectEqual(true, evt.cancelable);
testing.expectEqual(true, evt.defaultPrevented);
testing.expectEqual(false, evt.isTrusted);
testing.expectEqual(true, evt.timeStamp >= Math.floor(startTime));
</script>
<script id=propagate>
nb = 0;
let para = $('#para');
// the stop listener is capturing, so it propagates down
content.addEventListener('stop',function(e) {
e.stopPropagation();
nb += 1;
}, true)
para.addEventListener('stop',function(e) {
nb += 10;
});
para.dispatchEvent(new Event('stop'));
// didn't propagate down (because of capturing) to para handler
testing.expectEqual(1, nb);
</script>
<script id=immediate>
nb = 0;
content.addEventListener('immediate', function(e) {
e.stopImmediatePropagation();
nb += 1;
});
// the following event listener will not be invoked
content.addEventListener('immediate', function(e) {
nb += 10;
});
content.dispatchEvent(new Event('immediate'));
testing.expectEqual(1, nb);
</script>
<script id=legacy>
nb = 0;
content.addEventListener('legacy', incrementCallback);
let evtLegacy = document.createEvent('Event');
evtLegacy.initEvent('legacy');
content.dispatchEvent(evtLegacy);
testing.expectEqual(1, nb);
</script>
<script id=removeListener>
nb = 0;
document.addEventListener('count', incrementCallback);
document.removeEventListener('count', incrementCallback);
document.dispatchEvent(new Event('count'));
testing.expectEqual(0, nb);
</script>
<script id=once>
document.addEventListener('count', incrementCallback, {once: true});
document.dispatchEvent(new Event('count'));
document.dispatchEvent(new Event('count'));
document.dispatchEvent(new Event('count'));
testing.expectEqual(1, nb);
</script>
<script id=abortController>
nb = 0;
let ac = new AbortController()
document.addEventListener('count', incrementCallback, {signal: ac.signal})
document.dispatchEvent(new Event('count'));
document.dispatchEvent(new Event('count'));
ac.abort();
document.dispatchEvent(new Event('count'));
testing.expectEqual(2, nb);
document.removeEventListener('count', incrementCallback);
</script>
<script id=composedPath>
testing.expectEqual([], new Event('').composedPath());
let div1 = document.createElement('div');
let sr1 = div1.attachShadow({mode: 'open'});
sr1.innerHTML = "<p id=srp1></p>";
document.getElementsByTagName('body')[0].appendChild(div1);
let cp = null;
const shadowCallback = function(e) {
cp = e.composedPath().map((n) => n.id || n.nodeName || n.toString());
}
div1.addEventListener('click', shadowCallback);
sr1.getElementById('srp1').click();
testing.expectEqual(
['srp1', '#document-fragment', 'DIV', 'BODY', 'HTML', '#document', '[object Window]'],
cp
);
let div2 = document.createElement('div');
let sr2 = div2.attachShadow({mode: 'closed'});
sr2.innerHTML = "<p id=srp2></p>";
document.getElementsByTagName('body')[0].appendChild(div2);
cp = null;
div2.addEventListener('click', shadowCallback);
sr2.getElementById('srp2').click();
testing.expectEqual(
['DIV', 'BODY', 'HTML', '#document', '[object Window]'],
cp
);
</script>

View File

@@ -1,88 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=default>
let event = new KeyboardEvent("test", { key: "a" });
testing.expectEqual(true, event instanceof KeyboardEvent);
testing.expectEqual(true, event instanceof Event);
testing.expectEqual("test", event.type);
testing.expectEqual("a", event.key);
testing.expectEqual(0, event.location);
testing.expectEqual(false, event.repeat);
testing.expectEqual(false, event.isComposing);
testing.expectEqual(false, event.ctrlKey);
testing.expectEqual(false, event.shiftKey);
testing.expectEqual(false, event.metaKey);
testing.expectEqual(false, event.altKey);
</script>
<script id=getModifierState>
event = new KeyboardEvent("test", {
altKey: true,
shiftKey: true,
metaKey: true,
ctrlKey: true,
});
testing.expectEqual(true, event.getModifierState("Alt"));
testing.expectEqual(true, event.getModifierState("AltGraph"));
testing.expectEqual(true, event.getModifierState("Control"));
testing.expectEqual(true, event.getModifierState("Shift"));
testing.expectEqual(true, event.getModifierState("Meta"));
testing.expectEqual(false, event.getModifierState("OS"));
testing.expectEqual(true, event.getModifierState("Accel"));
</script>
<script id=keyDownListener>
event = new KeyboardEvent("keydown", { key: "z" });
let isKeyDown = false;
document.addEventListener("keydown", (e) => {
isKeyDown = true;
testing.expectEqual(true, e instanceof KeyboardEvent);
testing.expectEqual(true, e instanceof Event);
testing.expectEqual("z", event.key);
});
document.dispatchEvent(event);
testing.expectEqual(true, isKeyDown);
</script>
<script id=keyUpListener>
event = new KeyboardEvent("keyup", { key: "x" });
let isKeyUp = false;
document.addEventListener("keyup", (e) => {
isKeyUp = true;
testing.expectEqual(true, e instanceof KeyboardEvent);
testing.expectEqual(true, e instanceof Event);
testing.expectEqual("x", event.key);
});
document.dispatchEvent(event);
testing.expectEqual(true, isKeyUp);
</script>
<script id=keyPressListener>
event = new KeyboardEvent("keypress", { key: "w" });
let isKeyPress = false;
document.addEventListener("keypress", (e) => {
isKeyPress = true;
testing.expectEqual(true, e instanceof KeyboardEvent);
testing.expectEqual(true, e instanceof Event);
testing.expectEqual("w", event.key);
});
document.dispatchEvent(event);
testing.expectEqual(true, isKeyPress);
</script>

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=default>
let event = new MouseEvent('click');
testing.expectEqual('click', event.type);
testing.expectEqual(true, event instanceof MouseEvent);
testing.expectEqual(true, event instanceof Event);
testing.expectEqual(0, event.clientX);
testing.expectEqual(0, event.clientY);
testing.expectEqual(0, event.screenX);
testing.expectEqual(0, event.screenY);
</script>
<script id=parameters>
let new_event = new MouseEvent('click', { 'button': 0, 'clientX': 10, 'clientY': 20 });
testing.expectEqual(0, new_event.button);
testing.expectEqual(10, new_event.x);
testing.expectEqual(20, new_event.y);
testing.expectEqual(0, new_event.screenX);
testing.expectEqual(0, new_event.screenY);
</script>
<script id=listener>
let me = new MouseEvent('click');
testing.expectEqual(true, me instanceof Event);
var evt = null;
document.addEventListener('click', function (e) {
evt = e;
});
document.dispatchEvent(me);
testing.expectEqual('click', evt.type);
testing.expectEqual(true, evt instanceof MouseEvent);
</script>

View File

@@ -1,34 +0,0 @@
<script src="../testing.js"></script>
<script id=fetch type=module>
const promise1 = new Promise((resolve) => {
fetch('http://127.0.0.1:9589/xhr/json')
.then((res) => {
testing.expectEqual('cors', res.type);
return res.json()
})
.then((json) => {
resolve(json);
});
});
testing.async(promise1, (json) => {
testing.expectEqual({over: '9000!!!'}, json);
});
</script>
<script id=same-origin type=module>
const promise1 = new Promise((resolve) => {
fetch('http://localhost:9589/xhr/json')
.then((res) => {
testing.expectEqual('basic', res.type);
return res.json()
})
.then((json) => {
resolve(json);
});
});
testing.async(promise1, (json) => {
testing.expectEqual({over: '9000!!!'}, json);
});
</script>

View File

@@ -1,103 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=headers>
let headers = new Headers({"Set-Cookie": "name=world"});
testing.expectEqual("name=world", headers.get("set-cookie"));
let myHeaders = new Headers();
myHeaders.append("Content-Type", "image/jpeg"),
testing.expectEqual(false, myHeaders.has("Picture-Type"));
testing.expectEqual("image/jpeg", myHeaders.get("Content-Type"));
myHeaders.append("Content-Type", "image/png");
testing.expectEqual("image/jpeg, image/png", myHeaders.get("Content-Type"));
myHeaders.delete("Content-Type");
testing.expectEqual(null, myHeaders.get("Content-Type"));
myHeaders.set("Picture-Type", "image/svg")
testing.expectEqual("image/svg", myHeaders.get("Picture-Type"));
testing.expectEqual(true, myHeaders.has("Picture-Type"))
const originalHeaders = new Headers([["Content-Type", "application/json"], ["Authorization", "Bearer token123"]]);
testing.expectEqual("application/json", originalHeaders.get("Content-Type"));
testing.expectEqual("Bearer token123", originalHeaders.get("Authorization"));
const newHeaders = new Headers(originalHeaders);
testing.expectEqual("application/json", newHeaders.get("Content-Type"));
testing.expectEqual("Bearer token123" ,newHeaders.get("Authorization"));
testing.expectEqual(true ,newHeaders.has("Content-Type"));
testing.expectEqual(true ,newHeaders.has("Authorization"));
testing.expectEqual(false, newHeaders.has("X-Custom"));
newHeaders.set("X-Custom", "test-value");
testing.expectEqual("test-value", newHeaders.get("X-Custom"));
testing.expectEqual(null, originalHeaders.get("X-Custom"));
testing.expectEqual(false, originalHeaders.has("X-Custom"));
</script>
<script id=keys>
const testKeyHeaders = new Headers();
testKeyHeaders.set("Content-Type", "application/json");
testKeyHeaders.set("Authorization", "Bearer token123");
testKeyHeaders.set("X-Custom", "test-value");
const keys = [];
for (const key of testKeyHeaders.keys()) {
keys.push(key);
}
testing.expectEqual(3, keys.length);
testing.expectEqual(true, keys.includes("content-type"));
testing.expectEqual(true, keys.includes("authorization"));
testing.expectEqual(true, keys.includes("x-custom"));
</script>
<script id=values>
const testValuesHeaders = new Headers();
testValuesHeaders.set("Content-Type", "application/json");
testValuesHeaders.set("Authorization", "Bearer token123");
testValuesHeaders.set("X-Custom", "test-value");
const values = [];
for (const value of testValuesHeaders.values()) {
values.push(value);
}
testing.expectEqual(3, values.length);
testing.expectEqual(true, values.includes("application/json"));
testing.expectEqual(true, values.includes("Bearer token123"));
testing.expectEqual(true, values.includes("test-value"));
</script>
<script id=entries>
const testEntriesHeaders = new Headers();
testEntriesHeaders.set("Content-Type", "application/json");
testEntriesHeaders.set("Authorization", "Bearer token123");
testEntriesHeaders.set("X-Custom", "test-value");
const entries = [];
for (const entry of testEntriesHeaders.entries()) {
entries.push(entry);
}
testing.expectEqual(3, entries.length);
const entryMap = new Map(entries);
testing.expectEqual("application/json", entryMap.get("content-type"));
testing.expectEqual("Bearer token123", entryMap.get("authorization"));
testing.expectEqual("test-value", entryMap.get("x-custom"));
const entryKeys = Array.from(entryMap.keys());
testing.expectEqual(3, entryKeys.length);
testing.expectEqual(true, entryKeys.includes("content-type"));
testing.expectEqual(true, entryKeys.includes("authorization"));
testing.expectEqual(true, entryKeys.includes("x-custom"));
const entryValues = Array.from(entryMap.values());
testing.expectEqual(3, entryValues.length);
testing.expectEqual(true, entryValues.includes("application/json"));
testing.expectEqual(true, entryValues.includes("Bearer token123"));
testing.expectEqual(true, entryValues.includes("test-value"))
</script>

View File

@@ -1,23 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=request>
let request = new Request("flower.png");
testing.expectEqual("http://localhost:9589/fetch/flower.png", request.url);
testing.expectEqual("GET", request.method);
let request2 = new Request("https://google.com", {
method: "POST",
body: "Hello, World",
cache: "reload",
credentials: "omit",
headers: { "Sender": "me", "Target": "you" }
}
);
testing.expectEqual("https://google.com", request2.url);
testing.expectEqual("POST", request2.method);
testing.expectEqual("omit", request2.credentials);
testing.expectEqual("reload", request2.cache);
testing.expectEqual("me", request2.headers.get("SeNdEr"));
testing.expectEqual("you", request2.headers.get("target"));
</script>

View File

@@ -1,51 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=response>
let response = new Response("Hello, World!");
testing.expectEqual(200, response.status);
testing.expectEqual("", response.statusText);
testing.expectEqual(true, response.ok);
testing.expectEqual("", response.url);
testing.expectEqual(false, response.redirected);
let response2 = new Response("Error occurred", {
status: 404,
statusText: "Not Found",
headers: {
"Content-Type": "text/plain",
"X-Custom": "test-value",
"Cache-Control": "no-cache"
}
});
testing.expectEqual(404, response2.status);
testing.expectEqual("Not Found", response2.statusText);
testing.expectEqual(false, response2.ok);
testing.expectEqual("text/plain", response2.headers.get("Content-Type"));
testing.expectEqual("test-value", response2.headers.get("X-Custom"));
testing.expectEqual("no-cache", response2.headers.get("cache-control"));
let response3 = new Response("Created", { status: 201, statusText: "Created" });
testing.expectEqual("basic", response3.type);
testing.expectEqual(201, response3.status);
testing.expectEqual("Created", response3.statusText);
testing.expectEqual(true, response3.ok);
let nullResponse = new Response(null);
testing.expectEqual(200, nullResponse.status);
testing.expectEqual("", nullResponse.statusText);
let emptyResponse = new Response("");
testing.expectEqual(200, emptyResponse.status);
</script>
<script id=json type=module>
const promise1 = new Promise((resolve) => {
let response = new Response('[]');
response.json().then(resolve)
});
testing.async(promise1, (json) => {
testing.expectEqual([], json);
});
</script>

View File

@@ -1,125 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=Blob/Blob.text>
{
const parts = ["\r\nthe quick brown\rfo\rx\r", "\njumps over\r\nthe\nlazy\r", "\ndog"];
// "transparent" ending should not modify the final buffer.
const blob = new Blob(parts, { type: "text/html" });
const expected = parts.join("");
testing.expectEqual(expected.length, blob.size);
testing.expectEqual("text/html", blob.type);
testing.async(blob.text(), result => testing.expectEqual(expected, result));
}
{
const parts = ["\rhello\r", "\nwor\r\nld"];
// "native" ending should modify the final buffer.
const blob = new Blob(parts, { endings: "native" });
const expected = "\nhello\n\nwor\nld";
testing.expectEqual(expected.length, blob.size);
testing.async(blob.text(), result => testing.expectEqual(expected, result));
testing.async(blob.arrayBuffer(), result => testing.expectEqual(true, result instanceof ArrayBuffer));
}
</script>
<script id=Blob.stream>
{
const parts = ["may", "thy", "knife", "chip", "and", "shatter"];
const blob = new Blob(parts);
const reader = blob.stream().getReader();
testing.async(reader.read(), ({ done, value }) => {
const expected = new Uint8Array([109, 97, 121, 116, 104, 121, 107, 110,
105, 102, 101, 99, 104, 105, 112, 97,
110, 100, 115, 104, 97, 116, 116, 101,
114]);
testing.expectEqual(false, done);
testing.expectEqual(true, value instanceof Uint8Array);
testing.expectEqual(expected, value);
});
}
</script>
<script id=Blob.arrayBuffer/Blob.slice>
{
const parts = ["la", "symphonie", "des", "éclairs"];
const blob = new Blob(parts);
testing.async(blob.arrayBuffer(), result => testing.expectEqual(true, result instanceof ArrayBuffer));
let temp = blob.slice(0);
testing.expectEqual(blob.size, temp.size);
testing.async(temp.text(), result => {
testing.expectEqual("lasymphoniedeséclairs", result);
});
temp = blob.slice(-4, -2, "custom");
testing.expectEqual(2, temp.size);
testing.expectEqual("custom", temp.type);
testing.async(temp.text(), result => testing.expectEqual("ai", result));
temp = blob.slice(14);
testing.expectEqual(8, temp.size);
testing.async(temp.text(), result => testing.expectEqual("éclairs", result));
temp = blob.slice(6, -10, "text/eclair");
testing.expectEqual(6, temp.size);
testing.expectEqual("text/eclair", temp.type);
testing.async(temp.text(), result => testing.expectEqual("honied", result));
}
</script>
<!-- Firefox and Safari only -->
<script id=Blob.bytes>
{
const parts = ["light ", "panda ", "rocks ", "!"];
const blob = new Blob(parts);
testing.async(blob.bytes(), result => {
const expected = new Uint8Array([108, 105, 103, 104, 116, 32, 112, 97,
110, 100, 97, 32, 114, 111, 99, 107, 115,
32, 33]);
testing.expectEqual(true, result instanceof Uint8Array);
testing.expectEqual(expected, result);
});
}
// Test for SIMD.
{
const parts = [
"\rThe opened package\r\nof potato\nchi\rps",
"held the\r\nanswer to the\r mystery. Both det\rectives looke\r\rd\r",
"\rat it but failed to realize\nit was\r\nthe\rkey\r\n",
"\r\nto solve the \rcrime.\r"
];
const blob = new Blob(parts, { type: "text/html", endings: "native" });
testing.expectEqual(161, blob.size);
testing.expectEqual("text/html", blob.type);
testing.async(blob.bytes(), result => {
const expected = new Uint8Array([10, 84, 104, 101, 32, 111, 112, 101, 110,
101, 100, 32, 112, 97, 99, 107, 97, 103,
101, 10, 111, 102, 32, 112, 111, 116, 97,
116, 111, 10, 99, 104, 105, 10, 112, 115,
104, 101, 108, 100, 32, 116, 104, 101, 10,
97, 110, 115, 119, 101, 114, 32, 116, 111,
32, 116, 104, 101, 10, 32, 109, 121, 115,
116, 101, 114, 121, 46, 32, 66, 111, 116,
104, 32, 100, 101, 116, 10, 101, 99, 116,
105, 118, 101, 115, 32, 108, 111, 111, 107,
101, 10, 10, 100, 10, 10, 97, 116, 32, 105,
116, 32, 98, 117, 116, 32, 102, 97, 105, 108,
101, 100, 32, 116, 111, 32, 114, 101, 97,
108, 105, 122, 101, 10, 105, 116, 32, 119, 97,
115, 10, 116, 104, 101, 10, 107, 101, 121,
10, 10, 116, 111, 32, 115, 111, 108, 118, 101,
32, 116, 104, 101, 32, 10, 99, 114, 105, 109,
101, 46, 10]);
testing.expectEqual(true, result instanceof Uint8Array);
testing.expectEqual(expected, result);
});
}
</script>

View File

@@ -1,7 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=file>
let f = new File();
testing.expectEqual(true, f instanceof File);
</script>

View File

@@ -1,41 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=abortController>
var a1 = new AbortController();
var s1 = a1.signal;
testing.expectEqual(undefined, s1.throwIfAborted());
testing.expectEqual(undefined, s1.reason);
let target;;
let called = 0;
s1.addEventListener('abort', (e) => {
called += 1;
target = e.target;
});
a1.abort();
testing.expectEqual(true, s1.aborted)
testing.expectEqual(s1, target)
testing.expectEqual('AbortError', s1.reason)
testing.expectEqual(1, called)
</script>
<script id=abort>
var s2 = AbortSignal.abort('over 9000');
testing.expectEqual(true, s2.aborted);
testing.expectEqual('over 9000', s2.reason);
testing.expectEqual('AbortError', AbortSignal.abort().reason);
</script>
<script id=timeout>
var s3 = AbortSignal.timeout(10);
testing.onload(() => {
testing.expectEqual(true, s3.aborted);
testing.expectEqual('TimeoutError', s3.reason);
testing.expectError('Error: TimeoutError', () => {
s3.throwIfAborted()
});
});
</script>

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=canvas>
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
testing.expectEqual(true, ctx instanceof CanvasRenderingContext2D);
// We can't really test this but let's try to call it at least.
ctx.fillRect(0, 0, 0, 0);
}
</script>

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=x data-power="over 9000" data-empty data-some-long-key=ok></div>
<script id=dataset>
let el1 = document.createElement('div');
testing.expectEqual(undefined, el1.dataset.x);
el1.dataset.x = '123';
testing.expectEqual(true, delete el1.dataset.x);
testing.expectEqual(undefined, el1.dataset.x);
// yes, this is right
testing.expectEqual(true, delete el1.dataset.other);
let ds1 = el1.dataset;
ds1.helloWorld = 'yes';
testing.expectEqual('yes', el1.getAttribute('data-hello-world'));
el1.setAttribute('data-this-will-work', 'positive');
testing.expectEqual('positive', ds1.thisWillWork);
</script>
<script id=element>
let div = $('#x');
testing.expectEqual(undefined, div.dataset.nope);
testing.expectEqual('over 9000', div.dataset.power);
testing.expectEqual('', div.dataset.empty);
testing.expectEqual('ok', div.dataset.someLongKey);
testing.expectEqual(true, delete div.dataset.power);
testing.expectEqual(undefined, div.dataset.power);
</script>

View File

@@ -1,70 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<div id=content><a id=link href=#></a></div>
</body>
</html>
<script src="../testing.js"></script>
<applet></applet>
<script id=document>
testing.expectEqual('HTMLDocument', document.__proto__.constructor.name);
testing.expectEqual('Document', document.__proto__.__proto__.constructor.name);
testing.expectEqual('body', document.body.localName);
testing.expectEqual('localhost', document.domain);
testing.expectEqual('', document.referrer);
testing.expectEqual('', document.title);
testing.expectEqual('body', document.body.localName);
testing.expectEqual('head', document.head.localName);
testing.expectEqual(0, document.images.length);
testing.expectEqual(0, document.embeds.length);
testing.expectEqual(0, document.plugins.length);
testing.expectEqual(2, document.scripts.length);
testing.expectEqual(0, document.forms.length);
testing.expectEqual(1, document.links.length);
testing.expectEqual(0, document.applets.length); // deprecated, always returns 0
testing.expectEqual(0, document.anchors.length);
testing.expectEqual(7, document.all.length);
testing.expectEqual('document', document.currentScript.id);
document.title = 'foo';
testing.expectEqual('foo', document.title);
document.title = '';
document.getElementById('link').setAttribute('name', 'foo');
let list = document.getElementsByName('foo');
testing.expectEqual(1, list.length);
testing.expectEqual('', document.cookie);
document.cookie = 'name=Oeschger;';
document.cookie = 'favorite_food=tripe;';
testing.expectEqual('name=Oeschger; favorite_food=tripe', document.cookie);
// "" should be returned, but the framework overrules it atm
document.cookie = 'IgnoreMy=Ghost; HttpOnly';
testing.expectEqual('name=Oeschger; favorite_food=tripe', document.cookie);
// Return null since we only return elements when they have previously been localized
testing.expectEqual(null, document.elementFromPoint(2.5, 2.5));
testing.expectEqual([], document.elementsFromPoint(2.5, 2.5));
let div1 = document.createElement('div');
document.body.appendChild(div1);
div1.getClientRects(); // clal this to position it
let a = document.createElement('a');
a.href = "https://lightpanda.io";
document.body.appendChild(a);
// Note this will be placed after the div of previous test
a.getClientRects();
testing.expectEqual(true, !document.all);
testing.expectEqual(false, !!document.all);
testing.expectEqual('[object HTMLScriptElement]', document.all(6).toString());
testing.expectEqual('[object HTMLDivElement]', document.all('content').toString());
testing.expectEqual(document, document.defaultView.document );
testing.expectEqual('loading', document.readyState);
</script>

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=content>a<strong>b</strong>cc</div>
<script id=inner>
const content = $('#content');
testing.expectEqual('a<strong>b</strong>cc', content.innerHTML);
testing.expectEqual('abcc', content.innerText);
content. innerText = 'foo';
testing.expectEqual('foo', content.innerHTML);
testing.expectEqual('foo', content.innerText);
</script>
<script id=addEventListene>
let click_count = 0;
content.addEventListener('click', function() { click_count++ });
content.click()
testing.expectEqual(1, click_count);
</script>
<script id=style>
let style = content.style;
style.cssText = 'color: red; font-size: 12px; margin: 5px !important';
testing.expectEqual(3, style.length);
style.setProperty('background-color', 'blue')
testing.expectEqual('blue', style.getPropertyValue('background-color'));
testing.expectEqual(4, style.length);
</script>
<script id=a>
let a = document.createElement('a');
testing.expectEqual('', a.href);
testing.expectEqual('', a.host);
a.href = 'about';
testing.expectEqual('http://localhost:9589/html/about', a.href);
</script>
<script id=focus>
// detached node cannot be focused
const focused = document.activeElement;
document.createElement('a').focus();
testing.expectEqual(focused, document.activeElement);
</script>
<script id=link>
let l2 = document.createElement('link');
testing.expectEqual('', l2.href);
l2.href = 'https://lightpanda.io/opensource-browser/15';
testing.expectEqual('https://lightpanda.io/opensource-browser/15', l2.href);
l2.href = '/over/9000';
testing.expectEqual('http://localhost:9589/over/9000', l2.href);
</script>

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=ErrorEvent>
let e1 = new ErrorEvent('err1')
testing.expectEqual('', e1.message);
testing.expectEqual('', e1.filename);
testing.expectEqual(0, e1.lineno);
testing.expectEqual(0, e1.colno);
testing.expectEqual(undefined, e1.error);
let e2 = new ErrorEvent('err1', {
message: 'm1',
filename: 'fx19',
lineno: 443,
colno: 8999,
error: 'under 9000!',
});
testing.expectEqual('m1', e2.message);
testing.expectEqual('fx19', e2.filename);
testing.expectEqual(443, e2.lineno);
testing.expectEqual(8999, e2.colno);
testing.expectEqual('under 9000!', e2.error);
</script>

View File

@@ -1,37 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=history>
testing.expectEqual('auto', history.scrollRestoration);
history.scrollRestoration = 'manual';
testing.expectEqual('manual', history.scrollRestoration);
history.scrollRestoration = 'auto';
testing.expectEqual('auto', history.scrollRestoration);
testing.expectEqual(null, history.state)
history.pushState({ testInProgress: true }, null, 'http://127.0.0.1:9589/html/history/history_after_nav.skip.html');
testing.expectEqual({ testInProgress: true }, history.state);
history.pushState({ testInProgress: false }, null, 'http://127.0.0.1:9589/xhr/json');
history.replaceState({ "new": "field", testComplete: true }, null);
let state = { "new": "field", testComplete: true };
testing.expectEqual(state, history.state);
let popstateEventFired = false;
let popstateEventState = null;
window.addEventListener('popstate', (event) => {
popstateEventFired = true;
popstateEventState = event.state;
});
testing.onload(() => {
testing.expectEqual(true, popstateEventFired);
testing.expectEqual(state, popstateEventState);
})
history.back();
</script>

View File

@@ -1,23 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=history2>
let state = { "new": "field", testComplete: true, testInProgress: true };
history.replaceState(state, "");
history.pushState(null, null, 'http://127.0.0.1:9589/html/history/history_after_nav.skip.html');
let popstateEventFired = false;
let popstateEventState = null;
window.onpopstate = (event) => {
popstateEventFired = true;
popstateEventState = event.state;
};
testing.onload(() => {
testing.expectEqual(true, popstateEventFired);
testing.expectEqual(state, popstateEventState);
})
history.back();
</script>

View File

@@ -1,6 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=history-after-nav>
testing.expectEqual(true, history.state && history.state.testInProgress);
</script>

View File

@@ -1,32 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=image>
img = new Image();
testing.expectEqual(0, img.width);
testing.expectEqual(0, img.height);
img = new Image(4);
testing.expectEqual(4, img.width);
testing.expectEqual(0, img.height);
img = new Image(5, 6);
testing.expectEqual(5, img.width);
testing.expectEqual(6, img.height);
let fruit = new Image
testing.expectEqual(0, fruit.width);
fruit.width = 5;
testing.expectEqual(5, fruit.width);
fruit.width = '15';
testing.expectEqual(15, fruit.width);
fruit.width = 'apple';
testing.expectEqual(0, fruit.width);
let lyric = new Image
testing.expectEqual('', lyric.src);
lyric.src = 'okay';
testing.expectEqual('http://localhost:9589/html/okay', lyric.src);
lyric.src = 15;
testing.expectEqual('http://localhost:9589/html/15', lyric.src);
</script>

View File

@@ -1,111 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<form action="test.php" target="_blank" id=form>
<p>
<label>First name: <input type="text" name="first-name" id=input /></label>
</p>
</form>
<script id=input_properties>
let input = document.createElement('input');
testing.expectEqual(null, input.form);
input.form = 'foo';
testing.expectEqual(null, input.form);
testing.expectEqual('', input.name);
input.name = 'leto';
testing.expectEqual('leto', input.name);
testing.expectEqual('', input.accept);
input.accept = 'anything';
testing.expectEqual('anything', input.accept);
testing.expectEqual('', input.alt);
input.alt = 'x1';
testing.expectEqual('x1', input.alt);
testing.expectEqual(false, input.disabled);
input.disabled = true;
testing.expectEqual(true, input.disabled);
input.disabled = false;
testing.expectEqual(false, input.disabled);
testing.expectEqual(false, input.readOnly);
input.readOnly = true;
testing.expectEqual(true, input.readOnly);
input.readOnly = false;
testing.expectEqual(false, input.readOnly);
testing.expectEqual(-1, input.maxLength);
input.maxLength = 5;
testing.expectEqual(5, input.maxLength);
input.maxLength = 'banana';
testing.expectEqual(0, input.maxLength);
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => { input.maxLength = -45;});
testing.expectEqual(20, input.size);
input.size = 5;
testing.expectEqual(5, input.size);
input.size = -449;
testing.expectEqual(20, input.size);
testing.expectError('Error: ZeroNotAllowed', () => { input.size = 0; });
testing.expectEqual('', input.src);
input.src = 'foo'
testing.expectEqual('http://localhost:9589/html/foo', input.src);
input.src = '-3'
testing.expectEqual('http://localhost:9589/html/-3', input.src);
input.src = ''
testing.expectEqual('http://localhost:9589/html/input.html', input.src);
testing.expectEqual('text', input.type);
input.type = 'checkbox';
testing.expectEqual('checkbox', input.type);
input.type = '5';
testing.expectEqual('text', input.type);
</script>
<script id=related>
let input_checked = document.createElement('input')
testing.expectEqual(false, input_checked.defaultChecked);
testing.expectEqual(false, input_checked.checked);
input_checked.defaultChecked = true;
testing.expectEqual(true, input_checked.defaultChecked);
testing.expectEqual(true, input_checked.checked);
input_checked.checked = false;
testing.expectEqual(true, input_checked.defaultChecked);
testing.expectEqual(false, input_checked.checked);
input_checked.defaultChecked = true;
testing.expectEqual(false, input_checked.checked);
</script>
<script id=defaultValue>
testing.expectEqual('', input.defaultValue);
testing.expectEqual('', input.value);
input.defaultValue = 3.1;
testing.expectEqual('3.1', input.defaultValue);
testing.expectEqual('3.1', input.value)
input.value = 'mango';
testing.expectEqual('3.1', input.defaultValue);
testing.expectEqual('mango', input.value);
input.defaultValue = true;
testing.expectEqual('mango', input.value);
</script>
<script id=form>
const form = $('#form');
input = $('#input');
testing.expectEqual(form, input.form);
// invalid
input.form = 'foo';
testing.expectEqual(form, input.form);
</script>

View File

@@ -1,60 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<a id=link href=foo>OK</a>
<script id=link>
let link = $('#link');
testing.expectEqual('', link.target);
link.target = '_blank';
testing.expectEqual('_blank', link.target);
link.target = '';
testing.expectEqual('http://localhost:9589/html/foo', link.href);
link.href = 'https://lightpanda.io/';
testing.expectEqual('https://lightpanda.io/', link.href);
testing.expectEqual('https://lightpanda.io', link.origin);
link.host = 'lightpanda.io:443';
testing.expectEqual('lightpanda.io', link.host);
testing.expectEqual('', link.port);
testing.expectEqual('lightpanda.io', link.hostname);
link.host = 'lightpanda.io';
testing.expectEqual('lightpanda.io', link.host);
testing.expectEqual('', link.port);
testing.expectEqual('lightpanda.io', link.hostname);
testing.expectEqual('lightpanda.io', link.host);
testing.expectEqual('lightpanda.io', link.hostname);
link.hostname = 'foo.bar';
testing.expectEqual('https://foo.bar/', link.href);
testing.expectEqual('', link.search);
link.search = 'q=bar';
testing.expectEqual('?q=bar', link.search);
testing.expectEqual('https://foo.bar/?q=bar', link.href);
testing.expectEqual('', link.hash);
link.hash = 'frag';
testing.expectEqual('#frag', link.hash);
testing.expectEqual('https://foo.bar/?q=bar#frag', link.href);
testing.expectEqual('', link.port);
link.port = '443';
testing.expectEqual('foo.bar', link.host);
testing.expectEqual('foo.bar', link.hostname);
testing.expectEqual('https://foo.bar/?q=bar#frag', link.href);
link.port = null;
testing.expectEqual('https://foo.bar/?q=bar#frag', link.href);
testing.expectEqual('foo', link.href = 'foo');
testing.expectEqual('', link.type);
link.type = 'text/html';
testing.expectEqual('text/html', link.type);
testing.expectEqual('OK', link.text);
link.text = 'foo';
testing.expectEqual('foo', link.text);
</script>

View File

@@ -1,33 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=location>
testing.expectEqual('http://localhost:9589/html/location.html', location.href);
testing.expectEqual('http://localhost:9589/html/location.html', document.location.href);
testing.expectEqual("localhost:9589", location.host);
testing.expectEqual("localhost", location.hostname);
testing.expectEqual("http://localhost:9589", location.origin);
testing.expectEqual("/html/location.html", location.pathname);
testing.expectEqual("", location.hash);
testing.expectEqual("9589", location.port);
testing.expectEqual("", location.search);
</script>
<script id=location_hash>
location.hash = "";
testing.expectEqual("", location.hash);
testing.expectEqual('http://localhost:9589/html/location.html', location.href);
location.hash = "#abcdef";
testing.expectEqual("#abcdef", location.hash);
testing.expectEqual('http://localhost:9589/html/location.html#abcdef', location.href);
location.hash = "xyzxyz";
testing.expectEqual("#xyzxyz", location.hash);
testing.expectEqual('http://localhost:9589/html/location.html#xyzxyz', location.href);
location.hash = "";
testing.expectEqual("", location.hash);
testing.expectEqual('http://localhost:9589/html/location.html', location.href);
</script>

View File

@@ -1,18 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=navigation>
testing.expectEqual('object', typeof navigation);
testing.expectEqual('object', typeof navigation.currentEntry);
testing.expectEqual('string', typeof navigation.currentEntry.id);
testing.expectEqual('string', typeof navigation.currentEntry.key);
testing.expectEqual('string', typeof navigation.currentEntry.url);
const currentIndex = navigation.currentEntry.index;
navigation.navigate(
'http://localhost:9589/html/navigation/navigation_after_nav.skip.html',
{ state: { currentIndex: currentIndex, navTestInProgress: true } }
);
</script>

View File

@@ -1,8 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=navigation-after-nav>
const state = navigation.currentEntry.getState();
testing.expectEqual(true, state.navTestInProgress);
testing.expectEqual(state.currentIndex + 1, navigation.currentEntry.index);
</script>

View File

@@ -1,15 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=navigation_currententrychange>
let currentEntryChanged = false;
navigation.addEventListener("currententrychange", () => {
currentEntryChanged = true;
});
// Doesn't fully navigate if same document.
location.href = location.href + "#1";
testing.expectEqual(true, currentEntryChanged);
</script>

View File

@@ -1,8 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=navigator>
testing.expectEqual('Lightpanda/1.0', navigator.userAgent);
testing.expectEqual('1.0', navigator.appVersion);
testing.expectEqual('en-US', navigator.language);
</script>

View File

@@ -1,21 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=screen>
let screen = window.screen;
testing.expectEqual(1920, screen.width);
testing.expectEqual(1080, screen.height);
let orientation = screen.orientation;
testing.expectEqual(0, orientation.angle);
testing.expectEqual('landscape-primary', orientation.type);
// this shouldn't crash (it used to)
screen.addEventListener('change', () => {});
</script>
<script id=orientation>
screen.orientation.addEventListener('change', () => {})
// above shouldn't crash (it used to)
testing.expectEqual(true, true);
</script>

View File

@@ -1,32 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=dynamic_import type=module>
const promise1 = new Promise((resolve) => {
Promise.all([
import('./import.js'),
import('./import.js'),
import('./import.js'),
import('./import.js'),
import('./import.js'),
import('./import.js'),
import('./import2.js'),
import('./import.js'),
import('./import.js'),
]).then(resolve);
});
testing.async(promise1, (res) => {
testing.expectEqual(9, res.length);
testing.expectEqual('hello', res[0].greeting);
testing.expectEqual('hello', res[1].greeting);
testing.expectEqual('hello', res[2].greeting);
testing.expectEqual('hello', res[3].greeting);
testing.expectEqual('hello', res[4].greeting);
testing.expectEqual('hello', res[5].greeting);
testing.expectEqual('world', res[6].greeting);
testing.expectEqual('hello', res[7].greeting);
testing.expectEqual('hello', res[8].greeting);
});
</script>

View File

@@ -1,15 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=import type=module>
import * as im from './import.js';
testing.expectEqual('hello', im.greeting);
</script>
<script id=cached type=module>
// hopefully cached, who knows, no real way to assert this
// but at least it works.
import * as im from './import.js';
testing.expectEqual('hello', im.greeting);
</script>

View File

@@ -1,2 +0,0 @@
let greeting = 'hello';
export {greeting as 'greeting'};

View File

@@ -1,2 +0,0 @@
let greeting = 'world';
export {greeting as 'greeting'};

View File

@@ -1,24 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script type=importmap>
{
"imports": {
"core": "./import.js"
}
}
</script>
<script id=use_importmap type=module>
import * as im from 'core';
testing.expectEqual('hello', im.greeting);
</script>
<script id=cached_importmap type=module>
// hopefully cached, who knows, no real way to assert this
// but at least it works.
import * as im from 'core';
testing.expectEqual('hello', im.greeting);
</script>

View File

@@ -1,28 +0,0 @@
<!DOCTYPE html>
<head>
<script>
let dyn1_loaded = 0;
function loadScript(src) {
const script = document.createElement('script');
script.src = src;
document.getElementsByTagName("head")[0].appendChild(script)
}
</script>
</head>
<script src="../../testing.js"></script>
<script defer>
loadScript('inline_defer.js');
</script>
<script async>
loadScript('inline_defer.js');
</script>
<script id=inline_defer>
// inline script should ignore defer and async attributes. If we don't do
// this correctly, we'd end up in an infinite loop
// https://github.com/lightpanda-io/browser/issues/1014
testing.onload(() => testing.expectEqual(2, dyn1_loaded));
</script>

View File

@@ -1 +0,0 @@
dyn1_loaded += 1;

View File

@@ -1,35 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script defer id="remote_defer" src="order_defer.js"></script>
<script defer id="remote_async" src="order_async.js"></script>
<script type=module id="inline_module">
// inline module is always deferred.
list += 'g';
testing.expectEqual('abcdefg', list);
</script>
<script>
var list = '';
</script>
<script id="remote" src="order.js"></script>
<script async id="inline_async">
// inline script ignore async
list += 'b';
testing.expectEqual('ab', list);
</script>
<script defer id="inline_defer">
// inline script ignore defer
list += 'c';
testing.expectEqual('abc', list);
</script>
<script id="default">
// simple inline script
list += 'd';
testing.expectEqual('abcd', list);
</script>

View File

@@ -1,2 +0,0 @@
list += 'a';
testing.expectEqual('a', list);

View File

@@ -1,3 +0,0 @@
list += 'f';
testing.expectEqual('abcdef', list);

View File

@@ -1,2 +0,0 @@
list += 'e';
testing.expectEqual('abcde', list);

View File

@@ -1,31 +0,0 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id=script>
let script = document.createElement('script')
script.src = 'foo.bar';
script.async = true;
testing.expectEqual(true, script.async);
script.async = false;
testing.expectEqual(false, script.async);
script.defer = true;
testing.expectEqual(true, script.defer);
testing.expectEqual('', script.nonce);
script.nonce = 'hello';
testing.expectEqual('hello', script.nonce);
</script>
<script id=datauri src="data:text/plain;charset=utf-8;base64,dGVzdGluZy5leHBlY3RFcXVhbCh0cnVlLCB0cnVlKTs="></script>
<script id=datauri_url_encoded_text src="data:text/javascript,testing.expectEqual(3, 3);"></script>
<script id=datauri_encoded_padding src="data:text/javascript;base64,dGVzdGluZy5leHBlY3RFcXVhbCgxLCAxKTs%3D"></script>
<script id=datauri_fully_encoded src="data:text/javascript;base64,%64%47%56%7a%64%47%6c%75%5a%79%35%6c%65%48%42%6c%59%33%52%46%63%58%56%68%62%43%67%79%4c%43%41%79%4b%54%73%3d"></script>
<script id=datauri_with_whitespace src="data:text/javascript;base64,%20ZD%20Qg%0D%0APS%20An%20Zm91cic%0D%0A%207%20"></script>
<script id=datauri_url_encoded_unicode src="data:text/javascript,testing.expectEqual(4%2C%204)%3B"></script>

View File

@@ -1,80 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<form id=f1>
<select id=s1 name=s1><option>o1<option>o2</select>
</form>
<select id=s2></select>
<script id=select>
const s = document.getElementById('s1');
testing.expectEqual('[object HTMLFormElement]', s.form.toString());
testing.expectEqual(null, document.getElementById('s2').form);
testing.expectEqual(false, s.disabled);
s.disabled = true;
testing.expectEqual(true, s.disabled);
s.disabled = false;
testing.expectEqual(false, s.disabled);
testing.expectEqual(false, s.multiple);
s.multiple = true;
testing.expectEqual(true, s.multiple);
s.multiple = false;
testing.expectEqual(false, s.multiple);
testing.expectEqual('s1', s.name);
s.name = 'sel1';
testing.expectEqual('sel1', s.name);
testing.expectEqual(2, s.length);
testing.expectEqual(0, s.selectedIndex);
s.selectedIndex = 2; // out of range
testing.expectEqual(-1, s.selectedIndex);
s.selectedIndex = -1;
testing.expectEqual(-1, s.selectedIndex);
s.selectedIndex = 0;
testing.expectEqual(0, s.selectedIndex);
s.selectedIndex = 1;
testing.expectEqual(1, s.selectedIndex);
s.selectedIndex = -323;
testing.expectEqual(-1, s.selectedIndex);
let options = s.options;
testing.expectEqual(2, options.length);
testing.expectEqual('o2', options.item(1).value);
testing.expectEqual(-1, options.selectedIndex);
let o3 = document.createElement('option');
o3.value = 'o3';
options.add(o3)
testing.expectEqual(3, options.length);
testing.expectEqual('o3', options[2].value);
testing.expectEqual('o3', options.item(2).value);
let o4 = document.createElement('option');
o4.value = 'o4';
options.add(o4, 1);
testing.expectEqual(4, options.length);
testing.expectEqual('o4', options.item(1).value);
let o5 = document.createElement('option');
o5.value = 'o5';
options.add(o5, o3)
testing.expectEqual(5, options.length);
testing.expectEqual('o5', options.item(3).value);
options.remove(3)
testing.expectEqual(4, options.length);
testing.expectEqual('o3', options[3].value);
testing.expectEqual('o3', options.item(3).value);
testing.expectEqual(undefined, options[10]);
testing.expectEqual(null, options.item(10));
</script>

View File

@@ -1,179 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script>
class LightPanda extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
const slot1 = document.createElement('slot');
slot1.name = 'slot-1';
shadow.appendChild(slot1);
switch (this.getAttribute('mode')) {
case '1':
slot1.innerHTML = 'hello';
break;
case '2':
const slot2 = document.createElement('slot');
shadow.appendChild(slot2);
break;
}
}
}
window.customElements.define("lp-test", LightPanda);
</script>
<lp-test id=lp1 mode=1></lp-test>
<lp-test id=lp2 mode=0></lp-test>
<lp-test id=lp3 mode=0>default</lp-test>
<lp-test id=lp4 mode=1><p slot=other>default</p></lp-test>
<lp-test id=lp5 mode=1><p slot=slot-1>default</p> xx <b slot=slot-1>other</b></lp-test>
<lp-test id=lp6 mode=2>More <p slot=slot-1>default2</p> <span>!!</span></lp-test>
<script>
function assertNodes(expected, actual) {
actual = actual.map((n) => n.id || n.textContent)
testing.expectEqual(expected, actual);
}
</script>
<script id=HTMLSlotElement>
for (let idx of [1, 2, 3, 4]) {
const lp = $(`#lp${idx}`);
const slot = lp.shadowRoot.querySelector('slot');
assertNodes([], slot.assignedNodes());
assertNodes([], slot.assignedNodes({}));
assertNodes([], slot.assignedNodes({flatten: false}));
if (lp.getAttribute('mode') === '1') {
assertNodes(['hello'], slot.assignedNodes({flatten: true}));
} else {
assertNodes([], slot.assignedNodes({flatten: true}));
}
}
{
const lp5 = $('#lp5');
const s5 = lp5.shadowRoot.querySelector('slot');
assertNodes(['default', 'other'], s5.assignedNodes());
const lp6 = $('#lp6');
const s6 = lp6.shadowRoot.querySelectorAll('slot');
assertNodes(['default2'], s6[0].assignedNodes({}));
assertNodes(['default2'], s6[0].assignedNodes({flatten: true}));
assertNodes(['More ', ' ', '!!'], s6[1].assignedNodes({}));
assertNodes(['More ', ' ', '!!'], s6[1].assignedNodes({flatten: true}));
assertNodes(['default2'], s6[0].assignedElements({}));
assertNodes(['default2'], s6[0].assignedElements({flatten: true}));
assertNodes(['!!'], s6[1].assignedElements({}));
assertNodes(['!!'], s6[1].assignedElements({flatten: true}));
}
</script>
<lp-test id=sc1 mode=1></lp-test>
<script id=slotChange1>
{
let calls = 0;
const lp = $('#sc1');
const slot = lp.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', (e) => {
assertNodes(['slotted'], slot.assignedNodes({}));
calls += 1
}, {});
const div = document.createElement('div');
div.textContent = 'Hello!';
div.id = 'slotted';
testing.expectEqual(null, div.assignedSlot);
div.setAttribute('slot', 'slot-1');
lp.appendChild(div);
testing.expectEqual(slot, div.assignedSlot);
testing.onload(() => {
testing.expectEqual(1, calls)
});
}
</script>
<lp-test id=sc2 mode=1><div id=s2 slot=slot-1>hello</div></lp-test>
<script id=slotChange2>
{
let calls = 0;
const lp = $('#sc2');
const slot = lp.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', (e) => {
assertNodes([], slot.assignedNodes({}));
calls += 1;
});
const div = $('#s2');
div.removeAttribute('slot');
testing.onload(() => {
testing.expectEqual(1, calls)
});
}
</script>
<lp-test id=sc3 mode=1><div id=s3 slot=slot-1>hello</div></lp-test>
<script id=slotChange3>
{
let calls = 0;
const lp = $('#sc3');
const slot = lp.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', (e) => {
assertNodes([], slot.assignedNodes({}));
calls += 1;
});
const div = $('#s3');
div.slot = 'other';
testing.onload(() => {
testing.expectEqual(1, calls)
});
}
</script>
<lp-test id=sc4 mode=1></lp-test>
<script id=slotChange4>
{
let calls = 0;
const lp = $('#sc4');
const slot = lp.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', (e) => {
assertNodes(['slotted'], slot.assignedNodes({}));
calls += 1;
});
const div = document.createElement('div');
div.id = 'slotted';
div.slot = 'other';
lp.appendChild(div);
div.slot = 'slot-1'
testing.onload(() => {
testing.expectEqual(1, calls)
});
}
</script>
<lp-test id=sc5 mode=1><div id=s5 slot=slot-1>hello</div></lp-test>
<script id=slotChange5>
{
let calls = 0;
const lp = $('#sc5');
const slot = lp.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', (e) => {
assertNodes([], slot.assignedNodes({}));
calls += 1;
});
$('#s5').remove();
testing.onload(() => {
testing.expectEqual(1, calls)
});
}
</script>

View File

@@ -1,6 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=style>
testing.expectEqual(null, document.createElement('style').sheet);
</script>

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<svg id=lower width="200" height="100" style="border:1px solid #ccc" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">
<rect></rect>
<text x="100" y="95" font-size="14" text-anchor="middle">OVER 9000!!</text>
</svg>
<SVG ID=UPPER WIDTH="200" HEIGHT="100" STYLE="BORDER:1PX SOLID #CCC" XMLNS="http://www.w3.org/2000/svg" VIEWBOX="0 0 200 100">
<RECT></RECT>
<TEXT X="100" Y="95" FONT-SIZE="14" TEXT-ANCHOR="MIDDLE">OVER 9000!!!</TEXT>
</SVG>
<script id=svg>
testing.expectEqual(false, 'AString' instanceof SVGElement);
const svg1 = $('#lower');
testing.expectEqual('http://www.w3.org/2000/svg', svg1.getAttribute('xmlns'));
testing.expectEqual('http://www.w3.org/2000/svg', svg1.getAttributeNode('xmlns').value);
testing.expectEqual('http://www.w3.org/2000/svg', svg1.attributes.getNamedItem('xmlns').value);
testing.expectEqual('0 0 200 100', svg1.getAttribute('viewBox'));
testing.expectEqual('viewBox', svg1.getAttributeNode('viewBox').name);
testing.expectEqual(true, svg1.outerHTML.includes('viewBox'));
testing.expectEqual('svg', svg1.tagName);
testing.expectEqual('rect', svg1.querySelector('rect').tagName);
testing.expectEqual('text', svg1.querySelector('text').tagName);
const svg2 = $('#UPPER');
testing.expectEqual('http://www.w3.org/2000/svg', svg2.getAttribute('xmlns'));
testing.expectEqual('http://www.w3.org/2000/svg', svg2.getAttributeNode('xmlns').value);
testing.expectEqual('http://www.w3.org/2000/svg', svg2.attributes.getNamedItem('xmlns').value);
testing.expectEqual('0 0 200 100', svg2.getAttribute('viewBox'));
testing.expectEqual('viewBox', svg2.getAttributeNode('viewBox').name);
testing.expectEqual(true, svg2.outerHTML.includes('viewBox'));
testing.expectEqual('svg', svg2.tagName);
testing.expectEqual('rect', svg2.querySelector('rect').tagName);
testing.expectEqual('text', svg2.querySelector('text').tagName);
</script>

View File

@@ -1,29 +0,0 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=localstorage>
testing.expectEqual(0, localStorage.length);
testing.expectEqual(null, localStorage.getItem('foo'));
testing.expectEqual(null, localStorage.key(0));
localStorage.setItem('foo', 'bar');
testing.expectEqual(1, localStorage.length)
testing.expectEqual('bar', localStorage.getItem('foo'));
testing.expectEqual('foo', localStorage.key(0));
testing.expectEqual(null, localStorage.key(1));
localStorage.removeItem('foo');
testing.expectEqual(0, localStorage.length)
testing.expectEqual(null, localStorage.getItem('foo'));
localStorage['foo'] = 'bar';
testing.expectEqual(1, localStorage.length);
testing.expectEqual('bar', localStorage['foo']);
localStorage.setItem('a', '1');
localStorage.setItem('b', '2');
localStorage.setItem('c', '3');
testing.expectEqual(4, localStorage.length)
localStorage.clear();
testing.expectEqual(0, localStorage.length)
</script>

Some files were not shown because too many files have changed in this diff Show More