summaryrefslogtreecommitdiffstats
path: root/dom/events
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2020-04-17 16:02:56 +0200
committerGitHub <noreply@github.com>2020-04-17 16:02:56 +0200
commitd5102d6beafc2a2a0cec3cc3ee5f7ebde31ae7bf (patch)
tree2bfef192cbb748b675ce8308c242a376798e265d /dom/events
parent5caf99795aa81e1fc145b8e937b1ee8197ed2486 (diff)
parentf35aa3e15fedf3cd4ad163d60ab74a9537ca5c82 (diff)
downloadUXP-d5102d6beafc2a2a0cec3cc3ee5f7ebde31ae7bf.tar
UXP-d5102d6beafc2a2a0cec3cc3ee5f7ebde31ae7bf.tar.gz
UXP-d5102d6beafc2a2a0cec3cc3ee5f7ebde31ae7bf.tar.lz
UXP-d5102d6beafc2a2a0cec3cc3ee5f7ebde31ae7bf.tar.xz
UXP-d5102d6beafc2a2a0cec3cc3ee5f7ebde31ae7bf.zip
Merge pull request #1518 from MoonchildProductions/shadowdom-merge
Incremental shadowdom-merge
Diffstat (limited to 'dom/events')
-rw-r--r--dom/events/ContentEventHandler.cpp9
-rw-r--r--dom/events/DOMEventTargetHelper.cpp4
-rw-r--r--dom/events/DOMEventTargetHelper.h4
-rwxr-xr-xdom/events/Event.cpp34
-rwxr-xr-xdom/events/Event.h2
-rw-r--r--dom/events/EventDispatcher.cpp363
-rw-r--r--dom/events/EventDispatcher.h72
-rw-r--r--dom/events/EventStateManager.cpp37
-rw-r--r--dom/events/EventStates.h40
-rw-r--r--dom/events/MouseEvent.cpp5
-rw-r--r--dom/events/test/mochitest.ini1
-rw-r--r--dom/events/test/test_bug1305458.html50
12 files changed, 469 insertions, 152 deletions
diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp
index 935ade23f..df54a4d44 100644
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -980,11 +980,7 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
// Special case like <br contenteditable>
if (!mRootContent->HasChildren()) {
- nsresult rv = aRange->SetStart(mRootContent, 0);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- rv = aRange->SetEnd(mRootContent, 0);
+ nsresult rv = aRange->CollapseTo(mRootContent, 0);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@@ -2880,8 +2876,7 @@ ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aRange)
return NS_OK;
}
- nsresult rv = aRange->Set(childNode, offsetInChildNode,
- childNode, offsetInChildNode);
+ nsresult rv = aRange->CollapseTo(childNode, offsetInChildNode);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
diff --git a/dom/events/DOMEventTargetHelper.cpp b/dom/events/DOMEventTargetHelper.cpp
index dd9a01d8d..90ee61059 100644
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -328,10 +328,10 @@ DOMEventTargetHelper::GetEventHandler(nsIAtom* aType,
}
nsresult
-DOMEventTargetHelper::PreHandleEvent(EventChainPreVisitor& aVisitor)
+DOMEventTargetHelper::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
aVisitor.mCanHandle = true;
- aVisitor.mParentTarget = nullptr;
+ aVisitor.SetParentTarget(nullptr, false);
return NS_OK;
}
diff --git a/dom/events/DOMEventTargetHelper.h b/dom/events/DOMEventTargetHelper.h
index c5a0611c9..b14f05428 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/Event.cpp b/dom/events/Event.cpp
index 280e40ad5..aff65b40f 100755
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -11,6 +11,7 @@
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/dom/Performance.h"
@@ -157,18 +158,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event)
tmp->mEvent->mTarget = nullptr;
tmp->mEvent->mCurrentTarget = nullptr;
tmp->mEvent->mOriginalTarget = nullptr;
+ tmp->mEvent->mRelatedTarget = nullptr;
switch (tmp->mEvent->mClass) {
- case eMouseEventClass:
- case eMouseScrollEventClass:
- case eWheelEventClass:
- case eSimpleGestureEventClass:
- case ePointerEventClass:
- tmp->mEvent->AsMouseEventBase()->relatedTarget = nullptr;
- break;
case eDragEventClass: {
WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
dragEvent->mDataTransfer = nullptr;
- dragEvent->relatedTarget = nullptr;
break;
}
case eClipboardEventClass:
@@ -177,9 +171,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event)
case eMutationEventClass:
tmp->mEvent->AsMutationEvent()->mRelatedNode = nullptr;
break;
- case eFocusEventClass:
- tmp->mEvent->AsFocusEvent()->mRelatedTarget = nullptr;
- break;
default:
break;
}
@@ -195,21 +186,12 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mTarget)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mCurrentTarget)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mRelatedTarget)
switch (tmp->mEvent->mClass) {
- case eMouseEventClass:
- case eMouseScrollEventClass:
- case eWheelEventClass:
- case eSimpleGestureEventClass:
- case ePointerEventClass:
- NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
- cb.NoteXPCOMChild(tmp->mEvent->AsMouseEventBase()->relatedTarget);
- break;
case eDragEventClass: {
WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mDataTransfer");
cb.NoteXPCOMChild(dragEvent->mDataTransfer);
- NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
- cb.NoteXPCOMChild(dragEvent->relatedTarget);
break;
}
case eClipboardEventClass:
@@ -220,10 +202,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event)
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedNode");
cb.NoteXPCOMChild(tmp->mEvent->AsMutationEvent()->mRelatedNode);
break;
- case eFocusEventClass:
- NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedTarget");
- cb.NoteXPCOMChild(tmp->mEvent->AsFocusEvent()->mRelatedTarget);
- break;
default:
break;
}
@@ -298,6 +276,12 @@ Event::GetCurrentTarget() const
return mEvent->GetCurrentDOMEventTarget();
}
+void
+Event::ComposedPath(nsTArray<RefPtr<EventTarget>>& aPath)
+{
+ EventDispatcher::GetComposedPathFor(mEvent, aPath);
+}
+
NS_IMETHODIMP
Event::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget)
{
diff --git a/dom/events/Event.h b/dom/events/Event.h
index 0817aa809..4f1fcc827 100755
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -157,6 +157,8 @@ public:
EventTarget* GetTarget() const;
EventTarget* GetCurrentTarget() const;
+ void ComposedPath(nsTArray<RefPtr<EventTarget>>& aPath);
+
uint16_t EventPhase() const;
// xpidl implementation
diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp
index 1d4dfd7d9..1094c08c2 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,82 @@ 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 SetRootOfClosedTree(bool aSet)
+ {
+ mFlags.mRootOfClosedTree = aSet;
+ }
+
+ bool IsRootOfClosedTree()
+ {
+ return mFlags.mRootOfClosedTree;
+ }
+
+ void SetIsSlotInClosedTree(bool aSet)
+ {
+ mFlags.mIsSlotInClosedTree = aSet;
+ }
+
+ bool IsSlotInClosedTree()
+ {
+ return mFlags.mIsSlotInClosedTree;
+ }
+
+ void SetIsChromeHandler(bool aSet)
+ {
+ mFlags.mIsChromeHandler = aSet;
+ }
+
+ bool IsChromeHandler()
+ {
+ return mFlags.mIsChromeHandler;
}
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 +303,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 +356,37 @@ 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;
+ bool mRootOfClosedTree : 1;
+ bool mIsSlotInClosedTree : 1;
+ bool mIsChromeHandler : 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 +396,50 @@ 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);
+ SetRootOfClosedTree(aVisitor.mRootOfClosedTree);
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 +457,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 +489,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 +501,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 +590,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 +734,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,37 +771,43 @@ 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);
targetEtci->SetNewTarget(t);
EventTargetChainItem* topEtci = targetEtci;
targetEtci = nullptr;
- while (preVisitor.mParentTarget) {
- EventTarget* parentTarget = preVisitor.mParentTarget;
+ while (preVisitor.GetParentTarget()) {
+ EventTarget* parentTarget = preVisitor.GetParentTarget();
EventTargetChainItem* parentEtci =
- EventTargetChainItem::Create(chain, preVisitor.mParentTarget, topEtci);
+ EventTargetChainItem::Create(chain, parentTarget, topEtci);
if (!parentEtci->IsValid()) {
EventTargetChainItem::DestroyLast(chain, parentEtci);
rv = NS_ERROR_FAILURE;
break;
}
+ parentEtci->SetIsSlotInClosedTree(preVisitor.mParentIsSlotInClosedTree);
+ parentEtci->SetIsChromeHandler(preVisitor.mParentIsChromeHandler);
+
// Item needs event retargetting.
if (preVisitor.mEventTargetAtParent) {
// Need to set the target of the event
@@ -670,29 +816,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,11 +845,19 @@ 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);
+ MOZ_RELEASE_ASSERT(!aEvent->mPath);
+ aEvent->mPath = &chain;
EventTargetChainItem::HandleEventTargetChain(chain, postVisitor,
aCallback, cd);
+ aEvent->mPath = nullptr;
+
preVisitor.mEventStatus = postVisitor.mEventStatus;
// If the DOM event was created during event flow.
if (!preVisitor.mDOMEvent && postVisitor.mDOMEvent) {
@@ -1022,4 +1169,64 @@ EventDispatcher::CreateEvent(EventTarget* aOwner,
return nullptr;
}
+// static
+void
+EventDispatcher::GetComposedPathFor(WidgetEvent* aEvent,
+ nsTArray<RefPtr<EventTarget>>& aPath)
+{
+ nsTArray<EventTargetChainItem>* path = aEvent->mPath;
+ if (!path || path->IsEmpty() || !aEvent->mCurrentTarget) {
+ return;
+ }
+
+ EventTarget* currentTarget =
+ aEvent->mCurrentTarget->GetTargetForEventTargetChain();
+ if (!currentTarget) {
+ return;
+ }
+
+ AutoTArray<EventTarget*, 128> reversedComposedPath;
+ bool hasSeenCurrentTarget = false;
+ uint32_t hiddenSubtreeLevel = 0;
+ for (uint32_t i = path->Length(); i; ) {
+ --i;
+
+ EventTargetChainItem& item = path->ElementAt(i);
+ if (item.PreHandleEventOnly()) {
+ continue;
+ }
+
+ if (!hasSeenCurrentTarget && currentTarget == item.CurrentTarget()) {
+ hasSeenCurrentTarget = true;
+ } else if (hasSeenCurrentTarget && item.IsRootOfClosedTree()) {
+ ++hiddenSubtreeLevel;
+ }
+
+ if (hiddenSubtreeLevel == 0) {
+ reversedComposedPath.AppendElement(item.CurrentTarget());
+ }
+
+ if (item.IsSlotInClosedTree() && hiddenSubtreeLevel > 0) {
+ --hiddenSubtreeLevel;
+ }
+
+ if (item.IsChromeHandler()) {
+ if (hasSeenCurrentTarget) {
+ // The current behavior is to include only EventTargets from
+ // either chrome side of event path or content side, not from both.
+ break;
+ }
+
+ // Need to start all over to collect the composed path on content side.
+ reversedComposedPath.Clear();
+ }
+ }
+
+ aPath.SetCapacity(reversedComposedPath.Length());
+ for (uint32_t i = reversedComposedPath.Length(); i; ) {
+ --i;
+ aPath.AppendElement(reversedComposedPath[i]->GetTargetForDOMEvent());
+ }
+}
+
} // namespace mozilla
diff --git a/dom/events/EventDispatcher.h b/dom/events/EventDispatcher.h
index 3c754033d..8a34e6bf7 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,10 @@ public:
, mOriginalTargetIsInAnon(aIsInAnon)
, mWantsWillHandleEvent(false)
, mMayHaveListenerManager(true)
+ , mWantsPreHandleEvent(false)
+ , mRootOfClosedTree(false)
+ , mParentIsSlotInClosedTree(false)
+ , mParentIsChromeHandler(false)
, mParentTarget(nullptr)
, mEventTargetAtParent(nullptr)
{
@@ -137,13 +141,30 @@ public:
mForceContentDispatch = false;
mWantsWillHandleEvent = false;
mMayHaveListenerManager = true;
+ mWantsPreHandleEvent = false;
+ mRootOfClosedTree = false;
+ mParentIsSlotInClosedTree = false;
+ mParentIsChromeHandler = false;
mParentTarget = nullptr;
mEventTargetAtParent = nullptr;
}
+ dom::EventTarget* GetParentTarget()
+ {
+ return mParentTarget;
+ }
+
+ void SetParentTarget(dom::EventTarget* aParentTarget, bool aIsChromeHandler)
+ {
+ mParentTarget = aParentTarget;
+ if (mParentTarget) {
+ mParentIsChromeHandler = aIsChromeHandler;
+ }
+ }
+
/**
- * 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 +191,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,27 +203,45 @@ 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;
+
+ /**
+ * True if the current target is either closed ShadowRoot or root of
+ * chrome only access tree (for example native anonymous content).
+ */
+ bool mRootOfClosedTree;
+
+ /**
+ * True if mParentTarget is HTMLSlotElement in a closed shadow tree and the
+ * current target is assigned to that slot.
+ */
+ bool mParentIsSlotInClosedTree;
+
+ /**
+ * True if mParentTarget is a chrome handler in the event path.
+ */
+ bool mParentIsChromeHandler;
+
+private:
+ /**
* Parent item in the event target chain.
*/
dom::EventTarget* mParentTarget;
+public:
/**
* If the event needs to be retargeted, this is the event target,
* which should be used when the event is handled at mParentTarget.
*/
dom::EventTarget* mEventTargetAtParent;
-
- /**
- * An array of destination insertion points that need to be inserted
- * into the event path of nodes that are distributed by the
- * web components distribution algorithm.
- */
- nsTArray<nsIContent*> mDestInsertionPoints;
};
class EventChainPostVisitor : public mozilla::EventChainVisitor
@@ -280,6 +319,9 @@ public:
WidgetEvent* aEvent,
const nsAString& aEventType);
+ static void GetComposedPathFor(WidgetEvent* aEvent,
+ nsTArray<RefPtr<dom::EventTarget>>& aPath);
+
/**
* Called at shutting down.
*/
diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp
index e16d68c81..cc3bd3f5a 100644
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -3484,6 +3484,12 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
// make sure to fire the enter and exit_synth events after the
// eDragExit event, otherwise we'll clean up too early
GenerateDragDropEnterExit(presContext, aEvent->AsDragEvent());
+ if (ContentChild* child = ContentChild::GetSingleton()) {
+ // SendUpdateDropEffect to prevent nsIDragService from waiting for
+ // response of forwarded dragexit event.
+ child->SendUpdateDropEffect(nsIDragService::DRAGDROP_ACTION_NONE,
+ nsIDragService::DRAGDROP_ACTION_NONE);
+ }
break;
case eBeforeKeyUp:
@@ -3900,13 +3906,13 @@ CreateMouseOrPointerWidgetEvent(WidgetMouseEvent* aMouseEvent,
newPointerEvent->mWidth = sourcePointer->mWidth;
newPointerEvent->mHeight = sourcePointer->mHeight;
newPointerEvent->inputSource = sourcePointer->inputSource;
- newPointerEvent->relatedTarget = aRelatedContent;
+ newPointerEvent->mRelatedTarget = aRelatedContent;
aNewEvent = newPointerEvent.forget();
} else {
aNewEvent =
new WidgetMouseEvent(aMouseEvent->IsTrusted(), aMessage,
aMouseEvent->mWidget, WidgetMouseEvent::eReal);
- aNewEvent->relatedTarget = aRelatedContent;
+ aNewEvent->mRelatedTarget = aRelatedContent;
}
aNewEvent->mRefPoint = aMouseEvent->mRefPoint;
aNewEvent->mModifiers = aMouseEvent->mModifiers;
@@ -4446,6 +4452,19 @@ EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext,
FireDragEnterOrExit(sLastDragOverFrame->PresContext(),
aDragEvent, eDragExit,
targetContent, lastContent, sLastDragOverFrame);
+ nsIContent* target = sLastDragOverFrame ? sLastDragOverFrame.GetFrame()->GetContent() : nullptr;
+ if (IsRemoteTarget(target)) {
+ // Dragging something and moving from web content to chrome only
+ // fires dragexit and dragleave to xul:browser. We have to forward
+ // dragexit to sLastDragOverFrame when its content is a remote
+ // target. We don't forward dragleave since it's generated from
+ // dragexit.
+ WidgetDragEvent remoteEvent(aDragEvent->IsTrusted(), eDragExit,
+ aDragEvent->mWidget);
+ remoteEvent.AssignDragEventData(*aDragEvent, true);
+ nsEventStatus remoteStatus = nsEventStatus_eIgnore;
+ HandleCrossProcessEvent(&remoteEvent, &remoteStatus);
+ }
}
FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter,
@@ -4502,14 +4521,11 @@ EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext,
nsIContent* aTargetContent,
nsWeakFrame& aTargetFrame)
{
+ MOZ_ASSERT(aMessage == eDragLeave || aMessage == eDragExit ||
+ aMessage == eDragEnter);
nsEventStatus status = nsEventStatus_eIgnore;
WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget);
- event.mRefPoint = aDragEvent->mRefPoint;
- event.mModifiers = aDragEvent->mModifiers;
- event.buttons = aDragEvent->buttons;
- event.relatedTarget = aRelatedTarget;
- event.inputSource = aDragEvent->inputSource;
-
+ event.AssignDragEventData(*aDragEvent, true);
mCurrentTargetContent = aTargetContent;
if (aTargetContent != aRelatedTarget) {
@@ -4527,10 +4543,7 @@ EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext,
// collect any changes to moz cursor settings stored in the event's
// data transfer.
- if (aMessage == eDragLeave || aMessage == eDragExit ||
- aMessage == eDragEnter) {
- UpdateDragDataTransfer(&event);
- }
+ UpdateDragDataTransfer(&event);
}
// Finally dispatch the event to the frame
diff --git a/dom/events/EventStates.h b/dom/events/EventStates.h
index 2672d2897..3397110ba 100644
--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -292,14 +292,39 @@ private:
#define NS_EVENT_STATE_RTL NS_DEFINE_EVENT_STATE_MACRO(43)
// Element is highlighted (devtools inspector)
#define NS_EVENT_STATE_DEVTOOLS_HIGHLIGHTED NS_DEFINE_EVENT_STATE_MACRO(45)
-// Element is an unresolved custom element candidate
-#define NS_EVENT_STATE_UNRESOLVED NS_DEFINE_EVENT_STATE_MACRO(46)
+// States for tracking the state of the "dir" attribute for HTML elements. We
+// use these to avoid having to get "dir" attributes all the time during
+// selector matching for some parts of the UA stylesheet.
+//
+// These states are externally managed, because we also don't want to keep
+// getting "dir" attributes in IntrinsicState.
+//
+// Element is HTML and has a "dir" attibute. This state might go away depending
+// on how https://github.com/whatwg/html/issues/2769 gets resolved. The value
+// could be anything.
+#define NS_EVENT_STATE_HAS_DIR_ATTR NS_DEFINE_EVENT_STATE_MACRO(46)
+// Element is HTML, has a "dir" attribute, and the attribute's value is
+// case-insensitively equal to "ltr".
+#define NS_EVENT_STATE_DIR_ATTR_LTR NS_DEFINE_EVENT_STATE_MACRO(47)
+// Element is HTML, has a "dir" attribute, and the attribute's value is
+// case-insensitively equal to "rtl".
+#define NS_EVENT_STATE_DIR_ATTR_RTL NS_DEFINE_EVENT_STATE_MACRO(48)
+// Element is HTML, and is either a <bdi> element with no valid-valued "dir"
+// attribute or any HTML element which has a "dir" attribute whose value is
+// "auto".
+#define NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO NS_DEFINE_EVENT_STATE_MACRO(49)
+// Free bit NS_DEFINE_EVENT_STATE_MACRO(50)
// Element is transitioning for rules changed by style editor
-#define NS_EVENT_STATE_STYLEEDITOR_TRANSITIONING NS_DEFINE_EVENT_STATE_MACRO(47)
+#define NS_EVENT_STATE_STYLEEDITOR_TRANSITIONING NS_DEFINE_EVENT_STATE_MACRO(51)
// Content shows its placeholder
-#define NS_EVENT_STATE_PLACEHOLDERSHOWN NS_DEFINE_EVENT_STATE_MACRO(48)
+#define NS_EVENT_STATE_PLACEHOLDERSHOWN NS_DEFINE_EVENT_STATE_MACRO(52)
// Element has focus-within.
-#define NS_EVENT_STATE_FOCUS_WITHIN NS_DEFINE_EVENT_STATE_MACRO(49)
+#define NS_EVENT_STATE_FOCUS_WITHIN NS_DEFINE_EVENT_STATE_MACRO(53)
+
+#define DIR_ATTR_STATES (NS_EVENT_STATE_HAS_DIR_ATTR | \
+ NS_EVENT_STATE_DIR_ATTR_LTR | \
+ NS_EVENT_STATE_DIR_ATTR_RTL | \
+ NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO)
// Event state that is used for values that need to be parsed but do nothing.
#define NS_EVENT_STATE_IGNORE NS_DEFINE_EVENT_STATE_MACRO(63)
@@ -310,11 +335,10 @@ private:
#define DIRECTION_STATES (NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL)
-#define ESM_MANAGED_STATES (NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS | \
+#define ESM_MANAGED_STATES (DIR_ATTR_STATES | NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS | \
NS_EVENT_STATE_HOVER | NS_EVENT_STATE_DRAGOVER | \
NS_EVENT_STATE_URLTARGET | NS_EVENT_STATE_FOCUSRING | \
- NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_UNRESOLVED | \
- NS_EVENT_STATE_FOCUS_WITHIN)
+ NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_FOCUS_WITHIN)
#define INTRINSIC_STATES (~ESM_MANAGED_STATES)
diff --git a/dom/events/MouseEvent.cpp b/dom/events/MouseEvent.cpp
index 5e540ac5b..415e626c5 100644
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -79,7 +79,7 @@ MouseEvent::InitMouseEvent(const nsAString& aType,
case ePointerEventClass:
case eSimpleGestureEventClass: {
WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase();
- mouseEventBase->relatedTarget = aRelatedTarget;
+ mouseEventBase->mRelatedTarget = aRelatedTarget;
mouseEventBase->button = aButton;
mouseEventBase->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
mClientPoint.x = aClientX;
@@ -295,8 +295,7 @@ MouseEvent::GetRelatedTarget()
case eDragEventClass:
case ePointerEventClass:
case eSimpleGestureEventClass:
- relatedTarget =
- do_QueryInterface(mEvent->AsMouseEventBase()->relatedTarget);
+ relatedTarget = mEvent->AsMouseEventBase()->mRelatedTarget;
break;
default:
break;
diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini
index 0397487bb..27e8e7150 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 000000000..df65959a9
--- /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>