mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
add direct event handlers
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
// 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 Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
@@ -231,8 +232,6 @@ pub const EventHandler = struct {
|
|||||||
node: parser.EventNode,
|
node: parser.EventNode,
|
||||||
listener: *parser.EventListener,
|
listener: *parser.EventListener,
|
||||||
|
|
||||||
const js = @import("../js/js.zig");
|
|
||||||
|
|
||||||
pub const Listener = union(enum) {
|
pub const Listener = union(enum) {
|
||||||
function: js.Function,
|
function: js.Function,
|
||||||
object: js.Object,
|
object: js.Object,
|
||||||
@@ -404,6 +403,40 @@ const SignalCallback = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn DirectEventHandler(
|
||||||
|
comptime TargetT: type,
|
||||||
|
target: *TargetT,
|
||||||
|
event_type: []const u8,
|
||||||
|
maybe_listener: ?EventHandler.Listener,
|
||||||
|
cb: *?js.Function,
|
||||||
|
page_arena: std.mem.Allocator,
|
||||||
|
) !void {
|
||||||
|
const event_target = parser.toEventTarget(TargetT, target);
|
||||||
|
|
||||||
|
// Check if we have a listener set.
|
||||||
|
if (cb.*) |callback| {
|
||||||
|
const listener = try parser.eventTargetHasListener(event_target, event_type, false, callback.id);
|
||||||
|
std.debug.assert(listener != null);
|
||||||
|
try parser.eventTargetRemoveEventListener(event_target, event_type, listener.?, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maybe_listener) |listener| {
|
||||||
|
switch (listener) {
|
||||||
|
// If an object is given as listener, do nothing.
|
||||||
|
.object => {},
|
||||||
|
.function => |callback| {
|
||||||
|
_ = try EventHandler.register(page_arena, event_target, event_type, listener, null) orelse unreachable;
|
||||||
|
cb.* = callback;
|
||||||
|
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just unset the listener.
|
||||||
|
cb.* = null;
|
||||||
|
}
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
test "Browser: Event" {
|
test "Browser: Event" {
|
||||||
try testing.htmlRunner("events/event.html");
|
try testing.htmlRunner("events/event.html");
|
||||||
|
|||||||
@@ -177,5 +177,6 @@ pub const PopStateEvent = struct {
|
|||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
test "Browser: HTML.History" {
|
test "Browser: HTML.History" {
|
||||||
try testing.htmlRunner("html/history.html");
|
try testing.htmlRunner("html/history/history.html");
|
||||||
|
try testing.htmlRunner("html/history/history2.html");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const URL = @import("../../url.zig").URL;
|
|||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
const Page = @import("../page.zig").Page;
|
const Page = @import("../page.zig").Page;
|
||||||
|
|
||||||
|
const DirectEventHandler = @import("../events/event.zig").DirectEventHandler;
|
||||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
const EventHandler = @import("../events/event.zig").EventHandler;
|
const EventHandler = @import("../events/event.zig").EventHandler;
|
||||||
|
|
||||||
@@ -62,6 +63,8 @@ index: usize = 0,
|
|||||||
entries: std.ArrayListUnmanaged(*NavigationHistoryEntry) = .empty,
|
entries: std.ArrayListUnmanaged(*NavigationHistoryEntry) = .empty,
|
||||||
next_entry_id: usize = 0,
|
next_entry_id: usize = 0,
|
||||||
|
|
||||||
|
oncurrententrychange_callback: ?js.Function = null,
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry
|
// https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry
|
||||||
const NavigationHistoryEntry = struct {
|
const NavigationHistoryEntry = struct {
|
||||||
pub const prototype = *EventTarget;
|
pub const prototype = *EventTarget;
|
||||||
@@ -199,6 +202,16 @@ pub fn _forward(self: *Navigation, page: *Page) !NavigationReturn {
|
|||||||
return self.navigate(next_entry.url, .{ .traverse = new_index }, page);
|
return self.navigate(next_entry.url, .{ .traverse = new_index }, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `oncurrententrychange_callback`.
|
||||||
|
pub fn get_oncurrententrychange(self: *const Navigation) ?js.Function {
|
||||||
|
return self.oncurrententrychange_callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets `oncurrententrychange_callback`.
|
||||||
|
pub fn set_oncurrententrychange(self: *Navigation, maybe_listener: ?EventHandler.Listener, page: *Page) !void {
|
||||||
|
try DirectEventHandler(Navigation, self, "currententrychange", maybe_listener, &self.oncurrententrychange_callback, page.arena);
|
||||||
|
}
|
||||||
|
|
||||||
// This is for after true navigation processing, where we need to ensure that our entries are up to date.
|
// This is for after true navigation processing, where we need to ensure that our entries are up to date.
|
||||||
// This is only really safe to run in the `pageDoneCallback` where we can guarantee that the URL and NavigationKind are correct.
|
// This is only really safe to run in the `pageDoneCallback` where we can guarantee that the URL and NavigationKind are correct.
|
||||||
pub fn processNavigation(self: *Navigation, page: *Page) !void {
|
pub fn processNavigation(self: *Navigation, page: *Page) !void {
|
||||||
@@ -456,5 +469,5 @@ pub const NavigationCurrentEntryChangeEvent = struct {
|
|||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
test "Browser: Navigation" {
|
test "Browser: Navigation" {
|
||||||
try testing.htmlRunner("html/navigation.html");
|
try testing.htmlRunner("html/navigation/navigation.html");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ const fetchFn = @import("../fetch/fetch.zig").fetch;
|
|||||||
const storage = @import("../storage/storage.zig");
|
const storage = @import("../storage/storage.zig");
|
||||||
const ErrorEvent = @import("error_event.zig").ErrorEvent;
|
const ErrorEvent = @import("error_event.zig").ErrorEvent;
|
||||||
|
|
||||||
|
const DirectEventHandler = @import("../events/event.zig").DirectEventHandler;
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#interface-window-extensions
|
// https://dom.spec.whatwg.org/#interface-window-extensions
|
||||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
||||||
pub const Window = struct {
|
pub const Window = struct {
|
||||||
@@ -70,6 +72,7 @@ pub const Window = struct {
|
|||||||
scroll_x: u32 = 0,
|
scroll_x: u32 = 0,
|
||||||
scroll_y: u32 = 0,
|
scroll_y: u32 = 0,
|
||||||
onload_callback: ?js.Function = null,
|
onload_callback: ?js.Function = null,
|
||||||
|
onpopstate_callback: ?js.Function = null,
|
||||||
|
|
||||||
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
|
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
|
||||||
var fbs = std.io.fixedBufferStream("");
|
var fbs = std.io.fixedBufferStream("");
|
||||||
@@ -111,31 +114,17 @@ pub const Window = struct {
|
|||||||
|
|
||||||
/// Sets `onload_callback`.
|
/// Sets `onload_callback`.
|
||||||
pub fn set_onload(self: *Window, maybe_listener: ?EventHandler.Listener, page: *Page) !void {
|
pub fn set_onload(self: *Window, maybe_listener: ?EventHandler.Listener, page: *Page) !void {
|
||||||
const event_target = parser.toEventTarget(Window, self);
|
try DirectEventHandler(Window, self, "load", maybe_listener, &self.onload_callback, page.arena);
|
||||||
const event_type = "load";
|
}
|
||||||
|
|
||||||
// Check if we have a listener set.
|
/// Returns `onpopstate_callback`.
|
||||||
if (self.onload_callback) |callback| {
|
pub fn get_onpopstate(self: *const Window) ?js.Function {
|
||||||
const listener = try parser.eventTargetHasListener(event_target, event_type, false, callback.id);
|
return self.onpopstate_callback;
|
||||||
std.debug.assert(listener != null);
|
}
|
||||||
try parser.eventTargetRemoveEventListener(event_target, event_type, listener.?, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybe_listener) |listener| {
|
/// Sets `onpopstate_callback`.
|
||||||
switch (listener) {
|
pub fn set_onpopstate(self: *Window, maybe_listener: ?EventHandler.Listener, page: *Page) !void {
|
||||||
// If an object is given as listener, do nothing.
|
try DirectEventHandler(Window, self, "popstate", maybe_listener, &self.onpopstate_callback, page.arena);
|
||||||
.object => {},
|
|
||||||
.function => |callback| {
|
|
||||||
_ = try EventHandler.register(page.arena, event_target, event_type, listener, null) orelse unreachable;
|
|
||||||
self.onload_callback = callback;
|
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just unset the listener.
|
|
||||||
self.onload_callback = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_location(self: *Window) *Location {
|
pub fn get_location(self: *Window) *Location {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="../testing.js"></script>
|
<script src="../../testing.js"></script>
|
||||||
|
|
||||||
<script id=history>
|
<script id=history>
|
||||||
testing.expectEqual('auto', history.scrollRestoration);
|
testing.expectEqual('auto', history.scrollRestoration);
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
testing.expectEqual('auto', history.scrollRestoration);
|
testing.expectEqual('auto', history.scrollRestoration);
|
||||||
testing.expectEqual(null, history.state)
|
testing.expectEqual(null, history.state)
|
||||||
|
|
||||||
history.pushState({ testInProgress: true }, null, 'http://127.0.0.1:9582/src/tests/html/history2.html');
|
history.pushState({ testInProgress: true }, null, 'http://127.0.0.1:9582/src/tests/html/history/history_after_nav.html');
|
||||||
testing.expectEqual({ testInProgress: true }, history.state);
|
testing.expectEqual({ testInProgress: true }, history.state);
|
||||||
|
|
||||||
history.pushState({ testInProgress: false }, null, 'http://127.0.0.1:9582/xhr/json');
|
history.pushState({ testInProgress: false }, null, 'http://127.0.0.1:9582/xhr/json');
|
||||||
26
src/tests/html/history/history2.html
Normal file
26
src/tests/html/history/history2.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../../testing.js"></script>
|
||||||
|
|
||||||
|
<script id=history2>
|
||||||
|
history.pushState(
|
||||||
|
{"new": "field", testComplete: true },
|
||||||
|
null,
|
||||||
|
'http://127.0.0.1:9582/src/tests/html/history/history_after_nav.html'
|
||||||
|
);
|
||||||
|
|
||||||
|
let popstateEventFired = false;
|
||||||
|
let popstateEventState = null;
|
||||||
|
|
||||||
|
// uses the window event listener.
|
||||||
|
window.onpopstate = (event) => {
|
||||||
|
popstateEventFired = true;
|
||||||
|
popstateEventState = event.state;
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(true, popstateEventFired);
|
||||||
|
testing.expectEqual(true, popstateEventState.testComplete);
|
||||||
|
})
|
||||||
|
|
||||||
|
history.back();
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="../testing.js"></script>
|
<script src="../../testing.js"></script>
|
||||||
|
|
||||||
<script id=history2>
|
<script id=history2>
|
||||||
testing.expectEqual(true, history.state && history.state.testInProgress);
|
testing.expectEqual(true, history.state && history.state.testInProgress);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="../testing.js"></script>
|
<script src="../../testing.js"></script>
|
||||||
|
|
||||||
<script id=navigation>
|
<script id=navigation>
|
||||||
testing.expectEqual('object', typeof navigation);
|
testing.expectEqual('object', typeof navigation);
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
const currentIndex = navigation.currentEntry.index;
|
const currentIndex = navigation.currentEntry.index;
|
||||||
|
|
||||||
navigation.navigate(
|
navigation.navigate(
|
||||||
'http://127.0.0.1:9582/src/tests/html/navigation2.html',
|
'http://127.0.0.1:9582/src/tests/html/navigation/navigation2.html',
|
||||||
{ state: { currentIndex: currentIndex, navTestInProgress: true } }
|
{ state: { currentIndex: currentIndex, navTestInProgress: true } }
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="../testing.js"></script>
|
<script src="../../testing.js"></script>
|
||||||
|
|
||||||
<script id=navigation2>
|
<script id=navigation2>
|
||||||
const state = navigation.currentEntry.getState();
|
const state = navigation.currentEntry.getState();
|
||||||
Reference in New Issue
Block a user