Bug 1241582 - Port touch, window_close tests from legacy tool; r=ntim
MozReview-Commit-ID: 8Npzkbfekur
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -300,18 +300,19 @@ ResponsiveUI.prototype = {
* to ensure all in-page state is preserved, just like when you move a tab to
* a new window.
*
* For more details, see /devtools/docs/responsive-design-mode.md.
*/
init: Task.async(function* () {
let ui = this;
- // Watch for tab close so we can clean up RDM synchronously
+ // Watch for tab close and window close so we can clean up RDM synchronously
this.tab.addEventListener("TabClose", this);
+ this.browserWindow.addEventListener("unload", this);
// Swap page content from the current tab into a viewport within RDM
this.swap = swapToInnerBrowser({
tab: this.tab,
containerURL: TOOL_URL,
getInnerBrowser: Task.async(function* (containerBrowser) {
let toolWindow = ui.toolWindow = containerBrowser.contentWindow;
toolWindow.addEventListener("message", ui);
@@ -343,24 +344,26 @@ ResponsiveUI.prototype = {
if (this.destroying) {
return false;
}
this.destroying = true;
// If our tab is about to be closed, there's not enough time to exit
// gracefully, but that shouldn't be a problem since the tab will go away.
// So, skip any yielding when we're about to close the tab.
- let isTabClosing = options && options.reason == "TabClose";
+ let isWindowClosing = options && options.reason === "unload";
+ let isTabClosing = (options && options.reason === "TabClose") || isWindowClosing;
// Ensure init has finished before starting destroy
if (!isTabClosing) {
yield this.inited;
}
this.tab.removeEventListener("TabClose", this);
+ this.browserWindow.removeEventListener("unload", this);
this.toolWindow.removeEventListener("message", this);
if (!isTabClosing) {
// Stop the touch event simulator if it was running
yield this.emulationFront.clearTouchEventsOverride();
// Notify the inner browser to stop the frame script
yield message.request(this.toolWindow, "stop-frame-script");
@@ -376,18 +379,20 @@ ResponsiveUI.prototype = {
// Close the debugger client used to speak with emulation actor
let clientClosed = this.client.close();
if (!isTabClosing) {
yield clientClosed;
}
this.client = this.emulationFront = null;
- // Undo the swap and return the content back to a normal tab
- swap.stop();
+ if (!isWindowClosing) {
+ // Undo the swap and return the content back to a normal tab
+ swap.stop();
+ }
this.destroyed = true;
return true;
}),
connectToServer: Task.async(function* () {
if (!DebuggerServer.initialized) {
@@ -402,16 +407,17 @@ ResponsiveUI.prototype = {
handleEvent(event) {
let { browserWindow, tab } = this;
switch (event.type) {
case "message":
this.handleMessage(event);
break;
+ case "unload":
case "TabClose":
ResponsiveUIManager.closeIfNeeded(browserWindow, tab, {
reason: event.type,
});
break;
}
},
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -3,16 +3,17 @@ tags = devtools
subsuite = devtools
# !e10s: RDM only works for remote tabs
skip-if = !e10s
support-files =
devices.json
doc_page_state.html
geolocation.html
head.js
+ touch.html
!/devtools/client/commandline/test/helpers.js
!/devtools/client/framework/test/shared-head.js
!/devtools/client/framework/test/shared-redux-head.js
!/devtools/client/inspector/test/shared-head.js
!/devtools/client/shared/test/test-actor.js
!/devtools/client/shared/test/test-actor-registry.js
[browser_device_change.js]
@@ -31,8 +32,9 @@ support-files =
[browser_resize_cmd.js]
[browser_screenshot_button.js]
[browser_shutdown_close_sync.js]
[browser_toolbox_computed_view.js]
[browser_toolbox_rule_view.js]
[browser_toolbox_swap_browsers.js]
[browser_touch_simulation.js]
[browser_viewport_basics.js]
+[browser_window_close.js]
--- a/devtools/client/responsive.html/test/browser/browser_touch_simulation.js
+++ b/devtools/client/responsive.html/test/browser/browser_touch_simulation.js
@@ -1,25 +1,240 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test global touch simulation button
-const TEST_URL = "data:text/html;charset=utf-8,";
+const TEST_URL = `${URL_ROOT}touch.html`;
+const PREF_DOM_META_VIEWPORT_ENABLED = "dom.meta-viewport.enabled";
addRDMTask(TEST_URL, function* ({ ui }) {
- let { store, document } = ui.toolWindow;
- let touchButton = document.querySelector("#global-touch-simulation-button");
+ yield waitBootstrap(ui);
+ yield testWithNoTouch(ui);
+ yield enableTouchSimulation(ui);
+ yield testWithTouch(ui);
+ yield testWithMetaViewportEnabled(ui);
+ yield testWithMetaViewportDisabled(ui);
+ testTouchButton(ui);
+});
+
+function* testWithNoTouch(ui) {
+ yield injectEventUtils(ui);
+ yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+ let { EventUtils } = content;
+
+ let div = content.document.querySelector("div");
+ let x = 0, y = 0;
+
+ info("testWithNoTouch: Initial test parameter and mouse mouse outside div");
+ x = -1; y = -1;
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mousemove", isSynthesized: false }, content);
+ div.style.transform = "none";
+ div.style.backgroundColor = "";
+
+ info("testWithNoTouch: Move mouse into the div element");
+ yield EventUtils.synthesizeMouseAtCenter(div,
+ { type: "mousemove", isSynthesized: false }, content);
+ is(div.style.backgroundColor, "red", "mouseenter or mouseover should work");
+
+ info("testWithNoTouch: Drag the div element");
+ yield EventUtils.synthesizeMouseAtCenter(div,
+ { type: "mousedown", isSynthesized: false }, content);
+ x = 100; y = 100;
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mousemove", isSynthesized: false }, content);
+ is(div.style.transform, "none", "touchmove shouldn't work");
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mouseup", isSynthesized: false }, content);
+
+ info("testWithNoTouch: Move mouse out of the div element");
+ x = -1; y = -1;
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mousemove", isSynthesized: false }, content);
+ is(div.style.backgroundColor, "blue", "mouseout or mouseleave should work");
+
+ info("testWithNoTouch: Click the div element");
+ yield EventUtils.synthesizeClick(div);
+ is(div.dataset.isDelay, "false",
+ "300ms delay between touch events and mouse events should not work");
+ });
+}
+
+function* testWithTouch(ui) {
+ yield injectEventUtils(ui);
+
+ yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+ let { EventUtils } = content;
+
+ let div = content.document.querySelector("div");
+ let x = 0, y = 0;
+
+ info("testWithTouch: Initial test parameter and mouse mouse outside div");
+ x = -1; y = -1;
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mousemove", isSynthesized: false }, content);
+ div.style.transform = "none";
+ div.style.backgroundColor = "";
+
+ info("testWithTouch: Move mouse into the div element");
+ yield EventUtils.synthesizeMouseAtCenter(div,
+ { type: "mousemove", isSynthesized: false }, content);
+ isnot(div.style.backgroundColor, "red",
+ "mouseenter or mouseover should not work");
- // Wait until the viewport has been added
- yield waitUntilState(store, state => state.viewports.length == 1);
- yield waitForFrameLoad(ui, TEST_URL);
+ info("testWithTouch: Drag the div element");
+ yield EventUtils.synthesizeMouseAtCenter(div,
+ { type: "mousedown", isSynthesized: false }, content);
+ x = 100; y = 100;
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mousemove", isSynthesized: false }, content);
+ isnot(div.style.transform, "none", "touchmove should work");
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mouseup", isSynthesized: false }, content);
+
+ info("testWithTouch: Move mouse out of the div element");
+ x = -1; y = -1;
+ yield EventUtils.synthesizeMouse(div, x, y,
+ { type: "mousemove", isSynthesized: false }, content);
+ isnot(div.style.backgroundColor, "blue",
+ "mouseout or mouseleave should not work");
+ });
+}
+
+function* testWithMetaViewportEnabled(ui) {
+ yield SpecialPowers.pushPrefEnv({set: [[PREF_DOM_META_VIEWPORT_ENABLED, true]]});
+
+ yield injectEventUtils(ui);
+
+ yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+ let { synthesizeClick } = content.EventUtils;
+
+ let meta = content.document.querySelector("meta[name=viewport]");
+ let div = content.document.querySelector("div");
+ div.dataset.isDelay = "false";
+
+ info("testWithMetaViewportEnabled: " +
+ "click the div element with <meta name='viewport'>");
+ meta.content = "";
+ yield synthesizeClick(div);
+ is(div.dataset.isDelay, "true",
+ "300ms delay between touch events and mouse events should work");
+
+ info("testWithMetaViewportEnabled: " +
+ "click the div element with " +
+ "<meta name='viewport' content='user-scalable=no'>");
+ meta.content = "user-scalable=no";
+ yield synthesizeClick(div);
+ is(div.dataset.isDelay, "false",
+ "300ms delay between touch events and mouse events should not work");
+
+ info("testWithMetaViewportEnabled: " +
+ "click the div element with " +
+ "<meta name='viewport' content='minimum-scale=maximum-scale'>");
+ meta.content = "minimum-scale=maximum-scale";
+ yield synthesizeClick(div);
+ is(div.dataset.isDelay, "false",
+ "300ms delay between touch events and mouse events should not work");
+
+ info("testWithMetaViewportEnabled: " +
+ "click the div element with " +
+ "<meta name='viewport' content='width=device-width'>");
+ meta.content = "width=device-width";
+ yield synthesizeClick(div);
+ is(div.dataset.isDelay, "false",
+ "300ms delay between touch events and mouse events should not work");
+ });
+}
+
+function* testWithMetaViewportDisabled(ui) {
+ yield SpecialPowers.pushPrefEnv({set: [[PREF_DOM_META_VIEWPORT_ENABLED, false]]});
+
+ yield injectEventUtils(ui);
+
+ yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+ let { synthesizeClick } = content.EventUtils;
+
+ let meta = content.document.querySelector("meta[name=viewport]");
+ let div = content.document.querySelector("div");
+ div.dataset.isDelay = "false";
+
+ info("testWithMetaViewportDisabled: click the div with <meta name='viewport'>");
+ meta.content = "";
+ yield synthesizeClick(div);
+ is(div.dataset.isDelay, "true",
+ "300ms delay between touch events and mouse events should work");
+ });
+}
+
+function testTouchButton(ui) {
+ let { document } = ui.toolWindow;
+ let touchButton = document.querySelector("#global-touch-simulation-button");
ok(!touchButton.classList.contains("active"),
"Touch simulation is not active by default.");
touchButton.click();
ok(touchButton.classList.contains("active"),
"Touch simulation is started on click.");
-});
+
+ touchButton.click();
+
+ ok(!touchButton.classList.contains("active"),
+ "Touch simulation is stopped on click.");
+}
+
+function* waitBootstrap(ui) {
+ let { store } = ui.toolWindow;
+
+ yield waitUntilState(store, state => state.viewports.length == 1);
+ yield waitForFrameLoad(ui, TEST_URL);
+}
+
+function* injectEventUtils(ui) {
+ yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+ if ("EventUtils" in content) {
+ return;
+ }
+
+ let EventUtils = content.EventUtils = {};
+
+ EventUtils.window = {};
+ EventUtils.parent = EventUtils.window;
+ /* eslint-disable camelcase */
+ EventUtils._EU_Ci = Components.interfaces;
+ EventUtils._EU_Cc = Components.classes;
+ /* eslint-enable camelcase */
+ // EventUtils' `sendChar` function relies on the navigator to synthetize events.
+ EventUtils.navigator = content.navigator;
+ EventUtils.KeyboardEvent = content.KeyboardEvent;
+
+ EventUtils.synthesizeClick = element => new Promise(resolve => {
+ element.addEventListener("click", function onClick() {
+ element.removeEventListener("click", onClick);
+ resolve();
+ });
+
+ EventUtils.synthesizeMouseAtCenter(element,
+ { type: "mousedown", isSynthesized: false }, content);
+ EventUtils.synthesizeMouseAtCenter(element,
+ { type: "mouseup", isSynthesized: false }, content);
+ });
+
+ Services.scriptloader.loadSubScript(
+ "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+ });
+}
+
+const enableTouchSimulation = ui => new Promise(
+ Task.async(function* (resolve) {
+ let browser = ui.getViewportBrowser();
+
+ browser.addEventListener("mozbrowserloadend", function onLoad() {
+ browser.removeEventListener("mozbrowserloadend", onLoad);
+ resolve();
+ });
+
+ yield ui.updateTouchSimulation(true);
+ }));
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_window_close.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ window.open("data:text/html;charset=utf-8,", "_blank");
+ let newWindow = yield newWindowPromise;
+
+ newWindow.focus();
+ yield once(newWindow.gBrowser, "load", true);
+
+ let tab = newWindow.gBrowser.selectedTab;
+ yield openRDM(tab);
+
+ // Close the window on a tab with an active responsive design UI and
+ // wait for the UI to gracefully shutdown. This has leaked the window
+ // in the past.
+ ok(ResponsiveUIManager.isActiveForTab(tab),
+ "ResponsiveUI should be active for tab when the window is closed");
+ let offPromise = once(ResponsiveUIManager, "off");
+ yield BrowserTestUtils.closeWindow(newWindow);
+ yield offPromise;
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/touch.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+
+<meta charset="utf-8" />
+<meta name="viewport" />
+<title>test</title>
+
+
+<style>
+ div {
+ border :1px solid red;
+ width: 100px; height: 100px;
+ }
+</style>
+
+<div data-is-delay="false"></div>
+
+<script type="text/javascript;version=1.8">
+ "use strict";
+ let div = document.querySelector("div");
+ let initX, initY;
+ let previousEvent = "", touchendTime = 0;
+ let updatePreviousEvent = function (e) {
+ previousEvent = e.type;
+ };
+
+ div.style.transform = "none";
+ div.style.backgroundColor = "";
+
+ div.addEventListener("touchstart", function (evt) {
+ let touch = evt.changedTouches[0];
+ initX = touch.pageX;
+ initY = touch.pageY;
+ updatePreviousEvent(evt);
+ }, true);
+
+ div.addEventListener("touchmove", function (evt) {
+ let touch = evt.changedTouches[0];
+ let deltaX = touch.pageX - initX;
+ let deltaY = touch.pageY - initY;
+ div.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
+ updatePreviousEvent(evt);
+ }, true);
+
+ div.addEventListener("touchend", function (evt) {
+ if (!evt.touches.length) {
+ div.style.transform = "none";
+ }
+ touchendTime = performance.now();
+ updatePreviousEvent(evt);
+ }, true);
+
+ div.addEventListener("mouseenter", function (evt) {
+ div.style.backgroundColor = "red";
+ updatePreviousEvent(evt);
+ }, true);
+ div.addEventListener("mouseover", function(evt) {
+ div.style.backgroundColor = "red";
+ updatePreviousEvent(evt);
+ }, true);
+
+ div.addEventListener("mouseout", function (evt) {
+ div.style.backgroundColor = "blue";
+ updatePreviousEvent(evt);
+ }, true);
+
+ div.addEventListener("mouseleave", function (evt) {
+ div.style.backgroundColor = "blue";
+ updatePreviousEvent(evt);
+ }, true);
+
+ div.addEventListener("mousedown", function (evt) {
+ if (previousEvent === "touchend" && touchendTime !== 0) {
+ let now = performance.now();
+ div.dataset.isDelay = ((now - touchendTime) >= 300);
+ } else {
+ div.dataset.isDelay = false;
+ }
+ updatePreviousEvent(evt);
+ }, true);
+
+ div.addEventListener("mousemove", updatePreviousEvent, true);
+
+ div.addEventListener("mouseup", updatePreviousEvent, true);
+
+ div.addEventListener("click", updatePreviousEvent, true);
+</script>