Bug 544614 - Touch Events [r=mfinkle, bstover]

This commit is contained in:
Wes Johnston 2011-03-21 11:20:34 -07:00
Родитель df4a6c7a1b
Коммит 1c4760b2d3
4 изменённых файлов: 162 добавлений и 48 удалений

Просмотреть файл

@ -584,6 +584,9 @@ pref("image.mem.decodeondraw", true);
pref("content.image.allow_locking", false); pref("content.image.allow_locking", false);
pref("image.mem.min_discard_timeout_ms", 20000); pref("image.mem.min_discard_timeout_ms", 20000);
// enable touch events interfaces
pref("dom.w3c_touch_events.enabled", true);
#ifdef MOZ_SAFE_BROWSING #ifdef MOZ_SAFE_BROWSING
// Safe browsing does nothing unless this pref is set // Safe browsing does nothing unless this pref is set
pref("browser.safebrowsing.enabled", true); pref("browser.safebrowsing.enabled", true);
@ -633,4 +636,5 @@ pref("urlclassifier.updatecachemax", 4194304);
// URL for checking the reason for a malware warning. // URL for checking the reason for a malware warning.
pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site="); pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
#endif #endif

Просмотреть файл

@ -1197,6 +1197,10 @@ Browser.MainDragger = function MainDragger() {
Elements.browsers.addEventListener("PanBegin", this, false); Elements.browsers.addEventListener("PanBegin", this, false);
Elements.browsers.addEventListener("PanFinished", this, false); Elements.browsers.addEventListener("PanFinished", this, false);
// allow pages to to override panning, but should
// still allow the sidebars to be panned out of view
this.contentMouseCapture = false;
}; };
Browser.MainDragger.prototype = { Browser.MainDragger.prototype = {
@ -1227,48 +1231,42 @@ Browser.MainDragger.prototype = {
dragMove: function dragMove(dx, dy, scroller, aIsKinetic) { dragMove: function dragMove(dx, dy, scroller, aIsKinetic) {
let doffset = new Point(dx, dy); let doffset = new Point(dx, dy);
// First calculate any panning to take sidebars out of view // If the sidebars are showing, we pan them out of the way before panning the content.
let panOffset = this._panControlsAwayOffset(doffset); // The panning distance that should be used for the sidebars in is stored in sidebarOffset,
// and subtracted from doffset
let sidebarOffset = this._getSidebarOffset(doffset);
// If we started at one sidebar, stop when we get to the other. // If we started with one sidebar open, stop when we get to the other.
if (panOffset.x != 0 && !this._stopAtSidebar) { if (sidebarOffset.x != 0)
this._stopAtSidebar = panOffset.x; // negative: stop at left; positive: stop at right this._blockSidebars(sidebarOffset);
if (!this.contentMouseCapture)
this._panContent(doffset);
if (this._hitSidebar && aIsKinetic)
return false; // No kinetic panning after we've stopped at the sidebar.
// allow panning the sidebars if the page hasn't prevented it, or if any of the sidebars are showing
// (i.e. we always allow panning sidebars off screen but not necessarily panning them back on)
if (!this.contentMouseCapture || sidebarOffset.x != 0 || sidebarOffset.y > 0)
this._panChrome(doffset, sidebarOffset);
this._updateScrollbars();
return !doffset.equals(dx, dy);
},
_blockSidebars: function md_blockSidebars(aSidebarOffset) {
// only call this code once
if (!this._stopAtSidebar) {
this._stopAtSidebar = aSidebarOffset.x; // negative: stop at left; positive: stop at right
// after a timeout, we allow showing the sidebar, to give the appearance of some "friction" at the edge
this._sidebarTimeout = setTimeout(function(self) { this._sidebarTimeout = setTimeout(function(self) {
self._stopAtSidebar = 0; self._stopAtSidebar = 0;
self._sidebarTimeout = null; self._sidebarTimeout = null;
}, 350, this); }, 350, this);
} }
if (this._contentView && !this._contentView.isRoot()) {
this._panContentView(this._contentView, doffset);
// XXX we may need to have "escape borders" for iframe panning
// XXX does not deal with scrollables within scrollables
}
// Do content panning
this._panContentView(getBrowser().getRootView(), doffset);
if (this._hitSidebar && aIsKinetic)
return; // No kinetic panning after we've stopped at the sidebar.
// Any leftover panning in doffset would bring controls into view. Add to sidebar
// away panning for the total scroll offset.
let offsetX = doffset.x;
if ((this._stopAtSidebar > 0 && offsetX > 0) ||
(this._stopAtSidebar < 0 && offsetX < 0)) {
if (offsetX != panOffset.x)
this._hitSidebar = true;
doffset.x = panOffset.x;
} else {
doffset.add(panOffset);
}
Browser.tryFloatToolbar(doffset.x, 0);
this._panScroller(Browser.controlsScrollboxScroller, doffset);
this._panScroller(Browser.pageScrollboxScroller, doffset);
this._updateScrollbars();
return !doffset.equals(dx, dy);
}, },
handleEvent: function handleEvent(aEvent) { handleEvent: function handleEvent(aEvent) {
@ -1299,8 +1297,38 @@ Browser.MainDragger.prototype = {
} }
}, },
_panContent: function md_panContent(aOffset) {
if (this._contentView && !this._contentView.isRoot()) {
this._panContentView(this._contentView, aOffset);
// XXX we may need to have "escape borders" for iframe panning
// XXX does not deal with scrollables within scrollables
}
// Do content panning
this._panContentView(getBrowser().getRootView(), aOffset);
},
_panChrome: function md_panSidebars(aOffset, aSidebarOffset) {
// Any panning aOffset would bring controls into view. Add to aSidebarOffset
let offsetX = aOffset.x;
if ((this._stopAtSidebar > 0 && offsetX > 0) ||
(this._stopAtSidebar < 0 && offsetX < 0)) {
if (offsetX != aSidebarOffset.x)
this._hitSidebar = true;
aOffset.x = aSidebarOffset.x;
} else {
aOffset.add(aSidebarOffset);
}
Browser.tryFloatToolbar(aOffset.x, 0);
// pan the sidebars
this._panScroller(Browser.controlsScrollboxScroller, aOffset);
// pan the urlbar
this._panScroller(Browser.pageScrollboxScroller, aOffset);
},
/** Return offset that pans controls away from screen. Updates doffset with leftovers. */ /** Return offset that pans controls away from screen. Updates doffset with leftovers. */
_panControlsAwayOffset: function(doffset) { _getSidebarOffset: function(doffset) {
let x = 0, y = 0, rect; let x = 0, y = 0, rect;
rect = Rect.fromRect(Browser.pageScrollbox.getBoundingClientRect()).map(Math.round); rect = Rect.fromRect(Browser.pageScrollbox.getBoundingClientRect()).map(Math.round);
@ -1603,6 +1631,7 @@ const ContentTouchHandler = {
document.addEventListener("TapSingle", this, false); document.addEventListener("TapSingle", this, false);
document.addEventListener("TapDouble", this, false); document.addEventListener("TapDouble", this, false);
document.addEventListener("TapLong", this, false); document.addEventListener("TapLong", this, false);
document.addEventListener("TapMove", this, false);
document.addEventListener("PanBegin", this, false); document.addEventListener("PanBegin", this, false);
document.addEventListener("PopupChanged", this, false); document.addEventListener("PopupChanged", this, false);
@ -1619,8 +1648,8 @@ const ContentTouchHandler = {
// a long tap, without waiting for child process. // a long tap, without waiting for child process.
// //
messageManager.addMessageListener("Browser:ContextMenu", this); messageManager.addMessageListener("Browser:ContextMenu", this);
messageManager.addMessageListener("Browser:Highlight", this); messageManager.addMessageListener("Browser:Highlight", this);
messageManager.addMessageListener("Browser:CaptureEvents", this);
}, },
handleEvent: function handleEvent(aEvent) { handleEvent: function handleEvent(aEvent) {
@ -1661,12 +1690,12 @@ const ContentTouchHandler = {
this.tapSingle(aEvent.clientX, aEvent.clientY, aEvent.modifiers); this.tapSingle(aEvent.clientX, aEvent.clientY, aEvent.modifiers);
aEvent.preventDefault(); aEvent.preventDefault();
} }
} else {
this.tapUp(aEvent.clientX, aEvent.clientY);
} }
this._dispatchMouseEvent("Browser:MouseUp", aEvent.clientX, aEvent.clientY);
break; break;
case "TapSingle": case "TapSingle":
this.tapSingle(aEvent.clientX, aEvent.clientY, aEvent.modifiers); this.tapSingle(aEvent.clientX, aEvent.clientY, aEvent.modifiers);
this._dispatchMouseEvent("Browser:MouseUp", aEvent.clientX, aEvent.clientY);
break; break;
case "TapDouble": case "TapDouble":
this.tapDouble(aEvent.clientX, aEvent.clientY, aEvent.modifiers); this.tapDouble(aEvent.clientX, aEvent.clientY, aEvent.modifiers);
@ -1674,6 +1703,9 @@ const ContentTouchHandler = {
case "TapLong": case "TapLong":
this.tapLong(aEvent.clientX, aEvent.clientY); this.tapLong(aEvent.clientX, aEvent.clientY);
break; break;
case "TapMove":
this.tapMove(aEvent.clientX, aEvent.clientY);
break;
} }
} }
} }
@ -1694,6 +1726,9 @@ const ContentTouchHandler = {
document.dispatchEvent(event); document.dispatchEvent(event);
} }
break; break;
case "Browser:CaptureEvents":
Elements.browsers.customDragger.contentMouseCapture = aMessage.json.panning;
break;
} }
}, },
@ -1735,6 +1770,7 @@ const ContentTouchHandler = {
try { try {
fl.activateRemoteFrame(); fl.activateRemoteFrame();
} catch (e) {} } catch (e) {}
Elements.browsers.customDragger.contentMouseCapture = false;
this._dispatchMouseEvent("Browser:MouseDown", aX, aY); this._dispatchMouseEvent("Browser:MouseDown", aX, aY);
}, },
@ -1750,7 +1786,11 @@ const ContentTouchHandler = {
tapSingle: function tapSingle(aX, aY, aModifiers) { tapSingle: function tapSingle(aX, aY, aModifiers) {
// Cancel the mouse click if we are showing a context menu // Cancel the mouse click if we are showing a context menu
if (!ContextHelper.popupState) if (!ContextHelper.popupState)
this._dispatchMouseEvent("Browser:MouseUp", aX, aY, { modifiers: aModifiers }); this._dispatchMouseEvent("Browser:MouseClick", aX, aY, { modifiers: aModifiers });
},
tapMove: function tapMove(aX, aY) {
this._dispatchMouseEvent("Browser:MouseMove", aX, aY);
}, },
tapDouble: function tapDouble(aX, aY, aModifiers) { tapDouble: function tapDouble(aX, aY, aModifiers) {
@ -2733,7 +2773,7 @@ Tab.prototype = {
// If the scale level has not changed we want to be sure the content // If the scale level has not changed we want to be sure the content
// render correctly since the page refresh process could have been // render correctly since the page refresh process could have been
// stalled during page load. In this case if the page has the exact // stalled during page load. In this case if the page has the exact
// same width (like the same page, so by doing 'refresh') and the // same width (like the same page, so by doing 'refresh') and the
// page was scrolled the content is just checkerboard at this point // page was scrolled the content is just checkerboard at this point
// and this call ensure we render it correctly. // and this call ensure we render it correctly.
browser.getRootView()._updateCacheViewport(); browser.getRootView()._updateCacheViewport();

Просмотреть файл

@ -262,7 +262,7 @@ let Content = {
addMessageListener("Browser:MouseOver", this); addMessageListener("Browser:MouseOver", this);
addMessageListener("Browser:MouseLong", this); addMessageListener("Browser:MouseLong", this);
addMessageListener("Browser:MouseDown", this); addMessageListener("Browser:MouseDown", this);
addMessageListener("Browser:MouseUp", this); addMessageListener("Browser:MouseClick", this);
addMessageListener("Browser:MouseCancel", this); addMessageListener("Browser:MouseCancel", this);
addMessageListener("Browser:SaveAs", this); addMessageListener("Browser:SaveAs", this);
addMessageListener("Browser:ZoomToPoint", this); addMessageListener("Browser:ZoomToPoint", this);
@ -474,15 +474,17 @@ let Content = {
ContextHandler.messageId = json.messageId; ContextHandler.messageId = json.messageId;
let event = content.document.createEvent("PopupEvents"); let event = content.document.createEvent("MouseEvent");
event.initEvent("contextmenu", true, true); event.initMouseEvent("contextmenu", true, true, content,
0, x, y, x, y, false, false, false, false,
0, null);
event.x = x; event.x = x;
event.y = y; event.y = y;
element.dispatchEvent(event); element.dispatchEvent(event);
break; break;
} }
case "Browser:MouseUp": { case "Browser:MouseClick": {
this._formAssistant.focusSync = true; this._formAssistant.focusSync = true;
let element = elementFromPoint(x, y); let element = elementFromPoint(x, y);
if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) { if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
@ -1188,3 +1190,63 @@ var ConsoleAPIObserver = {
}; };
ConsoleAPIObserver.init(); ConsoleAPIObserver.init();
var TouchEventHandler = {
element: null,
init: function() {
addMessageListener("Browser:MouseUp", this);
addMessageListener("Browser:MouseDown", this);
addMessageListener("Browser:MouseMove", this);
},
receiveMessage: function(aMessage) {
if (Util.isParentProcess())
return;
let json = aMessage.json;
let cancelled = false;
switch (aMessage.name) {
case "Browser:MouseDown":
let cwu = Util.getWindowUtils(content);
this.element = cwu.elementFromPoint(json.x, json.y, false, false);
cancelled = !this.sendEvent("touchstart", json, this.element);
break;
case "Browser:MouseUp":
if (this.element)
this.sendEvent("touchend", json, this.element);
this.element = null;
break;
case "Browser:MouseMove":
if (this.element)
cancelled = !this.sendEvent("touchmove", json, this.element);
break;
}
if (cancelled)
sendAsyncMessage("Browser:CaptureEvents", { messageId: json.messageId,
panning: true });
},
sendEvent: function(aName, aData, aElement) {
if (!Services.prefs.getBoolPref("dom.w3c_touch_events.enabled"))
return true;
let evt = content.document.createEvent("touchevent");
let point = content.document.createTouch(content, aElement, 0,
aData.x, aData.y, aData.x, aData.y, aData.x, aData.y,
1, 1, 0, 0);
let touches = content.document.createTouchList(point);
if (aName == "touchend") {
let empty = content.document.createTouchList();
evt.initTouchEvent(aName, true, true, content, 0, true, true, true, true, empty, empty, touches);
} else {
evt.initTouchEvent(aName, true, true, content, 0, true, true, true, true, touches, touches, touches);
}
return aElement.dispatchEvent(evt);
}
}
TouchEventHandler.init();

Просмотреть файл

@ -335,6 +335,7 @@ MouseModule.prototype = {
this.dY += dragData.prevPanY - sY; this.dY += dragData.prevPanY - sY;
if (dragData.isPan()) { if (dragData.isPan()) {
this.sendMove(aEvent.clientX, aEvent.clientY, aEvent.target);
// Only pan when mouse event isn't part of a click. Prevent jittering on tap. // Only pan when mouse event isn't part of a click. Prevent jittering on tap.
this._kinetic.addData(sX - dragData.prevPanX, sY - dragData.prevPanY); this._kinetic.addData(sX - dragData.prevPanX, sY - dragData.prevPanY);
this._dragBy(this.dX, this.dY); this._dragBy(this.dX, this.dY);
@ -360,6 +361,14 @@ MouseModule.prototype = {
} }
}, },
sendMove: function(aX, aY, aTarget) {
let event = document.createEvent("Events");
event.initEvent("TapMove", true, true);
event.clientX = aX;
event.clientY = aY;
aTarget.dispatchEvent(event);
},
/** /**
* Inform our dragger of a dragStart. * Inform our dragger of a dragStart.
*/ */
@ -1246,4 +1255,3 @@ GestureModule.prototype = {
return r0.translate(offsetX, offsetY); return r0.translate(offsetX, offsetY);
} }
}; };