mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 16:28:58 +00:00
Merge pull request #928 from lightpanda-io/lit_compat
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (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 / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (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 / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
lit compatibility
This commit is contained in:
@@ -31,6 +31,7 @@ const parser = @import("netsurf.zig");
|
|||||||
const DataSet = @import("html/DataSet.zig");
|
const DataSet = @import("html/DataSet.zig");
|
||||||
const ShadowRoot = @import("dom/shadow_root.zig").ShadowRoot;
|
const ShadowRoot = @import("dom/shadow_root.zig").ShadowRoot;
|
||||||
const StyleSheet = @import("cssom/StyleSheet.zig");
|
const StyleSheet = @import("cssom/StyleSheet.zig");
|
||||||
|
const CSSStyleSheet = @import("cssom/CSSStyleSheet.zig");
|
||||||
const CSSStyleDeclaration = @import("cssom/CSSStyleDeclaration.zig");
|
const CSSStyleDeclaration = @import("cssom/CSSStyleDeclaration.zig");
|
||||||
|
|
||||||
// for HTMLScript (but probably needs to be added to more)
|
// for HTMLScript (but probably needs to be added to more)
|
||||||
@@ -53,6 +54,7 @@ style_sheet: ?*StyleSheet = null,
|
|||||||
|
|
||||||
// for dom/document
|
// for dom/document
|
||||||
active_element: ?*parser.Element = null,
|
active_element: ?*parser.Element = null,
|
||||||
|
adopted_style_sheets: ?Env.JsObject = null,
|
||||||
|
|
||||||
// for HTMLSelectElement
|
// for HTMLSelectElement
|
||||||
// By default, if no option is explicitly selected, the first option should
|
// By default, if no option is explicitly selected, the first option should
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Env = @import("../env.zig").Env;
|
||||||
const Page = @import("../page.zig").Page;
|
const Page = @import("../page.zig").Page;
|
||||||
const StyleSheet = @import("StyleSheet.zig");
|
const StyleSheet = @import("StyleSheet.zig");
|
||||||
const CSSRuleList = @import("CSSRuleList.zig");
|
const CSSRuleList = @import("CSSRuleList.zig");
|
||||||
@@ -39,7 +40,7 @@ const CSSStyleSheetOpts = struct {
|
|||||||
pub fn constructor(_opts: ?CSSStyleSheetOpts) !CSSStyleSheet {
|
pub fn constructor(_opts: ?CSSStyleSheetOpts) !CSSStyleSheet {
|
||||||
const opts = _opts orelse CSSStyleSheetOpts{};
|
const opts = _opts orelse CSSStyleSheetOpts{};
|
||||||
return .{
|
return .{
|
||||||
.proto = StyleSheet{ .disabled = opts.disabled },
|
.proto = .{ .disabled = opts.disabled },
|
||||||
.css_rules = .constructor(),
|
.css_rules = .constructor(),
|
||||||
.owner_rule = null,
|
.owner_rule = null,
|
||||||
};
|
};
|
||||||
@@ -72,6 +73,24 @@ pub fn _deleteRule(self: *CSSStyleSheet, index: usize) !void {
|
|||||||
_ = self.css_rules.list.orderedRemove(index);
|
_ = self.css_rules.list.orderedRemove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn _replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !Env.Promise {
|
||||||
|
_ = self;
|
||||||
|
_ = text;
|
||||||
|
// TODO: clear self.css_rules
|
||||||
|
// parse text and re-populate self.css_rules
|
||||||
|
|
||||||
|
const resolver = page.main_context.createPromiseResolver();
|
||||||
|
try resolver.resolve({});
|
||||||
|
return resolver.promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _replaceSync(self: *CSSStyleSheet, text: []const u8) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = text;
|
||||||
|
// TODO: clear self.css_rules
|
||||||
|
// parse text and re-populate self.css_rules
|
||||||
|
}
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
test "Browser.CSS.StyleSheet" {
|
test "Browser.CSS.StyleSheet" {
|
||||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
@@ -85,5 +104,14 @@ test "Browser.CSS.StyleSheet" {
|
|||||||
.{ "let index1 = css.insertRule('body { color: red; }', 0)", "undefined" },
|
.{ "let index1 = css.insertRule('body { color: red; }', 0)", "undefined" },
|
||||||
.{ "index1", "0" },
|
.{ "index1", "0" },
|
||||||
.{ "css.cssRules.length", "1" },
|
.{ "css.cssRules.length", "1" },
|
||||||
|
|
||||||
|
.{
|
||||||
|
\\ let replaced = false;
|
||||||
|
\\ css.replace('body{}').then(() => replaced = true);
|
||||||
|
,
|
||||||
|
null,
|
||||||
|
},
|
||||||
|
// microtasks are run between each statement
|
||||||
|
.{ "replaced", "true" },
|
||||||
}, .{});
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,6 +293,22 @@ pub const Document = struct {
|
|||||||
pub fn get_styleSheets(_: *parser.Document) []CSSStyleSheet {
|
pub fn get_styleSheets(_: *parser.Document) []CSSStyleSheet {
|
||||||
return &.{};
|
return &.{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_adoptedStyleSheets(self: *parser.Document, page: *Page) !Env.JsObject {
|
||||||
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
|
if (state.adopted_style_sheets) |obj| {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = try page.main_context.newArray(0).persist();
|
||||||
|
state.adopted_style_sheets = obj;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_adoptedStyleSheets(self: *parser.Document, sheets: Env.JsObject, page: *Page) !void {
|
||||||
|
const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self)));
|
||||||
|
state.adopted_style_sheets = try sheets.persist();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
@@ -484,6 +500,13 @@ test "Browser.DOM.Document" {
|
|||||||
.{ "v.nodeName", "DIV" },
|
.{ "v.nodeName", "DIV" },
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "const acss = document.adoptedStyleSheets", null },
|
||||||
|
.{ "acss.length", "0" },
|
||||||
|
.{ "acss.push(new CSSStyleSheet())", null },
|
||||||
|
.{ "document.adoptedStyleSheets.length", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
const Case = testing.JsRunner.Case;
|
const Case = testing.JsRunner.Case;
|
||||||
const tags = comptime parser.Tag.all();
|
const tags = comptime parser.Tag.all();
|
||||||
var createElements: [(tags.len) * 2]Case = undefined;
|
var createElements: [(tags.len) * 2]Case = undefined;
|
||||||
|
|||||||
@@ -137,18 +137,18 @@ pub const Element = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_innerHTML(self: *parser.Element, page: *Page) ![]const u8 {
|
pub fn get_innerHTML(self: *parser.Element, page: *Page) ![]const u8 {
|
||||||
var buf = std.ArrayList(u8).init(page.arena);
|
var buf = std.ArrayList(u8).init(page.call_arena);
|
||||||
try dump.writeChildren(parser.elementToNode(self), .{}, buf.writer());
|
try dump.writeChildren(parser.elementToNode(self), .{}, buf.writer());
|
||||||
return buf.items;
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_outerHTML(self: *parser.Element, page: *Page) ![]const u8 {
|
pub fn get_outerHTML(self: *parser.Element, page: *Page) ![]const u8 {
|
||||||
var buf = std.ArrayList(u8).init(page.arena);
|
var buf = std.ArrayList(u8).init(page.call_arena);
|
||||||
try dump.writeNode(parser.elementToNode(self), .{}, buf.writer());
|
try dump.writeNode(parser.elementToNode(self), .{}, buf.writer());
|
||||||
return buf.items;
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_innerHTML(self: *parser.Element, str: []const u8) !void {
|
pub fn set_innerHTML(self: *parser.Element, str: []const u8, page: *Page) !void {
|
||||||
const node = parser.elementToNode(self);
|
const node = parser.elementToNode(self);
|
||||||
const doc = try parser.nodeOwnerDocument(node) orelse return parser.DOMError.WrongDocument;
|
const doc = try parser.nodeOwnerDocument(node) orelse return parser.DOMError.WrongDocument;
|
||||||
// parse the fragment
|
// parse the fragment
|
||||||
@@ -157,6 +157,8 @@ pub const Element = struct {
|
|||||||
// remove existing children
|
// remove existing children
|
||||||
try Node.removeChildren(node);
|
try Node.removeChildren(node);
|
||||||
|
|
||||||
|
const fragment_node = parser.documentFragmentToNode(fragment);
|
||||||
|
|
||||||
// I'm not sure what the exact behavior is supposed to be. Initially,
|
// I'm not sure what the exact behavior is supposed to be. Initially,
|
||||||
// we were only copying the body of the document fragment. But it seems
|
// we were only copying the body of the document fragment. But it seems
|
||||||
// like head elements should be copied too. Specifically, some sites
|
// like head elements should be copied too. Specifically, some sites
|
||||||
@@ -166,9 +168,32 @@ pub const Element = struct {
|
|||||||
// or an actual document. In a blank page, something like:
|
// or an actual document. In a blank page, something like:
|
||||||
// x.innerHTML = '<script></script>';
|
// x.innerHTML = '<script></script>';
|
||||||
// does _not_ create an empty script, but in a real page, it does. Weird.
|
// does _not_ create an empty script, but in a real page, it does. Weird.
|
||||||
const fragment_node = parser.documentFragmentToNode(fragment);
|
|
||||||
const html = try parser.nodeFirstChild(fragment_node) orelse return;
|
const html = try parser.nodeFirstChild(fragment_node) orelse return;
|
||||||
const head = try parser.nodeFirstChild(html) orelse return;
|
const head = try parser.nodeFirstChild(html) orelse return;
|
||||||
|
const body = try parser.nodeNextSibling(head) orelse return;
|
||||||
|
|
||||||
|
if (try parser.elementTag(self) == .template) {
|
||||||
|
// HTMLElementTemplate is special. We don't append these as children
|
||||||
|
// of the template, but instead set its content as the body of the
|
||||||
|
// fragment. Simpler to do this by copying the body children into
|
||||||
|
// a new fragment
|
||||||
|
const clean = try parser.documentCreateDocumentFragment(doc);
|
||||||
|
const children = try parser.nodeGetChildNodes(body);
|
||||||
|
const ln = try parser.nodeListLength(children);
|
||||||
|
for (0..ln) |_| {
|
||||||
|
// always index 0, because nodeAppendChild moves the node out of
|
||||||
|
// the nodeList and into the new tree
|
||||||
|
const child = try parser.nodeListItem(children, 0) orelse continue;
|
||||||
|
_ = try parser.nodeAppendChild(@alignCast(@ptrCast(clean)), child);
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = try page.getOrCreateNodeState(node);
|
||||||
|
state.template_content = clean;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For any node other than a template, we copy the head and body elements
|
||||||
|
// as child nodes of the element
|
||||||
{
|
{
|
||||||
// First, copy some of the head element
|
// First, copy some of the head element
|
||||||
const children = try parser.nodeGetChildNodes(head);
|
const children = try parser.nodeGetChildNodes(head);
|
||||||
@@ -182,7 +207,6 @@ pub const Element = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const body = try parser.nodeNextSibling(head) orelse return;
|
|
||||||
const children = try parser.nodeGetChildNodes(body);
|
const children = try parser.nodeGetChildNodes(body);
|
||||||
const ln = try parser.nodeListLength(children);
|
const ln = try parser.nodeListLength(children);
|
||||||
for (0..ln) |_| {
|
for (0..ln) |_| {
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const parser = @import("../netsurf.zig");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
|
const Env = @import("../env.zig").Env;
|
||||||
|
const Page = @import("../page.zig").Page;
|
||||||
const Element = @import("element.zig").Element;
|
const Element = @import("element.zig").Element;
|
||||||
const ElementUnion = @import("element.zig").Union;
|
const ElementUnion = @import("element.zig").Union;
|
||||||
|
|
||||||
@@ -29,6 +32,7 @@ pub const ShadowRoot = struct {
|
|||||||
mode: Mode,
|
mode: Mode,
|
||||||
host: *parser.Element,
|
host: *parser.Element,
|
||||||
proto: *parser.DocumentFragment,
|
proto: *parser.DocumentFragment,
|
||||||
|
adopted_style_sheets: ?Env.JsObject = null,
|
||||||
|
|
||||||
pub const Mode = enum {
|
pub const Mode = enum {
|
||||||
open,
|
open,
|
||||||
@@ -38,6 +42,20 @@ pub const ShadowRoot = struct {
|
|||||||
pub fn get_host(self: *const ShadowRoot) !ElementUnion {
|
pub fn get_host(self: *const ShadowRoot) !ElementUnion {
|
||||||
return Element.toInterface(self.host);
|
return Element.toInterface(self.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_adoptedStyleSheets(self: *ShadowRoot, page: *Page) !Env.JsObject {
|
||||||
|
if (self.adopted_style_sheets) |obj| {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = try page.main_context.newArray(0).persist();
|
||||||
|
self.adopted_style_sheets = obj;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: Env.JsObject) !void {
|
||||||
|
self.adopted_style_sheets = try sheets.persist();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
@@ -80,4 +98,11 @@ test "Browser.DOM.ShadowRoot" {
|
|||||||
.{ "sr2.append(n1)", null},
|
.{ "sr2.append(n1)", null},
|
||||||
.{ "sr2.getElementById('conflict') == n1", "true" },
|
.{ "sr2.getElementById('conflict') == n1", "true" },
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "const acss = sr2.adoptedStyleSheets", null },
|
||||||
|
.{ "acss.length", "0" },
|
||||||
|
.{ "acss.push(new CSSStyleSheet())", null },
|
||||||
|
.{ "sr2.adoptedStyleSheets.length", "1" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf.zig");
|
const parser = @import("netsurf.zig");
|
||||||
|
const Page = @import("page.zig").Page;
|
||||||
const Walker = @import("dom/walker.zig").WalkerChildren;
|
const Walker = @import("dom/walker.zig").WalkerChildren;
|
||||||
|
|
||||||
pub const Opts = struct {
|
pub const Opts = struct {
|
||||||
|
// set to include element shadowroots in the dump
|
||||||
|
page: ?*const Page = null,
|
||||||
|
|
||||||
exclude_scripts: bool = false,
|
exclude_scripts: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,6 +92,14 @@ pub fn writeNode(node: *parser.Node, opts: Opts, writer: anytype) anyerror!void
|
|||||||
|
|
||||||
try writer.writeAll(">");
|
try writer.writeAll(">");
|
||||||
|
|
||||||
|
if (opts.page) |page| {
|
||||||
|
if (page.getNodeState(node)) |state| {
|
||||||
|
if (state.shadow_root) |sr| {
|
||||||
|
try writeChildren(@alignCast(@ptrCast(sr.proto)), opts, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// void elements can't have any content.
|
// void elements can't have any content.
|
||||||
if (try isVoid(parser.nodeToElement(node))) return;
|
if (try isVoid(parser.nodeToElement(node))) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1454,6 +1454,12 @@ test "Browser.HTML.HTMLTemplateElement" {
|
|||||||
.{ "document.getElementById('abc')", "null" },
|
.{ "document.getElementById('abc')", "null" },
|
||||||
.{ "document.getElementById('c').appendChild(t.content.cloneNode(true))", null },
|
.{ "document.getElementById('c').appendChild(t.content.cloneNode(true))", null },
|
||||||
.{ "document.getElementById('abc').id", "abc" },
|
.{ "document.getElementById('abc').id", "abc" },
|
||||||
|
.{ "t.innerHTML = '<span>over</span><p>9000!</p>';", null },
|
||||||
|
.{ "t.content.childNodes.length", "2" },
|
||||||
|
.{ "t.content.childNodes[0].tagName", "SPAN" },
|
||||||
|
.{ "t.content.childNodes[0].innerHTML", "over" },
|
||||||
|
.{ "t.content.childNodes[1].tagName", "P" },
|
||||||
|
.{ "t.content.childNodes[1].innerHTML", "9000!" },
|
||||||
}, .{});
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,8 +142,10 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const DumpOpts = struct {
|
pub const DumpOpts = struct {
|
||||||
exclude_scripts: bool = false,
|
// set to include element shadowroots in the dump
|
||||||
|
page: ?*const Page = null,
|
||||||
with_base: bool = false,
|
with_base: bool = false,
|
||||||
|
exclude_scripts: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// dump writes the page content into the given file.
|
// dump writes the page content into the given file.
|
||||||
@@ -162,6 +164,7 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try Dump.writeHTML(doc, .{
|
try Dump.writeHTML(doc, .{
|
||||||
|
.page = opts.page,
|
||||||
.exclude_scripts = opts.exclude_scripts,
|
.exclude_scripts = opts.exclude_scripts,
|
||||||
}, out);
|
}, out);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,8 +135,9 @@ fn run(alloc: Allocator) !void {
|
|||||||
// dump
|
// dump
|
||||||
if (opts.dump) {
|
if (opts.dump) {
|
||||||
try page.dump(.{
|
try page.dump(.{
|
||||||
.exclude_scripts = opts.noscript,
|
.page = page,
|
||||||
.with_base = opts.withbase,
|
.with_base = opts.withbase,
|
||||||
|
.exclude_scripts = opts.noscript,
|
||||||
}, std.io.getStdOut());
|
}, std.io.getStdOut());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -799,6 +799,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn newArray(self: *JsContext, len: u32) JsObject {
|
||||||
|
const arr = v8.Array.init(self.isolate, len);
|
||||||
|
return .{
|
||||||
|
.js_context = self,
|
||||||
|
.js_obj = arr.castTo(v8.Object),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap a v8.Exception
|
// Wrap a v8.Exception
|
||||||
fn createException(self: *const JsContext, e: v8.Value) Exception {
|
fn createException(self: *const JsContext, e: v8.Value) Exception {
|
||||||
return .{
|
return .{
|
||||||
|
|||||||
Reference in New Issue
Block a user