Bug 462935 - Magnify (pinch) gesture should match Safari's behavior on MacOS. r=gavin, r=Mardak, ui-r=beltzner, a1.9.1=beltzner

This commit is contained in:
Tom Dyas 2008-11-27 15:07:02 -08:00
Родитель 0be051d34f
Коммит 48275ea990
3 изменённых файлов: 377 добавлений и 74 удалений

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

@ -430,14 +430,19 @@ pref("font.language.group", "chrome://global/locale/intl.properties");
pref("intl.menuitems.alwaysappendaccesskeys","chrome://global/locale/intl.properties");
pref("intl.menuitems.insertseparatorbeforeaccesskeys","chrome://global/locale/intl.properties");
// simple gestures support
pref("browser.gesture.swipe.left", "Browser:BackOrBackDuplicate");
pref("browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate");
pref("browser.gesture.swipe.up", "cmd_scrollTop");
pref("browser.gesture.swipe.down", "cmd_scrollBottom");
pref("browser.gesture.pinch.latched", true);
pref("browser.gesture.pinch.threshold", 150);
pref("browser.gesture.pinch.out", "cmd_fullZoomEnlarge");
pref("browser.gesture.pinch.in", "cmd_fullZoomReduce");
pref("browser.gesture.pinch.out.shift", "cmd_fullZoomReset");
pref("browser.gesture.pinch.in.shift", "cmd_fullZoomReset");
pref("browser.gesture.twist.latched", false);
pref("browser.gesture.twist.threshold", 25);
pref("browser.gesture.twist.right", "Browser:NextTab");
pref("browser.gesture.twist.left", "Browser:PrevTab");

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

