mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
Compare commits
1 Commits
a4d290ba58
...
custom_ele
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7f209b70a |
@@ -127,22 +127,12 @@ pub const Document = struct {
|
||||
};
|
||||
|
||||
pub fn _createElement(self: *parser.Document, tag_name: []const u8, page: *Page) !CreateElementResult {
|
||||
const custom_element = page.window.custom_elements._get(tag_name) orelse {
|
||||
const e = try parser.documentCreateElement(self, tag_name);
|
||||
return .{ .element = try Element.toInterface(e) };
|
||||
};
|
||||
if (try page.window.custom_elements.newInstance(tag_name)) |ce| {
|
||||
return .{ .custom = ce };
|
||||
}
|
||||
|
||||
var result: Env.Function.Result = undefined;
|
||||
const js_obj = custom_element.newInstance(&result) catch |err| {
|
||||
log.fatal(.user_script, "newInstance error", .{
|
||||
.err = result.exception,
|
||||
.stack = result.stack,
|
||||
.tag_name = tag_name,
|
||||
.source = "createElement",
|
||||
});
|
||||
return err;
|
||||
};
|
||||
return .{ .custom = js_obj };
|
||||
const e = try parser.documentCreateElement(self, tag_name);
|
||||
return .{ .element = try Element.toInterface(e) };
|
||||
}
|
||||
|
||||
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.?;
|
||||
}
|
||||
|
||||
pub fn documentSetScriptAddedCallback(
|
||||
pub fn documentSetElementAddedCallback(
|
||||
doc: *Document,
|
||||
ctx: *anyopaque,
|
||||
callback: c.dom_script_added_callback,
|
||||
callback: c.dom_element_added_callback,
|
||||
) void {
|
||||
c._dom_document_set_script_added_callback(doc, ctx, callback);
|
||||
c._dom_document_set_element_added_callback(doc, ctx, callback);
|
||||
}
|
||||
|
||||
// DocumentHTML
|
||||
|
||||
@@ -297,16 +297,17 @@ pub const Page = struct {
|
||||
self.window.setStorageShelf(
|
||||
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 {
|
||||
const html_doc = self.window.document;
|
||||
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;
|
||||
_ = try parser.eventTargetAddEventListener(
|
||||
parser.toEventTarget(parser.Element, document_element),
|
||||
@@ -817,6 +818,43 @@ pub const Page = struct {
|
||||
}
|
||||
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 {
|
||||
@@ -1090,24 +1128,22 @@ fn timestamp() u32 {
|
||||
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.
|
||||
// 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`
|
||||
// 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
|
||||
// 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.?));
|
||||
if (self.delayed_navigation) {
|
||||
// if we're planning on navigating to another page, don't run this script
|
||||
return;
|
||||
}
|
||||
|
||||
var script = Script.init(element.?, self) catch |err| {
|
||||
log.warn(.browser, "script added init error", .{ .err = err });
|
||||
return;
|
||||
} orelse return;
|
||||
|
||||
_ = self.evalScript(&script);
|
||||
self.elementAdded(element.?) catch |err| {
|
||||
log.warn(.browser, "element added callback", .{ .err = err });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ const v8 = @import("v8");
|
||||
const Env = @import("../env.zig").Env;
|
||||
const Page = @import("../page.zig").Page;
|
||||
|
||||
const Element = @import("../dom/element.zig").Element;
|
||||
|
||||
pub const CustomElementRegistry = struct {
|
||||
// tag_name -> Function
|
||||
lookup: std.StringHashMapUnmanaged(Env.Function) = .empty,
|
||||
@@ -46,6 +44,26 @@ pub const CustomElementRegistry = struct {
|
||||
pub fn _get(self: *CustomElementRegistry, name: []const u8) ?Env.Function {
|
||||
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");
|
||||
@@ -63,7 +81,11 @@ test "Browser.CustomElementRegistry" {
|
||||
\\ class MyElement extends HTMLElement {
|
||||
\\ constructor() {
|
||||
\\ 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 HTMLElement", "true" },
|
||||
.{ "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
|
||||
// .{ "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()) {
|
||||
return .{.invalid = {}};
|
||||
return .{ .invalid = {} };
|
||||
}
|
||||
|
||||
// This can get tricky.
|
||||
@@ -1196,6 +1196,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
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
|
||||
// the src of the module to load.
|
||||
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