From a562ac8058836bc6b0aa17a190c67fd86066595d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 7 Oct 2016 14:58:46 -0500 Subject: [PATCH] Bug 1283453 - Add network throttling UI to RDM. r=gl MozReview-Commit-ID: 9MYLXuhlT2F --- .../locales/en-US/responsive.properties | 6 + .../client/responsive.html/actions/index.js | 23 ++-- .../client/responsive.html/actions/moz.build | 1 + .../actions/network-throttling.js | 21 +++ devtools/client/responsive.html/app.js | 19 ++- .../components/global-toolbar.js | 17 ++- .../responsive.html/components/moz.build | 1 + .../components/network-throttling-selector.js | 92 +++++++++++++ devtools/client/responsive.html/index.css | 102 +++++++------- devtools/client/responsive.html/manager.js | 126 ++++++++++++------ devtools/client/responsive.html/reducers.js | 1 + .../client/responsive.html/reducers/moz.build | 1 + .../reducers/network-throttling.js | 33 +++++ .../responsive.html/test/browser/browser.ini | 1 + .../browser/browser_network_throttling.js | 60 +++++++++ .../responsive.html/test/browser/head.js | 11 +- .../unit/test_change_network_throttling.js | 27 ++++ .../responsive.html/test/unit/xpcshell.ini | 1 + devtools/client/responsive.html/types.js | 21 ++- devtools/client/shared/moz.build | 1 + .../shared/network-throttling-profiles.js | 68 ++++++++++ 21 files changed, 523 insertions(+), 110 deletions(-) create mode 100644 devtools/client/responsive.html/actions/network-throttling.js create mode 100644 devtools/client/responsive.html/components/network-throttling-selector.js create mode 100644 devtools/client/responsive.html/reducers/network-throttling.js create mode 100644 devtools/client/responsive.html/test/browser/browser_network_throttling.js create mode 100644 devtools/client/responsive.html/test/unit/test_change_network_throttling.js create mode 100644 devtools/client/shared/network-throttling-profiles.js diff --git a/devtools/client/locales/en-US/responsive.properties b/devtools/client/locales/en-US/responsive.properties index 5b4ce5a8cc84..66ca88c0725e 100644 --- a/devtools/client/locales/en-US/responsive.properties +++ b/devtools/client/locales/en-US/responsive.properties @@ -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 diff --git a/devtools/client/responsive.html/actions/index.js b/devtools/client/responsive.html/actions/index.js index d4b4b0e8c7c5..e226eb0a8d5b 100644 --- a/devtools/client/responsive.html/actions/index.js +++ b/devtools/client/responsive.html/actions/index.js @@ -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", diff --git a/devtools/client/responsive.html/actions/moz.build b/devtools/client/responsive.html/actions/moz.build index c6a0d6767591..fa1d0a88ba38 100644 --- a/devtools/client/responsive.html/actions/moz.build +++ b/devtools/client/responsive.html/actions/moz.build @@ -8,6 +8,7 @@ DevToolsModules( 'devices.js', 'index.js', 'location.js', + 'network-throttling.js', 'screenshot.js', 'touch-simulation.js', 'viewports.js', diff --git a/devtools/client/responsive.html/actions/network-throttling.js b/devtools/client/responsive.html/actions/network-throttling.js new file mode 100644 index 000000000000..e92fb995c87a --- /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, + }; + }, + +}; diff --git a/devtools/client/responsive.html/app.js b/devtools/client/responsive.html/app.js index 37cd7caf7cdc..cc2f6902961d 100644 --- a/devtools/client/responsive.html/app.js +++ b/devtools/client/responsive.html/app.js @@ -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, diff --git a/devtools/client/responsive.html/components/global-toolbar.js b/devtools/client/responsive.html/components/global-toolbar.js index 68a6d9337723..a040133f68c7 100644 --- a/devtools/client/responsive.html/components/global-toolbar.js +++ b/devtools/client/responsive.html/components/global-toolbar.js @@ -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, diff --git a/devtools/client/responsive.html/components/moz.build b/devtools/client/responsive.html/components/moz.build index 36bbcac56e75..e37097b19bd7 100644 --- a/devtools/client/responsive.html/components/moz.build +++ b/devtools/client/responsive.html/components/moz.build @@ -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', diff --git a/devtools/client/responsive.html/components/network-throttling-selector.js b/devtools/client/responsive.html/components/network-throttling-selector.js new file mode 100644 index 000000000000..fa9f5c6a06c4 --- /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 + ); + }, + +}); diff --git a/devtools/client/responsive.html/index.css b/devtools/client/responsive.html/index.css index 65804ca551c2..b54eacb9e908 100644 --- a/devtools/client/responsive.html/index.css +++ b/devtools/client/responsive.html/index.css @@ -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; diff --git a/devtools/client/responsive.html/manager.js b/devtools/client/responsive.html/manager.js index d20d6d539896..16f40a9a4d91 100644 --- a/devtools/client/responsive.html/manager.js +++ b/devtools/client/responsive.html/manager.js @@ -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. */ diff --git a/devtools/client/responsive.html/reducers.js b/devtools/client/responsive.html/reducers.js index a1f074a21ea9..afb062a04e85 100644 --- a/devtools/client/responsive.html/reducers.js +++ b/devtools/client/responsive.html/reducers.js @@ -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"); diff --git a/devtools/client/responsive.html/reducers/moz.build b/devtools/client/responsive.html/reducers/moz.build index 8fb419ff81e2..e2152c20f6aa 100644 --- a/devtools/client/responsive.html/reducers/moz.build +++ b/devtools/client/responsive.html/reducers/moz.build @@ -7,6 +7,7 @@ DevToolsModules( 'devices.js', 'location.js', + 'network-throttling.js', 'screenshot.js', 'touch-simulation.js', 'viewports.js', diff --git a/devtools/client/responsive.html/reducers/network-throttling.js b/devtools/client/responsive.html/reducers/network-throttling.js new file mode 100644 index 000000000000..f892553c1216 --- /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); +}; diff --git a/devtools/client/responsive.html/test/browser/browser.ini b/devtools/client/responsive.html/test/browser/browser.ini index ddcb539522ce..969df4cfd426 100644 --- a/devtools/client/responsive.html/test/browser/browser.ini +++ b/devtools/client/responsive.html/test/browser/browser.ini @@ -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] diff --git a/devtools/client/responsive.html/test/browser/browser_network_throttling.js b/devtools/client/responsive.html/test/browser/browser_network_throttling.js new file mode 100644 index 000000000000..092224d7b0bd --- /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, + }); +}); diff --git a/devtools/client/responsive.html/test/browser/head.js b/devtools/client/responsive.html/test/browser/head.js index d17443f3e92a..a9dcb48306dd 100644 --- a/devtools/client/responsive.html/test/browser/head.js +++ b/devtools/client/responsive.html/test/browser/head.js @@ -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 */ diff --git a/devtools/client/responsive.html/test/unit/test_change_network_throttling.js b/devtools/client/responsive.html/test/unit/test_change_network_throttling.js new file mode 100644 index 000000000000..c20ae81338f0 --- /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."); +}); diff --git a/devtools/client/responsive.html/test/unit/xpcshell.ini b/devtools/client/responsive.html/test/unit/xpcshell.ini index 9f19ab174f6e..58ff71434b92 100644 --- a/devtools/client/responsive.html/test/unit/xpcshell.ini +++ b/devtools/client/responsive.html/test/unit/xpcshell.ini @@ -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] diff --git a/devtools/client/responsive.html/types.js b/devtools/client/responsive.html/types.js index 8d5481251406..0dd388f67108 100644 --- a/devtools/client/responsive.html/types.js +++ b/devtools/client/responsive.html/types.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, diff --git a/devtools/client/shared/moz.build b/devtools/client/shared/moz.build index 52311543eb8a..41e8d6f1f355 100644 --- a/devtools/client/shared/moz.build +++ b/devtools/client/shared/moz.build @@ -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', diff --git a/devtools/client/shared/network-throttling-profiles.js b/devtools/client/shared/network-throttling-profiles.js new file mode 100644 index 000000000000..ef139fda6755 --- /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 */