mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-24 05:33:16 +00:00
Merge branch 'main' into fix_context_lifetime
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const log = @import("log.zig");
|
||||
const Page = @import("browser/Page.zig");
|
||||
@@ -241,7 +242,7 @@ pub fn unregister(self: *Notification, comptime event: EventType, receiver: anyt
|
||||
if (listeners.items.len == 0) {
|
||||
listeners.deinit(self.allocator);
|
||||
const removed = self.listeners.remove(@intFromPtr(receiver));
|
||||
std.debug.assert(removed == true);
|
||||
lp.assert(removed == true, "Notification.unregister", .{ .type = event });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const net = std.net;
|
||||
@@ -157,7 +158,7 @@ fn readLoop(self: *Server, socket: posix.socket_t, timeout_ms: u32) !void {
|
||||
});
|
||||
defer http.removeCDPClient();
|
||||
|
||||
std.debug.assert(client.mode == .http);
|
||||
lp.assert(client.mode == .http, "Server.readLoop invalid mode", .{});
|
||||
while (true) {
|
||||
if (http.poll(timeout_ms) != .cdp_socket) {
|
||||
log.info(.app, "CDP timeout", .{});
|
||||
@@ -236,7 +237,7 @@ pub const Client = struct {
|
||||
const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0);
|
||||
const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
|
||||
// we expect the socket to come to us as nonblocking
|
||||
std.debug.assert(socket_flags & nonblocking == nonblocking);
|
||||
lp.assert(socket_flags & nonblocking == nonblocking, "Client.init blocking", .{});
|
||||
|
||||
var reader = try Reader(true).init(server.allocator);
|
||||
errdefer reader.deinit();
|
||||
@@ -311,7 +312,7 @@ pub const Client = struct {
|
||||
}
|
||||
|
||||
fn processHTTPRequest(self: *Client) !bool {
|
||||
std.debug.assert(self.reader.pos == 0);
|
||||
lp.assert(self.reader.pos == 0, "Client.HTTP pos", .{ .pos = self.reader.pos });
|
||||
const request = self.reader.buf[0..self.reader.len];
|
||||
|
||||
if (request.len > MAX_HTTP_REQUEST_SIZE) {
|
||||
@@ -592,8 +593,7 @@ pub const Client = struct {
|
||||
// blocking and switch it back to non-blocking after the write
|
||||
// is complete. Doesn't seem particularly efficiently, but
|
||||
// this should virtually never happen.
|
||||
std.debug.assert(changed_to_blocking == false);
|
||||
log.debug(.app, "CDP write would block", .{});
|
||||
lp.assert(changed_to_blocking == false, "Client.double block", .{});
|
||||
changed_to_blocking = true;
|
||||
_ = try posix.fcntl(self.socket, posix.F.SETFL, self.socket_flags & ~@as(u32, @bitCast(posix.O{ .NONBLOCK = true })));
|
||||
continue :LOOP;
|
||||
@@ -821,7 +821,7 @@ fn Reader(comptime EXPECT_MASK: bool) type {
|
||||
const pos = self.pos;
|
||||
const len = self.len;
|
||||
|
||||
std.debug.assert(pos <= len);
|
||||
lp.assert(pos <= len, "Client.Reader.compact precondition", .{ .pos = pos, .len = len });
|
||||
|
||||
// how many (if any) partial bytes do we have
|
||||
const partial_bytes = len - pos;
|
||||
@@ -842,7 +842,7 @@ fn Reader(comptime EXPECT_MASK: bool) type {
|
||||
const next_message_len = length_meta.@"1";
|
||||
// if this isn't true, then we have a full message and it
|
||||
// should have been processed.
|
||||
std.debug.assert(next_message_len > partial_bytes);
|
||||
lp.assert(pos <= len, "Client.Reader.compact postcondition", .{ .next_len = next_message_len, .partial = partial_bytes });
|
||||
|
||||
const missing_bytes = next_message_len - partial_bytes;
|
||||
|
||||
@@ -929,7 +929,7 @@ fn fillWebsocketHeader(buf: std.ArrayListUnmanaged(u8)) []const u8 {
|
||||
// makes the assumption that our caller reserved the first
|
||||
// 10 bytes for the header
|
||||
fn websocketHeader(buf: []u8, op_code: OpCode, payload_len: usize) []const u8 {
|
||||
std.debug.assert(buf.len == 10);
|
||||
lp.assert(buf.len == 10, "Websocket.Header", .{ .len = buf.len });
|
||||
|
||||
const len = payload_len;
|
||||
buf[0] = 128 | @intFromEnum(op_code); // fin | opcode
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const builtin = @import("builtin");
|
||||
const reflect = @import("reflect.zig");
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
const log = @import("../log.zig");
|
||||
const String = @import("../string.zig").String;
|
||||
@@ -38,6 +36,9 @@ const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.
|
||||
const Blob = @import("webapi/Blob.zig");
|
||||
const AbstractRange = @import("webapi/AbstractRange.zig");
|
||||
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Factory = @This();
|
||||
_page: *Page,
|
||||
_slab: SlabAllocator,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const JS = @import("js/js.zig");
|
||||
const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -785,7 +786,9 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
||||
// an extra socket, so it should not be possibl to
|
||||
// get an cdp_socket message when exit_when_done
|
||||
// is true.
|
||||
std.debug.assert(exit_when_done == false);
|
||||
if (IS_DEBUG) {
|
||||
std.debug.assert(exit_when_done == false);
|
||||
}
|
||||
|
||||
// data on a socket we aren't handling, return to caller
|
||||
return .cdp_socket;
|
||||
@@ -817,7 +820,9 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
||||
// we don't need to consider http_client.intercepted here
|
||||
// because exit_when_done is true, and that can only be
|
||||
// the case when interception isn't possible.
|
||||
std.debug.assert(http_client.intercepted == 0);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(http_client.intercepted == 0);
|
||||
}
|
||||
|
||||
const ms = ms_to_next_task orelse blk: {
|
||||
if (wait_ms - ms_remaining < 100) {
|
||||
@@ -1014,7 +1019,9 @@ fn getElementIdMap(page: *Page, node: *Node) ElementIdMaps {
|
||||
};
|
||||
}
|
||||
// Detached nodes should not have IDs registered
|
||||
std.debug.assert(false);
|
||||
if (IS_DEBUG) {
|
||||
std.debug.assert(false);
|
||||
}
|
||||
return .{
|
||||
.lookup = &page.document._elements_by_id,
|
||||
.removed_ids = &page.document._removed_ids,
|
||||
@@ -1260,14 +1267,14 @@ pub fn deliverSlotchangeEvents(self: *Page) void {
|
||||
}
|
||||
|
||||
fn notifyNetworkIdle(self: *Page) void {
|
||||
std.debug.assert(self._notified_network_idle == .done);
|
||||
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
|
||||
self._session.browser.notification.dispatch(.page_network_idle, &.{
|
||||
.timestamp = timestamp(.monotonic),
|
||||
});
|
||||
}
|
||||
|
||||
fn notifyNetworkAlmostIdle(self: *Page) void {
|
||||
std.debug.assert(self._notified_network_almost_idle == .done);
|
||||
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
|
||||
self._session.browser.notification.dispatch(.page_network_almost_idle, &.{
|
||||
.timestamp = timestamp(.monotonic),
|
||||
});
|
||||
@@ -1293,7 +1300,7 @@ pub fn appendNew(self: *Page, parent: *Node, child: Node.NodeOrText) !void {
|
||||
},
|
||||
};
|
||||
|
||||
std.debug.assert(node._parent == null);
|
||||
lp.assert(node._parent == null, "Page.appendNew", .{});
|
||||
try self._insertNodeRelative(true, parent, node, .append, .{
|
||||
// this opts has no meaning since we're passing `true` as the first
|
||||
// parameter, which indicates this comes from the parser, and has its
|
||||
@@ -2212,7 +2219,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
|
||||
const children = parent._children.?;
|
||||
switch (children.*) {
|
||||
.one => |n| {
|
||||
std.debug.assert(n == child);
|
||||
lp.assert(n == child, "Page.removeNode.one", .{});
|
||||
parent._children = null;
|
||||
self._factory.destroy(children);
|
||||
},
|
||||
@@ -2339,7 +2346,8 @@ pub fn insertNodeRelative(self: *Page, parent: *Node, child: *Node, relative: In
|
||||
}
|
||||
pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Node, child: *Node, relative: InsertNodeRelative, opts: InsertNodeOpts) !void {
|
||||
// caller should have made sure this was the case
|
||||
std.debug.assert(child._parent == null);
|
||||
|
||||
lp.assert(child._parent == null, "Page.insertNodeRelative parent", .{ .url = self.url });
|
||||
|
||||
const children = blk: {
|
||||
// expand parent._children so that it can take another child
|
||||
@@ -2368,14 +2376,14 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
|
||||
},
|
||||
.after => |ref_node| {
|
||||
// caller should have made sure this was the case
|
||||
std.debug.assert(ref_node._parent.? == parent);
|
||||
lp.assert(ref_node._parent.? == parent, "Page.insertNodeRelative after", .{ .url = self.url });
|
||||
// if ref_node is in parent, and expanded _children above to
|
||||
// accommodate another child, then `children` must be a list
|
||||
children.list.insertAfter(&ref_node._child_link, &child._child_link);
|
||||
},
|
||||
.before => |ref_node| {
|
||||
// caller should have made sure this was the case
|
||||
std.debug.assert(ref_node._parent.? == parent);
|
||||
lp.assert(ref_node._parent.? == parent, "Page.insertNodeRelative before", .{ .url = self.url });
|
||||
// if ref_node is in parent, and expanded _children above to
|
||||
// accommodate another child, then `children` must be a list
|
||||
children.list.insertBefore(&ref_node._child_link, &child._child_link);
|
||||
@@ -2653,7 +2661,7 @@ pub fn parseHtmlAsChildren(self: *Page, node: *Node, html: []const u8) !void {
|
||||
// https://github.com/servo/html5ever/issues/583
|
||||
const children = node._children orelse return;
|
||||
const first = children.one;
|
||||
std.debug.assert(first.is(Element.Html.Html) != null);
|
||||
lp.assert(first.is(Element.Html.Html) != null, "Page.parseHtmlAsChildren root", .{ .type = first._type });
|
||||
node._children = first._children;
|
||||
|
||||
if (self.hasMutationObservers()) {
|
||||
|
||||
@@ -96,7 +96,9 @@ fn runQueue(self: *Scheduler, queue: *Queue) !?u64 {
|
||||
|
||||
if (repeat_in_ms) |ms| {
|
||||
// Task cannot be repeated immediately, and they should know that
|
||||
std.debug.assert(ms != 0);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(ms != 0);
|
||||
}
|
||||
task.run_at = now + ms;
|
||||
try self.low_priority.add(task);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const js = @import("js/js.zig");
|
||||
@@ -496,7 +497,7 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
|
||||
// Called from the Page to let us know it's done parsing the HTML. Necessary that
|
||||
// we know this so that we know that we can start evaluating deferred scripts.
|
||||
pub fn staticScriptsDone(self: *ScriptManager) void {
|
||||
std.debug.assert(self.static_scripts_done == false);
|
||||
lp.assert(self.static_scripts_done == false, "ScriptManager.staticScriptsDone", .{});
|
||||
self.static_scripts_done = true;
|
||||
self.evaluate();
|
||||
}
|
||||
@@ -687,7 +688,7 @@ pub const Script = struct {
|
||||
// set `CURLOPT_SUPPRESS_CONNECT_HEADERS` and CONNECT to a proxy, this
|
||||
// will fail. This assertion exists to catch incorrect assumptions about
|
||||
// how libcurl works, or about how we've configured it.
|
||||
std.debug.assert(self.source.remote.capacity == 0);
|
||||
lp.assert(self.source.remote.capacity == 0, "ScriptManager.HeaderCallback", .{ .capacity = self.source.remote.capacity });
|
||||
var buffer = self.manager.buffer_pool.get();
|
||||
if (transfer.getContentLength()) |cl| {
|
||||
try buffer.ensureTotalCapacity(self.manager.allocator, cl);
|
||||
@@ -762,10 +763,12 @@ pub const Script = struct {
|
||||
|
||||
fn eval(self: *Script, page: *Page) void {
|
||||
// never evaluated, source is passed back to v8, via callbacks.
|
||||
std.debug.assert(self.mode != .import_async);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.mode != .import_async);
|
||||
|
||||
// never evaluated, source is passed back to v8 when asked for it.
|
||||
std.debug.assert(self.mode != .import);
|
||||
// never evaluated, source is passed back to v8 when asked for it.
|
||||
std.debug.assert(self.mode != .import);
|
||||
}
|
||||
|
||||
if (page.isGoingAway()) {
|
||||
// don't evaluate scripts for a dying page.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const log = @import("../log.zig");
|
||||
|
||||
@@ -92,7 +93,7 @@ pub fn deinit(self: *Session) void {
|
||||
// NOTE: the caller is not the owner of the returned value,
|
||||
// the pointer on Page is just returned as a convenience
|
||||
pub fn createPage(self: *Session) !*Page {
|
||||
std.debug.assert(self.page == null);
|
||||
lp.assert(self.page == null, "Session.createPage - page not null", .{});
|
||||
|
||||
const page_arena = &self.browser.page_arena;
|
||||
_ = page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
|
||||
@@ -116,8 +117,7 @@ pub fn createPage(self: *Session) !*Page {
|
||||
pub fn removePage(self: *Session) void {
|
||||
// Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one
|
||||
self.browser.notification.dispatch(.page_remove, .{});
|
||||
|
||||
std.debug.assert(self.page != null);
|
||||
lp.assert(self.page != null, "Session.removePage - page is null", .{});
|
||||
|
||||
self.page.?.deinit();
|
||||
self.page = null;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const ResolveOpts = struct {
|
||||
@@ -93,7 +94,7 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
|
||||
}
|
||||
|
||||
if (std.mem.startsWith(u8, out[in_i..], "../")) {
|
||||
std.debug.assert(out[out_i - 1] == '/');
|
||||
lp.assert(out[out_i - 1] == '/', "URL.resolve", .{ .out = out });
|
||||
|
||||
if (out_i > path_marker) {
|
||||
// go back before the /
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
const log = @import("../../log.zig");
|
||||
|
||||
const js = @import("js.zig");
|
||||
@@ -244,7 +244,7 @@ pub fn module(self: *Context, comptime want_result: bool, local: *const js.Local
|
||||
|
||||
if (cacheable) {
|
||||
// compileModule is synchronous - nothing can modify the cache during compilation
|
||||
std.debug.assert(gop.value_ptr.module == null);
|
||||
lp.assert(gop.value_ptr.module == null, "Context.module has module", .{});
|
||||
gop.value_ptr.module = try m.persist();
|
||||
if (!gop.found_existing) {
|
||||
gop.key_ptr.* = owned_url;
|
||||
@@ -261,7 +261,9 @@ pub fn module(self: *Context, comptime want_result: bool, local: *const js.Local
|
||||
}
|
||||
|
||||
const evaluated = mod.evaluate() catch {
|
||||
std.debug.assert(mod.getStatus() == .kErrored);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(mod.getStatus() == .kErrored);
|
||||
}
|
||||
|
||||
// Some module-loading errors aren't handled by TryCatch. We need to
|
||||
// get the error from the module itself.
|
||||
@@ -274,7 +276,7 @@ pub fn module(self: *Context, comptime want_result: bool, local: *const js.Local
|
||||
|
||||
// https://v8.github.io/api/head/classv8_1_1Module.html#a1f1758265a4082595757c3251bb40e0f
|
||||
// Must be a promise that gets returned here.
|
||||
std.debug.assert(evaluated.isPromise());
|
||||
lp.assert(evaluated.isPromise(), "Context.module non-promise", .{});
|
||||
|
||||
if (comptime !want_result) {
|
||||
// avoid creating a bunch of persisted objects if it isn't
|
||||
@@ -288,14 +290,16 @@ pub fn module(self: *Context, comptime want_result: bool, local: *const js.Local
|
||||
|
||||
// anyone who cares about the result, should also want it to
|
||||
// be cached
|
||||
std.debug.assert(cacheable);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(cacheable);
|
||||
}
|
||||
|
||||
// entry has to have been created atop this function
|
||||
const entry = self.module_cache.getPtr(owned_url).?;
|
||||
|
||||
// and the module must have been set after we compiled it
|
||||
std.debug.assert(entry.module != null);
|
||||
std.debug.assert(entry.module_promise == null);
|
||||
lp.assert(entry.module != null, "Context.module with module", .{});
|
||||
lp.assert(entry.module_promise == null, "Context.module with module_promise", .{});
|
||||
|
||||
entry.module_promise = try evaluated.toPromise().persist();
|
||||
return if (comptime want_result) entry.* else {};
|
||||
@@ -589,7 +593,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c
|
||||
// We need to do part of what the first case is going to do in
|
||||
// `dynamicModuleSourceCallback`, but we can skip some steps
|
||||
// since the module is alrady loaded,
|
||||
std.debug.assert(gop.value_ptr.module != null);
|
||||
lp.assert(gop.value_ptr.module != null, "Context._dynamicModuleCallback has module", .{});
|
||||
|
||||
// If the module hasn't been evaluated yet (it was only instantiated
|
||||
// as a static import dependency), we need to evaluate it now.
|
||||
@@ -606,11 +610,13 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c
|
||||
} else {
|
||||
// the module was loaded, but not evaluated, we _have_ to evaluate it now
|
||||
const evaluated = mod.evaluate() catch {
|
||||
std.debug.assert(status == .kErrored);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(status == .kErrored);
|
||||
}
|
||||
_ = resolver.reject("module evaluation", local.newString("Module evaluation failed"));
|
||||
return promise;
|
||||
};
|
||||
std.debug.assert(evaluated.isPromise());
|
||||
lp.assert(evaluated.isPromise(), "Context._dynamicModuleCallback non-promise", .{});
|
||||
gop.value_ptr.module_promise = try evaluated.toPromise().persist();
|
||||
}
|
||||
}
|
||||
@@ -667,9 +673,11 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
|
||||
|
||||
// we can only be here if the module has been evaluated and if
|
||||
// we have a resolve loading this asynchronously.
|
||||
std.debug.assert(module_entry.module_promise != null);
|
||||
std.debug.assert(module_entry.resolver_promise != null);
|
||||
std.debug.assert(self.module_cache.contains(state.specifier));
|
||||
lp.assert(module_entry.module_promise != null, "Context.resolveDynamicModule has module_promise", .{});
|
||||
lp.assert(module_entry.resolver_promise != null, "Context.resolveDynamicModule has resolver_promise", .{});
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.module_cache.contains(state.specifier));
|
||||
}
|
||||
state.module = module_entry.module.?;
|
||||
|
||||
// We've gotten the source for the module and are evaluating it.
|
||||
|
||||
@@ -76,6 +76,8 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot
|
||||
v8.v8__Isolate__SetHostImportModuleDynamicallyCallback(isolate.handle, Context.dynamicModuleCallback);
|
||||
v8.v8__Isolate__SetPromiseRejectCallback(isolate.handle, promiseRejectCallback);
|
||||
v8.v8__Isolate__SetMicrotasksPolicy(isolate.handle, v8.kExplicit);
|
||||
v8.v8__Isolate__SetFatalErrorHandler(isolate.handle, fatalCallback);
|
||||
v8.v8__Isolate__SetOOMErrorHandler(isolate.handle, oomCallback);
|
||||
|
||||
isolate.enter();
|
||||
errdefer isolate.exit();
|
||||
@@ -211,3 +213,17 @@ fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) v
|
||||
.note = "This should be updated to call window.unhandledrejection",
|
||||
});
|
||||
}
|
||||
|
||||
fn fatalCallback(c_location: [*c]const u8, c_message: [*c]const u8) callconv(.c) void {
|
||||
const location = std.mem.span(c_location);
|
||||
const message = std.mem.span(c_message);
|
||||
log.fatal(.app, "V8 fatal callback", .{ .location = location, .message = message });
|
||||
@import("../../crash_handler.zig").crash("Fatal V8 Error", .{ .location = location, .message = message }, @returnAddress());
|
||||
}
|
||||
|
||||
fn oomCallback(c_location: [*c]const u8, details: ?*const v8.OOMDetails) callconv(.c) void {
|
||||
const location = std.mem.span(c_location);
|
||||
const detail = if (details) |d| std.mem.span(d.detail) else "";
|
||||
log.fatal(.app, "V8 OOM", .{ .location = location, .detail = detail });
|
||||
@import("../../crash_handler.zig").crash("V8 OOM", .{ .location = location, .detail = detail }, @returnAddress());
|
||||
}
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const log = @import("../../log.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const js = @import("js.zig");
|
||||
const v8 = js.v8;
|
||||
@@ -28,9 +29,8 @@ const Env = @import("Env.zig");
|
||||
const bridge = @import("bridge.zig");
|
||||
const Context = @import("Context.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
const CONTEXT_ARENA_RETAIN = 1024 * 64;
|
||||
|
||||
@@ -70,7 +70,7 @@ pub fn deinit(self: *ExecutionWorld) void {
|
||||
// We also maintain our own "context_arena" which allows us to have
|
||||
// all page related memory easily managed.
|
||||
pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context {
|
||||
std.debug.assert(self.context == null);
|
||||
lp.assert(self.context == null, "ExecptionWorld.createContext has context", .{});
|
||||
|
||||
const env = self.env;
|
||||
const isolate = env.isolate;
|
||||
|
||||
@@ -426,7 +426,9 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio
|
||||
v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT(target, js_name, getter_callback);
|
||||
}
|
||||
} else {
|
||||
std.debug.assert(value.static == false);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(value.static == false);
|
||||
}
|
||||
const setter_callback = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, value.setter.?).?);
|
||||
v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT2(target, js_name, getter_callback, setter_callback);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("js.zig");
|
||||
const lp = @import("lightpanda");
|
||||
const log = @import("../../log.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const h5e = @import("html5ever.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
@@ -162,7 +163,7 @@ pub const Streaming = struct {
|
||||
}
|
||||
|
||||
pub fn start(self: *Streaming) !void {
|
||||
std.debug.assert(self.handle == null);
|
||||
lp.assert(self.handle == null, "Parser.start non-null handle", .{});
|
||||
|
||||
self.handle = h5e.html5ever_streaming_parser_create(
|
||||
&self.parser.container,
|
||||
@@ -357,7 +358,7 @@ fn getDataCallback(ctx: *anyopaque) callconv(.c) *anyopaque {
|
||||
const pn: *ParsedNode = @ptrCast(@alignCast(ctx));
|
||||
// For non-elements, data is null. But, we expect this to only ever
|
||||
// be called for elements.
|
||||
std.debug.assert(pn.data != null);
|
||||
lp.assert(pn.data != null, "Parser.getDataCallback null data", .{});
|
||||
return pn.data.?;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const log = @import("../../log.zig");
|
||||
const String = @import("../../string.zig").String;
|
||||
@@ -467,6 +468,22 @@ pub fn getAttribute(self: *const Element, name: []const u8, page: *Page) !?[]con
|
||||
return attributes.get(name, page);
|
||||
}
|
||||
|
||||
/// For simplicity, the namespace is currently ignored and only the local name is used.
|
||||
pub fn getAttributeNS(
|
||||
self: *const Element,
|
||||
maybe_namespace: ?[]const u8,
|
||||
local_name: []const u8,
|
||||
page: *Page,
|
||||
) !?[]const u8 {
|
||||
if (maybe_namespace) |namespace| {
|
||||
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
|
||||
log.warn(.not_implemented, "Element.getAttributeNS", .{ .namespace = namespace });
|
||||
}
|
||||
}
|
||||
|
||||
return self.getAttribute(local_name, page);
|
||||
}
|
||||
|
||||
pub fn getAttributeSafe(self: *const Element, name: []const u8) ?[]const u8 {
|
||||
const attributes = self._attributes orelse return null;
|
||||
return attributes.getSafe(name);
|
||||
@@ -499,6 +516,26 @@ pub fn setAttribute(self: *Element, name: []const u8, value: []const u8, page: *
|
||||
_ = try attributes.put(name, value, self, page);
|
||||
}
|
||||
|
||||
pub fn setAttributeNS(
|
||||
self: *Element,
|
||||
maybe_namespace: ?[]const u8,
|
||||
qualified_name: []const u8,
|
||||
value: []const u8,
|
||||
page: *Page,
|
||||
) !void {
|
||||
if (maybe_namespace) |namespace| {
|
||||
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
|
||||
log.warn(.not_implemented, "Element.setAttributeNS", .{ .namespace = namespace });
|
||||
}
|
||||
}
|
||||
|
||||
const local_name = if (std.mem.indexOfScalarPos(u8, qualified_name, 0, ':')) |idx|
|
||||
qualified_name[idx + 1 ..]
|
||||
else
|
||||
qualified_name;
|
||||
return self.setAttribute(local_name, value, page);
|
||||
}
|
||||
|
||||
pub fn setAttributeSafe(self: *Element, name: []const u8, value: []const u8, page: *Page) !void {
|
||||
const attributes = try self.getOrCreateAttributeList(page);
|
||||
_ = try attributes.putSafe(name, value, self, page);
|
||||
@@ -509,7 +546,7 @@ pub fn getOrCreateAttributeList(self: *Element, page: *Page) !*Attribute.List {
|
||||
}
|
||||
|
||||
pub fn createAttributeList(self: *Element, page: *Page) !*Attribute.List {
|
||||
std.debug.assert(self._attributes == null);
|
||||
lp.assert(self._attributes == null, "Element.createAttributeList non-null _attributes", .{});
|
||||
const a = try page.arena.create(Attribute.List);
|
||||
a.* = .{ .normalize = self._namespace == .html };
|
||||
self._attributes = a;
|
||||
@@ -1380,8 +1417,10 @@ pub const JsApi = struct {
|
||||
pub const hasAttribute = bridge.function(Element.hasAttribute, .{});
|
||||
pub const hasAttributes = bridge.function(Element.hasAttributes, .{});
|
||||
pub const getAttribute = bridge.function(Element.getAttribute, .{});
|
||||
pub const getAttributeNS = bridge.function(Element.getAttributeNS, .{});
|
||||
pub const getAttributeNode = bridge.function(Element.getAttributeNode, .{});
|
||||
pub const setAttribute = bridge.function(Element.setAttribute, .{ .dom_exception = true });
|
||||
pub const setAttributeNS = bridge.function(Element.setAttributeNS, .{ .dom_exception = true });
|
||||
pub const setAttributeNode = bridge.function(Element.setAttributeNode, .{});
|
||||
pub const removeAttribute = bridge.function(Element.removeAttribute, .{});
|
||||
pub const toggleAttribute = bridge.function(Element.toggleAttribute, .{ .dom_exception = true });
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const log = @import("../../log.zig");
|
||||
|
||||
const crypto = @import("../../crypto.zig");
|
||||
@@ -387,7 +388,7 @@ pub const CryptoKey = struct {
|
||||
|
||||
// HMAC is simply CSPRNG.
|
||||
const res = crypto.RAND_bytes(key.ptr, key.len);
|
||||
std.debug.assert(res == 1);
|
||||
lp.assert(res == 1, "SubtleCrypto.initHMAC", .{ .res = res });
|
||||
|
||||
const crypto_key = try page._factory.create(CryptoKey{
|
||||
._type = .hmac,
|
||||
@@ -581,7 +582,7 @@ pub const CryptoKey = struct {
|
||||
return error.Internal;
|
||||
}
|
||||
// Sanity check.
|
||||
std.debug.assert(derived_key.len == out_key_len);
|
||||
lp.assert(derived_key.len == out_key_len, "SubtleCrypto.deriveBitsX25519", .{});
|
||||
|
||||
// Length is in bits, convert to byte length.
|
||||
const length = (length_in_bits / 8) + (7 + (length_in_bits % 8)) / 8;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
@@ -58,7 +59,7 @@ pub fn length(self: *HTMLAllCollection, page: *const Page) u32 {
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.assert(self._last_index == 0);
|
||||
lp.assert(self._last_index == 0, "HTMLAllCollection.length", .{ .last_index = self._last_index });
|
||||
|
||||
var tw = &self._tw;
|
||||
defer tw.reset();
|
||||
|
||||
@@ -25,6 +25,8 @@ const NodeList = @import("NodeList.zig");
|
||||
const RadioNodeList = @import("RadioNodeList.zig");
|
||||
const HTMLCollection = @import("HTMLCollection.zig");
|
||||
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
const HTMLFormControlsCollection = @This();
|
||||
|
||||
_proto: *HTMLCollection,
|
||||
@@ -95,7 +97,9 @@ pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, page: *Pag
|
||||
}
|
||||
|
||||
// case == 2 was handled inside the loop
|
||||
std.debug.assert(count == 1);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(count == 1);
|
||||
}
|
||||
|
||||
return .{ .element = first_element.? };
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const String = @import("../../../string.zig").String;
|
||||
|
||||
@@ -124,7 +125,7 @@ pub fn NodeLive(comptime mode: Mode) type {
|
||||
// _tw is reset. Again, this should always be the case, but we're
|
||||
// asserting to make sure, else we'll have weird behavior, namely
|
||||
// the wrong item being returned for the wrong index.
|
||||
std.debug.assert(self._last_index == 0);
|
||||
lp.assert(self._last_index == 0, "NodeLives.length", .{ .last_index = self._last_index });
|
||||
|
||||
var tw = &self._tw;
|
||||
defer tw.reset();
|
||||
|
||||
@@ -26,6 +26,8 @@ const GenericIterator = @import("../collections/iterator.zig").Entry;
|
||||
const Page = @import("../../Page.zig");
|
||||
const String = @import("../../../string.zig").String;
|
||||
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{
|
||||
Attribute,
|
||||
@@ -223,7 +225,6 @@ pub const List = struct {
|
||||
|
||||
if (is_id) {
|
||||
const parent = element.asNode()._parent orelse {
|
||||
std.debug.assert(false);
|
||||
return entry;
|
||||
};
|
||||
try page.addElementId(parent, element, entry._value.str());
|
||||
@@ -248,7 +249,9 @@ pub const List = struct {
|
||||
// not efficient, won't be called often (if ever!)
|
||||
pub fn putAttribute(self: *List, attribute: *Attribute, element: *Element, page: *Page) !?*Attribute {
|
||||
// we expect our caller to make sure this is true
|
||||
std.debug.assert(attribute._element == null);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(attribute._element == null);
|
||||
}
|
||||
|
||||
const existing_attribute = try self.getAttribute(attribute._name, element, page);
|
||||
if (existing_attribute) |ea| {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const js = @import("../../js/js.zig");
|
||||
const reflect = @import("../../reflect.zig");
|
||||
|
||||
@@ -293,11 +294,11 @@ pub fn insertAdjacentHTML(
|
||||
// <html><head></head><body>{ ... }</body></html>
|
||||
// None of the following can be null.
|
||||
const maybe_html_node = doc_node.firstChild();
|
||||
std.debug.assert(maybe_html_node != null);
|
||||
lp.assert(maybe_html_node != null, "Html.insertAdjacentHTML null html", .{});
|
||||
const html_node = maybe_html_node orelse return;
|
||||
|
||||
const maybe_body_node = html_node.lastChild();
|
||||
std.debug.assert(maybe_body_node != null);
|
||||
lp.assert(maybe_body_node != null, "Html.insertAdjacentHTML null bodys", .{});
|
||||
const body = maybe_body_node orelse return;
|
||||
|
||||
const target_node, const prev_node = try self.asElement().asNode().findAdjacentNodes(position);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const log = @import("../../../log.zig");
|
||||
const URL = @import("../URL.zig");
|
||||
|
||||
@@ -81,7 +82,8 @@ pub fn getCurrentEntryOrNull(self: *Navigation) ?*NavigationHistoryEntry {
|
||||
pub fn getCurrentEntry(self: *Navigation) *NavigationHistoryEntry {
|
||||
// This should never fail. An entry should always be created before
|
||||
// we run the scripts on the page we are loading.
|
||||
std.debug.assert(self._entries.items.len > 0);
|
||||
const len = self._entries.items.len;
|
||||
lp.assert(len > 0, "Navigation.getCurrentEntry", .{ .len = len });
|
||||
|
||||
return self.getCurrentEntryOrNull().?;
|
||||
}
|
||||
|
||||
@@ -18,17 +18,19 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const Node = @import("../Node.zig");
|
||||
const Selector = @import("Selector.zig");
|
||||
const Part = Selector.Part;
|
||||
const Combinator = Selector.Combinator;
|
||||
const Segment = Selector.Segment;
|
||||
const Attribute = @import("../element/Attribute.zig");
|
||||
|
||||
const Selector = @import("Selector.zig");
|
||||
|
||||
const Part = Selector.Part;
|
||||
const Segment = Selector.Segment;
|
||||
const Combinator = Selector.Combinator;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
const Parser = @This();
|
||||
|
||||
input: []const u8,
|
||||
@@ -308,8 +310,11 @@ fn consumeUntilCommaOrParen(self: *Parser) []const u8 {
|
||||
}
|
||||
|
||||
fn pseudoClass(self: *Parser, arena: Allocator, page: *Page) !Selector.PseudoClass {
|
||||
// Must be called when we're at a ':'
|
||||
std.debug.assert(self.peek() == ':');
|
||||
if (comptime IS_DEBUG) {
|
||||
// Should have been verified by caller
|
||||
std.debug.assert(self.peek() == ':');
|
||||
}
|
||||
|
||||
self.input = self.input[1..];
|
||||
|
||||
// Parse the pseudo-class name
|
||||
@@ -657,13 +662,21 @@ fn parseNthPattern(self: *Parser) !Selector.NthPattern {
|
||||
}
|
||||
|
||||
pub fn id(self: *Parser, arena: Allocator) ![]const u8 {
|
||||
std.debug.assert(self.peek() == '#');
|
||||
if (comptime IS_DEBUG) {
|
||||
// should have been verified by caller
|
||||
std.debug.assert(self.peek() == '#');
|
||||
}
|
||||
|
||||
self.input = self.input[1..]; // Skip '#'
|
||||
return self.parseIdentifier(arena, error.InvalidIDSelector);
|
||||
}
|
||||
|
||||
fn class(self: *Parser, arena: Allocator) ![]const u8 {
|
||||
std.debug.assert(self.peek() == '.');
|
||||
if (comptime IS_DEBUG) {
|
||||
// should have been verified by caller
|
||||
std.debug.assert(self.peek() == '.');
|
||||
}
|
||||
|
||||
self.input = self.input[1..]; // Skip '.'
|
||||
return self.parseIdentifier(arena, error.InvalidClassSelector);
|
||||
}
|
||||
@@ -822,8 +835,10 @@ fn tag(self: *Parser) ![]const u8 {
|
||||
}
|
||||
|
||||
fn attribute(self: *Parser, arena: Allocator, page: *Page) !Selector.Attribute {
|
||||
// Must be called when we're at a '['
|
||||
std.debug.assert(self.peek() == '[');
|
||||
if (comptime IS_DEBUG) {
|
||||
// should have been verified by caller
|
||||
std.debug.assert(self.peek() == '[');
|
||||
}
|
||||
|
||||
self.input = self.input[1..];
|
||||
_ = self.skipSpaces();
|
||||
|
||||
@@ -88,9 +88,6 @@ pub fn parse(allocator: Allocator, url: [:0]const u8, str: []const u8) !Cookie {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure no one changes our max length without also expanding the size of scrap
|
||||
std.debug.assert(key_string.len <= 8);
|
||||
|
||||
const key = std.meta.stringToEnum(enum {
|
||||
path,
|
||||
domain,
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const json = std.json;
|
||||
|
||||
@@ -35,6 +37,8 @@ const InterceptState = @import("domains/fetch.zig").InterceptState;
|
||||
pub const URL_BASE = "chrome://newtab/";
|
||||
pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
|
||||
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
pub const CDP = CDPT(struct {
|
||||
const Client = *@import("../Server.zig").Client;
|
||||
});
|
||||
@@ -657,7 +661,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
||||
pub fn onInspectorEvent(ctx: *anyopaque, msg: []const u8) void {
|
||||
if (log.enabled(.cdp, .debug)) {
|
||||
// msg should be {"method":<method>,...
|
||||
std.debug.assert(std.mem.startsWith(u8, msg, "{\"method\":"));
|
||||
lp.assert(std.mem.startsWith(u8, msg, "{\"method\":"), "onInspectorEvent prefix", .{});
|
||||
const method_end = std.mem.indexOfScalar(u8, msg, ',') orelse {
|
||||
log.err(.cdp, "invalid inspector event", .{ .msg = msg });
|
||||
return;
|
||||
@@ -716,7 +720,9 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
||||
buf.appendSliceAssumeCapacity(field);
|
||||
buf.appendSliceAssumeCapacity(session_id);
|
||||
buf.appendSliceAssumeCapacity("\"}");
|
||||
std.debug.assert(buf.items.len == message_len);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(buf.items.len == message_len);
|
||||
}
|
||||
|
||||
try cdp.client.sendJSONRaw(buf);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const CdpStorage = @import("storage.zig");
|
||||
@@ -215,7 +216,7 @@ pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.R
|
||||
|
||||
// Isn't possible to do a network request within a Browser (which our
|
||||
// notification is tied to), without a page.
|
||||
std.debug.assert(bc.session.page != null);
|
||||
lp.assert(bc.session.page != null, "CDP.network.httpRequestFail null page", .{});
|
||||
|
||||
// We're missing a bunch of fields, but, for now, this seems like enough
|
||||
try bc.cdp.sendEvent("Network.loadingFailed", .{
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const log = @import("../../log.zig");
|
||||
const js = @import("../../browser/js/js.zig");
|
||||
const Page = @import("../../browser/Page.zig");
|
||||
const timestampF = @import("../../datetime.zig").timestamp;
|
||||
const Notification = @import("../../Notification.zig");
|
||||
const log = @import("../../log.zig");
|
||||
const js = @import("../../browser/js/js.zig");
|
||||
const v8 = js.v8;
|
||||
|
||||
const v8 = js.v8;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub fn processMessage(cmd: anytype) !void {
|
||||
@@ -142,7 +144,7 @@ fn close(cmd: anytype) !void {
|
||||
const target_id = bc.target_id orelse return error.TargetNotLoaded;
|
||||
|
||||
// can't be null if we have a target_id
|
||||
std.debug.assert(bc.session.page != null);
|
||||
lp.assert(bc.session.page != null, "CDP.page.close null page", .{});
|
||||
|
||||
try cmd.sendResult(.{}, .{});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const log = @import("../../log.zig");
|
||||
const js = @import("../../browser/js/js.zig");
|
||||
|
||||
@@ -165,10 +166,10 @@ fn createTarget(cmd: anytype) !void {
|
||||
}
|
||||
|
||||
// if target_id is null, we should never have a page
|
||||
std.debug.assert(bc.session.page == null);
|
||||
lp.assert(bc.session.page == null, "CDP.target.createTarget not null page", .{});
|
||||
|
||||
// if target_id is null, we should never have a session_id
|
||||
std.debug.assert(bc.session_id == null);
|
||||
lp.assert(bc.session_id == null, "CDP.target.createTarget not null session_id", .{});
|
||||
|
||||
const target_id = cmd.cdp.target_id_gen.next();
|
||||
|
||||
@@ -260,7 +261,7 @@ fn closeTarget(cmd: anytype) !void {
|
||||
}
|
||||
|
||||
// can't be null if we have a target_id
|
||||
std.debug.assert(bc.session.page != null);
|
||||
lp.assert(bc.session.page != null, "CDP.target.closeTarget null page", .{});
|
||||
|
||||
try cmd.sendResult(.{ .success = true }, .{ .include_session_id = false });
|
||||
|
||||
@@ -337,7 +338,7 @@ fn sendMessageToTarget(cmd: anytype) !void {
|
||||
return error.TargetNotLoaded;
|
||||
}
|
||||
|
||||
std.debug.assert(bc.session_id != null);
|
||||
lp.assert(bc.session_id != null, "CDP.target.sendMessageToTarget null session_id", .{});
|
||||
if (std.mem.eql(u8, bc.session_id.?, params.sessionId) == false) {
|
||||
// Is this right? Is the params.sessionId meant to be the active
|
||||
// sessionId? What else could it be? We have no other session_id.
|
||||
@@ -445,7 +446,7 @@ fn setAutoAttach(cmd: anytype) !void {
|
||||
|
||||
fn doAttachtoTarget(cmd: anytype, target_id: []const u8) !void {
|
||||
const bc = cmd.browser_context.?;
|
||||
std.debug.assert(bc.session_id == null);
|
||||
lp.assert(bc.session_id == null, "CDP.target.doAttachtoTarget not null session_id", .{});
|
||||
const session_id = cmd.cdp.session_id_gen.next();
|
||||
|
||||
// extra_headers should not be kept on a new page or tab,
|
||||
|
||||
129
src/crash_handler.zig
Normal file
129
src/crash_handler.zig
Normal file
@@ -0,0 +1,129 @@
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const abort = std.posix.abort;
|
||||
|
||||
// tracks how deep within a panic we're panicling
|
||||
var panic_level: usize = 0;
|
||||
|
||||
// Locked to avoid interleaving panic messages from multiple threads.
|
||||
var panic_mutex = std.Thread.Mutex{};
|
||||
|
||||
// overwrite's Zig default panic handler
|
||||
pub fn panic(msg: []const u8, _: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn {
|
||||
@branchHint(.cold);
|
||||
crash(msg, .{ .source = "global" }, begin_addr orelse @returnAddress());
|
||||
}
|
||||
|
||||
pub noinline fn crash(
|
||||
reason: []const u8,
|
||||
args: anytype,
|
||||
begin_addr: usize,
|
||||
) noreturn {
|
||||
@branchHint(.cold);
|
||||
|
||||
nosuspend switch (panic_level) {
|
||||
0 => {
|
||||
panic_level = panic_level + 1;
|
||||
|
||||
{
|
||||
panic_mutex.lock();
|
||||
defer panic_mutex.unlock();
|
||||
|
||||
var writer_w = std.fs.File.stderr().writerStreaming(&.{});
|
||||
const writer = &writer_w.interface;
|
||||
|
||||
writer.writeAll(
|
||||
\\
|
||||
\\Lightpanda has crashed. Please report the issue:
|
||||
\\https://github.com/lightpanda-io/browser/issues
|
||||
\\
|
||||
) catch abort();
|
||||
|
||||
writer.print("\nreason: {s}\n", .{reason}) catch abort();
|
||||
writer.print("OS: {s}\n", .{@tagName(builtin.os.tag)}) catch abort();
|
||||
writer.print("mode: {s}\n", .{@tagName(builtin.mode)}) catch abort();
|
||||
writer.print("version: {s}\n", .{lp.build_config.git_commit}) catch abort();
|
||||
inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |f| {
|
||||
writer.writeAll(f.name ++ ": ") catch break;
|
||||
@import("log.zig").writeValue(.pretty, @field(args, f.name), writer) catch abort();
|
||||
writer.writeByte('\n') catch abort();
|
||||
}
|
||||
|
||||
std.debug.dumpCurrentStackTraceToWriter(begin_addr, writer) catch abort();
|
||||
}
|
||||
|
||||
report(reason) catch {};
|
||||
},
|
||||
1 => {
|
||||
panic_level = 2;
|
||||
var stderr_w = std.fs.File.stderr().writerStreaming(&.{});
|
||||
const stderr = &stderr_w.interface;
|
||||
stderr.writeAll("panicked during a panic. Aborting.\n") catch abort();
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
fn report(reason: []const u8) !void {
|
||||
if (@import("telemetry/telemetry.zig").isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var curl_path: [2048]u8 = undefined;
|
||||
const curl_path_len = curlPath(&curl_path) orelse return;
|
||||
|
||||
var args_buffer: [4096]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&args_buffer);
|
||||
|
||||
try writer.print("https://crash.lightpanda.io/c?v={s}&r=", .{lp.build_config.git_commit});
|
||||
for (reason) |b| {
|
||||
switch (b) {
|
||||
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_' => try writer.writeByte(b),
|
||||
' ' => try writer.writeByte('+'),
|
||||
else => try writer.writeByte('!'), // some weird character, that we shouldn't have, but that'll we'll replace with a weird (bur url-safe) character
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeByte(0);
|
||||
const url = writer.buffered();
|
||||
|
||||
var argv = [_:null]?[*:0]const u8{
|
||||
curl_path[0..curl_path_len :0],
|
||||
"-fsSL",
|
||||
url[0 .. url.len - 1 :0],
|
||||
};
|
||||
std.debug.print("*{s}*\n", .{argv[2].?});
|
||||
|
||||
const result = std.c.fork();
|
||||
switch (result) {
|
||||
0 => {
|
||||
_ = std.c.close(0);
|
||||
_ = std.c.close(1);
|
||||
_ = std.c.close(2);
|
||||
_ = std.c.execve(argv[0].?, &argv, std.c.environ);
|
||||
std.c.exit(0);
|
||||
},
|
||||
else => return,
|
||||
}
|
||||
}
|
||||
|
||||
fn curlPath(buf: []u8) ?usize {
|
||||
const path = std.posix.getenv("PATH") orelse return null;
|
||||
var it = std.mem.tokenizeScalar(u8, path, std.fs.path.delimiter);
|
||||
|
||||
var fba = std.heap.FixedBufferAllocator.init(buf);
|
||||
const allocator = fba.allocator();
|
||||
|
||||
const cwd = std.fs.cwd();
|
||||
while (it.next()) |p| {
|
||||
defer fba.reset();
|
||||
const full_path = std.fs.path.joinZ(allocator, &.{ p, "curl" }) catch continue;
|
||||
cwd.accessZ(full_path, .{}) catch continue;
|
||||
return full_path.len;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
const posix = std.posix;
|
||||
|
||||
@@ -612,7 +613,7 @@ fn writeTime(into: []u8, time: Time) u8 {
|
||||
}
|
||||
|
||||
fn paddingTwoDigits(value: usize) [2]u8 {
|
||||
std.debug.assert(value < 61);
|
||||
lp.assert(value < 61, "datetime.paddingTwoDigits", .{ .value = value });
|
||||
const digits = "0001020304050607080910111213141516171819" ++
|
||||
"2021222324252627282930313233343536373839" ++
|
||||
"4041424344454647484950515253545556575859" ++
|
||||
|
||||
@@ -178,7 +178,9 @@ pub fn abort(self: *Client) void {
|
||||
};
|
||||
transfer.kill();
|
||||
}
|
||||
std.debug.assert(self.active == 0);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.active == 0);
|
||||
}
|
||||
|
||||
var n = self.queue.first;
|
||||
while (n) |node| {
|
||||
@@ -188,11 +190,10 @@ pub fn abort(self: *Client) void {
|
||||
}
|
||||
self.queue = .{};
|
||||
|
||||
// Maybe a bit of overkill
|
||||
// We can remove some (all?) of these once we're confident its right.
|
||||
std.debug.assert(self.handles.in_use.first == null);
|
||||
std.debug.assert(self.handles.available.len() == self.handles.handles.len);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.handles.in_use.first == null);
|
||||
std.debug.assert(self.handles.available.len() == self.handles.handles.len);
|
||||
|
||||
var running: c_int = undefined;
|
||||
std.debug.assert(c.curl_multi_perform(self.multi, &running) == c.CURLE_OK);
|
||||
std.debug.assert(running == 0);
|
||||
@@ -373,7 +374,9 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
|
||||
fn requestFailed(self: *Client, transfer: *Transfer, err: anyerror) void {
|
||||
// this shouldn't happen, we'll crash in debug mode. But in release, we'll
|
||||
// just noop this state.
|
||||
std.debug.assert(transfer._notified_fail == false);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(transfer._notified_fail == false);
|
||||
}
|
||||
if (transfer._notified_fail) {
|
||||
return;
|
||||
}
|
||||
@@ -552,7 +555,9 @@ fn processMessages(self: *Client) !bool {
|
||||
while (c.curl_multi_info_read(multi, &messages_count)) |msg_| {
|
||||
const msg: *c.CURLMsg = @ptrCast(msg_);
|
||||
// This is the only possible message type from CURL for now.
|
||||
std.debug.assert(msg.msg == c.CURLMSG_DONE);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(msg.msg == c.CURLMSG_DONE);
|
||||
}
|
||||
|
||||
const easy = msg.easy_handle.?;
|
||||
const transfer = try Transfer.fromEasy(easy);
|
||||
@@ -893,7 +898,9 @@ pub const Transfer = struct {
|
||||
}
|
||||
|
||||
fn buildResponseHeader(self: *Transfer, easy: *c.CURL) !void {
|
||||
std.debug.assert(self.response_header == null);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.response_header == null);
|
||||
}
|
||||
|
||||
var url: [*c]u8 = undefined;
|
||||
try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_EFFECTIVE_URL, &url));
|
||||
@@ -1036,7 +1043,9 @@ pub const Transfer = struct {
|
||||
// It can be called either on dataCallback or once the request for those
|
||||
// w/o body.
|
||||
fn headerDoneCallback(transfer: *Transfer, easy: *c.CURL) !void {
|
||||
std.debug.assert(transfer._header_done_called == false);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(transfer._header_done_called == false);
|
||||
}
|
||||
defer transfer._header_done_called = true;
|
||||
|
||||
try transfer.buildResponseHeader(easy);
|
||||
@@ -1076,7 +1085,9 @@ pub const Transfer = struct {
|
||||
// headerCallback is called by curl on each request's header line read.
|
||||
fn headerCallback(buffer: [*]const u8, header_count: usize, buf_len: usize, data: *anyopaque) callconv(.c) usize {
|
||||
// libcurl should only ever emit 1 header at a time
|
||||
std.debug.assert(header_count == 1);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(header_count == 1);
|
||||
}
|
||||
|
||||
const easy: *c.CURL = @ptrCast(@alignCast(data));
|
||||
var transfer = fromEasy(easy) catch |err| {
|
||||
@@ -1084,7 +1095,9 @@ pub const Transfer = struct {
|
||||
return 0;
|
||||
};
|
||||
|
||||
std.debug.assert(std.mem.endsWith(u8, buffer[0..buf_len], "\r\n"));
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(std.mem.endsWith(u8, buffer[0..buf_len], "\r\n"));
|
||||
}
|
||||
|
||||
const header = buffer[0 .. buf_len - 2];
|
||||
|
||||
@@ -1105,7 +1118,9 @@ pub const Transfer = struct {
|
||||
|
||||
// a bit silly, but it makes sure that we don't change the length check
|
||||
// above in a way that could break this.
|
||||
std.debug.assert(version_end < 13);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(version_end < 13);
|
||||
}
|
||||
|
||||
const status = std.fmt.parseInt(u16, header[version_start..version_end], 10) catch {
|
||||
if (comptime IS_DEBUG) {
|
||||
@@ -1180,7 +1195,9 @@ pub const Transfer = struct {
|
||||
|
||||
fn dataCallback(buffer: [*]const u8, chunk_count: usize, chunk_len: usize, data: *anyopaque) callconv(.c) usize {
|
||||
// libcurl should only ever emit 1 chunk at a time
|
||||
std.debug.assert(chunk_count == 1);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(chunk_count == 1);
|
||||
}
|
||||
|
||||
const easy: *c.CURL = @ptrCast(@alignCast(data));
|
||||
var transfer = fromEasy(easy) catch |err| {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
pub const c = @cImport({
|
||||
@cInclude("curl/curl.h");
|
||||
@@ -90,7 +91,7 @@ pub fn poll(self: *Http, timeout_ms: u32) Client.PerformStatus {
|
||||
}
|
||||
|
||||
pub fn addCDPClient(self: *Http, cdp_client: Client.CDPClient) void {
|
||||
std.debug.assert(self.client.cdp_client == null);
|
||||
lp.assert(self.client.cdp_client == null, "Http addCDPClient existing", .{});
|
||||
self.client.cdp_client = cdp_client;
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ pub const Connection = struct {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_CAINFO_BLOB, ca_blob));
|
||||
}
|
||||
} else {
|
||||
std.debug.assert(opts.tls_verify_host == false);
|
||||
lp.assert(opts.tls_verify_host == false, "Http.init tls_verify_host", .{});
|
||||
|
||||
// Verify peer checks that the cert is signed by a CA, verify host makes sure the
|
||||
// cert contains the server name.
|
||||
@@ -405,7 +406,7 @@ fn loadCerts(allocator: Allocator, arena: Allocator) !c.curl_blob {
|
||||
}
|
||||
|
||||
// Final encoding should not be larger than our initial size estimate
|
||||
std.debug.assert(buffer_size > arr.items.len);
|
||||
lp.assert(buffer_size > arr.items.len, "Http loadCerts", .{ .estiate = buffer_size, .len = arr.items.len });
|
||||
|
||||
return .{
|
||||
.len = arr.items.len,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
const std = @import("std");
|
||||
const c = @import("Http.zig").c;
|
||||
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
pub const Error = error{
|
||||
UnsupportedProtocol,
|
||||
FailedInit,
|
||||
@@ -109,7 +111,9 @@ pub const Error = error{
|
||||
};
|
||||
|
||||
pub fn fromCode(code: c.CURLcode) Error {
|
||||
std.debug.assert(code != c.CURLE_OK);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(code != c.CURLE_OK);
|
||||
}
|
||||
|
||||
return switch (code) {
|
||||
c.CURLE_UNSUPPORTED_PROTOCOL => Error.UnsupportedProtocol,
|
||||
@@ -218,7 +222,9 @@ pub const Multi = error{
|
||||
};
|
||||
|
||||
pub fn fromMCode(code: c.CURLMcode) Multi {
|
||||
std.debug.assert(code != c.CURLM_OK);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(code != c.CURLM_OK);
|
||||
}
|
||||
|
||||
return switch (code) {
|
||||
c.CURLM_BAD_HANDLE => Multi.BadHandle,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
// Generates incrementing prefixed integers, i.e. CTX-1, CTX-2, CTX-3.
|
||||
// Wraps to 0 on overflow.
|
||||
@@ -85,7 +86,7 @@ pub fn Incrementing(comptime T: type, comptime prefix: []const u8) type {
|
||||
}
|
||||
|
||||
pub fn uuidv4(hex: []u8) void {
|
||||
std.debug.assert(hex.len == 36);
|
||||
lp.assert(hex.len == 36, "uuidv4.len", .{ .len = hex.len });
|
||||
|
||||
var bin: [16]u8 = undefined;
|
||||
std.crypto.random.bytes(&bin);
|
||||
|
||||
@@ -27,6 +27,9 @@ pub const log = @import("log.zig");
|
||||
pub const js = @import("browser/js/js.zig");
|
||||
pub const dump = @import("browser/dump.zig");
|
||||
pub const build_config = @import("build_config");
|
||||
pub const crash_handler = @import("crash_handler.zig");
|
||||
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
pub const FetchOpts = struct {
|
||||
wait_ms: u32 = 5000,
|
||||
@@ -69,6 +72,23 @@ pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void {
|
||||
try writer.flush();
|
||||
}
|
||||
|
||||
pub inline fn assert(ok: bool, comptime ctx: []const u8, args: anytype) void {
|
||||
if (!ok) {
|
||||
if (comptime IS_DEBUG) {
|
||||
unreachable;
|
||||
}
|
||||
assertionFailure(ctx, args);
|
||||
}
|
||||
}
|
||||
|
||||
noinline fn assertionFailure(comptime ctx: []const u8, args: anytype) noreturn {
|
||||
@branchHint(.cold);
|
||||
if (@inComptime()) {
|
||||
@compileError(std.fmt.comptimePrint("assertion failure: " ++ ctx, args));
|
||||
}
|
||||
@import("crash_handler.zig").crash(ctx, args, @returnAddress());
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ const Allocator = std.mem.Allocator;
|
||||
const log = lp.log;
|
||||
const App = lp.App;
|
||||
const SigHandler = @import("Sighandler.zig");
|
||||
pub const panic = lp.crash_handler.panic;
|
||||
|
||||
pub fn main() !void {
|
||||
// allocator
|
||||
|
||||
@@ -10,6 +10,10 @@ const Notification = @import("../Notification.zig");
|
||||
const uuidv4 = @import("../id.zig").uuidv4;
|
||||
const IID_FILE = "iid";
|
||||
|
||||
pub fn isDisabled() bool {
|
||||
return std.process.hasEnvVarConstant("LIGHTPANDA_DISABLE_TELEMETRY");
|
||||
}
|
||||
|
||||
pub const Telemetry = TelemetryT(blk: {
|
||||
if (builtin.mode == .Debug or builtin.is_test) break :blk NoopProvider;
|
||||
break :blk @import("lightpanda.zig").LightPanda;
|
||||
@@ -30,7 +34,7 @@ fn TelemetryT(comptime P: type) type {
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(app: *App, run_mode: App.RunMode) !Self {
|
||||
const disabled = std.process.hasEnvVarConstant("LIGHTPANDA_DISABLE_TELEMETRY");
|
||||
const disabled = isDisabled();
|
||||
if (builtin.mode != .Debug and builtin.is_test == false) {
|
||||
log.info(.telemetry, "telemetry status", .{ .disabled = disabled });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user