mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 22:53:28 +00:00
Merge pull request #501 from karlseguin/renderer
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
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (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
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (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
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (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
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Add a dumb renderer to get coordinates
This commit is contained in:
@@ -298,10 +298,14 @@ pub const Page = struct {
|
||||
// current_script could by fetch module to resolve module's url to fetch.
|
||||
current_script: ?*const Script = null,
|
||||
|
||||
renderer: FlatRenderer,
|
||||
|
||||
fn init(session: *Session) Page {
|
||||
const arena = session.browser.page_arena.allocator();
|
||||
return .{
|
||||
.arena = arena,
|
||||
.session = session,
|
||||
.arena = session.browser.page_arena.allocator(),
|
||||
.renderer = FlatRenderer.init(arena),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -423,6 +427,53 @@ pub const Page = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub const ClickResult = union(enum) {
|
||||
navigate: std.Uri,
|
||||
};
|
||||
|
||||
pub const MouseEvent = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
type: Type,
|
||||
|
||||
const Type = enum {
|
||||
pressed,
|
||||
released,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn mouseEvent(self: *Page, allocator: Allocator, me: MouseEvent) !?ClickResult {
|
||||
if (me.type != .pressed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const element = self.renderer.getElementAtPosition(me.x, me.y) orelse return null;
|
||||
|
||||
const event = try parser.mouseEventCreate();
|
||||
defer parser.mouseEventDestroy(event);
|
||||
try parser.mouseEventInit(event, "click", .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.x = me.x,
|
||||
.y = me.y,
|
||||
});
|
||||
_ = try parser.elementDispatchEvent(element, @ptrCast(event));
|
||||
|
||||
if ((try parser.mouseEventDefaultPrevented(event)) == true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const node = parser.elementToNode(element);
|
||||
const tag = try parser.nodeName(node);
|
||||
if (std.ascii.eqlIgnoreCase(tag, "a")) {
|
||||
const href = (try parser.elementGetAttribute(element, "href")) orelse return null;
|
||||
var buf = try allocator.alloc(u8, 1024);
|
||||
return .{ .navigate = try std.Uri.resolve_inplace(self.uri, href, &buf) };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/#read-html
|
||||
fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8, aux_data: ?[]const u8) !void {
|
||||
const arena = self.arena;
|
||||
@@ -462,6 +513,7 @@ pub const Page = struct {
|
||||
try session.env.setUserContext(.{
|
||||
.uri = self.uri,
|
||||
.document = html_doc,
|
||||
.renderer = @ptrCast(&self.renderer),
|
||||
.cookie_jar = @ptrCast(&self.session.cookie_jar),
|
||||
.http_client = @ptrCast(self.session.http_client),
|
||||
});
|
||||
@@ -753,6 +805,70 @@ pub const Page = struct {
|
||||
};
|
||||
};
|
||||
|
||||
// provide very poor abstration to the rest of the code. In theory, we can change
|
||||
// the FlatRendere to a different implementation, and it'll all just work.
|
||||
pub const Renderer = FlatRenderer;
|
||||
|
||||
// This "renderer" positions elements in a single row in an unspecified order.
|
||||
// The important thing is that elements have a consistent position/index within
|
||||
// that row, which can be turned into a rectangle.
|
||||
const FlatRenderer = struct {
|
||||
allocator: Allocator,
|
||||
|
||||
// key is a @ptrFromInt of the element
|
||||
// value is the index position
|
||||
positions: std.AutoHashMapUnmanaged(u64, u32),
|
||||
|
||||
// given an index, get the element
|
||||
elements: std.ArrayListUnmanaged(u64),
|
||||
|
||||
const Element = @import("../dom/element.zig").Element;
|
||||
|
||||
// we expect allocator to be an arena
|
||||
pub fn init(allocator: Allocator) FlatRenderer {
|
||||
return .{
|
||||
.elements = .{},
|
||||
.positions = .{},
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getRect(self: *FlatRenderer, e: *parser.Element) !Element.DOMRect {
|
||||
var elements = &self.elements;
|
||||
const gop = try self.positions.getOrPut(self.allocator, @intFromPtr(e));
|
||||
var x: u32 = gop.value_ptr.*;
|
||||
if (gop.found_existing == false) {
|
||||
try elements.append(self.allocator, @intFromPtr(e));
|
||||
x = @intCast(elements.items.len);
|
||||
gop.value_ptr.* = x;
|
||||
}
|
||||
|
||||
return .{
|
||||
.x = @floatFromInt(x),
|
||||
.y = 0.0,
|
||||
.width = 1.0,
|
||||
.height = 1.0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn width(self: *const FlatRenderer) u32 {
|
||||
return @intCast(self.elements.items.len);
|
||||
}
|
||||
|
||||
pub fn height(_: *const FlatRenderer) u32 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
pub fn getElementAtPosition(self: *const FlatRenderer, x: i32, y: i32) ?*parser.Element {
|
||||
if (y != 1 or x < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const elements = self.elements.items;
|
||||
return if (x < elements.len) @ptrFromInt(elements[@intCast(x)]) else null;
|
||||
}
|
||||
};
|
||||
|
||||
const NoopInspector = struct {
|
||||
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
|
||||
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
|
||||
|
||||
@@ -37,6 +37,7 @@ pub const CDP = CDPT(struct {
|
||||
|
||||
const SessionIdGen = Incrementing(u32, "SID");
|
||||
const TargetIdGen = Incrementing(u32, "TID");
|
||||
const LoaderIdGen = Incrementing(u32, "LID");
|
||||
const BrowserContextIdGen = Incrementing(u32, "BID");
|
||||
|
||||
// Generic so that we can inject mocks into it.
|
||||
@@ -54,6 +55,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
||||
target_auto_attach: bool = false,
|
||||
|
||||
target_id_gen: TargetIdGen = .{},
|
||||
loader_id_gen: LoaderIdGen = .{},
|
||||
session_id_gen: SessionIdGen = .{},
|
||||
browser_context_id_gen: BrowserContextIdGen = .{},
|
||||
|
||||
@@ -183,6 +185,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
||||
},
|
||||
5 => switch (@as(u40, @bitCast(domain[0..5].*))) {
|
||||
asUint("Fetch") => return @import("domains/fetch.zig").processMessage(command),
|
||||
asUint("Input") => return @import("domains/input.zig").processMessage(command),
|
||||
else => {},
|
||||
},
|
||||
6 => switch (@as(u48, @bitCast(domain[0..6].*))) {
|
||||
@@ -281,8 +284,6 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
||||
// we should reject it.
|
||||
session_id: ?[]const u8,
|
||||
|
||||
// State
|
||||
url: []const u8,
|
||||
loader_id: []const u8,
|
||||
security_origin: []const u8,
|
||||
page_life_cycle_events: bool,
|
||||
@@ -303,7 +304,6 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
||||
.cdp = cdp,
|
||||
.target_id = null,
|
||||
.session_id = null,
|
||||
.url = URL_BASE,
|
||||
.security_origin = URL_BASE,
|
||||
.secure_context_type = "Secure", // TODO = enum
|
||||
.loader_id = LOADER_ID,
|
||||
@@ -333,6 +333,11 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getURL(self: *const Self) ?[]const u8 {
|
||||
const page = self.session.currentPage() orelse return null;
|
||||
return page.rawuri;
|
||||
}
|
||||
|
||||
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
|
||||
if (std.log.defaultLogEnabled(.debug)) {
|
||||
// msg should be {"id":<id>,...
|
||||
|
||||
100
src/cdp/domains/input.zig
Normal file
100
src/cdp/domains/input.zig
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const Page = @import("../../browser/browser.zig").Page;
|
||||
|
||||
pub fn processMessage(cmd: anytype) !void {
|
||||
const action = std.meta.stringToEnum(enum {
|
||||
dispatchMouseEvent,
|
||||
}, cmd.input.action) orelse return error.UnknownMethod;
|
||||
|
||||
switch (action) {
|
||||
.dispatchMouseEvent => return dispatchMouseEvent(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchMouseEvent
|
||||
fn dispatchMouseEvent(cmd: anytype) !void {
|
||||
const params = (try cmd.params(struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
type: Type,
|
||||
|
||||
const Type = enum {
|
||||
mousePressed,
|
||||
mouseReleased,
|
||||
mouseMoved,
|
||||
mouseWheel,
|
||||
};
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
try cmd.sendResult(null, .{});
|
||||
|
||||
// quickly ignore types we know we don't handle
|
||||
switch (params.type) {
|
||||
.mouseMoved, .mouseWheel => return,
|
||||
else => {},
|
||||
}
|
||||
|
||||
const bc = cmd.browser_context orelse return;
|
||||
const page = bc.session.currentPage() orelse return;
|
||||
|
||||
const mouse_event = Page.MouseEvent{
|
||||
.x = params.x,
|
||||
.y = params.y,
|
||||
.type = switch (params.type) {
|
||||
.mousePressed => .pressed,
|
||||
.mouseReleased => .released,
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
const click_result = (try page.mouseEvent(cmd.arena, mouse_event)) orelse return;
|
||||
|
||||
switch (click_result) {
|
||||
.navigate => |uri| try clickNavigate(cmd, uri),
|
||||
}
|
||||
// result already sent
|
||||
}
|
||||
|
||||
fn clickNavigate(cmd: anytype, uri: std.Uri) !void {
|
||||
const bc = cmd.browser_context.?;
|
||||
|
||||
var url_buf: std.ArrayListUnmanaged(u8) = .{};
|
||||
try uri.writeToStream(.{
|
||||
.scheme = true,
|
||||
.authentication = true,
|
||||
.authority = true,
|
||||
.port = true,
|
||||
.path = true,
|
||||
.query = true,
|
||||
}, url_buf.writer(cmd.arena));
|
||||
const url = url_buf.items;
|
||||
|
||||
try cmd.sendEvent("Page.frameRequestedNavigation", .{
|
||||
.url = url,
|
||||
.frameId = bc.target_id.?,
|
||||
.reason = "anchorClick",
|
||||
.disposition = "currentTab",
|
||||
}, .{ .session_id = bc.session_id.? });
|
||||
|
||||
bc.session.removePage();
|
||||
_ = try bc.session.createPage(null);
|
||||
|
||||
try @import("page.zig").navigateToUrl(cmd, url, false);
|
||||
}
|
||||
@@ -61,10 +61,10 @@ fn getFrameTree(cmd: anytype) !void {
|
||||
return cmd.sendResult(.{
|
||||
.frameTree = .{
|
||||
.frame = Frame{
|
||||
.url = bc.url,
|
||||
.id = target_id,
|
||||
.loaderId = bc.loader_id,
|
||||
.securityOrigin = bc.security_origin,
|
||||
.url = bc.getURL() orelse "about:blank",
|
||||
.secureContextType = bc.secure_context_type,
|
||||
},
|
||||
},
|
||||
@@ -129,6 +129,18 @@ fn createIsolatedWorld(cmd: anytype) !void {
|
||||
}
|
||||
|
||||
fn navigate(cmd: anytype) !void {
|
||||
const params = (try cmd.params(struct {
|
||||
url: []const u8,
|
||||
// referrer: ?[]const u8 = null,
|
||||
// transitionType: ?[]const u8 = null, // TODO: enum
|
||||
// frameId: ?[]const u8 = null,
|
||||
// referrerPolicy: ?[]const u8 = null, // TODO: enum
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
return navigateToUrl(cmd, params.url, true);
|
||||
}
|
||||
|
||||
pub fn navigateToUrl(cmd: anytype, url: []const u8, send_result: bool) !void {
|
||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||
|
||||
// didn't create?
|
||||
@@ -140,20 +152,9 @@ fn navigate(cmd: anytype) !void {
|
||||
// if we have a target_id we have to have a page;
|
||||
std.debug.assert(bc.session.page != null);
|
||||
|
||||
const params = (try cmd.params(struct {
|
||||
url: []const u8,
|
||||
referrer: ?[]const u8 = null,
|
||||
transitionType: ?[]const u8 = null, // TODO: enum
|
||||
frameId: ?[]const u8 = null,
|
||||
referrerPolicy: ?[]const u8 = null, // TODO: enum
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
// change state
|
||||
bc.reset();
|
||||
bc.url = params.url;
|
||||
|
||||
// TODO: hard coded ID
|
||||
bc.loader_id = "AF8667A203C5392DBE9AC290044AA4C2";
|
||||
bc.loader_id = cmd.cdp.loader_id_gen.next();
|
||||
|
||||
const LifecycleEvent = struct {
|
||||
frameId: []const u8,
|
||||
@@ -180,10 +181,12 @@ fn navigate(cmd: anytype) !void {
|
||||
}
|
||||
|
||||
// output
|
||||
try cmd.sendResult(.{
|
||||
.frameId = target_id,
|
||||
.loaderId = bc.loader_id,
|
||||
}, .{});
|
||||
if (send_result) {
|
||||
try cmd.sendResult(.{
|
||||
.frameId = target_id,
|
||||
.loaderId = bc.loader_id,
|
||||
}, .{});
|
||||
}
|
||||
|
||||
// TODO: at this point do we need async the following actions to be async?
|
||||
|
||||
@@ -199,7 +202,7 @@ fn navigate(cmd: anytype) !void {
|
||||
);
|
||||
|
||||
var page = bc.session.currentPage().?;
|
||||
try page.navigate(params.url, aux_data);
|
||||
try page.navigate(url, aux_data);
|
||||
|
||||
// Events
|
||||
|
||||
@@ -218,7 +221,7 @@ fn navigate(cmd: anytype) !void {
|
||||
.type = "Navigation",
|
||||
.frame = Frame{
|
||||
.id = target_id,
|
||||
.url = bc.url,
|
||||
.url = url,
|
||||
.securityOrigin = bc.security_origin,
|
||||
.secureContextType = bc.secure_context_type,
|
||||
.loaderId = bc.loader_id,
|
||||
@@ -281,7 +284,7 @@ test "cdp.page: getFrameTree" {
|
||||
.frame = .{
|
||||
.id = "TID-3",
|
||||
.loaderId = bc.loader_id,
|
||||
.url = bc.url,
|
||||
.url = "about:blank",
|
||||
.domainAndRegistry = "",
|
||||
.securityOrigin = bc.security_origin,
|
||||
.mimeType = "text/html",
|
||||
|
||||
@@ -132,7 +132,6 @@ fn createTarget(cmd: anytype) !void {
|
||||
_ = try bc.session.createPage(aux_data);
|
||||
|
||||
// change CDP state
|
||||
bc.url = "about:blank";
|
||||
bc.security_origin = "://";
|
||||
bc.secure_context_type = "InsecureScheme";
|
||||
bc.loader_id = LOADER_ID;
|
||||
@@ -142,11 +141,11 @@ fn createTarget(cmd: anytype) !void {
|
||||
// has been enabled?
|
||||
try cmd.sendEvent("Target.targetCreated", .{
|
||||
.targetInfo = TargetInfo{
|
||||
.url = bc.url,
|
||||
.attached = false,
|
||||
.targetId = target_id,
|
||||
.title = "about:blank",
|
||||
.browserContextId = bc.id,
|
||||
.attached = false,
|
||||
.url = "about:blank",
|
||||
},
|
||||
}, .{});
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ const Session = struct {
|
||||
return error.MockBrowserPageAlreadyExists;
|
||||
}
|
||||
self.page = .{
|
||||
.rawuri = "",
|
||||
.session = self,
|
||||
.aux_data = try self.arena.dupe(u8, aux_data orelse ""),
|
||||
};
|
||||
@@ -103,14 +104,20 @@ const Session = struct {
|
||||
|
||||
const Page = struct {
|
||||
session: *Session,
|
||||
rawuri: []const u8,
|
||||
aux_data: []const u8 = "",
|
||||
doc: ?*parser.Document = null,
|
||||
|
||||
pub fn navigate(self: *Page, url: []const u8, aux_data: []const u8) !void {
|
||||
_ = self;
|
||||
pub fn navigate(_: *Page, url: []const u8, aux_data: []const u8) !void {
|
||||
_ = url;
|
||||
_ = aux_data;
|
||||
}
|
||||
|
||||
const MouseEvent = @import("../browser/browser.zig").Page.MouseEvent;
|
||||
const ClickResult = @import("../browser/browser.zig").Page.ClickResult;
|
||||
pub fn mouseEvent(_: *Page, _: Allocator, _: MouseEvent) !?ClickResult {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const Client = struct {
|
||||
|
||||
@@ -33,6 +33,7 @@ const Node = @import("node.zig").Node;
|
||||
const Walker = @import("walker.zig").WalkerDepthFirst;
|
||||
const NodeList = @import("nodelist.zig").NodeList;
|
||||
const HTMLElem = @import("../html/elements.zig");
|
||||
const UserContext = @import("../user_context.zig").UserContext;
|
||||
pub const Union = @import("../html/elements.zig").Union;
|
||||
|
||||
const DOMException = @import("exceptions.zig").DOMException;
|
||||
@@ -43,6 +44,13 @@ pub const Element = struct {
|
||||
pub const prototype = *Node;
|
||||
pub const mem_guarantied = true;
|
||||
|
||||
pub const DOMRect = struct {
|
||||
x: f64,
|
||||
y: f64,
|
||||
width: f64,
|
||||
height: f64,
|
||||
};
|
||||
|
||||
pub fn toInterface(e: *parser.Element) !Union {
|
||||
return try HTMLElem.toInterface(Union, e);
|
||||
}
|
||||
@@ -339,6 +347,18 @@ pub const Element = struct {
|
||||
return Node.replaceChildren(parser.elementToNode(self), nodes);
|
||||
}
|
||||
|
||||
pub fn _getBoundingClientRect(self: *parser.Element, user_context: UserContext) !DOMRect {
|
||||
return user_context.renderer.getRect(self);
|
||||
}
|
||||
|
||||
pub fn get_clientWidth(_: *parser.Element, user_context: UserContext) u32 {
|
||||
return user_context.renderer.width();
|
||||
}
|
||||
|
||||
pub fn get_clientHeight(_: *parser.Element, user_context: UserContext) u32 {
|
||||
return user_context.renderer.height();
|
||||
}
|
||||
|
||||
pub fn deinit(_: *parser.Element, _: std.mem.Allocator) void {}
|
||||
};
|
||||
|
||||
@@ -484,5 +504,33 @@ pub fn testExecFn(
|
||||
var outerHTML = [_]Case{
|
||||
.{ .src = "document.getElementById('para').outerHTML", .ex = "<p id=\"para\"> And</p>" },
|
||||
};
|
||||
|
||||
var getBoundingClientRect = [_]Case{
|
||||
.{ .src = "document.getElementById('para').clientWidth", .ex = "0" },
|
||||
.{ .src = "document.getElementById('para').clientHeight", .ex = "1" },
|
||||
|
||||
.{ .src = "let r1 = document.getElementById('para').getBoundingClientRect()", .ex = "undefined" },
|
||||
.{ .src = "r1.x", .ex = "1" },
|
||||
.{ .src = "r1.y", .ex = "0" },
|
||||
.{ .src = "r1.width", .ex = "1" },
|
||||
.{ .src = "r1.height", .ex = "1" },
|
||||
|
||||
.{ .src = "let r2 = document.getElementById('content').getBoundingClientRect()", .ex = "undefined" },
|
||||
.{ .src = "r2.x", .ex = "2" },
|
||||
.{ .src = "r2.y", .ex = "0" },
|
||||
.{ .src = "r2.width", .ex = "1" },
|
||||
.{ .src = "r2.height", .ex = "1" },
|
||||
|
||||
.{ .src = "let r3 = document.getElementById('para').getBoundingClientRect()", .ex = "undefined" },
|
||||
.{ .src = "r3.x", .ex = "1" },
|
||||
.{ .src = "r3.y", .ex = "0" },
|
||||
.{ .src = "r3.width", .ex = "1" },
|
||||
.{ .src = "r3.height", .ex = "1" },
|
||||
|
||||
.{ .src = "document.getElementById('para').clientWidth", .ex = "2" },
|
||||
.{ .src = "document.getElementById('para').clientHeight", .ex = "1" },
|
||||
};
|
||||
try checkCases(js_env, &getBoundingClientRect);
|
||||
|
||||
try checkCases(js_env, &outerHTML);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ const pretty = @import("pretty");
|
||||
|
||||
const parser = @import("netsurf");
|
||||
const apiweb = @import("apiweb.zig");
|
||||
const browser = @import("browser/browser.zig");
|
||||
const Window = @import("html/window.zig").Window;
|
||||
const xhr = @import("xhr/xhr.zig");
|
||||
const storage = @import("storage/storage.zig");
|
||||
@@ -100,9 +101,14 @@ fn testExecFn(
|
||||
var cookie_jar = storage.CookieJar.init(alloc);
|
||||
defer cookie_jar.deinit();
|
||||
|
||||
var renderer = browser.Renderer.init(alloc);
|
||||
defer renderer.elements.deinit(alloc);
|
||||
defer renderer.positions.deinit(alloc);
|
||||
|
||||
try js_env.setUserContext(.{
|
||||
.uri = try std.Uri.parse(url),
|
||||
.document = doc,
|
||||
.renderer = &renderer,
|
||||
.cookie_jar = &cookie_jar,
|
||||
.http_client = &http_client,
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ const c = @cImport({
|
||||
@cInclude("dom/bindings/hubbub/parser.h");
|
||||
@cInclude("events/event_target.h");
|
||||
@cInclude("events/event.h");
|
||||
@cInclude("events/mouse_event.h");
|
||||
});
|
||||
|
||||
const mimalloc = @import("mimalloc");
|
||||
@@ -801,6 +802,11 @@ pub fn eventTargetDispatchEvent(et: *EventTarget, event: *Event) !bool {
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn elementDispatchEvent(element: *Element, event: *Event) !bool {
|
||||
const et: *EventTarget = toEventTarget(Element, element);
|
||||
return eventTargetDispatchEvent(et, @ptrCast(event));
|
||||
}
|
||||
|
||||
pub fn eventTargetTBaseFieldName(comptime T: type) ?[]const u8 {
|
||||
std.debug.assert(@inComptime());
|
||||
switch (@typeInfo(T)) {
|
||||
@@ -860,6 +866,61 @@ pub const EventTargetTBase = extern struct {
|
||||
}
|
||||
};
|
||||
|
||||
// MouseEvent
|
||||
|
||||
pub const MouseEvent = c.dom_mouse_event;
|
||||
|
||||
pub fn mouseEventCreate() !*MouseEvent {
|
||||
var evt: ?*MouseEvent = undefined;
|
||||
const err = c._dom_mouse_event_create(&evt);
|
||||
try DOMErr(err);
|
||||
return evt.?;
|
||||
}
|
||||
|
||||
pub fn mouseEventDestroy(evt: *MouseEvent) void {
|
||||
c._dom_mouse_event_destroy(evt);
|
||||
}
|
||||
|
||||
const MouseEventOpts = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
bubbles: bool = false,
|
||||
cancelable: bool = false,
|
||||
ctrl: bool = false,
|
||||
alt: bool = false,
|
||||
shift: bool = false,
|
||||
meta: bool = false,
|
||||
button: u16 = 0,
|
||||
click_count: u16 = 1,
|
||||
};
|
||||
|
||||
pub fn mouseEventInit(evt: *MouseEvent, typ: []const u8, opts: MouseEventOpts) !void {
|
||||
const s = try strFromData(typ);
|
||||
const err = c._dom_mouse_event_init(
|
||||
evt,
|
||||
s,
|
||||
opts.bubbles,
|
||||
opts.cancelable,
|
||||
null, // dom_abstract_view* ?
|
||||
opts.click_count, // details
|
||||
opts.x, // screen_x
|
||||
opts.y, // screen_y
|
||||
opts.x, // client_x
|
||||
opts.y, // client_y
|
||||
opts.ctrl,
|
||||
opts.alt,
|
||||
opts.shift,
|
||||
opts.meta,
|
||||
opts.button,
|
||||
null, // related target
|
||||
);
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn mouseEventDefaultPrevented(evt: *MouseEvent) !bool {
|
||||
return eventDefaultPrevented(@ptrCast(evt));
|
||||
}
|
||||
|
||||
// NodeType
|
||||
|
||||
pub const NodeType = enum(u4) {
|
||||
|
||||
@@ -2,10 +2,12 @@ const std = @import("std");
|
||||
const parser = @import("netsurf");
|
||||
const storage = @import("storage/storage.zig");
|
||||
const Client = @import("http/client.zig").Client;
|
||||
const Renderer = @import("browser/browser.zig").Renderer;
|
||||
|
||||
pub const UserContext = struct {
|
||||
http_client: *Client,
|
||||
uri: std.Uri,
|
||||
http_client: *Client,
|
||||
document: *parser.DocumentHTML,
|
||||
cookie_jar: *storage.CookieJar,
|
||||
renderer: *Renderer,
|
||||
};
|
||||
|
||||
2
vendor/zig-js-runtime
vendored
2
vendor/zig-js-runtime
vendored
Submodule vendor/zig-js-runtime updated: 64b9b2b0c9...6b48960a06
Reference in New Issue
Block a user