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
This commit is contained in:
Micah Tigley 2019-05-22 20:07:13 +00:00
Родитель 0f8cee5dd5
Коммит b15ec7f710
17 изменённых файлов: 304 добавлений и 30 удалений

Просмотреть файл

@ -312,6 +312,8 @@ pref("devtools.editor.detectindentation", true);
pref("devtools.editor.enableCodeFolding", true); pref("devtools.editor.enableCodeFolding", true);
pref("devtools.editor.autocomplete", true); pref("devtools.editor.autocomplete", true);
// The angle of the viewport.
pref("devtools.responsive.viewport.angle", 0);
// The width of the viewport. // The width of the viewport.
pref("devtools.responsive.viewport.width", 320); pref("devtools.responsive.viewport.width", 320);
// The height of the viewport. // The height of the viewport.

Просмотреть файл

@ -22,7 +22,7 @@ const { post } = require("../utils/message");
const { addDevice, editDevice, getDevices, removeDevice } = require("devtools/client/shared/devices"); const { addDevice, editDevice, getDevices, removeDevice } = require("devtools/client/shared/devices");
const { changeUserAgent, toggleTouchSimulation } = require("./ui"); const { changeUserAgent, toggleTouchSimulation } = require("./ui");
const { changeDevice, changePixelRatio } = require("./viewports"); const { changeDevice, changePixelRatio, changeViewportAngle } = require("./viewports");
const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList"; const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
@ -113,6 +113,7 @@ module.exports = {
post(window, { post(window, {
type: "change-device", type: "change-device",
device: newDevice, device: newDevice,
viewport,
}); });
// Update UI if the device is selected. // Update UI if the device is selected.
@ -212,13 +213,16 @@ module.exports = {
return; return;
} }
const viewport = getState().viewports[0];
post(window, { post(window, {
type: "change-device", type: "change-device",
device, device,
viewport,
}); });
dispatch(changeDevice(id, device.name, deviceType)); dispatch(changeDevice(id, device.name, deviceType));
dispatch(changePixelRatio(id, device.pixelRatio)); dispatch(changePixelRatio(id, device.pixelRatio));
dispatch(changeViewportAngle(id, viewport.angle));
dispatch(changeUserAgent(device.userAgent)); dispatch(changeUserAgent(device.userAgent));
dispatch(toggleTouchSimulation(device.touch)); dispatch(toggleTouchSimulation(device.touch));
}; };

Просмотреть файл

