--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -53,8 +53,14 @@ responsive.screenshot=Take a screenshot
# The first argument (%1$S) is the date string in yyyy-mm-dd format and the
# second argument (%2$S) is the time string in HH.MM.SS format.
responsive.screenshotGeneratedFilename=Screen Shot %1$S at %2$S
# LOCALIZATION NOTE (responsive.remoteOnly): Message displayed in the tab'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
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -19,43 +19,46 @@ createEnum([
"ADD_DEVICE_TYPE",
// Add an additional viewport to display the document.
"ADD_VIEWPORT",
// 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",
// Rotate the viewport.
"ROTATE_VIEWPORT",
// Take a screenshot of the viewport.
"TAKE_SCREENSHOT_START",
// Indicates when the screenshot action ends.
"TAKE_SCREENSHOT_END",
// 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",
// Update the touch simulation enabled state.
"UPDATE_TOUCH_SIMULATION_ENABLED",
], module.exports);
--- a/devtools/client/responsive.html/actions/moz.build
+++ b/devtools/client/responsive.html/actions/moz.build
@@ -3,12 +3,13 @@
# 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/.
DevToolsModules(
'devices.js',
'index.js',
'location.js',
+ 'network-throttling.js',
'screenshot.js',
'touch-simulation.js',
'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/network-throttling.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,
+ };
+ },
+
+};
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -10,43 +10,54 @@ const { createClass, createFactory, Prop
require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const {
updateDeviceDisplayed,
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"));
const Types = require("./types");
let App = createClass({
displayName: "App",
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,
},
onBrowserMounted() {
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",
device,
}, "*");
this.props.dispatch(changeDevice(id, device.name));
this.props.dispatch(updateTouchSimulationEnabled(device.touch));
},
@@ -95,23 +106,25 @@ let App = createClass({
this.props.dispatch(updateTouchSimulationEnabled(isEnabled));
},
render() {
let {
devices,
location,
+ networkThrottling,
screenshot,
touchSimulation,
viewports,
} = this.props;
let {
onBrowserMounted,
+ onChangeNetworkThrottling,
onChangeViewportDevice,
onContentResize,
onDeviceListUpdate,
onExit,
onResizeViewport,
onRotateViewport,
onScreenshot,
onUpdateDeviceDisplayed,
@@ -119,18 +132,20 @@ let App = createClass({
onUpdateTouchSimulation,
} = this;
return dom.div(
{
id: "app",
},
GlobalToolbar({
+ networkThrottling,
screenshot,
touchSimulation,
+ onChangeNetworkThrottling,
onExit,
onScreenshot,
onUpdateTouchSimulation,
}),
Viewports({
devices,
location,
screenshot,
--- a/devtools/client/responsive.html/components/global-toolbar.js
+++ b/devtools/client/responsive.html/components/global-toolbar.js
@@ -1,36 +1,42 @@
/* 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, createFactory, PropTypes, addons } =
+ require("devtools/client/shared/vendor/react");
+
const { getStr } = require("../utils/l10n");
-const { DOM: dom, createClass, PropTypes, addons } =
- require("devtools/client/shared/vendor/react");
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,
},
mixins: [ addons.PureRenderMixin ],
render() {
let {
+ networkThrottling,
screenshot,
touchSimulation,
+ onChangeNetworkThrottling,
onExit,
onScreenshot,
onUpdateTouchSimulation
} = this.props;
let touchButtonClass = "toolbar-button devtools-button";
if (touchSimulation.enabled) {
touchButtonClass += " active";
@@ -40,17 +46,22 @@ module.exports = createClass({
{
id: "global-toolbar",
className: "container",
},
dom.span(
{
className: "title",
},
- getStr("responsive.title")),
+ getStr("responsive.title")
+ ),
+ NetworkThrottlingSelector({
+ networkThrottling,
+ onChangeNetworkThrottling,
+ }),
dom.button({
id: "global-touch-simulation-button",
className: touchButtonClass,
title: (touchSimulation.enabled ?
getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
onClick: () => onUpdateTouchSimulation(!touchSimulation.enabled),
}),
dom.button({
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -4,14 +4,15 @@
# 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/.
DevToolsModules(
'browser.js',
'device-modal.js',
'device-selector.js',
'global-toolbar.js',
+ 'network-throttling-selector.js',
'resizable-viewport.js',
'viewport-dimension.js',
'viewport-toolbar.js',
'viewport.js',
'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/network-throttling-selector.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
+ );
+ },
+
+});
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -50,17 +50,17 @@ html, body {
flex-direction: column;
padding-top: 15px;
padding-bottom: 1%;
position: relative;
height: 100%;
}
/**
- * Common style for containers and toolbar buttons
+ * Common styles for shared components
*/
.container {
background-color: var(--theme-toolbar-background);
border: 1px solid var(--theme-splitter-color);
}
.toolbar-button {
@@ -72,16 +72,67 @@ html, body {
min-height: initial;
align-self: center;
}
.toolbar-button:active::before {
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
*/
#global-toolbar {
color: var(--theme-body-color-alt);
border-radius: 2px;
box-shadow: var(--rdm-box-shadow);
@@ -126,16 +177,21 @@ html, body {
margin: -6px 0 0 -6px;
}
#global-screenshot-button:disabled {
filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
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.
That is, when the horizontal space become smaller than the viewports and a
scrollbar appears, then the first viewport will still be visible */
position: sticky;
left: 0;
/* Individual viewports are inline elements, make sure they stay on a single
@@ -167,60 +223,16 @@ html, body {
border-width: 0;
border-bottom-width: 1px;
display: flex;
flex-direction: row;
justify-content: center;
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;
}
.viewport-rotate-button::before {
background-image: url("./images/rotate-viewport.svg");
}
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -9,25 +9,25 @@ const promise = require("promise");
const { Task } = require("devtools/shared/task");
const EventEmitter = require("devtools/shared/event-emitter");
const { getOwnerWindow } = require("sdk/tabs/utils");
const { startup } = require("sdk/window/helpers");
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
* opening and closing the responsive UI.
*
* While the HTML UI is in an experimental stage, the older ResponsiveUIManager
* from devtools/client/responsivedesign/responsivedesign.jsm delegates to this
* object when the pref "devtools.responsive.html.enabled" is true.
@@ -357,32 +357,31 @@ ResponsiveUI.prototype = {
yield this.inited;
}
this.tab.removeEventListener("TabClose", this);
this.browserWindow.removeEventListener("unload", this);
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");
}
// Destroy local state
let swap = this.swap;
this.browserWindow = null;
this.tab = null;
this.inited = null;
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;
}
this.client = this.emulationFront = null;
if (!isWindowClosing) {
// Undo the swap and return the content back to a normal tab
@@ -417,74 +416,113 @@ ResponsiveUI.prototype = {
ResponsiveUIManager.closeIfNeeded(browserWindow, tab, {
reason: event.type,
});
break;
}
},
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");
+ }),
+
+ 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.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.setUserAgentOverride(userAgent);
- } else {
+ if (!userAgent) {
this.emulationFront.clearUserAgentOverride();
+ return;
}
+ this.emulationFront.setUserAgentOverride(userAgent);
},
- updateDPPX(dppx) {
- if (dppx) {
- this.emulationFront.setDPPXOverride(dppx);
- } else {
- this.emulationFront.clearDPPXOverride();
+ 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.
*/
getViewportSize() {
return this.toolWindow.getViewportSize();
},
--- a/devtools/client/responsive.html/reducers.js
+++ b/devtools/client/responsive.html/reducers.js
@@ -1,11 +1,12 @@
/* 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";
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");
--- a/devtools/client/responsive.html/reducers/moz.build
+++ b/devtools/client/responsive.html/reducers/moz.build
@@ -2,12 +2,13 @@
# vim: set filetype=python:
# 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/.
DevToolsModules(
'devices.js',
'location.js',
+ 'network-throttling.js',
'screenshot.js',
'touch-simulation.js',
'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers/network-throttling.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);
+};
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -22,16 +22,17 @@ support-files =
[browser_device_modal_submit.js]
[browser_device_width.js]
[browser_exit_button.js]
[browser_frame_script_active.js]
[browser_menu_item_01.js]
[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]
[browser_screenshot_button.js]
[browser_shutdown_close_sync.js]
[browser_toolbox_computed_view.js]
[browser_toolbox_rule_view.js]
[browser_toolbox_swap_browsers.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_network_throttling.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,
+ });
+});
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -225,19 +225,18 @@ function openDeviceModal(ui) {
ui.toolWindow);
EventUtils.synthesizeMouseAtCenter(editDeviceOption, {type: "mouseup"},
ui.toolWindow);
ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
"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.`);
let option = [...select.options].find(o => o.value === String(value));
isnot(option, undefined, `value "${value}" should match an existing option.`);
let event = new toolWindow.UIEvent("change", {
view: toolWindow,
@@ -251,16 +250,24 @@ function switchDevice({ toolWindow }, va
resolve();
}, { once: true });
select.value = value;
select.dispatchEvent(event);
});
}
+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 */
let { interfaces: Ci } = Components;
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
let sessionHistory = webNav.sessionHistory;
let result = {
index: sessionHistory.index,
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/unit/test_change_network_throttling.js
@@ -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.");
+});
--- a/devtools/client/responsive.html/test/unit/xpcshell.ini
+++ b/devtools/client/responsive.html/test/unit/xpcshell.ini
@@ -3,13 +3,14 @@ tags = devtools
head = head.js ../../../framework/test/shared-redux-head.js
tail =
firefox-appdir = browser
[test_add_device.js]
[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]
[test_update_device_displayed.js]
[test_update_touch_simulation_enabled.js]
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.js
@@ -90,37 +90,50 @@ exports.devices = {
*/
exports.location = PropTypes.string;
/**
* The progression of the screenshot
*/
exports.screenshot = {
- isCapturing: PropTypes.bool.isRequired,
+ isCapturing: PropTypes.bool,
};
/**
* Touch simulation.
*/
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,
};
/**
* A single viewport displaying a document.
*/
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,
// The width of the viewport
width: PropTypes.number,
// The height of the viewport
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -31,16 +31,17 @@ DevToolsModules(
'file-watcher-worker.js',
'file-watcher.js',
'frame-script-utils.js',
'getjson.js',
'inplace-editor.js',
'Jsbeautify.jsm',
'key-shortcuts.js',
'keycodes.js',
+ 'network-throttling-profiles.js',
'node-attribute-parser.js',
'options-view.js',
'output-parser.js',
'poller.js',
'prefs.js',
'scroll.js',
'source-utils.js',
'SplitView.jsm',
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/network-throttling-profiles.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 */