mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-15 15:58:57 +00:00
Response constructor, window.CSS
This commit is contained in:
@@ -1221,7 +1221,6 @@ pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
// After constructor runs, invoke attributeChangedCallback for initial attributes
|
||||
const element = node.as(Element);
|
||||
if (element._attributes) |attributes| {
|
||||
|
||||
@@ -300,7 +300,7 @@ pub fn resolveSpecifier(self: *ScriptManager, arena: Allocator, base: [:0]const
|
||||
return s;
|
||||
}
|
||||
|
||||
return URL.resolve(arena, base, specifier, .{.always_dupe = true});
|
||||
return URL.resolve(arena, base, specifier, .{ .always_dupe = true });
|
||||
}
|
||||
|
||||
pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const u8) !void {
|
||||
|
||||
@@ -198,13 +198,10 @@ fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
|
||||
const value =
|
||||
if (msg.getValue()) |v8_value|
|
||||
context.valueToString(v8_value, .{}) catch |err| @errorName(err)
|
||||
else "no value"
|
||||
;
|
||||
else
|
||||
"no value";
|
||||
|
||||
log.debug(.js, "unhandled rejection", .{
|
||||
.value = value,
|
||||
.stack = context.stackTrace() catch |err| @errorName(err) orelse "???"
|
||||
});
|
||||
log.debug(.js, "unhandled rejection", .{ .value = value, .stack = context.stackTrace() catch |err| @errorName(err) orelse "???" });
|
||||
}
|
||||
|
||||
// Give it a Zig struct, get back a v8.FunctionTemplate.
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn Builder(comptime T: type) type {
|
||||
|
||||
pub fn property(value: anytype) Property {
|
||||
switch (@typeInfo(@TypeOf(value))) {
|
||||
.comptime_int, .int => return .{.int = value},
|
||||
.comptime_int, .int => return .{ .int = value },
|
||||
else => {},
|
||||
}
|
||||
@compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet");
|
||||
@@ -485,6 +485,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/collections.zig"),
|
||||
@import("../webapi/Console.zig"),
|
||||
@import("../webapi/Crypto.zig"),
|
||||
@import("../webapi/CSS.zig"),
|
||||
@import("../webapi/css/CSSRule.zig"),
|
||||
@import("../webapi/css/CSSRuleList.zig"),
|
||||
@import("../webapi/css/CSSStyleDeclaration.zig"),
|
||||
|
||||
69
src/browser/tests/css.html
Normal file
69
src/browser/tests/css.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="testing.js"></script>
|
||||
|
||||
<script id="exists">
|
||||
testing.expectEqual('object', typeof CSS);
|
||||
testing.expectEqual('function', typeof CSS.escape);
|
||||
testing.expectEqual('function', typeof CSS.supports);
|
||||
</script>
|
||||
|
||||
<script id="escape_basic">
|
||||
{
|
||||
testing.expectEqual('hello', CSS.escape('hello'));
|
||||
testing.expectEqual('world123', CSS.escape('world123'));
|
||||
testing.expectEqual('foo-bar', CSS.escape('foo-bar'));
|
||||
testing.expectEqual('_test', CSS.escape('_test'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="escape_first_character">
|
||||
{
|
||||
testing.expectEqual('\\30 abc', CSS.escape('0abc'));
|
||||
testing.expectEqual('\\31 23', CSS.escape('123'));
|
||||
testing.expectEqual('\\-test', CSS.escape('-test'));
|
||||
testing.expectEqual('\\--test', CSS.escape('--test'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="escape_special_characters">
|
||||
{
|
||||
testing.expectEqual('hello\\ world', CSS.escape('hello world'));
|
||||
testing.expectEqual('test\\!', CSS.escape('test!'));
|
||||
testing.expectEqual('foo\\#bar', CSS.escape('foo#bar'));
|
||||
testing.expectEqual('a\\(b\\)', CSS.escape('a(b)'));
|
||||
testing.expectEqual('test\\@example', CSS.escape('test@example'));
|
||||
testing.expectEqual('a\\[b\\]', CSS.escape('a[b]'));
|
||||
testing.expectEqual('a\\{b\\}', CSS.escape('a{b}'));
|
||||
testing.expectEqual('test\\:value', CSS.escape('test:value'));
|
||||
testing.expectEqual('a\\.b', CSS.escape('a.b'));
|
||||
testing.expectEqual('a\\,b', CSS.escape('a,b'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="escape_quotes">
|
||||
{
|
||||
testing.expectEqual('test\\"value', CSS.escape('test"value'));
|
||||
testing.expectEqual('test\\\'value', CSS.escape("test'value"));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="supports_basic">
|
||||
{
|
||||
testing.expectEqual(true, CSS.supports('display', 'block'));
|
||||
testing.expectEqual(true, CSS.supports('position', 'relative'));
|
||||
testing.expectEqual(true, CSS.supports('width', '100px'));
|
||||
testing.expectEqual(true, CSS.supports('color', 'red'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="supports_common_properties">
|
||||
{
|
||||
testing.expectEqual(true, CSS.supports('margin', '10px'));
|
||||
testing.expectEqual(true, CSS.supports('padding', '5px'));
|
||||
testing.expectEqual(true, CSS.supports('border', '1px solid black'));
|
||||
testing.expectEqual(true, CSS.supports('background-color', 'blue'));
|
||||
testing.expectEqual(true, CSS.supports('font-size', '16px'));
|
||||
testing.expectEqual(true, CSS.supports('opacity', '0.5'));
|
||||
testing.expectEqual(true, CSS.supports('z-index', '10'));
|
||||
}
|
||||
</script>
|
||||
@@ -32,7 +32,7 @@ pub const Attribute = @import("element/Attribute.zig");
|
||||
const CSSStyleProperties = @import("css/CSSStyleProperties.zig");
|
||||
pub const DOMStringMap = @import("element/DOMStringMap.zig");
|
||||
const DOMRect = @import("DOMRect.zig");
|
||||
const css = @import("css.zig");
|
||||
const CSS = @import("CSS.zig");
|
||||
const ShadowRoot = @import("ShadowRoot.zig");
|
||||
|
||||
pub const Svg = @import("element/Svg.zig");
|
||||
@@ -623,8 +623,8 @@ pub fn getBoundingClientRect(self: *Element, page: *Page) !*DOMRect {
|
||||
|
||||
const style = try self.getStyle(page);
|
||||
const decl = style.asCSSStyleDeclaration();
|
||||
width = css.parseDimension(decl.getPropertyValue("width", page)) orelse 1.0;
|
||||
height = css.parseDimension(decl.getPropertyValue("height", page)) orelse 1.0;
|
||||
width = CSS.parseDimension(decl.getPropertyValue("width", page)) orelse 1.0;
|
||||
height = CSS.parseDimension(decl.getPropertyValue("height", page)) orelse 1.0;
|
||||
|
||||
if (width == 1.0 or height == 1.0) {
|
||||
const tag = self.getTag();
|
||||
|
||||
@@ -69,7 +69,7 @@ pub const Entry = struct {
|
||||
}
|
||||
|
||||
pub fn getEntryType(self: *const Entry) []const u8 {
|
||||
return switch (self._entry_type) {
|
||||
return switch (self._entry_type) {
|
||||
.first_input => "first-input",
|
||||
.largest_contentful_paint => "largest-contentful-paint",
|
||||
.layout_shift => "layout-shift",
|
||||
|
||||
@@ -68,5 +68,5 @@ pub const JsApi = struct {
|
||||
pub const observe = bridge.function(PerformanceObserver.observe, .{});
|
||||
pub const disconnect = bridge.function(PerformanceObserver.disconnect, .{});
|
||||
pub const takeRecords = bridge.function(PerformanceObserver.takeRecords, .{});
|
||||
pub const supportedEntryTypes = bridge.accessor(PerformanceObserver.getSupportedEntryTypes, null, .{.static = true});
|
||||
pub const supportedEntryTypes = bridge.accessor(PerformanceObserver.getSupportedEntryTypes, null, .{ .static = true });
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ const Page = @import("../Page.zig");
|
||||
const Console = @import("Console.zig");
|
||||
const History = @import("History.zig");
|
||||
const Crypto = @import("Crypto.zig");
|
||||
const CSS = @import("CSS.zig");
|
||||
const Navigator = @import("Navigator.zig");
|
||||
const Screen = @import("Screen.zig");
|
||||
const Performance = @import("Performance.zig");
|
||||
@@ -43,6 +44,7 @@ const Window = @This();
|
||||
|
||||
_proto: *EventTarget,
|
||||
_document: *Document,
|
||||
_css: CSS = .init,
|
||||
_crypto: Crypto = .init,
|
||||
_console: Console = .init,
|
||||
_navigator: Navigator = .init,
|
||||
@@ -89,6 +91,10 @@ pub fn getCrypto(self: *Window) *Crypto {
|
||||
return &self._crypto;
|
||||
}
|
||||
|
||||
pub fn getCSS(self: *Window) *CSS {
|
||||
return &self._css;
|
||||
}
|
||||
|
||||
pub fn getPerformance(self: *Window) *Performance {
|
||||
return &self._performance;
|
||||
}
|
||||
@@ -380,6 +386,7 @@ pub const JsApi = struct {
|
||||
pub const location = bridge.accessor(Window.getLocation, null, .{ .cache = "location" });
|
||||
pub const history = bridge.accessor(Window.getHistory, null, .{ .cache = "history" });
|
||||
pub const crypto = bridge.accessor(Window.getCrypto, null, .{ .cache = "crypto" });
|
||||
pub const CSS = bridge.accessor(Window.getCSS, null, .{ .cache = "CSS" });
|
||||
pub const customElements = bridge.accessor(Window.getCustomElements, null, .{ .cache = "customElements" });
|
||||
pub const onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{});
|
||||
pub const onerror = bridge.accessor(Window.getOnError, Window.getOnError, .{});
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const CSS = @This();
|
||||
_pad: bool = false,
|
||||
|
||||
pub const init: CSS = .{};
|
||||
|
||||
pub fn parseDimension(value: []const u8) ?f64 {
|
||||
if (value.len == 0) {
|
||||
@@ -30,3 +37,134 @@ pub fn parseDimension(value: []const u8) ?f64 {
|
||||
|
||||
return std.fmt.parseFloat(f64, num_str) catch null;
|
||||
}
|
||||
|
||||
/// Escapes a CSS identifier string
|
||||
/// https://drafts.csswg.org/cssom/#the-css.escape()-method
|
||||
pub fn escape(_: *const CSS, value: []const u8, page: *Page) ![]const u8 {
|
||||
if (value.len == 0) {
|
||||
return error.InvalidCharacterError;
|
||||
}
|
||||
|
||||
const first = value[0];
|
||||
|
||||
// Count how many characters we need for the output
|
||||
var out_len: usize = escapeLen(true, first);
|
||||
for (value[1..]) |c| {
|
||||
out_len += escapeLen(false, c);
|
||||
}
|
||||
|
||||
if (out_len == value.len) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const result = try page.call_arena.alloc(u8, out_len);
|
||||
var pos: usize = 0;
|
||||
|
||||
if (needsEscape(true, first)) {
|
||||
pos = writeEscape(true, result, first);
|
||||
} else {
|
||||
result[0] = first;
|
||||
pos = 1;
|
||||
}
|
||||
|
||||
for (value[1..]) |c| {
|
||||
if (!needsEscape(false, c)) {
|
||||
result[pos] = c;
|
||||
pos += 1;
|
||||
} else {
|
||||
pos += writeEscape(false, result[pos..], c);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn supports(_: *const CSS, property_or_condition: []const u8, value: ?[]const u8) bool {
|
||||
_ = property_or_condition;
|
||||
_ = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn escapeLen(comptime is_first: bool, c: u8) usize {
|
||||
if (needsEscape(is_first, c) == false) {
|
||||
return 1;
|
||||
}
|
||||
if (c == 0) {
|
||||
return "\u{FFFD}".len;
|
||||
}
|
||||
if (isHexEscape(c) or ((comptime is_first) and c >= '0' and c <= '9')) {
|
||||
// Will be escaped as \XX (backslash + 1-6 hex digits + space)
|
||||
return 2 + hexDigitsNeeded(c);
|
||||
}
|
||||
// Escaped as \C (backslash + character)
|
||||
return 2;
|
||||
}
|
||||
|
||||
fn needsEscape(comptime is_first: bool, c: u8) bool {
|
||||
if (comptime is_first) {
|
||||
if (c >= '0' and c <= '9') {
|
||||
return true;
|
||||
}
|
||||
if (c == '-') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Characters that need escaping
|
||||
return switch (c) {
|
||||
0...0x1F, 0x7F => true,
|
||||
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '`', '{', '|', '}', '~' => true,
|
||||
' ' => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn isHexEscape(c: u8) bool {
|
||||
return (c >= 0x00 and c <= 0x1F) or c == 0x7F;
|
||||
}
|
||||
|
||||
fn hexDigitsNeeded(c: u8) usize {
|
||||
if (c < 0x10) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
fn writeEscape(comptime is_first: bool, buf: []u8, c: u8) usize {
|
||||
buf[0] = '\\';
|
||||
var data = buf[1..];
|
||||
|
||||
if (c == 0) {
|
||||
// NULL character becomes replacement character
|
||||
const replacement = "\u{FFFD}";
|
||||
@memcpy(data[0..replacement.len], replacement);
|
||||
return 1 + replacement.len;
|
||||
}
|
||||
|
||||
if (isHexEscape(c) or ((comptime is_first) and c >= '0' and c <= '9')) {
|
||||
const hex_str = std.fmt.bufPrint(data, "{x} ", .{c}) catch unreachable;
|
||||
return 1 + hex_str.len;
|
||||
}
|
||||
|
||||
data[0] = c;
|
||||
return 2;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(CSS);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "Css";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
pub const empty_with_no_proto = true;
|
||||
};
|
||||
|
||||
pub const escape = bridge.function(CSS.escape, .{});
|
||||
pub const supports = bridge.function(CSS.supports, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "WebApi: CSS" {
|
||||
try testing.htmlRunner("css.html", .{});
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ pub fn assign(self: *Slot, nodes: []const *Node) void {
|
||||
_ = nodes;
|
||||
|
||||
// let's see if this is ever actually used
|
||||
log.warn(.not_implemented, "Slot.assign", .{ });
|
||||
log.warn(.not_implemented, "Slot.assign", .{});
|
||||
}
|
||||
|
||||
fn findShadowRoot(self: *Slot) ?*ShadowRoot {
|
||||
|
||||
@@ -20,6 +20,7 @@ const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const Headers = @import("Headers.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const Response = @This();
|
||||
@@ -28,6 +29,22 @@ _status: u16,
|
||||
_data: []const u8,
|
||||
_arena: Allocator,
|
||||
|
||||
const InitOpts = struct {
|
||||
status: u16 = 200,
|
||||
headers: ?*Headers = null,
|
||||
statusText: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub fn init(body_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*Response {
|
||||
const opts = opts_ orelse InitOpts{};
|
||||
|
||||
return page._factory.create(Response{
|
||||
._status = opts.status,
|
||||
._data = if (body_) |b| try page.arena.dupe(u8, b) else "",
|
||||
._arena = page.arena,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn initFromFetch(arena: Allocator, data: []const u8, page: *Page) !*Response {
|
||||
return page._factory.create(Response{
|
||||
._status = 200,
|
||||
@@ -65,6 +82,7 @@ pub const JsApi = struct {
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(Response.init, .{});
|
||||
pub const ok = bridge.accessor(Response.isOK, null, .{});
|
||||
pub const status = bridge.accessor(Response.getStatus, null, .{});
|
||||
pub const json = bridge.function(Response.getJson, .{});
|
||||
|
||||
Reference in New Issue
Block a user