mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Merge pull request #638 from lightpanda-io/DOM-scoll-and-quads
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
scrollIntoViewIfNeeded, getContentQuads, innerWidth/Heigh
This commit is contained in:
@@ -389,6 +389,10 @@ pub const Element = struct {
|
|||||||
const s = try cssParse(state.call_arena, selectors, .{});
|
const s = try cssParse(state.call_arena, selectors, .{});
|
||||||
return s.match(CssNodeWrap{ .node = parser.elementToNode(self) });
|
return s.match(CssNodeWrap{ .node = parser.elementToNode(self) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn _scrollIntoViewIfNeeded(_: *parser.Element, center_if_needed: ?bool) void {
|
||||||
|
_ = center_if_needed;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
@@ -575,6 +579,12 @@ test "Browser.DOM.Element" {
|
|||||||
.{ "el.matches('.notok')", "false" },
|
.{ "el.matches('.notok')", "false" },
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "const el3 = document.createElement('div');", "undefined" },
|
||||||
|
.{ "el3.scrollIntoViewIfNeeded();", "undefined" },
|
||||||
|
.{ "el3.scrollIntoViewIfNeeded(false);", "undefined" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
// before
|
// before
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "const before_container = document.createElement('div');", "undefined" },
|
.{ "const before_container = document.createElement('div');", "undefined" },
|
||||||
|
|||||||
@@ -120,6 +120,18 @@ pub const Window = struct {
|
|||||||
return &self.history;
|
return &self.history;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
|
||||||
|
pub fn get_innerHeight(_: *Window, state: *SessionState) u32 {
|
||||||
|
// We do not have scrollbars or padding so this is the same as Element.clientHeight
|
||||||
|
return state.renderer.height();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The interior width of the window in pixels. That includes the width of the vertical scroll bar, if one is present.
|
||||||
|
pub fn get_innerWidth(_: *Window, state: *SessionState) u32 {
|
||||||
|
// We do not have scrollbars or padding so this is the same as Element.clientWidth
|
||||||
|
return state.renderer.width();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_name(self: *Window) []const u8 {
|
pub fn get_name(self: *Window) []const u8 {
|
||||||
return self.target;
|
return self.target;
|
||||||
}
|
}
|
||||||
@@ -281,14 +293,23 @@ test "Browser.HTML.Window" {
|
|||||||
\\ }
|
\\ }
|
||||||
\\ }
|
\\ }
|
||||||
,
|
,
|
||||||
"undefined",
|
null,
|
||||||
},
|
},
|
||||||
.{ "let id = requestAnimationFrame(step);", "undefined" },
|
.{ "requestAnimationFrame(step);", null }, // returned id is checked in the next test
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|
||||||
// cancelAnimationFrame should be able to cancel a request with the given id
|
// cancelAnimationFrame should be able to cancel a request with the given id
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "let request_id = requestAnimationFrame(timestamp => {});", "undefined" },
|
.{ "let request_id = requestAnimationFrame(timestamp => {});", null },
|
||||||
.{ "cancelAnimationFrame(request_id);", "undefined" },
|
.{ "cancelAnimationFrame(request_id);", "undefined" },
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "innerHeight", "1" },
|
||||||
|
.{ "innerWidth", "1" }, // Width is 1 even if there are no elements
|
||||||
|
.{ "document.createElement('div').getClientRects()", null },
|
||||||
|
.{ "document.createElement('div').getClientRects()", null },
|
||||||
|
.{ "innerHeight", "1" },
|
||||||
|
.{ "innerWidth", "2" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
const Node = @import("../Node.zig");
|
const Node = @import("../Node.zig");
|
||||||
const css = @import("../../browser/dom/css.zig");
|
const css = @import("../../browser/dom/css.zig");
|
||||||
const parser = @import("../../browser/netsurf.zig");
|
const parser = @import("../../browser/netsurf.zig");
|
||||||
const dom_node = @import("../../browser/dom/node.zig");
|
const dom_node = @import("../../browser/dom/node.zig");
|
||||||
|
const DOMRect = @import("../../browser/dom/element.zig").Element.DOMRect;
|
||||||
|
|
||||||
pub fn processMessage(cmd: anytype) !void {
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
const action = std.meta.stringToEnum(enum {
|
const action = std.meta.stringToEnum(enum {
|
||||||
@@ -31,6 +33,8 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
discardSearchResults,
|
discardSearchResults,
|
||||||
resolveNode,
|
resolveNode,
|
||||||
describeNode,
|
describeNode,
|
||||||
|
scrollIntoViewIfNeeded,
|
||||||
|
getContentQuads,
|
||||||
}, cmd.input.action) orelse return error.UnknownMethod;
|
}, cmd.input.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@@ -41,6 +45,8 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
.discardSearchResults => return discardSearchResults(cmd),
|
.discardSearchResults => return discardSearchResults(cmd),
|
||||||
.resolveNode => return resolveNode(cmd),
|
.resolveNode => return resolveNode(cmd),
|
||||||
.describeNode => return describeNode(cmd),
|
.describeNode => return describeNode(cmd),
|
||||||
|
.scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd),
|
||||||
|
.getContentQuads => return getContentQuads(cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,23 +239,98 @@ fn describeNode(cmd: anytype) !void {
|
|||||||
depth: u32 = 1,
|
depth: u32 = 1,
|
||||||
pierce: bool = false,
|
pierce: bool = false,
|
||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
if (params.backendNodeId != null or params.depth != 1 or params.pierce) {
|
|
||||||
return error.NotYetImplementedParams;
|
if (params.depth != 1 or params.pierce) return error.NotYetImplementedParams;
|
||||||
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
|
const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
|
||||||
|
|
||||||
|
return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{}) }, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// An array of quad vertices, x immediately followed by y for each point, points clock-wise.
|
||||||
|
// Note Y points downward
|
||||||
|
// We are assuming the start/endpoint is not repeated.
|
||||||
|
const Quad = [8]f64;
|
||||||
|
|
||||||
|
fn rectToQuad(rect: DOMRect) Quad {
|
||||||
|
return Quad{
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.x + rect.width,
|
||||||
|
rect.y,
|
||||||
|
rect.x + rect.width,
|
||||||
|
rect.y + rect.height,
|
||||||
|
rect.x,
|
||||||
|
rect.y + rect.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scrollIntoViewIfNeeded(cmd: anytype) !void {
|
||||||
|
const params = (try cmd.params(struct {
|
||||||
|
nodeId: ?Node.Id = null,
|
||||||
|
backendNodeId: ?u32 = null,
|
||||||
|
objectId: ?[]const u8 = null,
|
||||||
|
rect: ?DOMRect = null,
|
||||||
|
})) orelse return error.InvalidParams;
|
||||||
|
// Only 1 of nodeId, backendNodeId, objectId may be set, but chrome just takes the first non-null
|
||||||
|
|
||||||
|
// We retrieve the node to at least check if it exists and is valid.
|
||||||
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
|
||||||
|
|
||||||
|
const node_type = parser.nodeType(node._node) catch return error.InvalidNode;
|
||||||
|
switch (node_type) {
|
||||||
|
.element => {},
|
||||||
|
.document => {},
|
||||||
|
.text => {},
|
||||||
|
else => return error.NodeDoesNotHaveGeometry,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cmd.sendResult(null, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getNode(arena: Allocator, browser_context: anytype, node_id: ?Node.Id, backend_node_id: ?Node.Id, object_id: ?[]const u8) !*Node {
|
||||||
|
const input_node_id = node_id orelse backend_node_id;
|
||||||
|
if (input_node_id) |input_node_id_| {
|
||||||
|
return browser_context.node_registry.lookup_by_id.get(input_node_id_) orelse return error.NodeNotFound;
|
||||||
|
}
|
||||||
|
if (object_id) |object_id_| {
|
||||||
|
// Retrieve the object from which ever context it is in.
|
||||||
|
const parser_node = try browser_context.inspector.getNodePtr(arena, object_id_);
|
||||||
|
return try browser_context.node_registry.register(@ptrCast(parser_node));
|
||||||
|
}
|
||||||
|
return error.MissingParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getContentQuads
|
||||||
|
// Related to: https://drafts.csswg.org/cssom-view/#the-geometryutils-interface
|
||||||
|
fn getContentQuads(cmd: anytype) !void {
|
||||||
|
const params = (try cmd.params(struct {
|
||||||
|
nodeId: ?Node.Id = null,
|
||||||
|
backendNodeId: ?Node.Id = null,
|
||||||
|
objectId: ?[]const u8 = null,
|
||||||
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
if (params.nodeId != null) {
|
const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
|
||||||
const node = bc.node_registry.lookup_by_id.get(params.nodeId.?) orelse return error.NodeNotFound;
|
|
||||||
return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{}) }, .{});
|
// TODO likely if the following CSS properties are set the quads should be empty
|
||||||
}
|
// visibility: hidden
|
||||||
if (params.objectId != null) {
|
// display: none
|
||||||
// Retrieve the object from which ever context it is in.
|
|
||||||
const parser_node = try bc.inspector.getNodePtr(cmd.arena, params.objectId.?);
|
if (try parser.nodeType(node._node) != .element) return error.NodeIsNotAnElement;
|
||||||
const node = try bc.node_registry.register(@ptrCast(parser_node));
|
// TODO implement for document or text
|
||||||
return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{}) }, .{});
|
// Most likely document would require some hierachgy in the renderer. It is left unimplemented till we have a good example.
|
||||||
}
|
// Text may be tricky, multiple quads in case of multiple lines? empty quads of text = ""?
|
||||||
return error.MissingParams;
|
// Elements like SVGElement may have multiple quads.
|
||||||
|
|
||||||
|
const element = parser.nodeToElement(node._node);
|
||||||
|
const rect = try bc.session.page.?.state.renderer.getRect(element);
|
||||||
|
const quad = rectToQuad(rect);
|
||||||
|
|
||||||
|
return cmd.sendResult(.{ .quads = &.{quad} }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|||||||
Reference in New Issue
Block a user