mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Add Notification WebAPI
Adds a pretty simplistic Notification WebAPI. Also adds a dummy drawImage to CanvasRenderingContext2D. Trying to improve how we're seen by https://bot.sannysoft.com/
This commit is contained in:
@@ -941,6 +941,7 @@ pub const PageJsApis = flattenTypes(&.{
|
||||
@import("../webapi/ModelContext.zig"),
|
||||
@import("../webapi/Navigator.zig"),
|
||||
@import("../webapi/NavigatorUAData.zig"),
|
||||
@import("../webapi/Notification.zig"),
|
||||
@import("../webapi/net/FormData.zig"),
|
||||
@import("../webapi/net/Headers.zig"),
|
||||
@import("../webapi/net/Request.zig"),
|
||||
|
||||
@@ -158,3 +158,16 @@
|
||||
testing.expectEqual(null, element.getContext('webgl'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="CanvasRenderingContext2D#drawImage">
|
||||
{
|
||||
const ctx = document.createElement('canvas').getContext('2d');
|
||||
const img = new Image();
|
||||
const src = document.createElement('canvas');
|
||||
// No-op, but must accept the (image, dx, dy), (image, dx, dy, dw, dh) and
|
||||
// 9-arg overloads without throwing.
|
||||
ctx.drawImage(img, 0, 0);
|
||||
ctx.drawImage(src, 0, 0, 10, 10);
|
||||
testing.expectEqual(undefined, ctx.drawImage(src, 0, 0, 10, 10, 0, 0, 10, 10));
|
||||
}
|
||||
</script>
|
||||
|
||||
47
src/browser/tests/notification.html
Normal file
47
src/browser/tests/notification.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="./testing.js"></script>
|
||||
|
||||
<script id=Notification>
|
||||
{
|
||||
testing.expectEqual("function", typeof Notification);
|
||||
testing.expectEqual("default", Notification.permission);
|
||||
testing.expectEqual(2, Notification.maxActions);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=Notification#requestPermission>
|
||||
{
|
||||
testing.async(async () => {
|
||||
testing.expectEqual("default", await Notification.requestPermission());
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=Notification#constructor>
|
||||
{
|
||||
const n = new Notification("hello", { body: "world", icon: "i.png", tag: "t", lang: "en", dir: "rtl", silent: true });
|
||||
testing.expectEqual(true, n instanceof Notification);
|
||||
testing.expectEqual(true, n instanceof EventTarget);
|
||||
testing.expectEqual("hello", n.title);
|
||||
testing.expectEqual("world", n.body);
|
||||
testing.expectEqual("i.png", n.icon);
|
||||
testing.expectEqual("t", n.tag);
|
||||
testing.expectEqual("en", n.lang);
|
||||
testing.expectEqual("rtl", n.dir);
|
||||
testing.expectEqual(true, n.silent);
|
||||
testing.expectEqual("[object Notification]", Object.prototype.toString.call(n));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=Notification#defaults>
|
||||
{
|
||||
const n = new Notification("only-title");
|
||||
testing.expectEqual("only-title", n.title);
|
||||
testing.expectEqual("", n.body);
|
||||
testing.expectEqual("auto", n.dir);
|
||||
testing.expectEqual(false, n.silent);
|
||||
testing.expectEqual(false, n.requireInteraction);
|
||||
// close() is a no-op but must exist and not throw.
|
||||
n.close();
|
||||
}
|
||||
</script>
|
||||
@@ -50,6 +50,7 @@ pub const Type = union(enum) {
|
||||
font_face_set: *@import("css/FontFaceSet.zig"),
|
||||
websocket: *@import("net/WebSocket.zig"),
|
||||
cookie_store: *@import("storage/CookieStore.zig"),
|
||||
notification: *@import("Notification.zig"),
|
||||
};
|
||||
|
||||
pub fn init(page: *Page) !*EventTarget {
|
||||
@@ -161,6 +162,7 @@ pub fn format(self: *EventTarget, writer: *std.Io.Writer) !void {
|
||||
.font_face_set => writer.writeAll("<FontFaceSet>"),
|
||||
.websocket => writer.writeAll("<WebSocket>"),
|
||||
.cookie_store => writer.writeAll("<CookieStore>"),
|
||||
.notification => writer.writeAll("<Notification>"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -184,6 +186,7 @@ pub fn toString(self: *EventTarget) []const u8 {
|
||||
.font_face_set => return "[object FontFaceSet]",
|
||||
.websocket => return "[object WebSocket]",
|
||||
.cookie_store => return "[object CookieStore]",
|
||||
.notification => return "[object Notification]",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
175
src/browser/webapi/Notification.zig
Normal file
175
src/browser/webapi/Notification.zig
Normal file
@@ -0,0 +1,175 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
|
||||
const Execution = js.Execution;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const Notification = @This();
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_arena: Allocator,
|
||||
_proto: *EventTarget,
|
||||
_title: []const u8,
|
||||
_body: []const u8 = "",
|
||||
_icon: []const u8 = "",
|
||||
_image: []const u8 = "",
|
||||
_badge: []const u8 = "",
|
||||
_tag: []const u8 = "",
|
||||
_lang: []const u8 = "",
|
||||
_dir: []const u8 = "auto",
|
||||
_silent: bool = false,
|
||||
_require_interaction: bool = false,
|
||||
_renotify: bool = false,
|
||||
|
||||
const Options = struct {
|
||||
body: ?[]const u8 = null,
|
||||
icon: ?[]const u8 = null,
|
||||
image: ?[]const u8 = null,
|
||||
badge: ?[]const u8 = null,
|
||||
tag: ?[]const u8 = null,
|
||||
lang: ?[]const u8 = null,
|
||||
dir: ?[]const u8 = null,
|
||||
silent: ?bool = null,
|
||||
requireInteraction: ?bool = null,
|
||||
renotify: ?bool = null,
|
||||
};
|
||||
|
||||
pub fn init(title: []const u8, options_: ?Options, exec: *const Execution) !*Notification {
|
||||
const arena = try exec.getArena(.small, "Notification");
|
||||
errdefer exec.releaseArena(arena);
|
||||
|
||||
const options = options_ orelse Options{};
|
||||
return exec._factory.eventTargetWithAllocator(arena, Notification{
|
||||
._arena = arena,
|
||||
._proto = undefined,
|
||||
._title = try arena.dupe(u8, title),
|
||||
._body = if (options.body) |v| try arena.dupe(u8, v) else "",
|
||||
._icon = if (options.icon) |v| try arena.dupe(u8, v) else "",
|
||||
._image = if (options.image) |v| try arena.dupe(u8, v) else "",
|
||||
._badge = if (options.badge) |v| try arena.dupe(u8, v) else "",
|
||||
._tag = if (options.tag) |v| try arena.dupe(u8, v) else "",
|
||||
._lang = if (options.lang) |v| try arena.dupe(u8, v) else "",
|
||||
._dir = if (options.dir) |d| try arena.dupe(u8, d) else "auto",
|
||||
._silent = options.silent orelse false,
|
||||
._require_interaction = options.requireInteraction orelse false,
|
||||
._renotify = options.renotify orelse false,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Notification, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Notification, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *Notification) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn close(_: *Notification) void {}
|
||||
|
||||
fn getPermission() []const u8 {
|
||||
return "default";
|
||||
}
|
||||
|
||||
fn getMaxActions() u32 {
|
||||
return 2;
|
||||
}
|
||||
|
||||
fn requestPermission(_: ?js.Function, exec: *const Execution) !js.Promise {
|
||||
return exec.js.local.?.resolvePromise("default");
|
||||
}
|
||||
|
||||
fn getTitle(self: *const Notification) []const u8 {
|
||||
return self._title;
|
||||
}
|
||||
fn getBody(self: *const Notification) []const u8 {
|
||||
return self._body;
|
||||
}
|
||||
fn getIcon(self: *const Notification) []const u8 {
|
||||
return self._icon;
|
||||
}
|
||||
fn getImage(self: *const Notification) []const u8 {
|
||||
return self._image;
|
||||
}
|
||||
fn getBadge(self: *const Notification) []const u8 {
|
||||
return self._badge;
|
||||
}
|
||||
fn getTag(self: *const Notification) []const u8 {
|
||||
return self._tag;
|
||||
}
|
||||
fn getLang(self: *const Notification) []const u8 {
|
||||
return self._lang;
|
||||
}
|
||||
fn getDir(self: *const Notification) []const u8 {
|
||||
return self._dir;
|
||||
}
|
||||
fn getSilent(self: *const Notification) bool {
|
||||
return self._silent;
|
||||
}
|
||||
fn getRequireInteraction(self: *const Notification) bool {
|
||||
return self._require_interaction;
|
||||
}
|
||||
fn getRenotify(self: *const Notification) bool {
|
||||
return self._renotify;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Notification);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "Notification";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(Notification.init, .{});
|
||||
|
||||
pub const permission = bridge.accessor(getPermission, null, .{ .static = true });
|
||||
pub const maxActions = bridge.accessor(getMaxActions, null, .{ .static = true });
|
||||
pub const requestPermission = bridge.function(Notification.requestPermission, .{ .static = true });
|
||||
|
||||
pub const close = bridge.function(Notification.close, .{ .noop = true });
|
||||
|
||||
pub const title = bridge.accessor(getTitle, null, .{});
|
||||
pub const body = bridge.accessor(getBody, null, .{});
|
||||
pub const icon = bridge.accessor(getIcon, null, .{});
|
||||
pub const image = bridge.accessor(getImage, null, .{});
|
||||
pub const badge = bridge.accessor(getBadge, null, .{});
|
||||
pub const tag = bridge.accessor(getTag, null, .{});
|
||||
pub const lang = bridge.accessor(getLang, null, .{});
|
||||
pub const dir = bridge.accessor(getDir, null, .{});
|
||||
pub const silent = bridge.accessor(getSilent, null, .{});
|
||||
pub const requireInteraction = bridge.accessor(getRequireInteraction, null, .{});
|
||||
pub const renotify = bridge.accessor(getRenotify, null, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "WebApi: Notification" {
|
||||
try testing.htmlRunner("notification.html", .{});
|
||||
}
|
||||
@@ -83,6 +83,10 @@ pub fn createImageData(
|
||||
|
||||
pub fn putImageData(_: *const CanvasRenderingContext2D, _: *ImageData, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
|
||||
|
||||
// CanvasImageSource (HTMLImageElement, HTMLCanvasElement, ImageBitmap, ...) is
|
||||
// just taken as a js.Value for now since we don't use it, and that's much easier.
|
||||
pub fn drawImage(_: *const CanvasRenderingContext2D, _: js.Value, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
|
||||
|
||||
pub fn getImageData(
|
||||
_: *const CanvasRenderingContext2D,
|
||||
_: i32, // sx
|
||||
@@ -150,6 +154,7 @@ pub const JsApi = struct {
|
||||
pub const createImageData = bridge.function(CanvasRenderingContext2D.createImageData, .{ .dom_exception = true });
|
||||
|
||||
pub const putImageData = bridge.function(CanvasRenderingContext2D.putImageData, .{ .noop = true });
|
||||
pub const drawImage = bridge.function(CanvasRenderingContext2D.drawImage, .{ .noop = true });
|
||||
pub const getImageData = bridge.function(CanvasRenderingContext2D.getImageData, .{ .dom_exception = true });
|
||||
pub const save = bridge.function(CanvasRenderingContext2D.save, .{ .noop = true });
|
||||
pub const restore = bridge.function(CanvasRenderingContext2D.restore, .{ .noop = true });
|
||||
|
||||
Reference in New Issue
Block a user