summaryrefslogtreecommitdiff
path: root/dom/events
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2020-04-14 21:24:51 -0400
committerMatt A. Tobin <email@mattatobin.com>2020-04-14 21:25:54 -0400
commit5352b69a9286223272c0ed072900b4c78ba2ed7c (patch)
tree07b5c3969b8be3992d9442b58cf925869576e34e /dom/events
parent6f6d1f6ff788b6fac2a137ecbd41762f9976f42d (diff)
downloaduxp-5352b69a9286223272c0ed072900b4c78ba2ed7c.tar.gz
Bug 1305458 - Changing -moz-appearence on hover breaks change event
* Rename nsIDOMEventTarget::PreHandleEvent to nsIDOMEventTarget::GetEventTargetParent * Add nsIDOMEventTarget::PreHandleEvent * Add EventTargetChainItem::GetFirstEventTarget * Call EventTargetChainItem::PreHandleEvent even it sets mCanHandle=false * Move form control frame focus/blur from nsGenericHTMLFormElement::GetEventTargetParent to PreHandleEvent * Move fire change event from HTMLTextAreaElement::GetEventTargetParent to PreHandleEvent * Refine nsXULElement::GetEventTargetParent * Move dispatch XUL command from nsXULElement::GetEventTargetParent to PreHandleEvent * Move fire events and set value from HTMLInputElement::GetEventTargetParent to PreHandleEvent * Add test case * Let HTMLInputElement delegate event handling to it's parent class * Refine EventTargetChain flags to reduce overheads * Refine event target chain creation * Refine assertion in EventTargetChainItem::Create Tag #1375
Diffstat (limited to 'dom/events')
-rw-r--r--dom/events/DOMEventTargetHelper.cpp2
-rw-r--r--dom/events/DOMEventTargetHelper.h4
-rw-r--r--dom/events/EventDispatcher.cpp256
-rw-r--r--dom/events/EventDispatcher.h24
-rw-r--r--dom/events/test/mochitest.ini1
-rw-r--r--dom/events/test/test_bug1305458.html50
6 files changed, 251 insertions, 86 deletions
diff --git a/dom/events/DOMEventTargetHelper.cpp b/dom/events/DOMEventTargetHelper.cpp
index dd9a01d8d2..ee03463ef4 100644
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -328,7 +328,7 @@ DOMEventTargetHelper::GetEventHandler(nsIAtom* aType,
}
nsresult
-DOMEventTargetHelper::PreHandleEvent(EventChainPreVisitor& aVisitor)
+DOMEventTargetHelper::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = nullptr;
diff --git a/dom/events/DOMEventTargetHelper.h b/dom/events/DOMEventTargetHelper.h
index c5a0611c99..b14f054286 100644
--- a/dom/events/DOMEventTargetHelper.h
+++ b/dom/events/DOMEventTargetHelper.h
@@ -248,10 +248,10 @@ NS_DEFINE_STATIC_IID_ACCESSOR(DOMEventTargetHelper,
/* Use this macro to declare functions that forward the behavior of this
* interface to another object.
- * This macro doesn't forward PreHandleEvent because sometimes subclasses
+ * This macro doesn't forward GetEventTargetParent because sometimes subclasses
* want to override it.
*/
-#define NS_FORWARD_NSIDOMEVENTTARGET_NOPREHANDLEEVENT(_to) \
+#define NS_FORWARD_NSIDOMEVENTTARGET_NOGETEVENTTARGETPARENT(_to) \
NS_IMETHOD AddEventListener(const nsAString & type, nsIDOMEventListener *listener, bool useCapture, bool wantsUntrusted, uint8_t _argc) { \
return _to AddEventListener(type, listener, useCapture, wantsUntrusted, _argc); \
} \
diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp
index 1d4dfd7d9f..740e611e49 100644
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -130,13 +130,6 @@ static bool IsEventTargetChrome(EventTarget* aEventTarget,
return isChrome;
}
-
-#define NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH (1 << 0)
-#define NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT (1 << 1)
-#define NS_TARGET_CHAIN_MAY_HAVE_MANAGER (1 << 2)
-#define NS_TARGET_CHAIN_CHECKED_IF_CHROME (1 << 3)
-#define NS_TARGET_CHAIN_IS_CHROME_CONTENT (1 << 4)
-
// EventTargetChainItem represents a single item in the event target chain.
class EventTargetChainItem
{
@@ -144,8 +137,7 @@ private:
explicit EventTargetChainItem(EventTarget* aTarget);
public:
EventTargetChainItem()
- : mFlags(0)
- , mItemFlags(0)
+ : mItemFlags(0)
{
}
@@ -153,7 +145,8 @@ public:
EventTarget* aTarget,
EventTargetChainItem* aChild = nullptr)
{
- MOZ_ASSERT(!aChild || &aChain.ElementAt(aChain.Length() - 1) == aChild);
+ // The last item which can handle the event must be aChild.
+ MOZ_ASSERT(GetLastCanHandleEventTarget(aChain) == aChild);
return new (aChain.AppendElement()) EventTargetChainItem(aTarget);
}
@@ -165,6 +158,38 @@ public:
aChain.RemoveElementAt(lastIndex);
}
+ static EventTargetChainItem* GetFirstCanHandleEventTarget(
+ nsTArray<EventTargetChainItem>& aChain)
+ {
+ return &aChain[GetFirstCanHandleEventTargetIdx(aChain)];
+ }
+
+ static uint32_t GetFirstCanHandleEventTargetIdx(nsTArray<EventTargetChainItem>& aChain)
+ {
+ // aChain[i].PreHandleEventOnly() = true only when the target element wants
+ // PreHandleEvent and set mCanHandle=false. So we find the first element
+ // which can handle the event.
+ for (uint32_t i = 0; i < aChain.Length(); ++i) {
+ if (!aChain[i].PreHandleEventOnly()) {
+ return i;
+ }
+ }
+ MOZ_ASSERT(false);
+ return 0;
+ }
+
+ static EventTargetChainItem* GetLastCanHandleEventTarget(
+ nsTArray<EventTargetChainItem>& aChain)
+ {
+ // Fine the last item which can handle the event.
+ for (int32_t i = aChain.Length() - 1; i >= 0; --i) {
+ if (!aChain[i].PreHandleEventOnly()) {
+ return &aChain[i];
+ }
+ }
+ return nullptr;
+ }
+
bool IsValid()
{
NS_WARNING_ASSERTION(!!(mTarget), "Event target is not valid!");
@@ -183,44 +208,52 @@ public:
void SetForceContentDispatch(bool aForce)
{
- if (aForce) {
- mFlags |= NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH;
- } else {
- mFlags &= ~NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH;
- }
+ mFlags.mForceContentDispatch = aForce;
}
bool ForceContentDispatch()
{
- return !!(mFlags & NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH);
+ return mFlags.mForceContentDispatch;
}
void SetWantsWillHandleEvent(bool aWants)
{
- if (aWants) {
- mFlags |= NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT;
- } else {
- mFlags &= ~NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT;
- }
+ mFlags.mWantsWillHandleEvent = aWants;
}
bool WantsWillHandleEvent()
{
- return !!(mFlags & NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT);
+ return mFlags.mWantsWillHandleEvent;
+ }
+
+ void SetWantsPreHandleEvent(bool aWants)
+ {
+ mFlags.mWantsPreHandleEvent = aWants;
+ }
+
+ bool WantsPreHandleEvent()
+ {
+ return mFlags.mWantsPreHandleEvent;
+ }
+
+ void SetPreHandleEventOnly(bool aWants)
+ {
+ mFlags.mPreHandleEventOnly = aWants;
+ }
+
+ bool PreHandleEventOnly()
+ {
+ return mFlags.mPreHandleEventOnly;
}
void SetMayHaveListenerManager(bool aMayHave)
{
- if (aMayHave) {
- mFlags |= NS_TARGET_CHAIN_MAY_HAVE_MANAGER;
- } else {
- mFlags &= ~NS_TARGET_CHAIN_MAY_HAVE_MANAGER;
- }
+ mFlags.mMayHaveManager = aMayHave;
}
bool MayHaveListenerManager()
{
- return !!(mFlags & NS_TARGET_CHAIN_MAY_HAVE_MANAGER);
+ return mFlags.mMayHaveManager;
}
EventTarget* CurrentTarget()
@@ -240,10 +273,15 @@ public:
ELMCreationDetector& aCd);
/**
- * Resets aVisitor object and calls PreHandleEvent.
+ * Resets aVisitor object and calls GetEventTargetParent.
* Copies mItemFlags and mItemData to the current EventTargetChainItem.
*/
- void PreHandleEvent(EventChainPreVisitor& aVisitor);
+ void GetEventTargetParent(EventChainPreVisitor& aVisitor);
+
+ /**
+ * Calls PreHandleEvent for those items which called SetWantsPreHandleEvent.
+ */
+ void PreHandleEvent(EventChainVisitor& aVisitor);
/**
* If the current item in the event target chain has an event listener
@@ -288,7 +326,34 @@ public:
private:
nsCOMPtr<EventTarget> mTarget;
- uint16_t mFlags;
+
+ class EventTargetChainFlags
+ {
+ public:
+ explicit EventTargetChainFlags()
+ {
+ SetRawFlags(0);
+ }
+ // Cached flags for each EventTargetChainItem which are set when calling
+ // GetEventTargetParent to create event target chain. They are used to
+ // manage or speedup event dispatching.
+ bool mForceContentDispatch : 1;
+ bool mWantsWillHandleEvent : 1;
+ bool mMayHaveManager : 1;
+ bool mChechedIfChrome : 1;
+ bool mIsChromeContent : 1;
+ bool mWantsPreHandleEvent : 1;
+ bool mPreHandleEventOnly : 1;
+ private:
+ typedef uint32_t RawFlags;
+ void SetRawFlags(RawFlags aRawFlags)
+ {
+ static_assert(sizeof(EventTargetChainFlags) <= sizeof(RawFlags),
+ "EventTargetChainFlags must not be bigger than the RawFlags");
+ memcpy(this, &aRawFlags, sizeof(EventTargetChainFlags));
+ }
+ } mFlags;
+
uint16_t mItemFlags;
nsCOMPtr<nsISupports> mItemData;
// Event retargeting must happen whenever mNewTarget is non-null.
@@ -298,37 +363,49 @@ private:
bool IsCurrentTargetChrome()
{
- if (!(mFlags & NS_TARGET_CHAIN_CHECKED_IF_CHROME)) {
- mFlags |= NS_TARGET_CHAIN_CHECKED_IF_CHROME;
+ if (!mFlags.mChechedIfChrome) {
+ mFlags.mChechedIfChrome = true;
if (IsEventTargetChrome(mTarget)) {
- mFlags |= NS_TARGET_CHAIN_IS_CHROME_CONTENT;
+ mFlags.mIsChromeContent = true;
}
}
- return !!(mFlags & NS_TARGET_CHAIN_IS_CHROME_CONTENT);
+ return mFlags.mIsChromeContent;
}
};
EventTargetChainItem::EventTargetChainItem(EventTarget* aTarget)
: mTarget(aTarget)
- , mFlags(0)
, mItemFlags(0)
{
MOZ_ASSERT(!aTarget || mTarget == aTarget->GetTargetForEventTargetChain());
}
void
-EventTargetChainItem::PreHandleEvent(EventChainPreVisitor& aVisitor)
+EventTargetChainItem::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
aVisitor.Reset();
- Unused << mTarget->PreHandleEvent(aVisitor);
+ Unused << mTarget->GetEventTargetParent(aVisitor);
SetForceContentDispatch(aVisitor.mForceContentDispatch);
SetWantsWillHandleEvent(aVisitor.mWantsWillHandleEvent);
SetMayHaveListenerManager(aVisitor.mMayHaveListenerManager);
+ SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent);
+ SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle);
mItemFlags = aVisitor.mItemFlags;
mItemData = aVisitor.mItemData;
}
void
+EventTargetChainItem::PreHandleEvent(EventChainVisitor& aVisitor)
+{
+ if (!WantsPreHandleEvent()) {
+ return;
+ }
+ aVisitor.mItemFlags = mItemFlags;
+ aVisitor.mItemData = mItemData;
+ Unused << mTarget->PreHandleEvent(aVisitor);
+}
+
+void
EventTargetChainItem::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
aVisitor.mItemFlags = mItemFlags;
@@ -346,12 +423,17 @@ EventTargetChainItem::HandleEventTargetChain(
// Save the target so that it can be restored later.
nsCOMPtr<EventTarget> firstTarget = aVisitor.mEvent->mTarget;
uint32_t chainLength = aChain.Length();
+ uint32_t firstCanHandleEventTargetIdx =
+ EventTargetChainItem::GetFirstCanHandleEventTargetIdx(aChain);
// Capture
aVisitor.mEvent->mFlags.mInCapturePhase = true;
aVisitor.mEvent->mFlags.mInBubblingPhase = false;
- for (uint32_t i = chainLength - 1; i > 0; --i) {
+ for (uint32_t i = chainLength - 1; i > firstCanHandleEventTargetIdx; --i) {
EventTargetChainItem& item = aChain[i];
+ if (item.PreHandleEventOnly()) {
+ continue;
+ }
if ((!aVisitor.mEvent->mFlags.mNoContentDispatch ||
item.ForceContentDispatch()) &&
!aVisitor.mEvent->PropagationStopped()) {
@@ -373,7 +455,7 @@ EventTargetChainItem::HandleEventTargetChain(
// Target
aVisitor.mEvent->mFlags.mInBubblingPhase = true;
- EventTargetChainItem& targetItem = aChain[0];
+ EventTargetChainItem& targetItem = aChain[firstCanHandleEventTargetIdx];
if (!aVisitor.mEvent->PropagationStopped() &&
(!aVisitor.mEvent->mFlags.mNoContentDispatch ||
targetItem.ForceContentDispatch())) {
@@ -385,8 +467,11 @@ EventTargetChainItem::HandleEventTargetChain(
// Bubble
aVisitor.mEvent->mFlags.mInCapturePhase = false;
- for (uint32_t i = 1; i < chainLength; ++i) {
+ for (uint32_t i = firstCanHandleEventTargetIdx + 1; i < chainLength; ++i) {
EventTargetChainItem& item = aChain[i];
+ if (item.PreHandleEventOnly()) {
+ continue;
+ }
EventTarget* newTarget = item.GetNewTarget();
if (newTarget) {
// Item is at anonymous boundary. Need to retarget for the current item
@@ -471,6 +556,28 @@ EventTargetChainItemForChromeTarget(nsTArray<EventTargetChainItem>& aChain,
return etci;
}
+/* static */ EventTargetChainItem*
+MayRetargetToChromeIfCanNotHandleEvent(
+ nsTArray<EventTargetChainItem>& aChain, EventChainPreVisitor& aPreVisitor,
+ EventTargetChainItem* aTargetEtci, EventTargetChainItem* aChildEtci,
+ nsINode* aContent)
+{
+ if (!aPreVisitor.mWantsPreHandleEvent) {
+ // Keep EventTargetChainItem if we need to call PreHandleEvent on it.
+ EventTargetChainItem::DestroyLast(aChain, aTargetEtci);
+ }
+ if (aPreVisitor.mAutomaticChromeDispatch && aContent) {
+ // Event target couldn't handle the event. Try to propagate to chrome.
+ EventTargetChainItem* chromeTargetEtci =
+ EventTargetChainItemForChromeTarget(aChain, aContent, aChildEtci);
+ if (chromeTargetEtci) {
+ chromeTargetEtci->GetEventTargetParent(aPreVisitor);
+ return chromeTargetEtci;
+ }
+ }
+ return nullptr;
+}
+
/* static */ nsresult
EventDispatcher::Dispatch(nsISupports* aTarget,
nsPresContext* aPresContext,
@@ -593,7 +700,6 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
// Create the event target chain item for the event target.
EventTargetChainItem* targetEtci =
EventTargetChainItem::Create(chain, target->GetTargetForEventTargetChain());
- MOZ_ASSERT(&chain[0] == targetEtci);
if (!targetEtci->IsValid()) {
EventTargetChainItem::DestroyLast(chain, targetEtci);
return NS_ERROR_FAILURE;
@@ -631,21 +737,24 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
aEvent->mFlags.mIsBeingDispatched = true;
// Create visitor object and start event dispatching.
- // PreHandleEvent for the original target.
+ // GetEventTargetParent for the original target.
nsEventStatus status = aEventStatus ? *aEventStatus : nsEventStatus_eIgnore;
EventChainPreVisitor preVisitor(aPresContext, aEvent, aDOMEvent, status,
isInAnon);
- targetEtci->PreHandleEvent(preVisitor);
-
- if (!preVisitor.mCanHandle && preVisitor.mAutomaticChromeDispatch && content) {
- // Event target couldn't handle the event. Try to propagate to chrome.
- EventTargetChainItem::DestroyLast(chain, targetEtci);
- targetEtci = EventTargetChainItemForChromeTarget(chain, content);
- NS_ENSURE_STATE(targetEtci);
- MOZ_ASSERT(&chain[0] == targetEtci);
- targetEtci->PreHandleEvent(preVisitor);
- }
- if (preVisitor.mCanHandle) {
+ targetEtci->GetEventTargetParent(preVisitor);
+
+ if (!preVisitor.mCanHandle) {
+ targetEtci = MayRetargetToChromeIfCanNotHandleEvent(chain, preVisitor,
+ targetEtci, nullptr,
+ content);
+ }
+ if (!preVisitor.mCanHandle) {
+ // The original target and chrome target (mAutomaticChromeDispatch=true)
+ // can not handle the event but we still have to call their PreHandleEvent.
+ for (uint32_t i = 0; i < chain.Length(); ++i) {
+ chain[i].PreHandleEvent(preVisitor);
+ }
+ } else {
// At least the original target can handle the event.
// Setting the retarget to the |target| simplifies retargeting code.
nsCOMPtr<EventTarget> t = do_QueryInterface(aEvent->mTarget);
@@ -670,29 +779,22 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent);
}
- parentEtci->PreHandleEvent(preVisitor);
+ parentEtci->GetEventTargetParent(preVisitor);
if (preVisitor.mCanHandle) {
topEtci = parentEtci;
} else {
- EventTargetChainItem::DestroyLast(chain, parentEtci);
- parentEtci = nullptr;
- if (preVisitor.mAutomaticChromeDispatch && content) {
- // Even if the current target can't handle the event, try to
- // propagate to chrome.
- nsCOMPtr<nsINode> disabledTarget = do_QueryInterface(parentTarget);
- if (disabledTarget) {
- parentEtci = EventTargetChainItemForChromeTarget(chain,
- disabledTarget,
- topEtci);
- if (parentEtci) {
- parentEtci->PreHandleEvent(preVisitor);
- if (preVisitor.mCanHandle) {
- chain[0].SetNewTarget(parentTarget);
- topEtci = parentEtci;
- continue;
- }
- }
- }
+ nsCOMPtr<nsINode> disabledTarget = do_QueryInterface(parentTarget);
+ parentEtci = MayRetargetToChromeIfCanNotHandleEvent(chain,
+ preVisitor,
+ parentEtci,
+ topEtci,
+ disabledTarget);
+ if (parentEtci && preVisitor.mCanHandle) {
+ EventTargetChainItem* item =
+ EventTargetChainItem::GetFirstCanHandleEventTarget(chain);
+ item->SetNewTarget(parentTarget);
+ topEtci = parentEtci;
+ continue;
}
break;
}
@@ -706,7 +808,11 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
targets[i] = chain[i].CurrentTarget()->GetTargetForDOMEvent();
}
} else {
- // Event target chain is created. Handle the chain.
+ // Event target chain is created. PreHandle the chain.
+ for (uint32_t i = 0; i < chain.Length(); ++i) {
+ chain[i].PreHandleEvent(preVisitor);
+ }
+ // Handle the chain.
EventChainPostVisitor postVisitor(preVisitor);
EventTargetChainItem::HandleEventTargetChain(chain, postVisitor,
aCallback, cd);
diff --git a/dom/events/EventDispatcher.h b/dom/events/EventDispatcher.h
index 3c754033db..db7b47dbf1 100644
--- a/dom/events/EventDispatcher.h
+++ b/dom/events/EventDispatcher.h
@@ -31,14 +31,14 @@ class EventTarget;
* About event dispatching:
* When either EventDispatcher::Dispatch or
* EventDispatcher::DispatchDOMEvent is called an event target chain is
- * created. EventDispatcher creates the chain by calling PreHandleEvent
+ * created. EventDispatcher creates the chain by calling GetEventTargetParent
* on each event target and the creation continues until either the mCanHandle
* member of the EventChainPreVisitor object is false or the mParentTarget
* does not point to a new target. The event target chain is created in the
* heap.
*
* If the event needs retargeting, mEventTargetAtParent must be set in
- * PreHandleEvent.
+ * GetEventTargetParent.
*
* The capture, target and bubble phases of the event dispatch are handled
* by iterating through the event target chain. Iteration happens twice,
@@ -86,7 +86,7 @@ public:
/**
* Bits for items in the event target chain.
- * Set in PreHandleEvent() and used in PostHandleEvent().
+ * Set in GetEventTargetParent() and used in PostHandleEvent().
*
* @note These bits are different for each item in the event target chain.
* It is up to the Pre/PostHandleEvent implementation to decide how to
@@ -98,7 +98,7 @@ public:
/**
* Data for items in the event target chain.
- * Set in PreHandleEvent() and used in PostHandleEvent().
+ * Set in GetEventTargetParent() and used in PostHandleEvent().
*
* @note This data is different for each item in the event target chain.
* It is up to the Pre/PostHandleEvent implementation to decide how to
@@ -123,6 +123,7 @@ public:
, mOriginalTargetIsInAnon(aIsInAnon)
, mWantsWillHandleEvent(false)
, mMayHaveListenerManager(true)
+ , mWantsPreHandleEvent(false)
, mParentTarget(nullptr)
, mEventTargetAtParent(nullptr)
{
@@ -137,13 +138,14 @@ public:
mForceContentDispatch = false;
mWantsWillHandleEvent = false;
mMayHaveListenerManager = true;
+ mWantsPreHandleEvent = false;
mParentTarget = nullptr;
mEventTargetAtParent = nullptr;
}
/**
- * Member that must be set in PreHandleEvent by event targets. If set to false,
- * indicates that this event target will not be handling the event and
+ * Member that must be set in GetEventTargetParent by event targets. If set to
+ * false, indicates that this event target will not be handling the event and
* construction of the event target chain is complete. The target that sets
* mCanHandle to false is NOT included in the event target chain.
*/
@@ -170,7 +172,7 @@ public:
/**
* true if the original target of the event is inside anonymous content.
- * This is set before calling PreHandleEvent on event targets.
+ * This is set before calling GetEventTargetParent on event targets.
*/
bool mOriginalTargetIsInAnon;
@@ -182,11 +184,17 @@ public:
/**
* If it is known that the current target doesn't have a listener manager
- * when PreHandleEvent is called, set this to false.
+ * when GetEventTargetParent is called, set this to false.
*/
bool mMayHaveListenerManager;
/**
+ * Whether or not nsIDOMEventTarget::PreHandleEvent will be called. Default is
+ * false;
+ */
+ bool mWantsPreHandleEvent;
+
+ /**
* Parent item in the event target chain.
*/
dom::EventTarget* mParentTarget;
diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini
index 0397487bb8..27e8e7150e 100644
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -185,3 +185,4 @@ skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
[test_bug687787.html]
[test_bug1298970.html]
[test_bug1304044.html]
+[test_bug1305458.html]
diff --git a/dom/events/test/test_bug1305458.html b/dom/events/test/test_bug1305458.html
new file mode 100644
index 0000000000..df65959a97
--- /dev/null
+++ b/dom/events/test/test_bug1305458.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1305458
+-->
+<head>
+ <title>Test for Bug 1305458</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ input[type=number] {
+ -moz-appearance: textfield;
+ }
+ input[type=number]:focus,
+ input[type=number]:hover {
+ -moz-appearance: number-input;
+ }
+ </style>
+</head>
+<body onload="doTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1305458">Mozilla Bug 1305458</a>
+ <input id="test_input" type="number">
+ <div id="test_div">bar</div>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ var change_count = 0;
+ function doTest() {
+ let input = document.getElementById("test_input");
+ let div = document.getElementById("test_div");
+ input.addEventListener("change", () => {
+ ++change_count;
+ }, false);
+ // mouse hover
+ input.focus();
+ synthesizeMouse(input, 1, 1, {type: "mousemove"});
+ synthesizeKey("1", {});
+ input.blur();
+ is(change_count, 1, "input should fire change when blur");
+
+ input.focus();
+ synthesizeMouse(div, 1, 1, {type: "mousemove"});
+ synthesizeKey("1", {});
+ input.blur();
+ is(change_count, 2, "input should fire change when blur");
+ SimpleTest.finish();
+ }
+ </script>
+</body>
+</html>