mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 08:18:59 +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;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// After constructor runs, invoke attributeChangedCallback for initial attributes
|
// After constructor runs, invoke attributeChangedCallback for initial attributes
|
||||||
const element = node.as(Element);
|
const element = node.as(Element);
|
||||||
if (element._attributes) |attributes| {
|
if (element._attributes) |attributes| {
|
||||||
|
|||||||
@@ -198,13 +198,10 @@ fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
|
|||||||
const value =
|
const value =
|
||||||
if (msg.getValue()) |v8_value|
|
if (msg.getValue()) |v8_value|
|
||||||
context.valueToString(v8_value, .{}) catch |err| @errorName(err)
|
context.valueToString(v8_value, .{}) catch |err| @errorName(err)
|
||||||
else "no value"
|
else
|
||||||
;
|
"no value";
|
||||||
|
|
||||||
log.debug(.js, "unhandled rejection", .{
|
log.debug(.js, "unhandled rejection", .{ .value = value, .stack = context.stackTrace() catch |err| @errorName(err) orelse "???" });
|
||||||
.value = value,
|
|
||||||
.stack = context.stackTrace() catch |err| @errorName(err) orelse "???"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give it a Zig struct, get back a v8.FunctionTemplate.
|
// Give it a Zig struct, get back a v8.FunctionTemplate.
|
||||||
|
|||||||
@@ -485,6 +485,7 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/collections.zig"),
|
@import("../webapi/collections.zig"),
|
||||||
@import("../webapi/Console.zig"),
|
@import("../webapi/Console.zig"),
|
||||||
@import("../webapi/Crypto.zig"),
|
@import("../webapi/Crypto.zig"),
|
||||||
|
@import("../webapi/CSS.zig"),
|
||||||
@import("../webapi/css/CSSRule.zig"),
|
@import("../webapi/css/CSSRule.zig"),
|
||||||
@import("../webapi/css/CSSRuleList.zig"),
|
@import("../webapi/css/CSSRuleList.zig"),
|
||||||
@import("../webapi/css/CSSStyleDeclaration.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");
|
const CSSStyleProperties = @import("css/CSSStyleProperties.zig");
|
||||||
pub const DOMStringMap = @import("element/DOMStringMap.zig");
|
pub const DOMStringMap = @import("element/DOMStringMap.zig");
|
||||||
const DOMRect = @import("DOMRect.zig");
|
const DOMRect = @import("DOMRect.zig");
|
||||||
const css = @import("css.zig");
|
const CSS = @import("CSS.zig");
|
||||||
const ShadowRoot = @import("ShadowRoot.zig");
|
const ShadowRoot = @import("ShadowRoot.zig");
|
||||||
|
|
||||||
pub const Svg = @import("element/Svg.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 style = try self.getStyle(page);
|
||||||
const decl = style.asCSSStyleDeclaration();
|
const decl = style.asCSSStyleDeclaration();
|
||||||
width = css.parseDimension(decl.getPropertyValue("width", page)) orelse 1.0;
|
width = CSS.parseDimension(decl.getPropertyValue("width", page)) orelse 1.0;
|
||||||
height = css.parseDimension(decl.getPropertyValue("height", page)) orelse 1.0;
|
height = CSS.parseDimension(decl.getPropertyValue("height", page)) orelse 1.0;
|
||||||
|
|
||||||
if (width == 1.0 or height == 1.0) {
|
if (width == 1.0 or height == 1.0) {
|
||||||
const tag = self.getTag();
|
const tag = self.getTag();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const Page = @import("../Page.zig");
|
|||||||
const Console = @import("Console.zig");
|
const Console = @import("Console.zig");
|
||||||
const History = @import("History.zig");
|
const History = @import("History.zig");
|
||||||
const Crypto = @import("Crypto.zig");
|
const Crypto = @import("Crypto.zig");
|
||||||
|
const CSS = @import("CSS.zig");
|
||||||
const Navigator = @import("Navigator.zig");
|
const Navigator = @import("Navigator.zig");
|
||||||
const Screen = @import("Screen.zig");
|
const Screen = @import("Screen.zig");
|
||||||
const Performance = @import("Performance.zig");
|
const Performance = @import("Performance.zig");
|
||||||
@@ -43,6 +44,7 @@ const Window = @This();
|
|||||||
|
|
||||||
_proto: *EventTarget,
|
_proto: *EventTarget,
|
||||||
_document: *Document,
|
_document: *Document,
|
||||||
|
_css: CSS = .init,
|
||||||
_crypto: Crypto = .init,
|
_crypto: Crypto = .init,
|
||||||
_console: Console = .init,
|
_console: Console = .init,
|
||||||
_navigator: Navigator = .init,
|
_navigator: Navigator = .init,
|
||||||
@@ -89,6 +91,10 @@ pub fn getCrypto(self: *Window) *Crypto {
|
|||||||
return &self._crypto;
|
return &self._crypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getCSS(self: *Window) *CSS {
|
||||||
|
return &self._css;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getPerformance(self: *Window) *Performance {
|
pub fn getPerformance(self: *Window) *Performance {
|
||||||
return &self._performance;
|
return &self._performance;
|
||||||
}
|
}
|
||||||
@@ -380,6 +386,7 @@ pub const JsApi = struct {
|
|||||||
pub const location = bridge.accessor(Window.getLocation, null, .{ .cache = "location" });
|
pub const location = bridge.accessor(Window.getLocation, null, .{ .cache = "location" });
|
||||||
pub const history = bridge.accessor(Window.getHistory, null, .{ .cache = "history" });
|
pub const history = bridge.accessor(Window.getHistory, null, .{ .cache = "history" });
|
||||||
pub const crypto = bridge.accessor(Window.getCrypto, null, .{ .cache = "crypto" });
|
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 customElements = bridge.accessor(Window.getCustomElements, null, .{ .cache = "customElements" });
|
||||||
pub const onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{});
|
pub const onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{});
|
||||||
pub const onerror = bridge.accessor(Window.getOnError, Window.getOnError, .{});
|
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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
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 {
|
pub fn parseDimension(value: []const u8) ?f64 {
|
||||||
if (value.len == 0) {
|
if (value.len == 0) {
|
||||||
@@ -30,3 +37,134 @@ pub fn parseDimension(value: []const u8) ?f64 {
|
|||||||
|
|
||||||
return std.fmt.parseFloat(f64, num_str) catch null;
|
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", .{});
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const std = @import("std");
|
|||||||
const js = @import("../../js/js.zig");
|
const js = @import("../../js/js.zig");
|
||||||
|
|
||||||
const Page = @import("../../Page.zig");
|
const Page = @import("../../Page.zig");
|
||||||
|
const Headers = @import("Headers.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Response = @This();
|
const Response = @This();
|
||||||
@@ -28,6 +29,22 @@ _status: u16,
|
|||||||
_data: []const u8,
|
_data: []const u8,
|
||||||
_arena: Allocator,
|
_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 {
|
pub fn initFromFetch(arena: Allocator, data: []const u8, page: *Page) !*Response {
|
||||||
return page._factory.create(Response{
|
return page._factory.create(Response{
|
||||||
._status = 200,
|
._status = 200,
|
||||||
@@ -65,6 +82,7 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const constructor = bridge.constructor(Response.init, .{});
|
||||||
pub const ok = bridge.accessor(Response.isOK, null, .{});
|
pub const ok = bridge.accessor(Response.isOK, null, .{});
|
||||||
pub const status = bridge.accessor(Response.getStatus, null, .{});
|
pub const status = bridge.accessor(Response.getStatus, null, .{});
|
||||||
pub const json = bridge.function(Response.getJson, .{});
|
pub const json = bridge.function(Response.getJson, .{});
|
||||||
|
|||||||
Reference in New Issue
Block a user