--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -405,16 +405,17 @@ nsContentUtils::InitializeEventTable() {
{ &nsGkAtoms::onDOMNodeRemoved, { NS_MUTATION_NODEREMOVED, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMNodeInsertedIntoDocument, { NS_MUTATION_NODEINSERTEDINTODOCUMENT, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMNodeRemovedFromDocument, { NS_MUTATION_NODEREMOVEDFROMDOCUMENT, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMSubtreeModified, { NS_MUTATION_SUBTREEMODIFIED, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMActivate, { NS_UI_ACTIVATE, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMFocusIn, { NS_UI_FOCUSIN, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onDOMFocusOut, { NS_UI_FOCUSOUT, 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::onpageshow, { NS_PAGE_SHOW, EventNameType_HTML }},
{ &nsGkAtoms::onpagehide, { NS_PAGE_HIDE, EventNameType_HTML }},
{ &nsGkAtoms::onresize, { NS_RESIZE_EVENT,
(EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
{ &nsGkAtoms::onscroll, { NS_SCROLL_EVENT,
(EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
{ &nsGkAtoms::oncopy, { NS_COPY, EventNameType_HTMLXUL }},
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -636,16 +636,17 @@ GK_ATOM(onkeyup, "onkeyup")
GK_ATOM(onLoad, "onLoad")
GK_ATOM(onload, "onload")
GK_ATOM(only, "only") // this one is not an event
GK_ATOM(onmousedown, "onmousedown")
GK_ATOM(onmousemove, "onmousemove")
GK_ATOM(onmouseout, "onmouseout")
GK_ATOM(onmouseover, "onmouseover")
GK_ATOM(onmouseup, "onmouseup")
+GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
GK_ATOM(ononline, "ononline")
GK_ATOM(onoffline, "onoffline")
GK_ATOM(onoverflow, "onoverflow")
GK_ATOM(onoverflowchanged, "onoverflowchanged")
GK_ATOM(onpagehide, "onpagehide")
GK_ATOM(onpageshow, "onpageshow")
GK_ATOM(onpaint, "onpaint")
GK_ATOM(onpaste, "onpaste")
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -65,18 +65,18 @@ static const char* const sEventNames[] =
"popuphiding", "popuphidden", "close", "command", "broadcast", "commandupdate",
"dragenter", "dragover", "dragexit", "dragdrop", "draggesture",
"drag", "dragend", "dragstart", "dragleave", "drop", "resize",
"scroll", "overflow", "underflow", "overflowchanged",
"DOMSubtreeModified", "DOMNodeInserted", "DOMNodeRemoved",
"DOMNodeRemovedFromDocument", "DOMNodeInsertedIntoDocument",
"DOMAttrModified", "DOMCharacterDataModified",
"DOMActivate", "DOMFocusIn", "DOMFocusOut",
- "pageshow", "pagehide", "DOMMouseScroll", "offline", "online",
- "copy", "cut", "paste"
+ "pageshow", "pagehide", "DOMMouseScroll", "MozMousePixelScroll",
+ "offline", "online", "copy", "cut", "paste"
#ifdef MOZ_SVG
,
"SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll",
"SVGZoom"
#endif // MOZ_SVG
#ifdef MOZ_MEDIA
,
"loadstart", "progress", "loadedmetadata", "loadedfirstframe",
@@ -474,16 +474,18 @@ nsDOMEvent::SetEventType(const nsAString
mEvent->message = NS_MOUSE_EXIT_SYNTH;
else if (atom == nsGkAtoms::onmousemove)
mEvent->message = NS_MOUSE_MOVE;
else if (atom == nsGkAtoms::oncontextmenu)
mEvent->message = NS_CONTEXTMENU;
} else if (mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
if (atom == nsGkAtoms::onDOMMouseScroll)
mEvent->message = NS_MOUSE_SCROLL;
+ else if (atom == nsGkAtoms::onMozMousePixelScroll)
+ mEvent->message = NS_MOUSE_PIXEL_SCROLL;
} else if (mEvent->eventStructType == NS_DRAG_EVENT) {
if (atom == nsGkAtoms::ondragstart)
mEvent->message = NS_DRAGDROP_START;
else if (atom == nsGkAtoms::ondraggesture)
mEvent->message = NS_DRAGDROP_GESTURE;
else if (atom == nsGkAtoms::ondragenter)
mEvent->message = NS_DRAGDROP_ENTER;
else if (atom == nsGkAtoms::ondragover)
@@ -1388,16 +1390,18 @@ const char* nsDOMEvent::GetEventName(PRU
case NS_UI_FOCUSOUT:
return sEventNames[eDOMEvents_DOMFocusOut];
case NS_PAGE_SHOW:
return sEventNames[eDOMEvents_pageshow];
case NS_PAGE_HIDE:
return sEventNames[eDOMEvents_pagehide];
case NS_MOUSE_SCROLL:
return sEventNames[eDOMEvents_DOMMouseScroll];
+ case NS_MOUSE_PIXEL_SCROLL:
+ return sEventNames[eDOMEvents_MozMousePixelScroll];
case NS_OFFLINE:
return sEventNames[eDOMEvents_offline];
case NS_ONLINE:
return sEventNames[eDOMEvents_online];
case NS_COPY:
return sEventNames[eDOMEvents_copy];
case NS_CUT:
return sEventNames[eDOMEvents_cut];
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -119,16 +119,17 @@ public:
eDOMEvents_attrmodified,
eDOMEvents_characterdatamodified,
eDOMEvents_DOMActivate,
eDOMEvents_DOMFocusIn,
eDOMEvents_DOMFocusOut,
eDOMEvents_pageshow,
eDOMEvents_pagehide,
eDOMEvents_DOMMouseScroll,
+ eDOMEvents_MozMousePixelScroll,
eDOMEvents_offline,
eDOMEvents_online,
eDOMEvents_copy,
eDOMEvents_cut,
eDOMEvents_paste
#ifdef MOZ_SVG
,
eDOMEvents_SVGLoad,
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -108,17 +108,17 @@
#include "nsIDOMDocument.h"
#include "nsIDOMKeyEvent.h"
#include "nsIObserverService.h"
#include "nsIDocShell.h"
#include "nsIMarkupDocumentViewer.h"
#include "nsIScrollableViewProvider.h"
#include "nsIDOMDocumentRange.h"
#include "nsIDOMDocumentEvent.h"
-#include "nsIDOMMouseEvent.h"
+#include "nsIDOMMouseScrollEvent.h"
#include "nsIDOMDragEvent.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMDocumentView.h"
#include "nsIDOMNSUIEvent.h"
#include "nsDOMDragEvent.h"
#include "nsIDOMRange.h"
#include "nsCaret.h"
@@ -136,16 +136,17 @@
#include "imgIContainer.h"
#include "nsIProperties.h"
#include "nsISupportsPrimitives.h"
#include "nsEventDispatcher.h"
#include "nsPresShellIterator.h"
#include "nsServiceManagerUtils.h"
#include "nsITimer.h"
+#include "nsIFontMetrics.h"
#include "nsIDragService.h"
#include "nsIDragSession.h"
#include "nsDOMDataTransfer.h"
#include "nsContentAreaDragDrop.h"
#ifdef MOZ_XUL
#include "nsITreeBoxObject.h"
#endif
@@ -455,17 +456,19 @@ nsEventStateManager::nsEventStateManager
mLastFocusedWith(eEventFocusedByUnknown),
mPresContext(nsnull),
mLClickCount(0),
mMClickCount(0),
mRClickCount(0),
mNormalLMouseEventInProcess(PR_FALSE),
m_haveShutdown(PR_FALSE),
mBrowseWithCaret(PR_FALSE),
- mTabbedThroughDocument(PR_FALSE)
+ mTabbedThroughDocument(PR_FALSE),
+ mLastLineScrollConsumedX(PR_FALSE),
+ mLastLineScrollConsumedY(PR_FALSE)
{
if (sESMInstanceCount == 0) {
gUserInteractionTimerCallback = new nsUITimerCallback();
if (gUserInteractionTimerCallback) {
NS_ADDREF(gUserInteractionTimerCallback);
CallCreateInstance("@mozilla.org/timer;1", &gUserInteractionTimer);
if (gUserInteractionTimer) {
gUserInteractionTimer->InitWithCallback(gUserInteractionTimerCallback,
@@ -1402,16 +1405,30 @@ nsEventStateManager::PreHandleEvent(nsPr
(msEvent->scrollFlags & nsMouseScrollEvent::kIsFullPage)) ||
action == MOUSE_SCROLL_PAGE) {
msEvent->delta = (msEvent->delta > 0)
? nsIDOMNSUIEvent::SCROLL_PAGE_DOWN
: nsIDOMNSUIEvent::SCROLL_PAGE_UP;
}
}
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:
{
nsQueryContentEventHandler handler(mPresContext);
handler.OnQuerySelectedText((nsQueryContentEvent*)aEvent);
}
break;
case NS_QUERY_TEXT_CONTENT:
{
@@ -2409,16 +2426,76 @@ GetParentFrameToScroll(nsPresContext* aP
return nsnull;
if (aFrame->GetStyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED)
return aPresContext->GetPresShell()->GetRootScrollFrame();
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
nsEventStateManager::DoScrollText(nsPresContext* aPresContext,
nsIFrame* aTargetFrame,
nsInputEvent* aEvent,
PRInt32 aNumLines,
PRBool aScrollHorizontal,
ScrollQuantity aScrollQuantity)
{
@@ -2727,89 +2804,116 @@ nsEventStateManager::PostHandleEvent(nsP
}
}
nsCOMPtr<nsFrameSelection> frameSelection = shell->FrameSelection();
frameSelection->SetMouseDownState(PR_FALSE);
}
}
break;
case NS_MOUSE_SCROLL:
- if (nsEventStatus_eConsumeNoDefault != *aStatus) {
-
- // Build the preference keys, based on the event properties.
- nsMouseScrollEvent *msEvent = (nsMouseScrollEvent*) aEvent;
-
- NS_NAMED_LITERAL_CSTRING(actionslot, ".action");
- NS_NAMED_LITERAL_CSTRING(sysnumlinesslot, ".sysnumlines");
-
- nsCAutoString baseKey;
- GetBasePrefKeyForMouseWheel(msEvent, baseKey);
-
- // Extract the preferences
- nsCAutoString actionKey(baseKey);
- actionKey.Append(actionslot);
-
- 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;
+ case NS_MOUSE_PIXEL_SCROLL:
+ {
+ nsMouseScrollEvent *msEvent = static_cast<nsMouseScrollEvent*>(aEvent);
+
+ if (aEvent->message == NS_MOUSE_SCROLL) {
+ // Mark the subsequent pixel scrolls as valid / invalid, based on the
+ // observation if the previous line scroll has been canceled
+ if (msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal) {
+ mLastLineScrollConsumedX = (nsEventStatus_eConsumeNoDefault == *aStatus);
+ } else if (msEvent->scrollFlags & nsMouseScrollEvent::kIsVertical) {
+ mLastLineScrollConsumedY = (nsEventStatus_eConsumeNoDefault == *aStatus);
+ }
+ if (!(msEvent->scrollFlags & nsMouseScrollEvent::kHasPixels)) {
+ // No generated pixel scroll event will follow.
+ // Create and send a pixel scroll DOM event now.
+ SendPixelScrollEvent(aTargetFrame, msEvent, presContext, aStatus);
+ }
}
- switch (action) {
- case MOUSE_SCROLL_N_LINES:
- {
- DoScrollText(presContext, aTargetFrame, msEvent, msEvent->delta,
- (msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal),
- eScrollByLine);
+ if (*aStatus != nsEventStatus_eConsumeNoDefault) {
+ // Build the preference keys, based on the event properties.
+ NS_NAMED_LITERAL_CSTRING(actionslot, ".action");
+ NS_NAMED_LITERAL_CSTRING(sysnumlinesslot, ".sysnumlines");
+
+ nsCAutoString baseKey;
+ GetBasePrefKeyForMouseWheel(msEvent, baseKey);
+
+ // Extract the preferences
+ nsCAutoString actionKey(baseKey);
+ actionKey.Append(actionslot);
+
+ 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;
}
- 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);
+
+ 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;
+ }
}
- break;
-
- case MOUSE_SCROLL_HISTORY:
- {
- DoScrollHistory(msEvent->delta);
+
+ 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;
}
- break;
-
- case MOUSE_SCROLL_ZOOM:
- {
- DoScrollZoom(aTargetFrame, msEvent->delta);
- }
- break;
-
- default: // Including -1 (do nothing)
- break;
+ *aStatus = nsEventStatus_eConsumeNoDefault;
}
- *aStatus = nsEventStatus_eConsumeNoDefault;
-
}
-
break;
case NS_DRAGDROP_ENTER:
case NS_DRAGDROP_OVER:
{
NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "Expected a drag event");
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
--- a/content/events/src/nsEventStateManager.h
+++ b/content/events/src/nsEventStateManager.h
@@ -290,16 +290,20 @@ protected:
nsIDocShellTreeItem** aResult);
// These functions are for mousewheel scrolling
nsresult GetParentScrollingView(nsInputEvent* aEvent,
nsPresContext* aPresContext,
nsIFrame* &targetOuterFrame,
nsPresContext* &presCtxOuter);
+ void SendPixelScrollEvent(nsIFrame* aTargetFrame,
+ nsMouseScrollEvent* aEvent,
+ nsPresContext* aPresContext,
+ nsEventStatus* aStatus);
typedef enum {
eScrollByPixel,
eScrollByLine,
eScrollByPage
} ScrollQuantity;
nsresult DoScrollText(nsPresContext* aPresContext,
nsIFrame* aTargetFrame,
nsInputEvent* aEvent,
@@ -440,16 +444,20 @@ protected:
// Recursion guard for tabbing
PRPackedBool mTabbedThroughDocument;
// Array for accesskey support
nsCOMArray<nsIContent> mAccessKeys;
nsCOMArray<nsIDocShell> mTabbingFromDocShells;
+ // Unlocks pixel scrolling
+ PRPackedBool mLastLineScrollConsumedX;
+ PRPackedBool mLastLineScrollConsumedY;
+
#ifdef CLICK_HOLD_CONTEXT_MENUS
enum { kClickHoldDelay = 500 } ; // 500ms == 1/2 second
void CreateClickHoldTimer ( nsPresContext* aPresContext, nsIFrame* inDownFrame,
nsGUIEvent* inMouseDownEvent ) ;
void KillClickHoldTimer ( ) ;
void FireContextClick ( ) ;
static void sClickHoldCallback ( nsITimer* aTimer, void* aESM ) ;
--- a/content/events/test/Makefile.in
+++ b/content/events/test/Makefile.in
@@ -46,16 +46,17 @@ include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
test_bug238987.html \
test_bug288392.html \
test_bug328885.html \
test_bug336682_1.html \
test_bug336682_2.xul \
test_bug336682.js \
+ test_bug350471.xul \
test_bug367781.html \
test_bug368835.html \
test_bug379120.html \
test_bug391568.xhtml \
test_bug402089.html \
test_bug405632.html \
test_bug409604.html \
test_bug412567.html \
new file mode 100644
--- /dev/null
+++ b/content/events/test/test_bug350471.xul
@@ -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>
--- a/dom/public/idl/base/nsIDOMWindowUtils.idl
+++ b/dom/public/idl/base/nsIDOMWindowUtils.idl
@@ -125,30 +125,31 @@ interface nsIDOMWindowUtils : nsISupport
in long aY,
in long aButton,
in long aClickCount,
in long aModifiers);
/** Synthesize a mouse scroll event for a window. The event types supported
* are:
* DOMMouseScroll
+ * MozMousePixelScroll
*
* Events are sent in coordinates offset by aX and aY from the window.
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without UniversalXPConnect
* privileges.
*
* @param aType event type
* @param aX x offset
* @param aY y offset
* @param aButton button to synthesize
* @param aScrollFlags flag bits --- see nsMouseScrollFlags in nsGUIEvent.h
* @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
*/
void sendMouseScrollEvent(in AString aType,
in long aX,
in long aY,
in long aButton,
in long aScrollFlags,
in long aDelta,
--- a/dom/src/base/nsDOMWindowUtils.cpp
+++ b/dom/src/base/nsDOMWindowUtils.cpp
@@ -265,16 +265,18 @@ nsDOMWindowUtils::SendMouseScrollEvent(c
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget)
return NS_ERROR_NULL_POINTER;
PRInt32 msg;
if (aType.EqualsLiteral("DOMMouseScroll"))
msg = NS_MOUSE_SCROLL;
+ else if (aType.EqualsLiteral("MozMousePixelScroll"))
+ msg = NS_MOUSE_PIXEL_SCROLL;
else
return NS_ERROR_UNEXPECTED;
nsMouseScrollEvent event(PR_TRUE, msg, widget);
event.isShift = (aModifiers & nsIDOMNSEvent::SHIFT_MASK) ? PR_TRUE : PR_FALSE;
event.isControl = (aModifiers & nsIDOMNSEvent::CONTROL_MASK) ? PR_TRUE : PR_FALSE;
event.isAlt = (aModifiers & nsIDOMNSEvent::ALT_MASK) ? PR_TRUE : PR_FALSE;
event.isMeta = (aModifiers & nsIDOMNSEvent::META_MASK) ? PR_TRUE : PR_FALSE;
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -897,16 +897,19 @@ pref("middlemouse.scrollbarPosition", fa
// Clipboard behavior
pref("clipboard.autocopy", false);
// mouse wheel scroll transaction period of time (in milliseconds)
pref("mousewheel.transaction.timeout", 1500);
// mouse wheel scroll transaction is held even if the mouse cursor is moved.
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
pref("mousewheel.withnokey.action",0);
pref("mousewheel.withnokey.numlines",1);
pref("mousewheel.withnokey.sysnumlines",true);
pref("mousewheel.withcontrolkey.action",0);
pref("mousewheel.withcontrolkey.numlines",1);
pref("mousewheel.withcontrolkey.sysnumlines",true);
// mousewheel.withshiftkey, see the Mac note below.
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -225,57 +225,57 @@ function synthesizeMouse(aTarget, aOffse
}
/**
* Synthesize a mouse scroll event on a target. The actual client point is determined
* by taking the aTarget's client box and offseting it by aOffsetX and
* aOffsetY.
*
* 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.
*
* If the axis is specified, it must be one of "horizontal" or "vertical". If not specified,
* "vertical" is used.
*
* '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"
- * is the default if 'units' is ommitted.
+ * be specified.
+ *
+ * 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags.
*
* aWindow is optional, and defaults to the current window object.
*/
function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
if (!aWindow)
aWindow = window;
var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils) {
// See nsMouseScrollFlags in nsGUIEvent.h
const kIsVertical = 0x02;
const kIsHorizontal = 0x04;
- const kIsPixels = 0x08;
+ const kHasPixels = 0x08;
var button = aEvent.button || 0;
var modifiers = _parseModifiers(aEvent);
var left = aTarget.boxObject.x;
var top = aTarget.boxObject.y;
var type = aEvent.type || "DOMMouseScroll";
var axis = aEvent.axis || "vertical";
- var units = aEvent.units || "lines";
var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
- if (units == "pixels") {
- scrollFlags |= kIsPixels;
+ if (aEvent.hasPixels) {
+ scrollFlags |= kHasPixels;
}
utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button,
scrollFlags, aEvent.delta, modifiers);
}
}
/**
* Synthesize a key event. It is targeted at whatever would be targeted by an
--- a/toolkit/content/tests/widgets/test_mousescroll.xul
+++ b/toolkit/content/tests/widgets/test_mousescroll.xul
@@ -68,113 +68,146 @@ https://bugzilla.mozilla.org/show_bug.cg
<vbox style="width:100px; height:40px; background:white;"/>
<vbox style="width:100px; height:40px; background:black;"/>
</arrowscrollbox>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
/** Test for Bug 378028 **/
+/* and for Bug 350471 **/
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)
{
var listbox = document.getElementById(id);
- function helper(aStart, aDelta)
+ function helper(aStart, aDelta, aKind)
{
listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10,
- {axis:"vertical", delta:aDelta});
- is(listbox.getIndexOfFirstVisibleRow(), aStart + aDelta,
- "mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta);
+ {axis:"vertical", delta:aDelta, type:aKind.eventType,
+ hasPixels:aKind.hasPixels});
+ 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
listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10,
- {axis:"horizontal", delta:aDelta});
+ {axis:"horizontal", delta:aDelta, type:aKind.eventType,
+ hasPixels:aKind.hasPixels});
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);
}
-
- helper(2, -1);
- helper(2, 1);
- helper(2, -2);
- helper(2, 2);
+ kinds.forEach(function(aKind) {
+ helper(2, -1, aKind);
+ helper(2, 1, aKind);
+ helper(2, -2, aKind);
+ helper(2, 2, aKind);
+ });
}
function testRichListbox(id)
{
var listbox = document.getElementById(id);
- function helper(aStart, aDelta, aExpected)
+ function helper(aStart, aDelta, aExpected, aKind)
{
listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10,
- {axis:"vertical", delta:aDelta});
- is(listbox.getIndexOfFirstVisibleRow(), aExpected,
- "mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta);
+ {axis:"vertical", delta:aDelta, type:aKind.eventType,
+ hasPixels:aKind.hasPixels});
+ 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
listbox.scrollToIndex(aStart);
synthesizeMouseScroll(listbox, 10, 10,
- {axis:"horizontal", delta:aDelta});
+ {axis:"horizontal", delta:aDelta, type:aKind.eventType,
+ hasPixels:aKind.hasPixels});
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"
// 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
- helper(2, -100, 0);
- helper(2, 100, listbox.getRowCount() - listbox.getNumberOfVisibleRows());
+ kinds.forEach(function(aKind) {
+ helper(2, -100, 0, aKind);
+ helper(2, 100, listbox.getRowCount() - listbox.getNumberOfVisibleRows(), aKind);
+ });
}
function testArrowScrollbox(id)
{
var scrollbox = document.getElementById(id);
var scrollBoxObject = scrollbox.scrollBoxObject;
var orient = scrollbox.getAttribute("orient");
- function helper(aStart, aDelta, aExpected)
+ function helper(aStart, aDelta, aExpected, aKind)
{
var xpos = {};
var ypos = {};
var pos = orient == "horizontal" ? xpos : ypos;
scrollBoxObject.scrollTo(aStart, aStart);
synthesizeMouseScroll(scrollbox, 5, 5,
- {axis:"vertical", delta:aDelta});
+ {axis:"vertical", delta:aDelta, type:aKind.eventType,
+ hasPixels:aKind.hasPixels});
scrollBoxObject.getPosition(xpos, ypos);
// Note, vertical mouse scrolling is allowed to scroll horizontal
// arrowscrollboxes, because many users have no horizontal mouse scroll
// capability
- is(pos.value, aExpected,
- "mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta);
+ is(pos.value, aKind.shouldScrollDOM ? aExpected : aStart,
+ "mouse-scroll of '" + id + "' vertical starting " + aStart + " delta " + aDelta
+ + " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
scrollBoxObject.scrollTo(aStart, aStart);
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
// arrowscrollboxes
scrollBoxObject.getPosition(xpos, ypos);
- var expected = orient == "horizontal" ? aExpected : aStart;
+ var expected = (aKind.shouldScrollDOM && (orient == "horizontal")) ? aExpected : aStart;
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 scrolledHeight = {};
scrollBoxObject.getScrolledSize(scrolledWidth, scrolledHeight);
var scrollMaxX = scrolledWidth.value - scrollBoxObject.width;
var scrollMaxY = scrolledHeight.value - scrollBoxObject.height;
var scrollMax = orient == "horizontal" ? scrollMaxX : scrollMaxY;
- helper(50, -100, 0);
- helper(50, 100, scrollMax);
+ kinds.forEach(function(aKind) {
+ helper(50, -100, 0, aKind);
+ helper(50, 100, scrollMax, aKind);
+ });
}
function runTests()
{
testRichListbox("richlistbox");
testListbox("listbox");
testArrowScrollbox("hscrollbox");
testArrowScrollbox("vscrollbox");
--- a/toolkit/content/tests/widgets/tree_shared.js
+++ b/toolkit/content/tests/widgets/tree_shared.js
@@ -1091,34 +1091,47 @@ function testtag_tree_column_reorder()
is(document.treecolDragging, null, "drag to itself completed");
// XXX roc should this be here???
SimpleTest.finish();
}
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);
synthesizeMouseScroll(aTree.body, 1, 1,
- {type:"DOMMouseScroll", axis:"vertical", delta:aDelta});
- is(aTree.treeBoxObject.getFirstVisibleRow(), aStart + aDelta, "mouse-scroll vertical starting " + aStart + " delta " + aDelta);
+ {axis:"vertical", delta:aDelta, type:aKind.eventType,
+ 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);
// Check that horizontal scrolling has no effect
synthesizeMouseScroll(aTree.body, 1, 1,
- {type:"DOMMouseScroll", axis:"horizontal", delta:aDelta});
- is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, "mouse-scroll horizontal starting " + aStart + " delta " + aDelta);
+ {axis:"horizontal", delta:aDelta, type:aKind.eventType,
+ hasPixels:aKind.hasPixels});
+ is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, "mouse-scroll horizontal starting " + aStart + " delta " + aDelta
+ + " eventType " + aKind.eventType + " hasPixels " + aKind.hasPixels);
}
- helper(2, -1);
- helper(2, 1);
- helper(2, -2);
- helper(2, 2);
+ kinds.forEach(function(aKind) {
+ helper(2, -1, aKind);
+ helper(2, 1, aKind);
+ helper(2, -2, aKind);
+ helper(2, 2, aKind);
+ });
}
function synthesizeColumnDrag(aTree, aMouseDownColumnNumber, aMouseUpColumnNumber, aAfter)
{
var columns = getSortedColumnArray(aTree);
var down = columns[aMouseDownColumnNumber].element;
var up = columns[aMouseUpColumnNumber].element;
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -266,16 +266,17 @@ class nsHashKey;
// NS_XUL_COMMAND used to be here (NS_XUL_EVENT_START+4)
#define NS_XUL_BROADCAST (NS_XUL_EVENT_START+5)
#define NS_XUL_COMMAND_UPDATE (NS_XUL_EVENT_START+6)
//@}
// Scroll events
#define NS_MOUSE_SCROLL_START 1600
#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_UNDERFLOW (NS_SCROLLPORT_START)
#define NS_SCROLLPORT_OVERFLOW (NS_SCROLLPORT_START+1)
#define NS_SCROLLPORT_OVERFLOWCHANGED (NS_SCROLLPORT_START+2)
// Mutation events defined elsewhere starting at 1800
@@ -844,24 +845,69 @@ public:
nsCompositionEvent(PRBool isTrusted, PRUint32 msg, nsIWidget *w)
: nsInputEvent(isTrusted, msg, w, NS_COMPOSITION_EVENT)
{
}
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
{
public:
enum nsMouseScrollFlags {
kIsFullPage = 1 << 0,
kIsVertical = 1 << 1,
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)
: nsMouseEvent_base(isTrusted, msg, w, NS_MOUSE_SCROLL_EVENT),
scrollFlags(0), delta(0)
{
}
--- a/widget/src/cocoa/nsChildView.h
+++ b/widget/src/cocoa/nsChildView.h
@@ -94,16 +94,32 @@ extern "C" long TSMProcessRawKeyEvent(Ev
// Return Cocoa event's corresponding Carbon event. Not initialized (on
// synthetic events) until the OS actually "sends" the event. This method
// has been present in the same form since at least OS X 10.2.8.
- (EventRef)_eventRef;
@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<
#ifdef ACCESSIBILITY
mozAccessible,
#endif
mozView, NSTextInput>
{
@private
NSWindow* mWindow; // shortcut to the top window, [WEAK]
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -3488,89 +3488,131 @@ static nsEventStatus SendGeckoMouseEnter
// Handle an NSScrollWheel event for a single axis only.
-(void)scrollWheel:(NSEvent*)theEvent forAxis:(enum nsMouseScrollEvent::nsMouseScrollFlags)inAxis
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (!mGeckoChild)
return;
- float scrollDelta;
-
- if (inAxis & nsMouseScrollEvent::kIsVertical)
- scrollDelta = -[theEvent deltaY];
- else if (inAxis & nsMouseScrollEvent::kIsHorizontal)
- scrollDelta = -[theEvent deltaX];
- else
+ float scrollDelta = 0;
+ float scrollDeltaPixels = 0;
+ PRBool checkPixels = PR_TRUE;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs)
+ prefs->GetBoolPref("mousewheel.enable_pixel_scrolling", &checkPixels);
+
+ EventRef theCarbonEvent = [theEvent _eventRef];
+ UInt32 carbonEventKind = theCarbonEvent ? ::GetEventKind(theCarbonEvent) : 0;
+ // Calling deviceDeltaX or deviceDeltaY on theEvent will trigger a Cocoa
+ // assertion and an Objective-C NSInternalInconsistencyException if the
+ // underlying "Carbon" event doesn't contain pixel scrolling information.
+ // For these events, carbonEventKind is kEventMouseWheelMoved instead of
+ // kEventMouseScroll.
+ if (carbonEventKind != mozkEventMouseScroll)
+ checkPixels = PR_FALSE;
+ // Some scrolling devices supports pixel scrolling, e.g. a Macbook
+ // touchpad or a Mighty Mouse. On those devices, [event deviceDeltaX/Y]
+ // contains the amount of pixels to scroll.
+ if (inAxis & nsMouseScrollEvent::kIsVertical) {
+ scrollDelta = -[theEvent deltaY];
+ if (checkPixels && (scrollDelta == 0 || scrollDelta != floor(scrollDelta))) {
+ scrollDeltaPixels = -[theEvent deviceDeltaY];
+ }
+ } else if (inAxis & nsMouseScrollEvent::kIsHorizontal) {
+ scrollDelta = -[theEvent deltaX];
+ if (checkPixels && (scrollDelta == 0 || scrollDelta != floor(scrollDelta))) {
+ scrollDeltaPixels = -[theEvent deviceDeltaX];
+ }
+ } else {
return; // caller screwed up
-
- if (scrollDelta == 0)
- // No sense in firing off a Gecko event. Note that as of 10.4 Tiger,
- // a single NSScrollWheel event might result in deltaX = deltaY = 0.
- return;
-
- nsMouseScrollEvent geckoEvent(PR_TRUE, NS_MOUSE_SCROLL, nsnull);
- [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
- geckoEvent.scrollFlags |= inAxis;
-
- // 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);
+ }
+
+ 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;
}
-(void)scrollWheel:(NSEvent*)theEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;