Bug 45871: need to be able to scroll xul, r=mfinkle

This commit is contained in:
Ben Combee 2009-02-27 14:44:35 -05:00
Родитель 8bb8c4418c
Коммит 4d80c6628c
3 изменённых файлов: 310 добавлений и 71 удалений

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

@ -17,7 +17,7 @@
*
* The Initial Developer of the Original Code is
* Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2008
* Portions created by the Initial Developer are Copyright (C) 2008, 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
@ -64,6 +64,18 @@ function getScrollboxFromElement(elem) {
return scrollbox;
}
function dumpEvent(aEvent) {
dump("{ ");
for(var item in aEvent) {
var value = aEvent[item];
dump(item + ": ");
dump(value);
dump(", ");
}
dump("}\n");
}
/**
* Everything that is registed in _modules gets called with each event that the
* InputHandler is registered to listen for.
@ -89,20 +101,21 @@ function InputHandler() {
content.addEventListener("keydown", this, true);
content.addEventListener("keyup", this, true);
let prefsvc = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch2);
let prefsvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
let allowKinetic = prefsvc.getBoolPref("browser.ui.panning.kinetic");
this._modules.push(new PanningModule(this, allowKinetic));
this._modules.push(new ClickingModule(this));
this._modules.push(new ChromeInputModule(this));
this._modules.push(new ContentPanningModule(this, allowKinetic));
this._modules.push(new ContentClickingModule(this));
this._modules.push(new ScrollwheelModule(this));
}
InputHandler.prototype = {
_modules : [],
_grabbed : null,
_ignoreEvents: false,
grab: function(obj) {
grab: function grab(obj) {
this._grabbed = obj;
for each(mod in this._modules) {
@ -113,13 +126,24 @@ InputHandler.prototype = {
// call cancel on all modules
},
ungrab: function(obj) {
ungrab: function ungrab(obj) {
this._grabbed = null;
// only send events to this object
// call cancel on all modules
},
handleEvent: function (aEvent) {
startListening: function startListening() {
this._ignoreEvents = false;
},
stopListening: function stopListening() {
this._ignoreEvents = true;
},
handleEvent: function handleEvent(aEvent) {
if (this._ignoreEvents)
return;
if (this._grabbed) {
this._grabbed.handleEvent(aEvent);
}
@ -136,29 +160,26 @@ InputHandler.prototype = {
/**
* Kinetic panning code
* Panning code for chrome elements
*/
function PanningModule(owner, useKinetic) {
function ChromeInputModule(owner, useKinetic) {
this._owner = owner;
if (useKinetic !== undefined)
this._dragData.useKinetic = useKinetic;
}
PanningModule.prototype = {
ChromeInputModule.prototype = {
_owner: null,
_dragData: {
dragging: false,
sX: 0,
sY: 0,
useKinetic: true,
dragStartTimeout: -1,
// if set, we're scrolling a XUL element, not content
targetScrollbox: null,
// needed to prevent content panning when user clicks on non-draggable chrome
panningContent: null,
reset: function() {
reset: function reset() {
this.dragging = false;
this.sX = 0;
this.sY = 0;
@ -166,7 +187,235 @@ PanningModule.prototype = {
clearTimeout(this.dragStartTimeout);
this.dragStartTimeout = -1;
this.targetScrollbox = null;
this.panningContent = false;
}
},
_clickData: {
_clickTimeout : -1,
_events : [],
reset: function reset() {
if (this._clickTimeout != -1)
clearTimeout(this._clickTimeout);
this._clickTimeout = -1;
this._events = [];
},
},
handleEvent: function handleEvent(aEvent) {
switch (aEvent.type) {
case "mousedown":
this._onMouseDown(aEvent);
break;
case "mousemove":
this._onMouseMove(aEvent);
break;
case "mouseup":
this._onMouseUp(aEvent);
break;
}
},
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function cancelPending() {
this._dragData.reset();
},
_dragStart: function _dragStart(sX, sY) {
let dragData = this._dragData;
dragData.dragging = true;
dragData.dragStartTimeout = -1;
// grab all events until we stop the drag
ws.dragStart(sX, sY);
// prevent clicks from being sent once we start drag
this._clickData.reset();
},
_dragStop: function _dragStop(sX, sY) {
let dragData = this._dragData;
if (dragData.targetScrollbox)
dragData.targetScrollbox.scrollBy(dragData.sX - sX, dragData.sY - sY);
},
_dragMove: function _dragMove(sX, sY) {
let dragData = this._dragData;
if (dragData.targetScrollbox)
dragData.targetScrollbox.scrollBy(dragData.sX - sX, dragData.sY - sY);
},
_onMouseDown: function _onMouseDown(aEvent) {
// exit early for events in the content area
if (aEvent.target === document.getElementById("browser-canvas")) {
return;
}
let dragData = this._dragData;
dragData.targetScrollbox = getScrollboxFromElement(aEvent.target);
if (!dragData.targetScrollbox)
return;
// absorb the event for the scrollable XUL element and make all future events grabbed too
this._owner.grab(this);
aEvent.stopPropagation();
aEvent.preventDefault();
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
dragData.dragStartTimeout = setTimeout(function(self, sX, sY) { self._dragStart(sX, sY); },
200, this, aEvent.screenX, aEvent.screenY);
// store away the event for possible sending later if a drag doesn't happen
let clickData = this._clickData;
let clickEvent = document.createEvent("MouseEvent");
clickEvent.initMouseEvent(aEvent.type, aEvent.bubbles, aEvent.cancelable,
aEvent.view, aEvent.detail,
aEvent.screenX, aEvent.screenY, aEvent.clientX, aEvent.clientY,
aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKeyArg, aEvent.metaKeyArg,
aEvent.button, aEvent.relatedTarget);
clickData._events.push({event: clickEvent, target: aEvent.target, time: Date.now()});
// we're waiting for a click
if (clickData._clickTimeout != -1) {
// if we just got another mousedown, don't send anything until we get another mousedown
clearTimeout(clickData._clickTimeout);
clickData.clickTimeout = -1;
}
},
_onMouseUp: function _onMouseUp(aEvent) {
// only process if original mousedown was on a scrollable element
let dragData = this._dragData;
if (!dragData.targetScrollbox)
return;
let clickData = this._clickData;
// keep an eye out for mouseups that didn't start with a mousedown
if (!(clickData._events.length % 2)) {
clickData.reset();
}
else {
let clickEvent = document.createEvent("MouseEvent");
clickEvent.initMouseEvent(aEvent.type, aEvent.bubbles, aEvent.cancelable,
aEvent.view, aEvent.detail,
aEvent.screenX, aEvent.screenY, aEvent.clientX, aEvent.clientY,
aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKeyArg, aEvent.metaKeyArg,
aEvent.button, aEvent.relatedTarget);
clickData._events.push({event: clickEvent, target: aEvent.target, time: Date.now()});
if (clickData._clickTimeout == -1) {
clickData._clickTimeout = setTimeout(function(self) { self._sendSingleClick(); }, 400, this);
}
else {
clearTimeout(clickData._clickTimeout);
// this._sendDoubleClick();
}
}
aEvent.stopPropagation();
aEvent.preventDefault();
if (dragData.dragging)
this._dragStop(aEvent.screenX, aEvent.screenY);
dragData.reset(); // be sure to reset the timer
this._owner.ungrab(this);
},
_onMouseMove: function _onMouseMove(aEvent) {
let dragData = this._dragData;
// only process if original mousedown was on a scrollable element
if (!dragData.targetScrollbox)
return;
aEvent.stopPropagation();
aEvent.preventDefault();
let dx = dragData.sX - aEvent.screenX;
let dy = dragData.sY - aEvent.screenY;
if (!dragData.dragging && dragData.dragStartTimeout != -1) {
if ((Math.abs(dx*dx) + Math.abs(dy*dy)) > 100) {
clearTimeout(dragData.dragStartTimeout);
this._dragStart(aEvent.screenX, aEvent.screenY);
}
}
if (!dragData.dragging)
return;
this._dragMove(aEvent.screenX, aEvent.screenY);
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
},
// resend original events with our handler out of the loop
_sendSingleClick: function _sendSingleClick() {
let clickData = this._clickData;
this._owner.grab(this);
this._owner.stopListening();
// send original mouseDown/mouseUps again
this._redispatchChromeMouseEvent(clickData._events[0].event);
this._redispatchChromeMouseEvent(clickData._events[1].event);
this._owner.startListening();
this._owner.ungrab(this);
clickData.reset();
},
_redispatchChromeMouseEvent: function _redispatchChromeMouseEvent(aEvent) {
if (!(aEvent instanceof MouseEvent)) {
Cu.reportError("_redispatchChromeMouseEvent called with a non-mouse event");
return;
}
var cwu = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
// Redispatch the mouse event, ignoring the root scroll frame
cwu.sendMouseEvent(aEvent.type, aEvent.clientX, aEvent.clientY,
aEvent.button, aEvent.detail, 0, true);
}
};
/**
* Kinetic panning code for content
*/
function ContentPanningModule(owner, useKinetic) {
this._owner = owner;
if (useKinetic !== undefined)
this._dragData.useKinetic = useKinetic;
}
ContentPanningModule.prototype = {
_owner: null,
_dragData: {
dragging: false,
sX: 0,
sY: 0,
useKinetic: true,
dragStartTimeout: -1,
reset: function reset() {
this.dragging = false;
this.sX = 0;
this.sY = 0;
if (this.dragStartTimeout != -1)
clearTimeout(this.dragStartTimeout);
this.dragStartTimeout = -1;
}
},
@ -188,7 +437,7 @@ PanningModule.prototype = {
kineticStartY : 0,
kineticInitialVel: 0,
reset: function() {
reset: function reset() {
if (this.kineticHandle != -1) {
window.clearInterval(this.kineticHandle);
this.kineticHandle = -1;
@ -207,7 +456,11 @@ PanningModule.prototype = {
}
},
handleEvent: function(aEvent) {
handleEvent: function handleEvent(aEvent) {
// exit early for events outside displayed content area
if (aEvent.target !== document.getElementById("browser-canvas"))
return;
switch (aEvent.type) {
case "mousedown":
this._onMouseDown(aEvent);
@ -224,66 +477,51 @@ PanningModule.prototype = {
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function() {
cancelPending: function cancelPending() {
this._dragData.reset();
// XXX we should cancel kinetic here as well
},
_dragStart: function(sX, sY) {
_dragStart: function _dragStart(sX, sY) {
let dragData = this._dragData;
dragData.dragging = true;
dragData.dragStartTimeout = -1;
// grab all events until we stop the drag
this._owner.grab(this);
if (dragData.panningContent) {
if (dragData.useKinetic)
Browser.canvasBrowser.prepareForPanning();
ws.dragStart(sX, sY);
}
ws.dragStart(sX, sY);
// set the kinetic start time
this._kineticData.lastTime = Date.now();
},
_dragStop: function(sX, sY) {
_dragStop: function _dragStop(sX, sY) {
let dragData = this._dragData;
this._owner.ungrab(this);
if (dragData.targetScrollbox) {
dragData.targetScrollbox.scrollBy(dragData.sX - sX, dragData.sY - sY);
if (dragData.useKinetic) {
// start kinetic scrolling here for canvas only
if (!this._startKinetic(sX, sY))
this._endKinetic(sX, sY);
}
else if (dragData.panningContent) {
if (dragData.useKinetic) {
// start kinetic scrolling here for canvas only
if (!this._startKinetic(sX, sY))
this._endKinetic(sX, sY);
}
else {
ws.dragStop(sX, sY);
}
else {
ws.dragStop(sX, sY);
}
},
_dragMove: function(sX, sY) {
_dragMove: function _dragMove(sX, sY) {
let dragData = this._dragData;
if (dragData.targetScrollbox)
dragData.targetScrollbox.scrollBy(dragData.sX - sX, dragData.sY - sY);
else if (dragData.panningContent)
ws.dragMove(sX, sY);
ws.dragMove(sX, sY);
},
_onMouseDown: function(aEvent) {
_onMouseDown: function _onMouseDown(aEvent) {
// if we're in the process of kineticly scrolling, stop and start over
if (this.kineticHandle != -1)
this._endKinetic(aEvent.screenX, aEvent.screenY);
let dragData = this._dragData;
dragData.panningContent = (aEvent.target === document.getElementById("browser-canvas"));
if (!dragData.panningContent)
dragData.targetScrollbox = getScrollboxFromElement(aEvent.target);
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
@ -291,7 +529,7 @@ PanningModule.prototype = {
200, this, aEvent.screenX, aEvent.screenY);
},
_onMouseUp: function(aEvent) {
_onMouseUp: function _onMouseUp(aEvent) {
let dragData = this._dragData;
if (dragData.dragging)
@ -300,7 +538,7 @@ PanningModule.prototype = {
dragData.reset(); // be sure to reset the timer
},
_onMouseMove: function(aEvent) {
_onMouseMove: function _onMouseMove(aEvent) {
// don't do anything if we're in the process of kineticly scrolling
if (this._kineticData.kineticHandle != -1)
return;
@ -324,7 +562,7 @@ PanningModule.prototype = {
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
if (dragData.panningContent && dragData.useKinetic) {
if (dragData.useKinetic) {
// update our kinetic data
let kineticData = this._kineticData;
let t = Date.now();
@ -338,7 +576,7 @@ PanningModule.prototype = {
}
},
_startKinetic: function(sX, sY) {
_startKinetic: function _startKinetic(sX, sY) {
let kineticData = this._kineticData;
let dx = 0;
@ -395,7 +633,7 @@ PanningModule.prototype = {
return true;
},
_doKinetic: function(self) {
_doKinetic: function _doKinetic(self) {
let kineticData = self._kineticData;
let t = kineticData.kineticStep * kineticData.kineticStepSize;
@ -413,7 +651,7 @@ PanningModule.prototype = {
self._endKinetic(newX, newY);
},
_endKinetic: function(sX, sY) {
_endKinetic: function _endKinetic(sX, sY) {
ws.dragStop(sX, sY);
this._owner.ungrab(this);
this._dragData.reset();
@ -444,17 +682,17 @@ PanningModule.prototype = {
* Mouse click handlers
*/
function ClickingModule(owner) {
function ContentClickingModule(owner) {
this._owner = owner;
}
ClickingModule.prototype = {
ContentClickingModule.prototype = {
_clickTimeout : -1,
_events : [],
_zoomed : false,
handleEvent: function (aEvent) {
handleEvent: function handleEvent(aEvent) {
// exit early for events outside displayed content area
if (aEvent.target !== document.getElementById("browser-canvas"))
return;
@ -497,11 +735,11 @@ ClickingModule.prototype = {
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function() {
cancelPending: function cancelPending() {
this._reset();
},
_reset: function() {
_reset: function _reset() {
if (this._clickTimeout != -1)
clearTimeout(this._clickTimeout);
this._clickTimeout = -1;
@ -509,20 +747,18 @@ ClickingModule.prototype = {
this._events = [];
},
_sendSingleClick: function() {
_sendSingleClick: function _sendSingleClick() {
this._owner.grab(this);
this._redispatchMouseEvent(this._events[0].event);
this._redispatchMouseEvent(this._events[1].event);
this._dispatchContentMouseEvent(this._events[0].event);
this._dispatchContentMouseEvent(this._events[1].event);
this._owner.ungrab(this);
this._reset();
},
_sendDoubleClick: function() {
_sendDoubleClick: function _sendDoubleClick() {
this._owner.grab(this);
// XXX disable zooming until it works properly.
function optimalElementForPoint(cX, cY) {
var element = Browser.canvasBrowser.elementFromPoint(cX, cY);
if (!element)
@ -563,17 +799,16 @@ ClickingModule.prototype = {
},
_redispatchMouseEvent: function(aEvent, aType) {
_dispatchContentMouseEvent: function _dispatchContentMouseEvent(aEvent, aType) {
if (!(aEvent instanceof MouseEvent)) {
Cu.reportError("_redispatchMouseEvent called with a non-mouse event");
Cu.reportError("_dispatchContentMouseEvent called with a non-mouse event");
return;
}
var [x, y] = Browser.canvasBrowser._clientToContentCoords(aEvent.clientX, aEvent.clientY);
var cwin = Browser.selectedBrowser.contentWindow;
var cwu = cwin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
var cwu = cwin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
// Redispatch the mouse event, ignoring the root scroll frame
cwu.sendMouseEvent(aType || aEvent.type,
@ -584,7 +819,6 @@ ClickingModule.prototype = {
}
};
/**
* Scrollwheel zooming handler
*/
@ -594,7 +828,7 @@ function ScrollwheelModule(owner) {
}
ScrollwheelModule.prototype = {
handleEvent: function (aEvent) {
handleEvent: function handleEvent(aEvent) {
switch (aEvent.type) {
// UI panning events
case "DOMMouseScroll":
@ -608,6 +842,6 @@ ScrollwheelModule.prototype = {
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function() {
cancelPending: function cancelPending() {
}
};

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

@ -577,6 +577,8 @@
document.getAnonymousElementByAttribute(this, "anonid", "child-items");
</field>
<property name="scrollBoxObject" readonly="true" onget="return this._children.scrollBoxObject;"/>
<property name="activeItem">
<getter>
<![CDATA[
@ -800,6 +802,8 @@
document.getAnonymousElementByAttribute(this, "anonid", "items");
</field>
<property name="scrollBoxObject" readonly="true" onget="return this._items.scrollBoxObject;"/>
<property name="selectedItem" readonly="true">
<getter>
<![CDATA[

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

@ -402,6 +402,7 @@ toolbarbutton.panel-button {
padding: 0px 9px !important;
margin: 0px !important;
border: none !important;
-moz-user-select: none !important;
}
placelabel.bookmark-folder {