--- a/devtools/client/responsive.html/actions/devices.js
+++ b/devtools/client/responsive.html/actions/devices.js
@@ -9,17 +9,17 @@ const {
ADD_DEVICE_TYPE,
LOAD_DEVICE_LIST_START,
LOAD_DEVICE_LIST_ERROR,
LOAD_DEVICE_LIST_END,
UPDATE_DEVICE_DISPLAYED,
UPDATE_DEVICE_MODAL_OPEN,
} = require("./index");
-const { GetDevices } = require("devtools/client/shared/devices");
+const { getDevices } = require("devtools/client/shared/devices");
const Services = require("Services");
const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
/**
* Returns an object containing the user preference of displayed devices.
*
* @return {Object} containing two Sets:
@@ -97,17 +97,17 @@ module.exports = {
loadDevices() {
return function* (dispatch, getState) {
yield dispatch({ type: LOAD_DEVICE_LIST_START });
let preferredDevices = loadPreferredDevices();
let devices;
try {
- devices = yield GetDevices();
+ devices = yield getDevices();
} catch (e) {
console.error("Could not load device list: " + e);
dispatch({ type: LOAD_DEVICE_LIST_ERROR });
return;
}
for (let type of devices.TYPES) {
dispatch(module.exports.addDeviceType(type));
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -39,18 +39,18 @@ let App = createClass({
},
onBrowserMounted() {
window.postMessage({ type: "browser-mounted" }, "*");
},
onChangeViewportDevice(id, device) {
window.postMessage({
- type: "update-user-agent",
- userAgent: device.userAgent
+ type: "change-viewport-device",
+ device,
}, "*");
this.props.dispatch(changeDevice(id, device.name));
},
onContentResize({ width, height }) {
window.postMessage({
type: "content-resize",
width,
--- a/devtools/client/responsive.html/components/browser.js
+++ b/devtools/client/responsive.html/components/browser.js
@@ -97,16 +97,17 @@ module.exports = createClass({
requiresFloatingScrollbars,
// Tests expect events on resize to yield on various size changes
notifyOnResize: flags.testing,
});
}),
stopFrameScript: Task.async(function* () {
let { onContentResize } = this;
+
let browser = this.refs.browserContainer.querySelector("iframe.browser");
let mm = browser.frameLoader.messageManager;
e10s.off(mm, "OnContentResize", onContentResize);
yield e10s.request(mm, "Stop");
message.post(window, "stop-frame-script:done");
}),
render() {
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -418,58 +418,67 @@ ResponsiveUI.prototype = {
handleMessage(event) {
let { browserWindow, tab } = this;
if (event.origin !== "chrome://devtools") {
return;
}
switch (event.data.type) {
+ case "change-viewport-device":
+ let { userAgent, pixelRatio } = event.data.device;
+ this.updateUserAgent(userAgent);
+ this.updateDPPX(pixelRatio);
+ break;
case "content-resize":
let { width, height } = event.data;
this.emit("content-resize", {
width,
height,
});
break;
case "exit":
ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
break;
case "update-touch-simulation":
let { enabled } = event.data;
this.updateTouchSimulation(enabled);
break;
- case "update-user-agent":
- let { userAgent } = event.data;
- this.updateUserAgent(userAgent);
- break;
}
},
updateTouchSimulation: Task.async(function* (enabled) {
if (enabled) {
let reloadNeeded = yield this.emulationFront.setTouchEventsOverride(
Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED
);
if (reloadNeeded) {
this.getViewportBrowser().reload();
}
} else {
this.emulationFront.clearTouchEventsOverride();
}
}),
- updateUserAgent: function (userAgent) {
+ updateUserAgent(userAgent) {
if (userAgent) {
this.emulationFront.setUserAgentOverride(userAgent);
} else {
this.emulationFront.clearUserAgentOverride();
}
},
+ updateDPPX(dppx) {
+ if (dppx) {
+ this.emulationFront.setDPPXOverride(dppx);
+ } else {
+ this.emulationFront.clearDPPXOverride();
+ }
+ },
+
/**
* Helper for tests. Assumes a single viewport for now.
*/
getViewportSize() {
return this.toolWindow.getViewportSize();
},
/**
--- a/devtools/client/responsive.html/test/browser/browser_device_change.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_change.js
@@ -1,49 +1,78 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests changing viewport device
const TEST_URL = "data:text/html;charset=utf-8,Device list test";
+
+const DEFAULT_DPPX = window.devicePixelRatio;
const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
.getService(Ci.nsIHttpProtocolHandler)
.userAgent;
-const NOKIA_UA = "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; " +
- "Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)";
+
const Types = require("devtools/client/responsive.html/types");
+const { addDevice, removeDevice } = require("devtools/client/shared/devices");
+
+const testDevice = {
+ "name": "Fake Phone RDM Test",
+ "width": 320,
+ "height": 570,
+ "pixelRatio": 5.5,
+ "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
+ "touch": true,
+ "firefoxOS": true,
+ "os": "custom",
+ "featured": true,
+};
+
+// Add the new device to the list
+addDevice(testDevice);
+
addRDMTask(TEST_URL, function* ({ ui, manager }) {
let { store } = ui.toolWindow;
// Wait until the viewport has been added and the device list has been loaded
yield waitUntilState(store, state => state.viewports.length == 1
&& state.devices.listState == Types.deviceListState.LOADED);
// Test defaults
testViewportDimensions(ui, 320, 480);
yield testUserAgent(ui, DEFAULT_UA);
+ testDevicePixelRatio(yield getViewportDevicePixelRatio(ui), DEFAULT_DPPX);
testViewportSelectLabel(ui, "no device selected");
+ let waitingPixelRatio = onceDevicePixelRatioChange(ui);
+
// Test device with custom UA
- switchDevice(ui, "Nokia Lumia 520");
- yield waitForViewportResizeTo(ui, 320, 533);
- yield testUserAgent(ui, NOKIA_UA);
+ yield switchDevice(ui, "Fake Phone RDM Test");
+ yield waitForViewportResizeTo(ui, testDevice.width, testDevice.height);
+ yield testUserAgent(ui, testDevice.userAgent);
+
+ // Test device with custom pixelRatio
+ testDevicePixelRatio(yield waitingPixelRatio, testDevice.pixelRatio);
+ waitingPixelRatio = onceDevicePixelRatioChange(ui);
// Test resetting device when resizing viewport
yield testViewportResize(ui, ".viewport-vertical-resize-handle",
- [-10, -10], [320, 523], [0, -10], ui);
+ [-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui);
yield testUserAgent(ui, DEFAULT_UA);
testViewportSelectLabel(ui, "no device selected");
+ testDevicePixelRatio(yield waitingPixelRatio, DEFAULT_DPPX);
// Test device where UA field is blank
- switchDevice(ui, "Laptop (1366 x 768)");
+ yield switchDevice(ui, "Laptop (1366 x 768)");
yield waitForViewportResizeTo(ui, 1366, 768);
yield testUserAgent(ui, DEFAULT_UA);
+
+ ok(removeDevice(testDevice),
+ "Test Device properly removed.");
});
function testViewportDimensions(ui, w, h) {
let viewport = ui.toolWindow.document.querySelector(".viewport-content");
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"),
`${w}px`, `Viewport should have width of ${w}px`);
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"),
@@ -57,8 +86,32 @@ function testViewportSelectLabel(ui, lab
}
function* testUserAgent(ui, value) {
let ua = yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
return content.navigator.userAgent;
});
is(ua, value, `UA should be set to ${value}`);
}
+
+function testDevicePixelRatio(dppx, expected) {
+ is(dppx, expected, `devicePixelRatio should be set to ${expected}`);
+}
+
+function* getViewportDevicePixelRatio(ui) {
+ return yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+ return content.devicePixelRatio;
+ });
+}
+
+function onceDevicePixelRatioChange(ui) {
+ return ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+ let pixelRatio = content.devicePixelRatio;
+ let mql = content.matchMedia(`(resolution: ${pixelRatio}dppx)`);
+
+ return new Promise(resolve => {
+ mql.addListener(function listener() {
+ mql.removeListener(listener);
+ resolve(content.devicePixelRatio);
+ });
+ });
+ });
+}
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
@@ -1,15 +1,15 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test submitting display device changes on the device modal
-const { GetDevices, AddDevice } = require("devtools/client/shared/devices");
+const { getDevices, addDevice } = require("devtools/client/shared/devices");
const addedDevice = {
"name": "Fake Phone RDM Test",
"width": 320,
"height": 570,
"pixelRatio": 1.5,
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
"touch": true,
@@ -32,17 +32,17 @@ addRDMTask(TEST_URL, function* ({ ui })
&& state.devices.listState == Types.deviceListState.LOADED);
openDeviceModal(ui);
info("Checking displayed device checkboxes are checked in the device modal.");
let checkedCbs = [...document.querySelectorAll(".device-input-checkbox")]
.filter(cb => cb.checked);
- let remoteList = yield GetDevices();
+ let remoteList = yield getDevices();
let featuredCount = remoteList.TYPES.reduce((total, type) => {
return total + remoteList[type].reduce((subtotal, device) => {
return subtotal + ((device.os != "fxos" && device.featured) ? 1 : 0);
}, 0);
}, 0);
is(featuredCount, checkedCbs.length,
@@ -102,29 +102,29 @@ addRDMTask(TEST_URL, function* ({ ui })
info("Reopen device modal and check device is correctly unchecked");
openDeviceModal(ui);
ok([...document.querySelectorAll(".device-input-checkbox")]
.filter(cb => !cb.checked && cb.value === checkedVal)[0],
checkedVal + " is unchecked in the device modal.");
// Let's add a dummy device to simulate featured flag changes for next test
- AddDevice(addedDevice);
+ addDevice(addedDevice);
});
addRDMTask(TEST_URL, function* ({ ui }) {
let { store, document } = ui.toolWindow;
let select = document.querySelector(".viewport-device-selector");
// Wait until the viewport has been added
yield waitUntilState(store, state => state.viewports.length == 1);
openDeviceModal(ui);
- let remoteList = yield GetDevices();
+ let remoteList = yield getDevices();
let featuredCount = remoteList.TYPES.reduce((total, type) => {
return total + remoteList[type].reduce((subtotal, device) => {
return subtotal + ((device.os != "fxos" && device.featured) ? 1 : 0);
}, 0);
}, 0);
let preferredDevices = _loadPreferredDevices();
// Tests to prove that reloading the RDM didn't break our device list
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -200,28 +200,35 @@ function openDeviceModal(ui) {
ui.toolWindow);
EventUtils.synthesizeMouseAtCenter(editDeviceOption, {type: "mouseup"},
ui.toolWindow);
ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
"The device modal is displayed.");
}
-function switchDevice(ui, device) {
- let { document } = ui.toolWindow;
- let select = document.querySelector(".viewport-device-selector");
- select.scrollIntoView();
- let deviceOption = [...select.options].filter(o => {
- return o.value === device;
- })[0];
- EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"},
- ui.toolWindow);
- EventUtils.synthesizeMouseAtCenter(deviceOption, {type: "mouseup"},
- ui.toolWindow);
- is(select.selectedOptions[0], deviceOption, "Device should be selected");
+function switchDevice({ toolWindow }, name) {
+ return new Promise(resolve => {
+ let select = toolWindow.document.querySelector(".viewport-device-selector");
+
+ let event = new toolWindow.UIEvent("change", {
+ view: toolWindow,
+ bubbles: true,
+ cancelable: true
+ });
+
+ select.addEventListener("change", function onChange() {
+ is(select.value, name, "Device should be selected");
+ select.removeEventListener("change", onChange);
+ resolve();
+ });
+
+ select.value = name;
+ select.dispatchEvent(event);
+ });
}
function getSessionHistory(browser) {
return ContentTask.spawn(browser, {}, function* () {
/* eslint-disable no-undef */
let { interfaces: Ci } = Components;
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
let sessionHistory = webNav.sessionHistory;
--- a/devtools/client/shared/devices.js
+++ b/devtools/client/shared/devices.js
@@ -2,17 +2,17 @@
* 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 { getJSON } = require("devtools/client/shared/getjson");
const DEVICES_URL = "devtools.devices.url";
-const {LocalizationHelper} = require("devtools/shared/l10n");
+const { LocalizationHelper } = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/locale/device.properties");
/* This is a catalog of common web-enabled devices and their properties,
* intended for (mobile) device emulation.
*
* The properties of a device are:
* - name: brand and model(s).
* - width: viewport width.
@@ -24,45 +24,65 @@ const L10N = new LocalizationHelper("dev
*
* The device types are:
* ["phones", "tablets", "laptops", "televisions", "consoles", "watches"].
*
* You can easily add more devices to this catalog from your own code (e.g. an
* addon) like so:
*
* var myPhone = { name: "My Phone", ... };
- * require("devtools/client/shared/devices").AddDevice(myPhone, "phones");
+ * require("devtools/client/shared/devices").addDevice(myPhone, "phones");
*/
// Local devices catalog that addons can add to.
-var localDevices = {};
+let localDevices = {};
// Add a device to the local catalog.
-function AddDevice(device, type = "phones") {
+function addDevice(device, type = "phones") {
let list = localDevices[type];
if (!list) {
list = localDevices[type] = [];
}
list.push(device);
}
-exports.AddDevice = AddDevice;
+exports.addDevice = addDevice;
+
+// Remove a device from the local catalog.
+// returns `true` if the device is removed, `false` otherwise.
+function removeDevice(device, type = "phones") {
+ let list = localDevices[type];
+ if (!list) {
+ return false;
+ }
+
+ let index = list.findIndex(item => device);
+
+ if (index === -1) {
+ return false;
+ }
+
+ list.splice(index, 1);
+
+ return true;
+}
+exports.removeDevice = removeDevice;
// Get the complete devices catalog.
-function GetDevices() {
+function getDevices() {
// Fetch common devices from Mozilla's CDN.
return getJSON(DEVICES_URL).then(devices => {
for (let type in localDevices) {
if (!devices[type]) {
devices.TYPES.push(type);
devices[type] = [];
}
devices[type] = localDevices[type].concat(devices[type]);
}
return devices;
});
}
-exports.GetDevices = GetDevices;
+exports.getDevices = getDevices;
// Get the localized string for a device type.
-function GetDeviceString(deviceType) {
+function getDeviceString(deviceType) {
return L10N.getStr("device." + deviceType);
}
-exports.GetDeviceString = GetDeviceString;
+exports.getDeviceString = getDeviceString;
--- a/devtools/client/shared/test/browser_devices.js
+++ b/devtools/client/shared/test/browser_devices.js
@@ -1,50 +1,54 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
-var { GetDevices, GetDeviceString, AddDevice } = require("devtools/client/shared/devices");
+const {
+ getDevices,
+ getDeviceString,
+ addDevice
+} = require("devtools/client/shared/devices");
add_task(function* () {
Services.prefs.setCharPref("devtools.devices.url", TEST_URI_ROOT + "browser_devices.json");
- let devices = yield GetDevices();
+ let devices = yield getDevices();
is(devices.TYPES.length, 1, "Found 1 device type.");
let type1 = devices.TYPES[0];
is(devices[type1].length, 2, "Found 2 devices of type #1.");
- let string = GetDeviceString(type1);
+ let string = getDeviceString(type1);
ok(typeof string === "string" && string.length > 0, "Able to localize type #1.");
let device1 = {
name: "SquarePhone",
width: 320,
height: 320,
pixelRatio: 2,
userAgent: "Mozilla/5.0 (Mobile; rv:42.0)",
touch: true,
firefoxOS: true
};
- AddDevice(device1, type1);
- devices = yield GetDevices();
+ addDevice(device1, type1);
+ devices = yield getDevices();
is(devices[type1].length, 3, "Added new device of type #1.");
ok(devices[type1].filter(d => d.name === device1.name), "Found the new device.");
let type2 = "appliances";
let device2 = {
name: "Mr Freezer",
width: 800,
height: 600,
pixelRatio: 5,
userAgent: "Mozilla/5.0 (Appliance; rv:42.0)",
touch: true,
firefoxOS: true
};
- AddDevice(device2, type2);
- devices = yield GetDevices();
+ addDevice(device2, type2);
+ devices = yield getDevices();
is(devices.TYPES.length, 2, "Added device type #2.");
is(devices[type2].length, 1, "Added new device of type #2.");
});
--- a/devtools/client/webide/content/simulator.js
+++ b/devtools/client/webide/content/simulator.js
@@ -1,17 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var Cu = Components.utils;
var Ci = Components.interfaces;
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const { GetDevices, GetDeviceString } = require("devtools/client/shared/devices");
+const { getDevices, getDeviceString } = require("devtools/client/shared/devices");
const { Simulators, Simulator } = require("devtools/client/webide/modules/simulators");
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
const promise = require("promise");
const utils = require("devtools/client/webide/modules/utils");
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
@@ -75,24 +75,24 @@ var SimulatorEditor = {
opt(form.profile, "default", Strings.GetStringFromName("simulator_default_profile"));
opt(form.profile, "custom", "");
opt(form.profile, "pick", Strings.GetStringFromName("simulator_custom_profile"));
// Generate example devices list.
form.device.innerHTML = "";
form.device.classList.remove("custom");
opt(form.device, "custom", Strings.GetStringFromName("simulator_custom_device"));
- promises.push(GetDevices().then(devices => {
+ promises.push(getDevices().then(devices => {
devices.TYPES.forEach(type => {
let b2gDevices = devices[type].filter(d => d.firefoxOS);
if (b2gDevices.length < 1) {
return;
}
let optgroup = document.createElement("optgroup");
- optgroup.label = GetDeviceString(type);
+ optgroup.label = getDeviceString(type);
b2gDevices.forEach(device => {
this._devices[device.name] = device;
opt(optgroup, device.name, device.name);
});
form.device.appendChild(optgroup);
});
}));
--- a/devtools/client/webide/test/test_simulators.html
+++ b/devtools/client/webide/test/test_simulators.html
@@ -16,17 +16,17 @@
<script type="application/javascript;version=1.8">
window.onload = function() {
SimpleTest.waitForExplicitFinish();
const asyncStorage = require("devtools/shared/async-storage");
const EventEmitter = require("devtools/shared/event-emitter");
const { GetAvailableAddons } = require("devtools/client/webide/modules/addons");
- const { GetDevices } = require("devtools/client/shared/devices");
+ const { getDevices } = require("devtools/client/shared/devices");
const { Simulator, Simulators } = require("devtools/client/webide/modules/simulators");
const { AddonSimulatorProcess,
OldAddonSimulatorProcess,
CustomSimulatorProcess } = require("devtools/client/webide/modules/simulator-process");
function addonStatus(addon, status) {
if (addon.status == status) {
return promise.resolve();
@@ -310,17 +310,17 @@
is(form.name.value, customName + "2.0", "Name deduplication was undone when possible");
// Test `device`.
for (let param in defaults.phone) {
is(form[param].value, String(defaults.phone[param]), "Default phone value for device " + param);
}
- let devices = yield GetDevices();
+ let devices = yield getDevices();
devices = devices[devices.TYPES[0]];
let device = devices[devices.length - 1];
yield set(form.device, device.name);
is(form.device.value, device.name, "Device selector was changed");
is(form.width.value, String(device.width), "New device width is correct");
is(form.height.value, String(device.height), "New device height is correct");
--- a/devtools/server/actors/emulation.js
+++ b/devtools/server/actors/emulation.js
@@ -71,22 +71,55 @@ let EmulationActor = protocol.ActorClass
clearUserAgentOverride() {
if (this._previousUserAgentOverride !== null) {
return this.setUserAgentOverride(this._previousUserAgentOverride);
}
return false;
},
+ /* DPPX override */
+
+ _previousDPPXOverride: null,
+
+ setDPPXOverride(dppx) {
+ let { contentViewer } = this.docShell;
+
+ if (contentViewer.overrideDPPX === dppx) {
+ return false;
+ }
+
+ if (this._previousDPPXOverride === null) {
+ this._previousDPPXOverride = contentViewer.overrideDPPX;
+ }
+
+ contentViewer.overrideDPPX = dppx;
+
+ return true;
+ },
+
+ getDPPXOverride() {
+ return this.docShell.contentViewer.overrideDPPX;
+ },
+
+ clearDPPXOverride() {
+ if (this._previousDPPXOverride !== null) {
+ return this.setDPPXOverride(this._previousDPPXOverride);
+ }
+
+ return false;
+ },
+
disconnect() {
this.destroy();
},
destroy() {
this.clearTouchEventsOverride();
this.clearUserAgentOverride();
+ this.clearDPPXOverride();
this.docShell = null;
this.simulatorCore = null;
protocol.Actor.prototype.destroy.call(this);
},
});
exports.EmulationActor = EmulationActor;
--- a/devtools/shared/specs/emulation.js
+++ b/devtools/shared/specs/emulation.js
@@ -49,12 +49,35 @@ const emulationSpec = generateActorSpec(
},
clearUserAgentOverride: {
request: {},
response: {
valueChanged: RetVal("boolean")
}
},
+
+ setDPPXOverride: {
+ request: {
+ dppx: Arg(0, "number")
+ },
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ getDPPXOverride: {
+ request: {},
+ response: {
+ dppx: RetVal("number")
+ }
+ },
+
+ clearDPPXOverride: {
+ request: {},
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
}
});
exports.emulationSpec = emulationSpec;