author | Gabriel Luong <gabriel.luong@gmail.com> |
Mon, 21 Mar 2016 14:48:31 -0400 | |
changeset 289599 | 4eea3b603b0aca9c157dc2e9f3bb3c0d9f23b1c3 |
parent 289598 | 3ce5d23d337f45ae96100c00ab994b53d202d7c4 |
child 289600 | dd6593bfbb5962c3efd3382c121f1a965959cb5b |
push id | 30107 |
push user | cbook@mozilla.com |
push date | Tue, 22 Mar 2016 10:00:23 +0000 |
treeherder | mozilla-central@3587b25bae30 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jryans |
bugs | 1241714 |
milestone | 48.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/base/content/test/general/browser_parsable_css.js +++ b/browser/base/content/test/general/browser_parsable_css.js @@ -22,16 +22,19 @@ const kWhitelist = [ // Loop standalone client CSS uses placeholder cross browser pseudo-element {sourceName: /loop\/.*\.css$/i, errorMessage: /Unknown pseudo-class.*placeholder/i}, {sourceName: /loop\/.*shared\/css\/common.css$/i, errorMessage: /Unknown property 'user-select'/i}, // Highlighter CSS uses a UA-only pseudo-class, see bug 985597. {sourceName: /highlighters\.css$/i, errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i}, + // Responsive Design Mode CSS uses a UA-only pseudo-class, see Bug 1241714. + {sourceName: /responsive-ua\.css$/i, + errorMessage: /Unknown pseudo-class.*moz-dropdown-list/i}, ]; var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm"); var {generateURIsFromDirTree} = Cu.import(moduleLocation, {}); // Add suffix to stylesheets' URI so that we always load them here and // have them parsed. Add a random number so that even if we run this // test multiple times, it would be unlikely to affect each other.
--- a/devtools/client/locales/en-US/responsive.properties +++ b/devtools/client/locales/en-US/responsive.properties @@ -12,8 +12,12 @@ # documentation on web development on the web. # LOCALIZATION NOTE (responsive.title): the title displayed in the global # toolbar responsive.title=Responsive Design Mode # LOCALIZATION NOTE (responsive.exit): tooltip text of the exit button. responsive.exit=Close Responsive Design Mode + +# LOCALIZATION NOTE (responsive.noDeviceSelected): placeholder text for the +# device selector +responsive.noDeviceSelected=no device selected
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/actions/devices.js @@ -0,0 +1,29 @@ +/* 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 { + ADD_DEVICE, + ADD_DEVICE_TYPE, +} = require("./index"); + +module.exports = { + + addDevice(device, deviceType) { + return { + type: ADD_DEVICE, + device, + deviceType, + }; + }, + + addDeviceType(deviceType) { + return { + type: ADD_DEVICE_TYPE, + deviceType, + }; + }, + +};
--- a/devtools/client/responsive.html/actions/index.js +++ b/devtools/client/responsive.html/actions/index.js @@ -5,23 +5,32 @@ "use strict"; // This file lists all of the actions available in responsive design. This // central list of constants makes it easy to see all possible action names at // a glance. Please add a comment with each new action type. createEnum([ + // Add a new device. + "ADD_DEVICE", + + // Add a new device type. + "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 // directly entering a new URL, navigating with links, etc. "CHANGE_LOCATION", - // Add an additional viewport to display the document. - "ADD_VIEWPORT", - // Resize the viewport. "RESIZE_VIEWPORT", // Rotate the viewport. "ROTATE_VIEWPORT", ], module.exports);
--- a/devtools/client/responsive.html/actions/moz.build +++ b/devtools/client/responsive.html/actions/moz.build @@ -1,11 +1,12 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # 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', 'index.js', 'location.js', 'viewports.js', )
--- a/devtools/client/responsive.html/actions/viewports.js +++ b/devtools/client/responsive.html/actions/viewports.js @@ -1,32 +1,44 @@ /* 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 { ADD_VIEWPORT, + CHANGE_DEVICE, RESIZE_VIEWPORT, ROTATE_VIEWPORT } = require("./index"); module.exports = { /** * Add an additional viewport to display the document. */ addViewport() { return { type: ADD_VIEWPORT, }; }, /** + * Change the viewport device. + */ + changeDevice(id, device) { + return { + type: CHANGE_DEVICE, + id, + device, + }; + }, + + /** * Resize the viewport. */ resizeViewport(id, width, height) { return { type: RESIZE_VIEWPORT, id, width, height,
--- a/devtools/client/responsive.html/app.js +++ b/devtools/client/responsive.html/app.js @@ -3,61 +3,74 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { createClass, createFactory, PropTypes, DOM: dom } = require("devtools/client/shared/vendor/react"); const { connect } = require("devtools/client/shared/vendor/react-redux"); -const { resizeViewport, rotateViewport } = require("./actions/viewports"); +const { + changeDevice, + resizeViewport, + rotateViewport +} = require("./actions/viewports"); const Types = require("./types"); const Viewports = createFactory(require("./components/viewports")); const GlobalToolbar = createFactory(require("./components/global-toolbar")); let App = createClass({ displayName: "App", propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, location: Types.location.isRequired, viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired, onExit: PropTypes.func.isRequired, }, + onChangeViewportDevice(id, device) { + this.props.dispatch(changeDevice(id, device)); + }, + + onResizeViewport(id, width, height) { + this.props.dispatch(resizeViewport(id, width, height)); + }, + onRotateViewport(id) { this.props.dispatch(rotateViewport(id)); }, - onResizeViewport(id, width, height) { - this.props.dispatch(resizeViewport(id, width, height)); - }, - render() { let { + devices, location, viewports, onExit, } = this.props; let { + onChangeViewportDevice, + onResizeViewport, onRotateViewport, - onResizeViewport, } = this; return dom.div( { id: "app", }, GlobalToolbar({ onExit, }), Viewports({ + devices, location, viewports, + onChangeViewportDevice, onRotateViewport, onResizeViewport, }) ); }, });
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/components/device-selector.js @@ -0,0 +1,83 @@ +/* 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 { getStr } = require("./utils/l10n"); +const { DOM: dom, createClass, PropTypes, addons } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); + +module.exports = createClass({ + + displayName: "DeviceSelector", + + propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + selectedDevice: PropTypes.string.isRequired, + onChangeViewportDevice: PropTypes.func.isRequired, + onResizeViewport: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + onSelectChange({ target }) { + let { + devices, + onChangeViewportDevice, + onResizeViewport, + } = this.props; + + for (let type of devices.types) { + for (let device of devices[type]) { + if (device.name === target.value) { + onResizeViewport(device.width, device.height); + break; + } + } + } + + onChangeViewportDevice(target.value); + }, + + render() { + let { + devices, + selectedDevice, + } = this.props; + + let options = []; + for (let type of devices.types) { + for (let device of devices[type]) { + options.push(device); + } + } + + let selectClass = "viewport-device-selector"; + if (selectedDevice) { + selectClass += " selected"; + } + + return dom.select( + { + className: selectClass, + value: selectedDevice, + onChange: this.onSelectChange, + }, + dom.option({ + value: "", + disabled: true, + hidden: true, + }, getStr("responsive.noDeviceSelected")), + options.map(device => { + return dom.option({ + key: device.name, + value: device.name, + }, device.name); + }) + ); + }, + +});
--- a/devtools/client/responsive.html/components/moz.build +++ b/devtools/client/responsive.html/components/moz.build @@ -5,15 +5,16 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DIRS += [ 'utils', ] DevToolsModules( 'browser.js', + 'device-selector.js', 'global-toolbar.js', 'resizable-viewport.js', 'viewport-dimension.js', 'viewport-toolbar.js', 'viewport.js', 'viewports.js', )
--- a/devtools/client/responsive.html/components/resizable-viewport.js +++ b/devtools/client/responsive.html/components/resizable-viewport.js @@ -17,18 +17,20 @@ const ViewportToolbar = createFactory(re const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION; const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION; module.exports = createClass({ displayName: "ResizableViewport", propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, location: Types.location.isRequired, viewport: PropTypes.shape(Types.viewport).isRequired, + onChangeViewportDevice: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, onRotateViewport: PropTypes.func.isRequired, }, getInitialState() { return { isResizing: false, lastClientX: 0, @@ -92,35 +94,44 @@ module.exports = createClass({ if (height < VIEWPORT_MIN_HEIGHT) { height = VIEWPORT_MIN_HEIGHT; } else { lastClientY = clientY; } // Update the viewport store with the new width and height. this.props.onResizeViewport(width, height); + // Change the device selector back to an unselected device + this.props.onChangeViewportDevice(""); this.setState({ lastClientX, lastClientY }); }, render() { let { + devices, location, viewport, + onChangeViewportDevice, + onResizeViewport, onRotateViewport, } = this.props; return dom.div( { className: "resizable-viewport", }, ViewportToolbar({ + devices, + selectedDevice: viewport.device, + onChangeViewportDevice, + onResizeViewport, onRotateViewport, }), Browser({ location, width: viewport.width, height: viewport.height, isResizing: this.state.isResizing }),
--- a/devtools/client/responsive.html/components/viewport-dimension.js +++ b/devtools/client/responsive.html/components/viewport-dimension.js @@ -11,16 +11,17 @@ const Constants = require("../constants" const Types = require("../types"); module.exports = createClass({ displayName: "ViewportDimension", propTypes: { viewport: PropTypes.shape(Types.viewport).isRequired, + onChangeViewportDevice: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, }, getInitialState() { let { width, height } = this.props.viewport; return { width, @@ -49,17 +50,21 @@ module.exports = createClass({ } this.setState({ isInvalid, }); }, onInputBlur() { - this.onInputSubmit(); + let { width, height } = this.props.viewport; + + if (this.state.width != width || this.state.height != height) { + this.onInputSubmit(); + } this.setState({ isEditing: false, inInvalid: false, }); }, onInputChange({ target }) { @@ -104,16 +109,18 @@ module.exports = createClass({ width, height, isInvalid: false, }); return; } + // Change the device selector back to an unselected device + this.props.onChangeViewportDevice(""); this.props.onResizeViewport(parseInt(this.state.width, 10), parseInt(this.state.height, 10)); }, render() { let editableClass = "viewport-dimension-editable"; let inputClass = "viewport-dimension-input";
--- a/devtools/client/responsive.html/components/viewport-toolbar.js +++ b/devtools/client/responsive.html/components/viewport-toolbar.js @@ -1,36 +1,53 @@ /* 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 } = +const { DOM: dom, createClass, createFactory, PropTypes, addons } = require("devtools/client/shared/vendor/react"); +const Types = require("../types"); +const DeviceSelector = createFactory(require("./device-selector")); + module.exports = createClass({ displayName: "ViewportToolbar", mixins: [ addons.PureRenderMixin ], propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + selectedDevice: PropTypes.string.isRequired, + onChangeViewportDevice: PropTypes.func.isRequired, + onResizeViewport: PropTypes.func.isRequired, onRotateViewport: PropTypes.func.isRequired, }, render() { let { + devices, + selectedDevice, + onChangeViewportDevice, + onResizeViewport, onRotateViewport, } = this.props; return dom.div( { className: "viewport-toolbar", }, + DeviceSelector({ + devices, + selectedDevice, + onChangeViewportDevice, + onResizeViewport, + }), dom.button({ className: "viewport-rotate-button toolbar-button devtools-button", onClick: onRotateViewport, }) ); }, });
--- a/devtools/client/responsive.html/components/viewport.js +++ b/devtools/client/responsive.html/components/viewport.js @@ -11,22 +11,33 @@ const Types = require("../types"); const ResizableViewport = createFactory(require("./resizable-viewport")); const ViewportDimension = createFactory(require("./viewport-dimension")); module.exports = createClass({ displayName: "Viewport", propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, location: Types.location.isRequired, viewport: PropTypes.shape(Types.viewport).isRequired, + onChangeViewportDevice: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, onRotateViewport: PropTypes.func.isRequired, }, + onChangeViewportDevice(device) { + let { + viewport, + onChangeViewportDevice, + } = this.props; + + onChangeViewportDevice(viewport.id, device); + }, + onResizeViewport(width, height) { let { viewport, onResizeViewport, } = this.props; onResizeViewport(viewport.id, width, height); }, @@ -37,35 +48,40 @@ module.exports = createClass({ onRotateViewport, } = this.props; onRotateViewport(viewport.id); }, render() { let { + devices, location, viewport, } = this.props; let { + onChangeViewportDevice, onRotateViewport, onResizeViewport, } = this; return dom.div( { className: "viewport", }, ResizableViewport({ + devices, location, viewport, + onChangeViewportDevice, onResizeViewport, onRotateViewport, }), ViewportDimension({ viewport, + onChangeViewportDevice, onResizeViewport, }) ); }, });
--- a/devtools/client/responsive.html/components/viewports.js +++ b/devtools/client/responsive.html/components/viewports.js @@ -10,39 +10,45 @@ const { DOM: dom, createClass, createFac const Types = require("../types"); const Viewport = createFactory(require("./viewport")); module.exports = createClass({ displayName: "Viewports", propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, location: Types.location.isRequired, viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired, + onChangeViewportDevice: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, onRotateViewport: PropTypes.func.isRequired, }, render() { let { + devices, location, viewports, + onChangeViewportDevice, onResizeViewport, onRotateViewport, } = this.props; return dom.div( { id: "viewports", }, viewports.map(viewport => { return Viewport({ key: viewport.id, + devices, location, viewport, + onChangeViewportDevice, onResizeViewport, onRotateViewport, }); }) ); }, });
--- a/devtools/client/responsive.html/images/moz.build +++ b/devtools/client/responsive.html/images/moz.build @@ -3,9 +3,10 @@ # 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( 'close.svg', 'grippers.svg', 'rotate-viewport.svg', + 'select-arrow.svg', )
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/images/select-arrow.svg @@ -0,0 +1,29 @@ +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"> + <defs> + <style> + use:not(:target) { + display: none; + } + #light { + fill: #dde1e4; /* --theme-splitter-color */ + } + #light-selected { + fill: #393f4c; /* --theme-body-color */ + } + #dark { + fill: #8fa1b2; /* --theme-body-color */ + } + #dark-selected { + fill: #f5f7fa; /* --theme-selection-color */ + } + </style> + <path id="base-path" d="M7.9 16.3c-.3 0-.6-.1-.8-.4l-4-4.8c-.2-.3-.3-.5-.1-.8.1-.3.5-.3.9-.3h8c.4 0 .7 0 .9.3.2.4.1.6-.1.9l-4 4.8c-.2.3-.5.3-.8.3zM7.8 0c.3 0 .6.1.7.4L12.4 5c.2.3.3.4.1.7-.1.4-.5.3-.8.3H3.9c-.4 0-.8.1-.9-.2-.2-.4-.1-.6.1-.9L7 .3c.2-.3.5-.3.8-.3z"/> + </defs> + <use xlink:href="#base-path" id="light"/> + <use xlink:href="#base-path" id="light-selected"/> + <use xlink:href="#base-path" id="dark"/> + <use xlink:href="#base-path" id="dark-selected"/> +</svg>
--- a/devtools/client/responsive.html/index.css +++ b/devtools/client/responsive.html/index.css @@ -2,24 +2,30 @@ * React component group on how to best handle CSS. */ /** * CSS Variables specific to the responsive design mode */ .theme-light { --box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26); - --viewport-dimension-color: var(--theme-splitter-color); - --viewport-dimension-editing-color: var(--theme-body-color); + --viewport-color: var(--theme-splitter-color); + --viewport-active-color: var(--theme-body-color); + --viewport-selection-arrow: url("./images/select-arrow.svg#light"); + --viewport-selection-arrow-selected: + url("./images/select-arrow.svg#light-selected"); } .theme-dark { --box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26); - --viewport-dimension-color: var(--theme-body-color); - --viewport-dimension-editing-color: var(--theme-selection-color); + --viewport-color: var(--theme-body-color); + --viewport-active-color: var(--theme-selection-color); + --viewport-selection-arrow: url("./images/select-arrow.svg#dark"); + --viewport-selection-arrow-selected: + url("./images/select-arrow.svg#dark-selected"); } * { box-sizing: border-box; } #root, html, body { @@ -130,20 +136,60 @@ body { */ .viewport-toolbar { background-color: var(--theme-toolbar-background); border-bottom: 1px solid var(--theme-splitter-color); color: var(--theme-body-color); display: flex; flex-direction: row; - justify-content: flex-end; + 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: 136px; + background-repeat: no-repeat; + background-size: 7px; + border: none; + color: var(--viewport-color); + height: 100%; + padding: 0 16px; + text-align: center; + text-overflow: ellipsis; + width: 150px; +} + +.viewport-device-selector.selected { + background-image: var(--viewport-selection-arrow-selected); + color: var(--viewport-active-color); +} + +.viewport-device-selector:focus { + background-image: var(--viewport-selection-arrow-selected); + /* Remove the outline from the select box */ + color: transparent; + text-shadow: 0 0 0 var(--viewport-active-color); +} + +.viewport-device-selector > option { + background-color: var(--theme-toolbar-background); + color: var(--viewport-active-color); + text-align: left; + padding: 5px; +} + +.viewport-rotate-button { + position: absolute; + right: 0; +} + .viewport-rotate-button::before { background-image: url("./images/rotate-viewport.svg"); } /** * Viewport Browser */ @@ -204,23 +250,23 @@ body { } .viewport-dimension-editable { border-bottom: 1px solid transparent; } .viewport-dimension-editable, .viewport-dimension-input { - color: var(--viewport-dimension-color); + color: var(--viewport-color); transition: all 0.25s ease; } .viewport-dimension-editable.editing, .viewport-dimension-input.editing { - color: var(--viewport-dimension-editing-color); + color: var(--viewport-active-color); } .viewport-dimension-editable.editing { border-bottom: 1px solid var(--theme-selection-background); } .viewport-dimension-editable.editing.invalid { border-bottom: 1px solid #d92215;
--- a/devtools/client/responsive.html/index.js +++ b/devtools/client/responsive.html/index.js @@ -8,35 +8,43 @@ const { utils: Cu } = Components; const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {}); const { require } = BrowserLoader({ baseURI: "resource://devtools/client/responsive.html/", window: this }); +const { GetDevices } = require("devtools/client/shared/devices"); const Telemetry = require("devtools/client/shared/telemetry"); 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 App = createFactory(require("./app")); const Store = require("./store"); +const { addDevice, addDeviceType } = require("./actions/devices"); const { changeLocation } = require("./actions/location"); const { addViewport } = require("./actions/viewports"); +const { loadSheet } = require("sdk/stylesheet/utils"); let bootstrap = { telemetry: new Telemetry(), store: null, init() { + // Load a special UA stylesheet to reset certain styles such as dropdown + // lists. + loadSheet(window, + "resource://devtools/client/responsive.html/responsive-ua.css", + "agent"); this.telemetry.toolOpened("responsive"); let store = this.store = Store(); let app = App({ onExit: () => window.postMessage({type: "exit"}, "*"), }); let provider = createElement(Provider, { store }, app); ReactDOM.render(provider, document.querySelector("#root")); }, @@ -78,13 +86,25 @@ Object.defineProperty(window, "store", { }); /** * Called by manager.js to add the initial viewport based on the original page. */ window.addInitialViewport = contentURI => { try { bootstrap.dispatch(changeLocation(contentURI)); + + GetDevices().then(devices => { + for (let type of devices.TYPES) { + bootstrap.dispatch(addDeviceType(type)); + for (let device of devices[type]) { + if (device.os != "fxos") { + bootstrap.dispatch(addDevice(device, type)); + } + } + } + }); + bootstrap.dispatch(addViewport()); } catch (e) { console.error(e); } };
--- a/devtools/client/responsive.html/moz.build +++ b/devtools/client/responsive.html/moz.build @@ -12,14 +12,15 @@ DIRS += [ ] DevToolsModules( 'app.js', 'constants.js', 'index.css', 'manager.js', 'reducers.js', + 'responsive-ua.css', 'store.js', 'types.js', ) XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini'] BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
--- a/devtools/client/responsive.html/reducers.js +++ b/devtools/client/responsive.html/reducers.js @@ -1,8 +1,9 @@ /* 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.viewports = require("./reducers/viewports");
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/reducers/devices.js @@ -0,0 +1,39 @@ +/* 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 { + ADD_DEVICE, + ADD_DEVICE_TYPE, +} = require("../actions/index"); + +const INITIAL_DEVICES = { + types: [], +}; + +let reducers = { + + [ADD_DEVICE](devices, { device, deviceType }) { + return Object.assign({}, devices, { + [deviceType]: [...devices[deviceType], device], + }); + }, + + [ADD_DEVICE_TYPE](devices, { deviceType }) { + return Object.assign({}, devices, { + types: [...devices.types, deviceType], + [deviceType]: [], + }); + }, + +}; + +module.exports = function(devices = INITIAL_DEVICES, action) { + let reducer = reducers[action.type]; + if (!reducer) { + return devices; + } + return reducer(devices, action); +};
--- a/devtools/client/responsive.html/reducers/moz.build +++ b/devtools/client/responsive.html/reducers/moz.build @@ -1,10 +1,11 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # 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', 'viewports.js', )
--- a/devtools/client/responsive.html/reducers/viewports.js +++ b/devtools/client/responsive.html/reducers/viewports.js @@ -1,39 +1,53 @@ /* 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 { ADD_VIEWPORT, + CHANGE_DEVICE, RESIZE_VIEWPORT, ROTATE_VIEWPORT, } = require("../actions/index"); let nextViewportId = 0; const INITIAL_VIEWPORTS = []; const INITIAL_VIEWPORT = { id: nextViewportId++, + device: "", width: 320, height: 480, }; let reducers = { [ADD_VIEWPORT](viewports) { // For the moment, there can be at most one viewport. if (viewports.length === 1) { return viewports; } return [...viewports, Object.assign({}, INITIAL_VIEWPORT)]; }, + [CHANGE_DEVICE](viewports, { id, device }) { + return viewports.map(viewport => { + if (viewport.id !== id) { + return viewport; + } + + return Object.assign({}, viewport, { + device, + }); + }); + }, + [RESIZE_VIEWPORT](viewports, { id, width, height }) { return viewports.map(viewport => { if (viewport.id !== id) { return viewport; } return Object.assign({}, viewport, { width,
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/responsive-ua.css @@ -0,0 +1,6 @@ +@namespace url(http://www.w3.org/1999/xhtml); + +/* Reset default UA styles for dropdown options */ +*|*::-moz-dropdown-list { + border: 0 !important; +}
--- a/devtools/client/responsive.html/test/browser/browser.ini +++ b/devtools/client/responsive.html/test/browser/browser.ini @@ -1,8 +1,9 @@ [DEFAULT] tags = devtools subsuite = devtools support-files = + browser_devices.json head.js [browser_exit_button.js] [browser_viewport_basics.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_devices.json @@ -0,0 +1,25 @@ +{ + "TYPES": [ "phones" ], + "phones": [ + { + "name": "Firefox OS Flame", + "width": 320, + "height": 570, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "Alcatel One Touch Fire", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; ALCATELOneTouch4012X; rv:28.0) Gecko/28.0 Firefox/28.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + ], +}
--- a/devtools/client/responsive.html/test/browser/head.js +++ b/devtools/client/responsive.html/test/browser/head.js @@ -9,18 +9,26 @@ Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js", this); +const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/"; + +DevToolsUtils.testing = true; +Services.prefs.setCharPref("devtools.devices.url", + TEST_URI_ROOT + "browser_devices.json"); Services.prefs.setBoolPref("devtools.responsive.html.enabled", true); + registerCleanupFunction(() => { + DevToolsUtils.testing = false; + Services.pref.clearUserPref("devtools.devices.url"); Services.prefs.clearUserPref("devtools.responsive.html.enabled"); }); const { ResponsiveUIManager } = Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {}); /** * Open responsive design mode for the given tab. */ var openRDM = Task.async(function*(tab) {
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/test/unit/test_add_device.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test adding a new device. + +const { + addDevice, + addDeviceType, +} = require("devtools/client/responsive.html/actions/devices"); + +add_task(function*() { + let store = Store(); + const { getState, dispatch } = store; + + let device = { + "name": "Firefox OS Flame", + "width": 320, + "height": 570, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }; + + dispatch(addDeviceType("phones")); + dispatch(addDevice(device, "phones")); + + equal(getState().devices.phones.length, 1, + "Correct number of phones"); + ok(getState().devices.phones.includes(device), + "Device phone list contains Firefox OS Flame"); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/test/unit/test_add_device_type.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test adding a new device type. + +const { addDeviceType } = + require("devtools/client/responsive.html/actions/devices"); + +add_task(function*() { + let store = Store(); + const { getState, dispatch } = store; + + dispatch(addDeviceType("phones")); + + equal(getState().devices.types.length, 1, "Correct number of device types"); + equal(getState().devices.phones.length, 0, + "Defaults to an empty array of phones"); + ok(getState().devices.types.includes("phones"), + "Device types contain phones"); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/test/unit/test_change_viewport_device.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test changing the viewport device. + +const { + addDevice, + addDeviceType, +} = require("devtools/client/responsive.html/actions/devices"); +const { + addViewport, + changeDevice, +} = require("devtools/client/responsive.html/actions/viewports"); + +add_task(function*() { + let store = Store(); + const { getState, dispatch } = store; + + dispatch(addDeviceType("phones")); + dispatch(addDevice({ + "name": "Firefox OS Flame", + "width": 320, + "height": 570, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, "phones")); + dispatch(addViewport()); + + let viewport = getState().viewports[0]; + equal(viewport.device, "", "Default device is unselected"); + + dispatch(changeDevice(0, "Firefox OS Flame")); + + viewport = getState().viewports[0]; + equal(viewport.device, "Firefox OS Flame", + "Changed to Firefox OS Flame device"); +});
--- a/devtools/client/responsive.html/test/unit/xpcshell.ini +++ b/devtools/client/responsive.html/test/unit/xpcshell.ini @@ -1,10 +1,13 @@ [DEFAULT] 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_viewport_device.js] [test_resize_viewport.js] [test_rotate_viewport.js]
--- a/devtools/client/responsive.html/types.js +++ b/devtools/client/responsive.html/types.js @@ -5,27 +5,86 @@ "use strict"; const { PropTypes } = require("devtools/client/shared/vendor/react"); // React PropTypes are used to describe the expected "shape" of various common // objects that get passed down as props to components. /** + * A single device that can be displayed in the viewport. + */ +const device = { + + // The name of the device + name: PropTypes.string, + + // The width of the device + width: PropTypes.number, + + // The height of the device + height: PropTypes.number, + + // The pixel ratio of the device + pixelRatio: PropTypes.number, + + // The user agent string of the device + userAgent: PropTypes.string, + + // Whether or not it is a touch device + touch: PropTypes.bool, + + // The operating system of the device + os: PropTypes.String, + +}; + +/** + * 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 + phones: PropTypes.arrayOf(PropTypes.shape(device)), + + // An array of tablet devices + tablets: PropTypes.arrayOf(PropTypes.shape(device)), + + // An array of laptop devices + laptops: PropTypes.arrayOf(PropTypes.shape(device)), + + // An array of television devices + televisions: PropTypes.arrayOf(PropTypes.shape(device)), + + // An array of console devices + consoles: PropTypes.arrayOf(PropTypes.shape(device)), + + // An array of watch devices + watches: PropTypes.arrayOf(PropTypes.shape(device)), + +}; + +/** + * The location of the document displayed in the viewport(s). + */ +exports.location = PropTypes.string; + +/** * A single viewport displaying a document. */ exports.viewport = { // The id of the viewport id: PropTypes.number.isRequired, + // The currently selected device applied to the viewport. + device: PropTypes.string, + // The width of the viewport width: PropTypes.number, // The height of the viewport height: PropTypes.number, }; - -/** - * The location of the document displayed in the viewport(s). - */ -exports.location = PropTypes.string;