diff --git a/build.zig.zon b/build.zig.zon index 036d9ba5..682f823c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .fingerprint = 0xda130f3af836cea0, .dependencies = .{ .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/3aa2b39cb1ab588b85970beef5b374effccf1415.tar.gz", - .hash = "v8-0.0.0-xddH66TeAwDDEs3QkHFlukxqqrRXITzzmmIn2NHISHCn", + .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/beb187f3337a8c458e1917dc0105003fb7ae1b2f.tar.gz", + .hash = "v8-0.0.0-xddH6x_gAwAgDtdWGHjv52NsW07MQnfpUQDpZn7RR43Y", }, // .v8 = .{ .path = "../zig-v8-fork" } }, diff --git a/src/Scheduler.zig b/src/Scheduler.zig index 6ba8b6e1..98efb64f 100644 --- a/src/Scheduler.zig +++ b/src/Scheduler.zig @@ -74,6 +74,7 @@ fn runQueue(self: *Scheduler, queue: *Queue) !?u64 { const now = timestamp(.monotonic); + std.debug.print("running: {s}\n", .{task.name}); while (queue.peek()) |*task_| { if (task_.run_at > now) { return @intCast(task_.run_at - now); diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig index 56d3eb98..f6bbc5d9 100644 --- a/src/browser/Factory.zig +++ b/src/browser/Factory.zig @@ -45,7 +45,6 @@ const MemoryPoolAligned = std.heap.MemoryPoolAligned; // (and alignment) based pools. const Factory = @This(); _page: *Page, -_size_1_8: MemoryPoolAligned([1]u8, .@"8"), _size_8_8: MemoryPoolAligned([8]u8, .@"8"), _size_16_8: MemoryPoolAligned([16]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_56_8: MemoryPoolAligned([56]u8, .@"8"), _size_64_16: MemoryPoolAligned([64]u8, .@"16"), -_size_72_8: MemoryPoolAligned([72]u8, .@"8"), _size_80_16: MemoryPoolAligned([80]u8, .@"16"), _size_88_8: MemoryPoolAligned([88]u8, .@"8"), _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_144_8: MemoryPoolAligned([144]u8, .@"8"), _size_152_8: MemoryPoolAligned([152]u8, .@"8"), -_size_456_8: MemoryPoolAligned([456]u8, .@"8"), -_size_520_8: MemoryPoolAligned([520]u8, .@"8"), +_size_160_8: MemoryPoolAligned([160]u8, .@"8"), _size_648_8: MemoryPoolAligned([648]u8, .@"8"), pub fn init(page: *Page) Factory { return .{ ._page = page, - ._size_1_8 = MemoryPoolAligned([1]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_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_56_8 = MemoryPoolAligned([56]u8, .@"8").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_88_8 = MemoryPoolAligned([88]u8, .@"8").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_144_8 = MemoryPoolAligned([144]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_520_8 = MemoryPoolAligned([520]u8, .@"8").init(page.arena), + ._size_160_8 = MemoryPoolAligned([160]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 { 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 == 16) return @ptrCast(try self._size_16_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 == 56) return @ptrCast(try self._size_56_8.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 == 88) return @ptrCast(try self._size_88_8.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 == 144) return @ptrCast(try self._size_144_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 == 520) return @ptrCast(try self._size_520_8.create()); + if (comptime SO == 160) return @ptrCast(try self._size_160_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) })); } @@ -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. if (comptime wasAllocated(S)) { switch (@sizeOf(S)) { - 1 => self._size_1_8.destroy(@ptrCast(@alignCast(value))), 8 => self._size_8_8.destroy(@ptrCast(@alignCast(value))), 16 => self._size_16_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))), 56 => self._size_56_8.destroy(@ptrCast(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))), 88 => self._size_88_8.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)), 144 => self._size_144_8.destroy(@ptrCast(value)), 152 => self._size_152_8.destroy(@ptrCast(value)), - 456 => self._size_456_8.destroy(@ptrCast(value)), - 520 => self._size_520_8.destroy(@ptrCast(value)), + 160 => self._size_160_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) })), } diff --git a/src/browser/Page.zig b/src/browser/Page.zig index ff22f2b6..9a4a9f96 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -50,6 +50,7 @@ const Element = @import("webapi/Element.zig"); const Window = @import("webapi/Window.zig"); const Location = @import("webapi/Location.zig"); const Document = @import("webapi/Document.zig"); +const Performance = @import("webapi/Performance.zig"); const HtmlScript = @import("webapi/Element.zig").Html.Script; const MutationObserver = @import("webapi/MutationObserver.zig"); const IntersectionObserver = @import("webapi/IntersectionObserver.zig"); @@ -179,6 +180,7 @@ fn reset(self: *Page, comptime initializing: bool) !void { ._document = self.document, ._storage_bucket = storage_bucket, ._history = History.init(self), + ._performance = Performance.init(), ._proto = undefined, ._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 // to exit so fast that we've run none of the // 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 // 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 { self._script_manager.addFromElement(script, "parsing") catch |err| { log.err(.page, "page.scriptAddedCallback", .{ diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 29e9f683..0d8740c8 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -742,6 +742,8 @@ const Script = struct { break :blk true; }; + defer page.tick(); + if (success) { self.executeCallback(script_element._on_load, page); return; diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 4c7ff66b..d2d0640e 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -1926,6 +1926,10 @@ pub fn queueIntersectionDelivery(self: *Context) !void { }.run, self.page); } +pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void { + self.isolate.enqueueMicrotaskFunc(cb.func.castToFunction()); +} + // == Misc == // An interface for types that want to have their jsDeinit function to be diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 447ce3ef..1eab16c9 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -543,6 +543,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/storage/storage.zig"), @import("../webapi/URL.zig"), @import("../webapi/Window.zig"), + @import("../webapi/Performance.zig"), @import("../webapi/MutationObserver.zig"), @import("../webapi/IntersectionObserver.zig"), }); diff --git a/src/browser/tests/legacy/window/window.html b/src/browser/tests/legacy/window/window.html index aac91171..2e49edd2 100644 --- a/src/browser/tests/legacy/window/window.html +++ b/src/browser/tests/legacy/window/window.html @@ -15,7 +15,9 @@ start = timestamp; } requestAnimationFrame(step); - testing.eventually(() => testing.expectEqual(true, start > 0)); + testing.eventually(() => { + testing.expectEqual(true, start > 0) + }); let request_id = requestAnimationFrame(() => { start = 0; @@ -67,7 +69,7 @@ testing.expectEqual(true, called); - --> - --> - --> - --> diff --git a/src/browser/tests/mutation_observer/attribute_filter.html b/src/browser/tests/mutation_observer/attribute_filter.html new file mode 100644 index 00000000..eb1c0605 --- /dev/null +++ b/src/browser/tests/mutation_observer/attribute_filter.html @@ -0,0 +1,157 @@ + +
Test
+
Test
+
+
Child
+
+ + + + + + + + + + + + + diff --git a/src/browser/tests/mutation_observer/character_data.html b/src/browser/tests/mutation_observer/character_data.html index 6afa299c..260d2822 100644 --- a/src/browser/tests/mutation_observer/character_data.html +++ b/src/browser/tests/mutation_observer/character_data.html @@ -19,7 +19,7 @@ textNode.data = 'Changed text'; 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(textNode, mutations[0].target); testing.expectEqual(null, mutations[0].oldValue); @@ -43,7 +43,7 @@ textNode.data = 'Second change'; 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(textNode, mutations[0].target); diff --git a/src/browser/tests/mutation_observer/childlist.html b/src/browser/tests/mutation_observer/childlist.html index 915dbcf4..e15eaa35 100644 --- a/src/browser/tests/mutation_observer/childlist.html +++ b/src/browser/tests/mutation_observer/childlist.html @@ -23,7 +23,7 @@ parent.appendChild(child2); Promise.resolve().then(() => { - testing.expectEqual(1, mutations.length, {script_id: 'childlist'}); + testing.expectEqual(1, mutations.length); testing.expectEqual('childList', mutations[0].type); testing.expectEqual(parent, mutations[0].target); testing.expectEqual(1, mutations[0].addedNodes.length); @@ -51,7 +51,7 @@ emptyParent.appendChild(firstChild); 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(emptyParent, mutations[0].target); testing.expectEqual(1, mutations[0].addedNodes.length); @@ -78,7 +78,7 @@ removeParent.removeChild(onlyChild); 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(removeParent, mutations[0].target); testing.expectEqual(0, mutations[0].addedNodes.length); @@ -105,7 +105,7 @@ textParent.appendChild(textNode); 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(textParent, mutations[0].target); testing.expectEqual(1, mutations[0].addedNodes.length); @@ -133,7 +133,7 @@ middleParent.removeChild(middle); 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(middleParent, mutations[0].target); testing.expectEqual(0, mutations[0].addedNodes.length); @@ -165,7 +165,7 @@ insertParent.insertBefore(insertMiddle, insertLast); 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(insertParent, mutations[0].target); testing.expectEqual(1, mutations[0].addedNodes.length); @@ -199,7 +199,7 @@ // replaceChild generates two separate mutation records in modern spec: // 1. First record for insertBefore (new node added) // 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 testing.expectEqual('childList', mutations[0].type); @@ -248,7 +248,7 @@ multipleParent.appendChild(child3); 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(child1, mutations[0].addedNodes[0]); @@ -289,7 +289,7 @@ // innerHTML triggers mutations for both removals and additions // With tri-state: from_parser=true + parse_mode=fragment -> mutations fire // 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 testing.expectEqual('childList', mutations[0].type); diff --git a/src/browser/tests/mutation_observer/mutation_observer.html b/src/browser/tests/mutation_observer/mutation_observer.html index 8ced1815..76b95bb7 100644 --- a/src/browser/tests/mutation_observer/mutation_observer.html +++ b/src/browser/tests/mutation_observer/mutation_observer.html @@ -21,7 +21,7 @@ element.removeAttribute('data-foo'); 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(element, mutations[0].target); @@ -57,7 +57,7 @@ element.removeAttribute('data-test'); 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(null, mutations[0].oldValue); @@ -86,7 +86,7 @@ element.setAttribute('data-disconnected', 'test'); Promise.resolve().then(() => { - testing.expectEqual(false, callbackCalled, {script_id: 'mutation_observer_disconnect'}); + testing.expectEqual(false, callbackCalled); }); }); diff --git a/src/browser/tests/mutation_observer/mutations_during_callback.html b/src/browser/tests/mutation_observer/mutations_during_callback.html index 829f3d5a..938e4518 100644 --- a/src/browser/tests/mutation_observer/mutations_during_callback.html +++ b/src/browser/tests/mutation_observer/mutations_during_callback.html @@ -29,7 +29,7 @@ // After first microtask, first callback should have run and triggered second mutation }).then(() => { // 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('data-first', firstRecords[0].attributeName); testing.expectEqual(1, secondRecords.length); diff --git a/src/browser/tests/mutation_observer/subtree.html b/src/browser/tests/mutation_observer/subtree.html new file mode 100644 index 00000000..6492a20e --- /dev/null +++ b/src/browser/tests/mutation_observer/subtree.html @@ -0,0 +1,138 @@ + +
+
+
Child
+
+
+ +
+
+
Text here
+
+
+ +
+
+
+ + + + + + + + + + + diff --git a/src/browser/tests/performance.html b/src/browser/tests/performance.html new file mode 100644 index 00000000..5aed2cc1 --- /dev/null +++ b/src/browser/tests/performance.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index ed34d80f..7d97a94b 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -46,7 +46,8 @@ pub const ObserveOptions = struct { childList: bool = false, characterData: 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 { @@ -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 { + // 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 for (self._observing.items) |*obs| { if (obs.target == target) { - obs.options = options; + obs.options = copied_options; return; } } @@ -71,7 +82,7 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, try self._observing.append(page.arena, .{ .target = target, - .options = options, + .options = copied_options, }); } @@ -99,11 +110,25 @@ pub fn notifyAttributeChange( for (self._observing.items) |obs| { if (obs.target != target_node) { - continue; + if (!obs.options.subtree) { + continue; + } + if (!obs.target.contains(target_node)) { + continue; + } } if (!obs.options.attributes) { 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{ ._type = .attributes, @@ -135,7 +160,12 @@ pub fn notifyCharacterDataChange( ) !void { for (self._observing.items) |obs| { if (obs.target != target) { - continue; + if (!obs.options.subtree) { + continue; + } + if (!obs.target.contains(target)) { + continue; + } } if (!obs.options.characterData) { continue; @@ -174,7 +204,12 @@ pub fn notifyChildListChange( ) !void { for (self._observing.items) |obs| { if (obs.target != target) { - continue; + if (!obs.options.subtree) { + continue; + } + if (!obs.target.contains(target)) { + continue; + } } if (!obs.options.childList) { continue; diff --git a/src/browser/webapi/Performance.zig b/src/browser/webapi/Performance.zig new file mode 100644 index 00000000..3ac87871 --- /dev/null +++ b/src/browser/webapi/Performance.zig @@ -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", .{}); +} diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index b3ac4414..bafb12d4 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -25,6 +25,7 @@ const Page = @import("../Page.zig"); const Console = @import("Console.zig"); const History = @import("History.zig"); const Navigator = @import("Navigator.zig"); +const Performance = @import("Performance.zig"); const Document = @import("Document.zig"); const Location = @import("Location.zig"); const Fetch = @import("net/Fetch.zig"); @@ -39,6 +40,7 @@ _proto: *EventTarget, _document: *Document, _console: Console = .init, _navigator: Navigator = .init, +_performance: Performance, _history: History, _storage_bucket: *storage.Bucket, _on_load: ?js.Function = null, @@ -70,6 +72,10 @@ pub fn getNavigator(_: *const Window) Navigator { return .{}; } +pub fn getPerformance(self: *Window) *Performance { + return &self._performance; +} + pub fn getLocalStorage(self: *const Window) *storage.Lookup { return &self._storage_bucket.local; } @@ -134,11 +140,14 @@ pub fn requestAnimationFrame(self: *Window, cb: js.Function, page: *Page) !u32 { .repeat = false, .params = &.{}, .low_priority = false, + .animation_frame = true, .name = "window.requestAnimationFrame", }, 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 { var sc = self._timers.get(id) orelse return; @@ -208,6 +217,7 @@ const ScheduleOpts = struct { params: []js.Object, name: []const u8, low_priority: bool = false, + animation_frame: bool = false, }; fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 { if (self._timers.count() > 512) { @@ -235,6 +245,7 @@ fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: Schedul .name = opts.name, .timer_id = timer_id, .params = opts.params, + .animation_frame = opts.animation_frame, .repeat_ms = if (opts.repeat) if (delay_ms == 0) 1 else delay_ms else null, }); gop.value_ptr.* = callback; @@ -266,6 +277,8 @@ const ScheduleCallback = struct { removed: bool = false, + animation_frame: bool = false, + fn deinit(self: *ScheduleCallback) void { self.page._factory.destroy(self); } @@ -278,10 +291,17 @@ const ScheduleCallback = struct { return null; } - self.cb.call(void, .{self.params}) catch |err| { - // a non-JS error - log.warn(.js, "window.timer", .{ .name = self.name, .err = err }); - }; + 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| { + // a non-JS error + log.warn(.js, "window.timer", .{ .name = self.name, .err = err }); + }; + } if (self.repeat_ms) |ms| { return ms; @@ -302,11 +322,13 @@ pub const JsApi = struct { 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 window = bridge.accessor(Window.getWindow, null, .{ .cache = "window" }); pub const parent = bridge.accessor(Window.getWindow, null, .{ .cache = "parent" }); pub const console = bridge.accessor(Window.getConsole, null, .{ .cache = "console" }); 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 sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" }); 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 onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{}); pub const fetch = bridge.function(Window.fetch, .{}); + pub const queueMicrotask = bridge.function(Window.queueMicrotask, .{}); pub const setTimeout = bridge.function(Window.setTimeout, .{}); pub const clearTimeout = bridge.function(Window.clearTimeout, .{}); pub const setInterval = bridge.function(Window.setInterval, .{}); @@ -326,6 +349,17 @@ pub const JsApi = struct { pub const btoa = bridge.function(Window.btoa, .{}); pub const atob = bridge.function(Window.atob, .{}); 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");