mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 22:53:28 +00:00
experiment with executing custom element connectedCallback
This commit is contained in:
@@ -127,22 +127,12 @@ pub const Document = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn _createElement(self: *parser.Document, tag_name: []const u8, page: *Page) !CreateElementResult {
|
pub fn _createElement(self: *parser.Document, tag_name: []const u8, page: *Page) !CreateElementResult {
|
||||||
const custom_element = page.window.custom_elements._get(tag_name) orelse {
|
if (try page.window.custom_elements.newInstance(tag_name)) |ce| {
|
||||||
const e = try parser.documentCreateElement(self, tag_name);
|
return .{ .custom = ce };
|
||||||
return .{ .element = try Element.toInterface(e) };
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var result: Env.Function.Result = undefined;
|
const e = try parser.documentCreateElement(self, tag_name);
|
||||||
const js_obj = custom_element.newInstance(&result) catch |err| {
|
return .{ .element = try Element.toInterface(e) };
|
||||||
log.fatal(.user_script, "newInstance error", .{
|
|
||||||
.err = result.exception,
|
|
||||||
.stack = result.stack,
|
|
||||||
.tag_name = tag_name,
|
|
||||||
.source = "createElement",
|
|
||||||
});
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
return .{ .custom = js_obj };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion {
|
pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion {
|
||||||
|
|||||||
@@ -2189,12 +2189,12 @@ pub inline fn documentCreateAttributeNS(doc: *Document, ns: []const u8, qname: [
|
|||||||
return attr.?;
|
return attr.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn documentSetScriptAddedCallback(
|
pub fn documentSetElementAddedCallback(
|
||||||
doc: *Document,
|
doc: *Document,
|
||||||
ctx: *anyopaque,
|
ctx: *anyopaque,
|
||||||
callback: c.dom_script_added_callback,
|
callback: c.dom_element_added_callback,
|
||||||
) void {
|
) void {
|
||||||
c._dom_document_set_script_added_callback(doc, ctx, callback);
|
c._dom_document_set_element_added_callback(doc, ctx, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DocumentHTML
|
// DocumentHTML
|
||||||
|
|||||||
@@ -297,16 +297,17 @@ pub const Page = struct {
|
|||||||
self.window.setStorageShelf(
|
self.window.setStorageShelf(
|
||||||
try self.session.storage_shed.getOrPut(try self.origin(self.arena)),
|
try self.session.storage_shed.getOrPut(try self.origin(self.arena)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// we want to be notified of any dynamically added script tags
|
||||||
|
// so that we can load the script. Or dynamically added custom elements
|
||||||
|
// for their lifecycle callbacks.
|
||||||
|
parser.documentSetElementAddedCallback(doc, self, elementAddedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn processHTMLDoc(self: *Page) !void {
|
fn processHTMLDoc(self: *Page) !void {
|
||||||
const html_doc = self.window.document;
|
const html_doc = self.window.document;
|
||||||
const doc = parser.documentHTMLToDocument(html_doc);
|
const doc = parser.documentHTMLToDocument(html_doc);
|
||||||
|
|
||||||
// we want to be notified of any dynamically added script tags
|
|
||||||
// so that we can load the script
|
|
||||||
parser.documentSetScriptAddedCallback(doc, self, scriptAddedCallback);
|
|
||||||
|
|
||||||
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
|
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
|
||||||
_ = try parser.eventTargetAddEventListener(
|
_ = try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Element, document_element),
|
parser.toEventTarget(parser.Element, document_element),
|
||||||
@@ -817,6 +818,43 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn elementAdded(self: *Page, element: *parser.Element) !void {
|
||||||
|
if (self.delayed_navigation) {
|
||||||
|
// if we're planning on navigating to another page, we can skip whatever
|
||||||
|
// this is.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(element)))) {
|
||||||
|
.script => {
|
||||||
|
var script = Script.init(element, self) catch |err| {
|
||||||
|
log.warn(.browser, "script added", .{ .err = err });
|
||||||
|
return;
|
||||||
|
} orelse return;
|
||||||
|
_ = self.evalScript(&script);
|
||||||
|
},
|
||||||
|
.undef => {
|
||||||
|
// a custom element
|
||||||
|
const js_obj = self.main_context.getJsObject(element) orelse return;
|
||||||
|
|
||||||
|
// @memory
|
||||||
|
// getFunction, and more generally Env.Function always create
|
||||||
|
// a Persisted Object. But, in cases like this, we don't need
|
||||||
|
// it to persist.
|
||||||
|
const func = (try js_obj.getFunction("connectedCallback")) orelse return;
|
||||||
|
|
||||||
|
var result: Env.Function.Result = undefined;
|
||||||
|
func.tryCallWithThis(void, js_obj, .{}, &result) catch {
|
||||||
|
log.warn(.user_script, "connected callback", .{
|
||||||
|
.err = result.exception,
|
||||||
|
.stack = result.stack,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const DelayedNavigation = struct {
|
const DelayedNavigation = struct {
|
||||||
@@ -1090,24 +1128,22 @@ fn timestamp() u32 {
|
|||||||
return @intCast(ts.sec);
|
return @intCast(ts.sec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A callback from libdom whenever a script tag is added to the DOM.
|
// A callback from libdom whenever an html element is added to the DOM.
|
||||||
|
// Note that "added" could mean that it was removed from one parent and re-added
|
||||||
|
// to another, which is how MOST APIs implement "move" (corrently so).
|
||||||
|
//
|
||||||
|
// The only API which seems to actual "move" is Element.moveBefore, which we
|
||||||
|
// don't currently implement, but should support in general, and should handle
|
||||||
|
// specifically here.
|
||||||
// element is guaranteed to be a script element.
|
// element is guaranteed to be a script element.
|
||||||
// The script tag might not have a src. It might be any attribute, like
|
// The script tag might not have a src. It might have any attribute, like
|
||||||
// `nomodule`, `defer` and `async`. `Script.init` will return null on `nomodule`
|
// `nomodule`, `defer` and `async`. `Script.init` will return null on `nomodule`
|
||||||
// so that's handled. And because we're only executing the inline <script> tags
|
// so that's handled. And because we're only executing the inline <script> tags
|
||||||
// after the document is loaded, it's ok to execute any async and defer scripts
|
// after the document is loaded, it's ok to execute any async and defer scripts
|
||||||
// immediately.
|
// immediately.
|
||||||
pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void {
|
pub export fn elementAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void {
|
||||||
const self: *Page = @alignCast(@ptrCast(ctx.?));
|
const self: *Page = @alignCast(@ptrCast(ctx.?));
|
||||||
if (self.delayed_navigation) {
|
self.elementAdded(element.?) catch |err| {
|
||||||
// if we're planning on navigating to another page, don't run this script
|
log.warn(.browser, "element added callback", .{ .err = err });
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
var script = Script.init(element.?, self) catch |err| {
|
|
||||||
log.warn(.browser, "script added init error", .{ .err = err });
|
|
||||||
return;
|
|
||||||
} orelse return;
|
|
||||||
|
|
||||||
_ = self.evalScript(&script);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ const v8 = @import("v8");
|
|||||||
const Env = @import("../env.zig").Env;
|
const Env = @import("../env.zig").Env;
|
||||||
const Page = @import("../page.zig").Page;
|
const Page = @import("../page.zig").Page;
|
||||||
|
|
||||||
const Element = @import("../dom/element.zig").Element;
|
|
||||||
|
|
||||||
pub const CustomElementRegistry = struct {
|
pub const CustomElementRegistry = struct {
|
||||||
// tag_name -> Function
|
// tag_name -> Function
|
||||||
lookup: std.StringHashMapUnmanaged(Env.Function) = .empty,
|
lookup: std.StringHashMapUnmanaged(Env.Function) = .empty,
|
||||||
@@ -46,6 +44,26 @@ pub const CustomElementRegistry = struct {
|
|||||||
pub fn _get(self: *CustomElementRegistry, name: []const u8) ?Env.Function {
|
pub fn _get(self: *CustomElementRegistry, name: []const u8) ?Env.Function {
|
||||||
return self.lookup.get(name);
|
return self.lookup.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn newInstance(self: *const CustomElementRegistry, tag_name: []const u8) !?Env.JsObject {
|
||||||
|
const func = self.lookup.get(tag_name) orelse return null;
|
||||||
|
|
||||||
|
var result: Env.Function.Result = undefined;
|
||||||
|
const js_obj = func.newInstance(&result) catch |err| {
|
||||||
|
log.fatal(.user_script, "newInstance error", .{
|
||||||
|
.err = result.exception,
|
||||||
|
.stack = result.stack,
|
||||||
|
.tag_name = tag_name,
|
||||||
|
.source = "createElement",
|
||||||
|
});
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is associated with an HTML element, which, at the very least
|
||||||
|
// is going to be libdom node. It will outlive this call, and thus needs
|
||||||
|
// to be persisted.
|
||||||
|
return try js_obj.persist();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
@@ -63,7 +81,11 @@ test "Browser.CustomElementRegistry" {
|
|||||||
\\ class MyElement extends HTMLElement {
|
\\ class MyElement extends HTMLElement {
|
||||||
\\ constructor() {
|
\\ constructor() {
|
||||||
\\ super();
|
\\ super();
|
||||||
\\ this.textContent = 'Hello World';
|
\\ this.textContent = 'created';
|
||||||
|
\\ }
|
||||||
|
\\
|
||||||
|
\\ connectedCallback() {
|
||||||
|
\\ this.textContent = 'connected';
|
||||||
\\ }
|
\\ }
|
||||||
\\ }
|
\\ }
|
||||||
,
|
,
|
||||||
@@ -80,7 +102,9 @@ test "Browser.CustomElementRegistry" {
|
|||||||
.{ "el instanceof MyElement", "true" },
|
.{ "el instanceof MyElement", "true" },
|
||||||
.{ "el instanceof HTMLElement", "true" },
|
.{ "el instanceof HTMLElement", "true" },
|
||||||
.{ "el.tagName", "MY-ELEMENT" },
|
.{ "el.tagName", "MY-ELEMENT" },
|
||||||
.{ "el.textContent", "Hello World" },
|
.{ "el.textContent", "created" },
|
||||||
|
.{ "document.getElementsByTagName('body')[0].append(el)", null },
|
||||||
|
.{ "el.textContent", "connected" },
|
||||||
|
|
||||||
// Create element via HTML parsing
|
// Create element via HTML parsing
|
||||||
// .{ "document.body.innerHTML = '<my-element></my-element>'", "undefined" },
|
// .{ "document.body.innerHTML = '<my-element></my-element>'", "undefined" },
|
||||||
|
|||||||
@@ -1158,7 +1158,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!js_value.isArray()) {
|
if (!js_value.isArray()) {
|
||||||
return .{.invalid = {}};
|
return .{ .invalid = {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
// This can get tricky.
|
// This can get tricky.
|
||||||
@@ -1196,6 +1196,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
return .{ .invalid = {} };
|
return .{ .invalid = {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getJsObject(self: *JsContext, zig_value: *anyopaque) ?JsObject {
|
||||||
|
const po = self.identity_map.get(@intFromPtr(zig_value)) orelse return null;
|
||||||
|
return .{
|
||||||
|
.js_context = self,
|
||||||
|
.js_obj = po.castToObject(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Callback from V8, asking us to load a module. The "specifier" is
|
// Callback from V8, asking us to load a module. The "specifier" is
|
||||||
// the src of the module to load.
|
// the src of the module to load.
|
||||||
fn resolveModuleCallback(
|
fn resolveModuleCallback(
|
||||||
|
|||||||
2
vendor/netsurf/libdom
vendored
2
vendor/netsurf/libdom
vendored
Submodule vendor/netsurf/libdom updated: 614187b0aa...73d1bc9f23
Reference in New Issue
Block a user