зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset c289e719ca5d (bug 1285566) for timing out in devtools test browser_toolbox_computed_view.js. r=backout
This commit is contained in:
Родитель
fe6e35996e
Коммит
da55fffdc5
|
@ -4,23 +4,17 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const promise = require("promise");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { TouchEventSimulator } = require("devtools/shared/touch/simulator");
|
||||
const { getOwnerWindow } = require("sdk/tabs/utils");
|
||||
const { startup } = require("sdk/window/helpers");
|
||||
const message = require("./utils/message");
|
||||
const { swapToInnerBrowser } = require("./browser/swap");
|
||||
const { EmulationFront } = require("devtools/shared/fronts/emulation");
|
||||
|
||||
const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/main", true);
|
||||
loader.lazyRequireGetter(this, "DebuggerServer",
|
||||
"devtools/server/main", true);
|
||||
|
||||
/**
|
||||
* ResponsiveUIManager is the external API for the browser UI, etc. to use when
|
||||
* opening and closing the responsive UI.
|
||||
|
@ -254,6 +248,11 @@ ResponsiveUI.prototype = {
|
|||
*/
|
||||
toolWindow: null,
|
||||
|
||||
/**
|
||||
* Touch event simulator.
|
||||
*/
|
||||
touchEventSimulator: null,
|
||||
|
||||
/**
|
||||
* Open RDM while preserving the state of the page. We use `swapFrameLoaders`
|
||||
* to ensure all in-page state is preserved, just like when you move a tab to
|
||||
|
@ -285,8 +284,7 @@ ResponsiveUI.prototype = {
|
|||
// Notify the inner browser to start the frame script
|
||||
yield message.request(this.toolWindow, "start-frame-script");
|
||||
|
||||
// Get the protocol ready to speak with emulation actor
|
||||
yield this.connectToServer();
|
||||
this.touchEventSimulator = new TouchEventSimulator(this.getViewportBrowser());
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -317,11 +315,13 @@ ResponsiveUI.prototype = {
|
|||
this.tab.removeEventListener("TabClose", this);
|
||||
this.toolWindow.removeEventListener("message", this);
|
||||
|
||||
// Stop the touch event simulator if it was running
|
||||
if (!isTabClosing) {
|
||||
// Stop the touch event simulator if it was running
|
||||
yield this.emulationFront.clearTouchEventsOverride();
|
||||
yield this.touchEventSimulator.stop();
|
||||
}
|
||||
|
||||
// Notify the inner browser to stop the frame script
|
||||
// Notify the inner browser to stop the frame script
|
||||
if (!isTabClosing) {
|
||||
yield message.request(this.toolWindow, "stop-frame-script");
|
||||
}
|
||||
|
||||
|
@ -331,16 +331,9 @@ ResponsiveUI.prototype = {
|
|||
this.tab = null;
|
||||
this.inited = null;
|
||||
this.toolWindow = null;
|
||||
this.touchEventSimulator = null;
|
||||
this.swap = null;
|
||||
|
||||
if (!isTabClosing) {
|
||||
// Close the debugger client used to speak with emulation actor
|
||||
yield new Promise((resolve, reject) => {
|
||||
this.client.close(resolve);
|
||||
this.client = this.emulationFront = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Undo the swap and return the content back to a normal tab
|
||||
swap.stop();
|
||||
|
||||
|
@ -349,17 +342,6 @@ ResponsiveUI.prototype = {
|
|||
return true;
|
||||
}),
|
||||
|
||||
connectToServer: Task.async(function* () {
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
this.client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
yield this.client.connect();
|
||||
let { tab } = yield this.client.getTab();
|
||||
this.emulationFront = EmulationFront(this.client, tab);
|
||||
}),
|
||||
|
||||
handleEvent(event) {
|
||||
let { browserWindow, tab } = this;
|
||||
|
||||
|
@ -402,14 +384,12 @@ ResponsiveUI.prototype = {
|
|||
|
||||
updateTouchSimulation: Task.async(function* (enabled) {
|
||||
if (enabled) {
|
||||
let reloadNeeded = yield this.emulationFront.setTouchEventsOverride(
|
||||
Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED
|
||||
);
|
||||
let reloadNeeded = yield this.touchEventSimulator.start();
|
||||
if (reloadNeeded) {
|
||||
this.getViewportBrowser().reload();
|
||||
}
|
||||
} else {
|
||||
this.emulationFront.clearTouchEventsOverride();
|
||||
this.touchEventSimulator.stop();
|
||||
}
|
||||
}),
|
||||
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const { emulationSpec } = require("devtools/shared/specs/emulation");
|
||||
const { SimulatorCore } = require("devtools/shared/touch/simulator-core");
|
||||
|
||||
let EmulationActor = protocol.ActorClass(emulationSpec, {
|
||||
initialize(conn, tabActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.docShell = tabActor.docShell;
|
||||
this.simulatorCore = new SimulatorCore(tabActor.chromeEventHandler);
|
||||
},
|
||||
|
||||
_previousTouchEventsOverride: null,
|
||||
|
||||
setTouchEventsOverride(flag) {
|
||||
if (this.docShell.touchEventsOverride == flag) {
|
||||
return false;
|
||||
}
|
||||
if (this._previousTouchEventsOverride === null) {
|
||||
this._previousTouchEventsOverride = this.docShell.touchEventsOverride;
|
||||
}
|
||||
|
||||
// Start or stop the touch simulator depending on the override flag
|
||||
if (flag == Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED) {
|
||||
this.simulatorCore.start();
|
||||
} else {
|
||||
this.simulatorCore.stop();
|
||||
}
|
||||
|
||||
this.docShell.touchEventsOverride = flag;
|
||||
return true;
|
||||
},
|
||||
|
||||
getTouchEventsOverride() {
|
||||
return this.docShell.touchEventsOverride;
|
||||
},
|
||||
|
||||
clearTouchEventsOverride() {
|
||||
if (this._previousTouchEventsOverride !== null) {
|
||||
this.setTouchEventsOverride(this._previousTouchEventsOverride);
|
||||
}
|
||||
},
|
||||
|
||||
disconnect() {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
destroy() {
|
||||
this.clearTouchEventsOverride();
|
||||
this.docShell = null;
|
||||
this.simulatorCore = null;
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
});
|
||||
|
||||
exports.EmulationActor = EmulationActor;
|
|
@ -26,7 +26,6 @@ DevToolsModules(
|
|||
'device.js',
|
||||
'director-manager.js',
|
||||
'director-registry.js',
|
||||
'emulation.js',
|
||||
'environment.js',
|
||||
'errordocs.js',
|
||||
'eventlooplag.js',
|
||||
|
|
|
@ -569,11 +569,6 @@ var DebuggerServer = {
|
|||
constructor: "PerformanceEntriesActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/emulation", {
|
||||
prefix: "emulation",
|
||||
constructor: "EmulationActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
const { Front, FrontClass } = require("devtools/shared/protocol");
|
||||
const { emulationSpec } = require("devtools/shared/specs/emulation");
|
||||
|
||||
/**
|
||||
* The corresponding Front object for the EmulationActor.
|
||||
*/
|
||||
const EmulationFront = FrontClass(emulationSpec, {
|
||||
initialize: function (client, form) {
|
||||
Front.prototype.initialize.call(this, client);
|
||||
this.actorID = form.emulationActor;
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
Front.prototype.destroy.call(this);
|
||||
},
|
||||
});
|
||||
|
||||
exports.EmulationFront = EmulationFront;
|
|
@ -15,7 +15,6 @@ DevToolsModules(
|
|||
'device.js',
|
||||
'director-manager.js',
|
||||
'director-registry.js',
|
||||
'emulation.js',
|
||||
'eventlooplag.js',
|
||||
'framerate.js',
|
||||
'gcli.js',
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
|
||||
|
||||
const emulationSpec = generateActorSpec({
|
||||
typeName: "emulation",
|
||||
|
||||
methods: {
|
||||
setTouchEventsOverride: {
|
||||
request: {
|
||||
flag: Arg(0, "number")
|
||||
},
|
||||
response: {
|
||||
reload: RetVal("boolean")
|
||||
}
|
||||
},
|
||||
|
||||
getTouchEventsOverride: {
|
||||
request: {},
|
||||
response: {
|
||||
flag: RetVal("number")
|
||||
}
|
||||
},
|
||||
|
||||
clearTouchEventsOverride: {
|
||||
request: {},
|
||||
response: {}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
exports.emulationSpec = emulationSpec;
|
|
@ -16,7 +16,6 @@ DevToolsModules(
|
|||
'device.js',
|
||||
'director-manager.js',
|
||||
'director-registry.js',
|
||||
'emulation.js',
|
||||
'environment.js',
|
||||
'eventlooplag.js',
|
||||
'frame.js',
|
||||
|
|
|
@ -6,6 +6,5 @@
|
|||
|
||||
DevToolsModules(
|
||||
'simulator-content.js',
|
||||
'simulator-core.js',
|
||||
'simulator.js',
|
||||
)
|
||||
|
|
|
@ -1,26 +1,64 @@
|
|||
/* 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 */
|
||||
/* globals addMessageListener, sendAsyncMessage, addEventListener,
|
||||
removeEventListener */
|
||||
"use strict";
|
||||
|
||||
const { utils: Cu } = Components;
|
||||
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const { SimulatorCore } = require("devtools/shared/touch/simulator-core");
|
||||
const { interfaces: Ci, utils: Cu } = Components;
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
var 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;
|
||||
})();
|
||||
|
||||
var threshold = 25;
|
||||
try {
|
||||
threshold = Services.prefs.getIntPref("ui.dragThresholdX");
|
||||
} catch (e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
var delay = 500;
|
||||
try {
|
||||
delay = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
|
||||
} catch (e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches SimulatorCore in the content window to simulate touch events
|
||||
* Simulate touch events for platforms where they aren't generally available.
|
||||
* This frame script is managed by `simulator.js`.
|
||||
*/
|
||||
|
||||
var simulator = {
|
||||
events: [
|
||||
"mousedown",
|
||||
"mousemove",
|
||||
"mouseup",
|
||||
"touchstart",
|
||||
"touchend",
|
||||
"mouseenter",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"mouseleave"
|
||||
],
|
||||
|
||||
messages: [
|
||||
"TouchEventSimulator:Start",
|
||||
"TouchEventSimulator:Stop",
|
||||
],
|
||||
|
||||
contextMenuTimeout: null,
|
||||
|
||||
init() {
|
||||
this.simulatorCore = new SimulatorCore(docShell.chromeEventHandler);
|
||||
this.messages.forEach(msgName => {
|
||||
addMessageListener(msgName, this);
|
||||
});
|
||||
|
@ -29,15 +67,305 @@ var simulator = {
|
|||
receiveMessage(msg) {
|
||||
switch (msg.name) {
|
||||
case "TouchEventSimulator:Start":
|
||||
this.simulatorCore.start();
|
||||
sendAsyncMessage("TouchEventSimulator:Started");
|
||||
this.start();
|
||||
break;
|
||||
case "TouchEventSimulator:Stop":
|
||||
this.simulatorCore.stop();
|
||||
sendAsyncMessage("TouchEventSimulator:Stopped");
|
||||
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 "mouseenter":
|
||||
case "mouseover":
|
||||
case "mouseout":
|
||||
case "mouseleave":
|
||||
// Don't propagate events which are not related to touch events
|
||||
evt.stopPropagation();
|
||||
break;
|
||||
|
||||
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) {
|
||||
// Don't propagate mousemove event when touchstart event isn't fired
|
||||
evt.stopPropagation();
|
||||
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) {
|
||||
console.error("Exception in touch event helper: " + e);
|
||||
}
|
||||
}, this.getDelayBeforeMouseEvent(evt), 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;
|
||||
if (name === "touchend" || name === "touchcancel") {
|
||||
// "touchend" and "touchcancel" events should not have the removed touch
|
||||
// neither in touches nor in targetTouches
|
||||
touches = targetTouches = document.createTouchList();
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
getDelayBeforeMouseEvent(evt) {
|
||||
// On mobile platforms, Firefox inserts a 300ms delay between
|
||||
// touch events and accompanying mouse events, except if the
|
||||
// content window is not zoomable and the content window is
|
||||
// auto-zoomed to device-width.
|
||||
|
||||
// If the preference dom.meta-viewport.enabled is set to false,
|
||||
// we couldn't read viewport's information from getViewportInfo().
|
||||
// So we always simulate 300ms delay when the
|
||||
// dom.meta-viewport.enabled is false.
|
||||
let savedMetaViewportEnabled =
|
||||
Services.prefs.getBoolPref("dom.meta-viewport.enabled");
|
||||
if (!savedMetaViewportEnabled) {
|
||||
return 300;
|
||||
}
|
||||
|
||||
let content = this.getContent(evt.target);
|
||||
if (!content) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
let allowZoom = {},
|
||||
minZoom = {},
|
||||
maxZoom = {},
|
||||
autoSize = {};
|
||||
|
||||
utils.getViewportInfo(content.innerWidth, content.innerHeight, {},
|
||||
allowZoom, minZoom, maxZoom, {}, {}, autoSize);
|
||||
|
||||
// FIXME: On Safari and Chrome mobile platform, if the css property
|
||||
// touch-action set to none or manipulation would also suppress 300ms
|
||||
// delay. But Firefox didn't support this property now, we can't get
|
||||
// this value from utils.getVisitedDependentComputedStyle() to check
|
||||
// if we should suppress 300ms delay.
|
||||
if (!allowZoom.value || // user-scalable = no
|
||||
minZoom.value === maxZoom.value || // minimum-scale = maximum-scale
|
||||
autoSize.value // width = device-width
|
||||
) {
|
||||
return 0;
|
||||
} else {
|
||||
return 300;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
simulator.init();
|
||||
|
|
|
@ -1,362 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
const { Ci, Cu } = require("chrome");
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
var 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;
|
||||
})();
|
||||
|
||||
var threshold = 25;
|
||||
try {
|
||||
threshold = Services.prefs.getIntPref("ui.dragThresholdX");
|
||||
} catch (e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
var delay = 500;
|
||||
try {
|
||||
delay = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
|
||||
} catch (e) {
|
||||
// Fall back to default value
|
||||
}
|
||||
|
||||
function SimulatorCore(simulatorTarget) {
|
||||
this.simulatorTarget = simulatorTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate touch events for platforms where they aren't generally available.
|
||||
*/
|
||||
SimulatorCore.prototype = {
|
||||
events: [
|
||||
"mousedown",
|
||||
"mousemove",
|
||||
"mouseup",
|
||||
"touchstart",
|
||||
"touchend",
|
||||
"mouseenter",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"mouseleave"
|
||||
],
|
||||
|
||||
contextMenuTimeout: null,
|
||||
|
||||
simulatorTarget: null,
|
||||
|
||||
enabled: false,
|
||||
|
||||
start() {
|
||||
if (this.enabled) {
|
||||
// Simulator is already started
|
||||
return;
|
||||
}
|
||||
this.events.forEach(evt => {
|
||||
// Only listen trusted events to prevent messing with
|
||||
// event dispatched manually within content documents
|
||||
this.simulatorTarget.addEventListener(evt, this, true, false);
|
||||
});
|
||||
this.enabled = true;
|
||||
},
|
||||
|
||||
stop() {
|
||||
if (!this.enabled) {
|
||||
// Simulator isn't running
|
||||
return;
|
||||
}
|
||||
this.events.forEach(evt => {
|
||||
this.simulatorTarget.removeEventListener(evt, this, true);
|
||||
});
|
||||
this.enabled = false;
|
||||
},
|
||||
|
||||
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 "mouseenter":
|
||||
case "mouseover":
|
||||
case "mouseout":
|
||||
case "mouseleave":
|
||||
// Don't propagate events which are not related to touch events
|
||||
evt.stopPropagation();
|
||||
break;
|
||||
|
||||
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) {
|
||||
// Don't propagate mousemove event when touchstart event isn't fired
|
||||
evt.stopPropagation();
|
||||
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) {
|
||||
this.simulatorTarget.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();
|
||||
|
||||
this.simulatorTarget.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) {
|
||||
console.error("Exception in touch event helper: " + e);
|
||||
}
|
||||
}, this.getDelayBeforeMouseEvent(evt), 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;
|
||||
if (name === "touchend" || name === "touchcancel") {
|
||||
// "touchend" and "touchcancel" events should not have the removed touch
|
||||
// neither in touches nor in targetTouches
|
||||
touches = targetTouches = document.createTouchList();
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
getDelayBeforeMouseEvent(evt) {
|
||||
// On mobile platforms, Firefox inserts a 300ms delay between
|
||||
// touch events and accompanying mouse events, except if the
|
||||
// content window is not zoomable and the content window is
|
||||
// auto-zoomed to device-width.
|
||||
|
||||
// If the preference dom.meta-viewport.enabled is set to false,
|
||||
// we couldn't read viewport's information from getViewportInfo().
|
||||
// So we always simulate 300ms delay when the
|
||||
// dom.meta-viewport.enabled is false.
|
||||
let savedMetaViewportEnabled =
|
||||
Services.prefs.getBoolPref("dom.meta-viewport.enabled");
|
||||
if (!savedMetaViewportEnabled) {
|
||||
return 300;
|
||||
}
|
||||
|
||||
let content = this.getContent(evt.target);
|
||||
if (!content) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
let allowZoom = {},
|
||||
minZoom = {},
|
||||
maxZoom = {},
|
||||
autoSize = {};
|
||||
|
||||
utils.getViewportInfo(content.innerWidth, content.innerHeight, {},
|
||||
allowZoom, minZoom, maxZoom, {}, {}, autoSize);
|
||||
|
||||
// FIXME: On Safari and Chrome mobile platform, if the css property
|
||||
// touch-action set to none or manipulation would also suppress 300ms
|
||||
// delay. But Firefox didn't support this property now, we can't get
|
||||
// this value from utils.getVisitedDependentComputedStyle() to check
|
||||
// if we should suppress 300ms delay.
|
||||
if (!allowZoom.value || // user-scalable = no
|
||||
minZoom.value === maxZoom.value || // minimum-scale = maximum-scale
|
||||
autoSize.value // width = device-width
|
||||
) {
|
||||
return 0;
|
||||
} else {
|
||||
return 300;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.SimulatorCore = SimulatorCore;
|
Загрузка…
Ссылка в новой задаче