mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 22:53:28 +00:00
get fetch campire working
This commit is contained in:
@@ -2,10 +2,14 @@ const std = @import("std");
|
||||
const Node = @import("webapi/Node.zig");
|
||||
|
||||
pub const Opts = struct {
|
||||
// @ZIGDOM (none of these do anything)
|
||||
with_base: bool = false,
|
||||
strip_mode: StripMode = .{},
|
||||
|
||||
const StripMode = struct {
|
||||
// @ZIGDOM
|
||||
pub const StripMode = struct {
|
||||
js: bool = false,
|
||||
ui: bool = false,
|
||||
css: bool = false,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -387,7 +387,6 @@ pub fn throw(self: *Context, err: []const u8) js.Exception {
|
||||
pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOpts) !v8.Value {
|
||||
const isolate = self.isolate;
|
||||
|
||||
|
||||
// Check if it's a "simple" type. This is extracted so that it can be
|
||||
// reused by other parts of the code. "simple" types only require an
|
||||
// isolate to create (specifically, they don't our templates array)
|
||||
@@ -595,7 +594,6 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) !
|
||||
};
|
||||
const JsApi = bridge.Struct(ptr.child).JsApi;
|
||||
|
||||
|
||||
// The TAO contains the pointer to our Zig instance as
|
||||
// well as any meta data we'll need to use it later.
|
||||
// See the TaggedAnyOpaque struct for more details.
|
||||
|
||||
@@ -311,7 +311,6 @@ fn generateConstructor(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTem
|
||||
return template;
|
||||
}
|
||||
|
||||
|
||||
// ZIGDOM (HTMLAllCollection I think)
|
||||
// fn generateUndetectable(comptime Struct: type, template: v8.ObjectTemplate) void {
|
||||
// const has_js_call_as_function = @hasDecl(Struct, "jsCallAsFunction");
|
||||
|
||||
@@ -401,7 +401,6 @@ pub const SubType = enum {
|
||||
webassemblymemory,
|
||||
};
|
||||
|
||||
|
||||
pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/AbortController.zig"),
|
||||
@import("../webapi/AbortSignal.zig"),
|
||||
|
||||
@@ -106,7 +106,7 @@ pub const PersistentPromiseResolver = struct {
|
||||
|
||||
pub fn resolve(self: PersistentPromiseResolver, value: anytype) !void {
|
||||
const context = self.context;
|
||||
const js_value = try context.zigValueToJs(value);
|
||||
const js_value = try context.zigValueToJs(value, .{});
|
||||
|
||||
// resolver.resolve will return null if the promise isn't pending
|
||||
const ok = self.resolver.castToPromiseResolver().resolve(context.v8_context, js_value) orelse return;
|
||||
@@ -117,7 +117,7 @@ pub const PersistentPromiseResolver = struct {
|
||||
|
||||
pub fn reject(self: PersistentPromiseResolver, value: anytype) !void {
|
||||
const context = self.context;
|
||||
const js_value = try context.zigValueToJs(value);
|
||||
const js_value = try context.zigValueToJs(value, .{});
|
||||
|
||||
// resolver.reject will return null if the promise isn't pending
|
||||
const ok = self.resolver.castToPromiseResolver().reject(context.v8_context, js_value) orelse return;
|
||||
|
||||
@@ -58,6 +58,10 @@ _parse_mode: enum { document, fragment },
|
||||
// even thoug we'll create very few (if any) actual *Attributes.
|
||||
_attribute_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute),
|
||||
|
||||
// Same as _atlribute_lookup, but instead of individual attributes, this is for
|
||||
// the return of elements.attributes.
|
||||
_attribute_named_node_map_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute.NamedNodeMap),
|
||||
|
||||
_script_manager: ScriptManager,
|
||||
|
||||
_polyfill_loader: polyfill.Loader = .{},
|
||||
@@ -119,6 +123,7 @@ pub fn deinit(self: *Page) void {
|
||||
log.debug(.page, "page.deinit", .{ .url = self.url });
|
||||
}
|
||||
self.js.deinit();
|
||||
self._script_manager.deinit();
|
||||
}
|
||||
|
||||
fn reset(self: *Page, comptime initializing: bool) !void {
|
||||
@@ -144,6 +149,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
||||
self._parse_state = .pre;
|
||||
self._load_state = .parsing;
|
||||
self._attribute_lookup = .empty;
|
||||
self._attribute_named_node_map_lookup = .empty;
|
||||
self._event_manager = EventManager.init(self);
|
||||
|
||||
self._script_manager = ScriptManager.init(self);
|
||||
@@ -165,7 +171,7 @@ fn registerBackgroundTasks(self: *Page) !void {
|
||||
const Browser = @import("Browser.zig");
|
||||
|
||||
try self.scheduler.add(self._session.browser, struct {
|
||||
fn runMicrotasks(ctx: *anyopaque) ?u32 {
|
||||
fn runMicrotasks(ctx: *anyopaque) !?u32 {
|
||||
const b: *Browser = @ptrCast(@alignCast(ctx));
|
||||
b.runMicrotasks();
|
||||
return 5;
|
||||
@@ -173,7 +179,7 @@ fn registerBackgroundTasks(self: *Page) !void {
|
||||
}.runMicrotasks, 5, .{ .name = "page.microtasks" });
|
||||
|
||||
try self.scheduler.add(self._session.browser, struct {
|
||||
fn runMessageLoop(ctx: *anyopaque) ?u32 {
|
||||
fn runMessageLoop(ctx: *anyopaque) !?u32 {
|
||||
const b: *Browser = @ptrCast(@alignCast(ctx));
|
||||
b.runMessageLoop();
|
||||
return 100;
|
||||
@@ -992,7 +998,7 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi
|
||||
if (@TypeOf(list) == ?*Element.Attribute.List) {
|
||||
// from cloneNode
|
||||
|
||||
var existing = list orelse return ;
|
||||
var existing = list orelse return;
|
||||
|
||||
var attributes = try self.arena.create(Element.Attribute.List);
|
||||
attributes.* = .{};
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
const firstScript = document.querySelector('script');
|
||||
testing.expectEqual('SCRIPT', firstScript.tagName);
|
||||
|
||||
testing.expectEqual(null, document.querySelector('select'));
|
||||
testing.expectEqual(null, document.querySelector('article'));
|
||||
testing.expectEqual(null, document.querySelector('another'));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -170,7 +170,7 @@ pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?u32, fil
|
||||
return DOMTreeWalker.init(root, show, filter, page);
|
||||
}
|
||||
|
||||
// @ZIGDOM what_to_show tristate (null vs undefined vs value)
|
||||
// @ZIGDOM what_to_show tristate (null vs undefined vs value)
|
||||
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
||||
const show = what_to_show orelse NodeFilter.SHOW_ALL;
|
||||
return DOMNodeIterator.init(root, show, filter, page);
|
||||
|
||||
@@ -118,7 +118,7 @@ pub fn getTagNameLower(self: *const Element) []const u8 {
|
||||
.script => "script",
|
||||
.select => "select",
|
||||
.style => "style",
|
||||
.text_area => "textara",
|
||||
.text_area => "textarea",
|
||||
.title => "title",
|
||||
.ul => "ul",
|
||||
.unknown => |e| e._tag_name.str(),
|
||||
@@ -311,9 +311,14 @@ pub fn getAttributeNames(self: *const Element, page: *Page) ![][]const u8 {
|
||||
return attributes.getNames(page);
|
||||
}
|
||||
|
||||
pub fn getAttributeNamedNodeMap(self: *Element) Attribute.NamedNodeMap {
|
||||
const attributes = self._attributes orelse return .{};
|
||||
return .{ ._list = attributes.*, ._element = self };
|
||||
pub fn getAttributeNamedNodeMap(self: *Element, page: *Page) !*Attribute.NamedNodeMap {
|
||||
const gop = try page._attribute_named_node_map_lookup.getOrPut(page.arena, @intFromPtr(self));
|
||||
if (!gop.found_existing) {
|
||||
const attributes = try self.getOrCreateAttributeList(page);
|
||||
const named_node_map = try page._factory.create(Attribute.NamedNodeMap{ ._list = attributes, ._element = self });
|
||||
gop.value_ptr.* = named_node_map;
|
||||
}
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn getStyle(self: *Element, page: *Page) !*CSSStyleProperties {
|
||||
|
||||
@@ -326,7 +326,7 @@ fn needsLowerCasing(name: []const u8) bool {
|
||||
}
|
||||
|
||||
pub const NamedNodeMap = struct {
|
||||
_list: List = .{},
|
||||
_list: *List,
|
||||
|
||||
// Whenever the NamedNodeMap creates an Attribute, it needs to provide the
|
||||
// "ownerElement".
|
||||
@@ -418,6 +418,12 @@ pub const InnerIterator = struct {
|
||||
|
||||
fn formatAttribute(name: []const u8, value: []const u8, writer: *std.Io.Writer) !void {
|
||||
try writer.writeAll(name);
|
||||
|
||||
// Boolean attributes with empty values are serialized without a value
|
||||
if (value.len == 0 and boolean_attributes_lookup.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try writer.writeByte('=');
|
||||
if (value.len == 0) {
|
||||
return writer.writeAll("\"\"");
|
||||
@@ -433,6 +439,37 @@ fn formatAttribute(name: []const u8, value: []const u8, writer: *std.Io.Writer)
|
||||
return writer.writeByte('"');
|
||||
}
|
||||
|
||||
const boolean_attributes = [_][]const u8{
|
||||
"checked",
|
||||
"disabled",
|
||||
"required",
|
||||
"readonly",
|
||||
"multiple",
|
||||
"selected",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"controls",
|
||||
"loop",
|
||||
"muted",
|
||||
"hidden",
|
||||
"async",
|
||||
"defer",
|
||||
"novalidate",
|
||||
"formnovalidate",
|
||||
"ismap",
|
||||
"reversed",
|
||||
"default",
|
||||
"open",
|
||||
};
|
||||
|
||||
const boolean_attributes_lookup = std.StaticStringMap(void).initComptime(blk: {
|
||||
var entries: [boolean_attributes.len]struct { []const u8, void } = undefined;
|
||||
for (boolean_attributes, 0..) |attr, i| {
|
||||
entries[i] = .{ attr, {} };
|
||||
}
|
||||
break :blk entries;
|
||||
});
|
||||
|
||||
fn writeEscapedAttributeValue(value: []const u8, first_offset: usize, writer: *std.Io.Writer) !void {
|
||||
// Write everything before the first special character
|
||||
try writer.writeAll(value[0..first_offset]);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const std = @import("std");
|
||||
|
||||
const log = @import("../../../log.zig");
|
||||
const Http = @import("../../../http/Http.zig");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
@@ -8,15 +11,64 @@ const Response = @import("Response.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
_arena: Allocator,
|
||||
_promise: js.Promise,
|
||||
_has_response: bool,
|
||||
const Fetch = @This();
|
||||
|
||||
_page: *Page,
|
||||
_response: std.ArrayList(u8),
|
||||
_resolver: js.PersistentPromiseResolver,
|
||||
|
||||
pub const Input = Request.Input;
|
||||
|
||||
// @ZIGDOM just enough to get campire demo working
|
||||
pub fn init(input: Input, page: *Page) !js.Promise {
|
||||
// @ZIGDOM
|
||||
_ = input;
|
||||
_ = page;
|
||||
return undefined;
|
||||
const request = try Request.init(input, page);
|
||||
|
||||
const fetch = try page.arena.create(Fetch);
|
||||
fetch.* = .{
|
||||
._page = page,
|
||||
._response = .empty,
|
||||
._resolver = try page.js.createPromiseResolver(.page),
|
||||
};
|
||||
|
||||
const http_client = page._session.browser.http_client;
|
||||
const headers = try http_client.newHeaders();
|
||||
|
||||
try http_client.request(.{
|
||||
.ctx = fetch,
|
||||
.url = request._url,
|
||||
.method = .GET,
|
||||
.headers = headers,
|
||||
.cookie_jar = &page._session.cookie_jar,
|
||||
.resource_type = .fetch,
|
||||
.header_callback = httpHeaderDoneCallback,
|
||||
.data_callback = httpDataCallback,
|
||||
.done_callback = httpDoneCallback,
|
||||
.error_callback = httpErrorCallback,
|
||||
});
|
||||
return fetch._resolver.promise();
|
||||
}
|
||||
|
||||
fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void {
|
||||
const self: *Fetch = @ptrCast(@alignCast(transfer.ctx));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
||||
const self: *Fetch = @ptrCast(@alignCast(transfer.ctx));
|
||||
try self._response.appendSlice(self._page.arena, data);
|
||||
}
|
||||
|
||||
fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
const self: *Fetch = @ptrCast(@alignCast(ctx));
|
||||
|
||||
const page = self._page;
|
||||
const res = try Response.initFromFetch(page.arena, self._response.items, page);
|
||||
return self._resolver.resolve(res);
|
||||
}
|
||||
|
||||
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
const self: *Fetch = @ptrCast(@alignCast(ctx));
|
||||
self._resolver.reject(@errorName(err)) catch |inner| {
|
||||
log.err(.bug, "failed to reject", .{ .source = "fetch", .err = inner, .reject = err });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn getJson(self: *Response, page: *Page) !js.Promise {
|
||||
) catch |err| {
|
||||
return page.js.rejectPromise(.{@errorName(err)});
|
||||
};
|
||||
return page.js.resolvePromise(.{value});
|
||||
return page.js.resolvePromise(value);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -8,8 +8,8 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const FetchOpts = struct {
|
||||
wait_ms: u32 = 5000,
|
||||
dump_opts: dump.Opts,
|
||||
dump_file: ?std.fs.File = null,
|
||||
dump: dump.Opts,
|
||||
writer: ?*std.Io.Writer = null,
|
||||
};
|
||||
pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void {
|
||||
const Browser = @import("browser/Browser.zig");
|
||||
@@ -40,12 +40,9 @@ pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void {
|
||||
_ = try page.navigate(url, .{});
|
||||
_ = session.fetchWait(opts.wait_ms);
|
||||
|
||||
const file = opts.dump_file orelse return;
|
||||
|
||||
var buf: [4096]u8 = undefined;
|
||||
var writer = file.writer(&buf);
|
||||
try dump.deep(page.document.asNode(), opts.dump_opts, &writer.interface);
|
||||
try writer.interface.flush();
|
||||
const writer = opts.writer orelse return;
|
||||
try dump.deep(page.document.asNode(), opts.dump, writer);
|
||||
try writer.flush();
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -352,7 +352,7 @@ fn elapsed() struct { time: f64, unit: []const u8 } {
|
||||
}
|
||||
|
||||
const datetime = @import("datetime.zig");
|
||||
fn timestamp(mode: datetime.TimestampMode) u64 {
|
||||
fn timestamp(comptime mode: datetime.TimestampMode) u64 {
|
||||
if (comptime @import("builtin").is_test) {
|
||||
return 1739795092929;
|
||||
}
|
||||
|
||||
21
src/main.zig
21
src/main.zig
@@ -38,19 +38,17 @@ pub fn main() !void {
|
||||
if (gpa.detectLeaks()) std.posix.exit(1);
|
||||
};
|
||||
|
||||
var global_allocator = lp.GlobalAllocator.init(allocator);
|
||||
|
||||
// arena for main-specific allocations
|
||||
var main_arena = std.heap.ArenaAllocator.init(global_allocator.allocator());
|
||||
var main_arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer main_arena.deinit();
|
||||
|
||||
run(&global_allocator, main_arena.allocator()) catch |err| {
|
||||
run(allocator, main_arena.allocator()) catch |err| {
|
||||
log.fatal(.app, "exit", .{ .err = err });
|
||||
std.posix.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
fn run(allocator: *lp.GlobalAllocator, main_arena: Allocator) !void {
|
||||
fn run(allocator: Allocator, main_arena: Allocator) !void {
|
||||
const args = try parseArgs(main_arena);
|
||||
|
||||
switch (args.mode) {
|
||||
@@ -102,7 +100,6 @@ fn run(allocator: *lp.GlobalAllocator, main_arena: Allocator) !void {
|
||||
|
||||
switch (args.mode) {
|
||||
.serve => {
|
||||
log.fatal(.app, "serve not not supported in the zigdom branch yet\n", .{});
|
||||
return;
|
||||
// @ZIGDOM-CDP
|
||||
// .serve => |opts| {
|
||||
@@ -131,13 +128,15 @@ fn run(allocator: *lp.GlobalAllocator, main_arena: Allocator) !void {
|
||||
var fetch_opts = lp.FetchOpts{
|
||||
.wait_ms = 5000,
|
||||
.dump = .{
|
||||
.with_base = opts.with_base,
|
||||
.with_base = opts.withbase,
|
||||
.strip_mode = opts.strip_mode,
|
||||
},
|
||||
};
|
||||
|
||||
var stdout = std.fs.File.stdout();
|
||||
var writer = stdout.writer(&.{});
|
||||
if (opts.dump) {
|
||||
fetch_opts.dump_file = std.fs.File.stdout();
|
||||
fetch_opts.writer = &writer.interface;
|
||||
}
|
||||
|
||||
lp.fetch(app, url, fetch_opts) catch |err| {
|
||||
@@ -245,7 +244,7 @@ const Command = struct {
|
||||
};
|
||||
|
||||
const Fetch = struct {
|
||||
url: []const u8,
|
||||
url: [:0]const u8,
|
||||
dump: bool = false,
|
||||
common: Common,
|
||||
withbase: bool = false,
|
||||
@@ -513,7 +512,7 @@ fn parseFetchArgs(
|
||||
) !Command.Fetch {
|
||||
var dump: bool = false;
|
||||
var withbase: bool = false;
|
||||
var url: ?[]const u8 = null;
|
||||
var url: ?[:0]const u8 = null;
|
||||
var common: Command.Common = .{};
|
||||
var strip_mode: lp.dump.Opts.StripMode = .{};
|
||||
|
||||
@@ -576,7 +575,7 @@ fn parseFetchArgs(
|
||||
log.fatal(.app, "duplicate fetch url", .{ .help = "only 1 URL can be specified" });
|
||||
return error.TooManyURLs;
|
||||
}
|
||||
url = try allocator.dupe(u8, opt);
|
||||
url = try allocator.dupeZ(u8, opt);
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
|
||||
Reference in New Issue
Block a user