14 Commits

Author SHA1 Message Date
Muki Kiboigo
a4d290ba58 fix navigation shortcut URL stitching 2025-10-17 07:57:10 -07:00
Muki Kiboigo
f69c0cc072 clean up Navigation test names 2025-10-17 01:27:53 -07:00
Muki Kiboigo
bfaf0fa00a fix NavigationCurrentEntryChange Constructor 2025-10-17 01:27:53 -07:00
Muki Kiboigo
02d9a670ff functional NavigationCurrentEntryChangeEvent 2025-10-17 01:27:53 -07:00
Muki Kiboigo
317916307f add direct event handlers 2025-10-17 01:27:52 -07:00
Muki Kiboigo
5ac40309cf add tests for eqlDocument 2025-10-17 01:27:52 -07:00
Muki Kiboigo
882ed4d457 add NavigationCurrentEntryChangeEvent 2025-10-17 01:27:52 -07:00
Muki Kiboigo
cb179794ae split NavigationType and NavigationKind 2025-10-17 01:27:52 -07:00
Muki Kiboigo
b5f0d017cc fix navigation and related tests 2025-10-17 01:27:52 -07:00
Muki Kiboigo
d579f21bf2 History as compat layer over Navigation 2025-10-17 01:27:52 -07:00
Muki Kiboigo
813e36f44e add functional Navigation 2025-10-17 01:27:52 -07:00
Muki Kiboigo
e0a912722b add eqlDocument comparison 2025-10-17 01:27:52 -07:00
Muki Kiboigo
332b302285 add ENUM_JS_USE_TAG for enums 2025-10-17 01:27:51 -07:00
Muki Kiboigo
b1e8268ce0 initial Navigation scaffolding 2025-10-17 01:27:49 -07:00
36 changed files with 50 additions and 414 deletions

View File

@@ -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

View File

@@ -1,7 +1,7 @@
name: zig-fmt
env:
ZIG_VERSION: 0.15.2
ZIG_VERSION: 0.15.1
on:
pull_request:

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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
View File

@@ -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": {

View File

@@ -49,7 +49,7 @@
targetPkgs =
pkgs: with pkgs; [
# Build Tools
zigpkgs."0.15.2"
zigpkgs."0.15.1"
zls
python3
pkg-config

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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");

View File

@@ -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,

View File

@@ -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| {

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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)).*,
},

View File

@@ -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 {

View File

@@ -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{};

View File

@@ -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;
};

View File

@@ -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 = &.{},

View File

@@ -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)) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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.?;

View File

@@ -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 });
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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));
}
}