mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
Bug 1074180 - Convert touch simulator to frame script for e10s. r=ochameau
--HG-- rename : toolkit/devtools/touch-events.js => toolkit/devtools/touch/simulator-content.js extra : commitid : FXROVMfi1NS
This commit is contained in:
parent
834ce811ca
commit
c39e19e968
@ -10,13 +10,9 @@ let isMulet = "ResponsiveUI" in browserWindow;
|
||||
function enableTouch() {
|
||||
let require = Cu.import('resource://gre/modules/devtools/Loader.jsm', {})
|
||||
.devtools.require;
|
||||
let { TouchEventHandler } = require('devtools/touch-events');
|
||||
let chromeEventHandler = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler || window;
|
||||
let touchEventHandler = new TouchEventHandler(chromeEventHandler);
|
||||
touchEventHandler.start();
|
||||
let { TouchEventSimulator } = require('devtools/toolkit/touch/simulator');
|
||||
let touchEventSimulator = new TouchEventSimulator(shell.contentBrowser);
|
||||
touchEventSimulator.start();
|
||||
}
|
||||
|
||||
function setupButtons() {
|
||||
|
@ -19,7 +19,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
|
||||
var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
let { showDoorhanger } = require("devtools/shared/doorhanger");
|
||||
let {TouchEventHandler} = require("devtools/touch-events");
|
||||
let { TouchEventSimulator } = require("devtools/toolkit/touch/simulator");
|
||||
let { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
|
||||
|
||||
@ -134,7 +135,6 @@ function ResponsiveUI(aWindow, aTab)
|
||||
this.container = aWindow.gBrowser.getBrowserContainer(this.browser);
|
||||
this.stack = this.container.querySelector(".browserStack");
|
||||
this._telemetry = new Telemetry();
|
||||
this.e10s = !this.browser.contentWindow;
|
||||
|
||||
let childOn = () => {
|
||||
this.mm.removeMessageListener("ResponsiveMode:Start:Done", childOn);
|
||||
@ -219,11 +219,9 @@ function ResponsiveUI(aWindow, aTab)
|
||||
|
||||
this._telemetry.toolOpened("responsive");
|
||||
|
||||
if (!this.e10s) {
|
||||
// Touch events support
|
||||
this.touchEnableBefore = false;
|
||||
this.touchEventHandler = new TouchEventHandler(this.browser);
|
||||
}
|
||||
this.touchEventSimulator = new TouchEventSimulator(this.browser);
|
||||
|
||||
// Hook to display promotional Developer Edition doorhanger. Only displayed once.
|
||||
showDoorhanger({
|
||||
@ -274,9 +272,7 @@ ResponsiveUI.prototype = {
|
||||
this.closebutton.removeEventListener("command", this.bound_close, true);
|
||||
this.addbutton.removeEventListener("command", this.bound_addPreset, true);
|
||||
this.removebutton.removeEventListener("command", this.bound_removePreset, true);
|
||||
if (!this.e10s) {
|
||||
this.touchbutton.removeEventListener("command", this.bound_touch, true);
|
||||
}
|
||||
|
||||
// Removed elements.
|
||||
this.container.removeChild(this.toolbar);
|
||||
@ -295,8 +291,8 @@ ResponsiveUI.prototype = {
|
||||
this.stack.removeAttribute("responsivemode");
|
||||
|
||||
ActiveTabs.delete(this.tab);
|
||||
if (!this.e10s && this.touchEventHandler) {
|
||||
this.touchEventHandler.stop();
|
||||
if (this.touchEventSimulator) {
|
||||
this.touchEventSimulator.stop();
|
||||
}
|
||||
this._telemetry.toolClosed("responsive");
|
||||
let childOff = () => {
|
||||
@ -441,14 +437,12 @@ ResponsiveUI.prototype = {
|
||||
this.toolbar.appendChild(this.menulist);
|
||||
this.toolbar.appendChild(this.rotatebutton);
|
||||
|
||||
if (!this.e10s) {
|
||||
this.touchbutton = this.chromeDoc.createElement("toolbarbutton");
|
||||
this.touchbutton.setAttribute("tabindex", "0");
|
||||
this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch"));
|
||||
this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
|
||||
this.touchbutton.addEventListener("command", this.bound_touch, true);
|
||||
this.toolbar.appendChild(this.touchbutton);
|
||||
}
|
||||
|
||||
this.toolbar.appendChild(this.screenshotbutton);
|
||||
|
||||
@ -793,19 +787,13 @@ ResponsiveUI.prototype = {
|
||||
* Enable/Disable mouse -> touch events translation.
|
||||
*/
|
||||
enableTouch: function RUI_enableTouch() {
|
||||
if (!this.touchEventHandler.enabled) {
|
||||
let isReloadNeeded = this.touchEventHandler.start();
|
||||
this.touchbutton.setAttribute("checked", "true");
|
||||
return isReloadNeeded;
|
||||
}
|
||||
return false;
|
||||
return this.touchEventSimulator.start();
|
||||
},
|
||||
|
||||
disableTouch: function RUI_disableTouch() {
|
||||
if (this.touchEventHandler.enabled) {
|
||||
this.touchEventHandler.stop();
|
||||
this.touchbutton.removeAttribute("checked");
|
||||
}
|
||||
return this.touchEventSimulator.stop();
|
||||
},
|
||||
|
||||
hideTouchNotification: function RUI_hideTouchNotification() {
|
||||
@ -816,12 +804,12 @@ ResponsiveUI.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
toggleTouch: function RUI_toggleTouch() {
|
||||
toggleTouch: Task.async(function*() {
|
||||
this.hideTouchNotification();
|
||||
if (this.touchEventHandler.enabled) {
|
||||
if (this.touchEventSimulator.enabled) {
|
||||
this.disableTouch();
|
||||
} else {
|
||||
let isReloadNeeded = this.enableTouch();
|
||||
let isReloadNeeded = yield this.enableTouch();
|
||||
if (isReloadNeeded) {
|
||||
if (Services.prefs.getBoolPref("devtools.responsiveUI.no-reload-notification")) {
|
||||
return;
|
||||
@ -851,7 +839,7 @@ ResponsiveUI.prototype = {
|
||||
buttons);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Change the size of the browser.
|
||||
|
@ -12,6 +12,5 @@ skip-if = e10s # Bug ??????
|
||||
skip-if = e10s # Bug ??????
|
||||
[browser_responsiveui.js]
|
||||
[browser_responsiveui_touch.js]
|
||||
skip-if = e10s # Bug ?????? - [e10s] re-introduce touch feature in responsive mode
|
||||
[browser_responsiveuiaddcustompreset.js]
|
||||
[browser_responsive_devicewidth.js]
|
||||
|
@ -1,66 +1,50 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
let url = "http://mochi.test:8888/browser/browser/devtools/responsivedesign/test/touch.html";
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/" +
|
||||
"responsivedesign/test/touch.html";
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URI);
|
||||
let mgr = ResponsiveUI.ResponsiveUIManager;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
waitForFocus(startTest, content);
|
||||
}, true);
|
||||
|
||||
content.location = url;
|
||||
|
||||
function startTest() {
|
||||
mgr.once("on", function() {executeSoon(testWithNoTouch)});
|
||||
mgr.once("off", function() {executeSoon(finishUp)});
|
||||
let mgrOn = once(mgr, "on");
|
||||
mgr.toggle(window, gBrowser.selectedTab);
|
||||
}
|
||||
yield mgrOn;
|
||||
yield testWithNoTouch();
|
||||
yield mgr.getResponsiveUIForTab(gBrowser.selectedTab).enableTouch();
|
||||
yield testWithTouch();
|
||||
yield mgr.getResponsiveUIForTab(gBrowser.selectedTab).disableTouch();
|
||||
yield testWithNoTouch();
|
||||
let mgrOff = once(mgr, "off");
|
||||
mgr.toggle(window, gBrowser.selectedTab);
|
||||
yield mgrOff;
|
||||
});
|
||||
|
||||
function testWithNoTouch() {
|
||||
function* testWithNoTouch() {
|
||||
let div = content.document.querySelector("div");
|
||||
let x = 2, y = 2;
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
|
||||
yield BrowserTestUtils.synthesizeMouse("div", x, y,
|
||||
{ type: "mousedown", isSynthesized: false }, gBrowser.selectedBrowser);
|
||||
x += 20; y += 10;
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
|
||||
is(div.style.transform, "", "touch didn't work");
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
|
||||
testWithTouch();
|
||||
yield BrowserTestUtils.synthesizeMouse("div", x, y,
|
||||
{ type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser);
|
||||
is(div.style.transform, "none", "touch didn't work");
|
||||
yield BrowserTestUtils.synthesizeMouse("div", x, y,
|
||||
{ type: "mouseup", isSynthesized: false }, gBrowser.selectedBrowser);
|
||||
}
|
||||
|
||||
function testWithTouch() {
|
||||
mgr.getResponsiveUIForTab(gBrowser.selectedTab).enableTouch();
|
||||
function* testWithTouch() {
|
||||
let div = content.document.querySelector("div");
|
||||
let x = 2, y = 2;
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
|
||||
yield BrowserTestUtils.synthesizeMouse("div", x, y,
|
||||
{ type: "mousedown", isSynthesized: false }, gBrowser.selectedBrowser);
|
||||
x += 20; y += 10;
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
|
||||
yield BrowserTestUtils.synthesizeMouse("div", x, y,
|
||||
{ type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser);
|
||||
is(div.style.transform, "translate(20px, 10px)", "touch worked");
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
|
||||
yield BrowserTestUtils.synthesizeMouse("div", x, y,
|
||||
{ type: "mouseup", isSynthesized: false }, gBrowser.selectedBrowser);
|
||||
is(div.style.transform, "none", "end event worked");
|
||||
mgr.toggle(window, gBrowser.selectedTab);
|
||||
}
|
||||
|
||||
function testWithTouchAgain() {
|
||||
mgr.getResponsiveUIForTab(gBrowser.selectedTab).disableTouch();
|
||||
let div = content.document.querySelector("div");
|
||||
let x = 2, y = 2;
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
|
||||
x += 20; y += 10;
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
|
||||
is(div.style.transform, "", "touch didn't work");
|
||||
EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
|
||||
finishUp();
|
||||
}
|
||||
|
||||
|
||||
function finishUp() {
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,11 @@ let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
|
||||
Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
|
||||
|
||||
DevToolsUtils.testing = true;
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
registerCleanupFunction(() => {
|
||||
DevToolsUtils.testing = false;
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@
|
||||
var div = document.querySelector("div");
|
||||
var initX, initY;
|
||||
|
||||
div.style.transform = "none";
|
||||
|
||||
div.addEventListener("touchstart", function(evt) {
|
||||
var touch = evt.changedTouches[0];
|
||||
|
@ -87,7 +87,6 @@ BuiltinProvider.prototype = {
|
||||
"devtools/styleinspector/css-logic": "resource://gre/modules/devtools/styleinspector/css-logic",
|
||||
"devtools/css-color": "resource://gre/modules/devtools/css-color",
|
||||
"devtools/output-parser": "resource://gre/modules/devtools/output-parser",
|
||||
"devtools/touch-events": "resource://gre/modules/devtools/touch-events",
|
||||
"devtools/client": "resource://gre/modules/devtools/client",
|
||||
"devtools/pretty-fast": "resource://gre/modules/devtools/pretty-fast.js",
|
||||
"devtools/jsbeautify": "resource://gre/modules/devtools/jsbeautify/beautify.js",
|
||||
@ -146,7 +145,6 @@ SrcdirProvider.prototype = {
|
||||
let cssLogicURI = this.fileURI(OS.Path.join(toolkitDir, "styleinspector", "css-logic"));
|
||||
let cssColorURI = this.fileURI(OS.Path.join(toolkitDir, "css-color"));
|
||||
let outputParserURI = this.fileURI(OS.Path.join(toolkitDir, "output-parser"));
|
||||
let touchEventsURI = this.fileURI(OS.Path.join(toolkitDir, "touch-events"));
|
||||
let clientURI = this.fileURI(OS.Path.join(toolkitDir, "client"));
|
||||
let prettyFastURI = this.fileURI(OS.Path.join(toolkitDir), "pretty-fast.js");
|
||||
let jsBeautifyURI = this.fileURI(OS.Path.join(toolkitDir, "jsbeautify", "beautify.js"));
|
||||
@ -174,7 +172,6 @@ SrcdirProvider.prototype = {
|
||||
"devtools/styleinspector/css-logic": cssLogicURI,
|
||||
"devtools/css-color": cssColorURI,
|
||||
"devtools/output-parser": outputParserURI,
|
||||
"devtools/touch-events": touchEventsURI,
|
||||
"devtools/client": clientURI,
|
||||
"devtools/pretty-fast": prettyFastURI,
|
||||
"devtools/jsbeautify": jsBeautifyURI,
|
||||
|
@ -19,6 +19,7 @@ DIRS += [
|
||||
'shared',
|
||||
'styleinspector',
|
||||
'tern',
|
||||
'touch',
|
||||
'transport',
|
||||
'webconsole'
|
||||
]
|
||||
@ -36,7 +37,6 @@ EXTRA_JS_MODULES.devtools += [
|
||||
'event-parsers.js',
|
||||
'output-parser.js',
|
||||
'path.js',
|
||||
'touch-events.js',
|
||||
'worker-loader.js',
|
||||
]
|
||||
|
||||
|
@ -1,294 +0,0 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
let {Ci, Cu} = require("chrome");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let savedTouchEventsEnabled =
|
||||
Services.prefs.getIntPref("dom.w3c_touch_events.enabled");
|
||||
|
||||
let systemAppOrigin = (function() {
|
||||
let systemOrigin = "_";
|
||||
try {
|
||||
systemOrigin = Services.io.newURI(
|
||||
Services.prefs.getCharPref("b2g.system_manifest_url"), null, null)
|
||||
.prePath;
|
||||
} catch(e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
return systemOrigin;
|
||||
})();
|
||||
|
||||
let trackedWindows = new WeakMap();
|
||||
|
||||
// =================== Touch ====================
|
||||
// Simulate touch events on desktop
|
||||
function TouchEventHandler(window) {
|
||||
// Returns an already instanciated handler for this window
|
||||
let cached = trackedWindows.get(window);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
let contextMenuTimeout = 0;
|
||||
let threshold = 25;
|
||||
try {
|
||||
threshold = Services.prefs.getIntPref("ui.dragThresholdX");
|
||||
} catch(e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
let delay = 500;
|
||||
try {
|
||||
delay = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
|
||||
} catch(e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
let handler = {
|
||||
enabled: false,
|
||||
events: ["mousedown", "mousemove", "mouseup", "touchstart", "touchend"],
|
||||
start() {
|
||||
if (this.enabled) {
|
||||
return false;
|
||||
}
|
||||
this.enabled = true;
|
||||
let isReloadNeeded =
|
||||
Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 1;
|
||||
Services.prefs.setIntPref("dom.w3c_touch_events.enabled", 1);
|
||||
this.events.forEach((function(evt) {
|
||||
// Only listen trusted events to prevent messing with
|
||||
// event dispatched manually within content documents
|
||||
window.addEventListener(evt, this, true, false);
|
||||
}).bind(this));
|
||||
return isReloadNeeded;
|
||||
},
|
||||
stop() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
this.enabled = false;
|
||||
Services.prefs.setIntPref("dom.w3c_touch_events.enabled",
|
||||
savedTouchEventsEnabled);
|
||||
this.events.forEach((function(evt) {
|
||||
window.removeEventListener(evt, this, true);
|
||||
}).bind(this));
|
||||
},
|
||||
handleEvent(evt) {
|
||||
// The gaia system window use an hybrid system even on the device which is
|
||||
// a mix of mouse/touch events. So let's not cancel *all* mouse events
|
||||
// if it is the current target.
|
||||
let content = this.getContent(evt.target);
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
let isSystemWindow = content.location.toString()
|
||||
.startsWith(systemAppOrigin);
|
||||
|
||||
// App touchstart & touchend should also be dispatched on the system app
|
||||
// to match on-device behavior.
|
||||
if (evt.type.startsWith("touch") && !isSystemWindow) {
|
||||
let sysFrame = content.realFrameElement;
|
||||
if (!sysFrame) {
|
||||
return;
|
||||
}
|
||||
let sysDocument = sysFrame.ownerDocument;
|
||||
let sysWindow = sysDocument.defaultView;
|
||||
|
||||
let touchEvent = sysDocument.createEvent("touchevent");
|
||||
let touch = evt.touches[0] || evt.changedTouches[0];
|
||||
let point = sysDocument.createTouch(sysWindow, sysFrame, 0,
|
||||
touch.pageX, touch.pageY,
|
||||
touch.screenX, touch.screenY,
|
||||
touch.clientX, touch.clientY,
|
||||
1, 1, 0, 0);
|
||||
|
||||
let touches = sysDocument.createTouchList(point);
|
||||
let targetTouches = touches;
|
||||
let changedTouches = touches;
|
||||
touchEvent.initTouchEvent(evt.type, true, true, sysWindow, 0,
|
||||
false, false, false, false,
|
||||
touches, targetTouches, changedTouches);
|
||||
sysFrame.dispatchEvent(touchEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore all but real mouse event coming from physical mouse
|
||||
// (especially ignore mouse event being dispatched from a touch event)
|
||||
if (evt.button ||
|
||||
evt.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE ||
|
||||
evt.isSynthesized) {
|
||||
return;
|
||||
}
|
||||
|
||||
let eventTarget = this.target;
|
||||
let type = "";
|
||||
switch (evt.type) {
|
||||
case "mousedown":
|
||||
this.target = evt.target;
|
||||
|
||||
contextMenuTimeout =
|
||||
this.sendContextMenu(evt.target, evt.pageX, evt.pageY);
|
||||
|
||||
this.cancelClick = false;
|
||||
this.startX = evt.pageX;
|
||||
this.startY = evt.pageY;
|
||||
|
||||
// Capture events so if a different window show up the events
|
||||
// won't be dispatched to something else.
|
||||
evt.target.setCapture(false);
|
||||
|
||||
type = "touchstart";
|
||||
break;
|
||||
|
||||
case "mousemove":
|
||||
if (!eventTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.cancelClick) {
|
||||
if (Math.abs(this.startX - evt.pageX) > threshold ||
|
||||
Math.abs(this.startY - evt.pageY) > threshold) {
|
||||
this.cancelClick = true;
|
||||
content.clearTimeout(contextMenuTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
type = "touchmove";
|
||||
break;
|
||||
|
||||
case "mouseup":
|
||||
if (!eventTarget) {
|
||||
return;
|
||||
}
|
||||
this.target = null;
|
||||
|
||||
content.clearTimeout(contextMenuTimeout);
|
||||
type = "touchend";
|
||||
|
||||
// Only register click listener after mouseup to ensure
|
||||
// catching only real user click. (Especially ignore click
|
||||
// being dispatched on form submit)
|
||||
if (evt.detail == 1) {
|
||||
window.addEventListener("click", this, true, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case "click":
|
||||
// Mouse events has been cancelled so dispatch a sequence
|
||||
// of events to where touchend has been fired
|
||||
evt.preventDefault();
|
||||
evt.stopImmediatePropagation();
|
||||
|
||||
window.removeEventListener("click", this, true, false);
|
||||
|
||||
if (this.cancelClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
content.setTimeout(function dispatchMouseEvents(self) {
|
||||
try {
|
||||
self.fireMouseEvent("mousedown", evt);
|
||||
self.fireMouseEvent("mousemove", evt);
|
||||
self.fireMouseEvent("mouseup", evt);
|
||||
} catch(e) {
|
||||
Cu.reportError("Exception in touch event helper: " + e);
|
||||
}
|
||||
}, 0, this);
|
||||
return;
|
||||
}
|
||||
|
||||
let target = eventTarget || this.target;
|
||||
if (target && type) {
|
||||
this.sendTouchEvent(evt, target, type);
|
||||
}
|
||||
|
||||
if (!isSystemWindow) {
|
||||
evt.preventDefault();
|
||||
evt.stopImmediatePropagation();
|
||||
}
|
||||
},
|
||||
fireMouseEvent(type, evt) {
|
||||
let content = this.getContent(evt.target);
|
||||
let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true, 0,
|
||||
Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
||||
},
|
||||
sendContextMenu(target, x, y) {
|
||||
let doc = target.ownerDocument;
|
||||
let evt = doc.createEvent("MouseEvent");
|
||||
evt.initMouseEvent("contextmenu", true, true, doc.defaultView,
|
||||
0, x, y, x, y, false, false, false, false,
|
||||
0, null);
|
||||
|
||||
let content = this.getContent(target);
|
||||
let timeout = content.setTimeout((function contextMenu() {
|
||||
target.dispatchEvent(evt);
|
||||
this.cancelClick = true;
|
||||
}).bind(this), delay);
|
||||
|
||||
return timeout;
|
||||
},
|
||||
sendTouchEvent(evt, target, name) {
|
||||
function clone(obj) {
|
||||
return Cu.cloneInto(obj, target);
|
||||
}
|
||||
// When running OOP b2g desktop, we need to send the touch events
|
||||
// using the mozbrowser api on the unwrapped frame.
|
||||
if (target.localName == "iframe" && target.mozbrowser === true) {
|
||||
if (name == "touchstart") {
|
||||
this.touchstartTime = Date.now();
|
||||
} else if (name == "touchend") {
|
||||
// If we have a "fast" tap, don't send a click as both will be turned
|
||||
// into a click and that breaks eg. checkboxes.
|
||||
if (Date.now() - this.touchstartTime < delay) {
|
||||
this.cancelClick = true;
|
||||
}
|
||||
}
|
||||
let unwrapped = XPCNativeWrapper.unwrap(target);
|
||||
unwrapped.sendTouchEvent(name, clone([0]), // event type, id
|
||||
clone([evt.clientX]), // x
|
||||
clone([evt.clientY]), // y
|
||||
clone([1]), clone([1]), // rx, ry
|
||||
clone([0]), clone([0]), // rotation, force
|
||||
1); // count
|
||||
return;
|
||||
}
|
||||
let document = target.ownerDocument;
|
||||
let content = this.getContent(target);
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
let touchEvent = document.createEvent("touchevent");
|
||||
let point = document.createTouch(content, target, 0,
|
||||
evt.pageX, evt.pageY,
|
||||
evt.screenX, evt.screenY,
|
||||
evt.clientX, evt.clientY,
|
||||
1, 1, 0, 0);
|
||||
let touches = document.createTouchList(point);
|
||||
let targetTouches = touches;
|
||||
let changedTouches = touches;
|
||||
touchEvent.initTouchEvent(name, true, true, content, 0,
|
||||
false, false, false, false,
|
||||
touches, targetTouches, changedTouches);
|
||||
target.dispatchEvent(touchEvent);
|
||||
},
|
||||
getContent(target) {
|
||||
let win = (target && target.ownerDocument)
|
||||
? target.ownerDocument.defaultView
|
||||
: null;
|
||||
return win;
|
||||
}
|
||||
};
|
||||
trackedWindows.set(window, handler);
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
exports.TouchEventHandler = TouchEventHandler;
|
10
toolkit/devtools/touch/moz.build
Normal file
10
toolkit/devtools/touch/moz.build
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.touch += [
|
||||
'simulator-content.js',
|
||||
'simulator.js',
|
||||
]
|
304
toolkit/devtools/touch/simulator-content.js
Normal file
304
toolkit/devtools/touch/simulator-content.js
Normal file
@ -0,0 +1,304 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals addMessageListener, sendAsyncMessage, addEventListener,
|
||||
removeEventListener */
|
||||
"use strict";
|
||||
|
||||
let { interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let systemAppOrigin = (function() {
|
||||
let systemOrigin = "_";
|
||||
try {
|
||||
systemOrigin = Services.io.newURI(
|
||||
Services.prefs.getCharPref("b2g.system_manifest_url"), null, null)
|
||||
.prePath;
|
||||
} catch(e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
return systemOrigin;
|
||||
})();
|
||||
|
||||
let threshold = 25;
|
||||
try {
|
||||
threshold = Services.prefs.getIntPref("ui.dragThresholdX");
|
||||
} catch(e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
let delay = 500;
|
||||
try {
|
||||
delay = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
|
||||
} catch(e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate touch events for platforms where they aren't generally available.
|
||||
* This frame script is managed by `simulator.js`.
|
||||
*/
|
||||
let simulator = {
|
||||
events: [
|
||||
"mousedown",
|
||||
"mousemove",
|
||||
"mouseup",
|
||||
"touchstart",
|
||||
"touchend",
|
||||
],
|
||||
|
||||
messages: [
|
||||
"TouchEventSimulator:Start",
|
||||
"TouchEventSimulator:Stop",
|
||||
],
|
||||
|
||||
contextMenuTimeout: null,
|
||||
|
||||
init() {
|
||||
this.messages.forEach(msgName => {
|
||||
addMessageListener(msgName, this);
|
||||
});
|
||||
},
|
||||
|
||||
receiveMessage(msg) {
|
||||
switch (msg.name) {
|
||||
case "TouchEventSimulator:Start":
|
||||
this.start();
|
||||
break;
|
||||
case "TouchEventSimulator:Stop":
|
||||
this.stop();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
start() {
|
||||
this.events.forEach(evt => {
|
||||
// Only listen trusted events to prevent messing with
|
||||
// event dispatched manually within content documents
|
||||
addEventListener(evt, this, true, false);
|
||||
});
|
||||
sendAsyncMessage("TouchEventSimulator:Started");
|
||||
},
|
||||
|
||||
stop() {
|
||||
this.events.forEach(evt => {
|
||||
removeEventListener(evt, this, true);
|
||||
});
|
||||
sendAsyncMessage("TouchEventSimulator:Stopped");
|
||||
},
|
||||
|
||||
handleEvent(evt) {
|
||||
// The gaia system window use an hybrid system even on the device which is
|
||||
// a mix of mouse/touch events. So let's not cancel *all* mouse events
|
||||
// if it is the current target.
|
||||
let content = this.getContent(evt.target);
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
let isSystemWindow = content.location.toString()
|
||||
.startsWith(systemAppOrigin);
|
||||
|
||||
// App touchstart & touchend should also be dispatched on the system app
|
||||
// to match on-device behavior.
|
||||
if (evt.type.startsWith("touch") && !isSystemWindow) {
|
||||
let sysFrame = content.realFrameElement;
|
||||
if (!sysFrame) {
|
||||
return;
|
||||
}
|
||||
let sysDocument = sysFrame.ownerDocument;
|
||||
let sysWindow = sysDocument.defaultView;
|
||||
|
||||
let touchEvent = sysDocument.createEvent("touchevent");
|
||||
let touch = evt.touches[0] || evt.changedTouches[0];
|
||||
let point = sysDocument.createTouch(sysWindow, sysFrame, 0,
|
||||
touch.pageX, touch.pageY,
|
||||
touch.screenX, touch.screenY,
|
||||
touch.clientX, touch.clientY,
|
||||
1, 1, 0, 0);
|
||||
|
||||
let touches = sysDocument.createTouchList(point);
|
||||
let targetTouches = touches;
|
||||
let changedTouches = touches;
|
||||
touchEvent.initTouchEvent(evt.type, true, true, sysWindow, 0,
|
||||
false, false, false, false,
|
||||
touches, targetTouches, changedTouches);
|
||||
sysFrame.dispatchEvent(touchEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore all but real mouse event coming from physical mouse
|
||||
// (especially ignore mouse event being dispatched from a touch event)
|
||||
if (evt.button ||
|
||||
evt.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE ||
|
||||
evt.isSynthesized) {
|
||||
return;
|
||||
}
|
||||
|
||||
let eventTarget = this.target;
|
||||
let type = "";
|
||||
switch (evt.type) {
|
||||
case "mousedown":
|
||||
this.target = evt.target;
|
||||
|
||||
this.contextMenuTimeout =
|
||||
this.sendContextMenu(evt.target, evt.pageX, evt.pageY);
|
||||
|
||||
this.cancelClick = false;
|
||||
this.startX = evt.pageX;
|
||||
this.startY = evt.pageY;
|
||||
|
||||
// Capture events so if a different window show up the events
|
||||
// won't be dispatched to something else.
|
||||
evt.target.setCapture(false);
|
||||
|
||||
type = "touchstart";
|
||||
break;
|
||||
|
||||
case "mousemove":
|
||||
if (!eventTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.cancelClick) {
|
||||
if (Math.abs(this.startX - evt.pageX) > threshold ||
|
||||
Math.abs(this.startY - evt.pageY) > threshold) {
|
||||
this.cancelClick = true;
|
||||
content.clearTimeout(this.contextMenuTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
type = "touchmove";
|
||||
break;
|
||||
|
||||
case "mouseup":
|
||||
if (!eventTarget) {
|
||||
return;
|
||||
}
|
||||
this.target = null;
|
||||
|
||||
content.clearTimeout(this.contextMenuTimeout);
|
||||
type = "touchend";
|
||||
|
||||
// Only register click listener after mouseup to ensure
|
||||
// catching only real user click. (Especially ignore click
|
||||
// being dispatched on form submit)
|
||||
if (evt.detail == 1) {
|
||||
addEventListener("click", this, true, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case "click":
|
||||
// Mouse events has been cancelled so dispatch a sequence
|
||||
// of events to where touchend has been fired
|
||||
evt.preventDefault();
|
||||
evt.stopImmediatePropagation();
|
||||
|
||||
removeEventListener("click", this, true, false);
|
||||
|
||||
if (this.cancelClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
content.setTimeout(function dispatchMouseEvents(self) {
|
||||
try {
|
||||
self.fireMouseEvent("mousedown", evt);
|
||||
self.fireMouseEvent("mousemove", evt);
|
||||
self.fireMouseEvent("mouseup", evt);
|
||||
} catch(e) {
|
||||
Cu.reportError("Exception in touch event helper: " + e);
|
||||
}
|
||||
}, 0, this);
|
||||
return;
|
||||
}
|
||||
|
||||
let target = eventTarget || this.target;
|
||||
if (target && type) {
|
||||
this.sendTouchEvent(evt, target, type);
|
||||
}
|
||||
|
||||
if (!isSystemWindow) {
|
||||
evt.preventDefault();
|
||||
evt.stopImmediatePropagation();
|
||||
}
|
||||
},
|
||||
|
||||
fireMouseEvent(type, evt) {
|
||||
let content = this.getContent(evt.target);
|
||||
let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true, 0,
|
||||
Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
||||
},
|
||||
|
||||
sendContextMenu(target, x, y) {
|
||||
let doc = target.ownerDocument;
|
||||
let evt = doc.createEvent("MouseEvent");
|
||||
evt.initMouseEvent("contextmenu", true, true, doc.defaultView,
|
||||
0, x, y, x, y, false, false, false, false,
|
||||
0, null);
|
||||
|
||||
let content = this.getContent(target);
|
||||
let timeout = content.setTimeout((function contextMenu() {
|
||||
target.dispatchEvent(evt);
|
||||
this.cancelClick = true;
|
||||
}).bind(this), delay);
|
||||
|
||||
return timeout;
|
||||
},
|
||||
|
||||
sendTouchEvent(evt, target, name) {
|
||||
function clone(obj) {
|
||||
return Cu.cloneInto(obj, target);
|
||||
}
|
||||
// When running OOP b2g desktop, we need to send the touch events
|
||||
// using the mozbrowser api on the unwrapped frame.
|
||||
if (target.localName == "iframe" && target.mozbrowser === true) {
|
||||
if (name == "touchstart") {
|
||||
this.touchstartTime = Date.now();
|
||||
} else if (name == "touchend") {
|
||||
// If we have a "fast" tap, don't send a click as both will be turned
|
||||
// into a click and that breaks eg. checkboxes.
|
||||
if (Date.now() - this.touchstartTime < delay) {
|
||||
this.cancelClick = true;
|
||||
}
|
||||
}
|
||||
let unwrapped = XPCNativeWrapper.unwrap(target);
|
||||
unwrapped.sendTouchEvent(name, clone([0]), // event type, id
|
||||
clone([evt.clientX]), // x
|
||||
clone([evt.clientY]), // y
|
||||
clone([1]), clone([1]), // rx, ry
|
||||
clone([0]), clone([0]), // rotation, force
|
||||
1); // count
|
||||
return;
|
||||
}
|
||||
let document = target.ownerDocument;
|
||||
let content = this.getContent(target);
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
let touchEvent = document.createEvent("touchevent");
|
||||
let point = document.createTouch(content, target, 0,
|
||||
evt.pageX, evt.pageY,
|
||||
evt.screenX, evt.screenY,
|
||||
evt.clientX, evt.clientY,
|
||||
1, 1, 0, 0);
|
||||
let touches = document.createTouchList(point);
|
||||
let targetTouches = touches;
|
||||
let changedTouches = touches;
|
||||
touchEvent.initTouchEvent(name, true, true, content, 0,
|
||||
false, false, false, false,
|
||||
touches, targetTouches, changedTouches);
|
||||
target.dispatchEvent(touchEvent);
|
||||
},
|
||||
|
||||
getContent(target) {
|
||||
let win = (target && target.ownerDocument)
|
||||
? target.ownerDocument.defaultView
|
||||
: null;
|
||||
return win;
|
||||
}
|
||||
};
|
||||
|
||||
simulator.init();
|
81
toolkit/devtools/touch/simulator.js
Normal file
81
toolkit/devtools/touch/simulator.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
let { Ci } = require("chrome");
|
||||
let promise = require("promise");
|
||||
|
||||
const FRAME_SCRIPT =
|
||||
"resource://gre/modules/devtools/touch/simulator-content.js";
|
||||
|
||||
let trackedBrowsers = new WeakMap();
|
||||
let savedTouchEventsEnabled =
|
||||
Services.prefs.getIntPref("dom.w3c_touch_events.enabled");
|
||||
|
||||
/**
|
||||
* Simulate touch events for platforms where they aren't generally available.
|
||||
* Defers to the `simulator-content.js` frame script to perform the real work.
|
||||
*/
|
||||
function TouchEventSimulator(browser) {
|
||||
// Returns an already instantiated simulator for this browser
|
||||
let simulator = trackedBrowsers.get(browser);
|
||||
if (simulator) {
|
||||
return simulator;
|
||||
}
|
||||
|
||||
let mm = browser.messageManager;
|
||||
if (!mm) {
|
||||
// Maybe browser is an iframe
|
||||
mm = browser.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader.messageManager;
|
||||
}
|
||||
mm.loadFrameScript(FRAME_SCRIPT, true);
|
||||
|
||||
simulator = {
|
||||
enabled: false,
|
||||
|
||||
start() {
|
||||
if (this.enabled) {
|
||||
return promise.resolve({ isReloadNeeded: false });
|
||||
}
|
||||
this.enabled = true;
|
||||
|
||||
let deferred = promise.defer();
|
||||
let isReloadNeeded =
|
||||
Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 1;
|
||||
Services.prefs.setIntPref("dom.w3c_touch_events.enabled", 1);
|
||||
let onStarted = () => {
|
||||
mm.removeMessageListener("TouchEventSimulator:Started", onStarted);
|
||||
deferred.resolve({ isReloadNeeded });
|
||||
};
|
||||
mm.addMessageListener("TouchEventSimulator:Started", onStarted);
|
||||
mm.sendAsyncMessage("TouchEventSimulator:Start");
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
stop() {
|
||||
if (!this.enabled) {
|
||||
return promise.resolve();
|
||||
}
|
||||
this.enabled = false;
|
||||
|
||||
let deferred = promise.defer();
|
||||
Services.prefs.setIntPref("dom.w3c_touch_events.enabled",
|
||||
savedTouchEventsEnabled);
|
||||
let onStopped = () => {
|
||||
mm.removeMessageListener("TouchEventSimulator:Stopped", onStopped);
|
||||
deferred.resolve();
|
||||
};
|
||||
mm.addMessageListener("TouchEventSimulator:Stopped", onStopped);
|
||||
mm.sendAsyncMessage("TouchEventSimulator:Stop");
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
trackedBrowsers.set(browser, simulator);
|
||||
|
||||
return simulator;
|
||||
}
|
||||
|
||||
exports.TouchEventSimulator = TouchEventSimulator;
|
Loading…
Reference in New Issue
Block a user