mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 935501 - Get pointer events working for <input type=number>'s spin up/down buttons. r=smaug
This commit is contained in:
parent
1eaff6defb
commit
5f5f7f6466
@ -21,6 +21,7 @@
|
||||
#include "nsFocusManager.h"
|
||||
#include "nsNumberControlFrame.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsRepeatService.h"
|
||||
#include "nsContentCID.h"
|
||||
#include "nsIComponentManager.h"
|
||||
#include "nsIDOMHTMLFormElement.h"
|
||||
@ -1092,6 +1093,7 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<nsINodeInfo> aNodeInfo,
|
||||
, mHasRange(false)
|
||||
, mIsDraggingRange(false)
|
||||
, mProgressTimerIsActive(false)
|
||||
, mNumberControlSpinnerIsSpinning(false)
|
||||
{
|
||||
// We are in a type=text so we now we currenty need a nsTextEditorState.
|
||||
mInputData.mState = new nsTextEditorState(this);
|
||||
@ -1114,6 +1116,9 @@ HTMLInputElement::~HTMLInputElement()
|
||||
if (mFileList) {
|
||||
mFileList->Disconnect();
|
||||
}
|
||||
if (mNumberControlSpinnerIsSpinning) {
|
||||
StopNumberControlSpinnerSpin();
|
||||
}
|
||||
DestroyImageLoadingContent();
|
||||
FreeData();
|
||||
}
|
||||
@ -2582,6 +2587,26 @@ HTMLInputElement::Notify(nsITimer* aTimer)
|
||||
return NS_ERROR_INVALID_POINTER;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
HTMLInputElement::HandleNumberControlSpin(void* aData)
|
||||
{
|
||||
HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
|
||||
|
||||
NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
|
||||
"Should have called nsRepeatService::Stop()");
|
||||
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
do_QueryFrame(input->GetPrimaryFrame());
|
||||
if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
|
||||
// Type has changed (and possibly our frame type hasn't been updated yet)
|
||||
// or else we've lost our frame. Either way, stop the timer and don't do
|
||||
// anything else.
|
||||
input->StopNumberControlSpinnerSpin();
|
||||
} else {
|
||||
input->ApplyStep(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::MaybeDispatchProgressEvent(bool aFinalProgress)
|
||||
{
|
||||
@ -3274,6 +3299,42 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
if (mType == NS_FORM_INPUT_NUMBER &&
|
||||
aVisitor.mEvent->mFlags.mIsTrusted) {
|
||||
if (mNumberControlSpinnerIsSpinning) {
|
||||
// If the timer is running the user has depressed the mouse on one of the
|
||||
// spin buttons. If the mouse exits the button we either want to reverse
|
||||
// the direction of spin if it has moved over the other button, or else
|
||||
// we want to end the spin. We do this here (rather than in
|
||||
// PostHandleEvent) because we don't want to let content preventDefault()
|
||||
// the end of the spin.
|
||||
if (aVisitor.mEvent->message == NS_MOUSE_MOVE) {
|
||||
// Be aggressive about stopping the spin:
|
||||
bool stopSpin = true;
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
switch (numberControlFrame->GetSpinButtonForPointerEvent(
|
||||
aVisitor.mEvent->AsMouseEvent())) {
|
||||
case nsNumberControlFrame::eSpinButtonUp:
|
||||
mNumberControlSpinnerSpinsUp = true;
|
||||
stopSpin = false;
|
||||
break;
|
||||
case nsNumberControlFrame::eSpinButtonDown:
|
||||
mNumberControlSpinnerSpinsUp = false;
|
||||
stopSpin = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stopSpin) {
|
||||
StopNumberControlSpinnerSpin();
|
||||
}
|
||||
} else if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_UP) {
|
||||
StopNumberControlSpinnerSpin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
|
||||
|
||||
// We do this after calling the base class' PreHandleEvent so that
|
||||
@ -3397,6 +3458,34 @@ HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
|
||||
false);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::StartNumberControlSpinnerSpin()
|
||||
{
|
||||
MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
|
||||
|
||||
mNumberControlSpinnerIsSpinning = true;
|
||||
|
||||
nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
|
||||
|
||||
// Capture the mouse so that we can tell if the pointer moves from one
|
||||
// spin button to the other, or to some other element:
|
||||
nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::StopNumberControlSpinnerSpin()
|
||||
{
|
||||
if (mNumberControlSpinnerIsSpinning) {
|
||||
if (nsIPresShell::GetCapturingContent() == this) {
|
||||
nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
|
||||
}
|
||||
|
||||
nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
|
||||
|
||||
mNumberControlSpinnerIsSpinning = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
SelectTextFieldOnFocus()
|
||||
{
|
||||
@ -3484,9 +3573,12 @@ HTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
|
||||
GetValueInternal(mFocusedValue);
|
||||
}
|
||||
|
||||
if (mIsDraggingRange &&
|
||||
aVisitor.mEvent->message == NS_BLUR_CONTENT) {
|
||||
FinishRangeThumbDrag();
|
||||
if (aVisitor.mEvent->message == NS_BLUR_CONTENT) {
|
||||
if (mIsDraggingRange) {
|
||||
FinishRangeThumbDrag();
|
||||
} else if (mNumberControlSpinnerIsSpinning) {
|
||||
StopNumberControlSpinnerSpin();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateValidityUIBits(aVisitor.mEvent->message == NS_FOCUS_CONTENT);
|
||||
@ -3837,7 +3929,43 @@ HTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
|
||||
rv = NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (mType == NS_FORM_INPUT_NUMBER &&
|
||||
aVisitor.mEvent->mFlags.mIsTrusted) {
|
||||
if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
|
||||
!(mouseEvent->IsShift() || mouseEvent->IsControl() ||
|
||||
mouseEvent->IsAlt() || mouseEvent->IsMeta() ||
|
||||
mouseEvent->IsAltGraph() || mouseEvent->IsFn() ||
|
||||
mouseEvent->IsOS())) {
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_DOWN) {
|
||||
switch (numberControlFrame->GetSpinButtonForPointerEvent(
|
||||
aVisitor.mEvent->AsMouseEvent())) {
|
||||
case nsNumberControlFrame::eSpinButtonUp:
|
||||
ApplyStep(1);
|
||||
mNumberControlSpinnerSpinsUp = true;
|
||||
StartNumberControlSpinnerSpin();
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
break;
|
||||
case nsNumberControlFrame::eSpinButtonDown:
|
||||
ApplyStep(-1);
|
||||
mNumberControlSpinnerSpinsUp = false;
|
||||
StartNumberControlSpinnerSpin();
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
|
||||
// We didn't handle this to step up/down. Whatever this was, be
|
||||
// aggressive about stopping the spin. (And don't set
|
||||
// nsEventStatus_eConsumeNoDefault after doing so, since that
|
||||
// might prevent, say, the context menu from opening.)
|
||||
StopNumberControlSpinnerSpin();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -678,6 +678,15 @@ public:
|
||||
|
||||
HTMLInputElement* GetOwnerNumberControl();
|
||||
|
||||
void StartNumberControlSpinnerSpin();
|
||||
void StopNumberControlSpinnerSpin();
|
||||
|
||||
/**
|
||||
* The callback function used by the nsRepeatService that we use to spin the
|
||||
* spinner for <input type=number>.
|
||||
*/
|
||||
static void HandleNumberControlSpin(void* aData);
|
||||
|
||||
bool MozIsTextField(bool aExcludePassword);
|
||||
|
||||
nsIEditor* GetEditor();
|
||||
@ -1231,6 +1240,8 @@ protected:
|
||||
bool mHasRange : 1;
|
||||
bool mIsDraggingRange : 1;
|
||||
bool mProgressTimerIsActive : 1;
|
||||
bool mNumberControlSpinnerIsSpinning : 1;
|
||||
bool mNumberControlSpinnerSpinsUp : 1;
|
||||
|
||||
private:
|
||||
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
|
||||
|
@ -24,6 +24,9 @@ support-files =
|
||||
[test_input_file_picker.html]
|
||||
[test_input_list_attribute.html]
|
||||
[test_input_number_key_events.html]
|
||||
# Spin buttons are hidden on Firefox OS and Firefox for Android:
|
||||
skip-if = toolkit == "gonk" || os == 'android'
|
||||
[test_input_number_mouse_events.html]
|
||||
[test_input_number_rounding.html]
|
||||
[test_input_range_attr_order.html]
|
||||
[test_input_range_key_events.html]
|
||||
|
@ -0,0 +1,111 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=935501
|
||||
-->
|
||||
<head>
|
||||
<title>Test mouse events for number</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
input {
|
||||
margin: 0 ! important;
|
||||
border: 0 ! important;
|
||||
padding: 0 ! important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935501">Mozilla Bug 935501</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<input id="input" type="number">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Test for Bug 935501
|
||||
* This test checks how the value of <input type=number> changes in response to
|
||||
* various mouse events.
|
||||
**/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
function test() {
|
||||
var input = document.getElementById("input");
|
||||
var inputRect = input.getBoundingClientRect();
|
||||
|
||||
// Points over the input's spin-up and spin-down buttons (as offsets from the
|
||||
// top-left of the input's bounding client rect):
|
||||
const SPIN_UP_X = inputRect.width - 3;
|
||||
const SPIN_UP_Y = 3;
|
||||
const SPIN_DOWN_X = inputRect.width - 3;
|
||||
const SPIN_DOWN_Y = inputRect.height - 3;
|
||||
|
||||
// Test click on spin-up button:
|
||||
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
|
||||
is(input.value, 1, "Test step-up on mousedown on spin-up button");
|
||||
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
|
||||
is(input.value, 1, "Test mouseup on spin-up button");
|
||||
|
||||
// Test click on spin-down button:
|
||||
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
|
||||
is(input.value, 0, "Test step-down on mousedown on spin-down button");
|
||||
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
|
||||
is(input.value, 0, "Test mouseup on spin-down button");
|
||||
|
||||
// Test that preventDefault() works:
|
||||
function preventDefault(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
input.value = 1;
|
||||
input.addEventListener("mousedown", preventDefault, false);
|
||||
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, {});
|
||||
is(input.value, 1, "Test that preventDefault() works for click on spin-up button");
|
||||
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, {});
|
||||
is(input.value, 1, "Test that preventDefault() works for click on spin-down button");
|
||||
input.removeEventListener("mousedown", preventDefault, false);
|
||||
|
||||
// XXX TODO
|
||||
// Test spining when the mouse button is kept depressed on the spin-up
|
||||
// button:
|
||||
|
||||
// XXX TODO
|
||||
// Test spining when the mouse button is kept depressed on the spin-down
|
||||
// button:
|
||||
|
||||
// XXX TODO
|
||||
// Test spin direction reverses when the mouse button is depressod on the
|
||||
// spin-up button, then moved over the spin-down button once the spin begins:
|
||||
|
||||
// XXX TODO
|
||||
// Test spin direction reverses when the mouse button is depressod on the
|
||||
// spin-down button, then moved over the spin-up button once the spin begins:
|
||||
|
||||
// XXX TODO
|
||||
// Test that the spin is stopped when the mouse button is depressod on the
|
||||
// spin-down button, then moved outside both buttons once the spin starts:
|
||||
|
||||
// XXX TODO
|
||||
// Test that the spin is stopped when the mouse button is depressod on the
|
||||
// spin-up button, then moved outside both buttons once the spin starts:
|
||||
|
||||
// XXX TODO
|
||||
// Test that changing the input type in the middle of a spin cancels the spin:
|
||||
|
||||
// XXX TODO
|
||||
// Check that we do not spin when a mousedown occurs outside the spin
|
||||
// buttons and then the mouse is moved over the buttons:
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -14,6 +14,7 @@
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsINodeInfo.h"
|
||||
#include "nsINameSpaceManager.h"
|
||||
#include "mozilla/BasicEvents.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsContentCreatorFunctions.h"
|
||||
#include "nsContentList.h"
|
||||
@ -315,6 +316,21 @@ nsNumberControlFrame::GetAnonTextControl()
|
||||
return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
|
||||
}
|
||||
|
||||
int32_t
|
||||
nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
|
||||
{
|
||||
MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT,
|
||||
"Unexpected event type");
|
||||
|
||||
if (aEvent->originalTarget == mSpinUp) {
|
||||
return eSpinButtonUp;
|
||||
}
|
||||
if (aEvent->originalTarget == mSpinDown) {
|
||||
return eSpinButtonDown;
|
||||
}
|
||||
return eSpinButtonNone;
|
||||
}
|
||||
|
||||
void
|
||||
nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
|
||||
uint32_t aFilter)
|
||||
|
@ -14,6 +14,7 @@
|
||||
class nsPresContext;
|
||||
|
||||
namespace mozilla {
|
||||
class WidgetGUIEvent;
|
||||
namespace dom {
|
||||
class HTMLInputElement;
|
||||
}
|
||||
@ -29,6 +30,7 @@ class nsNumberControlFrame MOZ_FINAL : public nsContainerFrame
|
||||
NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
|
||||
typedef mozilla::dom::HTMLInputElement HTMLInputElement;
|
||||
typedef mozilla::WidgetGUIEvent WidgetGUIEvent;
|
||||
|
||||
nsNumberControlFrame(nsStyleContext* aContext);
|
||||
|
||||
@ -86,6 +88,19 @@ public:
|
||||
|
||||
HTMLInputElement* GetAnonTextControl();
|
||||
|
||||
enum SpinButtonEnum {
|
||||
eSpinButtonNone,
|
||||
eSpinButtonUp,
|
||||
eSpinButtonDown
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns one of the SpinButtonEnum values to depending on whether the
|
||||
* pointer event is over the spin-up button, the spin-down button, or
|
||||
* neither.
|
||||
*/
|
||||
int32_t GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const;
|
||||
|
||||
private:
|
||||
|
||||
nsresult MakeAnonymousElement(nsIContent** aResult,
|
||||
|
Loading…
Reference in New Issue
Block a user