Commit e607ccc0 authored by Mathias Bynens's avatar Mathias Bynens Committed by Commit Bot

[DevTools] Enable emulating timezones

This patch adds support for emulating timezones through the Sensors
drawer. It’s based on the idea that emulating geolocation without
also emulating the local timezone (and vice versa) is detectable,
and therefore the two should be tied together for more robust
emulation.

Boring but useful test/demo page:
https://mathiasbynens.be/demo/timezone

Video: https://www.youtube.com/watch?v=pIpN_AuV4AI

Design doc: https://goo.gle/devtools-timezone-emulation

Bug: chromium:993628
Change-Id: I5ef867ac2723a341444ab1ee04a26c0fa9a9d62d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1860014
Commit-Queue: Mathias Bynens <mathias@chromium.org>
Reviewed-by: default avatarPaul Lewis <aerotwist@chromium.org>
Reviewed-by: default avatarSigurd Schneider <sigurds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706349}
parent 5bb16cdd
...@@ -48,7 +48,7 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox { ...@@ -48,7 +48,7 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox {
} }
_addButtonClicked() { _addButtonClicked() {
this._list.addNewItem(this._customSetting.get().length, {title: '', lat: 0, long: 0}); this._list.addNewItem(this._customSetting.get().length, {title: '', lat: 0, long: 0, timezoneId: ''});
} }
/** /**
...@@ -68,6 +68,8 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox { ...@@ -68,6 +68,8 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox {
element.createChild('div', 'geolocations-list-text').textContent = geolocation.lat; element.createChild('div', 'geolocations-list-text').textContent = geolocation.lat;
element.createChild('div', 'geolocations-list-separator'); element.createChild('div', 'geolocations-list-separator');
element.createChild('div', 'geolocations-list-text').textContent = geolocation.long; element.createChild('div', 'geolocations-list-text').textContent = geolocation.long;
element.createChild('div', 'geolocations-list-separator');
element.createChild('div', 'geolocations-list-text').textContent = geolocation.timezoneId;
return element; return element;
} }
...@@ -95,6 +97,8 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox { ...@@ -95,6 +97,8 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox {
geolocation.lat = lat ? parseFloat(lat) : 0; geolocation.lat = lat ? parseFloat(lat) : 0;
const long = editor.control('long').value.trim(); const long = editor.control('long').value.trim();
geolocation.long = long ? parseFloat(long) : 0; geolocation.long = long ? parseFloat(long) : 0;
const timezoneId = editor.control('timezoneId').value.trim();
geolocation.timezoneId = timezoneId;
const list = this._customSetting.get(); const list = this._customSetting.get();
if (isNew) { if (isNew) {
...@@ -114,6 +118,7 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox { ...@@ -114,6 +118,7 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox {
editor.control('title').value = geolocation.title; editor.control('title').value = geolocation.title;
editor.control('lat').value = String(geolocation.lat); editor.control('lat').value = String(geolocation.lat);
editor.control('long').value = String(geolocation.long); editor.control('long').value = String(geolocation.long);
editor.control('timezoneId').value = String(geolocation.timezoneId);
return editor; return editor;
} }
...@@ -136,6 +141,8 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox { ...@@ -136,6 +141,8 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox {
titles.createChild('div', 'geolocations-list-text').textContent = Common.UIString('Lat'); titles.createChild('div', 'geolocations-list-text').textContent = Common.UIString('Lat');
titles.createChild('div', 'geolocations-list-separator geolocations-list-separator-invisible'); titles.createChild('div', 'geolocations-list-separator geolocations-list-separator-invisible');
titles.createChild('div', 'geolocations-list-text').textContent = Common.UIString('Long'); titles.createChild('div', 'geolocations-list-text').textContent = Common.UIString('Long');
titles.createChild('div', 'geolocations-list-separator geolocations-list-separator-invisible');
titles.createChild('div', 'geolocations-list-text').textContent = Common.UIString('Timezone ID');
const fields = content.createChild('div', 'geolocations-edit-row'); const fields = content.createChild('div', 'geolocations-edit-row');
fields.createChild('div', 'geolocations-list-text geolocations-list-title') fields.createChild('div', 'geolocations-list-text geolocations-list-title')
...@@ -149,6 +156,9 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox { ...@@ -149,6 +156,9 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox {
cell = fields.createChild('div', 'geolocations-list-text'); cell = fields.createChild('div', 'geolocations-list-text');
cell.appendChild(editor.createInput('long', 'text', ls`Longitude`, longValidator)); cell.appendChild(editor.createInput('long', 'text', ls`Longitude`, longValidator));
cell = fields.createChild('div', 'geolocations-list-text');
cell.appendChild(editor.createInput('timezoneId', 'text', ls`Timezone ID`, timezoneIdValidator));
return editor; return editor;
/** /**
...@@ -235,8 +245,28 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox { ...@@ -235,8 +245,28 @@ Emulation.GeolocationsSettingsTab = class extends UI.VBox {
} }
return {valid: true}; return {valid: true};
} }
/**
* @param {*} item
* @param {number} index
* @param {!HTMLInputElement|!HTMLSelectElement} input
* @return {!UI.ListWidget.ValidatorResult}
*/
function timezoneIdValidator(item, index, input) {
const value = input.value.trim();
// Chromium uses ICU's timezone implementation, which is very
// liberal in what it accepts. ICU does not simply use an allowlist
// but instead tries to make sense of the input, even for
// weird-looking timezone IDs. There's not much point in validating
// the input other than checking if it contains at least one slash.
if (value === '' || value.includes('/')) {
return {valid: true};
}
const errorMessage = ls`Timezone ID must contain "/"`;
return {valid: false, errorMessage};
}
} }
}; };
/** @typedef {{title: string, lat: number, long: number}} */ /** @typedef {{title: string, lat: number, long: number}} */
Emulation.GeolocationsSettingsTab.Item; Emulation.GeolocationsSettingsTab.Item;
\ No newline at end of file
...@@ -95,6 +95,10 @@ Emulation.SensorsView = class extends UI.VBox { ...@@ -95,6 +95,10 @@ Emulation.SensorsView = class extends UI.VBox {
const latitudeGroup = this._fieldsetElement.createChild('div', 'latlong-group'); const latitudeGroup = this._fieldsetElement.createChild('div', 'latlong-group');
const longitudeGroup = this._fieldsetElement.createChild('div', 'latlong-group'); const longitudeGroup = this._fieldsetElement.createChild('div', 'latlong-group');
const timezoneGroup = this._fieldsetElement.createChild('div', 'latlong-group');
const cmdOrCtrl = Host.isMac() ? '\u2318' : 'Ctrl';
const modifierKeyMessage = ls`Adjust with mousewheel or up/down keys. ${cmdOrCtrl}: ±10, Shift: ±1, Alt: ±0.01`;
this._latitudeInput = UI.createInput('', 'number'); this._latitudeInput = UI.createInput('', 'number');
latitudeGroup.appendChild(this._latitudeInput); latitudeGroup.appendChild(this._latitudeInput);
...@@ -104,6 +108,8 @@ Emulation.SensorsView = class extends UI.VBox { ...@@ -104,6 +108,8 @@ Emulation.SensorsView = class extends UI.VBox {
this._latitudeInput, this._applyGeolocationUserInput.bind(this), this._latitudeInput, this._applyGeolocationUserInput.bind(this),
SDK.EmulationModel.Geolocation.latitudeValidator, true, 0.1); SDK.EmulationModel.Geolocation.latitudeValidator, true, 0.1);
this._latitudeSetter(String(geolocation.latitude)); this._latitudeSetter(String(geolocation.latitude));
this._latitudeInput.title = modifierKeyMessage;
latitudeGroup.appendChild(UI.createLabel(ls`Latitude`, 'latlong-title', this._latitudeInput));
this._longitudeInput = UI.createInput('', 'number'); this._longitudeInput = UI.createInput('', 'number');
longitudeGroup.appendChild(this._longitudeInput); longitudeGroup.appendChild(this._longitudeInput);
...@@ -113,14 +119,17 @@ Emulation.SensorsView = class extends UI.VBox { ...@@ -113,14 +119,17 @@ Emulation.SensorsView = class extends UI.VBox {
this._longitudeInput, this._applyGeolocationUserInput.bind(this), this._longitudeInput, this._applyGeolocationUserInput.bind(this),
SDK.EmulationModel.Geolocation.longitudeValidator, true, 0.1); SDK.EmulationModel.Geolocation.longitudeValidator, true, 0.1);
this._longitudeSetter(String(geolocation.longitude)); this._longitudeSetter(String(geolocation.longitude));
const cmdOrCtrl = Host.isMac() ? '\u2318' : 'Ctrl';
const modifierKeyMessage = ls`Adjust with mousewheel or up/down keys. ${cmdOrCtrl}: ±10, Shift: ±1, Alt: ±0.01`;
this._latitudeInput.title = modifierKeyMessage;
this._longitudeInput.title = modifierKeyMessage; this._longitudeInput.title = modifierKeyMessage;
latitudeGroup.appendChild(UI.createLabel(ls`Latitude`, 'latlong-title', this._latitudeInput));
longitudeGroup.appendChild(UI.createLabel(ls`Longitude`, 'latlong-title', this._longitudeInput)); longitudeGroup.appendChild(UI.createLabel(ls`Longitude`, 'latlong-title', this._longitudeInput));
this._timezoneInput = UI.createInput('', 'text');
timezoneGroup.appendChild(this._timezoneInput);
this._timezoneInput.value = 'Europe/Berlin';
this._timezoneSetter = UI.bindInput(
this._timezoneInput, this._applyGeolocationUserInput.bind(this),
SDK.EmulationModel.Geolocation.timezoneIdValidator, false);
this._timezoneSetter(String(geolocation.timezoneId));
timezoneGroup.appendChild(UI.createLabel(ls`Timezone ID`, 'timezone-title', this._timezoneInput));
} }
_geolocationSelectChanged() { _geolocationSelectChanged() {
...@@ -132,20 +141,22 @@ Emulation.SensorsView = class extends UI.VBox { ...@@ -132,20 +141,22 @@ Emulation.SensorsView = class extends UI.VBox {
} else if (value === Emulation.SensorsView.NonPresetOptions.Custom) { } else if (value === Emulation.SensorsView.NonPresetOptions.Custom) {
this._geolocationOverrideEnabled = true; this._geolocationOverrideEnabled = true;
const geolocation = SDK.EmulationModel.Geolocation.parseUserInput( const geolocation = SDK.EmulationModel.Geolocation.parseUserInput(
this._latitudeInput.value.trim(), this._longitudeInput.value.trim(), ''); this._latitudeInput.value.trim(), this._longitudeInput.value.trim(), this._timezoneInput.value.trim());
if (!geolocation) { if (!geolocation) {
return; return;
} }
this._geolocation = geolocation; this._geolocation = geolocation;
} else if (value === Emulation.SensorsView.NonPresetOptions.Unavailable) { } else if (value === Emulation.SensorsView.NonPresetOptions.Unavailable) {
this._geolocationOverrideEnabled = true; this._geolocationOverrideEnabled = true;
this._geolocation = new SDK.EmulationModel.Geolocation(0, 0, true); this._geolocation = new SDK.EmulationModel.Geolocation(0, 0, '', true);
} else { } else {
this._geolocationOverrideEnabled = true; this._geolocationOverrideEnabled = true;
const coordinates = JSON.parse(value); const coordinates = JSON.parse(value);
this._geolocation = new SDK.EmulationModel.Geolocation(coordinates.lat, coordinates.long, false); this._geolocation =
new SDK.EmulationModel.Geolocation(coordinates.lat, coordinates.long, coordinates.timezoneId, false);
this._latitudeSetter(coordinates.lat); this._latitudeSetter(coordinates.lat);
this._longitudeSetter(coordinates.long); this._longitudeSetter(coordinates.long);
this._timezoneSetter(coordinates.timezoneId);
} }
this._applyGeolocation(); this._applyGeolocation();
...@@ -156,7 +167,7 @@ Emulation.SensorsView = class extends UI.VBox { ...@@ -156,7 +167,7 @@ Emulation.SensorsView = class extends UI.VBox {
_applyGeolocationUserInput() { _applyGeolocationUserInput() {
const geolocation = SDK.EmulationModel.Geolocation.parseUserInput( const geolocation = SDK.EmulationModel.Geolocation.parseUserInput(
this._latitudeInput.value.trim(), this._longitudeInput.value.trim(), ''); this._latitudeInput.value.trim(), this._longitudeInput.value.trim(), this._timezoneInput.value.trim());
if (!geolocation) { if (!geolocation) {
return; return;
} }
......
...@@ -123,6 +123,9 @@ ...@@ -123,6 +123,9 @@
<message name="IDS_DEVTOOLS_5b4f1c0e2bd8bc1ba726cd51dd70c1a6" desc="City name in Geolocations Settings"> <message name="IDS_DEVTOOLS_5b4f1c0e2bd8bc1ba726cd51dd70c1a6" desc="City name in Geolocations Settings">
Mountain View Mountain View
</message> </message>
<message name="IDS_DEVTOOLS_5cacd64cfc65b4f4ea83306b6cebf0be" desc="A tag of Sensors tool that can be searched in the command menu">
timezones
</message>
<message name="IDS_DEVTOOLS_614103b76fd0d9de068d69034fb6f987" desc="Text in Device Mode View of the Device Toolbar"> <message name="IDS_DEVTOOLS_614103b76fd0d9de068d69034fb6f987" desc="Text in Device Mode View of the Device Toolbar">
(<ph name="THIS__MODEL_DEVICE___TITLE">$1s<ex>iPhone X</ex></ph>) (<ph name="THIS__MODEL_DEVICE___TITLE">$1s<ex>iPhone X</ex></ph>)
</message> </message>
...@@ -180,6 +183,9 @@ ...@@ -180,6 +183,9 @@
<message name="IDS_DEVTOOLS_815dff01257e5ef182b25d4c1ef0a7a0" desc="Text in Device Mode Toolbar of the Device Toolbar"> <message name="IDS_DEVTOOLS_815dff01257e5ef182b25d4c1ef0a7a0" desc="Text in Device Mode Toolbar of the Device Toolbar">
Landscape Landscape
</message> </message>
<message name="IDS_DEVTOOLS_8189965036f7d675e0b7af67a2a873a0" desc="Text in Sensors View of the Device Toolbar">
Timezone ID
</message>
<message name="IDS_DEVTOOLS_81c3744a7fe979d029d1e7b84afd613f" desc="Reload warning text content in Sensors View of the Device Toolbar"> <message name="IDS_DEVTOOLS_81c3744a7fe979d029d1e7b84afd613f" desc="Reload warning text content in Sensors View of the Device Toolbar">
*Requires reload *Requires reload
</message> </message>
...@@ -309,6 +315,9 @@ ...@@ -309,6 +315,9 @@
<message name="IDS_DEVTOOLS_d15305d7a4e34e02489c74a5ef542f36" desc="Text in Sensors View of the Device Toolbar"> <message name="IDS_DEVTOOLS_d15305d7a4e34e02489c74a5ef542f36" desc="Text in Sensors View of the Device Toolbar">
Off Off
</message> </message>
<message name="IDS_DEVTOOLS_d9e39d238ea8f70b277a68d9c07b5046" desc="Error message in the Geolocations settings pane that declares timezone ID input invalid">
Timezone ID must contain &quot;/&quot;
</message>
<message name="IDS_DEVTOOLS_da31f3ff326e70f6b08748520c553920" desc="Text in Sensors View of the Device Toolbar"> <message name="IDS_DEVTOOLS_da31f3ff326e70f6b08748520c553920" desc="Text in Sensors View of the Device Toolbar">
γ (gamma) γ (gamma)
</message> </message>
...@@ -354,4 +363,4 @@ ...@@ -354,4 +363,4 @@
<message name="IDS_DEVTOOLS_fd37e9411d9f8a6af8fb4d3b787ccfc6" desc="Error message in the Devices settings pane that declares the minimum value for the width input"> <message name="IDS_DEVTOOLS_fd37e9411d9f8a6af8fb4d3b787ccfc6" desc="Error message in the Devices settings pane that declares the minimum value for the width input">
Width must be greater than or equal to <ph name="EMULATION_DEVICEMODEMODEL_MINDEVICESIZE">$1s<ex>50</ex></ph>. Width must be greater than or equal to <ph name="EMULATION_DEVICEMODEMODEL_MINDEVICESIZE">$1s<ex>50</ex></ph>.
</message> </message>
</grit-part> </grit-part>
\ No newline at end of file
...@@ -5,84 +5,87 @@ ...@@ -5,84 +5,87 @@
*/ */
:host { :host {
overflow:hidden; overflow: hidden;
} }
.header { .header {
padding: 0 0 6px; padding: 0 0 6px;
border-bottom: 1px solid #EEEEEE; border-bottom: 1px solid #EEEEEE;
font-size: 18px; font-size: 18px;
font-weight: normal; font-weight: normal;
flex: none; flex: none;
} }
.add-geolocations-button { .add-geolocations-button {
flex: none; flex: none;
margin: 10px 2px; margin: 10px 2px;
min-width: 140px; min-width: 140px;
align-self: flex-start; align-self: flex-start;
} }
.geolocations-list { .geolocations-list {
max-width: 500px; max-width: 600px;
min-width: 340px; min-width: 340px;
flex: auto; flex: auto;
} }
.geolocations-list-item { .geolocations-list-item {
padding: 3px 5px 3px 5px; padding: 3px 5px 3px 5px;
height: 30px; height: 30px;
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;
flex: auto 1 1; flex: auto 1 1;
} }
.geolocations-list-text { .geolocations-list-text {
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
flex: 0 0 70px; flex: 0 0 70px;
-webkit-user-select: none; -webkit-user-select: none;
color: #222; color: #222;
text-align: end; text-align: end;
position: relative; position: relative;
}
.geolocations-list-text:last-child {
flex: 0 0 170px;
} }
.geolocations-list-title { .geolocations-list-title {
text-align: start; text-align: start;
flex: auto; flex: auto;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
} }
.geolocations-list-title-text { .geolocations-list-title-text {
overflow: hidden; overflow: hidden;
flex: auto; flex: auto;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.geolocations-list-separator { .geolocations-list-separator {
flex: 0 0 1px; flex: 0 0 1px;
background-color: rgb(231, 231, 231); background-color: rgb(231, 231, 231);
height: 30px; height: 30px;
margin: 0 4px; margin: 0 4px;
} }
.geolocations-list-separator-invisible { .geolocations-list-separator-invisible {
visibility: hidden; visibility: hidden;
height: 100% !important; height: 100% !important;
} }
.geolocations-edit-row { .geolocations-edit-row {
flex: none; flex: none;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin: 6px 5px; margin: 6px 5px;
} }
.geolocations-edit-row input { .geolocations-edit-row input {
width: 100%; width: 100%;
text-align: inherit; text-align: inherit;
} }
...@@ -146,25 +146,25 @@ ...@@ -146,25 +146,25 @@
"persistence": "closeable", "persistence": "closeable",
"order": 100, "order": 100,
"className": "Emulation.SensorsView", "className": "Emulation.SensorsView",
"tags": "geolocation, accelerometer, device orientation" "tags": "geolocation, timezones, accelerometer, device orientation"
}, },
{ {
"type": "setting", "type": "setting",
"settingName": "emulation.geolocations", "settingName": "emulation.geolocations",
"settingType": "array", "settingType": "array",
"defaultValue": [ "defaultValue": [
{"title": "Berlin", "lat": 52.520007, "long": 13.404954}, {"title": "Berlin", "lat": 52.520007, "long": 13.404954, "timezoneId": "Europe/Berlin"},
{"title": "London", "lat": 51.507351, "long": -0.127758}, {"title": "London", "lat": 51.507351, "long": -0.127758, "timezoneId": "Europe/London"},
{"title": "Moscow", "lat": 55.755826, "long": 37.617300}, {"title": "Moscow", "lat": 55.755826, "long": 37.617300, "timezoneId": "Europe/Moscow"},
{"title": "Mountain View", "lat": 37.386052, "long": -122.083851}, {"title": "Mountain View", "lat": 37.386052, "long": -122.083851, "timezoneId": "US/Pacific"},
{"title": "Mumbai", "lat": 19.075984, "long": 72.877656}, {"title": "Mumbai", "lat": 19.075984, "long": 72.877656, "timezoneId": "Asia/Kolkata"},
{"title": "San Francisco", "lat": 37.774929, "long": -122.419416}, {"title": "San Francisco", "lat": 37.774929, "long": -122.419416, "timezoneId": "US/Pacific"},
{"title": "Shanghai", "lat": 31.230416, "long": 121.473701}, {"title": "Shanghai", "lat": 31.230416, "long": 121.473701, "timezoneId": "Asia/Shanghai"},
{"title": "São Paulo", "lat": -23.550520, "long": -46.633309}, {"title": "São Paulo", "lat": -23.550520, "long": -46.633309, "timezoneId": "America/Sao_Paulo"},
{"title": "Tokyo", "lat": 35.689487, "long": 139.691706} {"title": "Tokyo", "lat": 35.689487, "long": 139.691706, "timezoneId": "Asia/Tokyo"}
] ]
}, },
{ {
"type": "view", "type": "view",
"location": "settings-view", "location": "settings-view",
"id": "emulation-geolocations", "id": "emulation-geolocations",
......
...@@ -5,165 +5,164 @@ ...@@ -5,165 +5,164 @@
*/ */
.sensors-view { .sensors-view {
padding: 12px; padding: 12px;
display: block; display: block;
} }
.sensors-view input { .sensors-view input {
width: 100%; width: 100%;
max-width: 100px; max-width: 120px;
margin: -5px 10px 0px 0px; margin: -5px 10px 0px 0px;
text-align: end; text-align: end;
} }
.sensors-view input[readonly] { .sensors-view input[readonly] {
background-color: rgb(235, 235, 228); background-color: rgb(235, 235, 228);
} }
.sensors-view fieldset { .sensors-view fieldset {
border: none; border: none;
padding: 10px 0px; padding: 10px 0px;
margin-left: 0; margin-left: 0;
flex: 0 0 auto; flex: 0 0 auto;
margin: 0; margin: 0;
} }
.sensors-view fieldset[disabled] { .sensors-view fieldset[disabled] {
opacity: 0.5; opacity: 0.5;
} }
.sensors-view input:focus::-webkit-input-placeholder { .sensors-view input:focus::-webkit-input-placeholder {
color: transparent !important; color: transparent !important;
} }
.sensors-view .chrome-select { .sensors-view .chrome-select {
width: 200px; width: 200px;
} }
.sensors-group-title { .sensors-group-title {
width: 80px; width: 80px;
line-height: 24px; line-height: 24px;
} }
.sensors-group { .sensors-group {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 10px; margin-bottom: 10px;
} }
.geo-fields { .geo-fields {
flex: 2 0 200px; flex: 2 0 200px;
} }
.latlong-group { .latlong-group {
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;
} }
.latlong-title { .latlong-title {
width: 70px; width: 70px;
} }
/* Device Orientation */ /* Device Orientation */
.orientation-content { .orientation-content {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.orientation-fields { .orientation-fields {
margin-right: 10px; margin-right: 10px;
} }
.orientation-stage { .orientation-stage {
-webkit-perspective: 700px; -webkit-perspective: 700px;
-webkit-perspective-origin: 50% 50%; -webkit-perspective-origin: 50% 50%;
width: 160px; width: 160px;
height: 150px; height: 150px;
background: linear-gradient(#E1F5FE 0%, #E1F5FE 64%, #b0Ebf3 64%, #DEF6F9 100%); background: linear-gradient(#E1F5FE 0%, #E1F5FE 64%, #b0Ebf3 64%, #DEF6F9 100%);
transition: 0.2s ease opacity, 0.2s ease -webkit-filter; transition: 0.2s ease opacity, 0.2s ease -webkit-filter;
overflow: hidden; overflow: hidden;
margin-bottom: 10px; margin-bottom: 10px;
} }
.orientation-stage.disabled { .orientation-stage.disabled {
-webkit-filter: grayscale(); -webkit-filter: grayscale();
opacity: 0.5; opacity: 0.5;
cursor: default !important; cursor: default !important;
} }
.orientation-element, .orientation-element,
.orientation-element::before, .orientation-element::before,
.orientation-element::after .orientation-element::after{
{ position: absolute;
position: absolute; box-sizing: border-box;
box-sizing: border-box; transform-style: preserve-3d;
transform-style: preserve-3d; background: no-repeat;
background: no-repeat; background-size: cover;
background-size: cover; backface-visibility: hidden;
backface-visibility: hidden;
} }
.orientation-box { .orientation-box {
width: 62px; width: 62px;
height: 122px; height: 122px;
left: 0; left: 0;
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
margin: auto; margin: auto;
transform: rotate3d(1, 0, 0, 90deg); transform: rotate3d(1, 0, 0, 90deg);
} }
.orientation-box.is-animating, .is-animating .orientation-layer { .orientation-box.is-animating, .is-animating .orientation-layer {
transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
} }
.orientation-layer { .orientation-layer {
width: 100%; width: 100%;
height: 100%; height: 100%;
transform-style: preserve-3d; transform-style: preserve-3d;
} }
.orientation-front, .orientation-front,
.orientation-back .orientation-back
{ {
width: 62px; width: 62px;
height: 122px; height: 122px;
border-radius: 8px; border-radius: 8px;
} }
.orientation-front { .orientation-front {
background-image: url(Images/accelerometer-front.svg); background-image: url(Images/accelerometer-front.svg);
} }
.orientation-back { .orientation-back {
transform: rotateY(180deg) translateZ(8px); transform: rotateY(180deg) translateZ(8px);
background-image: url(Images/accelerometer-back.svg); background-image: url(Images/accelerometer-back.svg);
} }
.orientation-left, .orientation-left,
.orientation-right { .orientation-right {
width: 8px; width: 8px;
height: 106px; height: 106px;
top: 8px; top: 8px;
background-position: center center; background-position: center center;
} }
.orientation-left { .orientation-left {
left: -8px; left: -8px;
transform-origin: right center; transform-origin: right center;
transform: rotateY(-90deg); transform: rotateY(-90deg);
background-image: url(Images/accelerometer-left.png); background-image: url(Images/accelerometer-left.png);
} }
.orientation-right { .orientation-right {
right: -8px; right: -8px;
transform-origin: left center; transform-origin: left center;
transform: rotateY(90deg); transform: rotateY(90deg);
background-image: url(Images/accelerometer-right.png); background-image: url(Images/accelerometer-right.png);
} }
.orientation-left::before, .orientation-left::before,
...@@ -171,60 +170,60 @@ ...@@ -171,60 +170,60 @@
.orientation-right::before, .orientation-right::before,
.orientation-right::after .orientation-right::after
{ {
content: ''; content: '';
width: 8px; width: 8px;
height: 6px; height: 6px;
} }
.orientation-left::before, .orientation-left::before,
.orientation-left::after .orientation-left::after
{ {
background-image: url(Images/accelerometer-left.png); background-image: url(Images/accelerometer-left.png);
} }
.orientation-right::before, .orientation-right::before,
.orientation-right::after .orientation-right::after
{ {
background-image: url(Images/accelerometer-right.png); background-image: url(Images/accelerometer-right.png);
} }
.orientation-left::before, .orientation-left::before,
.orientation-right::before { .orientation-right::before {
top: -6px; top: -6px;
transform-origin: center bottom; transform-origin: center bottom;
transform: rotateX(26deg); transform: rotateX(26deg);
background-position: center top; background-position: center top;
} }
.orientation-left::after, .orientation-left::after,
.orientation-right::after { .orientation-right::after {
bottom: -6px; bottom: -6px;
transform-origin: center top; transform-origin: center top;
transform: rotateX(-25deg); transform: rotateX(-25deg);
background-position: center bottom; background-position: center bottom;
} }
.orientation-top, .orientation-top,
.orientation-bottom { .orientation-bottom {
width: 50px; width: 50px;
height: 8px; height: 8px;
left: 8px; left: 8px;
background-position: center center; background-position: center center;
} }
.orientation-top { .orientation-top {
top: -8px; top: -8px;
transform-origin: center bottom; transform-origin: center bottom;
transform: rotateX(90deg); transform: rotateX(90deg);
background-image: url(Images/accelerometer-top.png); background-image: url(Images/accelerometer-top.png);
} }
.orientation-bottom { .orientation-bottom {
bottom: -8px; bottom: -8px;
transform-origin: center top; transform-origin: center top;
transform: rotateX(-90deg); transform: rotateX(-90deg);
background-image: url(Images/accelerometer-bottom.png); background-image: url(Images/accelerometer-bottom.png);
} }
...@@ -233,67 +232,67 @@ ...@@ -233,67 +232,67 @@
.orientation-bottom::before, .orientation-bottom::before,
.orientation-bottom::after .orientation-bottom::after
{ {
content: ''; content: '';
width: 8px; width: 8px;
height: 8px; height: 8px;
} }
.orientation-top::before, .orientation-top::before,
.orientation-top::after .orientation-top::after
{ {
background-image: url(Images/accelerometer-top.png); background-image: url(Images/accelerometer-top.png);
} }
.orientation-bottom::before, .orientation-bottom::before,
.orientation-bottom::after .orientation-bottom::after
{ {
background-image: url(Images/accelerometer-bottom.png); background-image: url(Images/accelerometer-bottom.png);
} }
.orientation-top::before, .orientation-top::before,
.orientation-bottom::before { .orientation-bottom::before {
left: -6px; left: -6px;
transform-origin: right center; transform-origin: right center;
transform: rotateY(-26deg); transform: rotateY(-26deg);
background-position: left center; background-position: left center;
} }
.orientation-top::after, .orientation-top::after,
.orientation-bottom::after { .orientation-bottom::after {
right: -6px; right: -6px;
transform-origin: left center; transform-origin: left center;
transform: rotateY(26deg); transform: rotateY(26deg);
background-position: right center; background-position: right center;
} }
.orientation-axis-input-container { .orientation-axis-input-container {
margin-bottom: 10px; margin-bottom: 10px;
} }
.orientation-axis-input-container input { .orientation-axis-input-container input {
max-width: 100px; max-width: 120px;
} }
.orientation-reset-button { .orientation-reset-button {
min-width: 80px; min-width: 80px;
} }
fieldset.device-orientation-override-section { fieldset.device-orientation-override-section {
margin: 0; margin: 0;
display: flex; display: flex;
} }
.panel-section-separator { .panel-section-separator {
height: 2px; height: 2px;
margin-bottom: 8px; margin-bottom: 8px;
background: #f1f1f1; background: #f1f1f1;
} }
.reload-warning { .reload-warning {
align-self: center; align-self: center;
margin-left: 10px; margin-left: 10px;
} }
button.text-button { button.text-button {
margin: 0 10px; margin: 0 10px;
} }
...@@ -95,14 +95,17 @@ export default class EmulationModel extends SDK.SDKModel { ...@@ -95,14 +95,17 @@ export default class EmulationModel extends SDK.SDKModel {
emulateGeolocation(geolocation) { emulateGeolocation(geolocation) {
if (!geolocation) { if (!geolocation) {
this._emulationAgent.clearGeolocationOverride(); this._emulationAgent.clearGeolocationOverride();
this._emulationAgent.setTimezoneOverride('');
return; return;
} }
if (geolocation.error) { if (geolocation.error) {
this._emulationAgent.setGeolocationOverride(); this._emulationAgent.setGeolocationOverride();
this._emulationAgent.setTimezoneOverride('');
} else { } else {
this._emulationAgent.setGeolocationOverride( this._emulationAgent.setGeolocationOverride(
geolocation.latitude, geolocation.longitude, Geolocation.DefaultMockAccuracy); geolocation.latitude, geolocation.longitude, Geolocation.DefaultMockAccuracy);
this._emulationAgent.setTimezoneOverride(geolocation.timezoneId);
} }
} }
...@@ -201,11 +204,13 @@ export class Geolocation { ...@@ -201,11 +204,13 @@ export class Geolocation {
/** /**
* @param {number} latitude * @param {number} latitude
* @param {number} longitude * @param {number} longitude
* @param {string} timezoneId
* @param {boolean} error * @param {boolean} error
*/ */
constructor(latitude, longitude, error) { constructor(latitude, longitude, timezoneId, error) {
this.latitude = latitude; this.latitude = latitude;
this.longitude = longitude; this.longitude = longitude;
this.timezoneId = timezoneId;
this.error = error; this.error = error;
} }
...@@ -214,24 +219,20 @@ export class Geolocation { ...@@ -214,24 +219,20 @@ export class Geolocation {
*/ */
static parseSetting(value) { static parseSetting(value) {
if (value) { if (value) {
const splitError = value.split(':'); const [position, timezoneId, error] = value.split(':');
if (splitError.length === 2) { const [latitude, longitude] = position.split('@');
const splitPosition = splitError[0].split('@'); return new Geolocation(parseFloat(latitude), parseFloat(longitude), timezoneId, Boolean(error));
if (splitPosition.length === 2) {
return new Geolocation(parseFloat(splitPosition[0]), parseFloat(splitPosition[1]), !!splitError[1]);
}
}
} }
return new Geolocation(0, 0, false); return new Geolocation(0, 0, '', false);
} }
/** /**
* @param {string} latitudeString * @param {string} latitudeString
* @param {string} longitudeString * @param {string} longitudeString
* @param {string} errorStatus * @param {string} timezoneId
* @return {?Geolocation} * @return {?Geolocation}
*/ */
static parseUserInput(latitudeString, longitudeString, errorStatus) { static parseUserInput(latitudeString, longitudeString, timezoneId) {
if (!latitudeString && !longitudeString) { if (!latitudeString && !longitudeString) {
return null; return null;
} }
...@@ -245,7 +246,7 @@ export class Geolocation { ...@@ -245,7 +246,7 @@ export class Geolocation {
const latitude = isLatitudeValid ? parseFloat(latitudeString) : -1; const latitude = isLatitudeValid ? parseFloat(latitudeString) : -1;
const longitude = isLongitudeValid ? parseFloat(longitudeString) : -1; const longitude = isLongitudeValid ? parseFloat(longitudeString) : -1;
return new Geolocation(latitude, longitude, !!errorStatus); return new Geolocation(latitude, longitude, timezoneId, false);
} }
/** /**
...@@ -268,11 +269,25 @@ export class Geolocation { ...@@ -268,11 +269,25 @@ export class Geolocation {
return {valid}; return {valid};
} }
/**
* @param {string} value
* @return {{valid: boolean, errorMessage: (string|undefined)}}
*/
static timezoneIdValidator(value) {
// Chromium uses ICU's timezone implementation, which is very
// liberal in what it accepts. ICU does not simply use an allowlist
// but instead tries to make sense of the input, even for
// weird-looking timezone IDs. There's not much point in validating
// the input other than checking if it contains at least one slash.
const valid = value.includes('/');
return {valid};
}
/** /**
* @return {string} * @return {string}
*/ */
toSetting() { toSetting() {
return this.latitude + '@' + this.longitude + ':' + (this.error || ''); return `${this.latitude}@${this.longitude}:${this.timezoneId}:${this.error || ''}`;
} }
} }
...@@ -356,4 +371,4 @@ SDK.EmulationModel.Geolocation = Geolocation; ...@@ -356,4 +371,4 @@ SDK.EmulationModel.Geolocation = Geolocation;
/** @constructor */ /** @constructor */
SDK.EmulationModel.DeviceOrientation = DeviceOrientation; SDK.EmulationModel.DeviceOrientation = DeviceOrientation;
SDK.SDKModel.register(EmulationModel, SDK.Target.Capability.Emulation, true); SDK.SDKModel.register(EmulationModel, SDK.Target.Capability.Emulation, true);
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment