mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-15 15:58:57 +00:00
Window.scrollX/Y, postMessage, more custom element edge cases
This commit is contained in:
@@ -550,6 +550,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/Event.zig"),
|
||||
@import("../webapi/event/CustomEvent.zig"),
|
||||
@import("../webapi/event/ErrorEvent.zig"),
|
||||
@import("../webapi/event/MessageEvent.zig"),
|
||||
@import("../webapi/event/ProgressEvent.zig"),
|
||||
@import("../webapi/EventTarget.zig"),
|
||||
@import("../webapi/Location.zig"),
|
||||
|
||||
@@ -122,6 +122,10 @@
|
||||
testing.expectEqual(0, callbackCalls.length);
|
||||
|
||||
customElements.define('upgrade-attr-element', UpgradeAttrElement);
|
||||
testing.expectEqual(0, callbackCalls.length);
|
||||
|
||||
document.body.appendChild(el);
|
||||
|
||||
testing.expectEqual(1, callbackCalls.length);
|
||||
testing.expectEqual('existing', callbackCalls[0].name);
|
||||
testing.expectEqual(null, callbackCalls[0].oldValue);
|
||||
|
||||
@@ -181,4 +181,175 @@
|
||||
testing.expectEqual(null, attributeChangedCalls[1].oldValue);
|
||||
testing.expectEqual('world', attributeChangedCalls[1].newValue);
|
||||
}
|
||||
|
||||
{
|
||||
let attributeChangedCalls = [];
|
||||
let connectedCalls = 0;
|
||||
|
||||
class DetachedWithAttrs extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['foo'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
attributeChangedCalls.push({ name, oldValue, newValue });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
connectedCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = '<detached-with-attrs foo="bar"></detached-with-attrs>';
|
||||
|
||||
testing.expectEqual(0, attributeChangedCalls.length);
|
||||
|
||||
customElements.define('detached-with-attrs', DetachedWithAttrs);
|
||||
|
||||
testing.expectEqual(0, attributeChangedCalls.length);
|
||||
testing.expectEqual(0, connectedCalls);
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
testing.expectEqual(1, attributeChangedCalls.length);
|
||||
testing.expectEqual('foo', attributeChangedCalls[0].name);
|
||||
testing.expectEqual(null, attributeChangedCalls[0].oldValue);
|
||||
testing.expectEqual('bar', attributeChangedCalls[0].newValue);
|
||||
testing.expectEqual(1, connectedCalls);
|
||||
}
|
||||
|
||||
{
|
||||
let attributeChangedCalls = [];
|
||||
let constructorCalled = 0;
|
||||
|
||||
class ManualUpgradeWithAttrs extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['x', 'y'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructorCalled++;
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
attributeChangedCalls.push({ name, oldValue, newValue });
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('manual-upgrade-with-attrs', ManualUpgradeWithAttrs);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = '<manual-upgrade-with-attrs x="1" y="2"></manual-upgrade-with-attrs>';
|
||||
|
||||
testing.expectEqual(1, constructorCalled);
|
||||
testing.expectEqual(2, attributeChangedCalls.length);
|
||||
|
||||
const elem = container.querySelector('manual-upgrade-with-attrs');
|
||||
elem.setAttribute('z', '3');
|
||||
|
||||
customElements.upgrade(container);
|
||||
|
||||
testing.expectEqual(1, constructorCalled);
|
||||
testing.expectEqual(2, attributeChangedCalls.length);
|
||||
}
|
||||
|
||||
{
|
||||
let attributeChangedCalls = [];
|
||||
|
||||
class MixedAttrs extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['watched'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
attributeChangedCalls.push({ name, oldValue, newValue });
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = '<mixed-attrs watched="yes" ignored="no" also-ignored="maybe"></mixed-attrs>';
|
||||
document.body.appendChild(container);
|
||||
|
||||
testing.expectEqual(0, attributeChangedCalls.length);
|
||||
|
||||
customElements.define('mixed-attrs', MixedAttrs);
|
||||
|
||||
testing.expectEqual(1, attributeChangedCalls.length);
|
||||
testing.expectEqual('watched', attributeChangedCalls[0].name);
|
||||
testing.expectEqual('yes', attributeChangedCalls[0].newValue);
|
||||
}
|
||||
|
||||
{
|
||||
let attributeChangedCalls = [];
|
||||
|
||||
class EmptyAttr extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['empty', 'non-empty'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
attributeChangedCalls.push({ name, oldValue, newValue });
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = '<empty-attr empty="" non-empty="value"></empty-attr>';
|
||||
document.body.appendChild(container);
|
||||
|
||||
customElements.define('empty-attr', EmptyAttr);
|
||||
|
||||
testing.expectEqual(2, attributeChangedCalls.length);
|
||||
testing.expectEqual('empty', attributeChangedCalls[0].name);
|
||||
testing.expectEqual('', attributeChangedCalls[0].newValue);
|
||||
testing.expectEqual('non-empty', attributeChangedCalls[1].name);
|
||||
testing.expectEqual('value', attributeChangedCalls[1].newValue);
|
||||
}
|
||||
|
||||
{
|
||||
let parentCalls = [];
|
||||
let childCalls = [];
|
||||
|
||||
class NestedParent extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['parent-attr'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
parentCalls.push({ name, oldValue, newValue });
|
||||
}
|
||||
}
|
||||
|
||||
class NestedChild extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['child-attr'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
childCalls.push({ name, oldValue, newValue });
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = '<nested-parent parent-attr="p"><nested-child child-attr="c"></nested-child></nested-parent>';
|
||||
document.body.appendChild(container);
|
||||
|
||||
testing.expectEqual(0, parentCalls.length);
|
||||
testing.expectEqual(0, childCalls.length);
|
||||
|
||||
customElements.define('nested-parent', NestedParent);
|
||||
|
||||
testing.expectEqual(1, parentCalls.length);
|
||||
testing.expectEqual('parent-attr', parentCalls[0].name);
|
||||
testing.expectEqual('p', parentCalls[0].newValue);
|
||||
testing.expectEqual(0, childCalls.length);
|
||||
|
||||
customElements.define('nested-child', NestedChild);
|
||||
|
||||
testing.expectEqual(1, parentCalls.length);
|
||||
testing.expectEqual(1, childCalls.length);
|
||||
testing.expectEqual('child-attr', childCalls[0].name);
|
||||
testing.expectEqual('c', childCalls[0].newValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
170
src/browser/tests/event/message.html
Normal file
170
src/browser/tests/event/message.html
Normal file
@@ -0,0 +1,170 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=messageEventConstructor>
|
||||
{
|
||||
const evt = new MessageEvent('message', {
|
||||
data: { foo: 'bar', count: 42 },
|
||||
origin: 'https://example.com',
|
||||
source: window
|
||||
});
|
||||
|
||||
testing.expectEqual('message', evt.type);
|
||||
testing.expectEqual({ foo: 'bar', count: 42 }, evt.data);
|
||||
testing.expectEqual('https://example.com', evt.origin);
|
||||
testing.expectEqual(window, evt.source);
|
||||
testing.expectEqual(false, evt.bubbles);
|
||||
testing.expectEqual(false, evt.cancelable);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=messageEventWithString>
|
||||
{
|
||||
const evt2 = new MessageEvent('message', {
|
||||
data: 'Hello, World!',
|
||||
origin: 'https://test.com'
|
||||
});
|
||||
|
||||
testing.expectEqual('Hello, World!', evt2.data);
|
||||
testing.expectEqual('https://test.com', evt2.origin);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=messageEventDefaults>
|
||||
{
|
||||
const evt3 = new MessageEvent('message');
|
||||
|
||||
testing.expectEqual('', evt3.origin);
|
||||
testing.expectEqual(false, evt3.bubbles);
|
||||
testing.expectEqual(false, evt3.cancelable);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=messageEventInheritance>
|
||||
{
|
||||
const evt4 = new MessageEvent('custom');
|
||||
|
||||
testing.expectEqual('custom', evt4.type);
|
||||
testing.expectEqual(Event.NONE, evt4.eventPhase);
|
||||
testing.expectEqual(false, evt4.defaultPrevented);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=postMessageBasic>
|
||||
{
|
||||
let receivedEvent = null;
|
||||
|
||||
const handler = (e) => {
|
||||
receivedEvent = e;
|
||||
};
|
||||
window.addEventListener('message', handler, { once: true });
|
||||
|
||||
window.postMessage('test data', '*');
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual('test data', receivedEvent.data);
|
||||
testing.expectEqual(window, receivedEvent.source);
|
||||
testing.expectEqual('message', receivedEvent.type);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=postMessageWithObject>
|
||||
{
|
||||
let receivedData = null;
|
||||
|
||||
const handler = (e) => {
|
||||
receivedData = e.data;
|
||||
};
|
||||
window.addEventListener('message', handler, { once: true });
|
||||
|
||||
const testObj = { type: 'test', value: 123, nested: { key: 'value' } };
|
||||
window.postMessage(testObj, '*');
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual(testObj, receivedData);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=messageEventWithBubbles>
|
||||
{
|
||||
const evt = new MessageEvent('message', {
|
||||
data: 'test',
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
testing.expectEqual(true, evt.bubbles);
|
||||
testing.expectEqual(true, evt.cancelable);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=postMessageWithNumber>
|
||||
{
|
||||
let received = null;
|
||||
|
||||
const handler = (e) => {
|
||||
received = e.data;
|
||||
};
|
||||
window.addEventListener('message', handler, { once: true });
|
||||
|
||||
window.postMessage(42, '*');
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual(42, received);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=postMessageWithArray>
|
||||
{
|
||||
let received = null;
|
||||
|
||||
const handler = (e) => {
|
||||
received = e.data;
|
||||
};
|
||||
window.addEventListener('message', handler, { once: true });
|
||||
|
||||
const arr = [1, 2, 3, 'test'];
|
||||
window.postMessage(arr, '*');
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual(arr, received);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=postMessageWithNull>
|
||||
{
|
||||
let received = undefined;
|
||||
|
||||
const handler = (e) => {
|
||||
received = e.data;
|
||||
};
|
||||
window.addEventListener('message', handler, { once: true });
|
||||
|
||||
window.postMessage(null, '*');
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual(null, received);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=messageEventOriginFromLocation>
|
||||
{
|
||||
let receivedOrigin = null;
|
||||
|
||||
const handler = (e) => {
|
||||
receivedOrigin = e.origin;
|
||||
};
|
||||
window.addEventListener('message', handler, { once: true });
|
||||
|
||||
window.postMessage('test', '*');
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual('http://127.0.0.1:9582', receivedOrigin);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
19
src/browser/tests/event/message_multiple_listeners.html
Normal file
19
src/browser/tests/event/message_multiple_listeners.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=postMessageMultipleListeners>
|
||||
{
|
||||
let count = 0;
|
||||
|
||||
const handler1 = () => { count++; };
|
||||
const handler2 = () => { count++; };
|
||||
window.addEventListener('message', handler1, { once: true });
|
||||
window.addEventListener('message', handler2, { once: true });
|
||||
|
||||
window.postMessage('trigger', '*');
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual(2, count);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -92,6 +92,11 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!custom.asElement().asNode().isConnected()) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
upgradeCustomElement(custom, definition, page) catch {
|
||||
_ = page._undefined_custom_elements.swapRemove(idx);
|
||||
continue;
|
||||
@@ -134,7 +139,7 @@ fn upgradeElement(self: *CustomElementRegistry, element: *Element, page: *Page)
|
||||
try upgradeCustomElement(custom, definition, page);
|
||||
}
|
||||
|
||||
fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinition, page: *Page) !void {
|
||||
pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinition, page: *Page) !void {
|
||||
custom._definition = definition;
|
||||
|
||||
// Reset callback flags since this is a fresh upgrade
|
||||
|
||||
@@ -49,9 +49,10 @@ pub const EventPhase = enum(u8) {
|
||||
|
||||
pub const Type = union(enum) {
|
||||
generic,
|
||||
progress_event: *@import("event/ProgressEvent.zig"),
|
||||
error_event: *@import("event/ErrorEvent.zig"),
|
||||
custom_event: *@import("event/CustomEvent.zig"),
|
||||
message_event: *@import("event/MessageEvent.zig"),
|
||||
progress_event: *@import("event/ProgressEvent.zig"),
|
||||
};
|
||||
|
||||
const Options = struct {
|
||||
|
||||
@@ -34,6 +34,7 @@ const Location = @import("Location.zig");
|
||||
const Fetch = @import("net/Fetch.zig");
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
const ErrorEvent = @import("event/ErrorEvent.zig");
|
||||
const MessageEvent = @import("event/MessageEvent.zig");
|
||||
const MediaQueryList = @import("css/MediaQueryList.zig");
|
||||
const storage = @import("storage/storage.zig");
|
||||
const Element = @import("Element.zig");
|
||||
@@ -255,6 +256,28 @@ pub fn getComputedStyle(_: *const Window, _: *Element, page: *Page) !*CSSStyleDe
|
||||
return CSSStyleDeclaration.init(null, page);
|
||||
}
|
||||
|
||||
pub fn postMessage(self: *Window, message: js.Object, target_origin: ?[]const u8, page: *Page) !void {
|
||||
// For now, we ignore targetOrigin checking and just dispatch the message
|
||||
// In a full implementation, we would validate the origin
|
||||
_ = target_origin;
|
||||
|
||||
// postMessage queues a task (not a microtask), so use the scheduler
|
||||
const origin = try self._location.getOrigin(page);
|
||||
const callback = try page._factory.create(PostMessageCallback{
|
||||
.window = self,
|
||||
.message = try message.persist() ,
|
||||
.origin = try page.arena.dupe(u8, origin),
|
||||
.page = page,
|
||||
});
|
||||
errdefer page._factory.destroy(callback);
|
||||
|
||||
|
||||
try page.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
||||
.name = "postMessage",
|
||||
.low_priority = false,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn btoa(_: *const Window, input: []const u8, page: *Page) ![]const u8 {
|
||||
const encoded_len = std.base64.standard.Encoder.calcSize(input.len);
|
||||
const encoded = try page.call_arena.alloc(u8, encoded_len);
|
||||
@@ -268,6 +291,26 @@ pub fn atob(_: *const Window, input: []const u8, page: *Page) ![]const u8 {
|
||||
return decoded;
|
||||
}
|
||||
|
||||
pub fn getLength(_: *const Window) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn getInnerWidth(_: *const Window) u32 {
|
||||
return 1920;
|
||||
}
|
||||
|
||||
pub fn getInnerHeight(_: *const Window) u32 {
|
||||
return 1080;
|
||||
}
|
||||
|
||||
pub fn getScrollX(_: *const Window) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn getScrollY(_: *const Window) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ScheduleOpts = struct {
|
||||
repeat: bool,
|
||||
params: []js.Object,
|
||||
@@ -376,6 +419,35 @@ const ScheduleCallback = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const PostMessageCallback = struct {
|
||||
window: *Window,
|
||||
message: js.Object,
|
||||
origin: []const u8,
|
||||
page: *Page,
|
||||
|
||||
fn deinit(self: *PostMessageCallback) void {
|
||||
self.page._factory.destroy(self);
|
||||
}
|
||||
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||
defer self.deinit();
|
||||
|
||||
const message_event = try MessageEvent.init("message", .{
|
||||
.data = self.message,
|
||||
.origin = self.origin,
|
||||
.source = self.window,
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, self.page);
|
||||
|
||||
const event = message_event.asEvent();
|
||||
try self.page._event_manager.dispatch(self.window.asEventTarget(), event);
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Window);
|
||||
|
||||
@@ -415,27 +487,19 @@ pub const JsApi = struct {
|
||||
pub const requestAnimationFrame = bridge.function(Window.requestAnimationFrame, .{});
|
||||
pub const cancelAnimationFrame = bridge.function(Window.cancelAnimationFrame, .{});
|
||||
pub const matchMedia = bridge.function(Window.matchMedia, .{});
|
||||
pub const postMessage = bridge.function(Window.postMessage, .{});
|
||||
pub const btoa = bridge.function(Window.btoa, .{});
|
||||
pub const atob = bridge.function(Window.atob, .{});
|
||||
pub const reportError = bridge.function(Window.reportError, .{});
|
||||
pub const frames = bridge.accessor(Window.getWindow, null, .{ .cache = "frames" });
|
||||
pub const length = bridge.accessor(struct {
|
||||
fn wrap(_: *const Window) u32 {
|
||||
return 0;
|
||||
}
|
||||
}.wrap, null, .{ .cache = "length" });
|
||||
|
||||
pub const innerWidth = bridge.accessor(struct {
|
||||
fn wrap(_: *const Window) u32 {
|
||||
return 1920;
|
||||
}
|
||||
}.wrap, null, .{ .cache = "innerWidth" });
|
||||
pub const innerHeight = bridge.accessor(struct {
|
||||
fn wrap(_: *const Window) u32 {
|
||||
return 1080;
|
||||
}
|
||||
}.wrap, null, .{ .cache = "innerHeight" });
|
||||
pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{});
|
||||
pub const length = bridge.accessor(Window.getLength, null, .{ .cache = "length" });
|
||||
pub const innerWidth = bridge.accessor(Window.getInnerWidth, null, .{ .cache = "innerWidth" });
|
||||
pub const innerHeight = bridge.accessor(Window.getInnerHeight, null, .{ .cache = "innerHeight" });
|
||||
pub const scrollX = bridge.accessor(Window.getScrollX, null, .{ .cache = "scrollX" });
|
||||
pub const scrollY = bridge.accessor(Window.getScrollY, null, .{ .cache = "scrollY" });
|
||||
pub const pageXOffset = bridge.accessor(Window.getScrollX, null, .{ .cache = "pageXOffset" });
|
||||
pub const pageYOffset = bridge.accessor(Window.getScrollY, null, .{ .cache = "pageYOffset" });
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
|
||||
@@ -73,6 +73,16 @@ pub fn invokeAttributeChangedCallback(self: *Custom, name: []const u8, old_value
|
||||
pub fn invokeConnectedCallbackOnElement(comptime from_parser: bool, element: *Element, page: *Page) !void {
|
||||
// Autonomous custom element
|
||||
if (element.is(Custom)) |custom| {
|
||||
// If the element is undefined, check if a definition now exists and upgrade
|
||||
if (custom._definition == null) {
|
||||
const name = custom._tag_name.str();
|
||||
if (page.window._custom_elements._definitions.get(name)) |definition| {
|
||||
const CustomElementRegistry = @import("../../CustomElementRegistry.zig");
|
||||
CustomElementRegistry.upgradeCustomElement(custom, definition, page) catch {};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime from_parser) {
|
||||
// From parser, we know the element is brand new
|
||||
custom._connected_callback_invoked = true;
|
||||
|
||||
90
src/browser/webapi/event/MessageEvent.zig
Normal file
90
src/browser/webapi/event/MessageEvent.zig
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2023-2025 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 js = @import("../../js/js.zig");
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const Event = @import("../Event.zig");
|
||||
const Window = @import("../Window.zig");
|
||||
|
||||
const MessageEvent = @This();
|
||||
|
||||
_proto: *Event,
|
||||
_data: ?js.Object = null,
|
||||
_origin: []const u8 = "",
|
||||
_source: ?*Window = null,
|
||||
|
||||
pub const InitOptions = struct {
|
||||
data: ?js.Object = null,
|
||||
origin: ?[]const u8 = null,
|
||||
source: ?*Window = null,
|
||||
bubbles: bool = false,
|
||||
cancelable: bool = false,
|
||||
};
|
||||
|
||||
pub fn init(typ: []const u8, opts_: ?InitOptions, page: *Page) !*MessageEvent {
|
||||
const opts = opts_ orelse InitOptions{};
|
||||
|
||||
const event = try page._factory.event(typ, MessageEvent{
|
||||
._proto = undefined,
|
||||
._data = if (opts.data) |d| try d.persist() else null,
|
||||
._origin = if (opts.origin) |str| try page.arena.dupe(u8, str) else "",
|
||||
._source = opts.source,
|
||||
});
|
||||
|
||||
event._proto._bubbles = opts.bubbles;
|
||||
event._proto._cancelable = opts.cancelable;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *MessageEvent) *Event {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getData(self: *const MessageEvent) ?js.Object {
|
||||
return self._data;
|
||||
}
|
||||
|
||||
pub fn getOrigin(self: *const MessageEvent) []const u8 {
|
||||
return self._origin;
|
||||
}
|
||||
|
||||
pub fn getSource(self: *const MessageEvent) ?*Window {
|
||||
return self._source;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(MessageEvent);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "MessageEvent";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(MessageEvent.init, .{});
|
||||
pub const data = bridge.accessor(MessageEvent.getData, null, .{});
|
||||
pub const origin = bridge.accessor(MessageEvent.getOrigin, null, .{});
|
||||
pub const source = bridge.accessor(MessageEvent.getSource, null, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: MessageEvent" {
|
||||
try testing.htmlRunner("event/message.html", .{});
|
||||
}
|
||||
Reference in New Issue
Block a user