--- a/mobile/chrome/content/InputHandler.js
+++ b/mobile/chrome/content/InputHandler.js
@@ -40,16 +40,19 @@
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Maximum delay in ms between the two taps of a double-tap
const kDoubleClickInterval = 400;
+// Amount of time to wait before tap becomes long tap
+const kLongTapWait = 700;
+
// If a tap lasts longer than this duration in ms, treat it as a single-tap
// immediately instead of waiting for a possible double tap.
const kDoubleClickThreshold = 200;
// threshold in pixels for sensing a tap as opposed to a pan
const kTapRadius = Services.prefs.getIntPref("ui.dragThresholdX");
// maximum drag distance in pixels while axis locking can still be reverted
@@ -122,17 +125,16 @@ function InputHandler(browserViewContain
/* when set to true, next click won't be dispatched */
this._suppressNextClick = false;
/* these handle dragging of both chrome elements and content */
window.addEventListener("mousedown", this, true);
window.addEventListener("mouseup", this, true);
window.addEventListener("mousemove", this, true);
window.addEventListener("click", this, true);
- window.addEventListener("contextmenu", this, false);
window.addEventListener("MozSwipeGesture", this, true);
window.addEventListener("MozMagnifyGestureStart", this, true);
window.addEventListener("MozMagnifyGestureUpdate", this, true);
window.addEventListener("MozMagnifyGesture", this, true);
/* these handle key strokes in the browser view (where page content appears) */
browserViewContainer.addEventListener("keypress", this, false);
browserViewContainer.addEventListener("keyup", this, false);
@@ -215,16 +217,24 @@ InputHandler.prototype = {
* for the module to do so via the InputHandler. Hopefully the module is doing
* this under grab (that is, hopefully the module was grabbing while the mousedown
* and mouseup events came in, *not* just grabbing for making this call).
*/
suppressNextClick: function suppressNextClick() {
this._suppressNextClick = true;
},
+ /** Cancels all pending input sequences */
+ cancelPending: function cancelPending() {
+ let mods = this._modules;
+ for (let i = 0, len = mods.length; i < len; ++i)
+ if (mods[i])
+ mods[i].cancelPending();
+ },
+
/**
* Undoes any suppression caused by calling suppressNextClick(), unless the click
* already happened.
*/
allowClicks: function allowClicks() {
this._suppressNextClick = false;
},
@@ -328,56 +338,44 @@ function MouseModule(owner, browserViewC
this._downUpEvents = [];
this._targetScrollInterface = null;
var self = this;
this._kinetic = new KineticController(this._dragBy.bind(this),
this._kineticStop.bind(this));
this._singleClickTimeout = new Util.Timeout(this._doSingleClick.bind(this));
-
- messageManager.addMessageListener("Browser:ContextMenu", this);
+ this._longClickTimeout = new Util.Timeout(this._doLongClick.bind(this));
}
MouseModule.prototype = {
handleEvent: function handleEvent(aEvent) {
if (aEvent.button !== 0 && aEvent.type != "contextmenu" && aEvent.type != "MozBeforePaint")
return;
switch (aEvent.type) {
case "mousedown":
this._onMouseDown(aEvent);
break;
case "mousemove":
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
this._onMouseMove(aEvent);
break;
case "mouseup":
this._onMouseUp(aEvent);
break;
- case "contextmenu":
- if (ContextHelper.popupState)
- this.cancelPending();
- break;
case "MozBeforePaint":
this._waitingForPaint = false;
removeEventListener("MozBeforePaint", this, false);
break;
}
},
- receiveMessage: function receiveMessage(aMessage) {
- // TODO: Make "contextmenu" a first class part of InputHandler
- // Bug 554639
- if (aMessage.name != "Browser:ContextMenu" || !ContextHelper.popupState)
- return;
-
- this.cancelPending();
- },
-
/**
* This gets invoked by the input handler if another module grabs. We should
* reset our state or something here. This is probably doing the wrong thing
* in its current form.
*/
cancelPending: function cancelPending() {
if (this._kinetic.isActive())
this._kinetic.end();
@@ -405,123 +403,114 @@ MouseModule.prototype = {
let [targetScrollbox, targetScrollInterface, dragger]
= this.getScrollboxFromElement(aEvent.target);
// stop kinetic panning if targetScrollbox has changed
if (this._kinetic.isActive() && this._dragger != dragger)
this._kinetic.end();
this._targetScrollInterface = targetScrollInterface;
- this._dragger = dragger;
- this._target = aEvent.target;
- if (this._targetIsContent(aEvent)) {
- let event = document.createEvent("Events");
- event.initEvent("TapDown", true, false);
- event.clientX = aEvent.clientX;
- event.clientY = aEvent.clientY;
- aEvent.target.dispatchEvent(event);
-
+ // Do tap
+ let event = document.createEvent("Events");
+ event.initEvent("TapDown", true, true);
+ event.clientX = aEvent.clientX;
+ event.clientY = aEvent.clientY;
+ let success = aEvent.target.dispatchEvent(event);
+ if (success) {
this._recordEvent(aEvent);
+ this._target = aEvent.target;
+ this._longClickTimeout.once(kLongTapWait);
} else {
// cancel all pending content clicks
this._cleanClickBuffer();
}
- if (this._dragger) {
- let draggable = this._dragger.isDraggable(targetScrollbox, targetScrollInterface);
- if (draggable.x || draggable.y)
+ // Do pan
+ if (dragger) {
+ let draggable = dragger.isDraggable(targetScrollbox, targetScrollInterface);
+ dragData.locked = !draggable.x || !draggable.y;
+ if (draggable.x || draggable.y) {
+ this._dragger = dragger;
this._doDragStart(aEvent);
+ }
}
+
+ this._owner.grab(this);
},
/** Send tap up event and any necessary full taps. */
_onMouseUp: function _onMouseUp(aEvent) {
+ this._onMouseMove(aEvent);
+
let dragData = this._dragData;
- let oldIsPan = dragData.isPan();
- if (dragData.dragging) {
- dragData.setDragPosition(aEvent.screenX, aEvent.screenY);
- let [sX, sY] = dragData.panPosition();
+ if (dragData.dragging)
this._doDragStop();
+
+ // Do tap
+ if (this._target) {
+ let event = document.createEvent("Events");
+ event.initEvent("TapUp", true, true);
+ event.clientX = aEvent.clientX
+ event.clientY = aEvent.clientY;
+ let success = aEvent.target.dispatchEvent(event);
+ if (!success) {
+ this._cleanClickBuffer();
+ } else {
+ this._recordEvent(aEvent);
+ let commitToClicker = dragData.isClick() && (this._downUpEvents.length > 1);
+ if (commitToClicker)
+ // commit this click to the doubleclick timewait buffer
+ this._commitAnotherClick();
+ else
+ // clean the click buffer ourselves
+ this._cleanClickBuffer();
+ }
}
- if (this._targetIsContent(aEvent)) {
- if (this._dragger) {
- let event = document.createEvent("Events");
- event.initEvent("TapUp", true, false);
- event.clientX = aEvent.clientX
- event.clientY = aEvent.clientY;
- aEvent.target.dispatchEvent(event);
- }
-
- // User possibly clicked on something in content
- this._recordEvent(aEvent);
- let commitToClicker = dragData.isClick() && (this._downUpEvents.length > 1);
- if (commitToClicker)
- // commit this click to the doubleclick timewait buffer
- this._commitAnotherClick();
- else
- // clean the click buffer ourselves
- this._cleanClickBuffer();
- }
- else if (dragData.isPan()) {
- // User was panning around or contextmenu was open, do not allow chrome click
+ // Do pan
+ if (dragData.isPan()) {
+ // User was panning around, do not allow click
// XXX Instead of having suppressNextClick, we could grab until click is seen
// and THEN ungrab so that owner does not need to know anything about clicking.
let generatesClick = aEvent.detail;
if (generatesClick)
this._owner.suppressNextClick();
-
- // Pan has begun
- if (!oldIsPan) {
- let event = document.createEvent("Events");
- event.initEvent("PanBegin", true, false);
- document.dispatchEvent(event);
- }
}
this._owner.ungrab(this);
},
/**
* If we're in a drag, do what we have to do to drag on.
*/
_onMouseMove: function _onMouseMove(aEvent) {
let dragData = this._dragData;
if (dragData.dragging && !this._waitingForPaint) {
let oldIsPan = dragData.isPan();
dragData.setDragPosition(aEvent.screenX, aEvent.screenY);
- aEvent.stopPropagation();
- aEvent.preventDefault();
if (dragData.isPan()) {
- this._owner.grab(this);
// Only pan when mouse event isn't part of a click. Prevent jittering on tap.
let [sX, sY] = dragData.panPosition();
this._doDragMove();
// Let everyone know when mousemove begins a pan
if (!oldIsPan && dragData.isPan()) {
+ this._longClickTimeout.clear();
+
let event = document.createEvent("Events");
event.initEvent("PanBegin", true, false);
aEvent.target.dispatchEvent(event);
}
}
}
},
/**
- * Check if the event concern the browser content
- */
- _targetIsContent: function _targetIsContent(aEvent) {
- let target = aEvent.target;
- return target && target.id == "inputhandler-overlay";
- },
-
- /**
* Inform our dragger of a dragStart.
*/
_doDragStart: function _doDragStart(event) {
let dragData = this._dragData;
dragData.setDragStart(event.screenX, event.screenY);
this._kinetic.addData(0, 0);
if (!this._kinetic.isActive())
this._dragger.dragStart(event.clientX, event.clientY, event.target, this._targetScrollInterface);
@@ -573,24 +562,36 @@ MouseModule.prototype = {
}
return dragged;
},
/** Callback for kinetic scroller. */
_kineticStop: function _kineticStop() {
// Kinetic panning could finish while user is panning, so don't finish
// the pan just yet.
- if (!dragData.dragging && !dragData.isPan()) {
+ let dragData = this._dragData;
+ if (!dragData.dragging) {
this._dragger.dragStop(0, 0, this._targetScrollInterface);
let event = document.createEvent("Events");
event.initEvent("PanFinished", true, false);
document.dispatchEvent(event);
}
},
+ /** Called when tap down times out and becomes a long tap. */
+ _doLongClick: function _doLongClick() {
+ let ev = this._downUpEvents[0];
+
+ let event = document.createEvent("Events");
+ event.initEvent("LongTap", true, false);
+ event.clientX = ev.clientX;
+ event.clientY = ev.clientY;
+ ev.target.dispatchEvent(event);
+ },
+
/**
* Commit another click event to our click buffer. The `click buffer' is a
* timeout initiated by the first click. If the timeout is still alive when
* another click is committed, then the click buffer forms a double tap, and
* the timeout is cancelled. Otherwise, the timeout issues a single tap.
*/
_commitAnotherClick: function _commitAnotherClick() {
if (this._singleClickTimeout.isPending()) { // we're waiting for a second click for double
@@ -647,16 +648,17 @@ MouseModule.prototype = {
/**
* Clean out the click buffer. Should be called after a single, double, or
* non-click has been processed and all relevant (re)dispatches of events in
* the recorded down/up event queue have been issued out.
*/
_cleanClickBuffer: function _cleanClickBuffer() {
this._singleClickTimeout.clear();
+ this._longClickTimeout.clear();
this._downUpEvents.splice(0);
},
/**
* The default dragger object used by MouseModule when dragging a scrollable
* element that provides no customDragger. Simply performs the expected
* regular scrollBy calls on the scroller.
*/
@@ -740,18 +742,17 @@ MouseModule.prototype = {
},
toString: function toString() {
return '[MouseModule] {'
+ '\n\tdragData=' + this._dragData + ', '
+ 'dragger=' + this._dragger + ', '
+ '\n\tdownUpEvents=' + this._downUpEvents + ', '
+ 'length=' + this._downUpEvents.length + ', '
- + '\n\ttargetScroller=' + this._targetScrollInterface + ', '
- + '\n\tclickTimeout=' + this._clickTimeout + '\n }';
+ + '\n\ttargetScroller=' + this._targetScrollInterface + '}';
}
};
/**
* DragData handles processing drags on the screen, handling both
* locking of movement on one axis, and click detection.
*/
function DragData(dragRadius) {
@@ -1204,22 +1205,16 @@ GestureModule.prototype = {
else
this._pinchUpdate(aEvent);
break;
case "MozMagnifyGesture":
consume = true;
this._pinchEnd(aEvent);
break;
-
- case "contextmenu":
- // prevent context menu while pinching
- if (this._pinchZoom)
- consume = true;
- break;
}
if (consume) {
// prevent sending of event to content
aEvent.stopPropagation();
aEvent.preventDefault();
}
}
catch (e) {
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -1268,16 +1268,22 @@ const ContentTouchHandler = {
document.addEventListener("TapDown", this, false);
document.addEventListener("TapUp", this, false);
document.addEventListener("TapSingle", this, false);
document.addEventListener("TapDouble", this, false);
document.addEventListener("PanBegin", this, false);
},
handleEvent: function handleEvent(ev) {
+ if (!this._targetIsContent(ev)) {
+ TapHighlightHelper.hide();
+ this._dispatchMouseEvent("Browser:MouseCancel");
+ return;
+ }
+
switch (ev.type) {
case "TapDown":
this.tapDown(ev.clientX, ev.clientY);
break;
case "TapUp":
thisTapSingletapUp(ev.clientX, ev.clientY);
break;
case "TapSingle":
@@ -1287,16 +1293,24 @@ const ContentTouchHandler = {
this.tapDouble(ev.clientX1, ev.clientY1, ev.clientX2, ev.clientY2);
break;
case "PanBegin":
this.panBegin();
break;
}
},
+ /**
+ * Check if the event concern the browser content
+ */
+ _targetIsContent: function _targetIsContent(aEvent) {
+ let target = aEvent.target;
+ return target && target.id == "inputhandler-overlay";
+ },
+
_dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY, aModifiers) {
let aX = aX || 0;
let aY = aY || 0;
let aModifiers = aModifiers || null;
let browser = getBrowser();
let [x, y] = Browser.transformClientToBrowser(aX, aY);
browser.messageManager.sendAsyncMessage(aName, { x: x, y: y, modifiers: aModifiers });
},
@@ -1314,17 +1328,16 @@ const ContentTouchHandler = {
},
tapUp: function tapUp(aX, aY) {
TapHighlightHelper.hide(200);
},
panBegin: function panBegin() {
TapHighlightHelper.hide(0);
-
this._dispatchMouseEvent("Browser:MouseCancel");
},
tapSingle: function tapSingle(aX, aY, aModifiers) {
TapHighlightHelper.hide(200);
// Cancel the mouse click if we are showing a context menu
if (!ContextHelper.popupState)