--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -133,8 +133,24 @@ responsive.deviceAdderSave=Save
# %2$S is the height of the device. %3$S is the device pixel ratio value of the
# device. %4$S is the user agent of the device. %5$S is a boolean value
# noting whether touch input is supported.
responsive.deviceDetails=Size: %1$S x %2$S\nDPR: %3$S\nUA: %4$S\nTouch: %5$S
# LOCALIZATION NOTE (responsive.devicePixelRatioOption): UI option in a menu to configure
# the device pixel ratio. %1$S is the devicePixelRatio value of the device.
responsive.devicePixelRatioOption=DPR: %1$S
+
+# LOCALIZATION NOTE (responsive.reloadConditions.label): Label on button to open a menu
+# used to choose whether to reload the page automatically when certain actions occur.
+responsive.reloadConditions.label=Reload when…
+
+# LOCALIZATION NOTE (responsive.reloadConditions.title): Title on button to open a menu
+# used to choose whether to reload the page automatically when certain actions occur.
+responsive.reloadConditions.title=Choose whether to reload the page automatically when certain actions occur
+
+# LOCALIZATION NOTE (responsive.reloadConditions.touchSimulation): Label on checkbox used
+# to select whether to reload when touch simulation is toggled.
+responsive.reloadConditions.touchSimulation=Reload when touch simulation is toggled
+
+# LOCALIZATION NOTE (responsive.reloadConditions.userAgent): Label on checkbox used
+# to select whether to reload when user agent is changed.
+responsive.reloadConditions.userAgent=Reload when user agent is changed
\ No newline at end of file
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -352,8 +352,13 @@ pref("devtools.editor.autoclosebrackets"
pref("devtools.editor.detectindentation", true);
pref("devtools.editor.enableCodeFolding", true);
pref("devtools.editor.autocomplete", true);
// Pref to store the browser version at the time of a telemetry ping for an
// opened developer tool. This allows us to ping telemetry just once per browser
// version for each user.
pref("devtools.telemetry.tools.opened.version", "{}");
+
+// Whether to reload when touch simulation is toggled
+pref("devtools.responsive.reloadConditions.touchSimulation", false);
+// Whether to reload when user agent is changed
+pref("devtools.responsive.reloadConditions.userAgent", false);
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -36,28 +36,34 @@ createEnum([
// Change the network throttling profile.
"CHANGE_NETWORK_THROTTLING",
// The pixel ratio of the viewport has changed. This may be triggered by the user
// when changing the device displayed in the viewport, or when a pixel ratio is
// selected from the device pixel ratio dropdown.
"CHANGE_PIXEL_RATIO",
+ // Change one of the reload conditions.
+ "CHANGE_RELOAD_CONDITION",
+
// Change the touch simulation state.
"CHANGE_TOUCH_SIMULATION",
- // Indicates that the device list is being loaded
+ // Indicates that the device list is being loaded.
"LOAD_DEVICE_LIST_START",
- // Indicates that the device list loading action threw an error
+ // Indicates that the device list loading action threw an error.
"LOAD_DEVICE_LIST_ERROR",
- // Indicates that the device list has been loaded successfully
+ // Indicates that the device list has been loaded successfully.
"LOAD_DEVICE_LIST_END",
+ // Indicates that the reload conditions have been loaded successfully.
+ "LOAD_RELOAD_CONDITIONS_END",
+
// Remove a device.
"REMOVE_DEVICE",
// Remove the viewport's device assocation.
"REMOVE_DEVICE_ASSOCIATION",
// Resize the viewport.
"RESIZE_VIEWPORT",
--- a/devtools/client/responsive.html/actions/moz.build
+++ b/devtools/client/responsive.html/actions/moz.build
@@ -5,12 +5,13 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'devices.js',
'display-pixel-ratio.js',
'index.js',
'location.js',
'network-throttling.js',
+ 'reload-conditions.js',
'screenshot.js',
'touch-simulation.js',
'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/reload-conditions.js
@@ -0,0 +1,52 @@
+/* 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_RELOAD_CONDITION,
+ LOAD_RELOAD_CONDITIONS_END,
+} = require("./index");
+
+const Types = require("../types");
+const Services = require("Services");
+
+const PREF_PREFIX = "devtools.responsive.reloadConditions.";
+
+module.exports = {
+
+ changeReloadCondition(id, value) {
+ return dispatch => {
+ let pref = PREF_PREFIX + id;
+ Services.prefs.setBoolPref(pref, value);
+ dispatch({
+ type: CHANGE_RELOAD_CONDITION,
+ id,
+ value,
+ });
+ };
+ },
+
+ loadReloadConditions() {
+ return dispatch => {
+ // Loop over the conditions and load their values from prefs.
+ for (let id in Types.reloadConditions) {
+ // Skip over the loading state of the list.
+ if (id == "state") {
+ return;
+ }
+ let pref = PREF_PREFIX + id;
+ let value = Services.prefs.getBoolPref(pref, false);
+ dispatch({
+ type: CHANGE_RELOAD_CONDITION,
+ id,
+ value,
+ });
+ }
+
+ dispatch({ type: LOAD_RELOAD_CONDITIONS_END });
+ };
+ },
+
+};
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -14,16 +14,17 @@ const { connect } = require("devtools/cl
const {
addCustomDevice,
removeCustomDevice,
updateDeviceDisplayed,
updateDeviceModal,
updatePreferredDevices,
} = require("./actions/devices");
const { changeNetworkThrottling } = require("./actions/network-throttling");
+const { changeReloadCondition } = require("./actions/reload-conditions");
const { takeScreenshot } = require("./actions/screenshot");
const { changeTouchSimulation } = require("./actions/touch-simulation");
const {
changeDevice,
changePixelRatio,
removeDeviceAssociation,
resizeViewport,
rotateViewport,
@@ -35,29 +36,31 @@ const Types = require("./types");
class App extends Component {
static get propTypes() {
return {
devices: PropTypes.shape(Types.devices).isRequired,
dispatch: PropTypes.func.isRequired,
displayPixelRatio: Types.pixelRatio.value.isRequired,
networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+ reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
screenshot: PropTypes.shape(Types.screenshot).isRequired,
touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
};
}
constructor(props) {
super(props);
this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
this.onBrowserMounted = this.onBrowserMounted.bind(this);
this.onChangeDevice = this.onChangeDevice.bind(this);
this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this);
this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
+ this.onChangeReloadCondition = this.onChangeReloadCondition.bind(this);
this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
this.onContentResize = this.onContentResize.bind(this);
this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
this.onExit = this.onExit.bind(this);
this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this);
this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
this.onResizeViewport = this.onResizeViewport.bind(this);
this.onRotateViewport = this.onRotateViewport.bind(this);
@@ -99,16 +102,20 @@ class App extends Component {
onChangePixelRatio(pixelRatio) {
window.postMessage({
type: "change-pixel-ratio",
pixelRatio,
}, "*");
this.props.dispatch(changePixelRatio(0, pixelRatio));
}
+ onChangeReloadCondition(id, value) {
+ this.props.dispatch(changeReloadCondition(id, value));
+ }
+
onChangeTouchSimulation(enabled) {
window.postMessage({
type: "change-touch-simulation",
enabled,
}, "*");
this.props.dispatch(changeTouchSimulation(enabled));
}
@@ -160,27 +167,29 @@ class App extends Component {
this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
}
render() {
let {
devices,
displayPixelRatio,
networkThrottling,
+ reloadConditions,
screenshot,
touchSimulation,
viewports,
} = this.props;
let {
onAddCustomDevice,
onBrowserMounted,
onChangeDevice,
onChangeNetworkThrottling,
onChangePixelRatio,
+ onChangeReloadCondition,
onChangeTouchSimulation,
onContentResize,
onDeviceListUpdate,
onExit,
onRemoveCustomDevice,
onRemoveDeviceAssociation,
onResizeViewport,
onRotateViewport,
@@ -205,22 +214,24 @@ class App extends Component {
return dom.div(
{
id: "app",
},
GlobalToolbar({
devices,
displayPixelRatio,
networkThrottling,
+ reloadConditions,
screenshot,
selectedDevice,
selectedPixelRatio,
touchSimulation,
onChangeNetworkThrottling,
onChangePixelRatio,
+ onChangeReloadCondition,
onChangeTouchSimulation,
onExit,
onScreenshot,
}),
Viewports({
devices,
screenshot,
viewports,
--- a/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
+++ b/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
@@ -88,17 +88,17 @@ class DevicePixelRatioSelector extends P
}
if (!PIXEL_RATIO_PRESET.includes(displayPixelRatio)) {
hiddenOptions.push(displayPixelRatio);
}
let state = devices.listState;
let isDisabled = (state !== Types.loadableState.LOADED) || (selectedDevice !== "");
- let selectorClass = "";
+ let selectorClass = "toolbar-dropdown";
let title;
if (isDisabled) {
selectorClass += " disabled";
title = getFormatStr("responsive.devicePixelRatio.auto", selectedDevice);
} else {
title = getStr("responsive.changeDevicePixelRatio");
--- a/devtools/client/responsive.html/components/DeviceSelector.js
+++ b/devtools/client/responsive.html/components/DeviceSelector.js
@@ -67,17 +67,17 @@ class DeviceSelector extends PureCompone
}
}
}
options.sort(function (a, b) {
return a.name.localeCompare(b.name);
});
- let selectClass = "viewport-device-selector";
+ let selectClass = "viewport-device-selector toolbar-dropdown";
if (selectedDevice) {
selectClass += " selected";
}
let state = devices.listState;
let listContent;
if (state == Types.loadableState.LOADED) {
--- a/devtools/client/responsive.html/components/GlobalToolbar.js
+++ b/devtools/client/responsive.html/components/GlobalToolbar.js
@@ -7,46 +7,51 @@
const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { getStr } = require("../utils/l10n");
const Types = require("../types");
const DevicePixelRatioSelector = createFactory(require("./DevicePixelRatioSelector"));
const NetworkThrottlingSelector = createFactory(require("./NetworkThrottlingSelector"));
+const ReloadConditions = createFactory(require("./ReloadConditions"));
class GlobalToolbar extends PureComponent {
static get propTypes() {
return {
devices: PropTypes.shape(Types.devices).isRequired,
displayPixelRatio: Types.pixelRatio.value.isRequired,
networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+ reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
screenshot: PropTypes.shape(Types.screenshot).isRequired,
selectedDevice: PropTypes.string.isRequired,
selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
onChangeNetworkThrottling: PropTypes.func.isRequired,
onChangePixelRatio: PropTypes.func.isRequired,
+ onChangeReloadCondition: PropTypes.func.isRequired,
onChangeTouchSimulation: PropTypes.func.isRequired,
onExit: PropTypes.func.isRequired,
onScreenshot: PropTypes.func.isRequired,
};
}
render() {
let {
devices,
displayPixelRatio,
networkThrottling,
+ reloadConditions,
screenshot,
selectedDevice,
selectedPixelRatio,
touchSimulation,
onChangeNetworkThrottling,
onChangePixelRatio,
+ onChangeReloadCondition,
onChangeTouchSimulation,
onExit,
onScreenshot,
} = this.props;
let touchButtonClass = "toolbar-button devtools-button";
if (touchSimulation.enabled) {
touchButtonClass += " checked";
@@ -69,16 +74,20 @@ class GlobalToolbar extends PureComponen
}),
DevicePixelRatioSelector({
devices,
displayPixelRatio,
selectedDevice,
selectedPixelRatio,
onChangePixelRatio,
}),
+ ReloadConditions({
+ reloadConditions,
+ onChangeReloadCondition,
+ }),
dom.button({
id: "global-touch-simulation-button",
className: touchButtonClass,
title: (touchSimulation.enabled ?
getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
onClick: () => onChangeTouchSimulation(!touchSimulation.enabled),
}),
dom.button({
--- a/devtools/client/responsive.html/components/NetworkThrottlingSelector.js
+++ b/devtools/client/responsive.html/components/NetworkThrottlingSelector.js
@@ -43,17 +43,17 @@ class NetworkThrottlingSelector extends
}
}
render() {
let {
networkThrottling,
} = this.props;
- let selectClass = "";
+ let selectClass = "toolbar-dropdown";
let selectedProfile;
if (networkThrottling.enabled) {
selectClass += " selected";
selectedProfile = networkThrottling.profile;
} else {
selectedProfile = getStr("responsive.noThrottling");
}
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/ReloadConditions.js
@@ -0,0 +1,49 @@
+/* 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 { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const Types = require("../types");
+const { getStr } = require("../utils/l10n");
+const ToggleMenu = createFactory(require("./ToggleMenu"));
+
+class ReloadConditions extends PureComponent {
+ static get propTypes() {
+ return {
+ reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
+ onChangeReloadCondition: PropTypes.func.isRequired,
+ };
+ }
+
+ render() {
+ let {
+ reloadConditions,
+ onChangeReloadCondition,
+ } = this.props;
+
+ return ToggleMenu({
+ id: "global-reload-conditions-menu",
+ items: [
+ {
+ id: "touchSimulation",
+ label: getStr("responsive.reloadConditions.touchSimulation"),
+ checked: reloadConditions.touchSimulation,
+ },
+ {
+ id: "userAgent",
+ label: getStr("responsive.reloadConditions.userAgent"),
+ checked: reloadConditions.userAgent,
+ },
+ ],
+ label: getStr("responsive.reloadConditions.label"),
+ title: getStr("responsive.reloadConditions.title"),
+ onChange: onChangeReloadCondition,
+ });
+ }
+}
+
+module.exports = ReloadConditions;
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/ToggleMenu.js
@@ -0,0 +1,129 @@
+/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+let MenuItem = {
+ id: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ checked: PropTypes.bool,
+};
+
+class ToggleMenu extends PureComponent {
+ static get propTypes() {
+ return {
+ id: PropTypes.string,
+ items: PropTypes.arrayOf(PropTypes.shape(MenuItem)).isRequired,
+ label: PropTypes.string,
+ title: PropTypes.string,
+ onChange: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isOpen: false,
+ };
+
+ this.onItemChange = this.onItemChange.bind(this);
+ this.onToggleOpen = this.onToggleOpen.bind(this);
+ }
+
+ onItemChange({ target }) {
+ let {
+ onChange,
+ } = this.props;
+
+ // Close menu after changing an item
+ this.setState({
+ isOpen: false,
+ });
+
+ let id = target.name;
+ onChange(id, target.checked);
+ }
+
+ onToggleOpen() {
+ let {
+ isOpen,
+ } = this.state;
+
+ this.setState({
+ isOpen: !isOpen,
+ });
+ }
+
+ render() {
+ let {
+ id: menuID,
+ items,
+ label: toggleLabel,
+ title,
+ } = this.props;
+
+ let {
+ isOpen,
+ } = this.state;
+
+ let {
+ onItemChange,
+ onToggleOpen,
+ } = this;
+
+ let menuItems = items.map(({ id, label, checked }) => {
+ let inputID = `devtools-menu-item-${id}`;
+
+ return dom.div(
+ {
+ className: "devtools-menu-item",
+ },
+ dom.input({
+ type: "checkbox",
+ id: inputID,
+ name: id,
+ checked,
+ onChange: onItemChange,
+ }),
+ dom.label({
+ htmlFor: inputID,
+ }, label)
+ );
+ });
+
+ let menuClass = "devtools-menu";
+ if (isOpen) {
+ menuClass += " opened";
+ }
+ let menu = dom.div(
+ {
+ className: menuClass,
+ },
+ menuItems
+ );
+
+ let buttonClass = "devtools-toggle-menu";
+ buttonClass += " toolbar-dropdown toolbar-button devtools-button";
+ if (isOpen || items.some(({ checked }) => checked)) {
+ buttonClass += " selected";
+ }
+ return dom.div(
+ {
+ id: menuID,
+ className: buttonClass,
+ title,
+ onClick: onToggleOpen,
+ },
+ toggleLabel,
+ menu
+ );
+ }
+}
+
+module.exports = ToggleMenu;
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -7,14 +7,16 @@
DevToolsModules(
'Browser.js',
'DeviceAdder.js',
'DeviceModal.js',
'DevicePixelRatioSelector.js',
'DeviceSelector.js',
'GlobalToolbar.js',
'NetworkThrottlingSelector.js',
+ 'ReloadConditions.js',
'ResizableViewport.js',
+ 'ToggleMenu.js',
'Viewport.js',
'ViewportDimension.js',
'Viewports.js',
'ViewportToolbar.js',
)
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -62,38 +62,42 @@ body,
background-color: var(--theme-toolbar-background);
border: 1px solid var(--theme-splitter-color);
}
.toolbar-button {
margin: 0;
padding: 0;
border: none;
+ color: var(--viewport-color);
}
.toolbar-button:empty:hover:not(:disabled),
-.toolbar-button:empty:-moz-any(:hover:active, .checked):not(:disabled) {
+.toolbar-button:empty:-moz-any(:hover:active, .checked):not(:disabled),
+.toolbar-button:not(:empty),
+.toolbar-button:hover:not(:empty):not(:disabled):not(.checked) {
/* Reset background from .devtools-button */
background: none;
}
.toolbar-button:active::before {
filter: var(--theme-icon-checked-filter);
}
+.toolbar-button.selected {
+ color: var(--viewport-active-color);
+}
+
+.toolbar-button:not(:disabled):hover {
+ color: var(--viewport-hover-color);
+}
+
select {
-moz-appearance: none;
- background-color: var(--theme-toolbar-background);
- background-image: var(--viewport-selection-arrow);
- -moz-context-properties: fill;
- fill: currentColor;
color: var(--viewport-color);
- background-position: 100% 50%;
- background-repeat: no-repeat;
- background-size: 7px;
border: none;
height: 100%;
padding: 0 8px;
text-align: center;
text-overflow: ellipsis;
}
select.selected {
@@ -125,26 +129,73 @@ select > option:hover {
select > option.divider {
border-top: 1px solid var(--theme-splitter-color);
height: 0px;
padding: 0;
font-size: 0px;
}
/**
+ * Toggle Menu
+ */
+
+.devtools-toggle-menu {
+ position: relative;
+}
+
+.devtools-toggle-menu .devtools-menu {
+ display: none;
+ flex-direction: column;
+ align-items: start;
+ position: absolute;
+ left: 0;
+ top: 100%;
+ z-index: 1;
+ padding: 5px;
+ border-radius: 2px;
+ background-color: var(--theme-toolbar-background);
+ box-shadow: var(--rdm-box-shadow);
+}
+
+.devtools-toggle-menu .devtools-menu.opened {
+ display: flex;
+}
+
+.devtools-toggle-menu .devtools-menu-item {
+ display: flex;
+ align-items: center;
+}
+
+/**
+ * Common background for dropdowns like select and toggle menu
+ */
+.toolbar-dropdown,
+.toolbar-dropdown.devtools-button,
+.toolbar-dropdown.devtools-button:hover:not(:empty):not(:disabled):not(.checked) {
+ background-color: var(--theme-toolbar-background);
+ background-image: var(--viewport-selection-arrow);
+ background-position: 100% 50%;
+ background-repeat: no-repeat;
+ background-size: 7px;
+ -moz-context-properties: fill;
+ fill: currentColor;
+}
+
+/**
* Global Toolbar
*/
#global-toolbar {
color: var(--theme-body-color-alt);
border-radius: 2px;
box-shadow: var(--rdm-box-shadow);
margin: 0 0 15px 0;
padding: 4px 5px;
display: inline-flex;
+ align-items: center;
-moz-user-select: none;
}
#global-toolbar > .title {
border-right: 1px solid var(--theme-splitter-color);
padding: 1px 6px 0 2px;
}
@@ -152,16 +203,23 @@ select > option.divider {
margin-inline-start: 8px;
}
#global-toolbar > .toolbar-button::before {
width: 12px;
height: 12px;
}
+#global-toolbar .toolbar-dropdown {
+ background-position-x: right 5px;
+ border-right: 1px solid var(--theme-splitter-color);
+ padding-right: 15px;
+ /* padding-left: 0; */
+}
+
#global-touch-simulation-button::before {
background-image: url("./images/touch-events.svg");
}
#global-screenshot-button::before {
background-image: url("./images/screenshot.svg");
}
--- a/devtools/client/responsive.html/index.js
+++ b/devtools/client/responsive.html/index.js
@@ -18,20 +18,21 @@ const { loadAgentSheet } = require("./ut
const { createFactory, createElement } =
require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const message = require("./utils/message");
const App = createFactory(require("./app"));
const Store = require("./store");
-const { changeLocation } = require("./actions/location");
+const { loadDevices } = require("./actions/devices");
const { changeDisplayPixelRatio } = require("./actions/display-pixel-ratio");
+const { changeLocation } = require("./actions/location");
+const { loadReloadConditions } = require("./actions/reload-conditions");
const { addViewport, resizeViewport } = require("./actions/viewports");
-const { loadDevices } = require("./actions/devices");
// Exposed for use by tests
window.require = require;
let bootstrap = {
telemetry: new Telemetry(),
@@ -74,17 +75,20 @@ let bootstrap = {
};
// manager.js sends a message to signal init
message.wait(window, "init").then(() => bootstrap.init());
// manager.js sends a message to signal init is done, which can be used for delayed
// startup work that shouldn't block initial load
-message.wait(window, "post-init").then(() => bootstrap.dispatch(loadDevices()));
+message.wait(window, "post-init").then(() => {
+ bootstrap.dispatch(loadDevices());
+ bootstrap.dispatch(loadReloadConditions());
+});
window.addEventListener("unload", function () {
bootstrap.destroy();
}, {once: true});
// Allows quick testing of actions from the console
window.dispatch = action => bootstrap.dispatch(action);
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -1,16 +1,17 @@
/* 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 { Ci } = require("chrome");
const promise = require("promise");
+const Services = require("Services");
const EventEmitter = require("devtools/shared/old-event-emitter");
const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/debugger-client", 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);
@@ -22,16 +23,18 @@ loader.lazyRequireGetter(this, "startup"
"devtools/client/responsive.html/utils/window", true);
loader.lazyRequireGetter(this, "message",
"devtools/client/responsive.html/utils/message");
loader.lazyRequireGetter(this, "getStr",
"devtools/client/responsive.html/utils/l10n", true);
loader.lazyRequireGetter(this, "EmulationFront",
"devtools/shared/fronts/emulation", true);
+const RELOAD_CONDITION_PREF_PREFIX = "devtools.responsive.reloadConditions.";
+
function debug(msg) {
// console.log(`RDM manager: ${msg}`);
}
/**
* ResponsiveUIManager is the external API for the browser UI, etc. to use when
* opening and closing the responsive UI.
*/
@@ -400,20 +403,22 @@ ResponsiveUI.prototype = {
// Notify the inner browser to stop the frame script
await message.request(this.toolWindow, "stop-frame-script");
}
// Ensure the tab is reloaded if required when exiting RDM so that no emulated
// settings are left in a customized state.
if (!isTabContentDestroying) {
let reloadNeeded = false;
- reloadNeeded |= await this.updateDPPX();
- reloadNeeded |= await this.updateNetworkThrottling();
- reloadNeeded |= await this.updateUserAgent();
- reloadNeeded |= await this.updateTouchSimulation();
+ await this.updateDPPX();
+ await this.updateNetworkThrottling();
+ reloadNeeded |= await this.updateUserAgent() &&
+ this.reloadOnChange("userAgent");
+ reloadNeeded |= await this.updateTouchSimulation() &&
+ this.reloadOnChange("touchSimulation");
if (reloadNeeded) {
this.getViewportBrowser().reload();
}
}
// Destroy local state
let swap = this.swap;
this.browserWindow = null;
@@ -445,16 +450,21 @@ ResponsiveUI.prototype = {
DebuggerServer.init();
DebuggerServer.registerAllActors();
this.client = new DebuggerClient(DebuggerServer.connectPipe());
await this.client.connect();
let { tab } = await this.client.getTab();
this.emulationFront = EmulationFront(this.client, tab);
},
+ reloadOnChange(id) {
+ let pref = RELOAD_CONDITION_PREF_PREFIX + id;
+ return Services.prefs.getBoolPref(pref, false);
+ },
+
handleEvent(event) {
let { browserWindow, tab } = this;
switch (event.type) {
case "message":
this.handleMessage(event);
break;
case "BeforeTabRemotenessChange":
@@ -494,20 +504,22 @@ ResponsiveUI.prototype = {
case "remove-device-association":
this.onRemoveDeviceAssociation(event);
break;
}
},
async onChangeDevice(event) {
let { userAgent, pixelRatio, touch } = event.data.device;
- // Bug 1428799: Should we reload on UA change as well?
- await this.updateUserAgent(userAgent);
+ let reloadNeeded = false;
await this.updateDPPX(pixelRatio);
- let reloadNeeded = await this.updateTouchSimulation(touch);
+ reloadNeeded |= await this.updateUserAgent(userAgent) &&
+ this.reloadOnChange("userAgent");
+ reloadNeeded |= await this.updateTouchSimulation(touch) &&
+ this.reloadOnChange("touchSimulation");
if (reloadNeeded) {
this.getViewportBrowser().reload();
}
// Used by tests
this.emit("device-changed");
},
async onChangeNetworkThrottling(event) {
@@ -519,17 +531,18 @@ ResponsiveUI.prototype = {
onChangePixelRatio(event) {
let { pixelRatio } = event.data;
this.updateDPPX(pixelRatio);
},
async onChangeTouchSimulation(event) {
let { enabled } = event.data;
- let reloadNeeded = await this.updateTouchSimulation(enabled);
+ let reloadNeeded = await this.updateTouchSimulation(enabled) &&
+ this.reloadOnChange("touchSimulation");
if (reloadNeeded) {
this.getViewportBrowser().reload();
}
// Used by tests
this.emit("touch-simulation-changed");
},
onContentResize(event) {
@@ -541,20 +554,22 @@ ResponsiveUI.prototype = {
},
onExit() {
let { browserWindow, tab } = this;
ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
},
async onRemoveDeviceAssociation(event) {
- // Bug 1428799: Should we reload on UA change as well?
- await this.updateUserAgent();
+ let reloadNeeded = false;
await this.updateDPPX();
- let reloadNeeded = await this.updateTouchSimulation();
+ reloadNeeded |= await this.updateUserAgent() &&
+ this.reloadOnChange("userAgent");
+ reloadNeeded |= await this.updateTouchSimulation() &&
+ this.reloadOnChange("touchSimulation");
if (reloadNeeded) {
this.getViewportBrowser().reload();
}
// Used by tests
this.emit("device-association-removed");
},
/**
--- a/devtools/client/responsive.html/reducers.js
+++ b/devtools/client/responsive.html/reducers.js
@@ -3,11 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
exports.devices = require("./reducers/devices");
exports.displayPixelRatio = require("./reducers/display-pixel-ratio");
exports.location = require("./reducers/location");
exports.networkThrottling = require("./reducers/network-throttling");
+exports.reloadConditions = require("./reducers/reload-conditions");
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
@@ -4,12 +4,13 @@
# 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',
'display-pixel-ratio.js',
'location.js',
'network-throttling.js',
+ 'reload-conditions.js',
'screenshot.js',
'touch-simulation.js',
'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers/reload-conditions.js
@@ -0,0 +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 {
+ CHANGE_RELOAD_CONDITION,
+ LOAD_RELOAD_CONDITIONS_END,
+} = require("../actions/index");
+
+const Types = require("../types");
+
+const INITIAL_RELOAD_CONDITIONS = {
+ touchSimulation: false,
+ userAgent: false,
+ state: Types.loadableState.INITIALIZED,
+};
+
+let reducers = {
+
+ [CHANGE_RELOAD_CONDITION](conditions, { id, value }) {
+ return Object.assign({}, conditions, {
+ [id]: value,
+ });
+ },
+
+ [LOAD_RELOAD_CONDITIONS_END](conditions) {
+ return Object.assign({}, conditions, {
+ state: Types.loadableState.LOADED,
+ });
+ },
+
+};
+
+module.exports = function (conditions = INITIAL_RELOAD_CONDITIONS, action) {
+ let reducer = reducers[action.type];
+ if (!reducer) {
+ return conditions;
+ }
+ return reducer(conditions, action);
+};
--- a/devtools/client/responsive.html/test/browser/browser_device_change.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_change.js
@@ -26,16 +26,18 @@ const testDevice = {
};
// Add the new device to the list
addDeviceForTest(testDevice);
addRDMTask(TEST_URL, async function ({ ui }) {
let { store } = ui.toolWindow;
+ reloadOnUAChange(true);
+
// Wait until the viewport has been added and the device list has been loaded
await waitUntilState(store, state => state.viewports.length == 1
&& state.devices.listState == Types.loadableState.LOADED);
// Test defaults
testViewportDimensions(ui, 320, 480);
info("Should have default UA at the start of the test");
await testUserAgent(ui, DEFAULT_UA);
@@ -67,24 +69,28 @@ addRDMTask(TEST_URL, async function ({ u
// Test device with generic properties
await selectDevice(ui, "Laptop (1366 x 768)");
await waitForViewportResizeTo(ui, 1366, 768);
info("Should have default UA when using device without specific UA");
await testUserAgent(ui, DEFAULT_UA);
await testDevicePixelRatio(ui, 1);
await testTouchEventsOverride(ui, false);
+
+ reloadOnUAChange(false);
});
add_task(async function () {
const tab = await addTab(TEST_URL);
const { ui } = await openRDM(tab);
let { store } = ui.toolWindow;
+ reloadOnUAChange(true);
+
// Wait until the viewport has been added and the device list has been loaded
await waitUntilState(store, state => state.viewports.length == 1
&& state.devices.listState == Types.loadableState.LOADED);
// Select device with custom UA
let reloaded = waitForViewportLoad(ui);
await selectDevice(ui, "Fake Phone RDM Test");
await reloaded;
@@ -97,16 +103,18 @@ add_task(async function () {
await closeRDM(tab);
await reloaded;
// Ensure UA is reset to default after closing RDM
info("Should have default UA after closing RDM");
await testUserAgentFromBrowser(tab.linkedBrowser, DEFAULT_UA);
await removeTab(tab);
+
+ reloadOnUAChange(false);
});
function testViewportDimensions(ui, w, h) {
let viewport = ui.toolWindow.document.querySelector(".viewport-content");
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"),
`${w}px`, `Viewport should have width of ${w}px`);
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"),
--- a/devtools/client/responsive.html/test/browser/browser_device_pixel_ratio_change.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_pixel_ratio_change.js
@@ -51,37 +51,34 @@ async function testDefaults(ui) {
disabled: false,
});
testViewportDeviceSelectLabel(ui, "no device selected");
}
async function testChangingDevice(ui) {
info("Test Changing Device");
- let reloaded = waitForViewportLoad(ui);
await selectDevice(ui, testDevice.name);
- await reloaded;
await waitForViewportResizeTo(ui, testDevice.width, testDevice.height);
let dppx = await waitForDevicePixelRatio(ui, testDevice.pixelRatio);
is(dppx, testDevice.pixelRatio, "Content has expected devicePixelRatio");
testViewportDevicePixelRatioSelect(ui, {
value: testDevice.pixelRatio,
disabled: true,
});
testViewportDeviceSelectLabel(ui, testDevice.name);
}
async function testResetWhenResizingViewport(ui) {
info("Test reset when resizing the viewport");
let deviceRemoved = once(ui, "device-association-removed");
- let reloaded = waitForViewportLoad(ui);
await testViewportResize(ui, ".viewport-vertical-resize-handle",
[-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui);
- await Promise.all([ deviceRemoved, reloaded ]);
+ await deviceRemoved;
let dppx = await waitForDevicePixelRatio(ui, DEFAULT_DPPX);
is(dppx, DEFAULT_DPPX, "Content has expected devicePixelRatio");
testViewportDevicePixelRatioSelect(ui, {
value: DEFAULT_DPPX,
disabled: false,
});
--- a/devtools/client/responsive.html/test/browser/browser_touch_device.js
+++ b/devtools/client/responsive.html/test/browser/browser_touch_device.js
@@ -18,24 +18,28 @@ const testDevice = {
"os": "custom",
"featured": true,
};
// Add the new device to the list
addDeviceForTest(testDevice);
addRDMTask(TEST_URL, async function ({ ui, manager }) {
+ reloadOnTouchChange(true);
+
await waitStartup(ui);
await testDefaults(ui);
await testChangingDevice(ui);
await testResizingViewport(ui, true, false);
await testEnableTouchSimulation(ui);
await testResizingViewport(ui, false, true);
await testDisableTouchSimulation(ui);
+
+ reloadOnTouchChange(false);
});
async function waitStartup(ui) {
let { store } = ui.toolWindow;
// Wait until the viewport has been added and the device list has been loaded
await waitUntilState(store, state => state.viewports.length == 1
&& state.devices.listState == Types.loadableState.LOADED);
--- a/devtools/client/responsive.html/test/browser/browser_touch_simulation.js
+++ b/devtools/client/responsive.html/test/browser/browser_touch_simulation.js
@@ -4,25 +4,29 @@
"use strict";
// Test global touch simulation button
const TEST_URL = `${URL_ROOT}touch.html`;
const PREF_DOM_META_VIEWPORT_ENABLED = "dom.meta-viewport.enabled";
addRDMTask(TEST_URL, async function ({ ui }) {
+ reloadOnTouchChange(true);
+
await injectEventUtilsInContentTask(ui.getViewportBrowser());
await waitBootstrap(ui);
await testWithNoTouch(ui);
await toggleTouchSimulation(ui);
await testWithTouch(ui);
await testWithMetaViewportEnabled(ui);
await testWithMetaViewportDisabled(ui);
testTouchButton(ui);
+
+ reloadOnTouchChange(false);
});
async function testWithNoTouch(ui) {
await ContentTask.spawn(ui.getViewportBrowser(), {}, async function () {
let div = content.document.querySelector("div");
let x = 0, y = 0;
info("testWithNoTouch: Initial test parameter and mouse mouse outside div");
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -29,38 +29,39 @@ Services.scriptloader.loadSubScript(
// Import helpers for the inspector that are also shared with others
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
this);
const E10S_MULTI_ENABLED = Services.prefs.getIntPref("dom.ipc.processCount") > 1;
const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/";
const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
+const RELOAD_CONDITION_PREF_PREFIX = "devtools.responsive.reloadConditions.";
const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices");
const asyncStorage = require("devtools/shared/async-storage");
const { addDevice, removeDevice, removeLocalDevices } = require("devtools/client/shared/devices");
SimpleTest.requestCompleteLog();
SimpleTest.waitForExplicitFinish();
// Toggling the RDM UI involves several docShell swap operations, which are somewhat slow
// on debug builds. Usually we are just barely over the limit, so a blanket factor of 2
// should be enough.
requestLongerTimeout(2);
flags.testing = true;
-Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
-Services.prefs.setCharPref("devtools.devices.url",
- TEST_URI_ROOT + "devices.json");
+Services.prefs.setCharPref("devtools.devices.url", TEST_URI_ROOT + "devices.json");
registerCleanupFunction(async () => {
flags.testing = false;
Services.prefs.clearUserPref("devtools.devices.url");
Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
+ Services.prefs.clearUserPref("devtools.responsive.reloadConditions.touchSimulation");
+ Services.prefs.clearUserPref("devtools.responsive.reloadConditions.userAgent");
await asyncStorage.removeItem("devtools.devices.url_cache");
await removeLocalDevices();
});
loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
/**
* Open responsive design mode for the given tab.
@@ -414,8 +415,18 @@ function addDeviceInModal(ui, device) {
let existingCustomDevices = store.getState().devices.custom.length;
let adderSave = document.querySelector("#device-adder-save");
let saved = waitUntilState(store, state =>
state.devices.custom.length == existingCustomDevices + 1
);
Simulate.click(adderSave);
return saved;
}
+
+function reloadOnUAChange(enabled) {
+ let pref = RELOAD_CONDITION_PREF_PREFIX + "userAgent";
+ Services.prefs.setBoolPref(pref, enabled);
+}
+
+function reloadOnTouchChange(enabled) {
+ let pref = RELOAD_CONDITION_PREF_PREFIX + "touchSimulation";
+ Services.prefs.setBoolPref(pref, enabled);
+}
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.js
@@ -5,23 +5,51 @@
"use strict";
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { createEnum } = require("devtools/client/shared/enum");
// React PropTypes are used to describe the expected "shape" of various common
// objects that get passed down as props to components.
+/* ENUMS */
+
+/**
+ * An enum containing the possible states for loadable things.
+ */
+exports.loadableState = createEnum([
+ "INITIALIZED",
+ "LOADING",
+ "LOADED",
+ "ERROR",
+]);
+
/* GLOBAL */
/**
* The location of the document displayed in the viewport(s).
*/
exports.location = PropTypes.string;
+/**
+ * Whether to reload the page automatically when certain actions occur.
+ */
+exports.reloadConditions = {
+
+ // Whether to reload when touch simulation is toggled
+ touchSimulation: PropTypes.bool,
+
+ // Whether to reload when user agent is changed
+ userAgent: PropTypes.bool,
+
+ // Loaded state of these conditions
+ state: PropTypes.oneOf(Object.keys(exports.loadableState)),
+
+};
+
/* DEVICE */
/**
* A single device that can be displayed in the viewport.
*/
const device = {
// The name of the device
@@ -46,26 +74,16 @@ const device = {
os: PropTypes.String,
// Whether or not the device is displayed in the device selector
displayed: PropTypes.bool,
};
/**
- * An enum containing the possible states for loadable things.
- */
-exports.loadableState = createEnum([
- "INITIALIZED",
- "LOADING",
- "LOADED",
- "ERROR",
-]);
-
-/**
* A list of devices and their types that can be displayed in the viewport.
*/
exports.devices = {
// An array of device types
types: PropTypes.arrayOf(PropTypes.string),
// An array of phone devices