Skip to content

Commit ff43f74

Browse files
hjanuschkaDevtools-frontend LUCI CQ
authored andcommitted
Add accuracy parameter to geolocation emulation
This CL adds support for setting the accuracy parameter in geolocation emulation, allowing developers to test how their applications handle different levels of GPS accuracy. Bug: 40074843 Change-Id: I4eeb6240aaa2185f9b0fc64278941bf8ef2d31ec Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6559109 Commit-Queue: Mathias Bynens <[email protected]> Reviewed-by: Mathias Bynens <[email protected]> Reviewed-by: Danil Somsikov <[email protected]>
1 parent 86573a5 commit ff43f74

File tree

6 files changed

+130
-19
lines changed

6 files changed

+130
-19
lines changed

AUTHORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Feng Yu <[email protected]>
4040
Gabriel Luong <[email protected]>
4141
Gautham Banasandra <[email protected]>
4242
43+
Helmut Januschka <[email protected]>
4344
Henry Lim <[email protected]>
4445
4546
Jake Mulhern <[email protected]>
@@ -112,4 +113,3 @@ The Chromium Authors <*@chromium.org>
112113
The Qt Company <*@qt.io>
113114
# Please DO NOT APPEND here. See comments at the top of the file.
114115
# END organizations section.
115-