@ -48,6 +48,9 @@ createEnum([
// selected from the device pixel ratio dropdown. // selected from the device pixel ratio dropdown.
"CHANGE_PIXEL_RATIO", "CHANGE_PIXEL_RATIO",
// Change the viewport angle.
"CHANGE_VIEWPORT_ANGLE",
// Edit a device. // Edit a device.
"EDIT_DEVICE", "EDIT_DEVICE",

Просмотреть файл

@ -12,6 +12,7 @@ const {
ADD_VIEWPORT, ADD_VIEWPORT,
CHANGE_DEVICE, CHANGE_DEVICE,
CHANGE_PIXEL_RATIO, CHANGE_PIXEL_RATIO,
CHANGE_VIEWPORT_ANGLE,
REMOVE_DEVICE_ASSOCIATION, REMOVE_DEVICE_ASSOCIATION,
RESIZE_VIEWPORT, RESIZE_VIEWPORT,
ROTATE_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. * Remove the viewport's device assocation.
*/ */
@ -95,18 +104,6 @@ module.exports = {
* Rotate the viewport. * Rotate the viewport.
*/ */
rotateViewport(id) { 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 { return {
type: ROTATE_VIEWPORT, type: ROTATE_VIEWPORT,
id, id,

Просмотреть файл

@ -121,6 +121,11 @@ var global = this;
.getInterface(Ci.nsIWebProgress); .getInterface(Ci.nsIWebProgress);
webProgress.removeProgressListener(WebProgressListener); webProgress.removeProgressListener(WebProgressListener);
docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize; 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(); restoreScrollbars();
setDocumentInRDMPane(false); setDocumentInRDMPane(false);
stopOnResize(); stopOnResize();
@ -165,6 +170,10 @@ var global = this;
flushStyle(); flushStyle();
} }
function restoreScreenOrientation() {
docShell.contentViewer.DOMDocument.setRDMPaneOrientation("landscape-primary", 0);
}
function setDocumentInRDMPane(inRDMPane) { function setDocumentInRDMPane(inRDMPane) {
// We don't propegate this property to descendent documents. // We don't propegate this property to descendent documents.
docShell.contentViewer.DOMDocument.inRDMPane = inRDMPane; docShell.contentViewer.DOMDocument.inRDMPane = inRDMPane;
@ -199,6 +208,13 @@ var global = this;
return; return;
} }
setDocumentInRDMPane(true); 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(); makeScrollbarsFloating();
}, },
QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener",

Просмотреть файл

@ -6,6 +6,7 @@
"use strict"; "use strict";
const Services = require("Services");
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react"); const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories"); const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
@ -38,10 +39,12 @@ const {
const { const {
changeDevice, changeDevice,
changePixelRatio, changePixelRatio,
changeViewportAngle,
removeDeviceAssociation, removeDeviceAssociation,
resizeViewport, resizeViewport,
rotateViewport, rotateViewport,
} = require("../actions/viewports"); } = require("../actions/viewports");
const { getOrientation } = require("../utils/orientation");
const Types = require("../types"); const Types = require("../types");
@ -67,6 +70,7 @@ class App extends PureComponent {
this.onChangePixelRatio = this.onChangePixelRatio.bind(this); this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this); this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
this.onChangeUserAgent = this.onChangeUserAgent.bind(this); this.onChangeUserAgent = this.onChangeUserAgent.bind(this);
this.onChangeViewportOrientation = this.onChangeViewportOrientation.bind(this);
this.onContentResize = this.onContentResize.bind(this); this.onContentResize = this.onContentResize.bind(this);
this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this); this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
this.onEditCustomDevice = this.onEditCustomDevice.bind(this); this.onEditCustomDevice = this.onEditCustomDevice.bind(this);
@ -114,7 +118,12 @@ class App extends PureComponent {
window.postMessage({ window.postMessage({
type: "change-device", type: "change-device",
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(changeDevice(id, device.name, deviceType));
this.props.dispatch(changePixelRatio(id, device.pixelRatio)); this.props.dispatch(changePixelRatio(id, device.pixelRatio));
this.props.dispatch(changeUserAgent(device.userAgent)); this.props.dispatch(changeUserAgent(device.userAgent));
@ -154,6 +163,15 @@ class App extends PureComponent {
this.props.dispatch(changeUserAgent(userAgent)); 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 }) { onContentResize({ width, height }) {
window.postMessage({ window.postMessage({
type: "content-resize", 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) { 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)); this.props.dispatch(rotateViewport(id));
} }
@ -276,6 +331,7 @@ class App extends PureComponent {
onChangePixelRatio, onChangePixelRatio,
onChangeTouchSimulation, onChangeTouchSimulation,
onChangeUserAgent, onChangeUserAgent,
onChangeViewportOrientation,
onContentResize, onContentResize,
onDeviceListUpdate, onDeviceListUpdate,
onEditCustomDevice, onEditCustomDevice,
@ -335,6 +391,7 @@ class App extends PureComponent {
screenshot, screenshot,
viewports, viewports,
onBrowserMounted, onBrowserMounted,
onChangeViewportOrientation,
onContentResize, onContentResize,
onRemoveDeviceAssociation, onRemoveDeviceAssociation,
doResizeViewport, doResizeViewport,

Просмотреть файл

@ -12,6 +12,7 @@ const { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories"); const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { PORTRAIT_PRIMARY, LANDSCAPE_PRIMARY } = require("../constants");
const e10s = require("../utils/e10s"); const e10s = require("../utils/e10s");
const message = require("../utils/message"); const message = require("../utils/message");
const { getTopLevelWindow } = require("../utils/window"); const { getTopLevelWindow } = require("../utils/window");
@ -27,10 +28,12 @@ class Browser extends PureComponent {
static get propTypes() { static get propTypes() {
return { return {
onBrowserMounted: PropTypes.func.isRequired, onBrowserMounted: PropTypes.func.isRequired,
onChangeViewportOrientation: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired,
swapAfterMount: PropTypes.bool.isRequired, swapAfterMount: PropTypes.bool.isRequired,
userContextId: PropTypes.number.isRequired, userContextId: PropTypes.number.isRequired,
viewportId: PropTypes.number.isRequired,
}; };
} }
@ -38,6 +41,7 @@ class Browser extends PureComponent {
super(props); super(props);
this.onContentResize = this.onContentResize.bind(this); this.onContentResize = this.onContentResize.bind(this);
this.onResizeViewport = this.onResizeViewport.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() { async startFrameScript() {
const { const {
browser, browser,
onContentResize, onContentResize,
onResizeViewport, onResizeViewport,
onSetScreenOrientation,
} = this; } = this;
const mm = browser.frameLoader.messageManager; const mm = browser.frameLoader.messageManager;
@ -122,6 +135,7 @@ class Browser extends PureComponent {
// resized to match. // resized to match.
e10s.on(mm, "OnContentResize", onContentResize); e10s.on(mm, "OnContentResize", onContentResize);
e10s.on(mm, "OnResizeViewport", onResizeViewport); e10s.on(mm, "OnResizeViewport", onResizeViewport);
e10s.on(mm, "OnLocationChange", onSetScreenOrientation);
const ready = e10s.once(mm, "ChildScriptReady"); const ready = e10s.once(mm, "ChildScriptReady");
mm.loadFrameScript(FRAME_SCRIPT, true); mm.loadFrameScript(FRAME_SCRIPT, true);
@ -143,11 +157,13 @@ class Browser extends PureComponent {
browser, browser,
onContentResize, onContentResize,
onResizeViewport, onResizeViewport,
onSetScreenOrientation,
} = this; } = this;
const mm = browser.frameLoader.messageManager; const mm = browser.frameLoader.messageManager;
e10s.off(mm, "OnContentResize", onContentResize); e10s.off(mm, "OnContentResize", onContentResize);
e10s.off(mm, "OnResizeViewport", onResizeViewport); e10s.off(mm, "OnResizeViewport", onResizeViewport);
e10s.off(mm, "OnLocationChange", onSetScreenOrientation);
await e10s.request(mm, "Stop"); await e10s.request(mm, "Stop");
message.post(window, "stop-frame-script:done"); message.post(window, "stop-frame-script:done");
} }

Просмотреть файл

@ -23,6 +23,7 @@ class ResizableViewport extends PureComponent {
return { return {
leftAlignmentEnabled: PropTypes.bool.isRequired, leftAlignmentEnabled: PropTypes.bool.isRequired,
onBrowserMounted: PropTypes.func.isRequired, onBrowserMounted: PropTypes.func.isRequired,
onChangeViewportOrientation: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired,
onRemoveDeviceAssociation: PropTypes.func.isRequired, onRemoveDeviceAssociation: PropTypes.func.isRequired,
doResizeViewport: PropTypes.func.isRequired, doResizeViewport: PropTypes.func.isRequired,
@ -150,6 +151,7 @@ class ResizableViewport extends PureComponent {
swapAfterMount, swapAfterMount,
viewport, viewport,
onBrowserMounted, onBrowserMounted,
onChangeViewportOrientation,
onContentResize, onContentResize,
onResizeViewport, onResizeViewport,
} = this.props; } = this.props;
@ -178,7 +180,9 @@ class ResizableViewport extends PureComponent {
Browser({ Browser({
swapAfterMount, swapAfterMount,
userContextId: viewport.userContextId, userContextId: viewport.userContextId,
viewportId: viewport.id,
onBrowserMounted, onBrowserMounted,
onChangeViewportOrientation,
onContentResize, onContentResize,
onResizeViewport, onResizeViewport,
}) })

Просмотреть файл

@ -18,6 +18,7 @@ class Viewports extends PureComponent {
return { return {
leftAlignmentEnabled: PropTypes.bool.isRequired, leftAlignmentEnabled: PropTypes.bool.isRequired,
onBrowserMounted: PropTypes.func.isRequired, onBrowserMounted: PropTypes.func.isRequired,
onChangeViewportOrientation: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired,
onRemoveDeviceAssociation: PropTypes.func.isRequired, onRemoveDeviceAssociation: PropTypes.func.isRequired,
doResizeViewport: PropTypes.func.isRequired, doResizeViewport: PropTypes.func.isRequired,
@ -31,6 +32,7 @@ class Viewports extends PureComponent {
const { const {
leftAlignmentEnabled, leftAlignmentEnabled,
onBrowserMounted, onBrowserMounted,
onChangeViewportOrientation,
onContentResize, onContentResize,
onRemoveDeviceAssociation, onRemoveDeviceAssociation,
doResizeViewport, doResizeViewport,
@ -71,6 +73,7 @@ class Viewports extends PureComponent {
key: viewport.id, key: viewport.id,
leftAlignmentEnabled, leftAlignmentEnabled,
onBrowserMounted, onBrowserMounted,
onChangeViewportOrientation,
onContentResize, onContentResize,
onRemoveDeviceAssociation, onRemoveDeviceAssociation,
doResizeViewport, doResizeViewport,

Просмотреть файл

@ -6,3 +6,9 @@
// The minimum viewport width and height // The minimum viewport width and height
exports.MIN_VIEWPORT_DIMENSION = 50; 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";

Просмотреть файл

@ -8,6 +8,7 @@ const { Ci } = require("chrome");
const promise = require("promise"); const promise = require("promise");
const Services = require("Services"); const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter"); 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, "DebuggerClient", "devtools/shared/client/debugger-client", true);
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true); loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
@ -582,9 +583,12 @@ ResponsiveUI.prototype = {
}, },
async onChangeDevice(event) { 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; let reloadNeeded = false;
await this.updateDPPX(pixelRatio); await this.updateDPPX(pixelRatio);
await this.updateScreenOrientation(getOrientation(device, viewport), true);
reloadNeeded |= await this.updateUserAgent(userAgent) && reloadNeeded |= await this.updateUserAgent(userAgent) &&
this.reloadOnChange("userAgent"); this.reloadOnChange("userAgent");
reloadNeeded |= await this.updateTouchSimulation(touch) && reloadNeeded |= await this.updateTouchSimulation(touch) &&
@ -665,15 +669,8 @@ ResponsiveUI.prototype = {
}, },
async onRotateViewport(event) { async onRotateViewport(event) {
const targetFront = await this.client.mainRoot.getTab(); const { orientationType: type, angle } = event.data;
await this.updateScreenOrientation({ type, angle }, false);
// 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();
}
}, },
/** /**
@ -687,15 +684,21 @@ ResponsiveUI.prototype = {
return; return;
} }
const height =
Services.prefs.getIntPref("devtools.responsive.viewport.height", 0);
const pixelRatio = const pixelRatio =
Services.prefs.getIntPref("devtools.responsive.viewport.pixelRatio", 0); Services.prefs.getIntPref("devtools.responsive.viewport.pixelRatio", 0);
const touchSimulationEnabled = const touchSimulationEnabled =
Services.prefs.getBoolPref("devtools.responsive.touchSimulation.enabled", false); Services.prefs.getBoolPref("devtools.responsive.touchSimulation.enabled", false);
const userAgent = Services.prefs.getCharPref("devtools.responsive.userAgent", ""); const userAgent = Services.prefs.getCharPref("devtools.responsive.userAgent", "");
const width =
Services.prefs.getIntPref("devtools.responsive.viewport.width", 0);
let reloadNeeded = false; let reloadNeeded = false;
const viewportOrientation = this.getInitialViewportOrientation({ width, height });
await this.updateDPPX(pixelRatio); await this.updateDPPX(pixelRatio);
await this.updateScreenOrientation(viewportOrientation, true);
if (touchSimulationEnabled) { if (touchSimulationEnabled) {
reloadNeeded |= await this.updateTouchSimulation(touchSimulationEnabled) && reloadNeeded |= await this.updateTouchSimulation(touchSimulationEnabled) &&
@ -791,6 +794,29 @@ ResponsiveUI.prototype = {
return reloadNeeded; 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. * Helper for tests. Assumes a single viewport for now.
*/ */
@ -820,6 +846,12 @@ ResponsiveUI.prototype = {
return this.getViewportBrowser().messageManager; return this.getViewportBrowser().messageManager;
}, },
/**
* Helper for getting the initial viewport orientation.
*/
getInitialViewportOrientation(viewport) {
return getOrientation(viewport, viewport);
},
}; };
EventEmitter.decorate(ResponsiveUI.prototype); EventEmitter.decorate(ResponsiveUI.prototype);

Просмотреть файл

@ -10,6 +10,7 @@ const {
ADD_VIEWPORT, ADD_VIEWPORT,
CHANGE_DEVICE, CHANGE_DEVICE,
CHANGE_PIXEL_RATIO, CHANGE_PIXEL_RATIO,
CHANGE_VIEWPORT_ANGLE,
EDIT_DEVICE, EDIT_DEVICE,
REMOVE_DEVICE_ASSOCIATION, REMOVE_DEVICE_ASSOCIATION,
RESIZE_VIEWPORT, RESIZE_VIEWPORT,
@ -19,12 +20,14 @@ const {
const VIEWPORT_WIDTH_PREF = "devtools.responsive.viewport.width"; const VIEWPORT_WIDTH_PREF = "devtools.responsive.viewport.width";
const VIEWPORT_HEIGHT_PREF = "devtools.responsive.viewport.height"; const VIEWPORT_HEIGHT_PREF = "devtools.responsive.viewport.height";
const VIEWPORT_PIXEL_RATIO_PREF = "devtools.responsive.viewport.pixelRatio"; const VIEWPORT_PIXEL_RATIO_PREF = "devtools.responsive.viewport.pixelRatio";
const VIEWPORT_ANGLE_PREF = "devtools.responsive.viewport.angle";
let nextViewportId = 0; let nextViewportId = 0;
const INITIAL_VIEWPORTS = []; const INITIAL_VIEWPORTS = [];
const INITIAL_VIEWPORT = { const INITIAL_VIEWPORT = {
id: nextViewportId++, id: nextViewportId++,
angle: Services.prefs.getIntPref(VIEWPORT_ANGLE_PREF, 0),
device: "", device: "",
deviceType: "", deviceType: "",
height: Services.prefs.getIntPref(VIEWPORT_HEIGHT_PREF, 480), 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 }) { [EDIT_DEVICE](viewports, { viewport, newDevice, deviceType }) {
if (!viewport) { if (!viewport) {
return viewports; return viewports;

Просмотреть файл

@ -9,13 +9,24 @@ const TEST_URL = "data:text/html;charset=utf-8,";
addRDMTask(TEST_URL, async function({ ui }) { addRDMTask(TEST_URL, async function({ ui }) {
info("Rotate viewport to trigger 'orientationchange' event."); info("Rotate viewport to trigger 'orientationchange' event.");
await pushPref("devtools.responsive.viewport.angle", 0);
rotateViewport(ui); rotateViewport(ui);
await ContentTask.spawn(ui.getViewportBrowser(), {}, await ContentTask.spawn(ui.getViewportBrowser(), {},
async function() { 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 => { const orientationChange = new Promise(resolve => {
content.window.addEventListener("orientationchange", () => { content.window.addEventListener("orientationchange", () => {
ok(true, "'orientationchange' event fired"); 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(); resolve();
}); });
}); });

Просмотреть файл

@ -10,5 +10,6 @@ DevToolsModules(
'l10n.js', 'l10n.js',
'message.js', 'message.js',
'notification.js', 'notification.js',
'orientation.js',
'window.js', 'window.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;

Просмотреть файл

@ -76,6 +76,10 @@ const EmulationActor = protocol.ActorClassWithSpec(emulationSpec, {
return this._touchSimulator; return this._touchSimulator;
}, },
get win() {
return this.docShell.chromeEventHandler.ownerGlobal;
},
onWillNavigate({ isTopLevel }) { onWillNavigate({ isTopLevel }) {
// Make sure that print simulation is stopped before navigating to another page. We // 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 // 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(); 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. * Simulates the "orientationchange" event when device screen is rotated.
* *
* TODO: Update `window.screen.orientation` and `window.screen.angle` here. * @param {String} type
* See Bug 1357774. * 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() { async simulateScreenOrientationChange(type, angle, deviceChange) {
const win = this.docShell.chromeEventHandler.ownerGlobal; // Don't dispatch the "orientationchange" event if orientation change is a result
const { CustomEvent } = win; // of switching to a new device.
if (deviceChange) {
this.setScreenOrientation(type, angle);
return;
}
const { CustomEvent } = this.win;
const orientationChangeEvent = new CustomEvent("orientationchange"); const orientationChangeEvent = new CustomEvent("orientationchange");
win.dispatchEvent(orientationChangeEvent);
this.setScreenOrientation(type, angle);
this.win.dispatchEvent(orientationChangeEvent);
}, },
}); });

Просмотреть файл

@ -151,7 +151,11 @@ const emulationSpec = generateActorSpec({
}, },
simulateScreenOrientationChange: { simulateScreenOrientationChange: {
request: {}, request: {
orientation: Arg(0, "string"),
angle: Arg(1, "number"),
deviceChange: Arg(2, "boolean"),
},
response: {}, response: {},
}, },
}, },