mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-17 00:38:59 +00:00
tweak timing of intersection observer and how it handles disconnected nodes
This commit is contained in:
@@ -106,6 +106,7 @@ _mutation_delivery_depth: u32 = 0,
|
|||||||
|
|
||||||
// List of active IntersectionObservers
|
// List of active IntersectionObservers
|
||||||
_intersection_observers: std.ArrayList(*IntersectionObserver) = .{},
|
_intersection_observers: std.ArrayList(*IntersectionObserver) = .{},
|
||||||
|
_intersection_check_scheduled: bool = false,
|
||||||
_intersection_delivery_scheduled: bool = false,
|
_intersection_delivery_scheduled: bool = false,
|
||||||
|
|
||||||
// Lookup for customized built-in elements. Maps element pointer to definition.
|
// Lookup for customized built-in elements. Maps element pointer to definition.
|
||||||
@@ -250,6 +251,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._mutation_delivery_scheduled = false;
|
self._mutation_delivery_scheduled = false;
|
||||||
self._mutation_delivery_depth = 0;
|
self._mutation_delivery_depth = 0;
|
||||||
self._intersection_observers = .{};
|
self._intersection_observers = .{};
|
||||||
|
self._intersection_check_scheduled = false;
|
||||||
self._intersection_delivery_scheduled = false;
|
self._intersection_delivery_scheduled = false;
|
||||||
self._customized_builtin_definitions = .{};
|
self._customized_builtin_definitions = .{};
|
||||||
self._customized_builtin_connected_callback_invoked = .{};
|
self._customized_builtin_connected_callback_invoked = .{};
|
||||||
@@ -781,6 +783,15 @@ pub fn scriptAddedCallback(self: *Page, script: *HtmlScript) !void {
|
|||||||
|
|
||||||
pub fn domChanged(self: *Page) void {
|
pub fn domChanged(self: *Page) void {
|
||||||
self.version += 1;
|
self.version += 1;
|
||||||
|
|
||||||
|
if (self._intersection_check_scheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self._intersection_check_scheduled = true;
|
||||||
|
self.js.queueIntersectionChecks() catch |err| {
|
||||||
|
log.err(.page, "page.schedIntersectChecks", .{ .err = err });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getElementIdMap(page: *Page, node: *Node) *std.StringHashMapUnmanaged(*Element) {
|
pub fn getElementIdMap(page: *Page, node: *Node) *std.StringHashMapUnmanaged(*Element) {
|
||||||
@@ -849,27 +860,31 @@ pub fn checkIntersections(self: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn scheduleMutationDelivery(self: *Page) !void {
|
pub fn scheduleMutationDelivery(self: *Page) !void {
|
||||||
// Only queue if not already scheduled
|
|
||||||
if (self._mutation_delivery_scheduled) {
|
if (self._mutation_delivery_scheduled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self._mutation_delivery_scheduled = true;
|
self._mutation_delivery_scheduled = true;
|
||||||
|
|
||||||
// Queue mutation delivery as a microtask
|
|
||||||
try self.js.queueMutationDelivery();
|
try self.js.queueMutationDelivery();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scheduleIntersectionDelivery(self: *Page) !void {
|
pub fn scheduleIntersectionDelivery(self: *Page) !void {
|
||||||
// Only queue if not already scheduled
|
|
||||||
if (self._intersection_delivery_scheduled) {
|
if (self._intersection_delivery_scheduled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self._intersection_delivery_scheduled = true;
|
self._intersection_delivery_scheduled = true;
|
||||||
|
|
||||||
// Queue intersection delivery as a microtask
|
|
||||||
try self.js.queueIntersectionDelivery();
|
try self.js.queueIntersectionDelivery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn performScheduledIntersectionChecks(self: *Page) void {
|
||||||
|
if (!self._intersection_check_scheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self._intersection_check_scheduled = false;
|
||||||
|
self.checkIntersections() catch |err| {
|
||||||
|
log.err(.page, "page.schedIntersectChecks", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deliverIntersections(self: *Page) void {
|
pub fn deliverIntersections(self: *Page) void {
|
||||||
if (!self._intersection_delivery_scheduled) {
|
if (!self._intersection_delivery_scheduled) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1973,6 +1973,15 @@ pub fn queueMutationDelivery(self: *Context) !void {
|
|||||||
}.run, self.page);
|
}.run, self.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queueIntersectionChecks(self: *Context) !void {
|
||||||
|
self.isolate.enqueueMicrotask(struct {
|
||||||
|
fn run(data: ?*anyopaque) callconv(.c) void {
|
||||||
|
const page: *Page = @ptrCast(@alignCast(data.?));
|
||||||
|
page.performScheduledIntersectionChecks();
|
||||||
|
}
|
||||||
|
}.run, self.page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn queueIntersectionDelivery(self: *Context) !void {
|
pub fn queueIntersectionDelivery(self: *Context) !void {
|
||||||
self.isolate.enqueueMicrotask(struct {
|
self.isolate.enqueueMicrotask(struct {
|
||||||
fn run(data: ?*anyopaque) callconv(.c) void {
|
fn run(data: ?*anyopaque) callconv(.c) void {
|
||||||
|
|||||||
@@ -29,3 +29,33 @@
|
|||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=detached>
|
||||||
|
{
|
||||||
|
// never attached
|
||||||
|
let count = 0;
|
||||||
|
const div = document.createElement('div');
|
||||||
|
new IntersectionObserver((entries) => {
|
||||||
|
count += 1;
|
||||||
|
}).observe(div);
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
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) => {
|
||||||
|
count += 1;
|
||||||
|
}).observe(div1);
|
||||||
|
|
||||||
|
div2.appendChild(div1);
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(1, count);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
const div1 = document.createElement('div');
|
const div1 = document.createElement('div');
|
||||||
const div2 = document.createElement('div');
|
const div2 = document.createElement('div');
|
||||||
new IntersectionObserver((entries) => {
|
new IntersectionObserver((entries) => {
|
||||||
console.log(entries[0]);
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}).observe(div1);
|
}).observe(div1);
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id=reobserve>
|
<!-- <script id=reobserve>
|
||||||
{
|
{
|
||||||
let count = 0;
|
let count = 0;
|
||||||
let observer = new IntersectionObserver(entries => {
|
let observer = new IntersectionObserver(entries => {
|
||||||
@@ -160,4 +159,4 @@
|
|||||||
], capture)
|
], capture)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script> -->
|
||||||
|
|||||||
@@ -161,11 +161,13 @@ fn calculateIntersection(
|
|||||||
// For a headless browser without real layout, we treat all elements as fully visible.
|
// For a headless browser without real layout, we treat all elements as fully visible.
|
||||||
// This avoids fingerprinting issues (massive viewports) and matches the behavior
|
// This avoids fingerprinting issues (massive viewports) and matches the behavior
|
||||||
// scripts expect when querying element visibility.
|
// scripts expect when querying element visibility.
|
||||||
const is_intersecting = true;
|
// However, elements without a parent cannot intersect (they have no containing block).
|
||||||
const intersection_ratio: f64 = 1.0;
|
const has_parent = target.asNode().parentNode() != null;
|
||||||
|
const is_intersecting = has_parent;
|
||||||
|
const intersection_ratio: f64 = if (has_parent) 1.0 else 0.0;
|
||||||
|
|
||||||
// Intersection rect is the same as the target rect (fully visible)
|
// Intersection rect is the same as the target rect if visible, otherwise zero rect
|
||||||
const intersection_rect = target_rect;
|
const intersection_rect = if (has_parent) target_rect else &zero_rect;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.is_intersecting = is_intersecting,
|
.is_intersecting = is_intersecting,
|
||||||
@@ -199,11 +201,10 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
|
|||||||
const is_now_intersecting = data.is_intersecting and self.meetsThreshold(data.intersection_ratio);
|
const is_now_intersecting = data.is_intersecting and self.meetsThreshold(data.intersection_ratio);
|
||||||
|
|
||||||
// Create entry if:
|
// Create entry if:
|
||||||
// 1. First time observing this target (was_intersecting_opt == null)
|
// 1. First time observing this target AND it's intersecting
|
||||||
// 2. State changed
|
// 2. State changed
|
||||||
// 3. Currently intersecting
|
const should_report = (was_intersecting_opt == null and is_now_intersecting) or
|
||||||
const should_report = was_intersecting_opt == null or
|
(was_intersecting_opt != null and was_intersecting_opt.? != is_now_intersecting);
|
||||||
was_intersecting_opt.? != is_now_intersecting;
|
|
||||||
|
|
||||||
if (should_report) {
|
if (should_report) {
|
||||||
const entry = try page.arena.create(IntersectionObserverEntry);
|
const entry = try page.arena.create(IntersectionObserverEntry);
|
||||||
@@ -218,8 +219,11 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
|
|||||||
};
|
};
|
||||||
|
|
||||||
try self._pending_entries.append(page.arena, entry);
|
try self._pending_entries.append(page.arena, entry);
|
||||||
try self._previous_states.put(page.arena, target, is_now_intersecting);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always update the previous state, even if we didn't report
|
||||||
|
// This ensures we can detect state changes on subsequent checks
|
||||||
|
try self._previous_states.put(page.arena, target, is_now_intersecting);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkIntersections(self: *IntersectionObserver, page: *Page) !void {
|
pub fn checkIntersections(self: *IntersectionObserver, page: *Page) !void {
|
||||||
|
|||||||
Reference in New Issue
Block a user