mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-29 15:13:28 +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