Bug 1239562 - Use explicit events to fix test races in responsive design. r=ochameau

This commit is contained in:
J. Ryan Stinnett 2016-01-21 21:40:14 -06:00
parent 1813d29694
commit 0e4da7b1f1
11 changed files with 495 additions and 328 deletions

View File

@ -92,11 +92,11 @@ exports.items = [
}
];
function gcli_cmd_resize(args, context) {
function* gcli_cmd_resize(args, context) {
let browserWindow = context.environment.chromeWindow;
let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
mgr.handleGcliCommand(browserWindow,
browserWindow.gBrowser.selectedTab,
this.name,
args);
yield mgr.handleGcliCommand(browserWindow,
browserWindow.gBrowser.selectedTab,
this.name,
args);
}

View File

@ -2,131 +2,181 @@
* 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 Ci = Components.interfaces;
const gDeviceSizeWasPageSize = docShell.deviceSizeIsPageSize;
const gFloatingScrollbarsStylesheet = Services.io.newURI("chrome://devtools/skin/floating-scrollbars-responsive-design.css", null, null);
var gRequiresFloatingScrollbars;
"use strict";
var active = false;
/* global content, docShell, addEventListener, addMessageListener,
removeEventListener, removeMessageListener, sendAsyncMessage */
addMessageListener("ResponsiveMode:Start", startResponsiveMode);
addMessageListener("ResponsiveMode:Stop", stopResponsiveMode);
var global = this;
function startResponsiveMode({data:data}) {
if (active) {
return;
}
addMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
addMessageListener("ResponsiveMode:NotifyOnResize", notifiyOnResize);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_ALL);
docShell.deviceSizeIsPageSize = true;
gRequiresFloatingScrollbars = data.requiresFloatingScrollbars;
// At this point, a content viewer might not be loaded for this
// docshell. makeScrollbarsFloating will be triggered by onLocationChange.
if (docShell.contentViewer) {
makeScrollbarsFloating();
}
active = true;
sendAsyncMessage("ResponsiveMode:Start:Done");
}
function notifiyOnResize() {
content.addEventListener("resize", () => {
sendAsyncMessage("ResponsiveMode:OnContentResize");
}, false);
sendAsyncMessage("ResponsiveMode:NotifyOnResize:Done");
}
function stopResponsiveMode() {
if (!active) {
return;
}
active = false;
removeMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
removeMessageListener("ResponsiveMode:NotifyOnResize", notifiyOnResize);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.removeProgressListener(WebProgressListener);
docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize;
restoreScrollbars();
sendAsyncMessage("ResponsiveMode:Stop:Done");
}
function makeScrollbarsFloating() {
if (!gRequiresFloatingScrollbars) {
// Guard against loading this frame script mutiple times
(function() {
if (global.responsiveFrameScriptLoaded) {
return;
}
let allDocShells = [docShell];
var Ci = Components.interfaces;
const gDeviceSizeWasPageSize = docShell.deviceSizeIsPageSize;
const gFloatingScrollbarsStylesheet = Services.io.newURI("chrome://devtools/skin/floating-scrollbars-responsive-design.css", null, null);
var gRequiresFloatingScrollbars;
for (let i = 0; i < docShell.childCount; i++) {
let child = docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell);
allDocShells.push(child);
var active = false;
var resizeNotifications = false;
addMessageListener("ResponsiveMode:Start", startResponsiveMode);
addMessageListener("ResponsiveMode:Stop", stopResponsiveMode);
function debug(msg) {
// dump(`RDM CHILD: ${msg}\n`);
}
for (let d of allDocShells) {
let win = d.contentViewer.DOMDocument.defaultView;
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
try {
winUtils.loadSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
} catch(e) { }
}
flushStyle();
}
function restoreScrollbars() {
let allDocShells = [docShell];
for (let i = 0; i < docShell.childCount; i++) {
allDocShells.push(docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell));
}
for (let d of allDocShells) {
let win = d.contentViewer.DOMDocument.defaultView;
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
try {
winUtils.removeSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
} catch(e) { }
}
flushStyle();
}
function flushStyle() {
// Force presContext destruction
let isSticky = docShell.contentViewer.sticky;
docShell.contentViewer.sticky = false;
docShell.contentViewer.hide();
docShell.contentViewer.show();
docShell.contentViewer.sticky = isSticky;
}
function screenshot() {
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let width = content.innerWidth;
let height = content.innerHeight;
canvas.mozOpaque = true;
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d");
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
sendAsyncMessage("ResponsiveMode:RequestScreenshot:Done", canvas.toDataURL());
}
var WebProgressListener = {
onLocationChange(webProgress, request, URI, flags) {
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
function startResponsiveMode({data:data}) {
debug("START");
if (active) {
return;
}
makeScrollbarsFloating();
},
QueryInterface: function QueryInterface(aIID) {
if (aIID.equals(Ci.nsIWebProgressListener) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports)) {
return this;
addMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_ALL);
docShell.deviceSizeIsPageSize = true;
gRequiresFloatingScrollbars = data.requiresFloatingScrollbars;
if (data.notifyOnResize) {
startOnResize();
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
};
// At this point, a content viewer might not be loaded for this
// docshell. makeScrollbarsFloating will be triggered by onLocationChange.
if (docShell.contentViewer) {
makeScrollbarsFloating();
}
active = true;
sendAsyncMessage("ResponsiveMode:Start:Done");
}
function onResize() {
let { width, height } = content.screen;
debug(`EMIT RESIZE: ${width} x ${height}`);
sendAsyncMessage("ResponsiveMode:OnContentResize", {
width,
height,
});
}
function bindOnResize() {
content.addEventListener("resize", onResize, false);
}
function startOnResize() {
debug("START ON RESIZE");
if (resizeNotifications) {
return;
}
resizeNotifications = true;
bindOnResize();
addEventListener("DOMWindowCreated", bindOnResize, false);
}
function stopOnResize() {
debug("STOP ON RESIZE");
if (!resizeNotifications) {
return;
}
resizeNotifications = false;
content.removeEventListener("resize", onResize, false);
removeEventListener("DOMWindowCreated", bindOnResize, false);
}
function stopResponsiveMode() {
debug("STOP");
if (!active) {
return;
}
active = false;
removeMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.removeProgressListener(WebProgressListener);
docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize;
restoreScrollbars();
stopOnResize();
sendAsyncMessage("ResponsiveMode:Stop:Done");
}
function makeScrollbarsFloating() {
if (!gRequiresFloatingScrollbars) {
return;
}
let allDocShells = [docShell];
for (let i = 0; i < docShell.childCount; i++) {
let child = docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell);
allDocShells.push(child);
}
for (let d of allDocShells) {
let win = d.contentViewer.DOMDocument.defaultView;
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
try {
winUtils.loadSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
} catch(e) { }
}
flushStyle();
}
function restoreScrollbars() {
let allDocShells = [docShell];
for (let i = 0; i < docShell.childCount; i++) {
allDocShells.push(docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell));
}
for (let d of allDocShells) {
let win = d.contentViewer.DOMDocument.defaultView;
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
try {
winUtils.removeSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
} catch(e) { }
}
flushStyle();
}
function flushStyle() {
// Force presContext destruction
let isSticky = docShell.contentViewer.sticky;
docShell.contentViewer.sticky = false;
docShell.contentViewer.hide();
docShell.contentViewer.show();
docShell.contentViewer.sticky = isSticky;
}
function screenshot() {
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let width = content.innerWidth;
let height = content.innerHeight;
canvas.mozOpaque = true;
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d");
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
sendAsyncMessage("ResponsiveMode:RequestScreenshot:Done", canvas.toDataURL());
}
var WebProgressListener = {
onLocationChange(webProgress, request, URI, flags) {
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
return;
}
makeScrollbarsFloating();
},
QueryInterface: function QueryInterface(aIID) {
if (aIID.equals(Ci.nsIWebProgressListener) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
};
})();
global.responsiveFrameScriptLoaded = true;
sendAsyncMessage("ResponsiveMode:ChildScriptReady");

