Bug 442774 Wheel/touchpad scrolling gets stuck in frame, stop scrolling the web page as a whole r=Olli.Pettay, sr=roc

This commit is contained in:
Masayuki Nakano 2009-02-10 20:17:37 +09:00
parent 41a1a807d6
commit 4fa4a8c50d
10 changed files with 1697 additions and 26 deletions

View File

@ -375,47 +375,88 @@ class nsMouseWheelTransaction {
public:
static nsIFrame* GetTargetFrame() { return sTargetFrame; }
static void BeginTransaction(nsIFrame* aTargetFrame,
nsGUIEvent* aEvent);
static void UpdateTransaction();
PRInt32 aNumLines,
PRBool aScrollHorizontal);
static PRBool UpdateTransaction(PRInt32 aNumLines,
PRBool aScrollHorizontal);
static void EndTransaction();
static void OnEvent(nsEvent* aEvent);
static void Shutdown();
protected:
static nsIntPoint GetScreenPoint(nsGUIEvent* aEvent);
static void OnFailToScrollTarget();
static void OnTimeout(nsITimer *aTimer, void *aClosure);
static void SetTimeout();
static PRUint32 GetTimeoutTime();
static PRUint32 GetIgnoreMoveDelayTime();
static nsWeakFrame sTargetFrame;
static PRUint32 sTime; // in milliseconds
static PRUint32 sMouseMoved; // in milliseconds
static nsITimer* sTimer;
};
nsWeakFrame nsMouseWheelTransaction::sTargetFrame(nsnull);
PRUint32 nsMouseWheelTransaction::sTime = 0;
PRUint32 nsMouseWheelTransaction::sMouseMoved = 0;
nsITimer* nsMouseWheelTransaction::sTimer = nsnull;
void
nsMouseWheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
nsGUIEvent* aEvent)
static PRBool
CanScrollOn(nsIScrollableView* aScrollView, PRInt32 aNumLines,
PRBool aScrollHorizontal)
{
NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
sTargetFrame = aTargetFrame;
UpdateTransaction();
NS_PRECONDITION(aScrollView, "aScrollView is null");
NS_PRECONDITION(aNumLines, "aNumLines must be non-zero");
PRBool canScroll;
nsresult rv =
aScrollView->CanScroll(aScrollHorizontal, aNumLines > 0, canScroll);
return NS_SUCCEEDED(rv) && canScroll;
}
void
nsMouseWheelTransaction::UpdateTransaction()
nsMouseWheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
PRInt32 aNumLines,
PRBool aScrollHorizontal)
{
NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
sTargetFrame = aTargetFrame;
if (!UpdateTransaction(aNumLines, aScrollHorizontal)) {
NS_ERROR("BeginTransaction is called even cannot scroll the frame");
EndTransaction();
}
}
PRBool
nsMouseWheelTransaction::UpdateTransaction(PRInt32 aNumLines,
PRBool aScrollHorizontal)
{
nsIScrollableViewProvider* svp = do_QueryFrame(GetTargetFrame());
NS_ENSURE_TRUE(svp, PR_FALSE);
nsIScrollableView *scrollView = svp->GetScrollableView();
NS_ENSURE_TRUE(scrollView, PR_FALSE);
if (!CanScrollOn(scrollView, aNumLines, aScrollHorizontal)) {
OnFailToScrollTarget();
// We should not modify the transaction state when the view will not be
// scrolled actually.
return PR_FALSE;
}
SetTimeout();
// We should use current time instead of nsEvent.time.
// 1. Some events doesn't have the correct creation time.
// 2. If the computer runs slowly by other processes eating the CPU resource,
// the event creation time doesn't keep real time.
sTime = PR_IntervalToMilliseconds(PR_IntervalNow());
sMouseMoved = 0;
return PR_TRUE;
}
void
nsMouseWheelTransaction::EndTransaction()
{
if (sTimer)
sTimer->Cancel();
sTargetFrame = nsnull;
}
@ -433,8 +474,11 @@ nsMouseWheelTransaction::OnEvent(nsEvent* aEvent)
return;
if (OutOfTime(sTime, GetTimeoutTime())) {
// Time out the current transaction.
EndTransaction();
// Even if the scroll event which is handled after timeout, but onTimeout
// was not fired by timer, then the scroll event will scroll old frame,
// therefore, we should call OnTimeout here and ensure to finish the old
// transaction.
OnTimeout(nsnull, nsnull);
return;
}
@ -483,6 +527,61 @@ nsMouseWheelTransaction::OnEvent(nsEvent* aEvent)
}
}
void
nsMouseWheelTransaction::Shutdown()
{
NS_IF_RELEASE(sTimer);
}
void
nsMouseWheelTransaction::OnFailToScrollTarget()
{
NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction");
// This event is used for automated tests, see bug 442774.
nsContentUtils::DispatchTrustedEvent(
sTargetFrame->GetContent()->GetOwnerDoc(),
sTargetFrame->GetContent(),
NS_LITERAL_STRING("MozMouseScrollFailed"),
PR_TRUE, PR_TRUE);
}
void
nsMouseWheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure)
{
if (!sTargetFrame) {
// The transaction target was destroyed already
EndTransaction();
return;
}
// Store the sTargetFrame, the variable becomes null in EndTransaction.
nsIFrame* frame = sTargetFrame;
// We need to finish current transaction before DOM event firing. Because
// the next DOM event might create strange situation for us.
EndTransaction();
// This event is used for automated tests, see bug 442774.
nsContentUtils::DispatchTrustedEvent(
frame->GetContent()->GetOwnerDoc(),
frame->GetContent(),
NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"),
PR_TRUE, PR_TRUE);
}
void
nsMouseWheelTransaction::SetTimeout()
{
if (!sTimer) {
nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (!timer)
return;
timer.swap(sTimer);
}
sTimer->Cancel();
nsresult rv =
sTimer->InitWithFuncCallback(OnTimeout, nsnull, GetTimeoutTime(),
nsITimer::TYPE_ONE_SHOT);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "nsITimer::InitWithFuncCallback failed");
}
nsIntPoint
nsMouseWheelTransaction::GetScreenPoint(nsGUIEvent* aEvent)
{
@ -621,6 +720,7 @@ nsEventStateManager::~nsEventStateManager()
--sESMInstanceCount;
if(sESMInstanceCount == 0) {
nsMouseWheelTransaction::Shutdown();
NS_IF_RELEASE(gLastFocusedContent);
NS_IF_RELEASE(gLastFocusedDocument);
if (gUserInteractionTimerCallback) {
@ -2632,9 +2732,8 @@ nsEventStateManager::DoScrollText(nsPresContext* aPresContext,
nsIFrame* lastScrollFrame = nsMouseWheelTransaction::GetTargetFrame();
if (lastScrollFrame) {
nsIScrollableViewProvider* svp = do_QueryFrame(lastScrollFrame);
if (svp) {
scrollView = svp->GetScrollableView();
nsMouseWheelTransaction::UpdateTransaction();
if (svp && (scrollView = svp->GetScrollableView())) {
nsMouseWheelTransaction::UpdateTransaction(aNumLines, aScrollHorizontal);
} else {
nsMouseWheelTransaction::EndTransaction();
lastScrollFrame = nsnull;
@ -2666,12 +2765,10 @@ nsEventStateManager::DoScrollText(nsPresContext* aPresContext,
scrollView->GetLineHeight(&lineHeight);
if (lineHeight != 0) {
PRBool canScroll;
nsresult rv = scrollView->CanScroll(aScrollHorizontal,
(aNumLines > 0), canScroll);
if (NS_SUCCEEDED(rv) && canScroll) {
if (CanScrollOn(scrollView, aNumLines, aScrollHorizontal)) {
passToParent = PR_FALSE;
nsMouseWheelTransaction::BeginTransaction(scrollFrame, aEvent);
nsMouseWheelTransaction::BeginTransaction(scrollFrame,
aNumLines, aScrollHorizontal);
}
// Comboboxes need special care.

View File

@ -48,7 +48,7 @@
interface nsIDOMElement;
interface nsIDOMHTMLCanvasElement;
[scriptable, uuid(190be8e6-35af-4e3e-9a9f-719f5b1a44a0)]
[scriptable, uuid(8C6263C9-F3EF-419d-80EF-D5D716635FAA)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -297,4 +297,16 @@ interface nsIDOMWindowUtils : nsISupports {
* fired.
*/
readonly attribute boolean isMozAfterPaintPending;
/**
* Disable or enable non synthetic test mouse events on *all* windows.
*
* Cannot be accessed from unprivileged context (not content-accessible).
* Will throw a DOM security error if called without UniversalXPConnect
* privileges.
*
* @param aDisable If true, disable all non synthetic test mouse events
* on all windows. Otherwise, enable them.
*/
void disableNonTestMouseEvents(in boolean aDisable);
};

View File

@ -249,6 +249,7 @@ nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
event.clickCount = aClickCount;
event.time = PR_IntervalNow();
event.flags |= NS_EVENT_FLAG_SYNTETIC_TEST_EVENT;
float appPerDev = float(widget->GetDeviceContext()->AppUnitsPerDevPixel());
event.refPoint.x =
@ -681,3 +682,21 @@ nsDOMWindowUtils::GetIsMozAfterPaintPending(PRBool *aResult)
*aResult = presContext->IsDOMPaintEventPending();
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::DisableNonTestMouseEvents(PRBool aDisable)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->
IsCapabilityEnabled("UniversalXPConnect", &hasCap)) ||
!hasCap)
return NS_ERROR_DOM_SECURITY_ERR;
NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
nsIDocShell *docShell = mWindow->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
nsCOMPtr<nsIPresShell> presShell;
docShell->GetPresShell(getter_AddRefs(presShell));
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
return presShell->DisableNonTestMouseEvents(aDisable);
}

View File

@ -101,10 +101,10 @@ class gfxContext;
typedef short SelectionType;
typedef PRUint32 nsFrameState;
// b86c23c5-602d-4ca6-a968-379b244fed9e
// 780d34b0-00c3-4bbd-b57d-c600aaf53613
#define NS_IPRESSHELL_IID \
{ 0xb86c23c5, 0x602d, 0x4ca6, \
{ 0xa9, 0x68, 0x37, 0x9b, 0x24, 0x4f, 0xed, 0x9e } }
{ 0x780d34b0, 0xc3, 0x4bbd, \
{ 0xb5, 0x7d, 0xc6, 0x0, 0xaa, 0xf5, 0x36, 0x13 } }
// Constants for ScrollContentIntoView() function
#define NS_PRESSHELL_SCROLL_TOP 0
@ -772,6 +772,15 @@ public:
nsIFrame* GetDrawEventTargetFrame() { return mDrawEventTargetFrame; }
#endif
/**
* Stop or restart non synthetic test mouse event handling on *all*
* presShells.
*
* @param aDisable If true, disable all non synthetic test mouse events on all
* presShells. Otherwise, enable them.
*/
NS_IMETHOD DisableNonTestMouseEvents(PRBool aDisable) = 0;
protected:
// IMPORTANT: The ownership implicit in the following member variables
// has been explicitly checked. If you add any members to this class,

View File

@ -1009,6 +1009,8 @@ public:
static PRLogModuleInfo* gLog;
#endif
NS_IMETHOD DisableNonTestMouseEvents(PRBool aDisable);
protected:
virtual ~PresShell();
@ -1175,6 +1177,8 @@ protected:
ReflowCountMgr * mReflowCountMgr;
#endif
static PRBool sDisableNonTestMouseEvents;
private:
PRBool InZombieDocument(nsIContent *aContent);
@ -1250,6 +1254,8 @@ public:
nsRefPtr<PresShell> mPresShell;
};
PRBool PresShell::sDisableNonTestMouseEvents = PR_FALSE;
#ifdef PR_LOGGING
PRLogModuleInfo* PresShell::gLog;
#endif
@ -5530,6 +5536,13 @@ nsresult PresShell::RetargetEventToParent(nsGUIEvent* aEvent,
aEventStatus);
}
NS_IMETHODIMP
PresShell::DisableNonTestMouseEvents(PRBool aDisable)
{
sDisableNonTestMouseEvents = aDisable;
return NS_OK;
}
NS_IMETHODIMP
PresShell::HandleEvent(nsIView *aView,
nsGUIEvent* aEvent,
@ -5537,7 +5550,9 @@ PresShell::HandleEvent(nsIView *aView,
{
NS_ASSERTION(aView, "null view");
if (mIsDestroying || !nsContentUtils::IsSafeToRunScript()) {
if (mIsDestroying || !nsContentUtils::IsSafeToRunScript() ||
(sDisableNonTestMouseEvents && NS_IS_MOUSE_EVENT(aEvent) &&
!(aEvent->flags & NS_EVENT_FLAG_SYNTETIC_TEST_EVENT))) {
return NS_OK;
}

View File

@ -264,8 +264,10 @@ function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
var button = aEvent.button || 0;
var modifiers = _parseModifiers(aEvent);
var left = aTarget.boxObject.x;
var top = aTarget.boxObject.y;
var rect = aTarget.getBoundingClientRect();
var left = rect.left;
var top = rect.top;
var type = aEvent.type || "DOMMouseScroll";
var axis = aEvent.axis || "vertical";
@ -516,3 +518,14 @@ function synthesizeDrop(element, dragData, effectAllowed)
return dataTransfer.dropEffect;
}
function disableNonTestMouseEvents(aDisable)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils)
utils.disableNonTestMouseEvents(aDisable);
}

View File

@ -124,6 +124,11 @@ class nsHashKey;
// Event has been dispatched at least once
#define NS_EVENT_DISPATCHED 0x0400
#define NS_EVENT_FLAG_DISPATCHING 0x0800
// When an event is synthesized for testing, this flag will be set.
// Note that this is currently used only with mouse events. Because this flag
// is not needed on other events now. Therfore, if you need this flag on other
// events, you can do it.
#define NS_EVENT_FLAG_SYNTETIC_TEST_EVENT 0x1000
#define NS_PRIV_EVENT_UNTRUSTED_PERMITTED 0x8000

View File

@ -61,6 +61,8 @@ _TEST_FILES = test_bug343416.xul \
test_bug444800.xul \
test_bug462106.xul \
test_keycodes.xul \
test_wheeltransaction.xul \
window_wheeltransaction.xul \
$(NULL)
ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)

View File

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Wheel scroll transaction tests"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<body xmlns="http://www.w3.org/1999/xhtml">
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
window.open("window_wheeltransaction.xul", "_blank",
"chrome,width=600,height=600");
]]>
</script>
</window>

File diff suppressed because it is too large Load Diff