front_end/core/sdk/EmulationModel.ts

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ export class EmulationModel extends SDKModel<void> {
278278
.invoke_setGeolocationOverride({
279279
latitude: location.latitude,
280280
longitude: location.longitude,
281-
accuracy: Location.defaultGeoMockAccuracy,
281+
accuracy: location.accuracy,
282282
})
283283
.then(result => processEmulationResult('emulation-set-location', result)),
284284
this.#emulationAgent
@@ -451,45 +451,54 @@ export class EmulationModel extends SDKModel<void> {
451451
}
452452

453453
export class Location {
454+
static readonly DEFAULT_ACCURACY = 150;
454455
latitude: number;
455456
longitude: number;
456457
timezoneId: string;
457458
locale: string;
459+
accuracy: number;
458460
unavailable: boolean;
459461

460-
constructor(latitude: number, longitude: number, timezoneId: string, locale: string, unavailable: boolean) {
462+
constructor(
463+
latitude: number, longitude: number, timezoneId: string, locale: string, accuracy: number, unavailable: boolean) {
461464
this.latitude = latitude;
462465
this.longitude = longitude;
463466
this.timezoneId = timezoneId;
464467
this.locale = locale;
468+
this.accuracy = accuracy;
465469
this.unavailable = unavailable;
466470
}
467471

468472
static parseSetting(value: string): Location {
469473
if (value) {
470-
const [position, timezoneId, locale, unavailable] = value.split(':');
474+
const [position, timezoneId, locale, unavailable, ...maybeAccuracy] = value.split(':');
475+
const accuracy = maybeAccuracy.length ? Number(maybeAccuracy[0]) : Location.DEFAULT_ACCURACY;
471476
const [latitude, longitude] = position.split('@');
472-
return new Location(parseFloat(latitude), parseFloat(longitude), timezoneId, locale, Boolean(unavailable));
477+
return new Location(
478+
parseFloat(latitude), parseFloat(longitude), timezoneId, locale, accuracy, Boolean(unavailable));
473479
}
474-
return new Location(0, 0, '', '', false);
480+
return new Location(0, 0, '', '', Location.DEFAULT_ACCURACY, false);
475481
}
476482

477-
static parseUserInput(latitudeString: string, longitudeString: string, timezoneId: string, locale: string): Location
478-
|null {
479-
if (!latitudeString && !longitudeString) {
483+
static parseUserInput(
484+
latitudeString: string, longitudeString: string, timezoneId: string, locale: string,
485+
accuracyString: string): Location|null {
486+
if (!latitudeString && !longitudeString && !accuracyString) {
480487
return null;
481488
}
482489

483490
const isLatitudeValid = Location.latitudeValidator(latitudeString);
484491
const isLongitudeValid = Location.longitudeValidator(longitudeString);
492+
const {valid: isAccuracyValid} = Location.accuracyValidator(accuracyString);
485493

486-
if (!isLatitudeValid && !isLongitudeValid) {
494+
if (!isLatitudeValid && !isLongitudeValid && !isAccuracyValid) {
487495
return null;
488496
}
489497

490498
const latitude = isLatitudeValid ? parseFloat(latitudeString) : -1;
491499
const longitude = isLongitudeValid ? parseFloat(longitudeString) : -1;
492-
return new Location(latitude, longitude, timezoneId, locale, false);
500+
const accuracy = isAccuracyValid ? parseFloat(accuracyString) : Location.DEFAULT_ACCURACY;
501+
return new Location(latitude, longitude, timezoneId, locale, accuracy, false);
493502
}
494503

495504
static latitudeValidator(value: string): boolean {
@@ -522,11 +531,22 @@ export class Location {
522531
return value === '' || /[a-zA-Z]{2}/.test(value);
523532
}
524533

525-
toSetting(): string {
526-
return `${this.latitude}@${this.longitude}:${this.timezoneId}:${this.locale}:${this.unavailable || ''}`;
534+
static accuracyValidator(value: string): {
535+
valid: boolean,
536+
errorMessage: (string|undefined),
537+
} {
538+
if (!value) {
539+
return {valid: true, errorMessage: undefined};
540+
}
541+
const numValue = parseFloat(value);
542+
const valid = /^([+-]?[\d]+(\.\d+)?|[+-]?\.\d+)$/.test(value) && numValue >= 0;
543+
return {valid, errorMessage: undefined};
527544
}
528545

529-
static defaultGeoMockAccuracy = 150;
546+
toSetting(): string {
547+
return `${this.latitude}@${this.longitude}:${this.timezoneId}:${this.locale}:${this.unavailable || ''}:${
548+
this.accuracy || ''}`;
549+
}
530550
}
531551

532552
export class DeviceOrientation {

front_end/panels/sensors/LocationsSettingsTab.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import '../../ui/components/cards/cards.js';
77

88
import * as Common from '../../core/common/common.js';
99
import * as i18n from '../../core/i18n/i18n.js';
10+
import * as SDK from '../../core/sdk/sdk.js';
1011
import * as Buttons from '../../ui/components/buttons/buttons.js';
1112
import * as UI from '../../ui/legacy/legacy.js';
1213
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -47,6 +48,10 @@ const UIStrings = {
4748
*@description Label for text input for the longitude of a GPS position.
4849
*/
4950
longitude: 'Longitude',
51+
/**
52+
*@description Label for text input for the accuracy of a GPS position.
53+
*/
54+
accuracy: 'Accuracy',
5055
/**
5156
*@description Error message in the Locations settings pane that declares the location name input must not be empty
5257
*/
@@ -92,6 +97,15 @@ const UIStrings = {
9297
*@description Error message in the Locations settings pane that declares locale input invalid
9398
*/
9499
localeMustContainAlphabetic: 'Locale must contain alphabetic characters',
100+
/**
101+
*@description Error message in the Locations settings pane that declares that the value for the accuracy input must be a number
102+
*/
103+
accuracyMustBeANumber: 'Accuracy must be a number',
104+
/**
105+
*@description Error message in the Locations settings pane that declares the minimum value for the accuracy input
106+
*@example {0} PH1
107+
*/
108+
accuracyMustBeGreaterThanOrEqual: 'Accuracy must be greater than or equal to {PH1}',
95109
/**
96110
*@description Text of add locations button in Locations Settings Tab of the Device Toolbar
97111
*/
@@ -182,7 +196,14 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
182196
}
183197

184198
private addButtonClicked(): void {
185-
this.list.addNewItem(this.customSetting.get().length, {title: '', lat: 0, long: 0, timezoneId: '', locale: ''});
199+
this.list.addNewItem(this.customSetting.get().length, {
200+
title: '',
201+
lat: 0,
202+
long: 0,
203+
timezoneId: '',
204+
locale: '',
205+
accuracy: SDK.EmulationModel.Location.DEFAULT_ACCURACY
206+
});
186207
}
187208

188209
renderItem(location: LocationDescription, _editable: boolean): Element {
@@ -210,6 +231,9 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
210231
const locale = element.createChild('div', 'locations-list-text');
211232
locale.textContent = location.locale;
212233
locale.role = 'cell';
234+
element.createChild('div', 'locations-list-separator');
235+
element.createChild('div', 'locations-list-text').textContent =
236+
String(location.accuracy || SDK.EmulationModel.Location.DEFAULT_ACCURACY);
213237
return element;
214238
}
215239

@@ -229,6 +253,8 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
229253
location.timezoneId = timezoneId;
230254
const locale = editor.control('locale').value.trim();
231255
location.locale = locale;
256+
const accuracy = editor.control('accuracy').value.trim();
257+
location.accuracy = accuracy ? parseFloat(accuracy) : SDK.EmulationModel.Location.DEFAULT_ACCURACY;
232258

233259
const list = this.customSetting.get();
234260
if (isNew) {
@@ -244,6 +270,7 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
244270
editor.control('long').value = String(location.long);
245271
editor.control('timezone-id').value = location.timezoneId;
246272
editor.control('locale').value = location.locale;
273+
editor.control('accuracy').value = String(location.accuracy || SDK.EmulationModel.Location.DEFAULT_ACCURACY);
247274
return editor;
248275
}
249276

@@ -267,6 +294,8 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
267294
titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.timezoneId);
268295
titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
269296
titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.locale);
297+
titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
298+
titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.accuracy);
270299

271300
const fields = content.createChild('div', 'locations-edit-row');
272301
fields.createChild('div', 'locations-list-text locations-list-title locations-input-container')
@@ -287,6 +316,10 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
287316

288317
cell = fields.createChild('div', 'locations-list-text locations-input-container');
289318
cell.appendChild(editor.createInput('locale', 'text', i18nString(UIStrings.locale), localeValidator));
319+
fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
320+
321+
cell = fields.createChild('div', 'locations-list-text locations-input-container');
322+
cell.appendChild(editor.createInput('accuracy', 'text', i18nString(UIStrings.accuracy), accuracyValidator));
290323

291324
return editor;
292325

@@ -392,6 +425,29 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
392425
const errorMessage = i18nString(UIStrings.localeMustContainAlphabetic);
393426
return {valid: false, errorMessage};
394427
}
428+
429+
function accuracyValidator(
430+
_item: LocationDescription, _index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
431+
const minAccuracy = 0;
432+
const value = input.value.trim();
433+
const parsedValue = Number(value);
434+
435+
if (!value) {
436+
return {valid: true, errorMessage: undefined};
437+
}
438+
439+
let errorMessage;
440+
if (Number.isNaN(parsedValue)) {
441+
errorMessage = i18nString(UIStrings.accuracyMustBeANumber);
442+
} else if (parseFloat(value) < minAccuracy) {
443+
errorMessage = i18nString(UIStrings.accuracyMustBeGreaterThanOrEqual, {PH1: minAccuracy});
444+
}
445+
446+
if (errorMessage) {
447+
return {valid: false, errorMessage};
448+
}
449+
return {valid: true, errorMessage: undefined};
450+
}
395451
}
396452
}
397453
export interface LocationDescription {
@@ -400,4 +456,5 @@ export interface LocationDescription {
400456
long: number;
401457
timezoneId: string;
402458
locale: string;
459+
accuracy?: number;
403460
}

front_end/panels/sensors/SensorsView.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ const UIStrings = {
7373
*@description Label for the locale relevant to a custom location.
7474
*/
7575
locale: 'Locale',
76+
/**
77+
*@description Label for Accuracy of a GPS location.
78+
*/
79+
accuracy: 'Accuracy',
7680
/**
7781
*@description Label the orientation of a user's device e.g. tilt in 3D-space.
7882
*/
@@ -180,11 +184,14 @@ export class SensorsView extends UI.Widget.VBox {
180184
private longitudeInput!: HTMLInputElement;
181185
private timezoneInput!: HTMLInputElement;
182186
private localeInput!: HTMLInputElement;
187+
private accuracyInput!: HTMLInputElement;
183188
private latitudeSetter!: (arg0: string) => void;
184189
private longitudeSetter!: (arg0: string) => void;
185190
private timezoneSetter!: (arg0: string) => void;
186191
private localeSetter!: (arg0: string) => void;
192+
private accuracySetter!: (arg0: string) => void;
187193
private localeError!: HTMLElement;
194+
private accuracyError!: HTMLElement;
188195
private customLocationsGroup!: HTMLOptGroupElement;
189196
private readonly deviceOrientationSetting: Common.Settings.Setting<string>;
190197
private deviceOrientation: SDK.EmulationModel.DeviceOrientation;
@@ -310,6 +317,7 @@ export class SensorsView extends UI.Widget.VBox {
310317
const longitudeGroup = this.fieldsetElement.createChild('div', 'latlong-group');
311318
const timezoneGroup = this.fieldsetElement.createChild('div', 'latlong-group');
312319
const localeGroup = this.fieldsetElement.createChild('div', 'latlong-group');
320+
const accuracyGroup = this.fieldsetElement.createChild('div', 'latlong-group');
313321

314322
const cmdOrCtrl = Host.Platform.isMac() ? '\u2318' : 'Ctrl';
315323
const modifierKeyMessage = i18nString(UIStrings.adjustWithMousewheelOrUpdownKeys, {PH1: cmdOrCtrl});
@@ -357,6 +365,18 @@ export class SensorsView extends UI.Widget.VBox {
357365
this.localeSetter(location.locale);
358366
localeGroup.appendChild(UI.UIUtils.createLabel(i18nString(UIStrings.locale), 'locale-title', this.localeInput));
359367
this.localeError = localeGroup.createChild('div', 'locale-error');
368+
369+
this.accuracyInput = UI.UIUtils.createInput('', 'number', 'accuracy');
370+
accuracyGroup.appendChild(this.accuracyInput);
371+
this.accuracyInput.step = 'any';
372+
this.accuracyInput.value = SDK.EmulationModel.Location.DEFAULT_ACCURACY.toString();
373+
this.accuracySetter = UI.UIUtils.bindInput(
374+
this.accuracyInput, this.applyLocationUserInput.bind(this),
375+
(value: string) => SDK.EmulationModel.Location.accuracyValidator(value).valid, true, 1);
376+
this.accuracySetter(String(location.accuracy || SDK.EmulationModel.Location.DEFAULT_ACCURACY));
377+
accuracyGroup.appendChild(
378+
UI.UIUtils.createLabel(i18nString(UIStrings.accuracy), 'accuracy-title', this.accuracyInput));
379+
this.accuracyError = accuracyGroup.createChild('div', 'accuracy-error');
360380
}
361381

362382
#locationSelectChanged(): void {
@@ -371,23 +391,26 @@ export class SensorsView extends UI.Widget.VBox {
371391
this.#locationOverrideEnabled = true;
372392
const location = SDK.EmulationModel.Location.parseUserInput(
373393
this.latitudeInput.value.trim(), this.longitudeInput.value.trim(), this.timezoneInput.value.trim(),
374-
this.localeInput.value.trim());
394+
this.localeInput.value.trim(), this.accuracyInput.value.trim());
375395
if (!location) {
376396
return;
377397
}
378398
this.#location = location;
379399
} else if (value === NonPresetOptions.Unavailable) {
380400
this.#locationOverrideEnabled = true;
381-
this.#location = new SDK.EmulationModel.Location(0, 0, '', '', true);
401+
this.#location =
402+
new SDK.EmulationModel.Location(0, 0, '', '', SDK.EmulationModel.Location.DEFAULT_ACCURACY, true);
382403
} else {
383404
this.#locationOverrideEnabled = true;
384405
const coordinates = JSON.parse(value);
385406
this.#location = new SDK.EmulationModel.Location(
386-
coordinates.lat, coordinates.long, coordinates.timezoneId, coordinates.locale, false);
407+
coordinates.lat, coordinates.long, coordinates.timezoneId, coordinates.locale,
408+
coordinates.accuracy || SDK.EmulationModel.Location.DEFAULT_ACCURACY, false);
387409
this.latitudeSetter(coordinates.lat);
388410
this.longitudeSetter(coordinates.long);
389411
this.timezoneSetter(coordinates.timezoneId);
390412
this.localeSetter(coordinates.locale);
413+
this.accuracySetter(String(coordinates.accuracy || SDK.EmulationModel.Location.DEFAULT_ACCURACY));
391414
}
392415

393416
this.applyLocation();
@@ -399,7 +422,7 @@ export class SensorsView extends UI.Widget.VBox {
399422
private applyLocationUserInput(): void {
400423
const location = SDK.EmulationModel.Location.parseUserInput(
401424
this.latitudeInput.value.trim(), this.longitudeInput.value.trim(), this.timezoneInput.value.trim(),
402-
this.localeInput.value.trim());
425+
this.localeInput.value.trim(), this.accuracyInput.value.trim());
403426
if (!location) {
404427
return;
405428
}
@@ -438,6 +461,7 @@ export class SensorsView extends UI.Widget.VBox {
438461
this.longitudeSetter('0');
439462
this.timezoneSetter('');
440463
this.localeSetter('');
464+
this.accuracySetter(SDK.EmulationModel.Location.DEFAULT_ACCURACY.toString());
441465
}
442466

443467
private createDeviceOrientationSection(): void {

0 commit comments

Comments
 (0)