Bug 1283453 - Add network throttling UI to RDM. r=gl

MozReview-Commit-ID: 9MYLXuhlT2F
This commit is contained in:
J. Ryan Stinnett 2016-10-07 14:58:46 -05:00
Родитель 6de59d91bf
Коммит a562ac8058
21 изменённых файлов: 523 добавлений и 110 удалений

Просмотреть файл

@ -58,3 +58,9 @@ responsive.screenshotGeneratedFilename=Screen Shot %1$S at %2$S
# notification box if a user tries to open Responsive Design Mode in a
# non-remote tab.
responsive.remoteOnly=Responsive Design Mode is only available for remote browser tabs, such as those used for web content in multi-process Firefox.
# LOCALIZATION NOTE (responsive.noThrottling): UI option in a menu to configure
# network throttling. This option is the default and disables throttling so you
# just have normal network conditions. There is not very much room in the UI
# so a short string would be best if possible.
responsive.noThrottling=No throttling

Просмотреть файл

@ -24,10 +24,22 @@ createEnum([
// Change the device displayed in the viewport.
"CHANGE_DEVICE",
// The location of the page has changed. This may be triggered by the user
// Change the location of the page. This may be triggered by the user
// directly entering a new URL, navigating with links, etc.
"CHANGE_LOCATION",
// Change the network throttling profile.
"CHANGE_NETWORK_THROTTLING",
// Indicates that the device list is being loaded
"LOAD_DEVICE_LIST_START",
// Indicates that the device list loading action threw an error
"LOAD_DEVICE_LIST_ERROR",
// Indicates that the device list has been loaded successfully
"LOAD_DEVICE_LIST_END",
// Resize the viewport.
"RESIZE_VIEWPORT",
@ -43,15 +55,6 @@ createEnum([
// Update the device display state in the device selector.
"UPDATE_DEVICE_DISPLAYED",
// Indicates that the device list is being loaded
"LOAD_DEVICE_LIST_START",
// Indicates that the device list loading action threw an error
"LOAD_DEVICE_LIST_ERROR",
// Indicates that the device list has been loaded successfully
"LOAD_DEVICE_LIST_END",
// Update the device modal open state.
"UPDATE_DEVICE_MODAL_OPEN",

Просмотреть файл

@ -8,6 +8,7 @@ DevToolsModules(
'devices.js',
'index.js',
'location.js',
'network-throttling.js',
'screenshot.js',
'touch-simulation.js',
'viewports.js',

Просмотреть файл

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
CHANGE_NETWORK_THROTTLING,
} = require("./index");
module.exports = {
changeNetworkThrottling(enabled, profile) {
return {
type: CHANGE_NETWORK_THROTTLING,
enabled,
profile,
};
},
};

Просмотреть файл

@ -15,13 +15,14 @@ const {
updateDeviceModalOpen,
updatePreferredDevices,
} = require("./actions/devices");
const { changeNetworkThrottling } = require("./actions/network-throttling");
const { takeScreenshot } = require("./actions/screenshot");
const { updateTouchSimulationEnabled } = require("./actions/touch-simulation");
const {
changeDevice,
resizeViewport,
rotateViewport
} = require("./actions/viewports");
const { takeScreenshot } = require("./actions/screenshot");
const { updateTouchSimulationEnabled } = require("./actions/touch-simulation");
const DeviceModal = createFactory(require("./components/device-modal"));
const GlobalToolbar = createFactory(require("./components/global-toolbar"));
const Viewports = createFactory(require("./components/viewports"));
@ -33,6 +34,7 @@ let App = createClass({
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
location: Types.location.isRequired,
networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
screenshot: PropTypes.shape(Types.screenshot).isRequired,
touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
@ -42,6 +44,15 @@ let App = createClass({
window.postMessage({ type: "browser-mounted" }, "*");
},
onChangeNetworkThrottling(enabled, profile) {
window.postMessage({
type: "change-network-throtting",
enabled,
profile,
}, "*");
this.props.dispatch(changeNetworkThrottling(enabled, profile));
},
onChangeViewportDevice(id, device) {
window.postMessage({
type: "change-viewport-device",
@ -100,6 +111,7 @@ let App = createClass({
let {
devices,
location,
networkThrottling,
screenshot,
touchSimulation,
viewports,
@ -107,6 +119,7 @@ let App = createClass({
let {
onBrowserMounted,
onChangeNetworkThrottling,
onChangeViewportDevice,
onContentResize,
onDeviceListUpdate,
@ -124,8 +137,10 @@ let App = createClass({
id: "app",
},
GlobalToolbar({
networkThrottling,
screenshot,
touchSimulation,
onChangeNetworkThrottling,
onExit,
onScreenshot,
onUpdateTouchSimulation,

Просмотреть файл

@ -4,17 +4,21 @@
"use strict";
const { getStr } = require("../utils/l10n");
const { DOM: dom, createClass, PropTypes, addons } =
const { DOM: dom, createClass, createFactory, PropTypes, addons } =
require("devtools/client/shared/vendor/react");
const { getStr } = require("../utils/l10n");
const Types = require("../types");
const NetworkThrottlingSelector = createFactory(require("./network-throttling-selector"));
module.exports = createClass({
displayName: "GlobalToolbar",
propTypes: {
networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
screenshot: PropTypes.shape(Types.screenshot).isRequired,
touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
onChangeNetworkThrottling: PropTypes.func.isRequired,
onExit: PropTypes.func.isRequired,
onScreenshot: PropTypes.func.isRequired,
onUpdateTouchSimulation: PropTypes.func.isRequired,
@ -24,8 +28,10 @@ module.exports = createClass({
render() {
let {
networkThrottling,
screenshot,
touchSimulation,
onChangeNetworkThrottling,
onExit,
onScreenshot,
onUpdateTouchSimulation
@ -45,7 +51,12 @@ module.exports = createClass({
{
className: "title",
},
getStr("responsive.title")),
getStr("responsive.title")
),
NetworkThrottlingSelector({
networkThrottling,
onChangeNetworkThrottling,
}),
dom.button({
id: "global-touch-simulation-button",
className: touchButtonClass,

Просмотреть файл

@ -9,6 +9,7 @@ DevToolsModules(
'device-modal.js',
'device-selector.js',
'global-toolbar.js',
'network-throttling-selector.js',
'resizable-viewport.js',
'viewport-dimension.js',
'viewport-toolbar.js',

Просмотреть файл

@ -0,0 +1,92 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { DOM: dom, createClass, PropTypes, addons } =
require("devtools/client/shared/vendor/react");
const Types = require("../types");
const { getStr } = require("../utils/l10n");
const throttlingProfiles = require("devtools/client/shared/network-throttling-profiles");
module.exports = createClass({
displayName: "NetworkThrottlingSelector",
propTypes: {
networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
onChangeNetworkThrottling: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
onSelectChange({ target }) {
let {
onChangeNetworkThrottling,
} = this.props;
if (target.value == getStr("responsive.noThrottling")) {
onChangeNetworkThrottling(false, "");
return;
}
for (let profile of throttlingProfiles) {
if (profile.id === target.value) {
onChangeNetworkThrottling(true, profile.id);
return;
}
}
},
render() {
let {
networkThrottling,
} = this.props;
let selectClass = "";
let selectedProfile;
if (networkThrottling.enabled) {
selectClass += " selected";
selectedProfile = networkThrottling.profile;
} else {
selectedProfile = getStr("responsive.noThrottling");
}
let listContent = [
dom.option(
{
key: "disabled",
},
getStr("responsive.noThrottling")
),
dom.option(
{
key: "divider",
className: "divider",
disabled: true,
}
),
throttlingProfiles.map(profile => {
return dom.option(
{
key: profile.id,
},
profile.id
);
}),
];
return dom.select(
{
id: "global-network-throttling-selector",
className: selectClass,
value: selectedProfile,
onChange: this.onSelectChange,
},
...listContent
);
},
});

Просмотреть файл

@ -55,7 +55,7 @@ html, body {
}
/**
* Common style for containers and toolbar buttons
* Common styles for shared components
*/
.container {
@ -77,6 +77,57 @@ html, body {
filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
}
select {
-moz-appearance: none;
background-color: var(--theme-toolbar-background);
background-image: var(--viewport-selection-arrow);
background-position: 100% 50%;
background-repeat: no-repeat;
background-size: 7px;
border: none;
color: var(--viewport-color);
padding: 0 8px;
text-align: center;
text-overflow: ellipsis;
font-size: 11px;
}
select.selected {
background-image: var(--viewport-selection-arrow-selected);
color: var(--viewport-active-color);
}
select:hover {
background-image: var(--viewport-selection-arrow-hovered);
color: var(--viewport-hover-color);
}
/* This is (believed to be?) separate from the identical select.selected rule
set so that it overrides select:hover because of file ordering once the
select is focused. It's unclear whether the visual effect that results here
is intentional and desired. */
select:focus {
background-image: var(--viewport-selection-arrow-selected);
color: var(--viewport-active-color);
}
select > option {
text-align: left;
padding: 5px 10px;
}
select > option,
select > option:hover {
color: var(--viewport-active-color);
}
select > option.divider {
border-top: 1px solid var(--theme-splitter-color);
height: 0px;
padding: 0;
font-size: 0px;
}
/**
* Global Toolbar
*/
@ -131,6 +182,11 @@ html, body {
opacity: 1 !important;
}
#global-network-throttling-selector {
height: 15px;
padding-left: 0;
width: 103px;
}
#viewports {
/* Make sure left-most viewport is visible when there's horizontal overflow.
@ -172,50 +228,6 @@ html, body {
height: 18px;
}
.viewport-device-selector {
-moz-appearance: none;
background-color: var(--theme-toolbar-background);
background-image: var(--viewport-selection-arrow);
background-position: 100% 52%;
background-repeat: no-repeat;
background-size: 7px;
border: none;
color: var(--viewport-color);
height: 100%;
padding: 0 8px 0 8px;
text-align: center;
text-overflow: ellipsis;
width: 150px;
font-size: 11px;
width: -moz-fit-content;
}
.viewport-device-selector.selected {
background-image: var(--viewport-selection-arrow-selected);
color: var(--viewport-active-color);
}
.viewport-device-selector:hover {
background-image: var(--viewport-selection-arrow-hovered);
color: var(--viewport-hover-color);
}
.viewport-device-selector:focus {
background-image: var(--viewport-selection-arrow-selected);
color: var(--viewport-active-color);
}
.viewport-device-selector > option {
text-align: left;
padding: 5px 10px;
}
.viewport-device-selector > option,
.viewport-device-selector > option:hover,
.viewport-device-selector:hover > option:hover {
color: var(--viewport-active-color);
}
.viewport-rotate-button {
position: absolute;
right: 0;

Просмотреть файл

@ -14,15 +14,15 @@ const message = require("./utils/message");
const { swapToInnerBrowser } = require("./browser/swap");
const { EmulationFront } = require("devtools/shared/fronts/emulation");
const { getStr } = require("./utils/l10n");
const { TargetFactory } = require("devtools/client/framework/target");
const { gDevTools } = require("devtools/client/framework/devtools");
const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
loader.lazyRequireGetter(this, "DebuggerClient",
"devtools/shared/client/main", true);
loader.lazyRequireGetter(this, "DebuggerServer",
"devtools/server/main", true);
loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "throttlingProfiles",
"devtools/client/shared/network-throttling-profiles");
/**
* ResponsiveUIManager is the external API for the browser UI, etc. to use when
@ -362,9 +362,6 @@ ResponsiveUI.prototype = {
this.toolWindow.removeEventListener("message", this);
if (!isTabClosing) {
// Stop the touch event simulator if it was running
yield this.emulationFront.clearTouchEventsOverride();
// Notify the inner browser to stop the frame script
yield message.request(this.toolWindow, "stop-frame-script");
}
@ -377,7 +374,9 @@ ResponsiveUI.prototype = {
this.toolWindow = null;
this.swap = null;
// Close the debugger client used to speak with emulation actor
// Close the debugger client used to speak with emulation actor.
// The actor handles clearing any overrides itself, so it's not necessary to clear
// anything on shutdown client side.
let clientClosed = this.client.close();
if (!isTabClosing) {
yield clientClosed;
@ -422,65 +421,104 @@ ResponsiveUI.prototype = {
},
handleMessage(event) {
let { browserWindow, tab } = this;
if (event.origin !== "chrome://devtools") {
return;
}
switch (event.data.type) {
case "change-network-throtting":
this.onChangeNetworkThrottling(event);
break;
case "change-viewport-device":
let { userAgent, pixelRatio, touch } = event.data.device;
this.updateUserAgent(userAgent);
this.updateDPPX(pixelRatio);
this.updateTouchSimulation(touch);
this.onChangeViewportDevice(event);
break;
case "content-resize":
let { width, height } = event.data;
this.emit("content-resize", {
width,
height,
});
this.onContentResize(event);
break;
case "exit":
ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
this.onExit();
break;
case "update-touch-simulation":
let { enabled } = event.data;
this.updateTouchSimulation(enabled);
this.onUpdateTouchSimulation(event);
break;
}
},
updateTouchSimulation: Task.async(function* (enabled) {
if (enabled) {
let reloadNeeded = yield this.emulationFront.setTouchEventsOverride(
Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED
);
if (reloadNeeded) {
this.getViewportBrowser().reload();
}
} else {
this.emulationFront.clearTouchEventsOverride();
}
onChangeNetworkThrottling: Task.async(function* (event) {
let { enabled, profile } = event.data;
yield this.updateNetworkThrottling(enabled, profile);
// Used by tests
this.emit("network-throttling-changed");
}),
updateUserAgent(userAgent) {
if (userAgent) {
this.emulationFront.setUserAgentOverride(userAgent);
} else {
this.emulationFront.clearUserAgentOverride();
}
onChangeViewportDevice(event) {
let { userAgent, pixelRatio, touch } = event.data.device;
this.updateUserAgent(userAgent);
this.updateDPPX(pixelRatio);
this.updateTouchSimulation(touch);
},
onContentResize(event) {
let { width, height } = event.data;
this.emit("content-resize", {
width,
height,
});
},
onExit() {
let { browserWindow, tab } = this;
ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
},
onUpdateTouchSimulation(event) {
let { enabled } = event.data;
this.updateTouchSimulation(enabled);
},
updateDPPX(dppx) {
if (dppx) {
this.emulationFront.setDPPXOverride(dppx);
} else {
if (!dppx) {
this.emulationFront.clearDPPXOverride();
return;
}
this.emulationFront.setDPPXOverride(dppx);
},
updateNetworkThrottling: Task.async(function* (enabled, profile) {
if (!enabled) {
yield this.emulationFront.clearNetworkThrottling();
return;
}
let data = throttlingProfiles.find(({ id }) => id == profile);
let { download, upload, latency } = data;
yield this.emulationFront.setNetworkThrottling({
downloadThroughput: download,
uploadThroughput: upload,
latency,
});
}),
updateUserAgent(userAgent) {
if (!userAgent) {
this.emulationFront.clearUserAgentOverride();
return;
}
this.emulationFront.setUserAgentOverride(userAgent);
},
updateTouchSimulation: Task.async(function* (enabled) {
if (!enabled) {
yield this.emulationFront.clearTouchEventsOverride();
return;
}
let reloadNeeded = yield this.emulationFront.setTouchEventsOverride(
Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED
);
if (reloadNeeded) {
this.getViewportBrowser().reload();
}
}),
/**
* Helper for tests. Assumes a single viewport for now.
*/

Просмотреть файл

@ -6,6 +6,7 @@
exports.devices = require("./reducers/devices");
exports.location = require("./reducers/location");
exports.networkThrottling = require("./reducers/network-throttling");
exports.screenshot = require("./reducers/screenshot");
exports.touchSimulation = require("./reducers/touch-simulation");
exports.viewports = require("./reducers/viewports");

Просмотреть файл

@ -7,6 +7,7 @@
DevToolsModules(
'devices.js',
'location.js',
'network-throttling.js',
'screenshot.js',
'touch-simulation.js',
'viewports.js',

Просмотреть файл

@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
CHANGE_NETWORK_THROTTLING,
} = require("../actions/index");
const INITIAL_NETWORK_THROTTLING = {
enabled: false,
profile: "",
};
let reducers = {
[CHANGE_NETWORK_THROTTLING](throttling, { enabled, profile }) {
return {
enabled,
profile,
};
},
};
module.exports = function (throttling = INITIAL_NETWORK_THROTTLING, action) {
let reducer = reducers[action.type];
if (!reducer) {
return throttling;
}
return reducer(throttling, action);
};

Просмотреть файл

@ -27,6 +27,7 @@ support-files =
[browser_menu_item_02.js]
[browser_mouse_resize.js]
[browser_navigation.js]
[browser_network_throttling.js]
[browser_page_state.js]
[browser_permission_doorhanger.js]
[browser_resize_cmd.js]

Просмотреть файл

@ -0,0 +1,60 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const throttlingProfiles = require("devtools/client/shared/network-throttling-profiles");
// Tests changing network throttling
const TEST_URL = "data:text/html;charset=utf-8,Network throttling test";
addRDMTask(TEST_URL, function* ({ ui, manager }) {
let { store } = ui.toolWindow;
// Wait until the viewport has been added
yield waitUntilState(store, state => state.viewports.length == 1);
// Test defaults
testNetworkThrottlingSelectorLabel(ui, "No throttling");
yield testNetworkThrottlingState(ui, null);
// Test a fast profile
yield testThrottlingProfile(ui, "Wi-Fi");
// Test a slower profile
yield testThrottlingProfile(ui, "Regular 3G");
// Test switching back to no throttling
let changed = once(ui, "network-throttling-changed");
yield switchNetworkThrottling(ui, "No throttling");
yield changed;
testNetworkThrottlingSelectorLabel(ui, "No throttling");
yield testNetworkThrottlingState(ui, null);
});
function testNetworkThrottlingSelectorLabel(ui, expected) {
let selector = "#global-network-throttling-selector";
let select = ui.toolWindow.document.querySelector(selector);
is(select.selectedOptions[0].textContent, expected,
`Select label should be changed to ${expected}`);
}
var testNetworkThrottlingState = Task.async(function* (ui, expected) {
let state = yield ui.emulationFront.getNetworkThrottling();
Assert.deepEqual(state, expected, "Network throttling state should be " +
JSON.stringify(expected, null, 2));
});
var testThrottlingProfile = Task.async(function* (ui, profile) {
let changed = once(ui, "network-throttling-changed");
yield switchNetworkThrottling(ui, profile);
yield changed;
testNetworkThrottlingSelectorLabel(ui, profile);
let data = throttlingProfiles.find(({ id }) => id == profile);
let { download, upload, latency } = data;
yield testNetworkThrottlingState(ui, {
downloadThroughput: download,
uploadThroughput: upload,
latency,
});
});

Просмотреть файл

@ -230,9 +230,8 @@ function openDeviceModal(ui) {
"The device modal is displayed.");
}
function switchDevice({ toolWindow }, value) {
function switchSelector({ toolWindow }, selector, value) {
return new Promise(resolve => {
let selector = ".viewport-device-selector";
let select = toolWindow.document.querySelector(selector);
isnot(select, null, `selector "${selector}" should match an existing element.`);
@ -256,6 +255,14 @@ function switchDevice({ toolWindow }, value) {
});
}
function switchDevice(ui, value) {
return switchSelector(ui, ".viewport-device-selector", value);
}
function switchNetworkThrottling(ui, value) {
return switchSelector(ui, "#global-network-throttling-selector", value);
}
function getSessionHistory(browser) {
return ContentTask.spawn(browser, {}, function* () {
/* eslint-disable no-undef */

Просмотреть файл

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test changing the network throttling state
const {
changeNetworkThrottling,
} = require("devtools/client/responsive.html/actions/network-throttling");
add_task(function* () {
let store = Store();
const { getState, dispatch } = store;
ok(!getState().networkThrottling.enabled,
"Network throttling is disabled by default.");
equal(getState().networkThrottling.profile, "",
"Network throttling profile is empty by default.");
dispatch(changeNetworkThrottling(true, "Bob"));
ok(getState().networkThrottling.enabled,
"Network throttling is enabled.");
equal(getState().networkThrottling.profile, "Bob",
"Network throttling profile is set.");
});

Просмотреть файл

@ -8,6 +8,7 @@ firefox-appdir = browser
[test_add_device_type.js]
[test_add_viewport.js]
[test_change_location.js]
[test_change_network_throttling.js]
[test_change_viewport_device.js]
[test_resize_viewport.js]
[test_rotate_viewport.js]

Просмотреть файл

@ -95,7 +95,7 @@ exports.location = PropTypes.string;
*/
exports.screenshot = {
isCapturing: PropTypes.bool.isRequired,
isCapturing: PropTypes.bool,
};
@ -104,8 +104,21 @@ exports.screenshot = {
*/
exports.touchSimulation = {
// Whether or not the touch simulation is enabled
enabled: PropTypes.bool.isRequired,
// Whether or not touch simulation is enabled
enabled: PropTypes.bool,
};
/**
* Network throttling.
*/
exports.networkThrottling = {
// Whether or not network throttling is enabled
enabled: PropTypes.bool,
// Name of the selected throttling profile
profile: PropTypes.string,
};
@ -115,7 +128,7 @@ exports.touchSimulation = {
exports.viewport = {
// The id of the viewport
id: PropTypes.number.isRequired,
id: PropTypes.number,
// The currently selected device applied to the viewport.
device: PropTypes.string,

Просмотреть файл

@ -36,6 +36,7 @@ DevToolsModules(
'Jsbeautify.jsm',
'key-shortcuts.js',
'keycodes.js',
'network-throttling-profiles.js',
'node-attribute-parser.js',
'options-view.js',
'output-parser.js',

Просмотреть файл

@ -0,0 +1,68 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const K = 1024;
const M = 1024 * 1024;
const Bps = 1 / 8;
const KBps = K * Bps;
const MBps = M * Bps;
/**
* Predefined network throttling profiles.
* Speeds are in bytes per second. Latency is in ms.
*/
/* eslint-disable key-spacing */
module.exports = [
{
id: "GPRS",
download: 50 * KBps,
upload: 20 * KBps,
latency: 500,
},
{
id: "Regular 2G",
download: 250 * KBps,
upload: 50 * KBps,
latency: 300,
},
{
id: "Good 2G",
download: 450 * KBps,
upload: 150 * KBps,
latency: 150,
},
{
id: "Regular 3G",
download: 750 * KBps,
upload: 250 * KBps,
latency: 100,
},
{
id: "Good 3G",
download: 1.5 * MBps,
upload: 750 * KBps,
latency: 40,
},
{
id: "Regular 4G / LTE",
download: 4 * MBps,
upload: 3 * MBps,
latency: 20,
},
{
id: "DSL",
download: 2 * MBps,
upload: 1 * MBps,
latency: 5,
},
{
id: "Wi-Fi",
download: 30 * MBps,
upload: 15 * MBps,
latency: 2,
},
];
/* eslint-enable key-spacing */