Merge pull request #3288 from lightpanda-io/WorkerNavigator

WebAPI: Add WorkerNavigator
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-08-27 07:34:31 +08:00
commit 00a2a78303
6 files changed
+159 -12

No files matched your search

+2 -1
View File
@@ -1241,7 +1241,7 @@ pub const PageJsApis = flattenTypes(&.{
const worker_common_apis = [_]type{
@import("../webapi/WorkerGlobalScope.zig"),
@import("../webapi/WorkerLocation.zig"),
@import("../webapi/Navigator.zig"),
@import("../webapi/WorkerNavigator.zig"),
@import("../webapi/NavigatorUAData.zig"),
@import("../webapi/Permissions.zig"),
@import("../webapi/StorageManager.zig"),
@@ -1329,6 +1329,7 @@ pub const JsApis = blk: {
@import("../webapi/SharedWorkerGlobalScope.zig").JsApi,
@import("../webapi/WorkerGlobalScope.zig").JsApi,
@import("../webapi/WorkerLocation.zig").JsApi,
@import("../webapi/WorkerNavigator.zig").JsApi,
};
if (lp.build_config.wpt_extensions == false) {
break :blk base;
@@ -15,6 +15,10 @@ onmessage = async function(event) {
const results = {
has_navigator: typeof navigator !== 'undefined',
// The worker realm exposes WorkerNavigator, not Navigator.
is_worker_navigator: Object.getPrototypeOf(navigator) === WorkerNavigator.prototype,
to_string_tag: Object.prototype.toString.call(navigator),
no_navigator_interface: typeof Navigator === 'undefined',
// userAgent must match the value the page sees (passed in via postMessage).
user_agent: navigator.userAgent,
user_agent_matches_page: navigator.userAgent === event.data.pageUserAgent,
@@ -25,6 +29,7 @@ onmessage = async function(event) {
identity_stable: navigator === navigator,
// Permissions
permissions_distinct: navigator.permissions !== navigator,
permission_name: status.name,
permission_state: status.state,
@@ -40,6 +45,8 @@ onmessage = async function(event) {
no_plugins: navigator.plugins === undefined,
no_register_protocol_handler: navigator.registerProtocolHandler === undefined,
no_model_context: navigator.modelContext === undefined,
no_send_beacon: navigator.sendBeacon === undefined,
no_cookie_enabled: navigator.cookieEnabled === undefined,
};
postMessage({ ok: true, results });
} catch (e) {
+6
View File
@@ -270,6 +270,9 @@
const r = data.results;
testing.expectEqual(true, r.has_navigator);
testing.expectEqual(true, r.is_worker_navigator);
testing.expectEqual('[object WorkerNavigator]', r.to_string_tag);
testing.expectEqual(true, r.no_navigator_interface);
testing.expectEqual(true, r.user_agent_matches_page);
testing.expectEqual('Netscape', r.app_name);
testing.expectTrue(r.platform.length > 0);
@@ -277,6 +280,7 @@
testing.expectEqual(true, r.identity_stable);
// Permissions (transitive: Permissions -> PermissionStatus)
testing.expectEqual(true, r.permissions_distinct);
testing.expectEqual('geolocation', r.permission_name);
testing.expectEqual('prompt', r.permission_state);
@@ -292,6 +296,8 @@
testing.expectEqual(true, r.no_plugins);
testing.expectEqual(true, r.no_register_protocol_handler);
testing.expectEqual(true, r.no_model_context);
testing.expectEqual(true, r.no_send_beacon);
testing.expectEqual(true, r.no_cookie_enabled);
});
}
</script>
+6 -8
View File
@@ -265,17 +265,15 @@ pub const JsApi = struct {
pub const globalPrivacyControl = bridge.accessor(Navigator.getGlobalPrivacyControl, null, .{});
pub const javaEnabled = bridge.function(Navigator.javaEnabled, .{});
pub const sendBeacon = bridge.function(Navigator.sendBeacon, .{ .exposed = .window, .noop = true });
pub const sendBeacon = bridge.function(Navigator.sendBeacon, .{ .noop = true });
pub const permissions = bridge.accessor(Navigator.getPermissions, null, .{});
pub const storage = bridge.accessor(Navigator.getStorage, null, .{});
pub const userAgentData = bridge.accessor(Navigator.getUserAgentData, null, .{});
// window only
pub const plugins = bridge.accessor(Navigator.getPlugins, null, .{ .exposed = .window });
pub const geolocation = bridge.accessor(Navigator.getGeolocation, null, .{ .exposed = .window });
pub const modelContext = bridge.accessor(Navigator.getModelContext, null, .{ .exposed = .window });
pub const registerProtocolHandler = bridge.function(Navigator.registerProtocolHandler, .{ .exposed = .window });
pub const unregisterProtocolHandler = bridge.function(Navigator.unregisterProtocolHandler, .{ .exposed = .window });
pub const plugins = bridge.accessor(Navigator.getPlugins, null, .{});
pub const geolocation = bridge.accessor(Navigator.getGeolocation, null, .{});
pub const modelContext = bridge.accessor(Navigator.getModelContext, null, .{});
pub const registerProtocolHandler = bridge.function(Navigator.registerProtocolHandler, .{});
pub const unregisterProtocolHandler = bridge.function(Navigator.unregisterProtocolHandler, .{});
};
const testing = @import("../../testing.zig");
+3 -3
View File
@@ -36,7 +36,7 @@ const ScriptManagerBase = @import("../ScriptManagerBase.zig");
const Event = @import("Event.zig");
const Crypto = @import("Crypto.zig");
const Console = @import("Console.zig");
const Navigator = @import("Navigator.zig");
const WorkerNavigator = @import("WorkerNavigator.zig");
const Timers = @import("Timers.zig");
const Scheduler = @import("Scheduler.zig");
const EventTarget = @import("EventTarget.zig");
@@ -105,7 +105,7 @@ _message_ports: std.DoublyLinkedList = .{},
_proto: *EventTarget,
_console: Console = .init,
_crypto: Crypto = .init,
_navigator: Navigator = .init,
_navigator: WorkerNavigator = .init,
_performance: Performance,
_idb_factory: ?*idb.IDBFactory = null,
_on_error: ?JS.Function.Global = null,
@@ -300,7 +300,7 @@ pub fn getCrypto(self: *WorkerGlobalScope) *Crypto {
return &self._crypto;
}
pub fn getNavigator(self: *WorkerGlobalScope) *Navigator {
pub fn getNavigator(self: *WorkerGlobalScope) *WorkerNavigator {
return &self._navigator;
}
+135
View File
@@ -0,0 +1,135 @@
// 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 js = @import("../js/js.zig");
const Execution = js.Execution;
const Navigator = @import("Navigator.zig");
const Permissions = @import("Permissions.zig");
const StorageManager = @import("StorageManager.zig");
const NavigatorUAData = @import("NavigatorUAData.zig");
const WorkerNavigator = @This();
comptime {
// protect against identity_map conflict (make sure _pad: bool does its job)
for ([_][]const u8{ "_permissions", "_storage", "_ua_data" }) |name| {
if (@offsetOf(WorkerNavigator, name) == 0) {
@compileError(name ++ " aliases the WorkerNavigator");
}
}
}
_pad: bool = false,
_permissions: Permissions = .{},
_storage: StorageManager = .{},
_ua_data: NavigatorUAData = .{},
pub const init: WorkerNavigator = .{};
pub fn getUserAgent(_: *const WorkerNavigator, exec: *const Execution) []const u8 {
return Navigator.getUserAgent(&Navigator.init, exec);
}
pub fn getLanguages(_: *const WorkerNavigator) [2][]const u8 {
return Navigator.getLanguages(&Navigator.init);
}
pub fn getAppName(_: *const WorkerNavigator) []const u8 {
return Navigator.getAppName(&Navigator.init);
}
pub fn getAppCodeName(_: *const WorkerNavigator) []const u8 {
return Navigator.getAppCodeName(&Navigator.init);
}
pub fn getAppVersion(_: *const WorkerNavigator) []const u8 {
return Navigator.getAppVersion(&Navigator.init);
}
pub fn getLanguage(_: *const WorkerNavigator) []const u8 {
return Navigator.getLanguage(&Navigator.init);
}
pub fn getOnLine(_: *const WorkerNavigator) bool {
return Navigator.getOnLine(&Navigator.init);
}
pub fn getHardwareConcurrency(_: *const WorkerNavigator) u32 {
return Navigator.getHardwareConcurrency(&Navigator.init);
}
pub fn getDeviceMemory(_: *const WorkerNavigator) f64 {
return Navigator.getDeviceMemory(&Navigator.init);
}
pub fn getVendor(_: *const WorkerNavigator) []const u8 {
return Navigator.getVendor(&Navigator.init);
}
pub fn getProduct(_: *const WorkerNavigator) []const u8 {
return Navigator.getProduct(&Navigator.init);
}
pub fn getGlobalPrivacyControl(_: *const WorkerNavigator) bool {
return Navigator.getGlobalPrivacyControl(&Navigator.init);
}
pub fn getPlatform(_: *const WorkerNavigator) []const u8 {
return Navigator.getPlatform(&Navigator.init);
}
pub fn getPermissions(self: *WorkerNavigator) *Permissions {
return &self._permissions;
}
pub fn getStorage(self: *WorkerNavigator) *StorageManager {
return &self._storage;
}
pub fn getUserAgentData(self: *WorkerNavigator) *NavigatorUAData {
return &self._ua_data;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(WorkerNavigator);
pub const Meta = struct {
pub const name = "WorkerNavigator";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const userAgent = bridge.accessor(WorkerNavigator.getUserAgent, null, .{});
pub const appName = bridge.accessor(WorkerNavigator.getAppName, null, .{});
pub const appCodeName = bridge.accessor(WorkerNavigator.getAppCodeName, null, .{});
pub const appVersion = bridge.accessor(WorkerNavigator.getAppVersion, null, .{});
pub const platform = bridge.accessor(WorkerNavigator.getPlatform, null, .{});
pub const language = bridge.accessor(WorkerNavigator.getLanguage, null, .{});
pub const languages = bridge.accessor(WorkerNavigator.getLanguages, null, .{});
pub const onLine = bridge.accessor(WorkerNavigator.getOnLine, null, .{});
pub const hardwareConcurrency = bridge.accessor(WorkerNavigator.getHardwareConcurrency, null, .{});
pub const deviceMemory = bridge.accessor(WorkerNavigator.getDeviceMemory, null, .{});
pub const vendor = bridge.accessor(WorkerNavigator.getVendor, null, .{});
pub const product = bridge.accessor(WorkerNavigator.getProduct, null, .{});
pub const globalPrivacyControl = bridge.accessor(WorkerNavigator.getGlobalPrivacyControl, null, .{});
pub const permissions = bridge.accessor(WorkerNavigator.getPermissions, null, .{});
pub const storage = bridge.accessor(WorkerNavigator.getStorage, null, .{});
pub const userAgentData = bridge.accessor(WorkerNavigator.getUserAgentData, null, .{});
};