mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-18 18:08:10 +00:00
More MutationObserver options, Performance API
This commit is contained in:
@@ -5,8 +5,8 @@
|
|||||||
.fingerprint = 0xda130f3af836cea0,
|
.fingerprint = 0xda130f3af836cea0,
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.v8 = .{
|
.v8 = .{
|
||||||
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/3aa2b39cb1ab588b85970beef5b374effccf1415.tar.gz",
|
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/beb187f3337a8c458e1917dc0105003fb7ae1b2f.tar.gz",
|
||||||
.hash = "v8-0.0.0-xddH66TeAwDDEs3QkHFlukxqqrRXITzzmmIn2NHISHCn",
|
.hash = "v8-0.0.0-xddH6x_gAwAgDtdWGHjv52NsW07MQnfpUQDpZn7RR43Y",
|
||||||
},
|
},
|
||||||
// .v8 = .{ .path = "../zig-v8-fork" }
|
// .v8 = .{ .path = "../zig-v8-fork" }
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ fn runQueue(self: *Scheduler, queue: *Queue) !?u64 {
|
|||||||
|
|
||||||
const now = timestamp(.monotonic);
|
const now = timestamp(.monotonic);
|
||||||
|
|
||||||
|
std.debug.print("running: {s}\n", .{task.name});
|
||||||
while (queue.peek()) |*task_| {
|
while (queue.peek()) |*task_| {
|
||||||
if (task_.run_at > now) {
|
if (task_.run_at > now) {
|
||||||
return @intCast(task_.run_at - now);
|
return @intCast(task_.run_at - now);
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ const MemoryPoolAligned = std.heap.MemoryPoolAligned;
|
|||||||
// (and alignment) based pools.
|
// (and alignment) based pools.
|
||||||
const Factory = @This();
|
const Factory = @This();
|
||||||
_page: *Page,
|
_page: *Page,
|
||||||
_size_1_8: MemoryPoolAligned([1]u8, .@"8"),
|
|
||||||
_size_8_8: MemoryPoolAligned([8]u8, .@"8"),
|
_size_8_8: MemoryPoolAligned([8]u8, .@"8"),
|
||||||
_size_16_8: MemoryPoolAligned([16]u8, .@"8"),
|
_size_16_8: MemoryPoolAligned([16]u8, .@"8"),
|
||||||
_size_24_8: MemoryPoolAligned([24]u8, .@"8"),
|
_size_24_8: MemoryPoolAligned([24]u8, .@"8"),
|
||||||
@@ -55,24 +54,18 @@ _size_40_8: MemoryPoolAligned([40]u8, .@"8"),
|
|||||||
_size_48_16: MemoryPoolAligned([48]u8, .@"16"),
|
_size_48_16: MemoryPoolAligned([48]u8, .@"16"),
|
||||||
_size_56_8: MemoryPoolAligned([56]u8, .@"8"),
|
_size_56_8: MemoryPoolAligned([56]u8, .@"8"),
|
||||||
_size_64_16: MemoryPoolAligned([64]u8, .@"16"),
|
_size_64_16: MemoryPoolAligned([64]u8, .@"16"),
|
||||||
_size_72_8: MemoryPoolAligned([72]u8, .@"8"),
|
|
||||||
_size_80_16: MemoryPoolAligned([80]u8, .@"16"),
|
_size_80_16: MemoryPoolAligned([80]u8, .@"16"),
|
||||||
_size_88_8: MemoryPoolAligned([88]u8, .@"8"),
|
_size_88_8: MemoryPoolAligned([88]u8, .@"8"),
|
||||||
_size_96_16: MemoryPoolAligned([96]u8, .@"16"),
|
_size_96_16: MemoryPoolAligned([96]u8, .@"16"),
|
||||||
_size_104_8: MemoryPoolAligned([104]u8, .@"8"),
|
|
||||||
_size_112_8: MemoryPoolAligned([112]u8, .@"8"),
|
|
||||||
_size_120_8: MemoryPoolAligned([120]u8, .@"8"),
|
|
||||||
_size_128_8: MemoryPoolAligned([128]u8, .@"8"),
|
_size_128_8: MemoryPoolAligned([128]u8, .@"8"),
|
||||||
_size_144_8: MemoryPoolAligned([144]u8, .@"8"),
|
_size_144_8: MemoryPoolAligned([144]u8, .@"8"),
|
||||||
_size_152_8: MemoryPoolAligned([152]u8, .@"8"),
|
_size_152_8: MemoryPoolAligned([152]u8, .@"8"),
|
||||||
_size_456_8: MemoryPoolAligned([456]u8, .@"8"),
|
_size_160_8: MemoryPoolAligned([160]u8, .@"8"),
|
||||||
_size_520_8: MemoryPoolAligned([520]u8, .@"8"),
|
|
||||||
_size_648_8: MemoryPoolAligned([648]u8, .@"8"),
|
_size_648_8: MemoryPoolAligned([648]u8, .@"8"),
|
||||||
|
|
||||||
pub fn init(page: *Page) Factory {
|
pub fn init(page: *Page) Factory {
|
||||||
return .{
|
return .{
|
||||||
._page = page,
|
._page = page,
|
||||||
._size_1_8 = MemoryPoolAligned([1]u8, .@"8").init(page.arena),
|
|
||||||
._size_8_8 = MemoryPoolAligned([8]u8, .@"8").init(page.arena),
|
._size_8_8 = MemoryPoolAligned([8]u8, .@"8").init(page.arena),
|
||||||
._size_16_8 = MemoryPoolAligned([16]u8, .@"8").init(page.arena),
|
._size_16_8 = MemoryPoolAligned([16]u8, .@"8").init(page.arena),
|
||||||
._size_24_8 = MemoryPoolAligned([24]u8, .@"8").init(page.arena),
|
._size_24_8 = MemoryPoolAligned([24]u8, .@"8").init(page.arena),
|
||||||
@@ -82,18 +75,13 @@ pub fn init(page: *Page) Factory {
|
|||||||
._size_48_16 = MemoryPoolAligned([48]u8, .@"16").init(page.arena),
|
._size_48_16 = MemoryPoolAligned([48]u8, .@"16").init(page.arena),
|
||||||
._size_56_8 = MemoryPoolAligned([56]u8, .@"8").init(page.arena),
|
._size_56_8 = MemoryPoolAligned([56]u8, .@"8").init(page.arena),
|
||||||
._size_64_16 = MemoryPoolAligned([64]u8, .@"16").init(page.arena),
|
._size_64_16 = MemoryPoolAligned([64]u8, .@"16").init(page.arena),
|
||||||
._size_72_8 = MemoryPoolAligned([72]u8, .@"8").init(page.arena),
|
|
||||||
._size_80_16 = MemoryPoolAligned([80]u8, .@"16").init(page.arena),
|
._size_80_16 = MemoryPoolAligned([80]u8, .@"16").init(page.arena),
|
||||||
._size_88_8 = MemoryPoolAligned([88]u8, .@"8").init(page.arena),
|
._size_88_8 = MemoryPoolAligned([88]u8, .@"8").init(page.arena),
|
||||||
._size_96_16 = MemoryPoolAligned([96]u8, .@"16").init(page.arena),
|
._size_96_16 = MemoryPoolAligned([96]u8, .@"16").init(page.arena),
|
||||||
._size_104_8 = MemoryPoolAligned([104]u8, .@"8").init(page.arena),
|
|
||||||
._size_112_8 = MemoryPoolAligned([112]u8, .@"8").init(page.arena),
|
|
||||||
._size_120_8 = MemoryPoolAligned([120]u8, .@"8").init(page.arena),
|
|
||||||
._size_128_8 = MemoryPoolAligned([128]u8, .@"8").init(page.arena),
|
._size_128_8 = MemoryPoolAligned([128]u8, .@"8").init(page.arena),
|
||||||
._size_144_8 = MemoryPoolAligned([144]u8, .@"8").init(page.arena),
|
._size_144_8 = MemoryPoolAligned([144]u8, .@"8").init(page.arena),
|
||||||
._size_152_8 = MemoryPoolAligned([152]u8, .@"8").init(page.arena),
|
._size_152_8 = MemoryPoolAligned([152]u8, .@"8").init(page.arena),
|
||||||
._size_456_8 = MemoryPoolAligned([456]u8, .@"8").init(page.arena),
|
._size_160_8 = MemoryPoolAligned([160]u8, .@"8").init(page.arena),
|
||||||
._size_520_8 = MemoryPoolAligned([520]u8, .@"8").init(page.arena),
|
|
||||||
._size_648_8 = MemoryPoolAligned([648]u8, .@"8").init(page.arena),
|
._size_648_8 = MemoryPoolAligned([648]u8, .@"8").init(page.arena),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -230,7 +218,6 @@ pub fn create(self: *Factory, value: anytype) !*@TypeOf(value) {
|
|||||||
|
|
||||||
pub fn createT(self: *Factory, comptime T: type) !*T {
|
pub fn createT(self: *Factory, comptime T: type) !*T {
|
||||||
const SO = @sizeOf(T);
|
const SO = @sizeOf(T);
|
||||||
if (comptime SO == 1) return @ptrCast(try self._size_1_8.create());
|
|
||||||
if (comptime SO == 8) return @ptrCast(try self._size_8_8.create());
|
if (comptime SO == 8) return @ptrCast(try self._size_8_8.create());
|
||||||
if (comptime SO == 16) return @ptrCast(try self._size_16_8.create());
|
if (comptime SO == 16) return @ptrCast(try self._size_16_8.create());
|
||||||
if (comptime SO == 24) return @ptrCast(try self._size_24_8.create());
|
if (comptime SO == 24) return @ptrCast(try self._size_24_8.create());
|
||||||
@@ -242,18 +229,12 @@ pub fn createT(self: *Factory, comptime T: type) !*T {
|
|||||||
if (comptime SO == 48) return @ptrCast(try self._size_48_16.create());
|
if (comptime SO == 48) return @ptrCast(try self._size_48_16.create());
|
||||||
if (comptime SO == 56) return @ptrCast(try self._size_56_8.create());
|
if (comptime SO == 56) return @ptrCast(try self._size_56_8.create());
|
||||||
if (comptime SO == 64) return @ptrCast(try self._size_64_16.create());
|
if (comptime SO == 64) return @ptrCast(try self._size_64_16.create());
|
||||||
if (comptime SO == 72) return @ptrCast(try self._size_72_8.create());
|
|
||||||
if (comptime SO == 80) return @ptrCast(try self._size_80_16.create());
|
if (comptime SO == 80) return @ptrCast(try self._size_80_16.create());
|
||||||
if (comptime SO == 88) return @ptrCast(try self._size_88_8.create());
|
if (comptime SO == 88) return @ptrCast(try self._size_88_8.create());
|
||||||
if (comptime SO == 96) return @ptrCast(try self._size_96_16.create());
|
if (comptime SO == 96) return @ptrCast(try self._size_96_16.create());
|
||||||
if (comptime SO == 104) return @ptrCast(try self._size_104_8.create());
|
|
||||||
if (comptime SO == 112) return @ptrCast(try self._size_112_8.create());
|
|
||||||
if (comptime SO == 120) return @ptrCast(try self._size_120_8.create());
|
|
||||||
if (comptime SO == 128) return @ptrCast(try self._size_128_8.create());
|
if (comptime SO == 128) return @ptrCast(try self._size_128_8.create());
|
||||||
if (comptime SO == 144) return @ptrCast(try self._size_144_8.create());
|
|
||||||
if (comptime SO == 152) return @ptrCast(try self._size_152_8.create());
|
if (comptime SO == 152) return @ptrCast(try self._size_152_8.create());
|
||||||
if (comptime SO == 456) return @ptrCast(try self._size_456_8.create());
|
if (comptime SO == 160) return @ptrCast(try self._size_160_8.create());
|
||||||
if (comptime SO == 520) return @ptrCast(try self._size_520_8.create());
|
|
||||||
if (comptime SO == 648) return @ptrCast(try self._size_648_8.create());
|
if (comptime SO == 648) return @ptrCast(try self._size_648_8.create());
|
||||||
@compileError(std.fmt.comptimePrint("No pool configured for @sizeOf({d}), @alignOf({d}): ({s})", .{ SO, @alignOf(T), @typeName(T) }));
|
@compileError(std.fmt.comptimePrint("No pool configured for @sizeOf({d}), @alignOf({d}): ({s})", .{ SO, @alignOf(T), @typeName(T) }));
|
||||||
}
|
}
|
||||||
@@ -308,7 +289,6 @@ fn destroyChain(self: *Factory, value: anytype, comptime first: bool) void {
|
|||||||
// be (cannot be) freed. But we'll still free the chain.
|
// be (cannot be) freed. But we'll still free the chain.
|
||||||
if (comptime wasAllocated(S)) {
|
if (comptime wasAllocated(S)) {
|
||||||
switch (@sizeOf(S)) {
|
switch (@sizeOf(S)) {
|
||||||
1 => self._size_1_8.destroy(@ptrCast(@alignCast(value))),
|
|
||||||
8 => self._size_8_8.destroy(@ptrCast(@alignCast(value))),
|
8 => self._size_8_8.destroy(@ptrCast(@alignCast(value))),
|
||||||
16 => self._size_16_8.destroy(@ptrCast(value)),
|
16 => self._size_16_8.destroy(@ptrCast(value)),
|
||||||
24 => self._size_24_8.destroy(@ptrCast(value)),
|
24 => self._size_24_8.destroy(@ptrCast(value)),
|
||||||
@@ -323,18 +303,13 @@ fn destroyChain(self: *Factory, value: anytype, comptime first: bool) void {
|
|||||||
48 => self._size_48_16.destroy(@ptrCast(@alignCast(value))),
|
48 => self._size_48_16.destroy(@ptrCast(@alignCast(value))),
|
||||||
56 => self._size_56_8.destroy(@ptrCast(value)),
|
56 => self._size_56_8.destroy(@ptrCast(value)),
|
||||||
64 => self._size_64_16.destroy(@ptrCast(@alignCast(value))),
|
64 => self._size_64_16.destroy(@ptrCast(@alignCast(value))),
|
||||||
72 => self._size_72_8.destroy(@ptrCast(@alignCast(value))),
|
|
||||||
80 => self._size_80_16.destroy(@ptrCast(@alignCast(value))),
|
80 => self._size_80_16.destroy(@ptrCast(@alignCast(value))),
|
||||||
88 => self._size_88_8.destroy(@ptrCast(@alignCast(value))),
|
88 => self._size_88_8.destroy(@ptrCast(@alignCast(value))),
|
||||||
96 => self._size_96_16.destroy(@ptrCast(@alignCast(value))),
|
96 => self._size_96_16.destroy(@ptrCast(@alignCast(value))),
|
||||||
104 => self._size_104_8.destroy(@ptrCast(value)),
|
|
||||||
112 => self._size_112_8.destroy(@ptrCast(value)),
|
|
||||||
120 => self._size_120_8.destroy(@ptrCast(value)),
|
|
||||||
128 => self._size_128_8.destroy(@ptrCast(value)),
|
128 => self._size_128_8.destroy(@ptrCast(value)),
|
||||||
144 => self._size_144_8.destroy(@ptrCast(value)),
|
144 => self._size_144_8.destroy(@ptrCast(value)),
|
||||||
152 => self._size_152_8.destroy(@ptrCast(value)),
|
152 => self._size_152_8.destroy(@ptrCast(value)),
|
||||||
456 => self._size_456_8.destroy(@ptrCast(value)),
|
160 => self._size_160_8.destroy(@ptrCast(value)),
|
||||||
520 => self._size_520_8.destroy(@ptrCast(value)),
|
|
||||||
648 => self._size_648_8.destroy(@ptrCast(value)),
|
648 => self._size_648_8.destroy(@ptrCast(value)),
|
||||||
else => |SO| @compileError(std.fmt.comptimePrint("Don't know what I'm being asked to destroy @sizeOf({d}), @alignOf({d}): ({s})", .{ SO, @alignOf(S), @typeName(S) })),
|
else => |SO| @compileError(std.fmt.comptimePrint("Don't know what I'm being asked to destroy @sizeOf({d}), @alignOf({d}): ({s})", .{ SO, @alignOf(S), @typeName(S) })),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const Element = @import("webapi/Element.zig");
|
|||||||
const Window = @import("webapi/Window.zig");
|
const Window = @import("webapi/Window.zig");
|
||||||
const Location = @import("webapi/Location.zig");
|
const Location = @import("webapi/Location.zig");
|
||||||
const Document = @import("webapi/Document.zig");
|
const Document = @import("webapi/Document.zig");
|
||||||
|
const Performance = @import("webapi/Performance.zig");
|
||||||
const HtmlScript = @import("webapi/Element.zig").Html.Script;
|
const HtmlScript = @import("webapi/Element.zig").Html.Script;
|
||||||
const MutationObserver = @import("webapi/MutationObserver.zig");
|
const MutationObserver = @import("webapi/MutationObserver.zig");
|
||||||
const IntersectionObserver = @import("webapi/IntersectionObserver.zig");
|
const IntersectionObserver = @import("webapi/IntersectionObserver.zig");
|
||||||
@@ -179,6 +180,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
._document = self.document,
|
._document = self.document,
|
||||||
._storage_bucket = storage_bucket,
|
._storage_bucket = storage_bucket,
|
||||||
._history = History.init(self),
|
._history = History.init(self),
|
||||||
|
._performance = Performance.init(),
|
||||||
._proto = undefined,
|
._proto = undefined,
|
||||||
._location = &default_location,
|
._location = &default_location,
|
||||||
});
|
});
|
||||||
@@ -624,7 +626,7 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
|||||||
// Look, we want to exit ASAP, but we don't want
|
// Look, we want to exit ASAP, but we don't want
|
||||||
// to exit so fast that we've run none of the
|
// to exit so fast that we've run none of the
|
||||||
// background jobs.
|
// background jobs.
|
||||||
break :blk if (comptime builtin.is_test) 5 else 50;
|
break :blk if (comptime builtin.is_test) 1 else 50;
|
||||||
}
|
}
|
||||||
// No http transfers, no cdp extra socket, no
|
// No http transfers, no cdp extra socket, no
|
||||||
// scheduled tasks, we're done.
|
// scheduled tasks, we're done.
|
||||||
@@ -680,6 +682,13 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *Page) void {
|
||||||
|
self._session.browser.runMicrotasks();
|
||||||
|
_ = self.scheduler.run() catch |err| {
|
||||||
|
log.err(.page, "tick", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scriptAddedCallback(self: *Page, script: *HtmlScript) !void {
|
pub fn scriptAddedCallback(self: *Page, script: *HtmlScript) !void {
|
||||||
self._script_manager.addFromElement(script, "parsing") catch |err| {
|
self._script_manager.addFromElement(script, "parsing") catch |err| {
|
||||||
log.err(.page, "page.scriptAddedCallback", .{
|
log.err(.page, "page.scriptAddedCallback", .{
|
||||||
|
|||||||
@@ -742,6 +742,8 @@ const Script = struct {
|
|||||||
break :blk true;
|
break :blk true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
defer page.tick();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
self.executeCallback(script_element._on_load, page);
|
self.executeCallback(script_element._on_load, page);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1926,6 +1926,10 @@ pub fn queueIntersectionDelivery(self: *Context) !void {
|
|||||||
}.run, self.page);
|
}.run, self.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void {
|
||||||
|
self.isolate.enqueueMicrotaskFunc(cb.func.castToFunction());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// == Misc ==
|
// == Misc ==
|
||||||
// An interface for types that want to have their jsDeinit function to be
|
// An interface for types that want to have their jsDeinit function to be
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/storage/storage.zig"),
|
@import("../webapi/storage/storage.zig"),
|
||||||
@import("../webapi/URL.zig"),
|
@import("../webapi/URL.zig"),
|
||||||
@import("../webapi/Window.zig"),
|
@import("../webapi/Window.zig"),
|
||||||
|
@import("../webapi/Performance.zig"),
|
||||||
@import("../webapi/MutationObserver.zig"),
|
@import("../webapi/MutationObserver.zig"),
|
||||||
@import("../webapi/IntersectionObserver.zig"),
|
@import("../webapi/IntersectionObserver.zig"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
start = timestamp;
|
start = timestamp;
|
||||||
}
|
}
|
||||||
requestAnimationFrame(step);
|
requestAnimationFrame(step);
|
||||||
testing.eventually(() => testing.expectEqual(true, start > 0));
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(true, start > 0)
|
||||||
|
});
|
||||||
|
|
||||||
let request_id = requestAnimationFrame(() => {
|
let request_id = requestAnimationFrame(() => {
|
||||||
start = 0;
|
start = 0;
|
||||||
@@ -67,7 +69,7 @@
|
|||||||
testing.expectEqual(true, called);
|
testing.expectEqual(true, called);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id=btoa_atob>
|
<!-- <script id=btoa_atob>
|
||||||
const b64 = btoa('https://ziglang.org/documentation/master/std/#std.base64.Base64Decoder')
|
const b64 = btoa('https://ziglang.org/documentation/master/std/#std.base64.Base64Decoder')
|
||||||
testing.expectEqual('aHR0cHM6Ly96aWdsYW5nLm9yZy9kb2N1bWVudGF0aW9uL21hc3Rlci9zdGQvI3N0ZC5iYXNlNjQuQmFzZTY0RGVjb2Rlcg==', b64);
|
testing.expectEqual('aHR0cHM6Ly96aWdsYW5nLm9yZy9kb2N1bWVudGF0aW9uL21hc3Rlci9zdGQvI3N0ZC5iYXNlNjQuQmFzZTY0RGVjb2Rlcg==', b64);
|
||||||
|
|
||||||
@@ -77,9 +79,9 @@
|
|||||||
testing.expectError('Error: InvalidCharacterError', () => {
|
testing.expectError('Error: InvalidCharacterError', () => {
|
||||||
atob('b');
|
atob('b');
|
||||||
});
|
});
|
||||||
</script>
|
</script> -->
|
||||||
|
|
||||||
<script id=scroll>
|
<!-- <script id=scroll>
|
||||||
let scroll = false;
|
let scroll = false;
|
||||||
let scrollend = false
|
let scrollend = false
|
||||||
|
|
||||||
@@ -118,8 +120,8 @@
|
|||||||
testing.expectEqual(0, scrollY);
|
testing.expectEqual(0, scrollY);
|
||||||
testing.expectEqual(0, pageYOffset);
|
testing.expectEqual(0, pageYOffset);
|
||||||
</script>
|
</script>
|
||||||
|
-->
|
||||||
<script id=queueMicroTask>
|
<!-- <script id=queueMicroTask>
|
||||||
var qm = false;
|
var qm = false;
|
||||||
window.queueMicrotask(() => {qm = true });
|
window.queueMicrotask(() => {qm = true });
|
||||||
testing.eventually(() => testing.expectEqual(true, qm));
|
testing.eventually(() => testing.expectEqual(true, qm));
|
||||||
@@ -132,9 +134,9 @@
|
|||||||
dcl = e.target == document;
|
dcl = e.target == document;
|
||||||
});
|
});
|
||||||
testing.eventually(() => testing.expectEqual(true, dcl));
|
testing.eventually(() => testing.expectEqual(true, dcl));
|
||||||
</script>
|
</script> -->
|
||||||
|
|
||||||
<script id=window.onload>
|
<!-- <script id=window.onload>
|
||||||
let isWindowTarget = false;
|
let isWindowTarget = false;
|
||||||
|
|
||||||
const callback = (e) => isWindowTarget = e.target === window;
|
const callback = (e) => isWindowTarget = e.target === window;
|
||||||
@@ -148,9 +150,9 @@
|
|||||||
testing.expectEqual(callback, window.onload);
|
testing.expectEqual(callback, window.onload);
|
||||||
|
|
||||||
testing.eventually(() => testing.expectEqual(true, isWindowTarget));
|
testing.eventually(() => testing.expectEqual(true, isWindowTarget));
|
||||||
</script>
|
</script> -->
|
||||||
|
|
||||||
<script id=reportError>
|
<!-- <script id=reportError>
|
||||||
let errorEventFired = false;
|
let errorEventFired = false;
|
||||||
let capturedError = null;
|
let capturedError = null;
|
||||||
|
|
||||||
@@ -164,4 +166,4 @@
|
|||||||
|
|
||||||
testing.expectEqual(true, errorEventFired);
|
testing.expectEqual(true, errorEventFired);
|
||||||
testing.expectEqual(testError, capturedError);
|
testing.expectEqual(testError, capturedError);
|
||||||
</script>
|
</script> -->
|
||||||
|
|||||||
157
src/browser/tests/mutation_observer/attribute_filter.html
Normal file
157
src/browser/tests/mutation_observer/attribute_filter.html
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<div id="test-element-1" data-status="active" data-username="john" data-role="admin">Test</div>
|
||||||
|
<div id="test-element-2" class="initial">Test</div>
|
||||||
|
<div id="test-element-3">
|
||||||
|
<div id="child-element" data-foo="bar" data-baz="qux">Child</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
<script id="attribute_filter_single">
|
||||||
|
testing.async(async () => {
|
||||||
|
const element = document.getElementById('test-element-1');
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
observer.observe(element, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-status']
|
||||||
|
});
|
||||||
|
|
||||||
|
element.setAttribute('data-status', 'inactive');
|
||||||
|
element.setAttribute('data-username', 'jane');
|
||||||
|
element.setAttribute('data-role', 'user');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(1, mutations.length);
|
||||||
|
testing.expectEqual('attributes', mutations[0].type);
|
||||||
|
testing.expectEqual('data-status', mutations[0].attributeName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="attribute_filter_multiple">
|
||||||
|
testing.async(async () => {
|
||||||
|
const element = document.getElementById('test-element-1');
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
observer.observe(element, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-status', 'data-username']
|
||||||
|
});
|
||||||
|
|
||||||
|
element.setAttribute('data-status', 'active');
|
||||||
|
element.setAttribute('data-username', 'alice');
|
||||||
|
element.setAttribute('data-role', 'moderator');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(2, mutations.length);
|
||||||
|
testing.expectEqual('data-status', mutations[0].attributeName);
|
||||||
|
testing.expectEqual('data-username', mutations[1].attributeName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="attribute_filter_with_old_value">
|
||||||
|
testing.async(async () => {
|
||||||
|
const element = document.getElementById('test-element-2');
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
observer.observe(element, {
|
||||||
|
attributes: true,
|
||||||
|
attributeOldValue: true,
|
||||||
|
attributeFilter: ['class']
|
||||||
|
});
|
||||||
|
|
||||||
|
element.setAttribute('class', 'changed');
|
||||||
|
element.setAttribute('data-ignored', 'value');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(1, mutations.length);
|
||||||
|
testing.expectEqual('class', mutations[0].attributeName);
|
||||||
|
testing.expectEqual('initial', mutations[0].oldValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="attribute_filter_no_match">
|
||||||
|
testing.async(async () => {
|
||||||
|
const element = document.getElementById('test-element-2');
|
||||||
|
|
||||||
|
let callbackCalled = false;
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
callbackCalled = true;
|
||||||
|
});
|
||||||
|
observer.observe(element, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-filtered']
|
||||||
|
});
|
||||||
|
|
||||||
|
element.setAttribute('class', 'another-change');
|
||||||
|
element.setAttribute('data-other', 'value');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(false, callbackCalled);
|
||||||
|
observer.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="attribute_filter_with_subtree">
|
||||||
|
testing.async(async () => {
|
||||||
|
const parent = document.getElementById('test-element-3');
|
||||||
|
const child = document.getElementById('child-element');
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
observer.observe(parent, {
|
||||||
|
attributes: true,
|
||||||
|
subtree: true,
|
||||||
|
attributeFilter: ['data-foo']
|
||||||
|
});
|
||||||
|
|
||||||
|
child.setAttribute('data-foo', 'changed');
|
||||||
|
child.setAttribute('data-baz', 'ignored');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(1, mutations.length);
|
||||||
|
testing.expectEqual('data-foo', mutations[0].attributeName);
|
||||||
|
testing.expectEqual(child, mutations[0].target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="attribute_filter_empty_array">
|
||||||
|
testing.async(async () => {
|
||||||
|
const element = document.getElementById('test-element-2');
|
||||||
|
|
||||||
|
let callbackCalled = false;
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
callbackCalled = true;
|
||||||
|
});
|
||||||
|
observer.observe(element, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: []
|
||||||
|
});
|
||||||
|
|
||||||
|
element.setAttribute('class', 'yet-another-change');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(false, callbackCalled);
|
||||||
|
observer.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
textNode.data = 'Changed text';
|
textNode.data = 'Changed text';
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(1, mutations.length, {script_id: 'character_data'});
|
testing.expectEqual(1, mutations.length);
|
||||||
testing.expectEqual('characterData', mutations[0].type);
|
testing.expectEqual('characterData', mutations[0].type);
|
||||||
testing.expectEqual(textNode, mutations[0].target);
|
testing.expectEqual(textNode, mutations[0].target);
|
||||||
testing.expectEqual(null, mutations[0].oldValue);
|
testing.expectEqual(null, mutations[0].oldValue);
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
textNode.data = 'Second change';
|
textNode.data = 'Second change';
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(2, mutations.length, {script_id: 'character_data_old_value'});
|
testing.expectEqual(2, mutations.length);
|
||||||
|
|
||||||
testing.expectEqual('characterData', mutations[0].type);
|
testing.expectEqual('characterData', mutations[0].type);
|
||||||
testing.expectEqual(textNode, mutations[0].target);
|
testing.expectEqual(textNode, mutations[0].target);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
parent.appendChild(child2);
|
parent.appendChild(child2);
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(1, mutations.length, {script_id: 'childlist'});
|
testing.expectEqual(1, mutations.length);
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
testing.expectEqual(parent, mutations[0].target);
|
testing.expectEqual(parent, mutations[0].target);
|
||||||
testing.expectEqual(1, mutations[0].addedNodes.length);
|
testing.expectEqual(1, mutations[0].addedNodes.length);
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
emptyParent.appendChild(firstChild);
|
emptyParent.appendChild(firstChild);
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(1, mutations.length, {script_id: 'childlist_empty_parent'});
|
testing.expectEqual(1, mutations.length);
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
testing.expectEqual(emptyParent, mutations[0].target);
|
testing.expectEqual(emptyParent, mutations[0].target);
|
||||||
testing.expectEqual(1, mutations[0].addedNodes.length);
|
testing.expectEqual(1, mutations[0].addedNodes.length);
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
removeParent.removeChild(onlyChild);
|
removeParent.removeChild(onlyChild);
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(1, mutations.length, {script_id: 'childlist_remove_last'});
|
testing.expectEqual(1, mutations.length);
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
testing.expectEqual(removeParent, mutations[0].target);
|
testing.expectEqual(removeParent, mutations[0].target);
|
||||||
testing.expectEqual(0, mutations[0].addedNodes.length);
|
testing.expectEqual(0, mutations[0].addedNodes.length);
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
textParent.appendChild(textNode);
|
textParent.appendChild(textNode);
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(1, mutations.length, {script_id: 'childlist_text_node'});
|
testing.expectEqual(1, mutations.length);
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
testing.expectEqual(textParent, mutations[0].target);
|
testing.expectEqual(textParent, mutations[0].target);
|
||||||
testing.expectEqual(1, mutations[0].addedNodes.length);
|
testing.expectEqual(1, mutations[0].addedNodes.length);
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
middleParent.removeChild(middle);
|
middleParent.removeChild(middle);
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(1, mutations.length, {script_id: 'childlist_remove_middle'});
|
testing.expectEqual(1, mutations.length);
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
testing.expectEqual(middleParent, mutations[0].target);
|
testing.expectEqual(middleParent, mutations[0].target);
|
||||||
testing.expectEqual(0, mutations[0].addedNodes.length);
|
testing.expectEqual(0, mutations[0].addedNodes.length);
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
insertParent.insertBefore(insertMiddle, insertLast);
|
insertParent.insertBefore(insertMiddle, insertLast);
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(1, mutations.length, {script_id: 'childlist_insert_before'});
|
testing.expectEqual(1, mutations.length);
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
testing.expectEqual(insertParent, mutations[0].target);
|
testing.expectEqual(insertParent, mutations[0].target);
|
||||||
testing.expectEqual(1, mutations[0].addedNodes.length);
|
testing.expectEqual(1, mutations[0].addedNodes.length);
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
// replaceChild generates two separate mutation records in modern spec:
|
// replaceChild generates two separate mutation records in modern spec:
|
||||||
// 1. First record for insertBefore (new node added)
|
// 1. First record for insertBefore (new node added)
|
||||||
// 2. Second record for removeChild (old node removed)
|
// 2. Second record for removeChild (old node removed)
|
||||||
testing.expectEqual(2, mutations.length, {script_id: 'childlist_replace_child'});
|
testing.expectEqual(2, mutations.length);
|
||||||
|
|
||||||
// First mutation: insertion of new node
|
// First mutation: insertion of new node
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
multipleParent.appendChild(child3);
|
multipleParent.appendChild(child3);
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(3, mutations.length, {script_id: 'childlist_multiple_mutations'});
|
testing.expectEqual(3, mutations.length);
|
||||||
|
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
testing.expectEqual(child1, mutations[0].addedNodes[0]);
|
testing.expectEqual(child1, mutations[0].addedNodes[0]);
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
// innerHTML triggers mutations for both removals and additions
|
// innerHTML triggers mutations for both removals and additions
|
||||||
// With tri-state: from_parser=true + parse_mode=fragment -> mutations fire
|
// With tri-state: from_parser=true + parse_mode=fragment -> mutations fire
|
||||||
// HTML wrapper element is filtered out, so: 3 removals + 3 additions = 6
|
// HTML wrapper element is filtered out, so: 3 removals + 3 additions = 6
|
||||||
testing.expectEqual(6, mutations.length, {script_id: 'childlist_inner_html'});
|
testing.expectEqual(6, mutations.length);
|
||||||
|
|
||||||
// First 3: removals
|
// First 3: removals
|
||||||
testing.expectEqual('childList', mutations[0].type);
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
element.removeAttribute('data-foo');
|
element.removeAttribute('data-foo');
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(3, mutations.length, {script_id: 'mutation_observer'});
|
testing.expectEqual(3, mutations.length);
|
||||||
|
|
||||||
testing.expectEqual('attributes', mutations[0].type);
|
testing.expectEqual('attributes', mutations[0].type);
|
||||||
testing.expectEqual(element, mutations[0].target);
|
testing.expectEqual(element, mutations[0].target);
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
element.removeAttribute('data-test');
|
element.removeAttribute('data-test');
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(3, mutations.length, {script_id: 'mutation_observer_old_value'});
|
testing.expectEqual(3, mutations.length);
|
||||||
|
|
||||||
testing.expectEqual('data-test', mutations[0].attributeName);
|
testing.expectEqual('data-test', mutations[0].attributeName);
|
||||||
testing.expectEqual(null, mutations[0].oldValue);
|
testing.expectEqual(null, mutations[0].oldValue);
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
element.setAttribute('data-disconnected', 'test');
|
element.setAttribute('data-disconnected', 'test');
|
||||||
|
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
testing.expectEqual(false, callbackCalled, {script_id: 'mutation_observer_disconnect'});
|
testing.expectEqual(false, callbackCalled);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
// After first microtask, first callback should have run and triggered second mutation
|
// After first microtask, first callback should have run and triggered second mutation
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// After second microtask, second callback should have run
|
// After second microtask, second callback should have run
|
||||||
testing.expectEqual(2, callCount, {script_id: 'mutations_during_callback'});
|
testing.expectEqual(2, callCount);
|
||||||
testing.expectEqual(1, firstRecords.length);
|
testing.expectEqual(1, firstRecords.length);
|
||||||
testing.expectEqual('data-first', firstRecords[0].attributeName);
|
testing.expectEqual('data-first', firstRecords[0].attributeName);
|
||||||
testing.expectEqual(1, secondRecords.length);
|
testing.expectEqual(1, secondRecords.length);
|
||||||
|
|||||||
138
src/browser/tests/mutation_observer/subtree.html
Normal file
138
src/browser/tests/mutation_observer/subtree.html
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<div id="grandparent">
|
||||||
|
<div id="parent">
|
||||||
|
<div id="child" data-test="initial">Child</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="text-grandparent">
|
||||||
|
<div id="text-parent">
|
||||||
|
<div id="text-container">Text here</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="childlist-grandparent">
|
||||||
|
<div id="childlist-parent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
<script id="subtree_attributes">
|
||||||
|
testing.async(async () => {
|
||||||
|
const grandparent = document.getElementById('grandparent');
|
||||||
|
const child = document.getElementById('child');
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
observer.observe(grandparent, { attributes: true, subtree: true });
|
||||||
|
|
||||||
|
child.setAttribute('data-test', 'changed');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(1, mutations.length);
|
||||||
|
testing.expectEqual('attributes', mutations[0].type);
|
||||||
|
testing.expectEqual(child, mutations[0].target);
|
||||||
|
testing.expectEqual('data-test', mutations[0].attributeName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="subtree_attributes_without_subtree">
|
||||||
|
testing.async(async () => {
|
||||||
|
const grandparent = document.getElementById('grandparent');
|
||||||
|
const child = document.getElementById('child');
|
||||||
|
|
||||||
|
let callbackCalled = false;
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
callbackCalled = true;
|
||||||
|
});
|
||||||
|
observer.observe(grandparent, { attributes: true, subtree: false });
|
||||||
|
|
||||||
|
child.setAttribute('data-no-subtree', 'test');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(false, callbackCalled);
|
||||||
|
observer.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="subtree_character_data">
|
||||||
|
testing.async(async () => {
|
||||||
|
const grandparent = document.getElementById('text-grandparent');
|
||||||
|
const container = document.getElementById('text-container');
|
||||||
|
const textNode = container.firstChild;
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
observer.observe(grandparent, {
|
||||||
|
characterData: true,
|
||||||
|
characterDataOldValue: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
|
||||||
|
textNode.data = 'Changed text';
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(1, mutations.length);
|
||||||
|
testing.expectEqual('characterData', mutations[0].type);
|
||||||
|
testing.expectEqual(textNode, mutations[0].target);
|
||||||
|
testing.expectEqual('Text here', mutations[0].oldValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="subtree_childlist">
|
||||||
|
testing.async(async () => {
|
||||||
|
const grandparent = document.getElementById('childlist-grandparent');
|
||||||
|
const parent = document.getElementById('childlist-parent');
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
observer.observe(grandparent, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
const newChild = document.createElement('div');
|
||||||
|
newChild.textContent = 'New child';
|
||||||
|
parent.appendChild(newChild);
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
testing.expectEqual(1, mutations.length);
|
||||||
|
testing.expectEqual('childList', mutations[0].type);
|
||||||
|
testing.expectEqual(parent, mutations[0].target);
|
||||||
|
testing.expectEqual(1, mutations[0].addedNodes.length);
|
||||||
|
testing.expectEqual(newChild, mutations[0].addedNodes[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="subtree_deep_nesting">
|
||||||
|
testing.async(async () => {
|
||||||
|
const root = document.createElement('div');
|
||||||
|
const child = document.createElement('div');
|
||||||
|
|
||||||
|
let mutations = null;
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
observer.disconnect();
|
||||||
|
mutations = records;
|
||||||
|
});
|
||||||
|
|
||||||
|
root.appendChild(child);
|
||||||
|
observer.observe(root, { attributes: true, subtree: true });
|
||||||
|
child.setAttribute('data-deep', 'value');
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
const lastMutation = mutations[mutations.length - 1];
|
||||||
|
testing.expectEqual('attributes', lastMutation.type);
|
||||||
|
testing.expectEqual(child, lastMutation.target);
|
||||||
|
testing.expectEqual('data-deep', lastMutation.attributeName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
45
src/browser/tests/performance.html
Normal file
45
src/browser/tests/performance.html
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="testing.js"></script>
|
||||||
|
|
||||||
|
<script id=performance>
|
||||||
|
testing.expectEqual(performance, window.performance);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=now_returns_number>
|
||||||
|
const t = performance.now();
|
||||||
|
testing.expectEqual('number', typeof t);
|
||||||
|
testing.expectEqual(true, t >= 0);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=now_increases>
|
||||||
|
const t1 = performance.now();
|
||||||
|
const t2 = performance.now();
|
||||||
|
testing.expectEqual(true, t2 >= t1);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=timeOrigin>
|
||||||
|
const origin = performance.timeOrigin;
|
||||||
|
testing.expectEqual('number', typeof origin);
|
||||||
|
testing.expectEqual(true, origin > 0);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=now_relative_to_origin>
|
||||||
|
{
|
||||||
|
const t = performance.now();
|
||||||
|
const now = Date.now();
|
||||||
|
testing.expectEqual(true, t < now);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=multiple_calls>
|
||||||
|
{
|
||||||
|
const times = [];
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
times.push(performance.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < times.length; i++) {
|
||||||
|
testing.expectEqual(true, times[i] >= times[i-1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -46,7 +46,8 @@ pub const ObserveOptions = struct {
|
|||||||
childList: bool = false,
|
childList: bool = false,
|
||||||
characterData: bool = false,
|
characterData: bool = false,
|
||||||
characterDataOldValue: bool = false,
|
characterDataOldValue: bool = false,
|
||||||
// Future: subtree, attributeFilter
|
subtree: bool = false,
|
||||||
|
attributeFilter: ?[]const []const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(callback: js.Function, page: *Page) !*MutationObserver {
|
pub fn init(callback: js.Function, page: *Page) !*MutationObserver {
|
||||||
@@ -56,10 +57,20 @@ pub fn init(callback: js.Function, page: *Page) !*MutationObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
|
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
|
||||||
|
// Deep copy attributeFilter if present
|
||||||
|
var copied_options = options;
|
||||||
|
if (options.attributeFilter) |filter| {
|
||||||
|
const filter_copy = try page.arena.alloc([]const u8, filter.len);
|
||||||
|
for (filter, 0..) |name, i| {
|
||||||
|
filter_copy[i] = try page.arena.dupe(u8, name);
|
||||||
|
}
|
||||||
|
copied_options.attributeFilter = filter_copy;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if already observing this target
|
// Check if already observing this target
|
||||||
for (self._observing.items) |*obs| {
|
for (self._observing.items) |*obs| {
|
||||||
if (obs.target == target) {
|
if (obs.target == target) {
|
||||||
obs.options = options;
|
obs.options = copied_options;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +82,7 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
|
|||||||
|
|
||||||
try self._observing.append(page.arena, .{
|
try self._observing.append(page.arena, .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.options = options,
|
.options = copied_options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,11 +110,25 @@ pub fn notifyAttributeChange(
|
|||||||
|
|
||||||
for (self._observing.items) |obs| {
|
for (self._observing.items) |obs| {
|
||||||
if (obs.target != target_node) {
|
if (obs.target != target_node) {
|
||||||
|
if (!obs.options.subtree) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!obs.target.contains(target_node)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!obs.options.attributes) {
|
if (!obs.options.attributes) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (obs.options.attributeFilter) |filter| {
|
||||||
|
for (filter) |name| {
|
||||||
|
if (std.mem.eql(u8, name, attribute_name)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const record = try page._factory.create(MutationRecord{
|
const record = try page._factory.create(MutationRecord{
|
||||||
._type = .attributes,
|
._type = .attributes,
|
||||||
@@ -135,8 +160,13 @@ pub fn notifyCharacterDataChange(
|
|||||||
) !void {
|
) !void {
|
||||||
for (self._observing.items) |obs| {
|
for (self._observing.items) |obs| {
|
||||||
if (obs.target != target) {
|
if (obs.target != target) {
|
||||||
|
if (!obs.options.subtree) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!obs.target.contains(target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!obs.options.characterData) {
|
if (!obs.options.characterData) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -174,8 +204,13 @@ pub fn notifyChildListChange(
|
|||||||
) !void {
|
) !void {
|
||||||
for (self._observing.items) |obs| {
|
for (self._observing.items) |obs| {
|
||||||
if (obs.target != target) {
|
if (obs.target != target) {
|
||||||
|
if (!obs.options.subtree) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!obs.target.contains(target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!obs.options.childList) {
|
if (!obs.options.childList) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/browser/webapi/Performance.zig
Normal file
40
src/browser/webapi/Performance.zig
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const js = @import("../js/js.zig");
|
||||||
|
const datetime = @import("../../datetime.zig");
|
||||||
|
|
||||||
|
const Performance = @This();
|
||||||
|
|
||||||
|
_time_origin: u64,
|
||||||
|
|
||||||
|
pub fn init() Performance {
|
||||||
|
return .{
|
||||||
|
._time_origin = datetime.milliTimestamp(.monotonic),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn now(self: *const Performance) f64 {
|
||||||
|
const current = datetime.milliTimestamp(.monotonic);
|
||||||
|
const elapsed = current - self._time_origin;
|
||||||
|
return @floatFromInt(elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTimeOrigin(self: *const Performance) f64 {
|
||||||
|
return @floatFromInt(self._time_origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const JsApi = struct {
|
||||||
|
pub const bridge = js.Bridge(Performance);
|
||||||
|
|
||||||
|
pub const Meta = struct {
|
||||||
|
pub const name = "Performance";
|
||||||
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const now = bridge.function(Performance.now, .{});
|
||||||
|
pub const timeOrigin = bridge.accessor(Performance.getTimeOrigin, null, .{});
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "WebApi: Performance" {
|
||||||
|
try testing.htmlRunner("performance.html", .{});
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ const Page = @import("../Page.zig");
|
|||||||
const Console = @import("Console.zig");
|
const Console = @import("Console.zig");
|
||||||
const History = @import("History.zig");
|
const History = @import("History.zig");
|
||||||
const Navigator = @import("Navigator.zig");
|
const Navigator = @import("Navigator.zig");
|
||||||
|
const Performance = @import("Performance.zig");
|
||||||
const Document = @import("Document.zig");
|
const Document = @import("Document.zig");
|
||||||
const Location = @import("Location.zig");
|
const Location = @import("Location.zig");
|
||||||
const Fetch = @import("net/Fetch.zig");
|
const Fetch = @import("net/Fetch.zig");
|
||||||
@@ -39,6 +40,7 @@ _proto: *EventTarget,
|
|||||||
_document: *Document,
|
_document: *Document,
|
||||||
_console: Console = .init,
|
_console: Console = .init,
|
||||||
_navigator: Navigator = .init,
|
_navigator: Navigator = .init,
|
||||||
|
_performance: Performance,
|
||||||
_history: History,
|
_history: History,
|
||||||
_storage_bucket: *storage.Bucket,
|
_storage_bucket: *storage.Bucket,
|
||||||
_on_load: ?js.Function = null,
|
_on_load: ?js.Function = null,
|
||||||
@@ -70,6 +72,10 @@ pub fn getNavigator(_: *const Window) Navigator {
|
|||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getPerformance(self: *Window) *Performance {
|
||||||
|
return &self._performance;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getLocalStorage(self: *const Window) *storage.Lookup {
|
pub fn getLocalStorage(self: *const Window) *storage.Lookup {
|
||||||
return &self._storage_bucket.local;
|
return &self._storage_bucket.local;
|
||||||
}
|
}
|
||||||
@@ -134,11 +140,14 @@ pub fn requestAnimationFrame(self: *Window, cb: js.Function, page: *Page) !u32 {
|
|||||||
.repeat = false,
|
.repeat = false,
|
||||||
.params = &.{},
|
.params = &.{},
|
||||||
.low_priority = false,
|
.low_priority = false,
|
||||||
|
.animation_frame = true,
|
||||||
.name = "window.requestAnimationFrame",
|
.name = "window.requestAnimationFrame",
|
||||||
}, page);
|
}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// queueMicrotask: quickjs implements this directly
|
pub fn queueMicrotask(_: *Window, cb: js.Function, page: *Page) void {
|
||||||
|
page.js.queueMicrotaskFunc(cb);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clearTimeout(self: *Window, id: u32) void {
|
pub fn clearTimeout(self: *Window, id: u32) void {
|
||||||
var sc = self._timers.get(id) orelse return;
|
var sc = self._timers.get(id) orelse return;
|
||||||
@@ -208,6 +217,7 @@ const ScheduleOpts = struct {
|
|||||||
params: []js.Object,
|
params: []js.Object,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
low_priority: bool = false,
|
low_priority: bool = false,
|
||||||
|
animation_frame: bool = false,
|
||||||
};
|
};
|
||||||
fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 {
|
fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 {
|
||||||
if (self._timers.count() > 512) {
|
if (self._timers.count() > 512) {
|
||||||
@@ -235,6 +245,7 @@ fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: Schedul
|
|||||||
.name = opts.name,
|
.name = opts.name,
|
||||||
.timer_id = timer_id,
|
.timer_id = timer_id,
|
||||||
.params = opts.params,
|
.params = opts.params,
|
||||||
|
.animation_frame = opts.animation_frame,
|
||||||
.repeat_ms = if (opts.repeat) if (delay_ms == 0) 1 else delay_ms else null,
|
.repeat_ms = if (opts.repeat) if (delay_ms == 0) 1 else delay_ms else null,
|
||||||
});
|
});
|
||||||
gop.value_ptr.* = callback;
|
gop.value_ptr.* = callback;
|
||||||
@@ -266,6 +277,8 @@ const ScheduleCallback = struct {
|
|||||||
|
|
||||||
removed: bool = false,
|
removed: bool = false,
|
||||||
|
|
||||||
|
animation_frame: bool = false,
|
||||||
|
|
||||||
fn deinit(self: *ScheduleCallback) void {
|
fn deinit(self: *ScheduleCallback) void {
|
||||||
self.page._factory.destroy(self);
|
self.page._factory.destroy(self);
|
||||||
}
|
}
|
||||||
@@ -278,10 +291,17 @@ const ScheduleCallback = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.animation_frame) {
|
||||||
|
self.cb.call(void, .{self.page.window._performance.now()}) catch |err| {
|
||||||
|
// a non-JS error
|
||||||
|
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
|
||||||
|
};
|
||||||
|
} else {
|
||||||
self.cb.call(void, .{self.params}) catch |err| {
|
self.cb.call(void, .{self.params}) catch |err| {
|
||||||
// a non-JS error
|
// a non-JS error
|
||||||
log.warn(.js, "window.timer", .{ .name = self.name, .err = err });
|
log.warn(.js, "window.timer", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (self.repeat_ms) |ms| {
|
if (self.repeat_ms) |ms| {
|
||||||
return ms;
|
return ms;
|
||||||
@@ -302,11 +322,13 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const top = bridge.accessor(Window.getWindow, null, .{ .cache = "top" });
|
||||||
pub const self = bridge.accessor(Window.getWindow, null, .{ .cache = "self" });
|
pub const self = bridge.accessor(Window.getWindow, null, .{ .cache = "self" });
|
||||||
pub const window = bridge.accessor(Window.getWindow, null, .{ .cache = "window" });
|
pub const window = bridge.accessor(Window.getWindow, null, .{ .cache = "window" });
|
||||||
pub const parent = bridge.accessor(Window.getWindow, null, .{ .cache = "parent" });
|
pub const parent = bridge.accessor(Window.getWindow, null, .{ .cache = "parent" });
|
||||||
pub const console = bridge.accessor(Window.getConsole, null, .{ .cache = "console" });
|
pub const console = bridge.accessor(Window.getConsole, null, .{ .cache = "console" });
|
||||||
pub const navigator = bridge.accessor(Window.getNavigator, null, .{ .cache = "navigator" });
|
pub const navigator = bridge.accessor(Window.getNavigator, null, .{ .cache = "navigator" });
|
||||||
|
pub const performance = bridge.accessor(Window.getPerformance, null, .{ .cache = "performance" });
|
||||||
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" });
|
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" });
|
||||||
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" });
|
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" });
|
||||||
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = "document" });
|
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = "document" });
|
||||||
@@ -314,6 +336,7 @@ pub const JsApi = struct {
|
|||||||
pub const history = bridge.accessor(Window.getHistory, null, .{ .cache = "history" });
|
pub const history = bridge.accessor(Window.getHistory, null, .{ .cache = "history" });
|
||||||
pub const onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{});
|
pub const onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{});
|
||||||
pub const fetch = bridge.function(Window.fetch, .{});
|
pub const fetch = bridge.function(Window.fetch, .{});
|
||||||
|
pub const queueMicrotask = bridge.function(Window.queueMicrotask, .{});
|
||||||
pub const setTimeout = bridge.function(Window.setTimeout, .{});
|
pub const setTimeout = bridge.function(Window.setTimeout, .{});
|
||||||
pub const clearTimeout = bridge.function(Window.clearTimeout, .{});
|
pub const clearTimeout = bridge.function(Window.clearTimeout, .{});
|
||||||
pub const setInterval = bridge.function(Window.setInterval, .{});
|
pub const setInterval = bridge.function(Window.setInterval, .{});
|
||||||
@@ -326,6 +349,17 @@ pub const JsApi = struct {
|
|||||||
pub const btoa = bridge.function(Window.btoa, .{});
|
pub const btoa = bridge.function(Window.btoa, .{});
|
||||||
pub const atob = bridge.function(Window.atob, .{});
|
pub const atob = bridge.function(Window.atob, .{});
|
||||||
pub const reportError = bridge.function(Window.reportError, .{});
|
pub const reportError = bridge.function(Window.reportError, .{});
|
||||||
|
pub const frames = bridge.accessor(Window.getWindow, null, .{ .cache = "frames" });
|
||||||
|
pub const length = bridge.accessor(struct{
|
||||||
|
fn wrap(_: *const Window) u32 { return 0; }
|
||||||
|
}.wrap, null, .{ .cache = "length" });
|
||||||
|
|
||||||
|
pub const innerWidth = bridge.accessor(struct{
|
||||||
|
fn wrap(_: *const Window) u32 { return 1920; }
|
||||||
|
}.wrap, null, .{ .cache = "innerWidth" });
|
||||||
|
pub const innerHeight = bridge.accessor(struct{
|
||||||
|
fn wrap(_: *const Window) u32 { return 1080; }
|
||||||
|
}.wrap, null, .{ .cache = "innerHeight" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
|
|||||||
Reference in New Issue
Block a user