mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Merge pull request #847 from lightpanda-io/name-property-handler
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
enable conditionnal loading for polyfill
This commit is contained in:
@@ -95,6 +95,8 @@ pub const Page = struct {
|
|||||||
|
|
||||||
state_pool: *std.heap.MemoryPool(State),
|
state_pool: *std.heap.MemoryPool(State),
|
||||||
|
|
||||||
|
polyfill_loader: polyfill.Loader = .{},
|
||||||
|
|
||||||
pub fn init(self: *Page, arena: Allocator, session: *Session) !void {
|
pub fn init(self: *Page, arena: Allocator, session: *Session) !void {
|
||||||
const browser = session.browser;
|
const browser = session.browser;
|
||||||
self.* = .{
|
self.* = .{
|
||||||
@@ -117,10 +119,7 @@ pub const Page = struct {
|
|||||||
}),
|
}),
|
||||||
.main_context = undefined,
|
.main_context = undefined,
|
||||||
};
|
};
|
||||||
self.main_context = try session.executor.createJsContext(&self.window, self, self, true);
|
self.main_context = try session.executor.createJsContext(&self.window, self, self, true, Env.GlobalMissingCallback.init(&self.polyfill_loader));
|
||||||
|
|
||||||
// load polyfills
|
|
||||||
try polyfill.load(self.arena, self.main_context);
|
|
||||||
|
|
||||||
// message loop must run only non-test env
|
// message loop must run only non-test env
|
||||||
if (comptime !builtin.is_test) {
|
if (comptime !builtin.is_test) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ test "Browser.fetch" {
|
|||||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
try @import("polyfill.zig").load(testing.allocator, runner.page.main_context);
|
try @import("polyfill.zig").Loader.load("fetch", source, runner.page.main_context);
|
||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{
|
.{
|
||||||
|
|||||||
@@ -23,25 +23,69 @@ const log = @import("../../log.zig");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Env = @import("../env.zig").Env;
|
const Env = @import("../env.zig").Env;
|
||||||
|
|
||||||
const modules = [_]struct {
|
pub const Loader = struct {
|
||||||
name: []const u8,
|
state: enum { empty, loading } = .empty,
|
||||||
source: []const u8,
|
|
||||||
}{
|
|
||||||
.{ .name = "polyfill-fetch", .source = @import("fetch.zig").source },
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn load(allocator: Allocator, js_context: *Env.JsContext) !void {
|
done: struct {
|
||||||
var try_catch: Env.TryCatch = undefined;
|
fetch: bool = false,
|
||||||
try_catch.init(js_context);
|
} = .{},
|
||||||
defer try_catch.deinit();
|
|
||||||
|
|
||||||
for (modules) |m| {
|
pub fn load(name: []const u8, source: []const u8, js_context: *Env.JsContext) !void {
|
||||||
_ = js_context.exec(m.source, m.name) catch |err| {
|
var try_catch: Env.TryCatch = undefined;
|
||||||
if (try try_catch.err(allocator)) |msg| {
|
try_catch.init(js_context);
|
||||||
defer allocator.free(msg);
|
defer try_catch.deinit();
|
||||||
log.fatal(.app, "polyfill error", .{ .name = m.name, .err = msg });
|
|
||||||
|
_ = js_context.exec(source, name) catch |err| {
|
||||||
|
if (try try_catch.err(js_context.call_arena)) |msg| {
|
||||||
|
log.fatal(.app, "polyfill error", .{ .name = name, .err = msg });
|
||||||
}
|
}
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn missing(self: *Loader, name: []const u8, js_context: *Env.JsContext) bool {
|
||||||
|
// Avoid recursive calls during polyfill loading.
|
||||||
|
if (self.state == .loading) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.done.fetch and isFetch(name)) {
|
||||||
|
self.state = .loading;
|
||||||
|
defer self.state = .empty;
|
||||||
|
|
||||||
|
const _name = "fetch";
|
||||||
|
const source = @import("fetch.zig").source;
|
||||||
|
log.debug(.polyfill, "dynamic load", .{ .property = name });
|
||||||
|
load(_name, source, js_context) catch |err| {
|
||||||
|
log.fatal(.app, "polyfill load", .{ .name = name, .err = err });
|
||||||
|
};
|
||||||
|
|
||||||
|
// load the polyfill once.
|
||||||
|
self.done.fetch = true;
|
||||||
|
|
||||||
|
// We return false here: We want v8 to continue the calling chain
|
||||||
|
// to finally find the polyfill we just inserted. If we want to
|
||||||
|
// return false and stops the call chain, we have to use
|
||||||
|
// `info.GetReturnValue.Set()` function, or `undefined` will be
|
||||||
|
// returned immediately.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime builtin.mode == .Debug) {
|
||||||
|
log.debug(.unknown_prop, "unkown global property", .{
|
||||||
|
.info = "but the property can exist in pure JS",
|
||||||
|
.property = name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isFetch(name: []const u8) bool {
|
||||||
|
if (std.mem.eql(u8, name, "fetch")) return true;
|
||||||
|
if (std.mem.eql(u8, name, "Request")) return true;
|
||||||
|
if (std.mem.eql(u8, name, "Response")) return true;
|
||||||
|
if (std.mem.eql(u8, name, "Headers")) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const Inspector = @import("../browser/env.zig").Env.Inspector;
|
|||||||
const Incrementing = @import("../id.zig").Incrementing;
|
const Incrementing = @import("../id.zig").Incrementing;
|
||||||
const Notification = @import("../notification.zig").Notification;
|
const Notification = @import("../notification.zig").Notification;
|
||||||
|
|
||||||
|
const polyfill = @import("../browser/polyfill/polyfill.zig");
|
||||||
|
|
||||||
pub const URL_BASE = "chrome://newtab/";
|
pub const URL_BASE = "chrome://newtab/";
|
||||||
pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
|
pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
|
||||||
|
|
||||||
@@ -554,6 +556,10 @@ const IsolatedWorld = struct {
|
|||||||
executor: Env.ExecutionWorld,
|
executor: Env.ExecutionWorld,
|
||||||
grant_universal_access: bool,
|
grant_universal_access: bool,
|
||||||
|
|
||||||
|
// Polyfill loader for the isolated world.
|
||||||
|
// We want to load polyfill in the world's context.
|
||||||
|
polyfill_loader: polyfill.Loader = .{},
|
||||||
|
|
||||||
pub fn deinit(self: *IsolatedWorld) void {
|
pub fn deinit(self: *IsolatedWorld) void {
|
||||||
self.executor.deinit();
|
self.executor.deinit();
|
||||||
}
|
}
|
||||||
@@ -569,7 +575,13 @@ const IsolatedWorld = struct {
|
|||||||
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
|
// 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 {
|
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
|
||||||
if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
|
if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
|
||||||
_ = try self.executor.createJsContext(&page.window, page, {}, false);
|
_ = try self.executor.createJsContext(
|
||||||
|
&page.window,
|
||||||
|
page,
|
||||||
|
{},
|
||||||
|
false,
|
||||||
|
Env.GlobalMissingCallback.init(&self.polyfill_loader),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -284,9 +284,6 @@ pub fn pageCreated(bc: anytype, page: *Page) !void {
|
|||||||
if (bc.isolated_world) |*isolated_world| {
|
if (bc.isolated_world) |*isolated_world| {
|
||||||
// We need to recreate the isolated world context
|
// We need to recreate the isolated world context
|
||||||
try isolated_world.createContext(page);
|
try isolated_world.createContext(page);
|
||||||
|
|
||||||
const polyfill = @import("../../browser/polyfill/polyfill.zig");
|
|
||||||
try polyfill.load(bc.arena, &isolated_world.executor.js_context.?);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,13 @@ pub const Scope = enum {
|
|||||||
unknown_prop,
|
unknown_prop,
|
||||||
web_api,
|
web_api,
|
||||||
xhr,
|
xhr,
|
||||||
|
polyfill,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Opts = struct {
|
const Opts = struct {
|
||||||
format: Format = if (is_debug) .pretty else .logfmt,
|
format: Format = if (is_debug) .pretty else .logfmt,
|
||||||
level: Level = if (is_debug) .info else .warn,
|
level: Level = if (is_debug) .info else .warn,
|
||||||
filter_scopes: []const Scope = &.{.unknown_prop},
|
filter_scopes: []const Scope = &.{},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub var opts = Opts{};
|
pub var opts = Opts{};
|
||||||
|
|||||||
@@ -126,8 +126,6 @@ fn run(
|
|||||||
});
|
});
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
try polyfill.load(arena, runner.page.main_context);
|
|
||||||
|
|
||||||
// loop over the scripts.
|
// loop over the scripts.
|
||||||
const doc = parser.documentHTMLToDocument(runner.page.window.document);
|
const doc = parser.documentHTMLToDocument(runner.page.window.document);
|
||||||
const scripts = try parser.documentGetElementsByTagName(doc, "script");
|
const scripts = try parser.documentGetElementsByTagName(doc, "script");
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
// when the handle_scope is freed.
|
// when the handle_scope is freed.
|
||||||
// We also maintain our own "context_arena" which allows us to have
|
// We also maintain our own "context_arena" which allows us to have
|
||||||
// all page related memory easily managed.
|
// all page related memory easily managed.
|
||||||
pub fn createJsContext(self: *ExecutionWorld, global: anytype, state: State, module_loader: anytype, enter: bool) !*JsContext {
|
pub fn createJsContext(self: *ExecutionWorld, global: anytype, state: State, module_loader: anytype, enter: bool, global_callback: ?GlobalMissingCallback) !*JsContext {
|
||||||
std.debug.assert(self.js_context == null);
|
std.debug.assert(self.js_context == null);
|
||||||
|
|
||||||
const ModuleLoader = switch (@typeInfo(@TypeOf(module_loader))) {
|
const ModuleLoader = switch (@typeInfo(@TypeOf(module_loader))) {
|
||||||
@@ -399,6 +399,30 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
const global_template = js_global.getInstanceTemplate();
|
const global_template = js_global.getInstanceTemplate();
|
||||||
global_template.setInternalFieldCount(1);
|
global_template.setInternalFieldCount(1);
|
||||||
|
|
||||||
|
// Configure the missing property callback on the global
|
||||||
|
// object.
|
||||||
|
if (global_callback != null) {
|
||||||
|
const configuration = v8.NamedPropertyHandlerConfiguration{
|
||||||
|
.getter = struct {
|
||||||
|
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
|
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||||
|
const _isolate = info.getIsolate();
|
||||||
|
const v8_context = _isolate.getCurrentContext();
|
||||||
|
|
||||||
|
const js_context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||||
|
|
||||||
|
const property = valueToString(js_context.call_arena, .{ .handle = c_name.? }, _isolate, v8_context) catch "???";
|
||||||
|
if (js_context.global_callback.?.missing(property, js_context)) {
|
||||||
|
return v8.Intercepted.Yes;
|
||||||
|
}
|
||||||
|
return v8.Intercepted.No;
|
||||||
|
}
|
||||||
|
}.callback,
|
||||||
|
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
|
||||||
|
};
|
||||||
|
global_template.setNamedProperty(configuration, null);
|
||||||
|
}
|
||||||
|
|
||||||
// All the FunctionTemplates that we created and setup in Env.init
|
// All the FunctionTemplates that we created and setup in Env.init
|
||||||
// are now going to get associated with our global instance.
|
// are now going to get associated with our global instance.
|
||||||
inline for (Types, 0..) |s, i| {
|
inline for (Types, 0..) |s, i| {
|
||||||
@@ -481,6 +505,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
.ptr = safe_module_loader,
|
.ptr = safe_module_loader,
|
||||||
.func = ModuleLoader.fetchModuleSource,
|
.func = ModuleLoader.fetchModuleSource,
|
||||||
},
|
},
|
||||||
|
.global_callback = global_callback,
|
||||||
};
|
};
|
||||||
|
|
||||||
var js_context = &self.js_context.?;
|
var js_context = &self.js_context.?;
|
||||||
@@ -633,6 +658,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
// necessary to lookup/store the dependent module in the module_cache.
|
// necessary to lookup/store the dependent module in the module_cache.
|
||||||
module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty,
|
module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty,
|
||||||
|
|
||||||
|
// Global callback is called on missing property.
|
||||||
|
global_callback: ?GlobalMissingCallback = null,
|
||||||
|
|
||||||
const ModuleLoader = struct {
|
const ModuleLoader = struct {
|
||||||
ptr: *anyopaque,
|
ptr: *anyopaque,
|
||||||
func: *const fn (ptr: *anyopaque, specifier: []const u8) anyerror!?[]const u8,
|
func: *const fn (ptr: *anyopaque, specifier: []const u8) anyerror!?[]const u8,
|
||||||
@@ -2265,11 +2293,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
|
|
||||||
fn generateNamedIndexer(comptime Struct: type, template_proto: v8.ObjectTemplate) void {
|
fn generateNamedIndexer(comptime Struct: type, template_proto: v8.ObjectTemplate) void {
|
||||||
if (@hasDecl(Struct, "named_get") == false) {
|
if (@hasDecl(Struct, "named_get") == false) {
|
||||||
if (comptime builtin.mode == .Debug) {
|
|
||||||
if (log.enabled(.unknown_prop, .debug)) {
|
|
||||||
generateDebugNamedIndexer(Struct, template_proto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2329,31 +2352,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
template_proto.setNamedProperty(configuration, null);
|
template_proto.setNamedProperty(configuration, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generateDebugNamedIndexer(comptime Struct: type, template_proto: v8.ObjectTemplate) void {
|
|
||||||
const configuration = v8.NamedPropertyHandlerConfiguration{
|
|
||||||
.getter = struct {
|
|
||||||
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
|
||||||
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
|
||||||
const isolate = info.getIsolate();
|
|
||||||
const v8_context = isolate.getCurrentContext();
|
|
||||||
|
|
||||||
const js_context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
|
||||||
|
|
||||||
const property = valueToString(js_context.call_arena, .{ .handle = c_name.? }, isolate, v8_context) catch "???";
|
|
||||||
log.debug(.unknown_prop, "unkown property", .{ .@"struct" = @typeName(Struct), .property = property });
|
|
||||||
return v8.Intercepted.No;
|
|
||||||
}
|
|
||||||
}.callback,
|
|
||||||
|
|
||||||
// This is really cool. Without this, we'd intercept _all_ properties
|
|
||||||
// even those explicitly set. So, node.length for example would get routed
|
|
||||||
// to our `named_get`, rather than a `get_length`. This might be
|
|
||||||
// useful if we run into a type that we can't model properly in Zig.
|
|
||||||
.flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking,
|
|
||||||
};
|
|
||||||
template_proto.setNamedProperty(configuration, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generateUndetectable(comptime Struct: type, template: v8.ObjectTemplate) void {
|
fn generateUndetectable(comptime Struct: type, template: v8.ObjectTemplate) void {
|
||||||
const has_js_call_as_function = @hasDecl(Struct, "jsCallAsFunction");
|
const has_js_call_as_function = @hasDecl(Struct, "jsCallAsFunction");
|
||||||
|
|
||||||
@@ -2577,6 +2575,35 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
self.callScopeEndFn(self.ptr);
|
self.callScopeEndFn(self.ptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Callback called on global's property mssing.
|
||||||
|
// Return true to intercept the exectution or false to let the call
|
||||||
|
// continue the chain.
|
||||||
|
pub const GlobalMissingCallback = struct {
|
||||||
|
ptr: *anyopaque,
|
||||||
|
missingFn: *const fn (ptr: *anyopaque, name: []const u8, ctx: *JsContext) bool,
|
||||||
|
|
||||||
|
pub fn init(ptr: anytype) GlobalMissingCallback {
|
||||||
|
const T = @TypeOf(ptr);
|
||||||
|
const ptr_info = @typeInfo(T);
|
||||||
|
|
||||||
|
const gen = struct {
|
||||||
|
pub fn missing(pointer: *anyopaque, name: []const u8, ctx: *JsContext) bool {
|
||||||
|
const self: T = @ptrCast(@alignCast(pointer));
|
||||||
|
return ptr_info.pointer.child.missing(self, name, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.ptr = ptr,
|
||||||
|
.missingFn = gen.missing,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn missing(self: GlobalMissingCallback, name: []const u8, ctx: *JsContext) bool {
|
||||||
|
return self.missingFn(self.ptr, name, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty
|
|||||||
state,
|
state,
|
||||||
{},
|
{},
|
||||||
true,
|
true,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user