@ -694,8 +694,9 @@ let gGestureSupport = {
},
/**
* Dispatch events based on the type of mouse gesture event.
* For now, make sure to stop propagation of every gesture event
* Dispatch events based on the type of mouse gesture event. For now, make
* sure to stop propagation of every gesture event so that web content cannot
* receive gesture events.
*
* @param aEvent
* The gesture event to handle
@ -703,36 +704,73 @@ let gGestureSupport = {
handleEvent: function GS_handleEvent(aEvent) {
aEvent.stopPropagation();
// Create a preference object with some defaults
let def = function(aThreshold, aLatched)
({ threshold: aThreshold, latched: !!aLatched });
switch (aEvent.type) {
case "MozSwipeGesture":
return this.onSwipe(aEvent);
case "MozMagnifyGestureStart":
return this._setupGesture(aEvent, "pinch", def(150, 1), "out", "in");
case "MozRotateGestureStart":
return this.onStart(aEvent);
return this._setupGesture(aEvent, "twist", def(25, 0), "right", "left");
case "MozMagnifyGestureUpdate":
return this._handleUpdate(aEvent, 100, "pinch.out", "pinch.in");
case "MozRotateGestureUpdate":
return this._handleUpdate(aEvent, 22.5, "twist.right", "twist.left");
return this._doUpdate(aEvent);
}
},
/**
* Convert a gesture and pressed keys into the corresponding command action.
* The preference must have "shift" before "alt" before "ctrl" before "meta"
* with each separated by periods.
* Called at the start of "pinch" and "twist" gestures to setup all of the
* information needed to process the gesture
*
* @param aGestureKeys
* An array that has the gesture type as the first element and
* additional elements for each key pressed
* @return Id of the command to execute
* @param aEvent
* The continual motion start event to handle
* @param aGesture
* Name of the gesture to handle
* @param aPref
* Preference object with the names of preferences and defaults
* @param aInc
* Command to trigger for increasing motion (without gesture name)
* @param aDec
* Command to trigger for decreasing motion (without gesture name)
*/
_getCommand: function GS__getCommand(aGestureKeys) {
const gestureBranch = "browser.gesture."
try {
return gPrefService.getCharPref(gestureBranch + aGestureKeys.join("."));
}
// No preference is set, so don't give a command
catch (e) {}
_setupGesture: function GS__setupGesture(aEvent, aGesture, aPref, aInc, aDec) {
// Try to load user-set values from preferences
for (let [pref, def] in Iterator(aPref))
aPref[pref] = this._getPref(aGesture + "." + pref, def);
// Keep track of the total deltas and latching behavior
let offset = 0;
let latchDir = aEvent.delta > 0 ? 1 : -1;
let isLatched = false;
// Create the update function here to capture closure state
this._doUpdate = function GS__doUpdate(aEvent) {
// Update the offset with new event data
offset += aEvent.delta;
// Check if the cumulative deltas exceed the threshold
if (Math.abs(offset) > aPref["threshold"]) {
// Trigger the action if we don't care about latching; otherwise, make
// sure either we're not latched and going the same direction of the
// initial motion; or we're latched and going the opposite way
let sameDir = (latchDir ^ offset) >= 0;
if (!aPref["latched"] || (isLatched ^ sameDir)) {
this._doAction(aEvent, [aGesture, offset > 0 ? aInc : aDec]);
// We must be getting latched or leaving it, so just toggle
isLatched = !isLatched;
}
// Reset motion counter to prepare for more of the same gesture
offset = 0;
}
};
// The start event also contains deltas, so handle an update right away
this._doUpdate(aEvent);
},
/**
@ -762,7 +800,9 @@ let gGestureSupport = {
* @param aEvent
* The original gesture event to convert into a fake click event
* @param aGesture
* Name of the gesture
* Array of gesture name parts (to be joined by periods)
* @return Name of the command found for the event's keys and gesture. If no
* command is found, no value is returned (undefined).
*/
_doAction: function GS__doAction(aEvent, aGesture) {
// Create a fake event that pretends the gesture is a button click
@ -775,25 +815,32 @@ let gGestureSupport = {
let keyCombos = [];
const keys = ["shift", "alt", "ctrl", "meta"];
for each (let key in keys)
if (aEvent[key + "Key"])
if (aEvent[key + "Key"])
keyCombos.push(key);
try {
// Try each combination of key presses in decreasing order for commands
for (let subCombo in this._power(keyCombos)) {
let command = this._getCommand([aGesture].concat(subCombo));
// Convert a gesture and pressed keys into the corresponding command
// action where the preference has the gesture before "shift" before
// "alt" before "ctrl" before "meta" all separated by periods
let command = this._getPref(aGesture.concat(subCombo).join("."));
// Do the command if we found one to do
if (command) {
let node = document.getElementById(command);
// Use the command element if it exists
if (node && node.hasAttribute("oncommand"))
if (node && node.hasAttribute("oncommand")) {
// XXX: Use node.oncommand(event) once bug 246720 is fixed
return node.getAttribute("disabled") == "true" ? true :
if (node.getAttribute("disabled") != "true")
new Function("event", node.getAttribute("oncommand")).
call(node, fakeEvent);
call(node, fakeEvent);
}
// Otherwise it should be a "standard" command
return goDoCommand(command);
else
goDoCommand(command);
return command;
}
}
}
@ -801,6 +848,16 @@ let gGestureSupport = {
catch (e) {}
},
/**
* Convert continual motion events into an action if it exceeds a threshold
* in a given direction. This function will be set by _setupGesture to
* capture state that needs to be shared across multiple gesture updates.
*
* @param aEvent
* The continual motion update event to handle
*/
_doUpdate: function(aEvent) {},
/**
* Convert the swipe gesture into a browser action based on the direction
*
@ -808,53 +865,33 @@ let gGestureSupport = {
* The swipe event to handle
*/
onSwipe: function GS_onSwipe(aEvent) {
switch (aEvent.direction) {
case SimpleGestureEvent.DIRECTION_LEFT:
return this._doAction(aEvent, "swipe.left");
case SimpleGestureEvent.DIRECTION_RIGHT:
return this._doAction(aEvent, "swipe.right");
case SimpleGestureEvent.DIRECTION_UP:
return this._doAction(aEvent, "swipe.up");
case SimpleGestureEvent.DIRECTION_DOWN:
return this._doAction(aEvent, "swipe.down");
// Figure out which one (and only one) direction was triggered
for each (let dir in ["UP", "RIGHT", "DOWN", "LEFT"])
if (aEvent.direction == aEvent["DIRECTION_" + dir])
return this._doAction(aEvent, ["swipe", dir.toLowerCase()]);
},
/**
* Get a gesture preference or use a default if it doesn't exist
*
* @param aPref
* Name of the preference to load under the gesture branch
* @param aDef
* Default value if the preference doesn't exist
*/
_getPref: function GS__getPref(aPref, aDef) {
// Preferences branch under which all gestures preferences are stored
const branch = "browser.gesture.";
try {
// Determine what type of data to load based on default value's type
let type = typeof aDef;
let getFunc = "get" + (type == "boolean" ? "Bool" :
type == "number" ? "Int" : "Char") + "Pref";
return gPrefService[getFunc](branch + aPref);
}
},
// Keep track of offsets for continual motion events, e.g., zoom and rotate
_lastOffset: 0,
/**
* Handle the beginning of a continual motion event
*
* @param aEvent
* The continual motion event
*/
onStart: function GS_onStart(aEvent) {
this._lastOffset = 0;
},
/**
* Helper function to determine if a continual motion event has passed some
* threshold and should trigger some action. If the action is triggered, the
* tracking of the motion is reset as if a new motion has started.
*
* @param aEvent
* The continual motion event to handle
* @param aThreshold
* Minimum positive/negative difference before the action is triggered
* @param aInc
* Name of the gesture for increasing motion
* @param aDec
* Name of the gesture for decreasing motion
*/
_handleUpdate: function GS__handleUpdate(aEvent, aThreshold, aInc, aDec) {
// Update the offset with new event data
this._lastOffset += aEvent.delta;
// Do the gesture action when we pass the threshold and then reset motion
if (Math.abs(this._lastOffset) > aThreshold) {
this._doAction(aEvent, this._lastOffset > 0 ? aInc : aDec);
this.onStart(aEvent);
catch (e) {
return aDef;
}
},
};

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

@ -42,6 +42,8 @@
// reaching web content.
let test_utils;
let test_commandset;
let test_prefBranch = "browser.gesture.";
function test()
{
@ -58,8 +60,17 @@ function test()
test_TestEventListeners();
test_TestEventCreation();
// Reenable the default gestures support
// Reenable the default gestures support. The remaining tests target
// the Firefox gesture functionality.
gGestureSupport.init(true);
// Test Firefox's gestures support.
test_commandset = document.getElementById("mainCommandSet");
test_swipeGestures();
test_latchedGesture("pinch", "out", "in", "MozMagnifyGesture");
test_latchedGesture("twist", "right", "left", "MozRotateGesture");
test_thresholdGesture("pinch", "out", "in", "MozMagnifyGesture");
test_thresholdGesture("twist", "right", "left", "MozRotateGesture");
}
let test_eventCount = 0;
@ -223,3 +234,253 @@ function test_EnsureConstantsAreDisjoint()
ok(down ^ right, "DIRECTION_DOWN and DIRECTION_RIGHT are not bitwise disjoint");
ok(left ^ right, "DIRECTION_LEFT and DIRECTION_RIGHT are not bitwise disjoint");
}
// Helper for test of latched event processing. Emits the actual
// gesture events to test whether the commands associated with the
// gesture will only trigger once for each direction of movement.
function test_emitLatchedEvents(eventPrefix, initialDelta, cmd)
{
let cumulativeDelta = 0;
let isIncreasing = initialDelta > 0;
let expect = {};
// Reset the call counters and initialize expected values
for (let dir in cmd)
cmd[dir].callCount = expect[dir] = 0;
let check = function(aDir, aMsg) ok(cmd[aDir].callCount == expect[aDir], aMsg);
let checkBoth = function(aNum, aInc, aDec) {
let prefix = "Step " + aNum + ": ";
check("inc", prefix + aInc);
check("dec", prefix + aDec);
};
// Send the "Start" event.
test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, initialDelta, 0);
cumulativeDelta += initialDelta;
if (isIncreasing) {
expect.inc++;
checkBoth(1, "Increasing command was not triggered", "Decreasing command was triggered");
} else {
expect.dec++;
checkBoth(1, "Increasing command was triggered", "Decreasing command was not triggered");
}
// Send random values in the same direction and ensure neither
// command triggers.
for (let i = 0; i < 5; i++) {
let delta = Math.random() * (isIncreasing ? 100 : -100);
test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, delta, 0);
cumulativeDelta += delta;
checkBoth(2, "Increasing command was triggered", "Decreasing command was triggered");
}
// Now go back in the opposite direction.
test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0,
- initialDelta, 0);
cumulativeDelta += - initialDelta;
if (isIncreasing) {
expect.dec++;
checkBoth(3, "Increasing command was triggered", "Decreasing command was not triggered");
} else {
expect.inc++;
checkBoth(3, "Increasing command was not triggered", "Decreasing command was triggered");
}
// Send random values in the opposite direction and ensure neither
// command triggers.
for (let i = 0; i < 5; i++) {
let delta = Math.random() * (isIncreasing ? -100 : 100);
test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, delta, 0);
cumulativeDelta += delta;
checkBoth(4, "Increasing command was triggered", "Decreasing command was triggered");
}
// Go back to the original direction. The original command should trigger.
test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0,
initialDelta, 0);
cumulativeDelta += initialDelta;
if (isIncreasing) {
expect.inc++;
checkBoth(5, "Increasing command was not triggered", "Decreasing command was triggered");
} else {
expect.dec++;
checkBoth(5, "Increasing command was triggered", "Decreasing command was not triggered");
}
// Send the wrap-up event. No commands should be triggered.
test_utils.sendSimpleGestureEvent(eventPrefix, 0, cumulativeDelta, 0);
checkBoth(6, "Increasing command was triggered", "Decreasing command was triggered");
}
function test_addCommand(prefName, id)
{
let cmd = test_commandset.appendChild(document.createElement("command"));
cmd.setAttribute("id", id);
cmd.setAttribute("oncommand", "this.callCount++;");
cmd.origPrefName = prefName;
cmd.origPrefValue = gPrefService.getCharPref(prefName);
gPrefService.setCharPref(prefName, id);
return cmd;
}
function test_removeCommand(cmd)
{
gPrefService.setCharPref(cmd.origPrefName, cmd.origPrefValue);
test_commandset.removeChild(cmd);
}
// Test whether latched events are only called once per direction of motion.
function test_latchedGesture(gesture, inc, dec, eventPrefix)
{
let branch = test_prefBranch + gesture + ".";
// Put the gesture into latched mode.
let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
gPrefService.setBoolPref(branch + "latched", true);
// Install the test commands for increasing and decreasing motion.
let cmd = {
inc: test_addCommand(branch + inc, "test:incMotion"),
dec: test_addCommand(branch + dec, "test:decMotion"),
};
// Test the gestures in each direction.
test_emitLatchedEvents(eventPrefix, 500, cmd);
test_emitLatchedEvents(eventPrefix, -500, cmd);
// Restore the gesture to its original configuration.
gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
for (dir in cmd)
test_removeCommand(cmd[dir]);
}
// Test whether non-latched events are triggered upon sufficient motion.
function test_thresholdGesture(gesture, inc, dec, eventPrefix)
{
let branch = test_prefBranch + gesture + ".";
// Disable latched mode for this gesture.
let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
gPrefService.setBoolPref(branch + "latched", false);
// Set the triggering threshold value to 50.
let oldThresholdValue = gPrefService.getIntPref(branch + "threshold");
gPrefService.setIntPref(branch + "threshold", 50);
// Install the test commands for increasing and decreasing motion.
let cmdInc = test_addCommand(branch + inc, "test:incMotion");
let cmdDec = test_addCommand(branch + dec, "test:decMotion");
// Send the start event but stop short of triggering threshold.
cmdInc.callCount = cmdDec.callCount = 0;
test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 49.5, 0);
ok(cmdInc.callCount == 0, "Increasing command was triggered");
ok(cmdDec.callCount == 0, "Decreasing command was triggered");
// Now trigger the threshold.
cmdInc.callCount = cmdDec.callCount = 0;
test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 1, 0);
ok(cmdInc.callCount == 1, "Increasing command was not triggered");
ok(cmdDec.callCount == 0, "Decreasing command was triggered");
// The tracking counter should go to zero. Go back the other way and
// stop short of triggering the threshold.
cmdInc.callCount = cmdDec.callCount = 0;
test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, -49.5, 0);
ok(cmdInc.callCount == 0, "Increasing command was triggered");
ok(cmdDec.callCount == 0, "Decreasing command was triggered");
// Now cross the threshold and trigger the decreasing command.
cmdInc.callCount = cmdDec.callCount = 0;
test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, -1.5, 0);
ok(cmdInc.callCount == 0, "Increasing command was triggered");
ok(cmdDec.callCount == 1, "Decreasing command was not triggered");
// Send the wrap-up event. No commands should trigger.
cmdInc.callCount = cmdDec.callCount = 0;
test_utils.sendSimpleGestureEvent(eventPrefix, 0, -0.5, 0);
ok(cmdInc.callCount == 0, "Increasing command was triggered");
ok(cmdDec.callCount == 0, "Decreasing command was triggered");
// Restore the gesture to its original configuration.
gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
gPrefService.setIntPref(branch + "threshold", oldThresholdValue);
test_removeCommand(cmdInc);
test_removeCommand(cmdDec);
}
function test_swipeGestures()
{
// easier to type names for the direction constants
let up = SimpleGestureEvent.DIRECTION_UP;
let down = SimpleGestureEvent.DIRECTION_DOWN;
let left = SimpleGestureEvent.DIRECTION_LEFT;
let right = SimpleGestureEvent.DIRECTION_RIGHT;
let branch = test_prefBranch + "swipe.";
// Install the test commands for the swipe gestures.
let cmdUp = test_addCommand(branch + "up", "test:swipeUp");
let cmdDown = test_addCommand(branch + "down", "test:swipeDown");
let cmdLeft = test_addCommand(branch + "left", "test:swipeLeft");
let cmdRight = test_addCommand(branch + "right", "test:swipeRight");
function resetCounts() {
cmdUp.callCount = 0;
cmdDown.callCount = 0;
cmdLeft.callCount = 0;
cmdRight.callCount = 0;
}
// UP
resetCounts();
test_utils.sendSimpleGestureEvent("MozSwipeGesture", up, 0, 0);
ok(cmdUp.callCount == 1, "Step 1: Up command was not triggered");
ok(cmdDown.callCount == 0, "Step 1: Down command was triggered");
ok(cmdLeft.callCount == 0, "Step 1: Left command was triggered");
ok(cmdRight.callCount == 0, "Step 1: Right command was triggered");
// DOWN
resetCounts();
test_utils.sendSimpleGestureEvent("MozSwipeGesture", down, 0, 0);
ok(cmdUp.callCount == 0, "Step 2: Up command was triggered");
ok(cmdDown.callCount == 1, "Step 2: Down command was not triggered");
ok(cmdLeft.callCount == 0, "Step 2: Left command was triggered");
ok(cmdRight.callCount == 0, "Step 2: Right command was triggered");
// LEFT
resetCounts();
test_utils.sendSimpleGestureEvent("MozSwipeGesture", left, 0, 0);
ok(cmdUp.callCount == 0, "Step 3: Up command was triggered");
ok(cmdDown.callCount == 0, "Step 3: Down command was triggered");
ok(cmdLeft.callCount == 1, "Step 3: Left command was not triggered");
ok(cmdRight.callCount == 0, "Step 3: Right command was triggered");
// RIGHT
resetCounts();
test_utils.sendSimpleGestureEvent("MozSwipeGesture", right, 0, 0);
ok(cmdUp.callCount == 0, "Step 4: Up command was triggered");
ok(cmdDown.callCount == 0, "Step 4: Down command was triggered");
ok(cmdLeft.callCount == 0, "Step 4: Left command was triggered");
ok(cmdRight.callCount == 1, "Step 4: Right command was not triggered");
// Make sure combinations do not trigger events.
let combos = [ up | left, up | right, down | left, down | right];
for (let i = 0; i < combos.length; i++) {
resetCounts();
test_utils.sendSimpleGestureEvent("MozSwipeGesture", combos[i], 0, 0);
ok(cmdUp.callCount == 0, "Step 5-"+i+": Up command was triggered");
ok(cmdDown.callCount == 0, "Step 5-"+i+": Down command was triggered");
ok(cmdLeft.callCount == 0, "Step 5-"+i+": Left command was triggered");
ok(cmdRight.callCount == 0, "Step 5-"+i+": Right command was triggered");
}
// Remove the test commands.
test_removeCommand(cmdUp);
test_removeCommand(cmdDown);
test_removeCommand(cmdLeft);
test_removeCommand(cmdRight);
}