View File

@ -21,6 +21,7 @@ var { showDoorhanger } = require("devtools/client/shared/doorhanger");
var { TouchEventSimulator } = require("devtools/shared/touch/simulator");
var { Task } = require("resource://gre/modules/Task.jsm");
var promise = require("promise");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
@ -37,6 +38,10 @@ const INPUT_PARSER = /(\d+)[^\d]+(\d+)/;
const SHARED_L10N = new ViewHelpers.L10N("chrome://devtools/locale/shared.properties");
function debug(msg) {
// dump(`RDM UI: ${msg}\n`);
}
var ActiveTabs = new Map();
var Manager = {
@ -92,25 +97,27 @@ var Manager = {
* @param aCommand the command name.
* @param aArgs command arguments.
*/
handleGcliCommand: function(aWindow, aTab, aCommand, aArgs) {
handleGcliCommand: Task.async(function*(aWindow, aTab, aCommand, aArgs) {
switch (aCommand) {
case "resize to":
this.runIfNeeded(aWindow, aTab);
ActiveTabs.get(aTab).setSize(aArgs.width, aArgs.height);
let ui = ActiveTabs.get(aTab);
yield ui.inited;
ui.setSize(aArgs.width, aArgs.height);
break;
case "resize on":
this.runIfNeeded(aWindow, aTab);
break;
case "resize off":
if (this.isActiveForTab(aTab)) {
ActiveTabs.get(aTab).close();
yield ActiveTabs.get(aTab).close();
}
break;
case "resize toggle":
this.toggle(aWindow, aTab);
this.toggle(aWindow, aTab);
default:
}
}
})
}
EventEmitter.decorate(Manager);
@ -126,7 +133,7 @@ if (Services.prefs.getBoolPref("devtools.responsive.html.enabled")) {
this.ResponsiveUIManager = Manager;
}
var presets = [
var defaultPresets = [
// Phones
{key: "320x480", width: 320, height: 480}, // iPhone, B2G, with <meta viewport>
{key: "360x640", width: 360, height: 640}, // Android 4, phones, with <meta viewport>
@ -155,59 +162,6 @@ function ResponsiveUI(aWindow, aTab)
this.stack = this.container.querySelector(".browserStack");
this._telemetry = new Telemetry();
let childOn = () => {
this.mm.removeMessageListener("ResponsiveMode:Start:Done", childOn);
ResponsiveUIManager.emit("on", { tab: this.tab });
}
this.mm.addMessageListener("ResponsiveMode:Start:Done", childOn);
let requiresFloatingScrollbars = !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
this.mm.addMessageListener("ResponsiveMode:ChildScriptReady", () => {
this.mm.sendAsyncMessage("ResponsiveMode:Start", {
requiresFloatingScrollbars: requiresFloatingScrollbars
});
});
// Try to load presets from prefs
if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
try {
presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
} catch(e) {
// User pref is malformated.
Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e);
}
}
this.customPreset = {key: "custom", custom: true};
if (Array.isArray(presets)) {
this.presets = [this.customPreset].concat(presets);
} else {
Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated.");
this.presets = [this.customPreset];
}
try {
let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth");
let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight");
this.customPreset.width = Math.min(MAX_WIDTH, width);
this.customPreset.height = Math.min(MAX_HEIGHT, height);
this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
} catch(e) {
// Default size. The first preset (custom) is the one that will be used.
let bbox = this.stack.getBoundingClientRect();
this.customPreset.width = bbox.width - 40; // horizontal padding of the container
this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
this.currentPresetKey = this.presets[1].key; // most common preset
}
this.container.setAttribute("responsivemode", "true");
this.stack.setAttribute("responsivemode", "true");
// Let's bind some callbacks.
this.bound_presetSelected = this.presetSelected.bind(this);
this.bound_handleManualInput = this.handleManualInput.bind(this);
@ -220,34 +174,14 @@ function ResponsiveUI(aWindow, aTab)
this.bound_startResizing = this.startResizing.bind(this);
this.bound_stopResizing = this.stopResizing.bind(this);
this.bound_onDrag = this.onDrag.bind(this);
this.bound_onContentResize = this.onContentResize.bind(this);
// Events
this.tab.addEventListener("TabClose", this);
this.tabContainer.addEventListener("TabSelect", this);
this.mm.addMessageListener("ResponsiveMode:OnContentResize",
this.bound_onContentResize);
this.buildUI();
this.checkMenus();
ActiveTabs.set(this.tab, this);
try {
if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
this.rotate();
}
} catch(e) {}
ActiveTabs.set(aTab, this);
this._telemetry.toolOpened("responsive");
// Touch events support
this.touchEnableBefore = false;
this.touchEventSimulator = new TouchEventSimulator(this.browser);
// Hook to display promotional Developer Edition doorhanger. Only displayed once.
showDoorhanger({
window: this.mainWindow,
type: "deveditionpromo",
anchor: this.chromeDoc.querySelector("#content")
});
this.inited = this.init();
}
ResponsiveUI.prototype = {
@ -264,22 +198,131 @@ ResponsiveUI.prototype = {
}
},
init: Task.async(function*() {
debug("INIT BEGINS");
let ready = this.waitForMessage("ResponsiveMode:ChildScriptReady");
this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
yield ready;
let requiresFloatingScrollbars =
!this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
let started = this.waitForMessage("ResponsiveMode:Start:Done");
debug("SEND START");
this.mm.sendAsyncMessage("ResponsiveMode:Start", {
requiresFloatingScrollbars,
// Tests expect events on resize to yield on various size changes
notifyOnResize: DevToolsUtils.testing,
});
yield started;
// Load Presets
this.loadPresets();
// Events
this.tab.addEventListener("TabClose", this);
this.tabContainer.addEventListener("TabSelect", this);
// Setup the UI
this.container.setAttribute("responsivemode", "true");
this.stack.setAttribute("responsivemode", "true");
this.buildUI();
this.checkMenus();
// Rotate the responsive mode if needed
try {
if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
this.rotate();
}
} catch(e) {}
// Touch events support
this.touchEnableBefore = false;
this.touchEventSimulator = new TouchEventSimulator(this.browser);
// Hook to display promotional Developer Edition doorhanger.
// Only displayed once.
showDoorhanger({
window: this.mainWindow,
type: "deveditionpromo",
anchor: this.chromeDoc.querySelector("#content")
});
// Notify that responsive mode is on.
this._telemetry.toolOpened("responsive");
ResponsiveUIManager.emit("on", { tab: this.tab });
}),
loadPresets: function() {
// Try to load presets from prefs
let presets = defaultPresets;
if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
try {
presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
} catch(e) {
// User pref is malformated.
Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e);
}
}
this.customPreset = {key: "custom", custom: true};
if (Array.isArray(presets)) {
this.presets = [this.customPreset].concat(presets);
} else {
Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated.");
this.presets = [this.customPreset];
}
try {
let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth");
let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight");
this.customPreset.width = Math.min(MAX_WIDTH, width);
this.customPreset.height = Math.min(MAX_HEIGHT, height);
this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
} catch(e) {
// Default size. The first preset (custom) is the one that will be used.
let bbox = this.stack.getBoundingClientRect();
this.customPreset.width = bbox.width - 40; // horizontal padding of the container
this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
this.currentPresetKey = this.presets[1].key; // most common preset
}
},
/**
* Destroy the nodes. Remove listeners. Reset the style.
*/
close: function RUI_close() {
if (this.closing)
close: Task.async(function*() {
debug("CLOSE BEGINS");
if (this.closing) {
debug("ALREADY CLOSING, ABORT");
return;
}
this.closing = true;
// If we're closing very fast (in tests), ensure init has finished.
debug("CLOSE: WAIT ON INITED");
yield this.inited;
debug("CLOSE: INITED DONE");
this.unCheckMenus();
// Reset style of the stack.
debug(`CURRENT SIZE: ${this.stack.getAttribute("style")}`);
let style = "max-width: none;" +
"min-width: 0;" +
"max-height: none;" +
"min-height: 0;";
debug("RESET STACK SIZE");
this.stack.setAttribute("style", style);
// Wait for resize message before stopping in the child when testing
if (DevToolsUtils.testing) {
yield this.waitForMessage("ResponsiveMode:OnContentResize");
}
if (this.isResizing)
this.stopResizing();
@ -316,35 +359,33 @@ ResponsiveUI.prototype = {
this.touchEventSimulator.stop();
}
this._telemetry.toolClosed("responsive");
let childOff = () => {
this.mm.removeMessageListener("ResponsiveMode:Stop:Done", childOff);
ResponsiveUIManager.emit("off", { tab: this.tab });
}
this.mm.addMessageListener("ResponsiveMode:Stop:Done", childOff);
let stopped = this.waitForMessage("ResponsiveMode:Stop:Done");
this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
yield stopped;
this.inited = null;
ResponsiveUIManager.emit("off", { tab: this.tab });
}),
waitForMessage(message) {
return new Promise(resolve => {
let listener = () => {
this.mm.removeMessageListener(message, listener);
resolve();
};
this.mm.addMessageListener(message, listener);
});
},
/**
* Notify when the content has been resized. Only used in tests.
* Emit an event when the content has been resized. Only used in tests.
*/
_test_notifyOnResize: function() {
let deferred = promise.defer();
let mm = this.mm;
this.bound_onContentResize = this.onContentResize.bind(this);
mm.addMessageListener("ResponsiveMode:OnContentResize", this.bound_onContentResize);
mm.sendAsyncMessage("ResponsiveMode:NotifyOnResize");
mm.addMessageListener("ResponsiveMode:NotifyOnResize:Done", function onListeningResize() {
mm.removeMessageListener("ResponsiveMode:NotifyOnResize:Done", onListeningResize);
deferred.resolve();
onContentResize: function(msg) {
ResponsiveUIManager.emit("contentResize", {
tab: this.tab,
width: msg.data.width,
height: msg.data.height,
});
return deferred.promise;
},
onContentResize: function() {
ResponsiveUIManager.emit("contentResize", { tab: this.tab });
},
/**
@ -863,6 +904,18 @@ ResponsiveUI.prototype = {
}
}),
/**
* Get the current width and height.
*/
getSize() {
let width = Number(this.stack.style.minWidth.replace("px", ""));
let height = Number(this.stack.style.minHeight.replace("px", ""));
return {
width,
height,
};
},
/**
* Change the size of the browser.
*
@ -870,6 +923,7 @@ ResponsiveUI.prototype = {
* @param aHeight height of the browser.
*/
setSize: function RUI_setSize(aWidth, aHeight) {
debug(`SET SIZE TO ${aWidth} x ${aHeight}`);
this.setWidth(aWidth);
this.setHeight(aHeight);
},

View File

@ -11,6 +11,9 @@
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
function test() {
let manager = ResponsiveUI.ResponsiveUIManager;
let done;
function isOpen() {
return gBrowser.getBrowserContainer(gBrowser.selectedBrowser)
.hasAttribute("responsivemode");
@ -19,7 +22,10 @@ function test() {
helpers.addTabWithToolbar("data:text/html;charset=utf-8,hi", (options) => {
return helpers.audit(options, [
{
setup: "resize toggle",
setup() {
done = once(manager, "on");
return helpers.setInput(options, "resize toggle");
},
check: {
input: "resize toggle",
hints: "",
@ -29,12 +35,16 @@ function test() {
exec: {
output: ""
},
post: function() {
post: Task.async(function*() {
yield done;
ok(isOpen(), "responsive mode is open");
},
}),
},
{
setup: "resize toggle",
setup() {
done = once(manager, "off");
return helpers.setInput(options, "resize toggle");
},
check: {
input: "resize toggle",
hints: "",
@ -44,12 +54,16 @@ function test() {
exec: {
output: ""
},
post: function() {
post: Task.async(function*() {
yield done;
ok(!isOpen(), "responsive mode is closed");
},
}),
},
{
setup: "resize on",
setup() {
done = once(manager, "on");
return helpers.setInput(options, "resize on");
},
check: {
input: "resize on",
hints: "",
@ -59,12 +73,16 @@ function test() {
exec: {
output: ""
},
post: function() {
post: Task.async(function*() {
yield done;
ok(isOpen(), "responsive mode is open");
},
}),
},
{
setup: "resize off",
setup() {
done = once(manager, "off");
return helpers.setInput(options, "resize off");
},
check: {
input: "resize off",
hints: "",
@ -74,12 +92,16 @@ function test() {
exec: {
output: ""
},
post: function() {
post: Task.async(function*() {
yield done;
ok(!isOpen(), "responsive mode is closed");
},
}),
},
{
setup: "resize to 400 400",
setup() {
done = once(manager, "on");
return helpers.setInput(options, "resize to 400 400");
},
check: {
input: "resize to 400 400",
hints: "",
@ -93,12 +115,16 @@ function test() {
exec: {
output: ""
},
post: function() {
post: Task.async(function*() {
yield done;
ok(isOpen(), "responsive mode is open");
},
}),
},
{
setup: "resize off",
setup() {
done = once(manager, "off");
return helpers.setInput(options, "resize off");
},
check: {
input: "resize off",
hints: "",
@ -108,9 +134,10 @@ function test() {
exec: {
output: ""
},
post: function() {
post: Task.async(function*() {
yield done;
ok(!isOpen(), "responsive mode is closed");
},
}),
},
]);
}).then(finish);

View File

@ -5,15 +5,15 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function*() {
let tab = yield addTab("about:logo");
let {rdm} = yield openRDM(tab);
let { rdm, manager } = yield openRDM(tab);
ok(rdm, "An instance of the RDM should be attached to the tab.");
rdm.setSize(110, 500);
yield setSize(rdm, manager, 110, 500);
info("Checking initial width/height properties.");
yield doInitialChecks();
info("Changing the RDM size");
rdm.setSize(90, 500);
yield setSize(rdm, manager, 90, 500);
info("Checking for screen props");
yield checkScreenProps();

View File

@ -22,36 +22,36 @@ add_task(function*() {
yield addTab(TEST_URI);
info("Open the responsive design mode and set its size to 500x500 to start");
let {rdm} = yield openRDM();
rdm.setSize(500, 500);
let { rdm, manager } = yield openRDM();
yield setSize(rdm, manager, 500, 500);
info("Open the inspector, computed-view and select the test node");
let {inspector, view} = yield openComputedView();
yield selectNode("div", inspector);
info("Try shrinking the viewport and checking the applied styles");
yield testShrink(view, inspector, rdm);
yield testShrink(view, inspector, rdm, manager);
info("Try growing the viewport and checking the applied styles");
yield testGrow(view, inspector, rdm);
yield testGrow(view, inspector, rdm, manager);
yield closeRDM(rdm);
yield closeToolbox();
});
function* testShrink(computedView, inspector, rdm) {
function* testShrink(computedView, inspector, rdm, manager) {
is(computedWidth(computedView), "500px", "Should show 500px initially.");
let onRefresh = inspector.once("computed-view-refreshed");
rdm.setSize(100, 100);
yield setSize(rdm, manager, 100, 100);
yield onRefresh;
is(computedWidth(computedView), "100px", "Should be 100px after shrinking.");
}
function* testGrow(computedView, inspector, rdm) {
function* testGrow(computedView, inspector, rdm, manager) {
let onRefresh = inspector.once("computed-view-refreshed");
rdm.setSize(500, 500);
yield setSize(rdm, manager, 500, 500);
yield onRefresh;
is(computedWidth(computedView), "500px", "Should be 500px after growing.");

View File

@ -25,18 +25,18 @@ add_task(function*() {
yield addTab(TEST_URI);
info("Open the responsive design mode and set its size to 500x500 to start");
let {rdm} = yield openRDM();
rdm.setSize(500, 500);
let { rdm, manager } = yield openRDM();
yield setSize(rdm, manager, 500, 500);
info("Open the inspector, rule-view and select the test node");
let {inspector, view} = yield openRuleView();
yield selectNode("div", inspector);
info("Try shrinking the viewport and checking the applied styles");
yield testShrink(view, rdm);
yield testShrink(view, rdm, manager);
info("Try growing the viewport and checking the applied styles");
yield testGrow(view, rdm);
yield testGrow(view, rdm, manager);
info("Check that ESC still opens the split console");
yield testEscapeOpensSplitConsole(inspector);
@ -49,21 +49,21 @@ add_task(function*() {
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
});
function* testShrink(ruleView, rdm) {
function* testShrink(ruleView, rdm, manager) {
is(numberOfRules(ruleView), 2, "Should have two rules initially.");
info("Resize to 100x100 and wait for the rule-view to update");
let onRefresh = ruleView.once("ruleview-refreshed");
rdm.setSize(100, 100);
yield setSize(rdm, manager, 100, 100);
yield onRefresh;
is(numberOfRules(ruleView), 3, "Should have three rules after shrinking.");
}
function* testGrow(ruleView, rdm) {
function* testGrow(ruleView, rdm, manager) {
info("Resize to 500x500 and wait for the rule-view to update");
let onRefresh = ruleView.once("ruleview-refreshed");
rdm.setSize(500, 500);
yield setSize(rdm, manager, 500, 500);
yield onRefresh;
is(numberOfRules(ruleView), 2, "Should have two rules after growing.");

View File

@ -4,7 +4,6 @@
"use strict";
add_task(function*() {
SimpleTest.requestCompleteLog();
let tab = yield addTab("data:text/html,mop");
let {rdm, manager} = yield openRDM(tab, "menu");
@ -26,9 +25,6 @@ add_task(function*() {
let newWidth = (yield getSizing()).width;
is(originalWidth, newWidth, "Floating scrollbars shouldn't change the width");
yield rdm._test_notifyOnResize();
yield waitForTick();
yield testPresets(rdm, manager);
info("Testing mouse resizing");
@ -53,7 +49,10 @@ add_task(function*() {
info("Restarting responsive mode");
yield closeRDM(rdm);
let resized = waitForResizeTo(manager, widthBeforeClose, heightBeforeClose);
({rdm} = yield openRDM(tab, "keyboard"));
yield resized;
let currentSize = yield getSizing();
is(currentSize.width, widthBeforeClose, "width should be restored");
@ -80,9 +79,7 @@ function* testPresets(rdm, manager) {
for (let c = rdm.menulist.firstChild.childNodes.length - 4; c >= 0; c--) {
let item = rdm.menulist.firstChild.childNodes[c];
let [width, height] = extractSizeFromString(item.getAttribute("label"));
let onContentResize = once(manager, "contentResize");
rdm.menulist.selectedIndex = c;
yield onContentResize;
yield setPresetIndex(rdm, manager, c);
let {width: contentWidth, height: contentHeight} = yield getSizing();
is(contentWidth, width, "preset" + c + ": the width should be changed");
@ -91,8 +88,7 @@ function* testPresets(rdm, manager) {
}
function* testManualMouseResize(rdm, manager, pressedKey) {
rdm.setSize(100, 100);
yield once(manager, "contentResize");
yield setSize(rdm, manager, 100, 100);
let {width: initialWidth, height: initialHeight} = yield getSizing();
is(initialWidth, 100, "Width should be reset to 100");
@ -171,8 +167,7 @@ function* testInvalidUserInput(rdm) {
}
function* testRotate(rdm, manager) {
rdm.setSize(100, 200);
yield once(manager, "contentResize");
yield setSize(rdm, manager, 100, 200);
let {width: initialWidth, height: initialHeight} = yield getSizing();
rdm.rotate();

View File

@ -6,7 +6,7 @@
add_task(function*() {
let tab = yield addTab("data:text/html;charset=utf8,Test RDM custom presets");
let {rdm} = yield openRDM(tab);
let { rdm, manager } = yield openRDM(tab);
let oldPrompt = Services.prompt;
Services.prompt = {
@ -29,8 +29,6 @@ add_task(function*() {
ok(rdm, "RDM instance should be attached to the tab.");
yield rdm._test_notifyOnResize();
// Tries to add a custom preset and cancel the prompt
let idx = rdm.menulist.selectedIndex;
let presetCount = rdm.presets.length;
@ -48,35 +46,27 @@ add_task(function*() {
Services.prompt.value = "Testing preset";
Services.prompt.returnBool = true;
let resized = once(manager, "contentResize");
let customHeight = 123, customWidth = 456;
rdm.startResizing({});
rdm.setSize(customWidth, customHeight);
rdm.stopResizing({});
rdm.addbutton.doCommand();
// Force document reflow to avoid intermittent failures.
info("document height " + document.height);
yield resized;
yield closeRDM(rdm);
// We're still in the loop of initializing the responsive mode.
// Let's wait next loop to stop it.
yield waitForTick();
({rdm} = yield openRDM(tab));
is(container.getAttribute("responsivemode"), "true",
"Should be in responsive mode.");
let presetLabel = "456" + "\u00D7" + "123 (Testing preset)";
let customPresetIndex = getPresetIndex(rdm, presetLabel);
info(customPresetIndex);
let customPresetIndex = yield getPresetIndex(rdm, manager, presetLabel);
ok(customPresetIndex >= 0, "(idx = " + customPresetIndex + ") should be the" +
" previously added preset in the list of items");
let resizePromise = rdm._test_notifyOnResize();
rdm.menulist.selectedIndex = customPresetIndex;
yield resizePromise;
yield setPresetIndex(rdm, manager, customPresetIndex);
let browser = gBrowser.selectedBrowser;
let props = yield ContentTask.spawn(browser, {}, function*() {
@ -87,43 +77,44 @@ add_task(function*() {
is(props.innerWidth, 456, "Selecting preset should change the width");
is(props.innerHeight, 123, "Selecting preset should change the height");
info(`menulist count: ${rdm.menulist.itemCount}`)
rdm.removebutton.doCommand();
rdm.menulist.selectedIndex = 2;
yield setPresetIndex(rdm, manager, 2);
let deletedPresetA = rdm.menulist.selectedItem.getAttribute("label");
rdm.removebutton.doCommand();
rdm.menulist.selectedIndex = 2;
yield setPresetIndex(rdm, manager, 2);
let deletedPresetB = rdm.menulist.selectedItem.getAttribute("label");
rdm.removebutton.doCommand();
yield closeRDM(rdm);
yield waitForTick();
({rdm} = yield openRDM(tab));
customPresetIndex = getPresetIndex(rdm, deletedPresetA);
customPresetIndex = yield getPresetIndex(rdm, manager, deletedPresetA);
is(customPresetIndex, -1,
"Deleted preset " + deletedPresetA + " should not be in the list anymore");
customPresetIndex = getPresetIndex(rdm, deletedPresetB);
customPresetIndex = yield getPresetIndex(rdm, manager, deletedPresetB);
is(customPresetIndex, -1,
"Deleted preset " + deletedPresetB + " should not be in the list anymore");
yield closeRDM(rdm);
});
function getPresetIndex(rdm, presetLabel) {
function testOnePreset(c) {
var getPresetIndex = Task.async(function*(rdm, manager, presetLabel) {
var testOnePreset = Task.async(function*(c) {
if (c == 0) {
return -1;
}
rdm.menulist.selectedIndex = c;
yield setPresetIndex(rdm, manager, c);
let item = rdm.menulist.firstChild.childNodes[c];
if (item.getAttribute("label") === presetLabel) {
return c;
}
return testOnePreset(c - 1);
}
});
return testOnePreset(rdm.menulist.firstChild.childNodes.length - 4);
}
});

View File

@ -15,11 +15,15 @@ Services.scriptloader.loadSubScript(gcliHelpersURI, this);
DevToolsUtils.testing = true;
registerCleanupFunction(() => {
DevToolsUtils.testing = false;
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
Services.prefs.clearUserPref("devtools.responsiveUI.currentPreset");
Services.prefs.clearUserPref("devtools.responsiveUI.customHeight");
Services.prefs.clearUserPref("devtools.responsiveUI.customWidth");
Services.prefs.clearUserPref("devtools.responsiveUI.presets");
Services.prefs.clearUserPref("devtools.responsiveUI.rotate");
});
SimpleTest.requestCompleteLog();
/**
* Open the Responsive Design Mode
* @param {Tab} The browser tab to open it into (defaults to the selected tab).
@ -29,19 +33,26 @@ registerCleanupFunction(() => {
var openRDM = Task.async(function*(tab = gBrowser.selectedTab,
method = "menu") {
let manager = ResponsiveUI.ResponsiveUIManager;
let mgrOn = once(manager, "on");
let opened = once(manager, "on");
let resized = once(manager, "contentResize");
if (method == "menu") {
document.getElementById("Tools:ResponsiveUI").doCommand();
} else {
synthesizeKeyFromKeyTag(document.getElementById("key_responsiveUI"));
}
yield mgrOn;
yield opened;
let rdm = manager.getResponsiveUIForTab(tab);
rdm.transitionsEnabled = false;
registerCleanupFunction(() => {
rdm.transitionsEnabled = true;
});
// Wait for content to resize. This is triggered async by the preset menu
// auto-selecting its default entry once it's in the document.
yield resized;
return {rdm, manager};
});
@ -50,13 +61,15 @@ var openRDM = Task.async(function*(tab = gBrowser.selectedTab,
* @param {rdm} ResponsiveUI instance for the tab
*/
var closeRDM = Task.async(function*(rdm) {
let mgr = ResponsiveUI.ResponsiveUIManager;
let manager = ResponsiveUI.ResponsiveUIManager;
if (!rdm) {
rdm = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
rdm = manager.getResponsiveUIForTab(gBrowser.selectedTab);
}
let mgrOff = mgr.once("off");
let closed = once(manager, "off");
let resized = once(manager, "contentResize");
rdm.close();
yield mgrOff;
yield resized;
yield closed;
});
/**
@ -252,3 +265,38 @@ var selectNode = Task.async(function*(selector, inspector, reason = "test") {
inspector.selection.setNodeFront(nodeFront, reason);
yield updated;
});
function waitForResizeTo(manager, width, height) {
return new Promise(resolve => {
let onResize = (_, data) => {
if (data.width != width || data.height != height) {
return;
}
manager.off("contentResize", onResize);
info(`Got contentResize to ${width} x ${height}`);
resolve();
};
info(`Waiting for contentResize to ${width} x ${height}`);
manager.on("contentResize", onResize);
});
}
var setPresetIndex = Task.async(function*(rdm, manager, index) {
info(`Current preset: ${rdm.menulist.selectedIndex}, change to: ${index}`);
if (rdm.menulist.selectedIndex != index) {
let resized = once(manager, "contentResize");
rdm.menulist.selectedIndex = index;
yield resized;
}
});
var setSize = Task.async(function*(rdm, manager, width, height) {
let size = rdm.getSize();
info(`Current size: ${size.width} x ${size.height}, ` +
`set to: ${width} x ${height}`);
if (size.width != width || size.height != height) {
let resized = waitForResizeTo(manager, width, height);
rdm.setSize(width, height);
yield resized;
}
});

View File

@ -35,28 +35,30 @@ function* testButton(toolbox, Telemetry) {
checkResults("_RESPONSIVE_", Telemetry);
}
function delayedClicks(node, clicks) {
function waitForToggle() {
return new Promise(resolve => {
let clicked = 0;
// See TOOL_DELAY for why we need setTimeout here
setTimeout(function delayedClick() {
info("Clicking button " + node.id);
if (clicked >= clicks) {
node.addEventListener("click", function listener() {
node.removeEventListener("click", listener);
resolve();
});
} else {
setTimeout(delayedClick, TOOL_DELAY);
}
node.click();
clicked++;
}, TOOL_DELAY);
let handler = () => {
manager.off("on", handler);
manager.off("off", handler);
resolve();
};
let manager = ResponsiveUI.ResponsiveUIManager;
manager.on("on", handler);
manager.on("off", handler);
});
}
var delayedClicks = Task.async(function*(node, clicks) {
for (let i = 0; i < clicks; i++) {
info("Clicking button " + node.id);
let toggled = waitForToggle();
node.click();
yield toggled;
// See TOOL_DELAY for why we need setTimeout here
yield DevToolsUtils.waitForTime(TOOL_DELAY);
}
});
function checkResults(histIdFocus, Telemetry) {
let result = Telemetry.prototype.telemetryInfo;