diff --git a/devtools/client/responsivedesign/resize-commands.js b/devtools/client/responsivedesign/resize-commands.js index 28b38a18e9af..4d7f68c13a7a 100644 --- a/devtools/client/responsivedesign/resize-commands.js +++ b/devtools/client/responsivedesign/resize-commands.js @@ -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); } diff --git a/devtools/client/responsivedesign/responsivedesign-child.js b/devtools/client/responsivedesign/responsivedesign-child.js index 410696d957e3..de070c6e48fd 100644 --- a/devtools/client/responsivedesign/responsivedesign-child.js +++ b/devtools/client/responsivedesign/responsivedesign-child.js @@ -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"); diff --git a/devtools/client/responsivedesign/responsivedesign.jsm b/devtools/client/responsivedesign/responsivedesign.jsm index c46765caa54c..367f3b7e139d 100644 --- a/devtools/client/responsivedesign/responsivedesign.jsm +++ b/devtools/client/responsivedesign/responsivedesign.jsm @@ -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 {key: "360x640", width: 360, height: 640}, // Android 4, phones, with @@ -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); }, diff --git a/devtools/client/responsivedesign/test/browser_responsive_cmd.js b/devtools/client/responsivedesign/test/browser_responsive_cmd.js index 0a5c4ba4b09f..2345a754cea7 100644 --- a/devtools/client/responsivedesign/test/browser_responsive_cmd.js +++ b/devtools/client/responsivedesign/test/browser_responsive_cmd.js @@ -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); diff --git a/devtools/client/responsivedesign/test/browser_responsive_devicewidth.js b/devtools/client/responsivedesign/test/browser_responsive_devicewidth.js index 67b3e3bc815b..5795275164e7 100644 --- a/devtools/client/responsivedesign/test/browser_responsive_devicewidth.js +++ b/devtools/client/responsivedesign/test/browser_responsive_devicewidth.js @@ -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(); diff --git a/devtools/client/responsivedesign/test/browser_responsivecomputedview.js b/devtools/client/responsivedesign/test/browser_responsivecomputedview.js index 70fb04821498..67ac831a0eda 100644 --- a/devtools/client/responsivedesign/test/browser_responsivecomputedview.js +++ b/devtools/client/responsivedesign/test/browser_responsivecomputedview.js @@ -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."); diff --git a/devtools/client/responsivedesign/test/browser_responsiveruleview.js b/devtools/client/responsivedesign/test/browser_responsiveruleview.js index d6640710ee8f..2de0bbb44ef9 100644 --- a/devtools/client/responsivedesign/test/browser_responsiveruleview.js +++ b/devtools/client/responsivedesign/test/browser_responsiveruleview.js @@ -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."); diff --git a/devtools/client/responsivedesign/test/browser_responsiveui.js b/devtools/client/responsivedesign/test/browser_responsiveui.js index 2394df39dd70..3cd4a77112ba 100644 --- a/devtools/client/responsivedesign/test/browser_responsiveui.js +++ b/devtools/client/responsivedesign/test/browser_responsiveui.js @@ -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(); diff --git a/devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js b/devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js index 01d402cc6eba..21f205e1f788 100644 --- a/devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js +++ b/devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js @@ -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); -} +}); diff --git a/devtools/client/responsivedesign/test/head.js b/devtools/client/responsivedesign/test/head.js index 6481a4713552..d22f5c9c7364 100644 --- a/devtools/client/responsivedesign/test/head.js +++ b/devtools/client/responsivedesign/test/head.js @@ -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; + } +}); diff --git a/devtools/client/shared/test/browser_telemetry_button_responsive.js b/devtools/client/shared/test/browser_telemetry_button_responsive.js index e749cf09e087..f88eb47f2ba7 100644 --- a/devtools/client/shared/test/browser_telemetry_button_responsive.js +++ b/devtools/client/shared/test/browser_telemetry_button_responsive.js @@ -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;