mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-14 23:15:14 -04:00
feat(webapi): resolve navigator.geolocation from the emulation override
This commit is contained in:
1 parent
0e875e2b03
commit
a67bfbb74d
2 files changed
+166
-6
No files matched your search
@@ -16,10 +16,10 @@
|
||||
// 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 Frame = @import("../Frame.zig");
|
||||
const GeolocationOverride = @import("../GeolocationOverride.zig");
|
||||
const log = lp.log;
|
||||
|
||||
const Geolocation = @This();
|
||||
@@ -62,14 +62,51 @@ const Task = struct {
|
||||
fn run(ctx: *anyopaque) anyerror!?u32 {
|
||||
const self: *Task = @ptrCast(@alignCast(ctx));
|
||||
defer self.deinit();
|
||||
deliverError(self.frame, self.error_cb);
|
||||
deliver(self.frame, self.success, self.error_cb);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Task 2 only implements the no-override error path: without a
|
||||
// Browser.geolocation_override, position is unavailable. Reading the
|
||||
// override into a GeolocationPosition for the success callback is Task 3.
|
||||
// Picks the success or error path based on permission + whether an override
|
||||
// is currently set. A denied permission always errors, even with an override
|
||||
// set, since Chrome treats "denied" as authoritative.
|
||||
fn deliver(frame: *Frame, success: js.Function.Global, error_cb: ?js.Function.Global) void {
|
||||
const browser = frame._session.browser;
|
||||
const perm = browser.permissions.get("geolocation") orelse .prompt;
|
||||
if (perm != .denied) {
|
||||
if (browser.geolocation_override) |ov| {
|
||||
deliverSuccess(frame, success, ov);
|
||||
return;
|
||||
}
|
||||
}
|
||||
deliverError(frame, error_cb);
|
||||
}
|
||||
|
||||
fn deliverSuccess(frame: *Frame, success: js.Function.Global, ov: GeolocationOverride) void {
|
||||
const coords = frame.arena.create(GeolocationCoordinates) catch |alloc_err| {
|
||||
log.err(.frame, "geolocation.deliverSuccess.alloc", .{ .err = alloc_err });
|
||||
return;
|
||||
};
|
||||
coords.* = .{ ._latitude = ov.latitude, ._longitude = ov.longitude, ._accuracy = ov.accuracy };
|
||||
|
||||
const pos = frame.arena.create(GeolocationPosition) catch |alloc_err| {
|
||||
log.err(.frame, "geolocation.deliverSuccess.alloc", .{ .err = alloc_err });
|
||||
return;
|
||||
};
|
||||
pos.* = .{ ._coords = coords, ._timestamp = @floatFromInt(lp.datetime.milliTimestamp(.real)) };
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: js.TryCatch.Caught = .{};
|
||||
ls.toLocal(success).tryCall(void, .{pos}, &caught) catch |call_err| {
|
||||
log.err(.frame, "geolocation.deliverSuccess", .{ .err = call_err, .caught = caught });
|
||||
};
|
||||
}
|
||||
|
||||
// Without a Browser.geolocation_override (or with permission denied),
|
||||
// position is unavailable.
|
||||
fn deliverError(frame: *Frame, error_cb: ?js.Function.Global) void {
|
||||
const err_cb = error_cb orelse return;
|
||||
|
||||
@@ -131,6 +168,77 @@ pub const JsApi = struct {
|
||||
// watchPosition / clearWatch come in Task 4
|
||||
};
|
||||
|
||||
pub const GeolocationCoordinates = struct {
|
||||
_latitude: f64,
|
||||
_longitude: f64,
|
||||
_accuracy: f64,
|
||||
_altitude: ?f64 = null,
|
||||
_altitude_accuracy: ?f64 = null,
|
||||
_heading: ?f64 = null,
|
||||
_speed: ?f64 = null,
|
||||
|
||||
pub fn getLatitude(self: *const GeolocationCoordinates) f64 {
|
||||
return self._latitude;
|
||||
}
|
||||
pub fn getLongitude(self: *const GeolocationCoordinates) f64 {
|
||||
return self._longitude;
|
||||
}
|
||||
pub fn getAccuracy(self: *const GeolocationCoordinates) f64 {
|
||||
return self._accuracy;
|
||||
}
|
||||
pub fn getAltitude(self: *const GeolocationCoordinates) ?f64 {
|
||||
return self._altitude;
|
||||
}
|
||||
pub fn getAltitudeAccuracy(self: *const GeolocationCoordinates) ?f64 {
|
||||
return self._altitude_accuracy;
|
||||
}
|
||||
pub fn getHeading(self: *const GeolocationCoordinates) ?f64 {
|
||||
return self._heading;
|
||||
}
|
||||
pub fn getSpeed(self: *const GeolocationCoordinates) ?f64 {
|
||||
return self._speed;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(GeolocationCoordinates);
|
||||
pub const Meta = struct {
|
||||
pub const name = "GeolocationCoordinates";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const latitude = bridge.accessor(GeolocationCoordinates.getLatitude, null, .{});
|
||||
pub const longitude = bridge.accessor(GeolocationCoordinates.getLongitude, null, .{});
|
||||
pub const accuracy = bridge.accessor(GeolocationCoordinates.getAccuracy, null, .{});
|
||||
pub const altitude = bridge.accessor(GeolocationCoordinates.getAltitude, null, .{});
|
||||
pub const altitudeAccuracy = bridge.accessor(GeolocationCoordinates.getAltitudeAccuracy, null, .{});
|
||||
pub const heading = bridge.accessor(GeolocationCoordinates.getHeading, null, .{});
|
||||
pub const speed = bridge.accessor(GeolocationCoordinates.getSpeed, null, .{});
|
||||
};
|
||||
};
|
||||
|
||||
pub const GeolocationPosition = struct {
|
||||
_coords: *GeolocationCoordinates,
|
||||
_timestamp: f64,
|
||||
|
||||
pub fn getCoords(self: *const GeolocationPosition) *GeolocationCoordinates {
|
||||
return self._coords;
|
||||
}
|
||||
pub fn getTimestamp(self: *const GeolocationPosition) f64 {
|
||||
return self._timestamp;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(GeolocationPosition);
|
||||
pub const Meta = struct {
|
||||
pub const name = "GeolocationPosition";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const coords = bridge.accessor(GeolocationPosition.getCoords, null, .{});
|
||||
pub const timestamp = bridge.accessor(GeolocationPosition.getTimestamp, null, .{});
|
||||
};
|
||||
};
|
||||
|
||||
pub const GeolocationPositionError = struct {
|
||||
_code: u16,
|
||||
_message: []const u8,
|
||||
@@ -156,7 +264,7 @@ pub const GeolocationPositionError = struct {
|
||||
};
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{ Geolocation, GeolocationPositionError };
|
||||
return &.{ Geolocation, GeolocationPosition, GeolocationCoordinates, GeolocationPositionError };
|
||||
}
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
|
||||
@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
|
||||
|
||||
const CDP = @import("../CDP.zig");
|
||||
const Config = @import("../../Config.zig");
|
||||
const js = @import("../../browser/js/js.zig");
|
||||
|
||||
const log = lp.log;
|
||||
|
||||
@@ -382,3 +383,54 @@ test "cdp.Emulation: setGeolocationOverride and clear" {
|
||||
try ctx.expectSentResult(null, .{ .id = 4 });
|
||||
try testing.expect(browser.geolocation_override == null);
|
||||
}
|
||||
|
||||
test "cdp.Emulation: navigator.geolocation reads the override" {
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-GEO2", .url = "cdp/dom1.html" });
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
.method = "Browser.grantPermissions",
|
||||
.params = .{ .permissions = &[_][]const u8{"geolocation"} },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 1, .session_id = null });
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 2,
|
||||
.method = "Emulation.setGeolocationOverride",
|
||||
.params = .{ .latitude = 48.0, .longitude = 2.0, .accuracy = 10 },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 2 });
|
||||
|
||||
const frame = bc.mainFrame() orelse unreachable;
|
||||
|
||||
{
|
||||
// Registers the callback synchronously; getCurrentPosition schedules
|
||||
// delivery on frame.js.scheduler and returns before it runs.
|
||||
var ls: js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
_ = try ls.local.exec(
|
||||
\\ window.__geo_ok = false;
|
||||
\\ navigator.geolocation.getCurrentPosition(p => {
|
||||
\\ window.__geo_ok = (Math.round(p.coords.latitude) === 48 && Math.round(p.coords.longitude) === 2);
|
||||
\\ });
|
||||
, null);
|
||||
}
|
||||
|
||||
// Drive the session loop so the scheduled Task fires: Runner._tick runs
|
||||
// browser.runMacrotasks() (which drains frame.js.scheduler) on every tick
|
||||
// for a loaded page, same primitive Runner.waitForSelector/waitForScript
|
||||
// use to pump pending scheduler work under a CDP-loaded page.
|
||||
var runner = bc.session.runner(.{});
|
||||
_ = try runner.tickForFrame(bc.page_handle.?.frame_id, 1000, .{ .until = .done });
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
const v = try ls.local.exec("window.__geo_ok", null);
|
||||
try testing.expect(v.isTrue());
|
||||
}
|
||||
Reference in new issue
Block a user