From b15ec7f710d65353510c79f09a3157ab93f506b1 Mon Sep 17 00:00:00 2001 From: Micah Tigley Date: Wed, 22 May 2019 20:07:13 +0000 Subject: [PATCH] Bug 1357774 - Part 2: Add the ability to update orientation state of a simulated device in RDM r=gl This patch gives the RDM UI the ability to update the screen orientation based on the orientation of the simulated device screen. It fixes the following issues: - Initializing the orientation state of the selected device when RDM is opened. - Updating orientation state when the rotate button in the RDM toolbar is pressed. - Updating the orientation state when a new device is selected. There are three actions creators that are responsible for notifying the ResponsiveUI manager, `changeDevice`, `restoreDeviceState`, and `rotateViewport`. In particular: - `restoreDeviceState` is dispatched when the Responsive UI has finished initializing. If a previous RDM session had a device selected, then this action creator will also dispatch the `changeDevice` action to update the RDM UI to reflect the currently selected device. - `changeDevice` is dispatched when a device is selected. - `rotateViewport` is dispatched when the rotate button is clicked in the RDM toolbar. When either of these actions is dispatched, we post a "viewport-orientation-change" message to the window that notifies the manager to update the screen orientation accordingly. Finally, when RDM is closed, we need to ensure the original physical screen orientation is restored. We do this by calling the `setRDMPaneOrientation` on the docShell's document in the content frame script. Differential Revision: https://phabricator.services.mozilla.com/D30440 --HG-- extra : moz-landing-system : lando --- .../client/preferences/devtools-client.js | 2 + .../client/responsive.html/actions/devices.js | 6 +- .../client/responsive.html/actions/index.js | 3 + .../responsive.html/actions/viewports.js | 21 +++--- .../client/responsive.html/browser/content.js | 16 ++++ .../client/responsive.html/components/App.js | 57 ++++++++++++++ .../responsive.html/components/Browser.js | 16 ++++ .../components/ResizableViewport.js | 4 + .../responsive.html/components/Viewports.js | 3 + devtools/client/responsive.html/constants.js | 6 ++ devtools/client/responsive.html/manager.js | 52 ++++++++++--- .../responsive.html/reducers/viewports.js | 18 +++++ .../browser_orientationchange_event.js | 11 +++ .../client/responsive.html/utils/moz.build | 1 + .../responsive.html/utils/orientation.js | 75 +++++++++++++++++++ devtools/server/actors/emulation.js | 37 +++++++-- devtools/shared/specs/emulation.js | 6 +- 17 files changed, 304 insertions(+), 30 deletions(-) create mode 100644 devtools/client/responsive.html/utils/orientation.js diff --git a/devtools/client/preferences/devtools-client.js b/devtools/client/preferences/devtools-client.js index a8eeaf14319d..541ef963f86d 100644 --- a/devtools/client/preferences/devtools-client.js +++ b/devtools/client/preferences/devtools-client.js @@ -312,6 +312,8 @@ pref("devtools.editor.detectindentation", true); pref("devtools.editor.enableCodeFolding", true); pref("devtools.editor.autocomplete", true); +// The angle of the viewport. +pref("devtools.responsive.viewport.angle", 0); // The width of the viewport. pref("devtools.responsive.viewport.width", 320); // The height of the viewport. diff --git a/devtools/client/responsive.html/actions/devices.js b/devtools/client/responsive.html/actions/devices.js index b08ce7038eb4..6ce760c56eb0 100644 --- a/devtools/client/responsive.html/actions/devices.js +++ b/devtools/client/responsive.html/actions/devices.js @@ -22,7 +22,7 @@ const { post } = require("../utils/message"); const { addDevice, editDevice, getDevices, removeDevice } = require("devtools/client/shared/devices"); const { changeUserAgent, toggleTouchSimulation } = require("./ui"); -const { changeDevice, changePixelRatio } = require("./viewports"); +const { changeDevice, changePixelRatio, changeViewportAngle } = require("./viewports"); const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList"; @@ -113,6 +113,7 @@ module.exports = { post(window, { type: "change-device", device: newDevice, + viewport, }); // Update UI if the device is selected. @@ -212,13 +213,16 @@ module.exports = { return; } + const viewport = getState().viewports[0]; post(window, { type: "change-device", device, + viewport, }); dispatch(changeDevice(id, device.name, deviceType)); dispatch(changePixelRatio(id, device.pixelRatio)); + dispatch(changeViewportAngle(id, viewport.angle)); dispatch(changeUserAgent(device.userAgent)); dispatch(toggleTouchSimulation(device.touch)); }; diff --git a/devtools/client/responsive.html/actions/index.js b/devtools/client/responsive.html/actions/index.js index 0985265d74b5..726da95d6609 100644 --- a/devtools/client/responsive.html/actions/index.js +++ b/devtools/client/responsive.html/actions/index.js @@ -48,6 +48,9 @@ createEnum([ // selected from the device pixel ratio dropdown. "CHANGE_PIXEL_RATIO", + // Change the viewport angle. + "CHANGE_VIEWPORT_ANGLE", + // Edit a device. "EDIT_DEVICE", diff --git a/devtools/client/responsive.html/actions/viewports.js b/devtools/client/responsive.html/actions/viewports.js index 5925030b2769..6eaa5f163f6b 100644 --- a/devtools/client/responsive.html/actions/viewports.js +++ b/devtools/client/responsive.html/actions/viewports.js @@ -12,6 +12,7 @@ const { ADD_VIEWPORT, CHANGE_DEVICE, CHANGE_PIXEL_RATIO, + CHANGE_VIEWPORT_ANGLE, REMOVE_DEVICE_ASSOCIATION, RESIZE_VIEWPORT, ROTATE_VIEWPORT, @@ -63,6 +64,14 @@ module.exports = { }; }, + changeViewportAngle(id, angle) { + return { + type: CHANGE_VIEWPORT_ANGLE, + id, + angle, + }; + }, + /** * Remove the viewport's device assocation. */ @@ -95,18 +104,6 @@ module.exports = { * Rotate the viewport. */ rotateViewport(id) { - // TODO: Add `orientation` and `angle` properties to message data. See Bug 1357774. - - // There is no window object to post to when ran on XPCShell as part of the unit - // tests and will immediately throw an error as a result. - try { - post(window, { - type: "viewport-orientation-change", - }); - } catch (e) { - console.log("Unable to post message to window"); - } - return { type: ROTATE_VIEWPORT, id, diff --git a/devtools/client/responsive.html/browser/content.js b/devtools/client/responsive.html/browser/content.js index 21cf6c9863dd..9758a788457e 100644 --- a/devtools/client/responsive.html/browser/content.js +++ b/devtools/client/responsive.html/browser/content.js @@ -121,6 +121,11 @@ var global = this; .getInterface(Ci.nsIWebProgress); webProgress.removeProgressListener(WebProgressListener); docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize; + // Restore the original physical screen orientation values before RDM is stopped. + // This is necessary since the window document's `setCurrentRDMPaneOrientation` + // WebIDL operation can only modify the window's screen orientation values while the + // window content is in RDM. + restoreScreenOrientation(); restoreScrollbars(); setDocumentInRDMPane(false); stopOnResize(); @@ -165,6 +170,10 @@ var global = this; flushStyle(); } + function restoreScreenOrientation() { + docShell.contentViewer.DOMDocument.setRDMPaneOrientation("landscape-primary", 0); + } + function setDocumentInRDMPane(inRDMPane) { // We don't propegate this property to descendent documents. docShell.contentViewer.DOMDocument.inRDMPane = inRDMPane; @@ -199,6 +208,13 @@ var global = this; return; } setDocumentInRDMPane(true); + // Notify the Responsive UI manager to set orientation state on a location change. + // This is necessary since we want to ensure that the RDM Document's orientation + // state persists throughout while RDM is opened. + sendAsyncMessage("ResponsiveMode:OnLocationChange", { + width: content.innerWidth, + height: content.innerHeight, + }); makeScrollbarsFloating(); }, QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", diff --git a/devtools/client/responsive.html/components/App.js b/devtools/client/responsive.html/components/App.js index 35346ac23b61..ce13aa0b24e6 100644 --- a/devtools/client/responsive.html/components/App.js +++ b/devtools/client/responsive.html/components/App.js @@ -6,6 +6,7 @@ "use strict"; +const Services = require("Services"); const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); @@ -38,10 +39,12 @@ const { const { changeDevice, changePixelRatio, + changeViewportAngle, removeDeviceAssociation, resizeViewport, rotateViewport, } = require("../actions/viewports"); +const { getOrientation } = require("../utils/orientation"); const Types = require("../types"); @@ -67,6 +70,7 @@ class App extends PureComponent { this.onChangePixelRatio = this.onChangePixelRatio.bind(this); this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this); this.onChangeUserAgent = this.onChangeUserAgent.bind(this); + this.onChangeViewportOrientation = this.onChangeViewportOrientation.bind(this); this.onContentResize = this.onContentResize.bind(this); this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this); this.onEditCustomDevice = this.onEditCustomDevice.bind(this); @@ -114,7 +118,12 @@ class App extends PureComponent { window.postMessage({ type: "change-device", device, + viewport: this.props.viewports[id], }, "*"); + + const orientation = getOrientation(device, this.props.viewports[0]); + + this.props.dispatch(changeViewportAngle(0, orientation.angle)); this.props.dispatch(changeDevice(id, device.name, deviceType)); this.props.dispatch(changePixelRatio(id, device.pixelRatio)); this.props.dispatch(changeUserAgent(device.userAgent)); @@ -154,6 +163,15 @@ class App extends PureComponent { this.props.dispatch(changeUserAgent(userAgent)); } + onChangeViewportOrientation(id, { type, angle }) { + window.postMessage({ + type: "viewport-orientation-change", + orientationType: type, + angle, + }, "*"); + this.props.dispatch(changeViewportAngle(id, angle)); + } + onContentResize({ width, height }) { window.postMessage({ type: "content-resize", @@ -228,7 +246,44 @@ class App extends PureComponent { }, "*"); } + /** + * Dispatches the rotateViewport action creator. This utilized by the RDM toolbar as + * a prop. + * + * @param {Number} id + * The viewport ID. + */ onRotateViewport(id) { + let currentDevice; + const viewport = this.props.viewports[id]; + + for (const type of this.props.devices.types) { + for (const device of this.props.devices[type]) { + if (viewport.device === device.name) { + currentDevice = device; + } + } + } + + // If no device is selected, then assume the selected device's primary orientation is + // opposite of the viewport orientation. + if (!currentDevice) { + currentDevice = { + height: viewport.width, + width: viewport.height, + }; + } + + const currentAngle = Services.prefs.getIntPref("devtools.responsive.viewport.angle"); + // TODO: For Firefox Android, when the device is rotated clock-wise, the angle is + // updated to 270 degrees. This is valid since the user-agent determines whether it + // will be set to 90 or 270. However, we should update the angle based on the how the + // user-agent assigns the angle value. + // See https://w3c.github.io/screen-orientation/#dfn-screen-orientation-values-table + const angleToRotateTo = currentAngle === 270 ? 0 : 270; + const orientation = getOrientation(currentDevice, viewport, angleToRotateTo); + + this.onChangeViewportOrientation(id, orientation); this.props.dispatch(rotateViewport(id)); } @@ -276,6 +331,7 @@ class App extends PureComponent { onChangePixelRatio, onChangeTouchSimulation, onChangeUserAgent, + onChangeViewportOrientation, onContentResize, onDeviceListUpdate, onEditCustomDevice, @@ -335,6 +391,7 @@ class App extends PureComponent { screenshot, viewports, onBrowserMounted, + onChangeViewportOrientation, onContentResize, onRemoveDeviceAssociation, doResizeViewport, diff --git a/devtools/client/responsive.html/components/Browser.js b/devtools/client/responsive.html/components/Browser.js index 84964e58ef3c..43f8dec36aff 100644 --- a/devtools/client/responsive.html/components/Browser.js +++ b/devtools/client/responsive.html/components/Browser.js @@ -12,6 +12,7 @@ const { PureComponent } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); +const { PORTRAIT_PRIMARY, LANDSCAPE_PRIMARY } = require("../constants"); const e10s = require("../utils/e10s"); const message = require("../utils/message"); const { getTopLevelWindow } = require("../utils/window"); @@ -27,10 +28,12 @@ class Browser extends PureComponent { static get propTypes() { return { onBrowserMounted: PropTypes.func.isRequired, + onChangeViewportOrientation: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, swapAfterMount: PropTypes.bool.isRequired, userContextId: PropTypes.number.isRequired, + viewportId: PropTypes.number.isRequired, }; } @@ -38,6 +41,7 @@ class Browser extends PureComponent { super(props); this.onContentResize = this.onContentResize.bind(this); this.onResizeViewport = this.onResizeViewport.bind(this); + this.onSetScreenOrientation = this.onSetScreenOrientation.bind(this); } /** @@ -108,11 +112,20 @@ class Browser extends PureComponent { }); } + onSetScreenOrientation(msg) { + const { width, height } = msg.data; + const angle = Services.prefs.getIntPref("devtools.responsive.viewport.angle"); + const type = height >= width ? PORTRAIT_PRIMARY : LANDSCAPE_PRIMARY; + + this.props.onChangeViewportOrientation(this.props.viewportId, { type, angle }); + } + async startFrameScript() { const { browser, onContentResize, onResizeViewport, + onSetScreenOrientation, } = this; const mm = browser.frameLoader.messageManager; @@ -122,6 +135,7 @@ class Browser extends PureComponent { // resized to match. e10s.on(mm, "OnContentResize", onContentResize); e10s.on(mm, "OnResizeViewport", onResizeViewport); + e10s.on(mm, "OnLocationChange", onSetScreenOrientation); const ready = e10s.once(mm, "ChildScriptReady"); mm.loadFrameScript(FRAME_SCRIPT, true); @@ -143,11 +157,13 @@ class Browser extends PureComponent { browser, onContentResize, onResizeViewport, + onSetScreenOrientation, } = this; const mm = browser.frameLoader.messageManager; e10s.off(mm, "OnContentResize", onContentResize); e10s.off(mm, "OnResizeViewport", onResizeViewport); + e10s.off(mm, "OnLocationChange", onSetScreenOrientation); await e10s.request(mm, "Stop"); message.post(window, "stop-frame-script:done"); } diff --git a/devtools/client/responsive.html/components/ResizableViewport.js b/devtools/client/responsive.html/components/ResizableViewport.js index 9dd350832bcd..22ec9549c90a 100644 --- a/devtools/client/responsive.html/components/ResizableViewport.js +++ b/devtools/client/responsive.html/components/ResizableViewport.js @@ -23,6 +23,7 @@ class ResizableViewport extends PureComponent { return { leftAlignmentEnabled: PropTypes.bool.isRequired, onBrowserMounted: PropTypes.func.isRequired, + onChangeViewportOrientation: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired, onRemoveDeviceAssociation: PropTypes.func.isRequired, doResizeViewport: PropTypes.func.isRequired, @@ -150,6 +151,7 @@ class ResizableViewport extends PureComponent { swapAfterMount, viewport, onBrowserMounted, + onChangeViewportOrientation, onContentResize, onResizeViewport, } = this.props; @@ -178,7 +180,9 @@ class ResizableViewport extends PureComponent { Browser({ swapAfterMount, userContextId: viewport.userContextId, + viewportId: viewport.id, onBrowserMounted, + onChangeViewportOrientation, onContentResize, onResizeViewport, }) diff --git a/devtools/client/responsive.html/components/Viewports.js b/devtools/client/responsive.html/components/Viewports.js index 40ac7825e326..6f18f2fcf0d6 100644 --- a/devtools/client/responsive.html/components/Viewports.js +++ b/devtools/client/responsive.html/components/Viewports.js @@ -18,6 +18,7 @@ class Viewports extends PureComponent { return { leftAlignmentEnabled: PropTypes.bool.isRequired, onBrowserMounted: PropTypes.func.isRequired, + onChangeViewportOrientation: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired, onRemoveDeviceAssociation: PropTypes.func.isRequired, doResizeViewport: PropTypes.func.isRequired, @@ -31,6 +32,7 @@ class Viewports extends PureComponent { const { leftAlignmentEnabled, onBrowserMounted, + onChangeViewportOrientation, onContentResize, onRemoveDeviceAssociation, doResizeViewport, @@ -71,6 +73,7 @@ class Viewports extends PureComponent { key: viewport.id, leftAlignmentEnabled, onBrowserMounted, + onChangeViewportOrientation, onContentResize, onRemoveDeviceAssociation, doResizeViewport, diff --git a/devtools/client/responsive.html/constants.js b/devtools/client/responsive.html/constants.js index 81869b3f7747..a01cd69f5148 100644 --- a/devtools/client/responsive.html/constants.js +++ b/devtools/client/responsive.html/constants.js @@ -6,3 +6,9 @@ // The minimum viewport width and height exports.MIN_VIEWPORT_DIMENSION = 50; + +// Orientation types +exports.PORTRAIT_PRIMARY = "portrait-primary"; +exports.PORTRAIT_SECONDARY = "portrait-secondary"; +exports.LANDSCAPE_PRIMARY = "landscape-primary"; +exports.LANDSCAPE_SECONDARY = "landscape-secondary"; diff --git a/devtools/client/responsive.html/manager.js b/devtools/client/responsive.html/manager.js index 362de527adf5..b40225bd5fa8 100644 --- a/devtools/client/responsive.html/manager.js +++ b/devtools/client/responsive.html/manager.js @@ -8,6 +8,7 @@ const { Ci } = require("chrome"); const promise = require("promise"); const Services = require("Services"); const EventEmitter = require("devtools/shared/event-emitter"); +const { getOrientation } = require("./utils/orientation"); loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/debugger-client", true); loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true); @@ -582,9 +583,12 @@ ResponsiveUI.prototype = { }, async onChangeDevice(event) { - const { userAgent, pixelRatio, touch } = event.data.device; + const { device, viewport } = event.data; + const { pixelRatio, touch, userAgent } = event.data.device; + let reloadNeeded = false; await this.updateDPPX(pixelRatio); + await this.updateScreenOrientation(getOrientation(device, viewport), true); reloadNeeded |= await this.updateUserAgent(userAgent) && this.reloadOnChange("userAgent"); reloadNeeded |= await this.updateTouchSimulation(touch) && @@ -665,15 +669,8 @@ ResponsiveUI.prototype = { }, async onRotateViewport(event) { - const targetFront = await this.client.mainRoot.getTab(); - - // Ensure that simulateScreenOrientationChange is supported. - if (await targetFront.actorHasMethod("emulation", - "simulateScreenOrientationChange")) { - // TODO: From event.data, pass orientation and angle as arguments. - // See Bug 1357774. - await this.emulationFront.simulateScreenOrientationChange(); - } + const { orientationType: type, angle } = event.data; + await this.updateScreenOrientation({ type, angle }, false); }, /** @@ -687,15 +684,21 @@ ResponsiveUI.prototype = { return; } + const height = + Services.prefs.getIntPref("devtools.responsive.viewport.height", 0); const pixelRatio = Services.prefs.getIntPref("devtools.responsive.viewport.pixelRatio", 0); const touchSimulationEnabled = Services.prefs.getBoolPref("devtools.responsive.touchSimulation.enabled", false); const userAgent = Services.prefs.getCharPref("devtools.responsive.userAgent", ""); + const width = + Services.prefs.getIntPref("devtools.responsive.viewport.width", 0); let reloadNeeded = false; + const viewportOrientation = this.getInitialViewportOrientation({ width, height }); await this.updateDPPX(pixelRatio); + await this.updateScreenOrientation(viewportOrientation, true); if (touchSimulationEnabled) { reloadNeeded |= await this.updateTouchSimulation(touchSimulationEnabled) && @@ -791,6 +794,29 @@ ResponsiveUI.prototype = { return reloadNeeded; }, + /** + * Sets the screen orientation values of the simulated device. + * + * @param {Object} orientation + * The orientation to update the current device screen to. + * @param {Boolean} changeDevice + * Whether or not the reason for updating the screen orientation is a result + * of actually rotating the device via the RDM toolbar or if the user switched to + * another device. + */ + async updateScreenOrientation(orientation, changeDevice = false) { + const targetFront = await this.client.mainRoot.getTab(); + const simulateOrientationChangeSupported = + await targetFront.actorHasMethod("emulation", "simulateScreenOrientationChange"); + + // Ensure that simulateScreenOrientationChange is supported. + if (simulateOrientationChangeSupported) { + const { type, angle } = orientation; + await this.emulationFront.simulateScreenOrientationChange(type, angle, + changeDevice); + } + }, + /** * Helper for tests. Assumes a single viewport for now. */ @@ -820,6 +846,12 @@ ResponsiveUI.prototype = { return this.getViewportBrowser().messageManager; }, + /** + * Helper for getting the initial viewport orientation. + */ + getInitialViewportOrientation(viewport) { + return getOrientation(viewport, viewport); + }, }; EventEmitter.decorate(ResponsiveUI.prototype); diff --git a/devtools/client/responsive.html/reducers/viewports.js b/devtools/client/responsive.html/reducers/viewports.js index f290c763eade..e950b2d6056d 100644 --- a/devtools/client/responsive.html/reducers/viewports.js +++ b/devtools/client/responsive.html/reducers/viewports.js @@ -10,6 +10,7 @@ const { ADD_VIEWPORT, CHANGE_DEVICE, CHANGE_PIXEL_RATIO, + CHANGE_VIEWPORT_ANGLE, EDIT_DEVICE, REMOVE_DEVICE_ASSOCIATION, RESIZE_VIEWPORT, @@ -19,12 +20,14 @@ const { const VIEWPORT_WIDTH_PREF = "devtools.responsive.viewport.width"; const VIEWPORT_HEIGHT_PREF = "devtools.responsive.viewport.height"; const VIEWPORT_PIXEL_RATIO_PREF = "devtools.responsive.viewport.pixelRatio"; +const VIEWPORT_ANGLE_PREF = "devtools.responsive.viewport.angle"; let nextViewportId = 0; const INITIAL_VIEWPORTS = []; const INITIAL_VIEWPORT = { id: nextViewportId++, + angle: Services.prefs.getIntPref(VIEWPORT_ANGLE_PREF, 0), device: "", deviceType: "", height: Services.prefs.getIntPref(VIEWPORT_HEIGHT_PREF, 480), @@ -79,6 +82,21 @@ const reducers = { }); }, + [CHANGE_VIEWPORT_ANGLE](viewports, { id, angle }) { + return viewports.map(viewport => { + if (viewport.id !== id) { + return viewport; + } + + Services.prefs.setIntPref(VIEWPORT_ANGLE_PREF, angle); + + return { + ...viewport, + angle, + }; + }); + }, + [EDIT_DEVICE](viewports, { viewport, newDevice, deviceType }) { if (!viewport) { return viewports; diff --git a/devtools/client/responsive.html/test/browser/browser_orientationchange_event.js b/devtools/client/responsive.html/test/browser/browser_orientationchange_event.js index e43a9f5f656d..ff778352c7d2 100644 --- a/devtools/client/responsive.html/test/browser/browser_orientationchange_event.js +++ b/devtools/client/responsive.html/test/browser/browser_orientationchange_event.js @@ -9,13 +9,24 @@ const TEST_URL = "data:text/html;charset=utf-8,"; addRDMTask(TEST_URL, async function({ ui }) { info("Rotate viewport to trigger 'orientationchange' event."); + await pushPref("devtools.responsive.viewport.angle", 0); rotateViewport(ui); await ContentTask.spawn(ui.getViewportBrowser(), {}, async function() { + info("Check the original orientation values before the orientationchange"); + is(content.screen.orientation.type, "portrait-primary", + "Primary orientation type is portrait-primary."); + is(content.screen.orientation.angle, 0, + "Original angle is set at 0 degrees"); + const orientationChange = new Promise(resolve => { content.window.addEventListener("orientationchange", () => { ok(true, "'orientationchange' event fired"); + is(content.screen.orientation.type, "landscape-primary", + "Orientation state was updated to landscape-primary"); + is(content.screen.orientation.angle, 270, + "Orientation angle was updated to 270 degrees."); resolve(); }); }); diff --git a/devtools/client/responsive.html/utils/moz.build b/devtools/client/responsive.html/utils/moz.build index ce1afc425261..e9b7de76df30 100644 --- a/devtools/client/responsive.html/utils/moz.build +++ b/devtools/client/responsive.html/utils/moz.build @@ -10,5 +10,6 @@ DevToolsModules( 'l10n.js', 'message.js', 'notification.js', + 'orientation.js', 'window.js', ) diff --git a/devtools/client/responsive.html/utils/orientation.js b/devtools/client/responsive.html/utils/orientation.js new file mode 100644 index 000000000000..a3c9c993f4fc --- /dev/null +++ b/devtools/client/responsive.html/utils/orientation.js @@ -0,0 +1,75 @@ +/* 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 { PORTRAIT_PRIMARY, LANDSCAPE_PRIMARY } = require("../constants"); + +/** + * Helper that gets the screen orientation of the device displayed in the RDM viewport. + * This function take in both a device and viewport object and an optional rotated angle. + * If a rotated angle is passed, then we calculate what the orientation type of the device + * would be in relation to its current orientation. Otherwise, return the current + * orientation and angle. + * + * @param {Object} device + * The device whose content is displayed in the viewport. Used to determine the + * primary orientation. + * @param {Object} viewport + * The viewport displaying device content. Used to determine the current + * orientation type of the device while in RDM. + * @param {Number|null} angleToRotateTo + * Optional. The rotated angle specifies the degree to which the device WILL be + * turned to. If undefined, then only return the current orientation and angle + * of the device. + * @return {Object} the orientation of the device. + */ +function getOrientation(device, viewport, angleToRotateTo = null) { + const { width: deviceWidth, height: deviceHeight } = device; + const { width: viewportWidth, height: viewportHeight } = viewport; + + // Determine the primary orientation of the device screen. + const primaryOrientation = deviceHeight >= deviceWidth ? + PORTRAIT_PRIMARY : + LANDSCAPE_PRIMARY; + + // Determine the current orientation of the device screen. + const currentOrientation = viewportHeight >= viewportWidth ? + PORTRAIT_PRIMARY : + LANDSCAPE_PRIMARY; + + // Calculate the orientation angle of the device. + let angle; + + if (typeof angleToRotateTo === "number") { + angle = angleToRotateTo; + } else if (currentOrientation !== primaryOrientation) { + angle = 270; + } else { + angle = 0; + } + + // Calculate the orientation type of the device. + let orientationType = currentOrientation; + + // If the viewport orientation is different from the primary orientation and the angle + // to rotate to is 0, then we are moving the device orientation back to its primary + // orientation. + if (currentOrientation !== primaryOrientation && angleToRotateTo === 0) { + orientationType = primaryOrientation; + } else if (angleToRotateTo === 90 || angleToRotateTo === 270) { + if (currentOrientation.includes("portrait")) { + orientationType = LANDSCAPE_PRIMARY; + } else if (currentOrientation.includes("landscape")) { + orientationType = PORTRAIT_PRIMARY; + } + } + + return { + type: orientationType, + angle, + }; +} + +exports.getOrientation = getOrientation; diff --git a/devtools/server/actors/emulation.js b/devtools/server/actors/emulation.js index 2550f212fcad..8f38709358cd 100644 --- a/devtools/server/actors/emulation.js +++ b/devtools/server/actors/emulation.js @@ -76,6 +76,10 @@ const EmulationActor = protocol.ActorClassWithSpec(emulationSpec, { return this._touchSimulator; }, + get win() { + return this.docShell.chromeEventHandler.ownerGlobal; + }, + onWillNavigate({ isTopLevel }) { // Make sure that print simulation is stopped before navigating to another page. We // need to do this since the browser will cache the last state of the page in its @@ -352,17 +356,38 @@ const EmulationActor = protocol.ActorClassWithSpec(emulationSpec, { this.targetActor.docShell.contentViewer.stopEmulatingMedium(); }, + setScreenOrientation(type, angle) { + if (this.win.screen.orientation.angle !== angle || + this.win.screen.orientation.type !== type) { + this.win.document.setRDMPaneOrientation(type, angle); + } + }, + /** * Simulates the "orientationchange" event when device screen is rotated. * - * TODO: Update `window.screen.orientation` and `window.screen.angle` here. - * See Bug 1357774. + * @param {String} type + * The orientation type of the rotated device. + * @param {Number} angle + * The rotated angle of the device. + * @param {Boolean} deviceChange + * Whether or not screen orientation change is a result of changing the device + * or rotating the current device. If the latter, then dispatch the + * "orientationchange" event on the content window. */ - simulateScreenOrientationChange() { - const win = this.docShell.chromeEventHandler.ownerGlobal; - const { CustomEvent } = win; + async simulateScreenOrientationChange(type, angle, deviceChange) { + // Don't dispatch the "orientationchange" event if orientation change is a result + // of switching to a new device. + if (deviceChange) { + this.setScreenOrientation(type, angle); + return; + } + + const { CustomEvent } = this.win; const orientationChangeEvent = new CustomEvent("orientationchange"); - win.dispatchEvent(orientationChangeEvent); + + this.setScreenOrientation(type, angle); + this.win.dispatchEvent(orientationChangeEvent); }, }); diff --git a/devtools/shared/specs/emulation.js b/devtools/shared/specs/emulation.js index 7bd57c10daee..dec9db9996d7 100644 --- a/devtools/shared/specs/emulation.js +++ b/devtools/shared/specs/emulation.js @@ -151,7 +151,11 @@ const emulationSpec = generateActorSpec({ }, simulateScreenOrientationChange: { - request: {}, + request: { + orientation: Arg(0, "string"), + angle: Arg(1, "number"), + deviceChange: Arg(2, "boolean"), + }, response: {}, }, },