Bug 350471 - Reenable pixel scrolling (two-finger touchpad), r=smaug r=smichaud sr=roc

This commit is contained in:
Markus Stange 2008-09-17 13:27:19 +02:00
Родитель e4827f616e
Коммит 32449e2eac
17 изменённых файлов: 687 добавлений и 182 удалений

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

@ -410,6 +410,7 @@ nsContentUtils::InitializeEventTable() {
{ &nsGkAtoms::onDOMFocusIn, { NS_UI_FOCUSIN, EventNameType_HTMLXUL }}, { &nsGkAtoms::onDOMFocusIn, { NS_UI_FOCUSIN, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMFocusOut, { NS_UI_FOCUSOUT, EventNameType_HTMLXUL }}, { &nsGkAtoms::onDOMFocusOut, { NS_UI_FOCUSOUT, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMMouseScroll, { NS_MOUSE_SCROLL, EventNameType_HTMLXUL }}, { &nsGkAtoms::onDOMMouseScroll, { NS_MOUSE_SCROLL, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onMozMousePixelScroll, { NS_MOUSE_PIXEL_SCROLL, EventNameType_HTMLXUL }},
{ &nsGkAtoms::oninput, { NS_FORM_INPUT, EventNameType_HTMLXUL }}, { &nsGkAtoms::oninput, { NS_FORM_INPUT, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onpageshow, { NS_PAGE_SHOW, EventNameType_HTML }}, { &nsGkAtoms::onpageshow, { NS_PAGE_SHOW, EventNameType_HTML }},
{ &nsGkAtoms::onpagehide, { NS_PAGE_HIDE, EventNameType_HTML }}, { &nsGkAtoms::onpagehide, { NS_PAGE_HIDE, EventNameType_HTML }},

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

@ -641,6 +641,7 @@ GK_ATOM(onmousemove, "onmousemove")
GK_ATOM(onmouseout, "onmouseout") GK_ATOM(onmouseout, "onmouseout")
GK_ATOM(onmouseover, "onmouseover") GK_ATOM(onmouseover, "onmouseover")
GK_ATOM(onmouseup, "onmouseup") GK_ATOM(onmouseup, "onmouseup")
GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
GK_ATOM(ononline, "ononline") GK_ATOM(ononline, "ononline")
GK_ATOM(onoffline, "onoffline") GK_ATOM(onoffline, "onoffline")
GK_ATOM(onoverflow, "onoverflow") GK_ATOM(onoverflow, "onoverflow")

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

@ -70,8 +70,8 @@ static const char* const sEventNames[] = {
"DOMNodeRemovedFromDocument", "DOMNodeInsertedIntoDocument", "DOMNodeRemovedFromDocument", "DOMNodeInsertedIntoDocument",
"DOMAttrModified", "DOMCharacterDataModified", "DOMAttrModified", "DOMCharacterDataModified",
"DOMActivate", "DOMFocusIn", "DOMFocusOut", "DOMActivate", "DOMFocusIn", "DOMFocusOut",
"pageshow", "pagehide", "DOMMouseScroll", "offline", "online", "pageshow", "pagehide", "DOMMouseScroll", "MozMousePixelScroll",
"copy", "cut", "paste" "offline", "online", "copy", "cut", "paste"
#ifdef MOZ_SVG #ifdef MOZ_SVG
, ,
"SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll", "SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll",
@ -479,6 +479,8 @@ nsDOMEvent::SetEventType(const nsAString& aEventTypeArg)
} else if (mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) { } else if (mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
if (atom == nsGkAtoms::onDOMMouseScroll) if (atom == nsGkAtoms::onDOMMouseScroll)
mEvent->message = NS_MOUSE_SCROLL; mEvent->message = NS_MOUSE_SCROLL;
else if (atom == nsGkAtoms::onMozMousePixelScroll)
mEvent->message = NS_MOUSE_PIXEL_SCROLL;
} else if (mEvent->eventStructType == NS_DRAG_EVENT) { } else if (mEvent->eventStructType == NS_DRAG_EVENT) {
if (atom == nsGkAtoms::ondragstart) if (atom == nsGkAtoms::ondragstart)
mEvent->message = NS_DRAGDROP_START; mEvent->message = NS_DRAGDROP_START;
@ -1393,6 +1395,8 @@ const char* nsDOMEvent::GetEventName(PRUint32 aEventType)
return sEventNames[eDOMEvents_pagehide]; return sEventNames[eDOMEvents_pagehide];
case NS_MOUSE_SCROLL: case NS_MOUSE_SCROLL:
return sEventNames[eDOMEvents_DOMMouseScroll]; return sEventNames[eDOMEvents_DOMMouseScroll];
case NS_MOUSE_PIXEL_SCROLL:
return sEventNames[eDOMEvents_MozMousePixelScroll];
case NS_OFFLINE: case NS_OFFLINE:
return sEventNames[eDOMEvents_offline]; return sEventNames[eDOMEvents_offline];
case NS_ONLINE: case NS_ONLINE:

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

@ -124,6 +124,7 @@ public:
eDOMEvents_pageshow, eDOMEvents_pageshow,
eDOMEvents_pagehide, eDOMEvents_pagehide,
eDOMEvents_DOMMouseScroll, eDOMEvents_DOMMouseScroll,
eDOMEvents_MozMousePixelScroll,
eDOMEvents_offline, eDOMEvents_offline,
eDOMEvents_online, eDOMEvents_online,
eDOMEvents_copy, eDOMEvents_copy,

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

@ -113,7 +113,7 @@
#include "nsIScrollableViewProvider.h" #include "nsIScrollableViewProvider.h"
#include "nsIDOMDocumentRange.h" #include "nsIDOMDocumentRange.h"
#include "nsIDOMDocumentEvent.h" #include "nsIDOMDocumentEvent.h"
#include "nsIDOMMouseEvent.h" #include "nsIDOMMouseScrollEvent.h"
#include "nsIDOMDragEvent.h" #include "nsIDOMDragEvent.h"
#include "nsIDOMEventTarget.h" #include "nsIDOMEventTarget.h"
#include "nsIDOMDocumentView.h" #include "nsIDOMDocumentView.h"
@ -141,6 +141,7 @@
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "nsITimer.h" #include "nsITimer.h"
#include "nsIFontMetrics.h"
#include "nsIDragService.h" #include "nsIDragService.h"
#include "nsIDragSession.h" #include "nsIDragSession.h"
@ -460,7 +461,9 @@ nsEventStateManager::nsEventStateManager()
mNormalLMouseEventInProcess(PR_FALSE), mNormalLMouseEventInProcess(PR_FALSE),
m_haveShutdown(PR_FALSE), m_haveShutdown(PR_FALSE),
mBrowseWithCaret(PR_FALSE), mBrowseWithCaret(PR_FALSE),
mTabbedThroughDocument(PR_FALSE) mTabbedThroughDocument(PR_FALSE),
mLastLineScrollConsumedX(PR_FALSE),
mLastLineScrollConsumedY(PR_FALSE)
{ {
if (sESMInstanceCount == 0) { if (sESMInstanceCount == 0) {
gUserInteractionTimerCallback = new nsUITimerCallback(); gUserInteractionTimerCallback = new nsUITimerCallback();
@ -1407,6 +1410,20 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext,
} }
} }
break; break;
case NS_MOUSE_PIXEL_SCROLL:
{
if (mCurrentFocus) {
mCurrentTargetContent = mCurrentFocus;
}
// When the last line scroll has been canceled, eat the pixel scroll event
nsMouseScrollEvent *msEvent = static_cast<nsMouseScrollEvent*>(aEvent);
if ((msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal) ?
mLastLineScrollConsumedX : mLastLineScrollConsumedY) {
*aStatus = nsEventStatus_eConsumeNoDefault;
}
}
break;
case NS_QUERY_SELECTED_TEXT: case NS_QUERY_SELECTED_TEXT:
{ {
nsQueryContentEventHandler handler(mPresContext); nsQueryContentEventHandler handler(mPresContext);
@ -2414,6 +2431,66 @@ GetParentFrameToScroll(nsPresContext* aPresContext, nsIFrame* aFrame)
return aFrame->GetParent(); return aFrame->GetParent();
} }
static nsIScrollableView*
GetScrollableViewForFrame(nsPresContext* aPresContext, nsIFrame* aFrame)
{
for (; aFrame; aFrame = GetParentFrameToScroll(aPresContext, aFrame)) {
nsIScrollableViewProvider* svp;
CallQueryInterface(aFrame, &svp);
if (svp) {
nsIScrollableView* scrollView = svp->GetScrollableView();
if (scrollView)
return scrollView;
}
}
return nsnull;
}
void
nsEventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
nsMouseScrollEvent* aEvent,
nsPresContext* aPresContext,
nsEventStatus* aStatus)
{
nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
if (!targetContent)
GetFocusedContent(getter_AddRefs(targetContent));
if (!targetContent)
return;
while (targetContent->IsNodeOfType(nsINode::eTEXT)) {
targetContent = targetContent->GetParent();
}
nsIScrollableView* scrollView = GetScrollableViewForFrame(aPresContext, aTargetFrame);
nscoord lineHeight = 0;
if (scrollView) {
scrollView->GetLineHeight(&lineHeight);
} else {
// Fall back to the font height of the target frame.
const nsStyleFont* font = aTargetFrame->GetStyleFont();
const nsFont& f = font->mFont;
nsCOMPtr<nsIFontMetrics> fm = aPresContext->GetMetricsFor(f);
NS_ASSERTION(fm, "FontMetrics is null!");
if (fm)
fm->GetHeight(lineHeight);
}
PRBool isTrusted = (aEvent->flags & NS_EVENT_FLAG_TRUSTED) != 0;
nsMouseScrollEvent event(isTrusted, NS_MOUSE_PIXEL_SCROLL, nsnull);
event.refPoint = aEvent->refPoint;
event.widget = aEvent->widget;
event.time = aEvent->time;
event.isShift = aEvent->isShift;
event.isControl = aEvent->isControl;
event.isAlt = aEvent->isAlt;
event.isMeta = aEvent->isMeta;
event.scrollFlags = aEvent->scrollFlags;
event.delta = aPresContext->AppUnitsToIntCSSPixels(aEvent->delta * lineHeight);
nsEventDispatcher::Dispatch(targetContent, aPresContext, &event, nsnull, aStatus);
}
nsresult nsresult
nsEventStateManager::DoScrollText(nsPresContext* aPresContext, nsEventStateManager::DoScrollText(nsPresContext* aPresContext,
nsIFrame* aTargetFrame, nsIFrame* aTargetFrame,
@ -2732,79 +2809,106 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext,
} }
break; break;
case NS_MOUSE_SCROLL: case NS_MOUSE_SCROLL:
if (nsEventStatus_eConsumeNoDefault != *aStatus) { case NS_MOUSE_PIXEL_SCROLL:
{
nsMouseScrollEvent *msEvent = static_cast<nsMouseScrollEvent*>(aEvent);
// Build the preference keys, based on the event properties. if (aEvent->message == NS_MOUSE_SCROLL) {
nsMouseScrollEvent *msEvent = (nsMouseScrollEvent*) aEvent; // Mark the subsequent pixel scrolls as valid / invalid, based on the
// observation if the previous line scroll has been canceled
NS_NAMED_LITERAL_CSTRING(actionslot, ".action"); if (msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal) {
NS_NAMED_LITERAL_CSTRING(sysnumlinesslot, ".sysnumlines"); mLastLineScrollConsumedX = (nsEventStatus_eConsumeNoDefault == *aStatus);
} else if (msEvent->scrollFlags & nsMouseScrollEvent::kIsVertical) {
nsCAutoString baseKey; mLastLineScrollConsumedY = (nsEventStatus_eConsumeNoDefault == *aStatus);
GetBasePrefKeyForMouseWheel(msEvent, baseKey); }
if (!(msEvent->scrollFlags & nsMouseScrollEvent::kHasPixels)) {
// Extract the preferences // No generated pixel scroll event will follow.
nsCAutoString actionKey(baseKey); // Create and send a pixel scroll DOM event now.
actionKey.Append(actionslot); SendPixelScrollEvent(aTargetFrame, msEvent, presContext, aStatus);
}
nsCAutoString sysNumLinesKey(baseKey);
sysNumLinesKey.Append(sysnumlinesslot);
PRInt32 action = nsContentUtils::GetIntPref(actionKey.get());
PRBool useSysNumLines =
nsContentUtils::GetBoolPref(sysNumLinesKey.get());
if (useSysNumLines) {
if (msEvent->scrollFlags & nsMouseScrollEvent::kIsFullPage)
action = MOUSE_SCROLL_PAGE;
else if (msEvent->scrollFlags & nsMouseScrollEvent::kIsPixels)
action = MOUSE_SCROLL_PIXELS;
} }
switch (action) { if (*aStatus != nsEventStatus_eConsumeNoDefault) {
case MOUSE_SCROLL_N_LINES: // Build the preference keys, based on the event properties.
{ NS_NAMED_LITERAL_CSTRING(actionslot, ".action");
DoScrollText(presContext, aTargetFrame, msEvent, msEvent->delta, NS_NAMED_LITERAL_CSTRING(sysnumlinesslot, ".sysnumlines");
(msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal),
eScrollByLine);
}
break;
case MOUSE_SCROLL_PAGE: nsCAutoString baseKey;
{ GetBasePrefKeyForMouseWheel(msEvent, baseKey);
DoScrollText(presContext, aTargetFrame, msEvent, msEvent->delta,
(msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal),
eScrollByPage);
}
break;
case MOUSE_SCROLL_PIXELS: // Extract the preferences
{ nsCAutoString actionKey(baseKey);
DoScrollText(presContext, aTargetFrame, msEvent, msEvent->delta, actionKey.Append(actionslot);
(msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal),
eScrollByPixel);
}
break;
case MOUSE_SCROLL_HISTORY: nsCAutoString sysNumLinesKey(baseKey);
{ sysNumLinesKey.Append(sysnumlinesslot);
DoScrollHistory(msEvent->delta);
}
break;
case MOUSE_SCROLL_ZOOM: PRInt32 action = nsContentUtils::GetIntPref(actionKey.get());
{ PRBool useSysNumLines =
DoScrollZoom(aTargetFrame, msEvent->delta); nsContentUtils::GetBoolPref(sysNumLinesKey.get());
}
break;
default: // Including -1 (do nothing) if (useSysNumLines) {
break; if (msEvent->scrollFlags & nsMouseScrollEvent::kIsFullPage)
action = MOUSE_SCROLL_PAGE;
}
if (aEvent->message == NS_MOUSE_PIXEL_SCROLL) {
if (action == MOUSE_SCROLL_N_LINES) {
action = MOUSE_SCROLL_PIXELS;
} else {
// Do not scroll pixels when zooming
action = -1;
}
} else if (msEvent->scrollFlags & nsMouseScrollEvent::kHasPixels) {
if (action == MOUSE_SCROLL_N_LINES) {
// We shouldn't scroll lines when a pixel scroll event will follow.
action = -1;
}
}
switch (action) {
case MOUSE_SCROLL_N_LINES:
{
DoScrollText(presContext, aTargetFrame, msEvent, msEvent->delta,
(msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal),
eScrollByLine);
}
break;
case MOUSE_SCROLL_PAGE:
{
DoScrollText(presContext, aTargetFrame, msEvent, msEvent->delta,
(msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal),
eScrollByPage);
}
break;
case MOUSE_SCROLL_PIXELS:
{
DoScrollText(presContext, aTargetFrame, msEvent, msEvent->delta,
(msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal),
eScrollByPixel);
}
break;
case MOUSE_SCROLL_HISTORY:
{
DoScrollHistory(msEvent->delta);
}
break;
case MOUSE_SCROLL_ZOOM:
{
DoScrollZoom(aTargetFrame, msEvent->delta);
}
break;
default: // Including -1 (do nothing)
break;
}
*aStatus = nsEventStatus_eConsumeNoDefault;
} }
*aStatus = nsEventStatus_eConsumeNoDefault;
} }
break; break;
case NS_DRAGDROP_ENTER: case NS_DRAGDROP_ENTER:

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

@ -295,6 +295,10 @@ protected:
nsIFrame* &targetOuterFrame, nsIFrame* &targetOuterFrame,
nsPresContext* &presCtxOuter); nsPresContext* &presCtxOuter);
void SendPixelScrollEvent(nsIFrame* aTargetFrame,
nsMouseScrollEvent* aEvent,
nsPresContext* aPresContext,
nsEventStatus* aStatus);
typedef enum { typedef enum {
eScrollByPixel, eScrollByPixel,
eScrollByLine, eScrollByLine,
@ -445,6 +449,10 @@ protected:
nsCOMArray<nsIDocShell> mTabbingFromDocShells; nsCOMArray<nsIDocShell> mTabbingFromDocShells;
// Unlocks pixel scrolling
PRPackedBool mLastLineScrollConsumedX;
PRPackedBool mLastLineScrollConsumedY;
#ifdef CLICK_HOLD_CONTEXT_MENUS #ifdef CLICK_HOLD_CONTEXT_MENUS
enum { kClickHoldDelay = 500 } ; // 500ms == 1/2 second enum { kClickHoldDelay = 500 } ; // 500ms == 1/2 second

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

@ -51,6 +51,7 @@ _TEST_FILES = \
test_bug336682_1.html \ test_bug336682_1.html \
test_bug336682_2.xul \ test_bug336682_2.xul \
test_bug336682.js \ test_bug336682.js \
test_bug350471.xul \
test_bug367781.html \ test_bug367781.html \
test_bug368835.html \ test_bug368835.html \
test_bug379120.html \ test_bug379120.html \

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

@ -0,0 +1,229 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=350471
-->
<window title="Mozilla Bug 350471"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<title>Test for Bug 350471</title>
<script type="application/javascript" src="/MochiKit/packed.js" />
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"/>
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=350471">Mozilla Bug 350471</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
</body>
<vbox style="height: 150px; background: cyan; overflow: auto;" id="scrollbox">
<hbox style="height: 8000px;"><vbox style="width: 8000px;"/></hbox>
</vbox>
<script class="testbody" type="application/javascript;version=1.7"><![CDATA[
/** Test for Bug 350471 **/
// This test depends on general.smoothScroll being off.
const minLineHeight = 10, maxLineHeight = 20;
function between(x, min, max) (min <= max) ? (min <= x && x <= max) : (max <= x && x <= min);
function isbetween(x, min, max, msg) ok(between(x, min, max), msg + " - Expected " + min + " to " + max + ", got " + x);
function testEventDispatching() {
function helper(aAxis, aDelta, aKind, aShiftKey, aCtrlKey, aAltKey, aMetaKey) {
let expectedEvents = [];
let deltaUnit = "";
function listener(e) {
if (!expectedEvents.length) {
ok(false, "Received an event that I didn't expect. type: " + e.type +
", axis: " + e.axis + ", delta: " + e.delta);
return;
}
let expected = expectedEvents.shift();
["type", "shiftKey", "ctrlKey", "altKey", "metaKey"].forEach(function(field) {
is(e[field], expected[field],
"e." + field + " (" + e[field] + ") does not match expected value (" + expected[field] + ")");
});
let expectedAxis = expected.axis == "horizontal" ? e.HORIZONTAL_AXIS : e.VERTICAL_AXIS;
is(e.axis, expectedAxis,
"e.axis (" + e.axis + ") does not match expected value (" + expectedAxis + ")");
// When modifier keys are pressed, cancel the event.
// We don't want to zoom or navigate back / forward (history scroll).
if (aShiftKey || aCtrlKey || aAltKey || aMetaKey) {
e.preventDefault();
// Note: If this is a DOMMouseScroll event without hasPixels, we still
// expect a follow-up MozMousePixelScroll event.
} else {
// Only check the delta if no modifiers are pressed.
// History scroll and zoom change the deltas in nsESM::PreHandleEvent.
if (deltaUnit == (e.type == "DOMMouseScroll" ? "lines" : "pixels")) {
// no unit conversion necessary
is(e.detail, expected.delta,
"e.detail (" + e.detail + ") does not match expected value (" + expected.delta + ")");
} else if (e.type == "MozMousePixelScroll") {
// We sent a line scroll event but are receiving a pixel scroll event,
// so we need to convert the delta.
let minDelta = expected.delta * minLineHeight;
let maxDelta = expected.delta * maxLineHeight;
isbetween(e.detail, minDelta, maxDelta, "wrong pixel scroll event delta");
}
}
e.stopPropagation();
}
// Set up the expected values.
if (aKind == 0 || aKind == 1) {
expectedEvents.push({
type: "DOMMouseScroll",
axis: aAxis,
delta: aDelta,
hasPixels: (aKind == 1),
shiftKey: aShiftKey,
ctrlKey: aCtrlKey,
altKey: aAltKey,
metaKey: aMetaKey
});
}
if (aKind == 0 || aKind == 2) {
expectedEvents.push({
type: "MozMousePixelScroll",
axis: aAxis,
delta: aDelta,
shiftKey: aShiftKey,
ctrlKey: aCtrlKey,
altKey: aAltKey,
metaKey: aMetaKey
});
}
deltaUnit = aKind == 2 ? "pixels" : "lines";
document.addEventListener("DOMMouseScroll", listener, true);
document.addEventListener("MozMousePixelScroll", listener, true);
// Send the event to the documentElement.
synthesizeMouseScroll(document.documentElement, 10, 10, expectedEvents[0]);
document.removeEventListener("DOMMouseScroll", listener, true);
document.removeEventListener("MozMousePixelScroll", listener, true);
// expectedEvents should be empty now. If it's not, print errors.
expectedEvents.forEach(function(e) {
ok(false, "Didn't receive expected event: " + JSON.toString(e));
});
};
let i = 0;
[0, 1, 2].forEach(function(aKind) {
["horizontal", "vertical"].forEach(function(aAxis) {
[false, true].forEach(function(aShift) {
[false, true].forEach(function(aCtrl) {
[false, true].forEach(function(aAlt) {
[false, true].forEach(function(aMeta) {
helper(aAxis, [-5, -1, 0, 1, 5][i++ % 5], aKind, aShift, aCtrl, aAlt, aMeta);
});
});
});
});
});
});
}
function testDefaultHandling() {
let scrollbox = document.getElementById("scrollbox");
let currentTest = "";
function scrollWithPreventDefault(aEvent, aDoConsume) {
function listener(e) {
if (aDoConsume[e.type])
e.preventDefault();
}
scrollbox.addEventListener("DOMMouseScroll", listener, true);
scrollbox.addEventListener("MozMousePixelScroll", listener, true);
synthesizeMouseScroll(scrollbox, 10, 10, aEvent);
scrollbox.removeEventListener("DOMMouseScroll", listener, true);
scrollbox.removeEventListener("MozMousePixelScroll", listener, true);
}
function helper(aType, aHasPixels, aAxis, aStart, aDelta, aConsumeLine, aConsumePixel, aPositionShouldChange) {
scrollbox.scrollLeft = aStart;
scrollbox.scrollTop = aStart;
scrollWithPreventDefault({
type: aType,
axis: aAxis,
hasPixels: aHasPixels,
delta: aDelta
}, {
"DOMMouseScroll": aConsumeLine,
"MozMousePixelScroll": aConsumePixel
});
let newPos = scrollbox[aAxis == "horizontal" ? "scrollLeft" : "scrollTop"];
let newPosWrongAxis = scrollbox[aAxis == "horizontal" ? "scrollTop" : "scrollLeft"];
is(newPosWrongAxis, aStart, currentTest + " wrong axis scrolled - type: " + aType);
if (aPositionShouldChange) {
if (aType == "MozMousePixelScroll") {
// aDelta is in pixels, no conversion necessary
is(newPos, aStart + aDelta, currentTest + " wrong scroll position - type: " + aType);
} else {
// Use minLineHeight and maxLineHeight as an estimate for the conversion factor.
isbetween(newPos, aStart + aDelta * minLineHeight, aStart + aDelta * maxLineHeight,
currentTest + " wrong scroll position - type: " + aType);
}
} else {
is(newPos, aStart, currentTest + " The scroll position shouldn't have changed. - type: " + aType);
}
}
["horizontal", "vertical"].forEach(function(aAxis) {
[-5, 5].forEach(function(aDelta) {
[false, true].forEach(function(aConsumeLine) {
[false, true].forEach(function(aConsumePixel) {
let shouldScroll = !aConsumeLine && !aConsumePixel;
currentTest = "normal DOMMouseScroll: only scroll if neither line nor pixel scroll are consumed.";
helper("DOMMouseScroll", false, aAxis, 4000, aDelta, aConsumeLine, aConsumePixel, shouldScroll);
currentTest = "DOMMouseScroll with hasPixels: never scroll.";
helper("DOMMouseScroll", true, aAxis, 4000, aDelta, aConsumeLine, aConsumePixel, false);
currentTest = "MozMousePixelScroll (consumed: " + aConsumePixel +
") with preceding DOMMouseScroll (consumed: " + aConsumeLine +
"): " + (shouldScroll ? "scroll." : "don't scroll.");
// It shouldn't matter:
// 1. whether hasPixels is set on the preceding DOMMouseScroll event or
// 2. whether the preceding DOMMouseScroll event's MozMousePixelScroll event is consumed.
helper("DOMMouseScroll", true, aAxis, 4000, aDelta, aConsumeLine, false, false);
helper("MozMousePixelScroll", false, aAxis, 4000, aDelta, false, aConsumePixel, shouldScroll);
helper("DOMMouseScroll", false, aAxis, 4000, aDelta, aConsumeLine, false, !aConsumeLine);
helper("MozMousePixelScroll", false, aAxis, 4000, aDelta, false, aConsumePixel, shouldScroll);
helper("DOMMouseScroll", false, aAxis, 4000, aDelta, aConsumeLine, true, false);
helper("MozMousePixelScroll", false, aAxis, 4000, aDelta, false, aConsumePixel, shouldScroll);
});
});
});
});
}
function runTests() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
Components.utils.import("resource://gre/modules/JSON.jsm");
testEventDispatching();
testDefaultHandling();
SimpleTest.finish();
}
window.onload = function() { setTimeout(runTests, 0); };
SimpleTest.waitForExplicitFinish();
]]></script>
</window>

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

@ -130,6 +130,7 @@ interface nsIDOMWindowUtils : nsISupports {
/** Synthesize a mouse scroll event for a window. The event types supported /** Synthesize a mouse scroll event for a window. The event types supported
* are: * are:
* DOMMouseScroll * DOMMouseScroll
* MozMousePixelScroll
* *
* Events are sent in coordinates offset by aX and aY from the window. * Events are sent in coordinates offset by aX and aY from the window.
* *
@ -143,7 +144,7 @@ interface nsIDOMWindowUtils : nsISupports {
* @param aButton button to synthesize * @param aButton button to synthesize
* @param aScrollFlags flag bits --- see nsMouseScrollFlags in nsGUIEvent.h * @param aScrollFlags flag bits --- see nsMouseScrollFlags in nsGUIEvent.h
* @param aDelta the direction and amount to scroll (in lines or pixels, * @param aDelta the direction and amount to scroll (in lines or pixels,
* depending on whether kIsPixels is set in aScrollFlags) * depending on the event type)
* @param aModifiers modifiers pressed, using constants defined in nsIDOMNSEvent * @param aModifiers modifiers pressed, using constants defined in nsIDOMNSEvent
*/ */
void sendMouseScrollEvent(in AString aType, void sendMouseScrollEvent(in AString aType,

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

@ -270,6 +270,8 @@ nsDOMWindowUtils::SendMouseScrollEvent(const nsAString& aType,
PRInt32 msg; PRInt32 msg;
if (aType.EqualsLiteral("DOMMouseScroll")) if (aType.EqualsLiteral("DOMMouseScroll"))
msg = NS_MOUSE_SCROLL; msg = NS_MOUSE_SCROLL;
else if (aType.EqualsLiteral("MozMousePixelScroll"))
msg = NS_MOUSE_PIXEL_SCROLL;
else else
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;

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

@ -902,6 +902,9 @@ pref("mousewheel.transaction.timeout", 1500);
// mouse wheel scroll transaction is held even if the mouse cursor is moved. // mouse wheel scroll transaction is held even if the mouse cursor is moved.
pref("mousewheel.transaction.ignoremovedelay", 100); pref("mousewheel.transaction.ignoremovedelay", 100);
// Macbook touchpad two finger pixel scrolling
pref("mousewheel.enable_pixel_scrolling", true);
// 0=lines, 1=pages, 2=history , 3=text size // 0=lines, 1=pages, 2=history , 3=text size
pref("mousewheel.withnokey.action",0); pref("mousewheel.withnokey.action",0);
pref("mousewheel.withnokey.numlines",1); pref("mousewheel.withnokey.numlines",1);

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

@ -230,17 +230,18 @@ function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
* aOffsetY. * aOffsetY.
* *
* aEvent is an object which may contain the properties: * aEvent is an object which may contain the properties:
* shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, units, delta * shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels
* *
* If the type is specified, an mouse scroll event of that type is fired. Otherwise, * If the type is specified, a mouse scroll event of that type is fired. Otherwise,
* "DOMMouseScroll" is used. * "DOMMouseScroll" is used.
* *
* If the axis is specified, it must be one of "horizontal" or "vertical". If not specified, * If the axis is specified, it must be one of "horizontal" or "vertical". If not specified,
* "vertical" is used. * "vertical" is used.
* *
* 'delta' is the amount to scroll by (can be positive or negative). It must * 'delta' is the amount to scroll by (can be positive or negative). It must
* be specified. 'units' is the units of 'delta', either "pixels" or "lines"; "lines" * be specified.
* is the default if 'units' is ommitted. *
* 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags.
* *
* aWindow is optional, and defaults to the current window object. * aWindow is optional, and defaults to the current window object.
*/ */
@ -257,7 +258,7 @@ function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
// See nsMouseScrollFlags in nsGUIEvent.h // See nsMouseScrollFlags in nsGUIEvent.h
const kIsVertical = 0x02; const kIsVertical = 0x02;
const kIsHorizontal = 0x04; const kIsHorizontal = 0x04;
const kIsPixels = 0x08; const kHasPixels = 0x08;
var button = aEvent.button || 0; var button = aEvent.button || 0;
var modifiers = _parseModifiers(aEvent); var modifiers = _parseModifiers(aEvent);
@ -267,10 +268,9 @@ function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
var type = aEvent.type || "DOMMouseScroll"; var type = aEvent.type || "DOMMouseScroll";
var axis = aEvent.axis || "vertical"; var axis = aEvent.axis || "vertical";
var units = aEvent.units || "lines";
var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical; var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
if (units == "pixels") { if (aEvent.hasPixels) {
scrollFlags |= kIsPixels; scrollFlags |= kHasPixels;
} }
utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button, utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button,
scrollFlags, aEvent.delta, modifiers); scrollFlags, aEvent.delta, modifiers);

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

@ -73,59 +73,86 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=378028
<script type="application/javascript"><![CDATA[ <script type="application/javascript"><![CDATA[
/** Test for Bug 378028 **/ /** Test for Bug 378028 **/
/* and for Bug 350471 **/
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
/* There are three kinds of scroll events:
1. line scrolls without hasPixels
2. line scrolls with hasPixels
3. pixel scrolls
Listboxes and arrowscrollboxes (DOM event scrolling) should only react to
line scrolls and ignore hasPixels.
Richlistboxes ("native" scrolling) should be scrollable by kind 1 and 3.
*/
const kinds = [
{ eventType: "DOMMouseScroll", hasPixels: false, shouldScrollDOM: true, shouldScrollNative: true },
{ eventType: "DOMMouseScroll", hasPixels: true, shouldScrollDOM: true, shouldScrollNative: false },
{ eventType: "MozMousePixelScroll", hasPixels: false, shouldScrollDOM: false, shouldScrollNative: true }
];
function testListbox(id) function testListbox(id)
{ {
var listbox = document.getElementById(id); var listbox = document.getElementById(id);
function helper(aStart, aDelta) function helper(aStart, aDelta, aKind)
{ {
listbox.scrollToIndex(aStart); listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10, synthesizeMouseScroll(listbox, 10, 10,
{axis:"vertical", delta:aDelta}); {axis:"vertical", delta:aDelta, type:aKind.eventType,
is(listbox.getIndexOfFirstVisibleRow(), aStart + aDelta, hasPixels:aKind.hasPixels});
"mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta); is(listbox.getIndexOfFirstVisibleRow(), aKind.shouldScrollDOM ? aStart + aDelta : aStart,
"mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
// Check that horizontal scrolling has no effect // Check that horizontal scrolling has no effect
listbox.scrollToIndex(aStart); listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10, synthesizeMouseScroll(listbox, 10, 10,
{axis:"horizontal", delta:aDelta}); {axis:"horizontal", delta:aDelta, type:aKind.eventType,
hasPixels:aKind.hasPixels});
is(listbox.getIndexOfFirstVisibleRow(), aStart, is(listbox.getIndexOfFirstVisibleRow(), aStart,
"mouse-scroll of '" + id + "' horizontal starting " + aStart + " delta " + aDelta); "mouse-scroll of '" + id + "' horizontal starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
} }
kinds.forEach(function(aKind) {
helper(2, -1); helper(2, -1, aKind);
helper(2, 1); helper(2, 1, aKind);
helper(2, -2); helper(2, -2, aKind);
helper(2, 2); helper(2, 2, aKind);
});
} }
function testRichListbox(id) function testRichListbox(id)
{ {
var listbox = document.getElementById(id); var listbox = document.getElementById(id);
function helper(aStart, aDelta, aExpected) function helper(aStart, aDelta, aExpected, aKind)
{ {
listbox.scrollToIndex(aStart); listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10, synthesizeMouseScroll(listbox, 10, 10,
{axis:"vertical", delta:aDelta}); {axis:"vertical", delta:aDelta, type:aKind.eventType,
is(listbox.getIndexOfFirstVisibleRow(), aExpected, hasPixels:aKind.hasPixels});
"mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta); is(listbox.getIndexOfFirstVisibleRow(), aKind.shouldScrollNative ? aExpected : aStart,
"mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
// Check that horizontal scrolling has no effect // Check that horizontal scrolling has no effect
listbox.scrollToIndex(aStart); listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10, synthesizeMouseScroll(listbox, 10, 10,
{axis:"horizontal", delta:aDelta}); {axis:"horizontal", delta:aDelta, type:aKind.eventType,
hasPixels:aKind.hasPixels});
is(listbox.getIndexOfFirstVisibleRow(), aStart, is(listbox.getIndexOfFirstVisibleRow(), aStart,
"mouse-scroll of '" + id + "' horizontal starting " + aStart + " delta " + aDelta); "mouse-scroll of '" + id + "' horizontal starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
} }
// richlistbox currently uses native XUL scrolling, so the "line" // richlistbox currently uses native XUL scrolling, so the "line"
// amounts don't necessarily correspond 1-to-1 with listbox items. So // amounts don't necessarily correspond 1-to-1 with listbox items. So
// we just check that scrolling up/down a lot hits the first/last items // we just check that scrolling up/down a lot hits the first/last items
helper(2, -100, 0); kinds.forEach(function(aKind) {
helper(2, 100, listbox.getRowCount() - listbox.getNumberOfVisibleRows()); helper(2, -100, 0, aKind);
helper(2, 100, listbox.getRowCount() - listbox.getNumberOfVisibleRows(), aKind);
});
} }
function testArrowScrollbox(id) function testArrowScrollbox(id)
@ -134,7 +161,7 @@ function testArrowScrollbox(id)
var scrollBoxObject = scrollbox.scrollBoxObject; var scrollBoxObject = scrollbox.scrollBoxObject;
var orient = scrollbox.getAttribute("orient"); var orient = scrollbox.getAttribute("orient");
function helper(aStart, aDelta, aExpected) function helper(aStart, aDelta, aExpected, aKind)
{ {
var xpos = {}; var xpos = {};
var ypos = {}; var ypos = {};
@ -142,23 +169,27 @@ function testArrowScrollbox(id)
scrollBoxObject.scrollTo(aStart, aStart); scrollBoxObject.scrollTo(aStart, aStart);
synthesizeMouseScroll(scrollbox, 5, 5, synthesizeMouseScroll(scrollbox, 5, 5,
{axis:"vertical", delta:aDelta}); {axis:"vertical", delta:aDelta, type:aKind.eventType,
hasPixels:aKind.hasPixels});
scrollBoxObject.getPosition(xpos, ypos); scrollBoxObject.getPosition(xpos, ypos);
// Note, vertical mouse scrolling is allowed to scroll horizontal // Note, vertical mouse scrolling is allowed to scroll horizontal
// arrowscrollboxes, because many users have no horizontal mouse scroll // arrowscrollboxes, because many users have no horizontal mouse scroll
// capability // capability
is(pos.value, aExpected, is(pos.value, aKind.shouldScrollDOM ? aExpected : aStart,
"mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta); "mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
scrollBoxObject.scrollTo(aStart, aStart); scrollBoxObject.scrollTo(aStart, aStart);
synthesizeMouseScroll(scrollbox, 5, 5, synthesizeMouseScroll(scrollbox, 5, 5,
{axis:"horizontal", delta:aDelta}); {axis:"horizontal", delta:aDelta, type:aKind.eventType,
hasPixels:aKind.hasPixels});
// horizontal mouse scrolling is never allowed to scroll vertical // horizontal mouse scrolling is never allowed to scroll vertical
// arrowscrollboxes // arrowscrollboxes
scrollBoxObject.getPosition(xpos, ypos); scrollBoxObject.getPosition(xpos, ypos);
var expected = orient == "horizontal" ? aExpected : aStart; var expected = (aKind.shouldScrollDOM && (orient == "horizontal")) ? aExpected : aStart;
is(pos.value, expected, is(pos.value, expected,
"mouse-scroll of '" + id + "' horizontal starting " + aStart + " delta " + aDelta); "mouse-scroll of '" + id + "' horizontal starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
} }
var scrolledWidth = {}; var scrolledWidth = {};
@ -168,8 +199,10 @@ function testArrowScrollbox(id)
var scrollMaxY = scrolledHeight.value - scrollBoxObject.height; var scrollMaxY = scrolledHeight.value - scrollBoxObject.height;
var scrollMax = orient == "horizontal" ? scrollMaxX : scrollMaxY; var scrollMax = orient == "horizontal" ? scrollMaxX : scrollMaxY;
helper(50, -100, 0); kinds.forEach(function(aKind) {
helper(50, 100, scrollMax); helper(50, -100, 0, aKind);
helper(50, 100, scrollMax, aKind);
});
} }
function runTests() function runTests()

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

@ -1096,24 +1096,37 @@ function testtag_tree_column_reorder()
function testtag_tree_mousescroll(aTree) function testtag_tree_mousescroll(aTree)
{ {
function helper(aStart, aDelta) /* Scroll event kinds, see test_mousescroll.xul */
const kinds = [
{ eventType: "DOMMouseScroll", hasPixels: false, shouldScrollDOM: true, shouldScrollNative: true },
{ eventType: "DOMMouseScroll", hasPixels: true, shouldScrollDOM: true, shouldScrollNative: false },
{ eventType: "MozMousePixelScroll", hasPixels: false, shouldScrollDOM: false, shouldScrollNative: true }
];
function helper(aStart, aDelta, aKind)
{ {
aTree.treeBoxObject.scrollToRow(aStart); aTree.treeBoxObject.scrollToRow(aStart);
synthesizeMouseScroll(aTree.body, 1, 1, synthesizeMouseScroll(aTree.body, 1, 1,
{type:"DOMMouseScroll", axis:"vertical", delta:aDelta}); {axis:"vertical", delta:aDelta, type:aKind.eventType,
is(aTree.treeBoxObject.getFirstVisibleRow(), aStart + aDelta, "mouse-scroll vertical starting " + aStart + " delta " + aDelta); hasPixels:aKind.hasPixels});
var expected = aKind.shouldScrollDOM ? aStart + aDelta : aStart;
is(aTree.treeBoxObject.getFirstVisibleRow(), expected, "mouse-scroll vertical starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
aTree.treeBoxObject.scrollToRow(aStart); aTree.treeBoxObject.scrollToRow(aStart);
// Check that horizontal scrolling has no effect // Check that horizontal scrolling has no effect
synthesizeMouseScroll(aTree.body, 1, 1, synthesizeMouseScroll(aTree.body, 1, 1,
{type:"DOMMouseScroll", axis:"horizontal", delta:aDelta}); {axis:"horizontal", delta:aDelta, type:aKind.eventType,
is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, "mouse-scroll horizontal starting " + aStart + " delta " + aDelta); hasPixels:aKind.hasPixels});
is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, "mouse-scroll horizontal starting " + aStart + " delta " + aDelta
+ " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
} }
helper(2, -1); kinds.forEach(function(aKind) {
helper(2, 1); helper(2, -1, aKind);
helper(2, -2); helper(2, 1, aKind);
helper(2, 2); helper(2, -2, aKind);
helper(2, 2, aKind);
});
} }
function synthesizeColumnDrag(aTree, aMouseDownColumnNumber, aMouseUpColumnNumber, aAfter) function synthesizeColumnDrag(aTree, aMouseDownColumnNumber, aMouseUpColumnNumber, aAfter)

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

@ -271,6 +271,7 @@ class nsHashKey;
// Scroll events // Scroll events
#define NS_MOUSE_SCROLL_START 1600 #define NS_MOUSE_SCROLL_START 1600
#define NS_MOUSE_SCROLL (NS_MOUSE_SCROLL_START) #define NS_MOUSE_SCROLL (NS_MOUSE_SCROLL_START)
#define NS_MOUSE_PIXEL_SCROLL (NS_MOUSE_SCROLL_START + 1)
#define NS_SCROLLPORT_START 1700 #define NS_SCROLLPORT_START 1700
#define NS_SCROLLPORT_UNDERFLOW (NS_SCROLLPORT_START) #define NS_SCROLLPORT_UNDERFLOW (NS_SCROLLPORT_START)
@ -849,6 +850,43 @@ public:
nsTextEventReply theReply; nsTextEventReply theReply;
}; };
/* Mouse Scroll Events: Line Scrolling, Pixel Scrolling and Common Event Flows
*
* There are two common event flows:
* (1) Normal line scrolling:
* 1. An NS_MOUSE_SCROLL event without kHasPixels is dispatched to Gecko.
* 2. A DOMMouseScroll event is sent into the DOM.
* 3. A MozMousePixelScroll event is sent into the DOM.
* 4. If neither event has been consumed, the default handling of the
* NS_MOUSE_SCROLL event is executed.
*
* (2) Pixel scrolling:
* 1. An NS_MOUSE_SCROLL event with kHasPixels is dispatched to Gecko.
* 2. A DOMMouseScroll event is sent into the DOM.
* 3. No scrolling takes place in the default handler.
* 4. An NS_MOUSE_PIXEL_SCROLL event is dispatched to Gecko.
* 5. A MozMousePixelScroll event is sent into the DOM.
* 6. If neither the NS_MOUSE_PIXELSCROLL event nor the preceding
* NS_MOUSE_SCROLL event have been consumed, the default handler scrolls.
* 7. Steps 4.-6. are repeated for every pixel scroll that belongs to
* the announced line scroll. Once enough pixels have been sent to
* complete a line, a new NS_MOUSE_SCROLL event is sent (goto step 1.).
*
* If a DOMMouseScroll event has been preventDefaulted, the associated
* following MozMousePixelScroll events are still sent - they just don't result
* in any scrolling (their default handler isn't executed).
*
* How many pixel scrolls make up one line scroll is decided in the widget layer
* where the NS_MOUSE(_PIXEL)_SCROLL events are created.
*
* This event flow model satisfies several requirements:
* - DOMMouseScroll handlers don't need to be aware of the existence of pixel
* scrolling.
* - preventDefault on a DOMMouseScroll event results in no scrolling.
* - DOMMouseScroll events aren't polluted with a kHasPixels flag.
* - You can make use of pixel scroll DOM events (MozMousePixelScroll).
*/
class nsMouseScrollEvent : public nsMouseEvent_base class nsMouseScrollEvent : public nsMouseEvent_base
{ {
public: public:
@ -856,7 +894,15 @@ public:
kIsFullPage = 1 << 0, kIsFullPage = 1 << 0,
kIsVertical = 1 << 1, kIsVertical = 1 << 1,
kIsHorizontal = 1 << 2, kIsHorizontal = 1 << 2,
kIsPixels = 1 << 3 kHasPixels = 1 << 3 // Marks line scroll events that are provided as
// a fallback for pixel scroll events.
// These scroll events are used by things that can't
// be scrolled pixel-wise, like trees. You should
// ignore them when processing pixel scroll events
// to avoid double-processing the same scroll gesture.
// When kHasPixels is set, the event is guaranteed to
// be followed up by an event that contains pixel
// scrolling information.
}; };
nsMouseScrollEvent(PRBool isTrusted, PRUint32 msg, nsIWidget *w) nsMouseScrollEvent(PRBool isTrusted, PRUint32 msg, nsIWidget *w)

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

@ -99,6 +99,22 @@ extern "C" long TSMProcessRawKeyEvent(EventRef carbonEvent);
@end @end
// Needed to support pixel scrolling.
// See http://developer.apple.com/qa/qa2005/qa1453.html.
// kEventMouseScroll is only defined on 10.5+. Using the moz prefix avoids
// potential symbol conflicts.
// This should be changed when 10.4 support is dropped.
enum {
mozkEventMouseScroll = 11
};
// Support for pixel scroll deltas, not part of NSEvent.h
// See http://lists.apple.com/archives/cocoa-dev/2007/Feb/msg00050.html
@interface NSEvent (DeviceDelta)
- (float)deviceDeltaX;
- (float)deviceDeltaY;
@end
@interface ChildView : NSView< @interface ChildView : NSView<
#ifdef ACCESSIBILITY #ifdef ACCESSIBILITY
mozAccessible, mozAccessible,

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

@ -3493,77 +3493,119 @@ static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
if (!mGeckoChild) if (!mGeckoChild)
return; return;
float scrollDelta; float scrollDelta = 0;
float scrollDeltaPixels = 0;
PRBool checkPixels = PR_TRUE;
if (inAxis & nsMouseScrollEvent::kIsVertical) nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
scrollDelta = -[theEvent deltaY]; if (prefs)
else if (inAxis & nsMouseScrollEvent::kIsHorizontal) prefs->GetBoolPref("mousewheel.enable_pixel_scrolling", &checkPixels);
scrollDelta = -[theEvent deltaX];
else
return; // caller screwed up
if (scrollDelta == 0) EventRef theCarbonEvent = [theEvent _eventRef];
// No sense in firing off a Gecko event. Note that as of 10.4 Tiger, UInt32 carbonEventKind = theCarbonEvent ? ::GetEventKind(theCarbonEvent) : 0;
// a single NSScrollWheel event might result in deltaX = deltaY = 0. // Calling deviceDeltaX or deviceDeltaY on theEvent will trigger a Cocoa
return; // assertion and an Objective-C NSInternalInconsistencyException if the
// underlying "Carbon" event doesn't contain pixel scrolling information.
nsMouseScrollEvent geckoEvent(PR_TRUE, NS_MOUSE_SCROLL, nsnull); // For these events, carbonEventKind is kEventMouseWheelMoved instead of
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; // kEventMouseScroll.
geckoEvent.scrollFlags |= inAxis; if (carbonEventKind != mozkEventMouseScroll)
checkPixels = PR_FALSE;
// Gecko only understands how to scroll by an integer value. Using floor // Some scrolling devices supports pixel scrolling, e.g. a Macbook
// and ceil is better than truncating the fraction, especially when // touchpad or a Mighty Mouse. On those devices, [event deviceDeltaX/Y]
// |delta| < 1. // contains the amount of pixels to scroll.
if (scrollDelta < 0) if (inAxis & nsMouseScrollEvent::kIsVertical) {
geckoEvent.delta = (PRInt32)floorf(scrollDelta); scrollDelta = -[theEvent deltaY];
else if (checkPixels && (scrollDelta == 0 || scrollDelta != floor(scrollDelta))) {
geckoEvent.delta = (PRInt32)ceilf(scrollDelta); scrollDeltaPixels = -[theEvent deviceDeltaY];
nsAutoRetainCocoaObject kungFuDeathGrip(self);
mGeckoChild->DispatchWindowEvent(geckoEvent);
if (!mGeckoChild)
return;
// dispatch scroll wheel carbon event for plugins
{
EventRef theEvent;
OSStatus err = ::MacCreateEvent(NULL,
kEventClassMouse,
kEventMouseWheelMoved,
TicksToEventTime(TickCount()),
kEventAttributeUserEvent,
&theEvent);
if (err == noErr) {
EventMouseWheelAxis axis;
if (inAxis & nsMouseScrollEvent::kIsVertical)
axis = kEventMouseWheelAxisY;
else if (inAxis & nsMouseScrollEvent::kIsHorizontal)
axis = kEventMouseWheelAxisX;
SetEventParameter(theEvent,
kEventParamMouseWheelAxis,
typeMouseWheelAxis,
sizeof(EventMouseWheelAxis),
&axis);
SInt32 delta = (SInt32)-geckoEvent.delta;
SetEventParameter(theEvent,
kEventParamMouseWheelDelta,
typeLongInteger,
sizeof(SInt32),
&delta);
Point mouseLoc;
::GetGlobalMouse(&mouseLoc);
SetEventParameter(theEvent,
kEventParamMouseLocation,
typeQDPoint,
sizeof(Point),
&mouseLoc);
::SendEventToEventTarget(theEvent, GetWindowEventTarget((WindowRef)[[self window] windowRef]));
ReleaseEvent(theEvent);
} }
} else if (inAxis & nsMouseScrollEvent::kIsHorizontal) {
scrollDelta = -[theEvent deltaX];
if (checkPixels && (scrollDelta == 0 || scrollDelta != floor(scrollDelta))) {
scrollDeltaPixels = -[theEvent deviceDeltaX];
}
} else {
return; // caller screwed up
}
BOOL hasPixels = (scrollDeltaPixels != 0);
if (!hasPixels && scrollDelta == 0)
// No sense in firing off a Gecko event.
return;
if (scrollDelta != 0) {
// Send the line scroll event.
nsMouseScrollEvent geckoEvent(PR_TRUE, NS_MOUSE_SCROLL, nsnull);
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
geckoEvent.scrollFlags |= inAxis;
if (hasPixels)
geckoEvent.scrollFlags |= nsMouseScrollEvent::kHasPixels;
// Gecko only understands how to scroll by an integer value. Using floor
// and ceil is better than truncating the fraction, especially when
// |delta| < 1.
if (scrollDelta < 0)
geckoEvent.delta = (PRInt32)floorf(scrollDelta);
else
geckoEvent.delta = (PRInt32)ceilf(scrollDelta);
nsAutoRetainCocoaObject kungFuDeathGrip(self);
mGeckoChild->DispatchWindowEvent(geckoEvent);
if (!mGeckoChild)
return;
// dispatch scroll wheel carbon event for plugins
{
EventRef theEvent;
OSStatus err = ::MacCreateEvent(NULL,
kEventClassMouse,
kEventMouseWheelMoved,
TicksToEventTime(TickCount()),
kEventAttributeUserEvent,
&theEvent);
if (err == noErr) {
EventMouseWheelAxis axis;
if (inAxis & nsMouseScrollEvent::kIsVertical)
axis = kEventMouseWheelAxisY;
else if (inAxis & nsMouseScrollEvent::kIsHorizontal)
axis = kEventMouseWheelAxisX;
SetEventParameter(theEvent,
kEventParamMouseWheelAxis,
typeMouseWheelAxis,
sizeof(EventMouseWheelAxis),
&axis);
SInt32 delta = (SInt32)-geckoEvent.delta;
SetEventParameter(theEvent,
kEventParamMouseWheelDelta,
typeLongInteger,
sizeof(SInt32),
&delta);
Point mouseLoc;
::GetGlobalMouse(&mouseLoc);
SetEventParameter(theEvent,
kEventParamMouseLocation,
typeQDPoint,
sizeof(Point),
&mouseLoc);
::SendEventToEventTarget(theEvent, GetWindowEventTarget((WindowRef)[[self window] windowRef]));
ReleaseEvent(theEvent);
}
}
}
if (hasPixels) {
// Send the pixel scroll event.
nsMouseScrollEvent geckoEvent(PR_TRUE, NS_MOUSE_PIXEL_SCROLL, nsnull);
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
geckoEvent.scrollFlags |= inAxis;
geckoEvent.delta = NSToIntRound(scrollDeltaPixels);
nsAutoRetainCocoaObject kungFuDeathGrip(self);
mGeckoChild->DispatchWindowEvent(geckoEvent);
} }
NS_OBJC_END_TRY_ABORT_BLOCK; NS_OBJC_END_TRY_ABORT_BLOCK;