mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Compare commits
14 Commits
55e9d8d166
...
a4d290ba58
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4d290ba58 | ||
|
|
f69c0cc072 | ||
|
|
bfaf0fa00a | ||
|
|
02d9a670ff | ||
|
|
317916307f | ||
|
|
5ac40309cf | ||
|
|
882ed4d457 | ||
|
|
cb179794ae | ||
|
|
b5f0d017cc | ||
|
|
d579f21bf2 | ||
|
|
813e36f44e | ||
|
|
e0a912722b | ||
|
|
332b302285 | ||
|
|
b1e8268ce0 |
2
.github/actions/install/action.yml
vendored
2
.github/actions/install/action.yml
vendored
@@ -5,7 +5,7 @@ inputs:
|
||||
zig:
|
||||
description: 'Zig version to install'
|
||||
required: false
|
||||
default: '0.15.2'
|
||||
default: '0.15.1'
|
||||
arch:
|
||||
description: 'CPU arch used to select the v8 lib'
|
||||
required: false
|
||||
|
||||
2
.github/workflows/zig-fmt.yml
vendored
2
.github/workflows/zig-fmt.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: zig-fmt
|
||||
|
||||
env:
|
||||
ZIG_VERSION: 0.15.2
|
||||
ZIG_VERSION: 0.15.1
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM debian:stable
|
||||
|
||||
ARG MINISIG=0.12
|
||||
ARG ZIG=0.15.2
|
||||
ARG ZIG=0.15.1
|
||||
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
|
||||
ARG V8=14.0.365.4
|
||||
ARG ZIG_V8=v0.1.33
|
||||
|
||||
11
Makefile
11
Makefile
@@ -96,16 +96,9 @@ wpt-summary:
|
||||
@printf "\e[36mBuilding wpt...\e[0m\n"
|
||||
@$(ZIG) build wpt -- --summary $(filter-out $@,$(MAKECMDGOALS)) || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
|
||||
|
||||
## Test - `grep` is used to filter out the huge compile command on build
|
||||
ifeq ($(OS), macos)
|
||||
## Test
|
||||
test:
|
||||
@script -q /dev/null sh -c 'TEST_FILTER="${F}" $(ZIG) build test -freference-trace --summary all' 2>&1 \
|
||||
| grep --line-buffered -v "^/.*zig test -freference-trace"
|
||||
else
|
||||
test:
|
||||
@script -qec 'TEST_FILTER="${F}" $(ZIG) build test -freference-trace --summary all' /dev/null 2>&1 \
|
||||
| grep --line-buffered -v "^/.*zig test -freference-trace"
|
||||
endif
|
||||
@TEST_FILTER='${F}' $(ZIG) build test -freference-trace --summary all
|
||||
|
||||
## Run demo/runner end to end tests
|
||||
end2end:
|
||||
|
||||
@@ -164,7 +164,7 @@ You can also follow the progress of our Javascript support in our dedicated [zig
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Lightpanda is written with [Zig](https://ziglang.org/) `0.15.2`. You have to
|
||||
Lightpanda is written with [Zig](https://ziglang.org/) `0.15.1`. You have to
|
||||
install it with the right version in order to build the project.
|
||||
|
||||
Lightpanda also depends on
|
||||
|
||||
@@ -23,7 +23,7 @@ const Build = std.Build;
|
||||
|
||||
/// Do not rename this constant. It is scanned by some scripts to determine
|
||||
/// which zig version to install.
|
||||
const recommended_zig_version = "0.15.2";
|
||||
const recommended_zig_version = "0.15.1";
|
||||
|
||||
pub fn build(b: *Build) !void {
|
||||
switch (comptime builtin.zig_version.order(std.SemanticVersion.parse(recommended_zig_version) catch unreachable)) {
|
||||
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -75,11 +75,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1760968520,
|
||||
"narHash": "sha256-EjGslHDzCBKOVr+dnDB1CAD7wiQSHfUt3suOpFj9O1Q=",
|
||||
"lastModified": 1756822655,
|
||||
"narHash": "sha256-xQAk8xLy7srAkR5NMZFsQFioL02iTHuuEIs3ohGpgdk=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e755547441a0413942a37692f7bf7fc6315bb7f6",
|
||||
"rev": "4bdac60bfe32c41103ae500ddf894c258291dd61",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -136,11 +136,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1760747435,
|
||||
"narHash": "sha256-wNB/W3x+or4mdNxFPNOH5/WFckNpKgFRZk7OnOsLtm0=",
|
||||
"lastModified": 1756555914,
|
||||
"narHash": "sha256-7yoSPIVEuL+3Wzf6e7NHuW3zmruHizRrYhGerjRHTLI=",
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"rev": "d0f239b887b1ac736c0f3dde91bf5bf2ecf3a420",
|
||||
"rev": "d0df3a2fd0f11134409d6d5ea0e510e5e477f7d6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
targetPkgs =
|
||||
pkgs: with pkgs; [
|
||||
# Build Tools
|
||||
zigpkgs."0.15.2"
|
||||
zigpkgs."0.15.1"
|
||||
zls
|
||||
python3
|
||||
pkg-config
|
||||
|
||||
@@ -212,7 +212,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element, comptime c
|
||||
if (source == .@"inline" and self.scripts.first == null) {
|
||||
// inline script with no pending scripts, execute it immediately.
|
||||
// (if there is a pending script, then we cannot execute this immediately
|
||||
// as it needs to be executed in order)
|
||||
// as it needs to best executed in order)
|
||||
return script.eval(page);
|
||||
}
|
||||
|
||||
@@ -326,31 +326,16 @@ pub fn waitForModule(self: *ScriptManager, url: [:0]const u8) !GetResult {
|
||||
};
|
||||
const sync = entry.value_ptr.*;
|
||||
|
||||
// We can have multiple scripts waiting for the same module in concurrency.
|
||||
// We use the waiters to ensures only the last waiter deinit the resources.
|
||||
sync.waiters += 1;
|
||||
defer sync.waiters -= 1;
|
||||
|
||||
var client = self.client;
|
||||
while (true) {
|
||||
switch (sync.state) {
|
||||
.loading => {},
|
||||
.done => {
|
||||
if (sync.waiters == 1) {
|
||||
// Our caller has its own higher level cache (caching the
|
||||
// actual compiled module). There's no reason for us to keep
|
||||
// this if we are the last waiter.
|
||||
defer self.sync_module_pool.destroy(sync);
|
||||
defer self.sync_modules.removeByPtr(entry.key_ptr);
|
||||
return .{
|
||||
.shared = false,
|
||||
.buffer = sync.buffer,
|
||||
.buffer_pool = &self.buffer_pool,
|
||||
};
|
||||
}
|
||||
|
||||
// Our caller has its own higher level cache (caching the
|
||||
// actual compiled module). There's no reason for us to keep this
|
||||
defer self.sync_module_pool.destroy(sync);
|
||||
defer self.sync_modules.removeByPtr(entry.key_ptr);
|
||||
return .{
|
||||
.shared = true,
|
||||
.buffer = sync.buffer,
|
||||
.buffer_pool = &self.buffer_pool,
|
||||
};
|
||||
@@ -897,8 +882,6 @@ const SyncModule = struct {
|
||||
manager: *ScriptManager,
|
||||
buffer: std.ArrayListUnmanaged(u8) = .{},
|
||||
state: State = .loading,
|
||||
// number of waiters for the module.
|
||||
waiters: u8 = 0,
|
||||
|
||||
const State = union(enum) {
|
||||
done,
|
||||
@@ -1014,7 +997,6 @@ pub const AsyncModule = struct {
|
||||
var self: *AsyncModule = @ptrCast(@alignCast(ctx));
|
||||
defer self.manager.async_module_pool.destroy(self);
|
||||
self.cb(self.cb_data, .{
|
||||
.shared = false,
|
||||
.buffer = self.buffer,
|
||||
.buffer_pool = &self.manager.buffer_pool,
|
||||
});
|
||||
@@ -1038,13 +1020,8 @@ pub const AsyncModule = struct {
|
||||
pub const GetResult = struct {
|
||||
buffer: std.ArrayListUnmanaged(u8),
|
||||
buffer_pool: *BufferPool,
|
||||
shared: bool,
|
||||
|
||||
pub fn deinit(self: *GetResult) void {
|
||||
// if the result is shared, don't deinit.
|
||||
if (self.shared) {
|
||||
return;
|
||||
}
|
||||
self.buffer_pool.release(self.buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -562,7 +562,7 @@ pub const Selector = union(enum) {
|
||||
|
||||
const ntag = try n.tag();
|
||||
|
||||
if (std.ascii.eqlIgnoreCase("input", ntag)) {
|
||||
if (std.ascii.eqlIgnoreCase("intput", ntag)) {
|
||||
const ntype = try n.attr("type");
|
||||
if (ntype == null) return false;
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const log = @import("../../log.zig");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const parser = @import("../netsurf.zig");
|
||||
@@ -314,11 +313,6 @@ pub const Document = struct {
|
||||
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
|
||||
state.adopted_style_sheets = try sheets.persist();
|
||||
}
|
||||
|
||||
pub fn _hasFocus(_: *parser.Document) bool {
|
||||
log.debug(.web_api, "not implemented", .{ .feature = "Document hasFocus" });
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
|
||||
@@ -286,7 +286,7 @@ const Opts = struct {
|
||||
|
||||
// WEB IDL https://dom.spec.whatwg.org/#htmlcollection
|
||||
// HTMLCollection is re implemented in zig here because libdom
|
||||
// dom_html_collection expects a comparison function callback as argument.
|
||||
// dom_html_collection expects a comparison function callback as arguement.
|
||||
// But we wanted a dynamically comparison here, according to the match tagname.
|
||||
pub const HTMLCollection = struct {
|
||||
matcher: Matcher,
|
||||
|
||||
@@ -360,30 +360,18 @@ pub const Node = struct {
|
||||
node: Union,
|
||||
};
|
||||
pub fn _getRootNode(self: *parser.Node, options: ?struct { composed: bool = false }, page: *Page) !GetRootNodeResult {
|
||||
const composed = if (options) |opts| opts.composed else false;
|
||||
if (options) |options_| if (options_.composed) {
|
||||
log.warn(.web_api, "not implemented", .{ .feature = "getRootNode composed" });
|
||||
};
|
||||
|
||||
var current_root = parser.nodeGetRootNode(self);
|
||||
|
||||
while (true) {
|
||||
const node_type = parser.nodeType(current_root);
|
||||
|
||||
if (node_type == .document_fragment) {
|
||||
if (parser.documentFragmentGetHost(@ptrCast(current_root))) |host| {
|
||||
if (page.getNodeState(host)) |state| {
|
||||
if (state.shadow_root) |sr| {
|
||||
if (!composed) {
|
||||
return .{ .shadow_root = sr };
|
||||
}
|
||||
current_root = parser.nodeGetRootNode(@ptrCast(sr.host));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
const root = parser.nodeGetRootNode(self);
|
||||
if (page.getNodeState(root)) |state| {
|
||||
if (state.shadow_root) |sr| {
|
||||
return .{ .shadow_root = sr };
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return .{ .node = try Node.toInterface(current_root) };
|
||||
return .{ .node = try Node.toInterface(root) };
|
||||
}
|
||||
|
||||
pub fn _hasChildNodes(self: *parser.Node) bool {
|
||||
@@ -473,7 +461,7 @@ pub const Node = struct {
|
||||
|
||||
// Check if the hierarchy node tree constraints are respected.
|
||||
// For now, it checks only if new nodes are not self.
|
||||
// TODO implements the others constraints.
|
||||
// TODO implements the others contraints.
|
||||
// see https://dom.spec.whatwg.org/#concept-node-tree
|
||||
pub fn hierarchy(self: *parser.Node, nodes: []const NodeOrText) bool {
|
||||
for (nodes) |n| {
|
||||
|
||||
@@ -106,6 +106,7 @@ pub const NodeIterator = struct {
|
||||
defer self.callbackEnd();
|
||||
|
||||
if (self.pointer_before_current) {
|
||||
self.pointer_before_current = false;
|
||||
// Unlike TreeWalker, NodeIterator starts at the first node
|
||||
if (.accept == try NodeFilter.verify(self.what_to_show, self.filter_func, self.reference_node)) {
|
||||
self.pointer_before_current = false;
|
||||
@@ -115,7 +116,6 @@ pub const NodeIterator = struct {
|
||||
|
||||
if (try self.firstChild(self.reference_node)) |child| {
|
||||
self.reference_node = child;
|
||||
self.pointer_before_current = false;
|
||||
return try Node.toInterface(child);
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ test "Performance: now" {
|
||||
}
|
||||
|
||||
var after = perf._now();
|
||||
while (after <= now) { // Loop until after > now
|
||||
while (after <= now) { // Loop untill after > now
|
||||
try testing.expectEqual(after, now);
|
||||
after = perf._now();
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ pub const Range = struct {
|
||||
pub fn _setStart(self: *Range, node: *parser.Node, offset_: i32) !void {
|
||||
try ensureValidOffset(node, offset_);
|
||||
const offset: u32 = @intCast(offset_);
|
||||
const position = compare(node, offset, self.proto.end_node, self.proto.end_offset) catch |err| switch (err) {
|
||||
const position = compare(node, offset, self.proto.start_node, self.proto.start_offset) catch |err| switch (err) {
|
||||
error.WrongDocument => blk: {
|
||||
// allow a node with a different root than the current, or
|
||||
// a disconnected one. Treat it as if it's "after", so that
|
||||
@@ -103,7 +103,7 @@ pub const Range = struct {
|
||||
};
|
||||
|
||||
if (position == 1) {
|
||||
// if we're setting the node after the current end, the end must
|
||||
// if we're setting the node after the current start, the end must
|
||||
// be set too.
|
||||
self.proto.end_offset = offset;
|
||||
self.proto.end_node = node;
|
||||
@@ -378,7 +378,7 @@ fn compare(node_a: *parser.Node, offset_a: u32, node_b: *parser.Node, offset_b:
|
||||
|
||||
const child_parent, const child_index = try getParentAndIndex(child);
|
||||
std.debug.assert(node_a == child_parent);
|
||||
return if (offset_a <= child_index) -1 else 1;
|
||||
return if (child_index < offset_a) -1 else 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
@@ -227,6 +227,7 @@ pub const TreeWalker = struct {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
if (!result.should_descend) {
|
||||
// This is an .accept node - return it
|
||||
self.current_node = result.node;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright (C) 2023-2025 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 parser = @import("../netsurf.zig");
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
|
||||
pub const CompositionEvent = struct {
|
||||
data: []const u8,
|
||||
proto: parser.Event,
|
||||
|
||||
pub const union_make_copy = true;
|
||||
pub const prototype = *parser.Event;
|
||||
|
||||
pub const ConstructorOptions = struct {
|
||||
data: []const u8 = "",
|
||||
};
|
||||
|
||||
pub fn constructor(event_type: []const u8, options_: ?ConstructorOptions) !CompositionEvent {
|
||||
const options: ConstructorOptions = options_ orelse .{};
|
||||
|
||||
const event = try parser.eventCreate();
|
||||
defer parser.eventDestroy(event);
|
||||
try parser.eventInit(event, event_type, .{});
|
||||
parser.eventSetInternalType(event, .composition_event);
|
||||
|
||||
return .{
|
||||
.proto = event.*,
|
||||
.data = options.data,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_data(self: *const CompositionEvent) []const u8 {
|
||||
return self.data;
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "Browser: Events.Composition" {
|
||||
try testing.htmlRunner("events/composition.html");
|
||||
}
|
||||
@@ -38,7 +38,6 @@ const KeyboardEvent = @import("keyboard_event.zig").KeyboardEvent;
|
||||
const ErrorEvent = @import("../html/error_event.zig").ErrorEvent;
|
||||
const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent;
|
||||
const PopStateEvent = @import("../html/History.zig").PopStateEvent;
|
||||
const CompositionEvent = @import("composition_event.zig").CompositionEvent;
|
||||
const NavigationCurrentEntryChangeEvent = @import("../navigation/navigation.zig").NavigationCurrentEntryChangeEvent;
|
||||
|
||||
// Event interfaces
|
||||
@@ -51,7 +50,6 @@ pub const Interfaces = .{
|
||||
ErrorEvent,
|
||||
MessageEvent,
|
||||
PopStateEvent,
|
||||
CompositionEvent,
|
||||
NavigationCurrentEntryChangeEvent,
|
||||
};
|
||||
|
||||
@@ -77,11 +75,10 @@ pub const Event = struct {
|
||||
.custom_event => .{ .CustomEvent = @as(*CustomEvent, @ptrCast(evt)).* },
|
||||
.progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* },
|
||||
.mouse_event => .{ .MouseEvent = @as(*parser.MouseEvent, @ptrCast(evt)) },
|
||||
.error_event => .{ .ErrorEvent = (@as(*ErrorEvent, @fieldParentPtr("proto", evt))).* },
|
||||
.error_event => .{ .ErrorEvent = @as(*ErrorEvent, @ptrCast(evt)).* },
|
||||
.message_event => .{ .MessageEvent = @as(*MessageEvent, @ptrCast(evt)).* },
|
||||
.keyboard_event => .{ .KeyboardEvent = @as(*parser.KeyboardEvent, @ptrCast(evt)) },
|
||||
.pop_state => .{ .PopStateEvent = @as(*PopStateEvent, @ptrCast(evt)).* },
|
||||
.composition_event => .{ .CompositionEvent = (@as(*CompositionEvent, @fieldParentPtr("proto", evt))).* },
|
||||
.navigation_current_entry_change_event => .{
|
||||
.NavigationCurrentEntryChangeEvent = @as(*NavigationCurrentEntryChangeEvent, @ptrCast(evt)).*,
|
||||
},
|
||||
|
||||
@@ -732,9 +732,6 @@ pub const HTMLInputElement = struct {
|
||||
pub fn set_value(self: *parser.Input, value: []const u8) !void {
|
||||
try parser.inputSetValue(self, value);
|
||||
}
|
||||
pub fn _select(_: *parser.Input) void {
|
||||
log.debug(.web_api, "not implemented", .{ .feature = "HTMLInputElement select" });
|
||||
}
|
||||
};
|
||||
|
||||
pub const HTMLLIElement = struct {
|
||||
|
||||
@@ -42,7 +42,7 @@ pub const ErrorEvent = struct {
|
||||
const event = try parser.eventCreate();
|
||||
defer parser.eventDestroy(event);
|
||||
try parser.eventInit(event, event_type, .{});
|
||||
parser.eventSetInternalType(event, .error_event);
|
||||
parser.eventSetInternalType(event, .event);
|
||||
|
||||
const o = opts orelse ErrorEventInit{};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub const SVGElement = struct {
|
||||
// Currently the prototype chain is not implemented (will not be returned by toInterface())
|
||||
// For that we need parser.SvgElement and the derived types with tags in the v-table.
|
||||
pub const prototype = *Element;
|
||||
// While this is a Node, could consider not exposing the subtype until we have
|
||||
// While this is a Node, could consider not exposing the subtype untill we have
|
||||
// a Self type to cast to.
|
||||
pub const subtype = .node;
|
||||
};
|
||||
|
||||
@@ -42,7 +42,6 @@ const Request = @import("../fetch/Request.zig");
|
||||
const fetchFn = @import("../fetch/fetch.zig").fetch;
|
||||
|
||||
const storage = @import("../storage/storage.zig");
|
||||
const ErrorEvent = @import("error_event.zig").ErrorEvent;
|
||||
|
||||
const DirectEventHandler = @import("../events/event.zig").DirectEventHandler;
|
||||
|
||||
@@ -276,25 +275,6 @@ pub const Window = struct {
|
||||
return out;
|
||||
}
|
||||
|
||||
pub fn _reportError(self: *Window, err: js.Object, page: *Page) !void {
|
||||
var error_event = try ErrorEvent.constructor("error", .{
|
||||
.@"error" = err,
|
||||
});
|
||||
_ = try parser.eventTargetDispatchEvent(
|
||||
parser.toEventTarget(Window, self),
|
||||
@as(*parser.Event, &error_event.proto),
|
||||
);
|
||||
|
||||
if (parser.eventDefaultPrevented(&error_event.proto) == false) {
|
||||
const err_string = err.toString() catch "Unknown error";
|
||||
log.info(.user_script, "error", .{
|
||||
.err = err_string,
|
||||
.stack = page.stackTrace() catch "???",
|
||||
.source = "window.reportError",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const CreateTimeoutOpts = struct {
|
||||
name: []const u8,
|
||||
args: []js.Object = &.{},
|
||||
|
||||
@@ -127,10 +127,10 @@ pub fn processNavigation(self: *Navigation, page: *Page) !void {
|
||||
|
||||
/// Pushes an entry into the Navigation stack WITHOUT actually navigating to it.
|
||||
/// For that, use `navigate`.
|
||||
pub fn pushEntry(self: *Navigation, _url: []const u8, state: ?[]const u8, page: *Page, dispatch: bool) !*NavigationHistoryEntry {
|
||||
pub fn pushEntry(self: *Navigation, _url: ?[]const u8, state: ?[]const u8, page: *Page, dispatch: bool) !*NavigationHistoryEntry {
|
||||
const arena = page.session.arena;
|
||||
|
||||
const url = try arena.dupe(u8, _url);
|
||||
const url = if (_url) |u| try arena.dupe(u8, u) else null;
|
||||
|
||||
// truncates our history here.
|
||||
if (self.entries.items.len > self.index + 1) {
|
||||
@@ -267,8 +267,8 @@ pub const TraverseToOptions = struct {
|
||||
info: ?js.Object = null,
|
||||
};
|
||||
|
||||
pub fn _traverseTo(self: *Navigation, key: []const u8, _opts: ?TraverseToOptions, page: *Page) !NavigationReturn {
|
||||
log.debug(.browser, "not implemented", .{ .options = _opts });
|
||||
pub fn _traverseTo(self: *Navigation, key: []const u8, _: ?TraverseToOptions, page: *Page) !NavigationReturn {
|
||||
// const opts = _opts orelse TraverseToOptions{};
|
||||
|
||||
for (self.entries.items, 0..) |entry, i| {
|
||||
if (std.mem.eql(u8, key, entry.key)) {
|
||||
|
||||
@@ -22,7 +22,7 @@ fn register(
|
||||
typ: []const u8,
|
||||
listener: EventHandler.Listener,
|
||||
) !?js.Function {
|
||||
const target = parser.toEventTarget(NavigationEventTarget, self);
|
||||
const target = @as(*parser.EventTarget, @ptrCast(self));
|
||||
|
||||
// The only time this can return null if the listener is already
|
||||
// registered. But before calling `register`, all of our functions
|
||||
@@ -33,7 +33,7 @@ fn register(
|
||||
}
|
||||
|
||||
fn unregister(self: *NavigationEventTarget, typ: []const u8, cbk_id: usize) !void {
|
||||
const et = parser.toEventTarget(NavigationEventTarget, self);
|
||||
const et = @as(*parser.EventTarget, @ptrCast(self));
|
||||
// check if event target has already this listener
|
||||
const lst = try parser.eventTargetHasListener(et, typ, false, cbk_id);
|
||||
if (lst == null) {
|
||||
|
||||
@@ -559,8 +559,7 @@ pub const EventType = enum(u8) {
|
||||
message_event = 7,
|
||||
keyboard_event = 8,
|
||||
pop_state = 9,
|
||||
composition_event = 10,
|
||||
navigation_current_entry_change_event = 11,
|
||||
navigation_current_entry_change_event = 10,
|
||||
};
|
||||
|
||||
pub const MutationEvent = c.dom_mutation_event;
|
||||
|
||||
@@ -1016,31 +1016,6 @@ pub const Page = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// insertText is a shortcut to insert text into the active element.
|
||||
pub fn insertText(self: *Page, v: []const u8) !void {
|
||||
const Document = @import("dom/document.zig").Document;
|
||||
const element = (try Document.getActiveElement(@ptrCast(self.window.document), self)) orelse return;
|
||||
const node = parser.elementToNode(element);
|
||||
|
||||
const tag = (try parser.nodeHTMLGetTagType(node)) orelse return;
|
||||
switch (tag) {
|
||||
.input => {
|
||||
const input_type = try parser.inputGetType(@ptrCast(element));
|
||||
if (std.mem.eql(u8, input_type, "text")) {
|
||||
const value = try parser.inputGetValue(@ptrCast(element));
|
||||
const new_value = try std.mem.concat(self.arena, u8, &.{ value, v });
|
||||
try parser.inputSetValue(@ptrCast(element), new_value);
|
||||
}
|
||||
},
|
||||
.textarea => {
|
||||
const value = try parser.textareaGetValue(@ptrCast(node));
|
||||
const new_value = try std.mem.concat(self.arena, u8, &.{ value, v });
|
||||
try parser.textareaSetValue(@ptrCast(node), new_value);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot navigate immediately as navigating will delete the DOM tree,
|
||||
// which holds this event's node.
|
||||
// As such we schedule the function to be called as soon as possible.
|
||||
|
||||
@@ -702,7 +702,7 @@ const IsolatedWorld = struct {
|
||||
// The isolate world must share at least some of the state with the related page, specifically the DocumentHTML
|
||||
// (assuming grantUniveralAccess will be set to True!).
|
||||
// We just created the world and the page. The page's state lives in the session, but is update on navigation.
|
||||
// This also means this pointer becomes invalid after removePage until a new page is created.
|
||||
// This also means this pointer becomes invalid after removePage untill a new page is created.
|
||||
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
|
||||
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
|
||||
// if (self.executor.context != null) return error.Only1IsolatedContextSupported;
|
||||
|
||||
@@ -23,13 +23,11 @@ pub fn processMessage(cmd: anytype) !void {
|
||||
const action = std.meta.stringToEnum(enum {
|
||||
dispatchKeyEvent,
|
||||
dispatchMouseEvent,
|
||||
insertText,
|
||||
}, cmd.input.action) orelse return error.UnknownMethod;
|
||||
|
||||
switch (action) {
|
||||
.dispatchKeyEvent => return dispatchKeyEvent(cmd),
|
||||
.dispatchMouseEvent => return dispatchMouseEvent(cmd),
|
||||
.insertText => return insertText(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,20 +115,6 @@ fn dispatchMouseEvent(cmd: anytype) !void {
|
||||
// result already sent
|
||||
}
|
||||
|
||||
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-insertText
|
||||
fn insertText(cmd: anytype) !void {
|
||||
const params = (try cmd.params(struct {
|
||||
text: []const u8, // The text to insert
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
const bc = cmd.browser_context orelse return;
|
||||
const page = bc.session.currentPage() orelse return;
|
||||
|
||||
try page.insertText(params.text);
|
||||
|
||||
try cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
fn clickNavigate(cmd: anytype, uri: std.Uri) !void {
|
||||
const bc = cmd.browser_context.?;
|
||||
|
||||
|
||||
@@ -21,48 +21,9 @@ const std = @import("std");
|
||||
pub fn processMessage(cmd: anytype) !void {
|
||||
const action = std.meta.stringToEnum(enum {
|
||||
enable,
|
||||
setIgnoreCertificateErrors,
|
||||
}, cmd.input.action) orelse return error.UnknownMethod;
|
||||
|
||||
switch (action) {
|
||||
.enable => return cmd.sendResult(null, .{}),
|
||||
.setIgnoreCertificateErrors => return setIgnoreCertificateErrors(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
fn setIgnoreCertificateErrors(cmd: anytype) !void {
|
||||
const params = (try cmd.params(struct {
|
||||
ignore: bool,
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
if (params.ignore) {
|
||||
try cmd.cdp.browser.http_client.disableTlsVerify();
|
||||
} else {
|
||||
try cmd.cdp.browser.http_client.enableTlsVerify();
|
||||
}
|
||||
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
test "cdp.Security: setIgnoreCertificateErrors" {
|
||||
var ctx = testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 8,
|
||||
.method = "Security.setIgnoreCertificateErrors",
|
||||
.params = .{ .ignore = true },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 8 });
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 9,
|
||||
.method = "Security.setIgnoreCertificateErrors",
|
||||
.params = .{ .ignore = false },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 9 });
|
||||
}
|
||||
|
||||
@@ -93,11 +93,6 @@ notification: ?*Notification = null,
|
||||
// restoring, this originally-configured value is what it goes to.
|
||||
http_proxy: ?[:0]const u8 = null,
|
||||
|
||||
// track if the client use a proxy for connections.
|
||||
// We can't use http_proxy because we want also to track proxy configured via
|
||||
// CDP.
|
||||
use_proxy: bool,
|
||||
|
||||
// The complete user-agent header line
|
||||
user_agent: [:0]const u8,
|
||||
|
||||
@@ -131,7 +126,6 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie
|
||||
.handles = handles,
|
||||
.allocator = allocator,
|
||||
.http_proxy = opts.http_proxy,
|
||||
.use_proxy = opts.http_proxy != null,
|
||||
.user_agent = opts.user_agent,
|
||||
.transfer_pool = transfer_pool,
|
||||
};
|
||||
@@ -321,7 +315,6 @@ pub fn changeProxy(self: *Client, proxy: [:0]const u8) !void {
|
||||
for (self.handles.handles) |*h| {
|
||||
try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy.ptr));
|
||||
}
|
||||
self.use_proxy = true;
|
||||
}
|
||||
|
||||
// Same restriction as changeProxy. Should be ok since this is only called on
|
||||
@@ -333,41 +326,6 @@ pub fn restoreOriginalProxy(self: *Client) !void {
|
||||
for (self.handles.handles) |*h| {
|
||||
try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy));
|
||||
}
|
||||
self.use_proxy = proxy != null;
|
||||
}
|
||||
|
||||
// Enable TLS verification on all connections.
|
||||
pub fn enableTlsVerify(self: *const Client) !void {
|
||||
try self.ensureNoActiveConnection();
|
||||
|
||||
for (self.handles.handles) |*h| {
|
||||
const easy = h.conn.easy;
|
||||
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYHOST, @as(c_long, 2)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYPEER, @as(c_long, 1)));
|
||||
|
||||
if (self.use_proxy) {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_SSL_VERIFYHOST, @as(c_long, 2)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_SSL_VERIFYPEER, @as(c_long, 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable TLS verification on all connections.
|
||||
pub fn disableTlsVerify(self: *const Client) !void {
|
||||
try self.ensureNoActiveConnection();
|
||||
|
||||
for (self.handles.handles) |*h| {
|
||||
const easy = h.conn.easy;
|
||||
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYHOST, @as(c_long, 0)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYPEER, @as(c_long, 0)));
|
||||
|
||||
if (self.use_proxy) {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_SSL_VERIFYHOST, @as(c_long, 0)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_SSL_VERIFYPEER, @as(c_long, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void {
|
||||
@@ -850,7 +808,7 @@ pub const Transfer = struct {
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
// abortAuthChallenge is called when an auth challenge interception is
|
||||
// abortAuthChallenge is called when an auth chanllenge interception is
|
||||
// abort. We don't call self.client.endTransfer here b/c it has been done
|
||||
// before interception process.
|
||||
pub fn abortAuthChallenge(self: *Transfer) void {
|
||||
|
||||
@@ -487,7 +487,7 @@ pub const Client = struct {
|
||||
}
|
||||
|
||||
// called by CDP
|
||||
// Websocket frames have a variable length header. For server-client,
|
||||
// Websocket frames have a variable lenght header. For server-client,
|
||||
// it could be anywhere from 2 to 10 bytes. Our IO.Loop doesn't have
|
||||
// writev, so we need to get creative. We'll JSON serialize to a
|
||||
// buffer, where the first 10 bytes are reserved. We can then backfill
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<p id="para"> And</p>
|
||||
<!--comment-->
|
||||
</div>
|
||||
<div id="rootNodeComposed"></div>
|
||||
</body>
|
||||
|
||||
<script src="../testing.js"></script>
|
||||
@@ -37,26 +36,6 @@ let first_child = content.firstChild.nextSibling; // nextSibling because of line
|
||||
testing.expectEqual('HTMLDocument', content.getRootNode().__proto__.constructor.name);
|
||||
</script>
|
||||
|
||||
<script id=getRootNodeComposed>
|
||||
const testContainer = $('#rootNodeComposed');
|
||||
const shadowHost = document.createElement('div');
|
||||
testContainer.appendChild(shadowHost);
|
||||
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
|
||||
const shadowChild = document.createElement('span');
|
||||
shadowRoot.appendChild(shadowChild);
|
||||
|
||||
testing.expectEqual('ShadowRoot', shadowChild.getRootNode().__proto__.constructor.name);
|
||||
testing.expectEqual('ShadowRoot', shadowChild.getRootNode({ composed: false }).__proto__.constructor.name);
|
||||
testing.expectEqual('HTMLDocument', shadowChild.getRootNode({ composed: true }).__proto__.constructor.name);
|
||||
testing.expectEqual('HTMLDocument', shadowHost.getRootNode().__proto__.constructor.name);
|
||||
|
||||
const disconnected = document.createElement('div');
|
||||
const disconnectedChild = document.createElement('span');
|
||||
disconnected.appendChild(disconnectedChild);
|
||||
testing.expectEqual('HTMLDivElement', disconnectedChild.getRootNode().__proto__.constructor.name);
|
||||
testing.expectEqual('HTMLDivElement', disconnectedChild.getRootNode({ composed: true }).__proto__.constructor.name);
|
||||
</script>
|
||||
|
||||
<script id=firstChild>
|
||||
let body_first_child = document.body.firstChild;
|
||||
testing.expectEqual('div', body_first_child.localName);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=noNata>
|
||||
{
|
||||
let event = new CompositionEvent("test", {});
|
||||
testing.expectEqual(true, event instanceof CompositionEvent);
|
||||
testing.expectEqual(true, event instanceof Event);
|
||||
|
||||
testing.expectEqual("test", event.type);
|
||||
testing.expectEqual("", event.data);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=withData>
|
||||
{
|
||||
let event = new CompositionEvent("test2", {data: "over 9000!"});
|
||||
testing.expectEqual("test2", event.type);
|
||||
testing.expectEqual("over 9000!", event.data);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=dispatch>
|
||||
{
|
||||
let called = 0;
|
||||
document.addEventListener('CE', (e) => {
|
||||
testing.expectEqual('test-data', e.data);
|
||||
testing.expectEqual(true, e instanceof CompositionEvent);
|
||||
called += 1
|
||||
});
|
||||
|
||||
document.dispatchEvent(new CompositionEvent('CE', {data: 'test-data'}));
|
||||
testing.expectEqual(1, called);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -149,19 +149,3 @@
|
||||
|
||||
testing.eventually(() => testing.expectEqual(true, isWindowTarget));
|
||||
</script>
|
||||
|
||||
<script id=reportError>
|
||||
let errorEventFired = false;
|
||||
let capturedError = null;
|
||||
|
||||
window.addEventListener('error', (e) => {
|
||||
errorEventFired = true;
|
||||
capturedError = e.error;
|
||||
});
|
||||
|
||||
const testError = new Error('Test error message');
|
||||
window.reportError(testError);
|
||||
|
||||
testing.expectEqual(true, errorEventFired);
|
||||
testing.expectEqual(testError, capturedError);
|
||||
</script>
|
||||
|
||||
40
src/url.zig
40
src/url.zig
@@ -227,14 +227,6 @@ pub const URL = struct {
|
||||
const path1 = try self.uri.path.toRawMaybeAlloc(arena);
|
||||
const path2 = try other.uri.path.toRawMaybeAlloc(arena);
|
||||
|
||||
if ((self.uri.query == null) != (other.uri.query == null)) return false;
|
||||
if (self.uri.query) |self_query| {
|
||||
const other_query = other.uri.query.?;
|
||||
const query1 = try self_query.toRawMaybeAlloc(arena);
|
||||
const query2 = try other_query.toRawMaybeAlloc(arena);
|
||||
if (!std.mem.eql(u8, query1, query2)) return false;
|
||||
}
|
||||
|
||||
return std.mem.eql(u8, path1, path2);
|
||||
}
|
||||
};
|
||||
@@ -611,7 +603,7 @@ test "URL: eqlDocument" {
|
||||
{
|
||||
const url1 = try URL.parse("https://lightpanda.io/about?foo=bar", null);
|
||||
const url2 = try URL.parse("https://lightpanda.io/about?baz=qux", null);
|
||||
try testing.expectEqual(false, try url1.eqlDocument(&url2, arena));
|
||||
try testing.expectEqual(true, try url1.eqlDocument(&url2, arena));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -631,34 +623,4 @@ test "URL: eqlDocument" {
|
||||
const url2 = try URL.parse("https://lightpanda.io", null);
|
||||
try testing.expectEqual(false, try url1.eqlDocument(&url2, arena));
|
||||
}
|
||||
|
||||
{
|
||||
const url1 = try URL.parse("https://lightpanda.io/about?foo=bar", null);
|
||||
const url2 = try URL.parse("https://lightpanda.io/about", null);
|
||||
try testing.expectEqual(false, try url1.eqlDocument(&url2, arena));
|
||||
}
|
||||
|
||||
{
|
||||
const url1 = try URL.parse("https://lightpanda.io/about", null);
|
||||
const url2 = try URL.parse("https://lightpanda.io/about?foo=bar", null);
|
||||
try testing.expectEqual(false, try url1.eqlDocument(&url2, arena));
|
||||
}
|
||||
|
||||
{
|
||||
const url1 = try URL.parse("https://lightpanda.io/about?foo=bar", null);
|
||||
const url2 = try URL.parse("https://lightpanda.io/about?foo=bar", null);
|
||||
try testing.expectEqual(true, try url1.eqlDocument(&url2, arena));
|
||||
}
|
||||
|
||||
{
|
||||
const url1 = try URL.parse("https://lightpanda.io/about?", null);
|
||||
const url2 = try URL.parse("https://lightpanda.io/about", null);
|
||||
try testing.expectEqual(false, try url1.eqlDocument(&url2, arena));
|
||||
}
|
||||
|
||||
{
|
||||
const url1 = try URL.parse("https://duckduckgo.com/", null);
|
||||
const url2 = try URL.parse("https://duckduckgo.com/?q=lightpanda", null);
|
||||
try testing.expectEqual(false, try url1.eqlDocument(&url2, arena));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user