diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/events | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/events')
431 files changed, 71612 insertions, 0 deletions
diff --git a/dom/events/AnimationEvent.cpp b/dom/events/AnimationEvent.cpp new file mode 100644 index 000000000..970f95818 --- /dev/null +++ b/dom/events/AnimationEvent.cpp @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/AnimationEvent.h" +#include "mozilla/ContentEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +AnimationEvent::AnimationEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalAnimationEvent* aEvent) + : Event(aOwner, aPresContext, + aEvent ? aEvent : new InternalAnimationEvent(false, eVoidEvent)) +{ + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +NS_INTERFACE_MAP_BEGIN(AnimationEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMAnimationEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMPL_ADDREF_INHERITED(AnimationEvent, Event) +NS_IMPL_RELEASE_INHERITED(AnimationEvent, Event) + +//static +already_AddRefed<AnimationEvent> +AnimationEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const AnimationEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<AnimationEvent> e = new AnimationEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + + e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable); + + InternalAnimationEvent* internalEvent = e->mEvent->AsAnimationEvent(); + internalEvent->mAnimationName = aParam.mAnimationName; + internalEvent->mElapsedTime = aParam.mElapsedTime; + internalEvent->mPseudoElement = aParam.mPseudoElement; + + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +NS_IMETHODIMP +AnimationEvent::GetAnimationName(nsAString& aAnimationName) +{ + aAnimationName = mEvent->AsAnimationEvent()->mAnimationName; + return NS_OK; +} + +NS_IMETHODIMP +AnimationEvent::GetElapsedTime(float* aElapsedTime) +{ + *aElapsedTime = ElapsedTime(); + return NS_OK; +} + +float +AnimationEvent::ElapsedTime() +{ + return mEvent->AsAnimationEvent()->mElapsedTime; +} + +NS_IMETHODIMP +AnimationEvent::GetPseudoElement(nsAString& aPseudoElement) +{ + aPseudoElement = mEvent->AsAnimationEvent()->mPseudoElement; + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<AnimationEvent> +NS_NewDOMAnimationEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalAnimationEvent* aEvent) +{ + RefPtr<AnimationEvent> it = + new AnimationEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/AnimationEvent.h b/dom/events/AnimationEvent.h new file mode 100644 index 000000000..23713a136 --- /dev/null +++ b/dom/events/AnimationEvent.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_dom_AnimationEvent_h_ +#define mozilla_dom_AnimationEvent_h_ + +#include "mozilla/EventForwards.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/AnimationEventBinding.h" +#include "nsIDOMAnimationEvent.h" + +class nsAString; + +namespace mozilla { +namespace dom { + +class AnimationEvent : public Event, + public nsIDOMAnimationEvent +{ +public: + AnimationEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalAnimationEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_TO_EVENT + NS_DECL_NSIDOMANIMATIONEVENT + + static already_AddRefed<AnimationEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const AnimationEventInit& aParam, + ErrorResult& aRv); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return AnimationEventBinding::Wrap(aCx, this, aGivenProto); + } + + // xpidl implementation + // GetAnimationName(nsAString& aAnimationName); + // GetPseudoElement(nsAString& aPseudoElement); + + float ElapsedTime(); + +protected: + ~AnimationEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::AnimationEvent> +NS_NewDOMAnimationEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalAnimationEvent* aEvent); + +#endif // mozilla_dom_AnimationEvent_h_ diff --git a/dom/events/AsyncEventDispatcher.cpp b/dom/events/AsyncEventDispatcher.cpp new file mode 100644 index 000000000..da36f7993 --- /dev/null +++ b/dom/events/AsyncEventDispatcher.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() +#include "mozilla/dom/EventTarget.h" +#include "nsContentUtils.h" +#include "nsIDOMEvent.h" + +namespace mozilla { + +using namespace dom; + +/****************************************************************************** + * mozilla::AsyncEventDispatcher + ******************************************************************************/ + +AsyncEventDispatcher::AsyncEventDispatcher(EventTarget* aTarget, + WidgetEvent& aEvent) + : mTarget(aTarget) +{ + MOZ_ASSERT(mTarget); + RefPtr<Event> event = + EventDispatcher::CreateEvent(aTarget, nullptr, &aEvent, EmptyString()); + mEvent = event.forget(); + NS_ASSERTION(mEvent, "Should never fail to create an event"); + mEvent->DuplicatePrivateData(); + mEvent->SetTrusted(aEvent.IsTrusted()); +} + +NS_IMETHODIMP +AsyncEventDispatcher::Run() +{ + if (mCanceled) { + return NS_OK; + } + mTarget->AsyncEventRunning(this); + RefPtr<Event> event = mEvent ? mEvent->InternalDOMEvent() : nullptr; + if (!event) { + event = NS_NewDOMEvent(mTarget, nullptr, nullptr); + event->InitEvent(mEventType, mBubbles, false); + event->SetTrusted(true); + } + if (mOnlyChromeDispatch) { + MOZ_ASSERT(event->IsTrusted()); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + } + bool dummy; + mTarget->DispatchEvent(event, &dummy); + return NS_OK; +} + +nsresult +AsyncEventDispatcher::Cancel() +{ + mCanceled = true; + return NS_OK; +} + +nsresult +AsyncEventDispatcher::PostDOMEvent() +{ + RefPtr<AsyncEventDispatcher> ensureDeletionWhenFailing = this; + return NS_DispatchToCurrentThread(this); +} + +void +AsyncEventDispatcher::RunDOMEventWhenSafe() +{ + RefPtr<AsyncEventDispatcher> ensureDeletionWhenFailing = this; + nsContentUtils::AddScriptRunner(this); +} + +/****************************************************************************** + * mozilla::LoadBlockingAsyncEventDispatcher + ******************************************************************************/ + +LoadBlockingAsyncEventDispatcher::~LoadBlockingAsyncEventDispatcher() +{ + if (mBlockedDoc) { + mBlockedDoc->UnblockOnload(true); + } +} + +} // namespace mozilla diff --git a/dom/events/AsyncEventDispatcher.h b/dom/events/AsyncEventDispatcher.h new file mode 100644 index 000000000..094e764b6 --- /dev/null +++ b/dom/events/AsyncEventDispatcher.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AsyncEventDispatcher_h_ +#define mozilla_AsyncEventDispatcher_h_ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIDocument.h" +#include "nsIDOMEvent.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +class nsINode; + +namespace mozilla { + +/** + * Use AsyncEventDispatcher to fire a DOM event that requires safe a stable DOM. + * For example, you may need to fire an event from within layout, but + * want to ensure that the event handler doesn't mutate the DOM at + * the wrong time, in order to avoid resulting instability. + */ + +class AsyncEventDispatcher : public CancelableRunnable +{ +public: + /** + * If aOnlyChromeDispatch is true, the event is dispatched to only + * chrome node. In that case, if aTarget is already a chrome node, + * the event is dispatched to it, otherwise the dispatch path starts + * at the first chrome ancestor of that target. + */ + AsyncEventDispatcher(nsINode* aTarget, const nsAString& aEventType, + bool aBubbles, bool aOnlyChromeDispatch) + : mTarget(aTarget) + , mEventType(aEventType) + , mBubbles(aBubbles) + , mOnlyChromeDispatch(aOnlyChromeDispatch) + { + } + + AsyncEventDispatcher(dom::EventTarget* aTarget, const nsAString& aEventType, + bool aBubbles) + : mTarget(aTarget) + , mEventType(aEventType) + , mBubbles(aBubbles) + { + } + + AsyncEventDispatcher(dom::EventTarget* aTarget, nsIDOMEvent* aEvent) + : mTarget(aTarget) + , mEvent(aEvent) + { + } + + AsyncEventDispatcher(dom::EventTarget* aTarget, WidgetEvent& aEvent); + + NS_IMETHOD Run() override; + nsresult Cancel() override; + nsresult PostDOMEvent(); + void RunDOMEventWhenSafe(); + + nsCOMPtr<dom::EventTarget> mTarget; + nsCOMPtr<nsIDOMEvent> mEvent; + nsString mEventType; + bool mBubbles = false; + bool mOnlyChromeDispatch = false; + bool mCanceled = false; +}; + +class LoadBlockingAsyncEventDispatcher final : public AsyncEventDispatcher +{ +public: + LoadBlockingAsyncEventDispatcher(nsINode* aEventNode, + const nsAString& aEventType, + bool aBubbles, bool aDispatchChromeOnly) + : AsyncEventDispatcher(aEventNode, aEventType, + aBubbles, aDispatchChromeOnly) + , mBlockedDoc(aEventNode->OwnerDoc()) + { + if (mBlockedDoc) { + mBlockedDoc->BlockOnload(); + } + } + + LoadBlockingAsyncEventDispatcher(nsINode* aEventNode, nsIDOMEvent* aEvent) + : AsyncEventDispatcher(aEventNode, aEvent) + , mBlockedDoc(aEventNode->OwnerDoc()) + { + if (mBlockedDoc) { + mBlockedDoc->BlockOnload(); + } + } + + ~LoadBlockingAsyncEventDispatcher(); + +private: + nsCOMPtr<nsIDocument> mBlockedDoc; +}; + +} // namespace mozilla + +#endif // mozilla_AsyncEventDispatcher_h_ diff --git a/dom/events/BeforeAfterKeyboardEvent.cpp b/dom/events/BeforeAfterKeyboardEvent.cpp new file mode 100644 index 000000000..c94227755 --- /dev/null +++ b/dom/events/BeforeAfterKeyboardEvent.cpp @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BeforeAfterKeyboardEvent.h" +#include "mozilla/TextEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +BeforeAfterKeyboardEvent::BeforeAfterKeyboardEvent( + EventTarget* aOwner, + nsPresContext* aPresContext, + InternalBeforeAfterKeyboardEvent* aEvent) + : KeyboardEvent(aOwner, aPresContext, + aEvent ? aEvent : + new InternalBeforeAfterKeyboardEvent(false, + eVoidEvent, + nullptr)) +{ + MOZ_ASSERT(mEvent->mClass == eBeforeAfterKeyboardEventClass, + "event type mismatch eBeforeAfterKeyboardEventClass"); + + if (!aEvent) { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +// static +already_AddRefed<BeforeAfterKeyboardEvent> +BeforeAfterKeyboardEvent::Constructor( + EventTarget* aOwner, + const nsAString& aType, + const BeforeAfterKeyboardEventInit& aParam) +{ + RefPtr<BeforeAfterKeyboardEvent> event = + new BeforeAfterKeyboardEvent(aOwner, nullptr, nullptr); + ErrorResult rv; + event->InitWithKeyboardEventInit(aOwner, aType, aParam, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + } + + event->mEvent->AsBeforeAfterKeyboardEvent()->mEmbeddedCancelled = + aParam.mEmbeddedCancelled; + + return event.forget(); +} + +// static +already_AddRefed<BeforeAfterKeyboardEvent> +BeforeAfterKeyboardEvent::Constructor( + const GlobalObject& aGlobal, + const nsAString& aType, + const BeforeAfterKeyboardEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, aType, aParam); +} + +Nullable<bool> +BeforeAfterKeyboardEvent::GetEmbeddedCancelled() +{ + nsAutoString type; + GetType(type); + if (type.EqualsLiteral("mozbrowserafterkeydown") || + type.EqualsLiteral("mozbrowserafterkeyup")) { + return mEvent->AsBeforeAfterKeyboardEvent()->mEmbeddedCancelled; + } + return Nullable<bool>(); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<BeforeAfterKeyboardEvent> +NS_NewDOMBeforeAfterKeyboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalBeforeAfterKeyboardEvent* aEvent) +{ + RefPtr<BeforeAfterKeyboardEvent> it = + new BeforeAfterKeyboardEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/BeforeAfterKeyboardEvent.h b/dom/events/BeforeAfterKeyboardEvent.h new file mode 100644 index 000000000..5ada2e861 --- /dev/null +++ b/dom/events/BeforeAfterKeyboardEvent.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BeforeAfterKeyboardEvent_h_ +#define mozilla_dom_BeforeAfterKeyboardEvent_h_ + +#include "mozilla/dom/KeyboardEvent.h" +#include "mozilla/dom/BeforeAfterKeyboardEventBinding.h" + +namespace mozilla { +namespace dom { + +class BeforeAfterKeyboardEvent : public KeyboardEvent +{ +public: + BeforeAfterKeyboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalBeforeAfterKeyboardEvent* aEvent); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return BeforeAfterKeyboardEventBinding::Wrap(aCx, this, aGivenProto); + } + + static already_AddRefed<BeforeAfterKeyboardEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const BeforeAfterKeyboardEventInit& aParam, + ErrorResult& aRv); + + static already_AddRefed<BeforeAfterKeyboardEvent> + Constructor(EventTarget* aOwner, const nsAString& aType, + const BeforeAfterKeyboardEventInit& aEventInitDict); + + // This function returns a boolean value when event typs is either + // "mozbrowserafterkeydown" or "mozbrowserafterkeyup". + Nullable<bool> GetEmbeddedCancelled(); +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::BeforeAfterKeyboardEvent> +NS_NewDOMBeforeAfterKeyboardEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalBeforeAfterKeyboardEvent* aEvent); + +#endif // mozilla_dom_BeforeAfterKeyboardEvent_h_ diff --git a/dom/events/BeforeUnloadEvent.cpp b/dom/events/BeforeUnloadEvent.cpp new file mode 100644 index 000000000..efcf8e205 --- /dev/null +++ b/dom/events/BeforeUnloadEvent.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BeforeUnloadEvent.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ADDREF_INHERITED(BeforeUnloadEvent, Event) +NS_IMPL_RELEASE_INHERITED(BeforeUnloadEvent, Event) + +NS_INTERFACE_MAP_BEGIN(BeforeUnloadEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMBeforeUnloadEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMETHODIMP +BeforeUnloadEvent::SetReturnValue(const nsAString& aReturnValue) +{ + mText = aReturnValue; + return NS_OK; // Don't throw an exception +} + +NS_IMETHODIMP +BeforeUnloadEvent::GetReturnValue(nsAString& aReturnValue) +{ + aReturnValue = mText; + return NS_OK; // Don't throw an exception +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<BeforeUnloadEvent> +NS_NewDOMBeforeUnloadEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + RefPtr<BeforeUnloadEvent> it = + new BeforeUnloadEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/BeforeUnloadEvent.h b/dom/events/BeforeUnloadEvent.h new file mode 100644 index 000000000..781332c6a --- /dev/null +++ b/dom/events/BeforeUnloadEvent.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BeforeUnloadEvent_h_ +#define mozilla_dom_BeforeUnloadEvent_h_ + +#include "mozilla/dom/BeforeUnloadEventBinding.h" +#include "mozilla/dom/Event.h" +#include "nsIDOMBeforeUnloadEvent.h" + +namespace mozilla { +namespace dom { + +class BeforeUnloadEvent : public Event, + public nsIDOMBeforeUnloadEvent +{ +public: + BeforeUnloadEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) + : Event(aOwner, aPresContext, aEvent) + { + } + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return BeforeUnloadEventBinding::Wrap(aCx, this, aGivenProto); + } + + NS_DECL_ISUPPORTS_INHERITED + + // Forward to Event + NS_FORWARD_TO_EVENT + + // nsIDOMBeforeUnloadEvent Interface + NS_DECL_NSIDOMBEFOREUNLOADEVENT + +protected: + ~BeforeUnloadEvent() {} + + nsString mText; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::BeforeUnloadEvent> +NS_NewDOMBeforeUnloadEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent); + +#endif // mozilla_dom_BeforeUnloadEvent_h_ diff --git a/dom/events/ClipboardEvent.cpp b/dom/events/ClipboardEvent.cpp new file mode 100644 index 000000000..cff1eb621 --- /dev/null +++ b/dom/events/ClipboardEvent.cpp @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ClipboardEvent.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/dom/DataTransfer.h" +#include "nsIClipboard.h" + +namespace mozilla { +namespace dom { + +ClipboardEvent::ClipboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalClipboardEvent* aEvent) + : Event(aOwner, aPresContext, + aEvent ? aEvent : new InternalClipboardEvent(false, eVoidEvent)) +{ + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +NS_INTERFACE_MAP_BEGIN(ClipboardEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMClipboardEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMPL_ADDREF_INHERITED(ClipboardEvent, Event) +NS_IMPL_RELEASE_INHERITED(ClipboardEvent, Event) + +nsresult +ClipboardEvent::InitClipboardEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsIDOMDataTransfer* aClipboardData) +{ + nsCOMPtr<DataTransfer> clipboardData = do_QueryInterface(aClipboardData); + // Null clipboardData is OK + + ErrorResult rv; + InitClipboardEvent(aType, aCanBubble, aCancelable, clipboardData); + + return rv.StealNSResult(); +} + +void +ClipboardEvent::InitClipboardEvent(const nsAString& aType, bool aCanBubble, + bool aCancelable, + DataTransfer* aClipboardData) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + Event::InitEvent(aType, aCanBubble, aCancelable); + mEvent->AsClipboardEvent()->mClipboardData = aClipboardData; +} + +already_AddRefed<ClipboardEvent> +ClipboardEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const ClipboardEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<ClipboardEvent> e = new ClipboardEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + + RefPtr<DataTransfer> clipboardData; + if (e->mEventIsInternal) { + InternalClipboardEvent* event = e->mEvent->AsClipboardEvent(); + if (event) { + // Always create a clipboardData for the copy event. If this is changed to + // support other types of events, make sure that read/write privileges are + // checked properly within DataTransfer. + clipboardData = new DataTransfer(ToSupports(e), eCopy, false, -1); + clipboardData->SetData(aParam.mDataType, aParam.mData, + *aGlobal.GetSubjectPrincipal(), aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + } + } + + e->InitClipboardEvent(aType, aParam.mBubbles, aParam.mCancelable, + clipboardData); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +NS_IMETHODIMP +ClipboardEvent::GetClipboardData(nsIDOMDataTransfer** aClipboardData) +{ + NS_IF_ADDREF(*aClipboardData = GetClipboardData()); + return NS_OK; +} + +DataTransfer* +ClipboardEvent::GetClipboardData() +{ + InternalClipboardEvent* event = mEvent->AsClipboardEvent(); + + if (!event->mClipboardData) { + if (mEventIsInternal) { + event->mClipboardData = + new DataTransfer(ToSupports(this), eCopy, false, -1); + } else { + event->mClipboardData = + new DataTransfer(ToSupports(this), event->mMessage, + event->mMessage == ePaste, + nsIClipboard::kGlobalClipboard); + } + } + + return event->mClipboardData; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<ClipboardEvent> +NS_NewDOMClipboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalClipboardEvent* aEvent) +{ + RefPtr<ClipboardEvent> it = + new ClipboardEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/ClipboardEvent.h b/dom/events/ClipboardEvent.h new file mode 100644 index 000000000..c3dcde8f2 --- /dev/null +++ b/dom/events/ClipboardEvent.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ClipboardEvent_h_ +#define mozilla_dom_ClipboardEvent_h_ + +#include "mozilla/EventForwards.h" +#include "mozilla/dom/ClipboardEventBinding.h" +#include "mozilla/dom/Event.h" +#include "nsIDOMClipboardEvent.h" + +namespace mozilla { +namespace dom { +class DataTransfer; + +class ClipboardEvent : public Event, + public nsIDOMClipboardEvent +{ +public: + ClipboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalClipboardEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIDOMCLIPBOARDEVENT + + // Forward to base class + NS_FORWARD_TO_EVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return ClipboardEventBinding::Wrap(aCx, this, aGivenProto); + } + + static already_AddRefed<ClipboardEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const ClipboardEventInit& aParam, + ErrorResult& aRv); + + DataTransfer* GetClipboardData(); + + void InitClipboardEvent(const nsAString& aType, bool aCanBubble, + bool aCancelable, + DataTransfer* aClipboardData); + +protected: + ~ClipboardEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::ClipboardEvent> +NS_NewDOMClipboardEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalClipboardEvent* aEvent); + +#endif // mozilla_dom_ClipboardEvent_h_ diff --git a/dom/events/CommandEvent.cpp b/dom/events/CommandEvent.cpp new file mode 100644 index 000000000..3506659db --- /dev/null +++ b/dom/events/CommandEvent.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/CommandEvent.h" +#include "mozilla/MiscEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +CommandEvent::CommandEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetCommandEvent* aEvent) + : Event(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetCommandEvent(false, nullptr, nullptr, nullptr)) +{ + mEvent->mTime = PR_Now(); + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + } +} + +NS_INTERFACE_MAP_BEGIN(CommandEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMCommandEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMPL_ADDREF_INHERITED(CommandEvent, Event) +NS_IMPL_RELEASE_INHERITED(CommandEvent, Event) + +NS_IMETHODIMP +CommandEvent::GetCommand(nsAString& aCommand) +{ + nsIAtom* command = mEvent->AsCommandEvent()->mCommand; + if (command) { + command->ToString(aCommand); + } else { + aCommand.Truncate(); + } + return NS_OK; +} + +NS_IMETHODIMP +CommandEvent::InitCommandEvent(const nsAString& aTypeArg, + bool aCanBubbleArg, + bool aCancelableArg, + const nsAString& aCommand) +{ + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + + Event::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg); + + mEvent->AsCommandEvent()->mCommand = NS_Atomize(aCommand); + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<CommandEvent> +NS_NewDOMCommandEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetCommandEvent* aEvent) +{ + RefPtr<CommandEvent> it = + new CommandEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/CommandEvent.h b/dom/events/CommandEvent.h new file mode 100644 index 000000000..cb21c1d49 --- /dev/null +++ b/dom/events/CommandEvent.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_CommandEvent_h_ +#define mozilla_dom_CommandEvent_h_ + +#include "mozilla/EventForwards.h" +#include "mozilla/dom/CommandEventBinding.h" +#include "mozilla/dom/Event.h" +#include "nsIDOMCommandEvent.h" + +namespace mozilla { +namespace dom { + +class CommandEvent : public Event, + public nsIDOMCommandEvent +{ +public: + CommandEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetCommandEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIDOMCOMMANDEVENT + + // Forward to base class + NS_FORWARD_TO_EVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return CommandEventBinding::Wrap(aCx, this, aGivenProto); + } + + void InitCommandEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + const nsAString& aCommand, + ErrorResult& aRv) + { + aRv = InitCommandEvent(aType, aCanBubble, aCancelable, aCommand); + } + +protected: + ~CommandEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::CommandEvent> +NS_NewDOMCommandEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetCommandEvent* aEvent); + +#endif // mozilla_dom_CommandEvent_h_ diff --git a/dom/events/CompositionEvent.cpp b/dom/events/CompositionEvent.cpp new file mode 100644 index 000000000..1f3112e70 --- /dev/null +++ b/dom/events/CompositionEvent.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/CompositionEvent.h" +#include "mozilla/TextEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +CompositionEvent::CompositionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetCompositionEvent* aEvent) + : UIEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetCompositionEvent(false, eVoidEvent, nullptr)) +{ + NS_ASSERTION(mEvent->mClass == eCompositionEventClass, + "event type mismatch"); + + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + + // XXX compositionstart is cancelable in draft of DOM3 Events. + // However, it doesn't make sence for us, we cannot cancel composition + // when we sends compositionstart event. + mEvent->mFlags.mCancelable = false; + } + + // XXX Do we really need to duplicate the data value? + mData = mEvent->AsCompositionEvent()->mData; + // TODO: Native event should have locale information. +} + +NS_IMPL_ADDREF_INHERITED(CompositionEvent, UIEvent) +NS_IMPL_RELEASE_INHERITED(CompositionEvent, UIEvent) + +NS_INTERFACE_MAP_BEGIN(CompositionEvent) +NS_INTERFACE_MAP_END_INHERITING(UIEvent) + +void +CompositionEvent::GetData(nsAString& aData) const +{ + aData = mData; +} + +void +CompositionEvent::GetLocale(nsAString& aLocale) const +{ + aLocale = mLocale; +} + +void +CompositionEvent::InitCompositionEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + const nsAString& aData, + const nsAString& aLocale) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0); + mData = aData; + mLocale = aLocale; +} + +void +CompositionEvent::GetRanges(TextClauseArray& aRanges) +{ + // If the mRanges is not empty, we return the cached value. + if (!mRanges.IsEmpty()) { + aRanges = mRanges; + return; + } + RefPtr<TextRangeArray> textRangeArray = mEvent->AsCompositionEvent()->mRanges; + if (!textRangeArray) { + return; + } + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mOwner); + const TextRange* targetRange = textRangeArray->GetTargetClause(); + for (size_t i = 0; i < textRangeArray->Length(); i++) { + const TextRange& range = textRangeArray->ElementAt(i); + mRanges.AppendElement(new TextClause(window, range, targetRange)); + } + aRanges = mRanges; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<CompositionEvent> +NS_NewDOMCompositionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetCompositionEvent* aEvent) +{ + RefPtr<CompositionEvent> event = + new CompositionEvent(aOwner, aPresContext, aEvent); + return event.forget(); +} diff --git a/dom/events/CompositionEvent.h b/dom/events/CompositionEvent.h new file mode 100644 index 000000000..ed2316caa --- /dev/null +++ b/dom/events/CompositionEvent.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_CompositionEvent_h_ +#define mozilla_dom_CompositionEvent_h_ + +#include "mozilla/dom/CompositionEventBinding.h" +#include "mozilla/dom/TextClause.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/UIEvent.h" +#include "mozilla/EventForwards.h" + +namespace mozilla { +namespace dom { + +typedef nsTArray<RefPtr<TextClause>> TextClauseArray; + +class CompositionEvent : public UIEvent +{ +public: + CompositionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetCompositionEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_TO_UIEVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return CompositionEventBinding::Wrap(aCx, this, aGivenProto); + } + + void InitCompositionEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + const nsAString& aData, + const nsAString& aLocale); + void GetData(nsAString&) const; + void GetLocale(nsAString&) const; + void GetRanges(TextClauseArray& aRanges); + +protected: + ~CompositionEvent() {} + + nsString mData; + nsString mLocale; + TextClauseArray mRanges; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::CompositionEvent> +NS_NewDOMCompositionEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetCompositionEvent* aEvent); + +#endif // mozilla_dom_CompositionEvent_h_ diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp new file mode 100644 index 000000000..935ade23f --- /dev/null +++ b/dom/events/ContentEventHandler.cpp @@ -0,0 +1,3080 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContentEventHandler.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLUnknownElement.h" +#include "mozilla/dom/Selection.h" +#include "nsCaret.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsCopySupport.h" +#include "nsFocusManager.h" +#include "nsFontMetrics.h" +#include "nsFrameSelection.h" +#include "nsIContentIterator.h" +#include "nsIPresShell.h" +#include "nsISelection.h" +#include "nsIFrame.h" +#include "nsIObjectFrame.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsQueryObject.h" +#include "nsRange.h" +#include "nsTextFragment.h" +#include "nsTextFrame.h" +#include "nsView.h" + +#include <algorithm> + +namespace mozilla { + +using namespace dom; +using namespace widget; + +/******************************************************************/ +/* ContentEventHandler */ +/******************************************************************/ + +// NOTE +// +// ContentEventHandler *creates* ranges as following rules: +// 1. Start of range: +// 1.1. Cases: [textNode or text[Node or textNode[ +// When text node is start of a range, start node is the text node and +// start offset is any number between 0 and the length of the text. +// 1.2. Case: [<element>: +// When start of an element node is start of a range, start node is +// parent of the element and start offset is the element's index in the +// parent. +// 1.3. Case: <element/>[ +// When after an empty element node is start of a range, start node is +// parent of the element and start offset is the element's index in the +// parent + 1. +// 1.4. Case: <element>[ +// When start of a non-empty element is start of a range, start node is +// the element and start offset is 0. +// 1.5. Case: <root>[ +// When start of a range is 0 and there are no nodes causing text, +// start node is the root node and start offset is 0. +// 1.6. Case: [</root> +// When start of a range is out of bounds, start node is the root node +// and start offset is number of the children. +// 2. End of range: +// 2.1. Cases: ]textNode or text]Node or textNode] +// When a text node is end of a range, end node is the text node and +// end offset is any number between 0 and the length of the text. +// 2.2. Case: ]<element> +// When before an element node (meaning before the open tag of the +// element) is end of a range, end node is previous node causing text. +// Note that this case shouldn't be handled directly. If rule 2.1 and +// 2.3 are handled correctly, the loop with nsContentIterator shouldn't +// reach the element node since the loop should've finished already at +// handling the last node which caused some text. +// 2.3. Case: <element>] +// When a line break is caused before a non-empty element node and it's +// end of a range, end node is the element and end offset is 0. +// (i.e., including open tag of the element) +// 2.4. Cases: <element/>] +// When after an empty element node is end of a range, end node is +// parent of the element node and end offset is the element's index in +// the parent + 1. (i.e., including close tag of the element or empty +// element) +// 2.5. Case: ]</root> +// When end of a range is out of bounds, end node is the root node and +// end offset is number of the children. +// +// ContentEventHandler *treats* ranges as following additional rules: +// 1. When the start node is an element node which doesn't have children, +// it includes a line break caused before itself (i.e., includes its open +// tag). For example, if start position is { <br>, 0 }, the line break +// caused by <br> should be included into the flatten text. +// 2. When the end node is an element node which doesn't have children, +// it includes the end (i.e., includes its close tag except empty element). +// Although, currently, any close tags don't cause line break, this also +// includes its open tag. For example, if end position is { <br>, 0 }, the +// line break caused by the <br> should be included into the flatten text. + +ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext) + : mPresContext(aPresContext) + , mPresShell(aPresContext->GetPresShell()) + , mSelection(nullptr) + , mFirstSelectedRange(nullptr) + , mRootContent(nullptr) +{ +} + +nsresult +ContentEventHandler::InitBasic() +{ + NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE); + + // If text frame which has overflowing selection underline is dirty, + // we need to flush the pending reflow here. + mPresShell->FlushPendingNotifications(Flush_Layout); + + // Flushing notifications can cause mPresShell to be destroyed (bug 577963). + NS_ENSURE_TRUE(!mPresShell->IsDestroying(), NS_ERROR_FAILURE); + + return NS_OK; +} + +nsresult +ContentEventHandler::InitRootContent(Selection* aNormalSelection) +{ + MOZ_ASSERT(aNormalSelection); + + // Root content should be computed with normal selection because normal + // selection is typically has at least one range but the other selections + // not so. If there is a range, computing its root is easy, but if + // there are no ranges, we need to use ancestor limit instead. + MOZ_ASSERT(aNormalSelection->Type() == SelectionType::eNormal); + + if (!aNormalSelection->RangeCount()) { + // If there is no selection range, we should compute the selection root + // from ancestor limiter or root content of the document. + nsresult rv = + aNormalSelection->GetAncestorLimiter(getter_AddRefs(mRootContent)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + if (!mRootContent) { + mRootContent = mPresShell->GetDocument()->GetRootElement(); + if (NS_WARN_IF(!mRootContent)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + return NS_OK; + } + + RefPtr<nsRange> range(aNormalSelection->GetRangeAt(0)); + if (NS_WARN_IF(!range)) { + return NS_ERROR_UNEXPECTED; + } + + // If there is a selection, we should retrieve the selection root from + // the range since when the window is inactivated, the ancestor limiter + // of selection was cleared by blur event handler of EditorBase but the + // selection range still keeps storing the nodes. If the active element of + // the deactive window is <input> or <textarea>, we can compute the + // selection root from them. + nsINode* startNode = range->GetStartParent(); + nsINode* endNode = range->GetEndParent(); + if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) { + return NS_ERROR_FAILURE; + } + + // See bug 537041 comment 5, the range could have removed node. + if (NS_WARN_IF(startNode->GetUncomposedDoc() != mPresShell->GetDocument())) { + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(startNode->GetUncomposedDoc() == endNode->GetUncomposedDoc(), + "firstNormalSelectionRange crosses the document boundary"); + + mRootContent = startNode->GetSelectionRootContent(mPresShell); + if (NS_WARN_IF(!mRootContent)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +ContentEventHandler::InitCommon(SelectionType aSelectionType) +{ + if (mSelection && mSelection->Type() == aSelectionType) { + return NS_OK; + } + + mSelection = nullptr; + mFirstSelectedRange = nullptr; + mRootContent = nullptr; + + nsresult rv = InitBasic(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISelectionController> selectionController = + mPresShell->GetSelectionControllerForFocusedContent(); + if (NS_WARN_IF(!selectionController)) { + return NS_ERROR_NOT_AVAILABLE; + } + nsCOMPtr<nsISelection> selection; + rv = selectionController->GetSelection(ToRawSelectionType(aSelectionType), + getter_AddRefs(selection)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + + mSelection = static_cast<Selection*>(selection.get()); + if (NS_WARN_IF(!mSelection)) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<Selection> normalSelection; + if (mSelection->Type() == SelectionType::eNormal) { + normalSelection = mSelection; + } else { + nsCOMPtr<nsISelection> domSelection; + nsresult rv = + selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + if (NS_WARN_IF(!domSelection)) { + return NS_ERROR_NOT_AVAILABLE; + } + normalSelection = domSelection->AsSelection(); + if (NS_WARN_IF(!normalSelection)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + rv = InitRootContent(normalSelection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mSelection->RangeCount()) { + mFirstSelectedRange = mSelection->GetRangeAt(0); + if (NS_WARN_IF(!mFirstSelectedRange)) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + } + + // Even if there are no selection ranges, it's usual case if aSelectionType + // is a special selection. + if (aSelectionType != SelectionType::eNormal) { + MOZ_ASSERT(!mFirstSelectedRange); + return NS_OK; + } + + // But otherwise, we need to assume that there is a selection range at the + // beginning of the root content if aSelectionType is eNormal. + rv = nsRange::CreateRange(mRootContent, 0, mRootContent, 0, + getter_AddRefs(mFirstSelectedRange)); + if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!mFirstSelectedRange)) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult +ContentEventHandler::Init(WidgetQueryContentEvent* aEvent) +{ + NS_ASSERTION(aEvent, "aEvent must not be null"); + MOZ_ASSERT(aEvent->mMessage == eQuerySelectedText || + aEvent->mInput.mSelectionType == SelectionType::eNormal); + + if (NS_WARN_IF(!aEvent->mInput.IsValidOffset()) || + NS_WARN_IF(!aEvent->mInput.IsValidEventMessage(aEvent->mMessage))) { + return NS_ERROR_FAILURE; + } + + // Note that we should ignore WidgetQueryContentEvent::Input::mSelectionType + // if the event isn't eQuerySelectedText. + SelectionType selectionType = + aEvent->mMessage == eQuerySelectedText ? aEvent->mInput.mSelectionType : + SelectionType::eNormal; + if (NS_WARN_IF(selectionType == SelectionType::eNone)) { + return NS_ERROR_FAILURE; + } + + nsresult rv = InitCommon(selectionType); + NS_ENSURE_SUCCESS(rv, rv); + + // Be aware, WidgetQueryContentEvent::mInput::mOffset should be made absolute + // offset before sending it to ContentEventHandler because querying selection + // every time may be expensive. So, if the caller caches selection, it + // should initialize the event with the cached value. + if (aEvent->mInput.mRelativeToInsertionPoint) { + MOZ_ASSERT(selectionType == SelectionType::eNormal); + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(aEvent->mWidget); + if (composition) { + uint32_t compositionStart = composition->NativeOffsetOfStartComposition(); + if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) { + return NS_ERROR_FAILURE; + } + } else { + LineBreakType lineBreakType = GetLineBreakType(aEvent); + uint32_t selectionStart = 0; + rv = GetStartOffset(mFirstSelectedRange, &selectionStart, lineBreakType); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) { + return NS_ERROR_FAILURE; + } + } + } + + aEvent->mSucceeded = false; + + aEvent->mReply.mContentsRoot = mRootContent.get(); + + aEvent->mReply.mHasSelection = !mSelection->IsCollapsed(); + + nsRect r; + nsIFrame* frame = nsCaret::GetGeometry(mSelection, &r); + if (!frame) { + frame = mRootContent->GetPrimaryFrame(); + if (NS_WARN_IF(!frame)) { + return NS_ERROR_FAILURE; + } + } + aEvent->mReply.mFocusedWidget = frame->GetNearestWidget(); + + return NS_OK; +} + +nsresult +ContentEventHandler::Init(WidgetSelectionEvent* aEvent) +{ + NS_ASSERTION(aEvent, "aEvent must not be null"); + + nsresult rv = InitCommon(); + NS_ENSURE_SUCCESS(rv, rv); + + aEvent->mSucceeded = false; + + return NS_OK; +} + +nsIContent* +ContentEventHandler::GetFocusedContent() +{ + nsIDocument* doc = mPresShell->GetDocument(); + if (!doc) { + return nullptr; + } + nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow(); + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + return nsFocusManager::GetFocusedDescendant(window, true, + getter_AddRefs(focusedWindow)); +} + +bool +ContentEventHandler::IsPlugin(nsIContent* aContent) +{ + return aContent && + aContent->GetDesiredIMEState().mEnabled == IMEState::PLUGIN; +} + +nsresult +ContentEventHandler::QueryContentRect(nsIContent* aContent, + WidgetQueryContentEvent* aEvent) +{ + NS_PRECONDITION(aContent, "aContent must not be null"); + + nsIFrame* frame = aContent->GetPrimaryFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + // get rect for first frame + nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size()); + nsresult rv = ConvertToRootRelativeOffset(frame, resultRect); + NS_ENSURE_SUCCESS(rv, rv); + + // account for any additional frames + while ((frame = frame->GetNextContinuation()) != nullptr) { + nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size()); + rv = ConvertToRootRelativeOffset(frame, frameRect); + NS_ENSURE_SUCCESS(rv, rv); + resultRect.UnionRect(resultRect, frameRect); + } + + aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect( + resultRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel())); + // Returning empty rect may cause native IME confused, let's make sure to + // return non-empty rect. + EnsureNonEmptyRect(aEvent->mReply.mRect); + aEvent->mSucceeded = true; + + return NS_OK; +} + +// Editor places a bogus BR node under its root content if the editor doesn't +// have any text. This happens even for single line editors. +// When we get text content and when we change the selection, +// we don't want to include the bogus BRs at the end. +static bool IsContentBR(nsIContent* aContent) +{ + return aContent->IsHTMLElement(nsGkAtoms::br) && + !aContent->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::type, + nsGkAtoms::moz, + eIgnoreCase) && + !aContent->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::mozeditorbogusnode, + nsGkAtoms::_true, + eIgnoreCase); +} + +static bool IsMozBR(nsIContent* aContent) +{ + return aContent->IsHTMLElement(nsGkAtoms::br) && !IsContentBR(aContent); +} + +static void ConvertToNativeNewlines(nsAFlatString& aString) +{ +#if defined(XP_WIN) + aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r\n")); +#endif +} + +static void AppendString(nsAString& aString, nsIContent* aContent) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "aContent is not a text node!"); + const nsTextFragment* text = aContent->GetText(); + if (!text) { + return; + } + text->AppendTo(aString); +} + +static void AppendSubString(nsAString& aString, nsIContent* aContent, + uint32_t aXPOffset, uint32_t aXPLength) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "aContent is not a text node!"); + const nsTextFragment* text = aContent->GetText(); + if (!text) { + return; + } + text->AppendTo(aString, int32_t(aXPOffset), int32_t(aXPLength)); +} + +#if defined(XP_WIN) +static uint32_t CountNewlinesInXPLength(nsIContent* aContent, + uint32_t aXPLength) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "aContent is not a text node!"); + const nsTextFragment* text = aContent->GetText(); + if (!text) { + return 0; + } + // For automated tests, we should abort on debug build. + MOZ_ASSERT(aXPLength == UINT32_MAX || aXPLength <= text->GetLength(), + "aXPLength is out-of-bounds"); + const uint32_t length = std::min(aXPLength, text->GetLength()); + uint32_t newlines = 0; + for (uint32_t i = 0; i < length; ++i) { + if (text->CharAt(i) == '\n') { + ++newlines; + } + } + return newlines; +} + +static uint32_t CountNewlinesInNativeLength(nsIContent* aContent, + uint32_t aNativeLength) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "aContent is not a text node!"); + const nsTextFragment* text = aContent->GetText(); + if (!text) { + return 0; + } + // For automated tests, we should abort on debug build. + MOZ_ASSERT( + (aNativeLength == UINT32_MAX || aNativeLength <= text->GetLength() * 2), + "aNativeLength is unexpected value"); + const uint32_t xpLength = text->GetLength(); + uint32_t newlines = 0; + for (uint32_t i = 0, nativeOffset = 0; + i < xpLength && nativeOffset < aNativeLength; + ++i, ++nativeOffset) { + // For automated tests, we should abort on debug build. + MOZ_ASSERT(i < text->GetLength(), "i is out-of-bounds"); + if (text->CharAt(i) == '\n') { + ++newlines; + ++nativeOffset; + } + } + return newlines; +} +#endif + +/* static */ uint32_t +ContentEventHandler::GetNativeTextLength(nsIContent* aContent, + uint32_t aStartOffset, + uint32_t aEndOffset) +{ + MOZ_ASSERT(aEndOffset >= aStartOffset, + "aEndOffset must be equals or larger than aStartOffset"); + if (NS_WARN_IF(!aContent->IsNodeOfType(nsINode::eTEXT))) { + return 0; + } + if (aStartOffset == aEndOffset) { + return 0; + } + return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aEndOffset) - + GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aStartOffset); +} + +/* static */ uint32_t +ContentEventHandler::GetNativeTextLength(nsIContent* aContent, + uint32_t aMaxLength) +{ + if (NS_WARN_IF(!aContent->IsNodeOfType(nsINode::eTEXT))) { + return 0; + } + return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aMaxLength); +} + +/* static */ uint32_t +ContentEventHandler::GetNativeTextLengthBefore(nsIContent* aContent, + nsINode* aRootNode) +{ + if (NS_WARN_IF(aContent->IsNodeOfType(nsINode::eTEXT))) { + return 0; + } + return ShouldBreakLineBefore(aContent, aRootNode) ? + GetBRLength(LINE_BREAK_TYPE_NATIVE) : 0; +} + +/* static inline */ uint32_t +ContentEventHandler::GetBRLength(LineBreakType aLineBreakType) +{ +#if defined(XP_WIN) + // Length of \r\n + return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1; +#else + return 1; +#endif +} + +/* static */ uint32_t +ContentEventHandler::GetTextLength(nsIContent* aContent, + LineBreakType aLineBreakType, + uint32_t aMaxLength) +{ + MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eTEXT)); + + uint32_t textLengthDifference = +#if defined(XP_WIN) + // On Windows, the length of a native newline ("\r\n") is twice the length + // of the XP newline ("\n"), so XP length is equal to the length of the + // native offset plus the number of newlines encountered in the string. + (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? + CountNewlinesInXPLength(aContent, aMaxLength) : 0; +#else + // On other platforms, the native and XP newlines are the same. + 0; +#endif + + const nsTextFragment* text = aContent->GetText(); + if (!text) { + return 0; + } + uint32_t length = std::min(text->GetLength(), aMaxLength); + return length + textLengthDifference; +} + +static uint32_t ConvertToXPOffset(nsIContent* aContent, uint32_t aNativeOffset) +{ +#if defined(XP_WIN) + // On Windows, the length of a native newline ("\r\n") is twice the length of + // the XP newline ("\n"), so XP offset is equal to the length of the native + // offset minus the number of newlines encountered in the string. + return aNativeOffset - CountNewlinesInNativeLength(aContent, aNativeOffset); +#else + // On other platforms, the native and XP newlines are the same. + return aNativeOffset; +#endif +} + +/* static */ bool +ContentEventHandler::ShouldBreakLineBefore(nsIContent* aContent, + nsINode* aRootNode) +{ + // We don't need to append linebreak at the start of the root element. + if (aContent == aRootNode) { + return false; + } + + // If it's not an HTML element (including other markup language's elements), + // we shouldn't insert like break before that for now. Becoming this is a + // problem must be edge case. E.g., when ContentEventHandler is used with + // MathML or SVG elements. + if (!aContent->IsHTMLElement()) { + return false; + } + + // If the element is <br>, we need to check if the <br> is caused by web + // content. Otherwise, i.e., it's caused by internal reason of Gecko, + // it shouldn't be exposed as a line break to flatten text. + if (aContent->IsHTMLElement(nsGkAtoms::br)) { + return IsContentBR(aContent); + } + + // Note that ideally, we should refer the style of the primary frame of + // aContent for deciding if it's an inline. However, it's difficult + // IMEContentObserver to notify IME of text change caused by style change. + // Therefore, currently, we should check only from the tag for now. + if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a, + nsGkAtoms::abbr, + nsGkAtoms::acronym, + nsGkAtoms::b, + nsGkAtoms::bdi, + nsGkAtoms::bdo, + nsGkAtoms::big, + nsGkAtoms::cite, + nsGkAtoms::code, + nsGkAtoms::data, + nsGkAtoms::del, + nsGkAtoms::dfn, + nsGkAtoms::em, + nsGkAtoms::font, + nsGkAtoms::i, + nsGkAtoms::ins, + nsGkAtoms::kbd, + nsGkAtoms::mark, + nsGkAtoms::s, + nsGkAtoms::samp, + nsGkAtoms::small, + nsGkAtoms::span, + nsGkAtoms::strike, + nsGkAtoms::strong, + nsGkAtoms::sub, + nsGkAtoms::sup, + nsGkAtoms::time, + nsGkAtoms::tt, + nsGkAtoms::u, + nsGkAtoms::var)) { + return false; + } + + // If the element is unknown element, we shouldn't insert line breaks before + // it since unknown elements should be ignored. + RefPtr<HTMLUnknownElement> unknownHTMLElement = do_QueryObject(aContent); + return !unknownHTMLElement; +} + +nsresult +ContentEventHandler::GenerateFlatTextContent(nsIContent* aContent, + nsAFlatString& aString, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aString.IsEmpty()); + + RefPtr<nsRange> range = new nsRange(mRootContent); + ErrorResult rv; + range->SelectNodeContents(*aContent, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + return GenerateFlatTextContent(range, aString, aLineBreakType); +} + +nsresult +ContentEventHandler::GenerateFlatTextContent(nsRange* aRange, + nsAFlatString& aString, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aString.IsEmpty()); + + if (aRange->Collapsed()) { + return NS_OK; + } + + nsINode* startNode = aRange->GetStartParent(); + nsINode* endNode = aRange->GetEndParent(); + if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) { + return NS_ERROR_FAILURE; + } + + if (startNode == endNode && startNode->IsNodeOfType(nsINode::eTEXT)) { + nsIContent* content = startNode->AsContent(); + AppendSubString(aString, content, aRange->StartOffset(), + aRange->EndOffset() - aRange->StartOffset()); + ConvertToNativeNewlines(aString); + return NS_OK; + } + + nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator(); + nsresult rv = iter->Init(aRange); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + for (; !iter->IsDone(); iter->Next()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + if (!node->IsContent()) { + continue; + } + nsIContent* content = node->AsContent(); + + if (content->IsNodeOfType(nsINode::eTEXT)) { + if (content == startNode) { + AppendSubString(aString, content, aRange->StartOffset(), + content->TextLength() - aRange->StartOffset()); + } else if (content == endNode) { + AppendSubString(aString, content, 0, aRange->EndOffset()); + } else { + AppendString(aString, content); + } + } else if (ShouldBreakLineBefore(content, mRootContent)) { + aString.Append(char16_t('\n')); + } + } + if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) { + ConvertToNativeNewlines(aString); + } + return NS_OK; +} + +static FontRange* +AppendFontRange(nsTArray<FontRange>& aFontRanges, uint32_t aBaseOffset) +{ + FontRange* fontRange = aFontRanges.AppendElement(); + fontRange->mStartOffset = aBaseOffset; + return fontRange; +} + +/* static */ uint32_t +ContentEventHandler::GetTextLengthInRange(nsIContent* aContent, + uint32_t aXPStartOffset, + uint32_t aXPEndOffset, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eTEXT)); + + return aLineBreakType == LINE_BREAK_TYPE_NATIVE ? + GetNativeTextLength(aContent, aXPStartOffset, aXPEndOffset) : + aXPEndOffset - aXPStartOffset; +} + +/* static */ void +ContentEventHandler::AppendFontRanges(FontRangeArray& aFontRanges, + nsIContent* aContent, + int32_t aBaseOffset, + int32_t aXPStartOffset, + int32_t aXPEndOffset, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eTEXT)); + + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (!frame) { + // It is a non-rendered content, create an empty range for it. + AppendFontRange(aFontRanges, aBaseOffset); + return; + } + + int32_t baseOffset = aBaseOffset; + nsTextFrame* curr = do_QueryFrame(frame); + MOZ_ASSERT(curr, "Not a text frame"); + while (curr) { + int32_t frameXPStart = std::max(curr->GetContentOffset(), aXPStartOffset); + int32_t frameXPEnd = std::min(curr->GetContentEnd(), aXPEndOffset); + if (frameXPStart >= frameXPEnd) { + curr = static_cast<nsTextFrame*>(curr->GetNextContinuation()); + continue; + } + + gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated); + + nsTextFrame* next = nullptr; + if (frameXPEnd < aXPEndOffset) { + next = static_cast<nsTextFrame*>(curr->GetNextContinuation()); + while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) { + frameXPEnd = std::min(next->GetContentEnd(), aXPEndOffset); + next = frameXPEnd < aXPEndOffset ? + static_cast<nsTextFrame*>(next->GetNextContinuation()) : nullptr; + } + } + + gfxTextRun::Range skipRange(iter.ConvertOriginalToSkipped(frameXPStart), + iter.ConvertOriginalToSkipped(frameXPEnd)); + gfxTextRun::GlyphRunIterator runIter(textRun, skipRange); + int32_t lastXPEndOffset = frameXPStart; + while (runIter.NextRun()) { + gfxFont* font = runIter.GetGlyphRun()->mFont.get(); + int32_t startXPOffset = + iter.ConvertSkippedToOriginal(runIter.GetStringStart()); + // It is possible that the first glyph run has exceeded the frame, + // because the whole frame is filled by skipped chars. + if (startXPOffset >= frameXPEnd) { + break; + } + + if (startXPOffset > lastXPEndOffset) { + // Create range for skipped leading chars. + AppendFontRange(aFontRanges, baseOffset); + baseOffset += GetTextLengthInRange( + aContent, lastXPEndOffset, startXPOffset, aLineBreakType); + lastXPEndOffset = startXPOffset; + } + + FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset); + fontRange->mFontName = font->GetName(); + fontRange->mFontSize = font->GetAdjustedSize(); + + // The converted original offset may exceed the range, + // hence we need to clamp it. + int32_t endXPOffset = + iter.ConvertSkippedToOriginal(runIter.GetStringEnd()); + endXPOffset = std::min(frameXPEnd, endXPOffset); + baseOffset += GetTextLengthInRange(aContent, startXPOffset, endXPOffset, + aLineBreakType); + lastXPEndOffset = endXPOffset; + } + if (lastXPEndOffset < frameXPEnd) { + // Create range for skipped trailing chars. It also handles case + // that the whole frame contains only skipped chars. + AppendFontRange(aFontRanges, baseOffset); + baseOffset += GetTextLengthInRange( + aContent, lastXPEndOffset, frameXPEnd, aLineBreakType); + } + + curr = next; + } +} + +nsresult +ContentEventHandler::GenerateFlatFontRanges(nsRange* aRange, + FontRangeArray& aFontRanges, + uint32_t& aLength, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array"); + + if (aRange->Collapsed()) { + return NS_OK; + } + + nsINode* startNode = aRange->GetStartParent(); + nsINode* endNode = aRange->GetEndParent(); + if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) { + return NS_ERROR_FAILURE; + } + + // baseOffset is the flattened offset of each content node. + int32_t baseOffset = 0; + nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator(); + nsresult rv = iter->Init(aRange); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + for (; !iter->IsDone(); iter->Next()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + if (!node->IsContent()) { + continue; + } + nsIContent* content = node->AsContent(); + + if (content->IsNodeOfType(nsINode::eTEXT)) { + int32_t startOffset = content != startNode ? 0 : aRange->StartOffset(); + int32_t endOffset = content != endNode ? + content->TextLength() : aRange->EndOffset(); + AppendFontRanges(aFontRanges, content, baseOffset, + startOffset, endOffset, aLineBreakType); + baseOffset += GetTextLengthInRange(content, startOffset, endOffset, + aLineBreakType); + } else if (ShouldBreakLineBefore(content, mRootContent)) { + if (aFontRanges.IsEmpty()) { + MOZ_ASSERT(baseOffset == 0); + FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset); + nsIFrame* frame = content->GetPrimaryFrame(); + if (frame) { + const nsFont& font = frame->GetParent()->StyleFont()->mFont; + const FontFamilyList& fontList = font.fontlist; + const FontFamilyName& fontName = fontList.IsEmpty() ? + FontFamilyName(fontList.GetDefaultFontType()) : + fontList.GetFontlist()[0]; + fontName.AppendToString(fontRange->mFontName, false); + fontRange->mFontSize = + frame->PresContext()->AppUnitsToDevPixels(font.size); + } + } + baseOffset += GetBRLength(aLineBreakType); + } + } + + aLength = baseOffset; + return NS_OK; +} + +nsresult +ContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent, + bool aForward, + uint32_t* aXPOffset) +{ + // XXX This method assumes that the frame boundaries must be cluster + // boundaries. It's false, but no problem now, maybe. + if (!aContent->IsNodeOfType(nsINode::eTEXT) || + *aXPOffset == 0 || *aXPOffset == aContent->TextLength()) { + return NS_OK; + } + + NS_ASSERTION(*aXPOffset <= aContent->TextLength(), + "offset is out of range."); + + RefPtr<nsFrameSelection> fs = mPresShell->FrameSelection(); + int32_t offsetInFrame; + CaretAssociationHint hint = + aForward ? CARET_ASSOCIATE_BEFORE : CARET_ASSOCIATE_AFTER; + nsIFrame* frame = fs->GetFrameForNodeOffset(aContent, int32_t(*aXPOffset), + hint, &offsetInFrame); + if (!frame) { + // This content doesn't have any frames, we only can check surrogate pair... + const nsTextFragment* text = aContent->GetText(); + NS_ENSURE_TRUE(text, NS_ERROR_FAILURE); + if (NS_IS_LOW_SURROGATE(text->CharAt(*aXPOffset)) && + NS_IS_HIGH_SURROGATE(text->CharAt(*aXPOffset - 1))) { + *aXPOffset += aForward ? 1 : -1; + } + return NS_OK; + } + int32_t startOffset, endOffset; + nsresult rv = frame->GetOffsets(startOffset, endOffset); + NS_ENSURE_SUCCESS(rv, rv); + if (*aXPOffset == static_cast<uint32_t>(startOffset) || + *aXPOffset == static_cast<uint32_t>(endOffset)) { + return NS_OK; + } + if (frame->GetType() != nsGkAtoms::textFrame) { + return NS_ERROR_FAILURE; + } + nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); + int32_t newOffsetInFrame = *aXPOffset - startOffset; + newOffsetInFrame += aForward ? -1 : 1; + textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame); + *aXPOffset = startOffset + newOffsetInFrame; + return NS_OK; +} + +nsresult +ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange, + uint32_t aOffset, + uint32_t aLength, + LineBreakType aLineBreakType, + bool aExpandToClusterBoundaries, + uint32_t* aNewOffset, + nsIContent** aLastTextNode) +{ + if (aNewOffset) { + *aNewOffset = aOffset; + } + if (aLastTextNode) { + *aLastTextNode = nullptr; + } + + // 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); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator(); + nsresult rv = iter->Init(mRootContent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint32_t offset = 0; + uint32_t endOffset = aOffset + aLength; + bool startSet = false; + for (; !iter->IsDone(); iter->Next()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + // FYI: mRootContent shouldn't cause any text. So, we can skip it simply. + if (node == mRootContent || !node->IsContent()) { + continue; + } + nsIContent* content = node->AsContent(); + + if (aLastTextNode && content->IsNodeOfType(nsINode::eTEXT)) { + NS_IF_RELEASE(*aLastTextNode); + NS_ADDREF(*aLastTextNode = content); + } + + uint32_t textLength = + content->IsNodeOfType(nsINode::eTEXT) ? + GetTextLength(content, aLineBreakType) : + (ShouldBreakLineBefore(content, mRootContent) ? + GetBRLength(aLineBreakType) : 0); + if (!textLength) { + continue; + } + + // When the start offset is in between accumulated offset and the last + // offset of the node, the node is the start node of the range. + if (!startSet && aOffset <= offset + textLength) { + nsINode* startNode = nullptr; + int32_t startNodeOffset = -1; + if (content->IsNodeOfType(nsINode::eTEXT)) { + // Rule #1.1: [textNode or text[Node or textNode[ + uint32_t xpOffset = aOffset - offset; + if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) { + xpOffset = ConvertToXPOffset(content, xpOffset); + } + + if (aExpandToClusterBoundaries) { + uint32_t oldXPOffset = xpOffset; + rv = ExpandToClusterBoundary(content, false, &xpOffset); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (aNewOffset) { + // This is correct since a cluster shouldn't include line break. + *aNewOffset -= (oldXPOffset - xpOffset); + } + } + startNode = content; + startNodeOffset = static_cast<int32_t>(xpOffset); + } else if (aOffset < offset + textLength) { + // Rule #1.2 [<element> + startNode = content->GetParent(); + if (NS_WARN_IF(!startNode)) { + return NS_ERROR_FAILURE; + } + startNodeOffset = startNode->IndexOf(content); + if (NS_WARN_IF(startNodeOffset == -1)) { + // The content is being removed from the parent! + return NS_ERROR_FAILURE; + } + } else if (!content->HasChildren()) { + // Rule #1.3: <element/>[ + startNode = content->GetParent(); + if (NS_WARN_IF(!startNode)) { + return NS_ERROR_FAILURE; + } + startNodeOffset = startNode->IndexOf(content) + 1; + if (NS_WARN_IF(startNodeOffset == 0)) { + // The content is being removed from the parent! + return NS_ERROR_FAILURE; + } + } else { + // Rule #1.4: <element>[ + startNode = content; + startNodeOffset = 0; + } + NS_ASSERTION(startNode, "startNode must not be nullptr"); + NS_ASSERTION(startNodeOffset >= 0, + "startNodeOffset must not be negative"); + rv = aRange->SetStart(startNode, startNodeOffset); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + startSet = true; + + if (!aLength) { + rv = aRange->SetEnd(startNode, startNodeOffset); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + } + + // When the end offset is in the content, the node is the end node of the + // range. + if (endOffset <= offset + textLength) { + MOZ_ASSERT(startSet, + "The start of the range should've been set already"); + if (content->IsNodeOfType(nsINode::eTEXT)) { + // Rule #2.1: ]textNode or text]Node or textNode] + uint32_t xpOffset = endOffset - offset; + if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) { + uint32_t xpOffsetCurrent = ConvertToXPOffset(content, xpOffset); + if (xpOffset && GetBRLength(aLineBreakType) > 1) { + MOZ_ASSERT(GetBRLength(aLineBreakType) == 2); + uint32_t xpOffsetPre = ConvertToXPOffset(content, xpOffset - 1); + // If previous character's XP offset is same as current character's, + // it means that the end offset is between \r and \n. So, the + // range end should be after the \n. + if (xpOffsetPre == xpOffsetCurrent) { + xpOffset = xpOffsetCurrent + 1; + } else { + xpOffset = xpOffsetCurrent; + } + } + } + if (aExpandToClusterBoundaries) { + rv = ExpandToClusterBoundary(content, true, &xpOffset); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + NS_ASSERTION(xpOffset <= INT32_MAX, + "The end node offset is too large"); + rv = aRange->SetEnd(content, static_cast<int32_t>(xpOffset)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + if (endOffset == offset) { + // Rule #2.2: ]<element> + // NOTE: Please don't crash on release builds because it must be + // overreaction but we shouldn't allow this bug when some + // automated tests find this. + MOZ_ASSERT(false, "This case should've already been handled at " + "the last node which caused some text"); + return NS_ERROR_FAILURE; + } + + if (content->HasChildren() && + ShouldBreakLineBefore(content, mRootContent)) { + // Rule #2.3: </element>] + rv = aRange->SetEnd(content, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + // Rule #2.4: <element/>] + nsINode* endNode = content->GetParent(); + if (NS_WARN_IF(!endNode)) { + return NS_ERROR_FAILURE; + } + int32_t indexInParent = endNode->IndexOf(content); + if (NS_WARN_IF(indexInParent == -1)) { + // The content is being removed from the parent! + return NS_ERROR_FAILURE; + } + rv = aRange->SetEnd(endNode, indexInParent + 1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + offset += textLength; + } + + if (!startSet) { + MOZ_ASSERT(!mRootContent->IsNodeOfType(nsINode::eTEXT)); + if (!offset) { + // Rule #1.5: <root>[</root> + // When there are no nodes causing text, the start of the DOM range + // should be start of the root node since clicking on such editor (e.g., + // <div contenteditable><span></span></div>) sets caret to the start of + // the editor (i.e., before <span> in the example). + rv = aRange->SetStart(mRootContent, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!aLength) { + rv = aRange->SetEnd(mRootContent, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + } else { + // Rule #1.5: [</root> + rv = aRange->SetStart(mRootContent, + static_cast<int32_t>(mRootContent->GetChildCount())); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + if (aNewOffset) { + *aNewOffset = offset; + } + } + // Rule #2.5: ]</root> + rv = aRange->SetEnd(mRootContent, + static_cast<int32_t>(mRootContent->GetChildCount())); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +/* static */ LineBreakType +ContentEventHandler::GetLineBreakType(WidgetQueryContentEvent* aEvent) +{ + return GetLineBreakType(aEvent->mUseNativeLineBreak); +} + +/* static */ LineBreakType +ContentEventHandler::GetLineBreakType(WidgetSelectionEvent* aEvent) +{ + return GetLineBreakType(aEvent->mUseNativeLineBreak); +} + +/* static */ LineBreakType +ContentEventHandler::GetLineBreakType(bool aUseNativeLineBreak) +{ + return aUseNativeLineBreak ? + LINE_BREAK_TYPE_NATIVE : LINE_BREAK_TYPE_XP; +} + +nsresult +ContentEventHandler::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent) +{ + switch (aEvent->mMessage) { + case eQuerySelectedText: + return OnQuerySelectedText(aEvent); + case eQueryTextContent: + return OnQueryTextContent(aEvent); + case eQueryCaretRect: + return OnQueryCaretRect(aEvent); + case eQueryTextRect: + return OnQueryTextRect(aEvent); + case eQueryTextRectArray: + return OnQueryTextRectArray(aEvent); + case eQueryEditorRect: + return OnQueryEditorRect(aEvent); + case eQueryContentState: + return OnQueryContentState(aEvent); + case eQuerySelectionAsTransferable: + return OnQuerySelectionAsTransferable(aEvent); + case eQueryCharacterAtPoint: + return OnQueryCharacterAtPoint(aEvent); + case eQueryDOMWidgetHittest: + return OnQueryDOMWidgetHittest(aEvent); + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + return NS_OK; +} + +// Similar to nsFrameSelection::GetFrameForNodeOffset, +// but this is more flexible for OnQueryTextRect to use +static nsresult GetFrameForTextRect(nsINode* aNode, + int32_t aNodeOffset, + bool aHint, + nsIFrame** aReturnFrame) +{ + NS_ENSURE_TRUE(aNode && aNode->IsNodeOfType(nsINode::eCONTENT), + NS_ERROR_UNEXPECTED); + nsIContent* content = static_cast<nsIContent*>(aNode); + nsIFrame* frame = content->GetPrimaryFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + int32_t childNodeOffset = 0; + return frame->GetChildFrameContainingOffset(aNodeOffset, aHint, + &childNodeOffset, aReturnFrame); +} + +nsresult +ContentEventHandler::OnQuerySelectedText(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + + if (!mFirstSelectedRange) { + MOZ_ASSERT(aEvent->mInput.mSelectionType != SelectionType::eNormal); + MOZ_ASSERT(aEvent->mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND); + MOZ_ASSERT(aEvent->mReply.mString.IsEmpty()); + MOZ_ASSERT(!aEvent->mReply.mHasSelection); + aEvent->mSucceeded = true; + return NS_OK; + } + + nsINode* const startNode = mFirstSelectedRange->GetStartParent(); + nsINode* const endNode = mFirstSelectedRange->GetEndParent(); + + // Make sure the selection is within the root content range. + if (!nsContentUtils::ContentIsDescendantOf(startNode, mRootContent) || + !nsContentUtils::ContentIsDescendantOf(endNode, mRootContent)) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ASSERTION(aEvent->mReply.mString.IsEmpty(), + "The reply string must be empty"); + + LineBreakType lineBreakType = GetLineBreakType(aEvent); + rv = GetStartOffset(mFirstSelectedRange, + &aEvent->mReply.mOffset, lineBreakType); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsINode> anchorNode, focusNode; + int32_t anchorOffset = 0, focusOffset = 0; + if (mSelection->RangeCount()) { + // If there is only one selection range, the anchor/focus node and offset + // are the information of the range. Therefore, we have the direction + // information. + if (mSelection->RangeCount() == 1) { + anchorNode = mSelection->GetAnchorNode(); + focusNode = mSelection->GetFocusNode(); + if (NS_WARN_IF(!anchorNode) || NS_WARN_IF(!focusNode)) { + return NS_ERROR_FAILURE; + } + anchorOffset = static_cast<int32_t>(mSelection->AnchorOffset()); + focusOffset = static_cast<int32_t>(mSelection->FocusOffset()); + if (NS_WARN_IF(anchorOffset < 0) || NS_WARN_IF(focusOffset < 0)) { + return NS_ERROR_FAILURE; + } + + int16_t compare = nsContentUtils::ComparePoints(anchorNode, anchorOffset, + focusNode, focusOffset); + aEvent->mReply.mReversed = compare > 0; + } + // However, if there are 2 or more selection ranges, we have no information + // of that. + else { + aEvent->mReply.mReversed = false; + } + + if (!mFirstSelectedRange->Collapsed()) { + rv = GenerateFlatTextContent(mFirstSelectedRange, aEvent->mReply.mString, + lineBreakType); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + aEvent->mReply.mString.Truncate(); + } + } else { + NS_ASSERTION(mFirstSelectedRange->Collapsed(), + "When mSelection doesn't have selection, mFirstSelectedRange must be " + "collapsed"); + anchorNode = focusNode = mFirstSelectedRange->GetStartParent(); + if (NS_WARN_IF(!anchorNode)) { + return NS_ERROR_FAILURE; + } + anchorOffset = focusOffset = + static_cast<int32_t>(mFirstSelectedRange->StartOffset()); + if (NS_WARN_IF(anchorOffset < 0)) { + return NS_ERROR_FAILURE; + } + + aEvent->mReply.mReversed = false; + aEvent->mReply.mString.Truncate(); + } + + + nsIFrame* frame = nullptr; + rv = GetFrameForTextRect(focusNode, focusOffset, true, &frame); + if (NS_SUCCEEDED(rv) && frame) { + aEvent->mReply.mWritingMode = frame->GetWritingMode(); + } else { + aEvent->mReply.mWritingMode = WritingMode(); + } + + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +ContentEventHandler::OnQueryTextContent(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(aEvent->mReply.mString.IsEmpty(), + "The reply string must be empty"); + + LineBreakType lineBreakType = GetLineBreakType(aEvent); + + RefPtr<nsRange> range = new nsRange(mRootContent); + rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, + aEvent->mInput.mLength, lineBreakType, false, + &aEvent->mReply.mOffset); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GenerateFlatTextContent(range, aEvent->mReply.mString, lineBreakType); + NS_ENSURE_SUCCESS(rv, rv); + + if (aEvent->mWithFontRanges) { + uint32_t fontRangeLength; + rv = GenerateFlatFontRanges(range, aEvent->mReply.mFontRanges, + fontRangeLength, lineBreakType); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(fontRangeLength == aEvent->mReply.mString.Length(), + "Font ranges doesn't match the string"); + } + + aEvent->mSucceeded = true; + + return NS_OK; +} + +void +ContentEventHandler::EnsureNonEmptyRect(nsRect& aRect) const +{ + // See the comment in ContentEventHandler.h why this doesn't set them to + // one device pixel. + aRect.height = std::max(1, aRect.height); + aRect.width = std::max(1, aRect.width); +} + +void +ContentEventHandler::EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const +{ + aRect.height = std::max(1, aRect.height); + aRect.width = std::max(1, aRect.width); +} + +ContentEventHandler::NodePosition +ContentEventHandler::GetNodePositionHavingFlatText( + const NodePosition& aNodePosition) +{ + return GetNodePositionHavingFlatText(aNodePosition.mNode, + aNodePosition.mOffset); +} + +ContentEventHandler::NodePosition +ContentEventHandler::GetNodePositionHavingFlatText(nsINode* aNode, + int32_t aNodeOffset) +{ + if (aNode->IsNodeOfType(nsINode::eTEXT)) { + return NodePosition(aNode, aNodeOffset); + } + + int32_t childCount = static_cast<int32_t>(aNode->GetChildCount()); + + // If it's a empty element node, returns itself. + if (!childCount) { + MOZ_ASSERT(!aNodeOffset || aNodeOffset == 1); + return NodePosition(aNode, aNodeOffset); + } + + // If there is a node at given position, return the start of it. + if (aNodeOffset < childCount) { + return NodePosition(aNode->GetChildAt(aNodeOffset), 0); + } + + // If the offset represents "after" the node, we need to return the last + // child of it. For example, if a range is |<p>[<br>]</p>|, then, the + // end point is {<p>, 1}. In such case, callers need the <br> node. + if (aNodeOffset == childCount) { + NodePosition result; + result.mNode = aNode->GetChildAt(childCount - 1); + result.mOffset = result.mNode->IsNodeOfType(nsINode::eTEXT) ? + static_cast<int32_t>(result.mNode->AsContent()->TextLength()) : 1; + } + + NS_WARNING("aNodeOffset is invalid value"); + return NodePosition(); +} + +ContentEventHandler::FrameAndNodeOffset +ContentEventHandler::GetFirstFrameInRangeForTextRect(nsRange* aRange) +{ + NodePosition nodePosition; + nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator(); + for (iter->Init(aRange); !iter->IsDone(); iter->Next()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + + if (!node->IsContent()) { + continue; + } + + if (node->IsNodeOfType(nsINode::eTEXT)) { + // If the range starts at the end of a text node, we need to find + // next node which causes text. + int32_t offsetInNode = + node == aRange->GetStartParent() ? aRange->StartOffset() : 0; + if (static_cast<uint32_t>(offsetInNode) < node->Length()) { + nodePosition.mNode = node; + nodePosition.mOffset = offsetInNode; + break; + } + continue; + } + + // If the element node causes a line break before it, it's the first + // node causing text. + if (ShouldBreakLineBefore(node->AsContent(), mRootContent) || + IsMozBR(node->AsContent())) { + nodePosition.mNode = node; + nodePosition.mOffset = 0; + } + } + + if (!nodePosition.IsValid()) { + return FrameAndNodeOffset(); + } + + nsIFrame* firstFrame = nullptr; + GetFrameForTextRect(nodePosition.mNode, nodePosition.mOffset, + true, &firstFrame); + return FrameAndNodeOffset(firstFrame, nodePosition.mOffset); +} + +ContentEventHandler::FrameAndNodeOffset +ContentEventHandler::GetLastFrameInRangeForTextRect(nsRange* aRange) +{ + NodePosition nodePosition; + nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator(); + iter->Init(aRange); + + nsINode* endNode = aRange->GetEndParent(); + uint32_t endOffset = static_cast<uint32_t>(aRange->EndOffset()); + // If the end point is start of a text node or specified by its parent and + // index, the node shouldn't be included into the range. For example, + // with this case, |<p>abc[<br>]def</p>|, the range ends at 3rd children of + // <p> (see the range creation rules, "2.4. Cases: <element/>]"). This causes + // following frames: + // +----+-----+ + // | abc|[<br>| + // +----+-----+ + // +----+ + // |]def| + // +----+ + // So, if this method includes the 2nd text frame's rect to its result, the + // caller will return too tall rect which includes 2 lines in this case isn't + // expected by native IME (e.g., popup of IME will be positioned at bottom + // of "d" instead of right-bottom of "c"). Therefore, this method shouldn't + // include the last frame when its content isn't really in aRange. + nsINode* nextNodeOfRangeEnd = nullptr; + if (endNode->IsNodeOfType(nsINode::eTEXT)) { + // Don't set nextNodeOfRangeEnd to the start node of aRange because if + // endNode is same as start node of the range, the text node shouldn't be + // next of range end even if the offset is 0. This could occur with empty + // text node. + if (!endOffset && aRange->GetStartParent() != endNode) { + nextNodeOfRangeEnd = endNode; + } + } else if (endOffset < endNode->GetChildCount()) { + nextNodeOfRangeEnd = endNode->GetChildAt(endOffset); + } + + for (iter->Last(); !iter->IsDone(); iter->Prev()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + + if (!node->IsContent() || node == nextNodeOfRangeEnd) { + continue; + } + + if (node->IsNodeOfType(nsINode::eTEXT)) { + nodePosition.mNode = node; + if (node == aRange->GetEndParent()) { + nodePosition.mOffset = aRange->EndOffset(); + } else { + nodePosition.mOffset = node->Length(); + } + // If the text node is empty or the last node of the range but the index + // is 0, we should store current position but continue looking for + // previous node (If there are no nodes before it, we should use current + // node position for returning its frame). + if (!nodePosition.mOffset) { + continue; + } + break; + } + + if (ShouldBreakLineBefore(node->AsContent(), mRootContent) || + IsMozBR(node->AsContent())) { + nodePosition.mNode = node; + nodePosition.mOffset = 0; + break; + } + } + + if (!nodePosition.IsValid()) { + return FrameAndNodeOffset(); + } + + nsIFrame* lastFrame = nullptr; + GetFrameForTextRect(nodePosition.mNode, nodePosition.mOffset, + true, &lastFrame); + if (!lastFrame) { + return FrameAndNodeOffset(); + } + + // If the last frame is a text frame, we need to check if the range actually + // includes at least one character in the range. Therefore, if it's not a + // text frame, we need to do nothing anymore. + if (lastFrame->GetType() != nsGkAtoms::textFrame) { + return FrameAndNodeOffset(lastFrame, nodePosition.mOffset); + } + + int32_t start, end; + if (NS_WARN_IF(NS_FAILED(lastFrame->GetOffsets(start, end)))) { + return FrameAndNodeOffset(); + } + + // If the start offset in the node is same as the computed offset in the + // node and it's not 0, the frame shouldn't be added to the text rect. So, + // this should return previous text frame and its last offset if there is + // at least one text frame. + if (nodePosition.mOffset && nodePosition.mOffset == start) { + GetFrameForTextRect(nodePosition.mNode, --nodePosition.mOffset, + true, &lastFrame); + if (NS_WARN_IF(!lastFrame)) { + return FrameAndNodeOffset(); + } + } + + return FrameAndNodeOffset(lastFrame, nodePosition.mOffset); +} + +ContentEventHandler::FrameRelativeRect +ContentEventHandler::GetLineBreakerRectBefore(nsIFrame* aFrame) +{ + // Note that this method should be called only with an element's frame whose + // open tag causes a line break or moz-<br> for computing empty last line's + // rect. + MOZ_ASSERT(ShouldBreakLineBefore(aFrame->GetContent(), mRootContent) || + IsMozBR(aFrame->GetContent())); + + nsIFrame* frameForFontMetrics = aFrame; + + // If it's not a <br> frame, this method computes the line breaker's rect + // outside the frame. Therefore, we need to compute with parent frame's + // font metrics in such case. + if (aFrame->GetType() != nsGkAtoms::brFrame && aFrame->GetParent()) { + frameForFontMetrics = aFrame->GetParent(); + } + + // Note that <br> element's rect is decided with line-height but we need + // a rect only with font height. Additionally, <br> frame's width and + // height are 0 in quirks mode if it's not an empty line. So, we cannot + // use frame rect information even if it's a <br> frame. + + FrameRelativeRect result(aFrame); + + RefPtr<nsFontMetrics> fontMetrics = + nsLayoutUtils::GetInflatedFontMetricsForFrame(frameForFontMetrics); + if (NS_WARN_IF(!fontMetrics)) { + return FrameRelativeRect(); + } + + const WritingMode kWritingMode = frameForFontMetrics->GetWritingMode(); + nscoord baseline = aFrame->GetCaretBaseline(); + if (kWritingMode.IsVertical()) { + if (kWritingMode.IsLineInverted()) { + result.mRect.x = baseline - fontMetrics->MaxDescent(); + } else { + result.mRect.x = baseline - fontMetrics->MaxAscent(); + } + result.mRect.width = fontMetrics->MaxHeight(); + } else { + result.mRect.y = baseline - fontMetrics->MaxAscent(); + result.mRect.height = fontMetrics->MaxHeight(); + } + + // If aFrame isn't a <br> frame, caret should be at outside of it because + // the line break is before its open tag. For example, case of + // |<div><p>some text</p></div>|, caret is before <p> element and in <div> + // element, the caret should be left of top-left corner of <p> element like: + // + // +-<div>------------------- <div>'s border box + // | I +-<p>----------------- <p>'s border box + // | I | + // | I | + // | | + // ^- caret + // + // However, this is a hack for unusual scenario. This hack shouldn't be + // used as far as possible. + if (aFrame->GetType() != nsGkAtoms::brFrame) { + if (kWritingMode.IsVertical()) { + if (kWritingMode.IsLineInverted()) { + // above of top-left corner of aFrame. + result.mRect.x = 0; + } else { + // above of top-right corner of aFrame. + result.mRect.x = aFrame->GetRect().XMost() - result.mRect.width; + } + result.mRect.y = -mPresContext->AppUnitsPerDevPixel(); + } else { + // left of top-left corner of aFrame. + result.mRect.x = -mPresContext->AppUnitsPerDevPixel(); + result.mRect.y = 0; + } + } + return result; +} + +ContentEventHandler::FrameRelativeRect +ContentEventHandler::GuessLineBreakerRectAfter(nsIContent* aTextContent) +{ + // aTextContent should be a text node. + MOZ_ASSERT(aTextContent->IsNodeOfType(nsINode::eTEXT)); + + FrameRelativeRect result; + int32_t length = static_cast<int32_t>(aTextContent->Length()); + if (NS_WARN_IF(length < 0)) { + return result; + } + // Get the last nsTextFrame which is caused by aTextContent. Note that + // a text node can cause multiple text frames, e.g., the text is too long + // and wrapped by its parent block or the text has line breakers and its + // white-space property respects the line breakers (e.g., |pre|). + nsIFrame* lastTextFrame = nullptr; + nsresult rv = GetFrameForTextRect(aTextContent, length, true, &lastTextFrame); + if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!lastTextFrame)) { + return result; + } + const nsRect kLastTextFrameRect = lastTextFrame->GetRect(); + if (lastTextFrame->GetWritingMode().IsVertical()) { + // Below of the last text frame. + result.mRect.SetRect(0, kLastTextFrameRect.height, + kLastTextFrameRect.width, 0); + } else { + // Right of the last text frame (not bidi-aware). + result.mRect.SetRect(kLastTextFrameRect.width, 0, + 0, kLastTextFrameRect.height); + } + result.mBaseFrame = lastTextFrame; + return result; +} + +ContentEventHandler::FrameRelativeRect +ContentEventHandler::GuessFirstCaretRectIn(nsIFrame* aFrame) +{ + const WritingMode kWritingMode = aFrame->GetWritingMode(); + + // Computes the font height, but if it's not available, we should use + // default font size of Firefox. The default font size in default settings + // is 16px. + RefPtr<nsFontMetrics> fontMetrics = + nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame); + const nscoord kMaxHeight = + fontMetrics ? fontMetrics->MaxHeight() : + 16 * mPresContext->AppUnitsPerDevPixel(); + + nsRect caretRect; + const nsRect kContentRect = aFrame->GetContentRect() - aFrame->GetPosition(); + caretRect.y = kContentRect.y; + if (!kWritingMode.IsVertical()) { + if (kWritingMode.IsBidiLTR()) { + caretRect.x = kContentRect.x; + } else { + // Move 1px left for the space of caret itself. + const nscoord kOnePixel = mPresContext->AppUnitsPerDevPixel(); + caretRect.x = kContentRect.XMost() - kOnePixel; + } + caretRect.height = kMaxHeight; + // However, don't add kOnePixel here because it may cause 2px width at + // aligning the edge to device pixels. + caretRect.width = 1; + } else { + if (kWritingMode.IsVerticalLR()) { + caretRect.x = kContentRect.x; + } else { + caretRect.x = kContentRect.XMost() - kMaxHeight; + } + caretRect.width = kMaxHeight; + // Don't add app units for a device pixel because it may cause 2px height + // at aligning the edge to device pixels. + caretRect.height = 1; + } + return FrameRelativeRect(caretRect, aFrame); +} + +nsresult +ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LineBreakType lineBreakType = GetLineBreakType(aEvent); + const uint32_t kBRLength = GetBRLength(lineBreakType); + + RefPtr<nsRange> range = new nsRange(mRootContent); + + bool isVertical = false; + LayoutDeviceIntRect rect; + uint32_t offset = aEvent->mInput.mOffset; + const uint32_t kEndOffset = offset + aEvent->mInput.mLength; + bool wasLineBreaker = false; + // lastCharRect stores the last charRect value (see below for the detail of + // charRect). + nsRect lastCharRect; + // lastFrame is base frame of lastCharRect. + nsIFrame* lastFrame = nullptr; + while (offset < kEndOffset) { + nsCOMPtr<nsIContent> lastTextContent; + rv = SetRangeFromFlatTextOffset(range, offset, 1, lineBreakType, true, + nullptr, getter_AddRefs(lastTextContent)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // If the range is collapsed, offset has already reached the end of the + // contents. + if (range->Collapsed()) { + break; + } + + // Get the first frame which causes some text after the offset. + FrameAndNodeOffset firstFrame = GetFirstFrameInRangeForTextRect(range); + + // If GetFirstFrameInRangeForTextRect() does not return valid frame, that + // means that there are no visible frames having text or the offset reached + // the end of contents. + if (!firstFrame.IsValid()) { + nsAutoString allText; + rv = GenerateFlatTextContent(mRootContent, allText, lineBreakType); + // If the offset doesn't reach the end of contents yet but there is no + // frames for the node, that means that current offset's node is hidden + // by CSS or something. Ideally, we should handle it with the last + // visible text node's last character's rect, but it's not usual cases + // in actual web services. Therefore, currently, we should make this + // case fail. + if (NS_WARN_IF(NS_FAILED(rv)) || offset < allText.Length()) { + return NS_ERROR_FAILURE; + } + // Otherwise, we should append caret rect at the end of the contents + // later. + break; + } + + nsIContent* firstContent = firstFrame.mFrame->GetContent(); + if (NS_WARN_IF(!firstContent)) { + return NS_ERROR_FAILURE; + } + + bool startsBetweenLineBreaker = false; + nsAutoString chars; + // XXX not bidi-aware this class... + isVertical = firstFrame->GetWritingMode().IsVertical(); + + nsIFrame* baseFrame = firstFrame; + // charRect should have each character rect or line breaker rect relative + // to the base frame. + AutoTArray<nsRect, 16> charRects; + + // If the first frame is a text frame, the result should be computed with + // the frame's API. + if (firstFrame->GetType() == nsGkAtoms::textFrame) { + rv = firstFrame->GetCharacterRectsInRange(firstFrame.mOffsetInNode, + kEndOffset - offset, charRects); + if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(charRects.IsEmpty())) { + return rv; + } + // Assign the characters whose rects are computed by the call of + // nsTextFrame::GetCharacterRectsInRange(). + AppendSubString(chars, firstContent, firstFrame.mOffsetInNode, + charRects.Length()); + if (NS_WARN_IF(chars.Length() != charRects.Length())) { + return NS_ERROR_UNEXPECTED; + } + if (kBRLength > 1 && chars[0] == '\n' && + offset == aEvent->mInput.mOffset && offset) { + // If start of range starting from previous offset of query range is + // same as the start of query range, the query range starts from + // between a line breaker (i.e., the range starts between "\r" and + // "\n"). + RefPtr<nsRange> rangeToPrevOffset = new nsRange(mRootContent); + rv = SetRangeFromFlatTextOffset(rangeToPrevOffset, + aEvent->mInput.mOffset - 1, 1, + lineBreakType, true, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + startsBetweenLineBreaker = + range->GetStartParent() == rangeToPrevOffset->GetStartParent() && + range->StartOffset() == rangeToPrevOffset->StartOffset(); + } + } + // Other contents should cause a line breaker rect before it. + // Note that moz-<br> element does not cause any text, however, + // it represents empty line at the last of current block. Therefore, + // we need to compute its rect too. + else if (ShouldBreakLineBefore(firstContent, mRootContent) || + IsMozBR(firstContent)) { + nsRect brRect; + // If the frame is not a <br> frame, we need to compute the caret rect + // with last character's rect before firstContent if there is. + // For example, if caret is after "c" of |<p>abc</p><p>def</p>|, IME may + // query a line breaker's rect after "c". Then, if we compute it only + // with the 2nd <p>'s block frame, the result will be: + // +-<p>--------------------------------+ + // |abc | + // +------------------------------------+ + // + // I+-<p>--------------------------------+ + // |def | + // +------------------------------------+ + // However, users expect popup windows of IME should be positioned at + // right-bottom of "c" like this: + // +-<p>--------------------------------+ + // |abcI | + // +------------------------------------+ + // + // +-<p>--------------------------------+ + // |def | + // +------------------------------------+ + // Therefore, if the first frame isn't a <br> frame and there is a text + // node before the first node in the queried range, we should compute the + // first rect with the previous character's rect. + // If we already compute a character's rect in the queried range, we can + // compute it with the cached last character's rect. (However, don't + // use this path if it's a <br> frame because trusting <br> frame's rect + // is better than guessing the rect from the previous character.) + if (firstFrame->GetType() != nsGkAtoms::brFrame && + aEvent->mInput.mOffset != offset) { + baseFrame = lastFrame; + brRect = lastCharRect; + if (!wasLineBreaker) { + if (isVertical) { + // Right of the last character. + brRect.y = brRect.YMost() + 1; + brRect.height = 1; + } else { + // Under the last character. + brRect.x = brRect.XMost() + 1; + brRect.width = 1; + } + } + } + // If it's not a <br> frame and it's the first character rect at the + // queried range, we need to the previous character of the start of + // the queried range if there is a text node. + else if (firstFrame->GetType() != nsGkAtoms::brFrame && lastTextContent) { + FrameRelativeRect brRectRelativeToLastTextFrame = + GuessLineBreakerRectAfter(lastTextContent); + if (NS_WARN_IF(!brRectRelativeToLastTextFrame.IsValid())) { + return NS_ERROR_FAILURE; + } + // Look for the last text frame for lastTextContent. + nsIFrame* primaryFrame = lastTextContent->GetPrimaryFrame(); + if (NS_WARN_IF(!primaryFrame)) { + return NS_ERROR_FAILURE; + } + baseFrame = primaryFrame->LastContinuation(); + if (NS_WARN_IF(!baseFrame)) { + return NS_ERROR_FAILURE; + } + brRect = brRectRelativeToLastTextFrame.RectRelativeTo(baseFrame); + } + // Otherwise, we need to compute the line breaker's rect only with the + // first frame's rect. But this may be unexpected. For example, + // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is + // before "a", therefore, users expect the rect left of "a". However, + // we don't have enough information about the next character here and + // this isn't usual case (e.g., IME typically tries to query the rect + // of "a" or caret rect for computing its popup position). Therefore, + // we shouldn't do more complicated hack here unless we'll get some bug + // reports actually. + else { + FrameRelativeRect relativeBRRect = GetLineBreakerRectBefore(firstFrame); + brRect = relativeBRRect.RectRelativeTo(firstFrame); + } + charRects.AppendElement(brRect); + chars.AssignLiteral("\n"); + if (kBRLength > 1 && offset == aEvent->mInput.mOffset && offset) { + // If the first frame for the previous offset of the query range and + // the first frame for the start of query range are same, that means + // the start offset is between the first line breaker (i.e., the range + // starts between "\r" and "\n"). + rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset - 1, 1, + lineBreakType, true, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + FrameAndNodeOffset frameForPrevious = + GetFirstFrameInRangeForTextRect(range); + startsBetweenLineBreaker = frameForPrevious.mFrame == firstFrame.mFrame; + } + } else { + NS_WARNING("The frame is neither a text frame nor a frame whose content " + "causes a line break"); + return NS_ERROR_FAILURE; + } + + for (size_t i = 0; i < charRects.Length() && offset < kEndOffset; i++) { + nsRect charRect = charRects[i]; + // Store lastCharRect before applying CSS transform because it may be + // used for computing a line breaker rect. Then, the computed line + // breaker rect will be applied CSS transform again. Therefore, + // the value of lastCharRect should be raw rect value relative to the + // base frame. + lastCharRect = charRect; + lastFrame = baseFrame; + rv = ConvertToRootRelativeOffset(baseFrame, charRect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rect = LayoutDeviceIntRect::FromUnknownRect( + charRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel())); + // Returning empty rect may cause native IME confused, let's make sure to + // return non-empty rect. + EnsureNonEmptyRect(rect); + + aEvent->mReply.mRectArray.AppendElement(rect); + offset++; + + // If it's not a line breaker or the line breaker length is same as + // XP line breaker's, we need to do nothing for current character. + wasLineBreaker = chars[i] == '\n'; + if (!wasLineBreaker || kBRLength == 1) { + continue; + } + + MOZ_ASSERT(kBRLength == 2); + + // If it's already reached the end of query range, we don't need to do + // anymore. + if (offset == kEndOffset) { + break; + } + + // If the query range starts from between a line breaker, i.e., it starts + // between "\r" and "\n", the appended rect was for the "\n". Therefore, + // we don't need to append same rect anymore for current "\r\n". + if (startsBetweenLineBreaker) { + continue; + } + + // The appended rect was for "\r" of "\r\n". Therefore, we need to + // append same rect for "\n" too because querying rect of "\r" and "\n" + // should return same rect. E.g., IME may query previous character's + // rect of first character of a line. + aEvent->mReply.mRectArray.AppendElement(rect); + offset++; + } + } + + // If the query range is longer than actual content length, we should append + // caret rect at the end of the content as the last character rect because + // native IME may want to query character rect at the end of contents for + // deciding the position of a popup window (e.g., suggest window for next + // word). Note that when this method hasn't appended character rects, it + // means that the offset is too large or the query range is collapsed. + if (offset < kEndOffset || aEvent->mReply.mRectArray.IsEmpty()) { + // If we've already retrieved some character rects before current offset, + // we can guess the last rect from the last character's rect unless it's a + // line breaker. (If it's a line breaker, the caret rect is in next line.) + if (!aEvent->mReply.mRectArray.IsEmpty() && !wasLineBreaker) { + rect = aEvent->mReply.mRectArray.LastElement(); + if (isVertical) { + rect.y = rect.YMost() + 1; + rect.height = 1; + MOZ_ASSERT(rect.width); + } else { + rect.x = rect.XMost() + 1; + rect.width = 1; + MOZ_ASSERT(rect.height); + } + aEvent->mReply.mRectArray.AppendElement(rect); + } else { + // Note that don't use eQueryCaretRect here because if caret is at the + // end of the content, it returns actual caret rect instead of computing + // the rect itself. It means that the result depends on caret position. + // So, we shouldn't use it for consistency result in automated tests. + WidgetQueryContentEvent queryTextRect(eQueryTextRect, *aEvent); + WidgetQueryContentEvent::Options options(*aEvent); + queryTextRect.InitForQueryTextRect(offset, 1, options); + rv = OnQueryTextRect(&queryTextRect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (NS_WARN_IF(!queryTextRect.mSucceeded)) { + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(!queryTextRect.mReply.mRect.IsEmpty()); + if (queryTextRect.mReply.mWritingMode.IsVertical()) { + queryTextRect.mReply.mRect.height = 1; + } else { + queryTextRect.mReply.mRect.width = 1; + } + aEvent->mReply.mRectArray.AppendElement(queryTextRect.mReply.mRect); + } + } + + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + + // If mLength is 0 (this may be caused by bug of native IME), we should + // redirect this event to OnQueryCaretRect(). + if (!aEvent->mInput.mLength) { + return OnQueryCaretRect(aEvent); + } + + LineBreakType lineBreakType = GetLineBreakType(aEvent); + RefPtr<nsRange> range = new nsRange(mRootContent); + nsCOMPtr<nsIContent> lastTextContent; + rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, + aEvent->mInput.mLength, lineBreakType, true, + &aEvent->mReply.mOffset, + getter_AddRefs(lastTextContent)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GenerateFlatTextContent(range, aEvent->mReply.mString, lineBreakType); + NS_ENSURE_SUCCESS(rv, rv); + + // used to iterate over all contents and their frames + nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator(); + iter->Init(range); + + // Get the first frame which causes some text after the offset. + FrameAndNodeOffset firstFrame = GetFirstFrameInRangeForTextRect(range); + + // If GetFirstFrameInRangeForTextRect() does not return valid frame, that + // means that there are no visible frames having text or the offset reached + // the end of contents. + if (!firstFrame.IsValid()) { + nsAutoString allText; + rv = GenerateFlatTextContent(mRootContent, allText, lineBreakType); + // If the offset doesn't reach the end of contents but there is no frames + // for the node, that means that current offset's node is hidden by CSS or + // something. Ideally, we should handle it with the last visible text + // node's last character's rect, but it's not usual cases in actual web + // services. Therefore, currently, we should make this case fail. + if (NS_WARN_IF(NS_FAILED(rv)) || + static_cast<uint32_t>(aEvent->mInput.mOffset) < allText.Length()) { + return NS_ERROR_FAILURE; + } + + // Look for the last frame which should be included text rects. + ErrorResult erv; + range->SelectNodeContents(*mRootContent, erv); + if (NS_WARN_IF(erv.Failed())) { + return NS_ERROR_UNEXPECTED; + } + nsRect rect; + FrameAndNodeOffset lastFrame = GetLastFrameInRangeForTextRect(range); + // If there is at least one frame which can be used for computing a rect + // for a character or a line breaker, we should use it for guessing the + // caret rect at the end of the contents. + if (lastFrame) { + if (NS_WARN_IF(!lastFrame->GetContent())) { + return NS_ERROR_FAILURE; + } + FrameRelativeRect relativeRect; + // If there is a <br> frame at the end, it represents an empty line at + // the end with moz-<br> or content <br> in a block level element. + if (lastFrame->GetType() == nsGkAtoms::brFrame) { + relativeRect = GetLineBreakerRectBefore(lastFrame); + } + // If there is a text frame at the end, use its information. + else if (lastFrame->GetType() == nsGkAtoms::textFrame) { + relativeRect = GuessLineBreakerRectAfter(lastFrame->GetContent()); + } + // If there is an empty frame which is neither a text frame nor a <br> + // frame at the end, guess caret rect in it. + else { + relativeRect = GuessFirstCaretRectIn(lastFrame); + } + if (NS_WARN_IF(!relativeRect.IsValid())) { + return NS_ERROR_FAILURE; + } + rect = relativeRect.RectRelativeTo(lastFrame); + rv = ConvertToRootRelativeOffset(lastFrame, rect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + aEvent->mReply.mWritingMode = lastFrame->GetWritingMode(); + } + // Otherwise, if there are no contents in mRootContent, guess caret rect in + // its frame (with its font height and content box). + else { + nsIFrame* rootContentFrame = mRootContent->GetPrimaryFrame(); + if (NS_WARN_IF(!rootContentFrame)) { + return NS_ERROR_FAILURE; + } + FrameRelativeRect relativeRect = GuessFirstCaretRectIn(rootContentFrame); + if (NS_WARN_IF(!relativeRect.IsValid())) { + return NS_ERROR_FAILURE; + } + rect = relativeRect.RectRelativeTo(rootContentFrame); + rv = ConvertToRootRelativeOffset(rootContentFrame, rect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + aEvent->mReply.mWritingMode = rootContentFrame->GetWritingMode(); + } + aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect( + rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel())); + EnsureNonEmptyRect(aEvent->mReply.mRect); + aEvent->mSucceeded = true; + return NS_OK; + } + + nsRect rect, frameRect; + nsPoint ptOffset; + + // If the first frame is a text frame, the result should be computed with + // the frame's rect but not including the rect before start point of the + // queried range. + if (firstFrame->GetType() == nsGkAtoms::textFrame) { + rect.SetRect(nsPoint(0, 0), firstFrame->GetRect().Size()); + rv = ConvertToRootRelativeOffset(firstFrame, rect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + frameRect = rect; + // Exclude the rect before start point of the queried range. + firstFrame->GetPointFromOffset(firstFrame.mOffsetInNode, &ptOffset); + if (firstFrame->GetWritingMode().IsVertical()) { + rect.y += ptOffset.y; + rect.height -= ptOffset.y; + } else { + rect.x += ptOffset.x; + rect.width -= ptOffset.x; + } + } + // If first frame causes a line breaker but it's not a <br> frame, we cannot + // compute proper rect only with the frame because typically caret is at + // right of the last character of it. For example, if caret is after "c" of + // |<p>abc</p><p>def</p>|, IME may query a line breaker's rect after "c". + // Then, if we compute it only with the 2nd <p>'s block frame, the result + // will be: + // +-<p>--------------------------------+ + // |abc | + // +------------------------------------+ + // + // I+-<p>--------------------------------+ + // |def | + // +------------------------------------+ + // However, users expect popup windows of IME should be positioned at + // right-bottom of "c" like this: + // +-<p>--------------------------------+ + // |abcI | + // +------------------------------------+ + // + // +-<p>--------------------------------+ + // |def | + // +------------------------------------+ + // Therefore, if the first frame isn't a <br> frame and there is a text + // node before the first node in the queried range, we should compute the + // first rect with the previous character's rect. + else if (firstFrame->GetType() != nsGkAtoms::brFrame && lastTextContent) { + FrameRelativeRect brRectAfterLastChar = + GuessLineBreakerRectAfter(lastTextContent); + if (NS_WARN_IF(!brRectAfterLastChar.IsValid())) { + return NS_ERROR_FAILURE; + } + rect = brRectAfterLastChar.mRect; + rv = ConvertToRootRelativeOffset(brRectAfterLastChar.mBaseFrame, rect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + frameRect = rect; + } + // Otherwise, we need to compute the line breaker's rect only with the + // first frame's rect. But this may be unexpected. For example, + // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is before + // "a", therefore, users expect the rect left of "a". However, we don't + // have enough information about the next character here and this isn't + // usual case (e.g., IME typically tries to query the rect of "a" or caret + // rect for computing its popup position). Therefore, we shouldn't do + // more complicated hack here unless we'll get some bug reports actually. + else { + FrameRelativeRect relativeRect = GetLineBreakerRectBefore(firstFrame); + if (NS_WARN_IF(!relativeRect.IsValid())) { + return NS_ERROR_FAILURE; + } + rect = relativeRect.RectRelativeTo(firstFrame); + rv = ConvertToRootRelativeOffset(firstFrame, rect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + frameRect = rect; + } + // UnionRect() requires non-empty rect. So, let's make sure to get non-emtpy + // rect from the first frame. + EnsureNonEmptyRect(rect); + + // Get the last frame which causes some text in the range. + FrameAndNodeOffset lastFrame = GetLastFrameInRangeForTextRect(range); + if (NS_WARN_IF(!lastFrame.IsValid())) { + return NS_ERROR_FAILURE; + } + + // iterate over all covered frames + for (nsIFrame* frame = firstFrame; frame != lastFrame;) { + frame = frame->GetNextContinuation(); + if (!frame) { + do { + iter->Next(); + nsINode* node = iter->GetCurrentNode(); + if (!node) { + break; + } + if (!node->IsNodeOfType(nsINode::eCONTENT)) { + continue; + } + nsIFrame* primaryFrame = node->AsContent()->GetPrimaryFrame(); + // The node may be hidden by CSS. + if (!primaryFrame) { + continue; + } + // We should take only text frame's rect and br frame's rect. We can + // always use frame rect of text frame and GetLineBreakerRectBefore() + // can return exactly correct rect only for <br> frame for now. On the + // other hand, GetLineBreakRectBefore() returns guessed caret rect for + // the other frames. We shouldn't include such odd rect to the result. + if (primaryFrame->GetType() == nsGkAtoms::textFrame || + primaryFrame->GetType() == nsGkAtoms::brFrame) { + frame = primaryFrame; + } + } while (!frame && !iter->IsDone()); + if (!frame) { + break; + } + } + if (frame->GetType() == nsGkAtoms::textFrame) { + frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size()); + } else { + MOZ_ASSERT(frame->GetType() == nsGkAtoms::brFrame); + FrameRelativeRect relativeRect = GetLineBreakerRectBefore(frame); + if (NS_WARN_IF(!relativeRect.IsValid())) { + return NS_ERROR_FAILURE; + } + frameRect = relativeRect.RectRelativeTo(frame); + } + rv = ConvertToRootRelativeOffset(frame, frameRect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // UnionRect() requires non-empty rect. So, let's make sure to get + // non-emtpy rect from the frame. + EnsureNonEmptyRect(frameRect); + if (frame != lastFrame) { + // not last frame, so just add rect to previous result + rect.UnionRect(rect, frameRect); + } + } + + // Get the ending frame rect. + // FYI: If first frame and last frame are same, frameRect is already set + // to the rect excluding the text before the query range. + if (firstFrame.mFrame != lastFrame.mFrame) { + frameRect.SetRect(nsPoint(0, 0), lastFrame->GetRect().Size()); + rv = ConvertToRootRelativeOffset(lastFrame, frameRect); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // Shrink the last frame for cutting off the text after the query range. + if (lastFrame->GetType() == nsGkAtoms::textFrame) { + lastFrame->GetPointFromOffset(lastFrame.mOffsetInNode, &ptOffset); + if (lastFrame->GetWritingMode().IsVertical()) { + frameRect.height -= lastFrame->GetRect().height - ptOffset.y; + } else { + frameRect.width -= lastFrame->GetRect().width - ptOffset.x; + } + // UnionRect() requires non-empty rect. So, let's make sure to get + // non-empty rect from the last frame. + EnsureNonEmptyRect(frameRect); + + if (firstFrame.mFrame == lastFrame.mFrame) { + rect.IntersectRect(rect, frameRect); + } else { + rect.UnionRect(rect, frameRect); + } + } + + aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect( + rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel())); + // Returning empty rect may cause native IME confused, let's make sure to + // return non-empty rect. + EnsureNonEmptyRect(aEvent->mReply.mRect); + aEvent->mReply.mWritingMode = lastFrame->GetWritingMode(); + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +ContentEventHandler::OnQueryEditorRect(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + + nsIContent* focusedContent = GetFocusedContent(); + rv = QueryContentRect(IsPlugin(focusedContent) ? + focusedContent : mRootContent.get(), aEvent); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +nsresult +ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + + // When the selection is collapsed and the queried offset is current caret + // position, we should return the "real" caret rect. + if (mSelection->IsCollapsed()) { + nsRect caretRect; + nsIFrame* caretFrame = nsCaret::GetGeometry(mSelection, &caretRect); + if (caretFrame) { + uint32_t offset; + rv = GetStartOffset(mFirstSelectedRange, + &offset, GetLineBreakType(aEvent)); + NS_ENSURE_SUCCESS(rv, rv); + if (offset == aEvent->mInput.mOffset) { + rv = ConvertToRootRelativeOffset(caretFrame, caretRect); + NS_ENSURE_SUCCESS(rv, rv); + nscoord appUnitsPerDevPixel = + caretFrame->PresContext()->AppUnitsPerDevPixel(); + aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect( + caretRect.ToOutsidePixels(appUnitsPerDevPixel)); + // Returning empty rect may cause native IME confused, let's make sure + // to return non-empty rect. + EnsureNonEmptyRect(aEvent->mReply.mRect); + aEvent->mReply.mWritingMode = caretFrame->GetWritingMode(); + aEvent->mReply.mOffset = aEvent->mInput.mOffset; + aEvent->mSucceeded = true; + return NS_OK; + } + } + } + + // Otherwise, we should guess the caret rect from the character's rect. + WidgetQueryContentEvent queryTextRectEvent(eQueryTextRect, *aEvent); + WidgetQueryContentEvent::Options options(*aEvent); + queryTextRectEvent.InitForQueryTextRect(aEvent->mInput.mOffset, 1, options); + rv = OnQueryTextRect(&queryTextRectEvent); + if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!queryTextRectEvent.mSucceeded)) { + return NS_ERROR_FAILURE; + } + queryTextRectEvent.mReply.mString.Truncate(); + aEvent->mReply = queryTextRectEvent.mReply; + if (aEvent->GetWritingMode().IsVertical()) { + aEvent->mReply.mRect.height = 1; + } else { + aEvent->mReply.mRect.width = 1; + } + // Returning empty rect may cause native IME confused, let's make sure to + // return non-empty rect. + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +ContentEventHandler::OnQueryContentState(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +ContentEventHandler::OnQuerySelectionAsTransferable( + WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + + if (!aEvent->mReply.mHasSelection) { + aEvent->mSucceeded = true; + aEvent->mReply.mTransferable = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIDocument> doc = mPresShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + rv = nsCopySupport::GetTransferableForSelection( + mSelection, doc, getter_AddRefs(aEvent->mReply.mTransferable)); + NS_ENSURE_SUCCESS(rv, rv); + + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +ContentEventHandler::OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent) +{ + nsresult rv = Init(aEvent); + if (NS_FAILED(rv)) { + return rv; + } + + aEvent->mReply.mOffset = aEvent->mReply.mTentativeCaretOffset = + WidgetQueryContentEvent::NOT_FOUND; + + nsIFrame* rootFrame = mPresShell->GetRootFrame(); + NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE); + nsIWidget* rootWidget = rootFrame->GetNearestWidget(); + NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE); + + // The root frame's widget might be different, e.g., the event was fired on + // a popup but the rootFrame is the document root. + if (rootWidget != aEvent->mWidget) { + NS_PRECONDITION(aEvent->mWidget, "The event must have the widget"); + nsView* view = nsView::GetViewFor(aEvent->mWidget); + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + rootFrame = view->GetFrame(); + NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE); + rootWidget = rootFrame->GetNearestWidget(); + NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE); + } + + WidgetQueryContentEvent eventOnRoot(true, eQueryCharacterAtPoint, + rootWidget); + eventOnRoot.mUseNativeLineBreak = aEvent->mUseNativeLineBreak; + eventOnRoot.mRefPoint = aEvent->mRefPoint; + if (rootWidget != aEvent->mWidget) { + eventOnRoot.mRefPoint += aEvent->mWidget->WidgetToScreenOffset() - + rootWidget->WidgetToScreenOffset(); + } + nsPoint ptInRoot = + nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame); + + nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot); + if (!targetFrame || !targetFrame->GetContent() || + !nsContentUtils::ContentIsDescendantOf(targetFrame->GetContent(), + mRootContent)) { + // There is no character at the point. + aEvent->mSucceeded = true; + return NS_OK; + } + nsPoint ptInTarget = ptInRoot + rootFrame->GetOffsetToCrossDoc(targetFrame); + int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel(); + int32_t targetAPD = targetFrame->PresContext()->AppUnitsPerDevPixel(); + ptInTarget = ptInTarget.ScaleToOtherAppUnits(rootAPD, targetAPD); + + nsIFrame::ContentOffsets tentativeCaretOffsets = + targetFrame->GetContentOffsetsFromPoint(ptInTarget); + if (!tentativeCaretOffsets.content || + !nsContentUtils::ContentIsDescendantOf(tentativeCaretOffsets.content, + mRootContent)) { + // There is no character nor tentative caret point at the point. + aEvent->mSucceeded = true; + return NS_OK; + } + + rv = GetFlatTextLengthInRange(NodePosition(mRootContent, 0), + NodePosition(tentativeCaretOffsets), + mRootContent, + &aEvent->mReply.mTentativeCaretOffset, + GetLineBreakType(aEvent)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (targetFrame->GetType() != nsGkAtoms::textFrame) { + // There is no character at the point but there is tentative caret point. + aEvent->mSucceeded = true; + return NS_OK; + } + + MOZ_ASSERT( + aEvent->mReply.mTentativeCaretOffset != WidgetQueryContentEvent::NOT_FOUND, + "The point is inside a character bounding box. Why tentative caret point " + "hasn't been found?"); + + nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame); + nsIFrame::ContentOffsets contentOffsets = + textframe->GetCharacterOffsetAtFramePoint(ptInTarget); + NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE); + uint32_t offset; + rv = GetFlatTextLengthInRange(NodePosition(mRootContent, 0), + NodePosition(contentOffsets), + mRootContent, &offset, + GetLineBreakType(aEvent)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + WidgetQueryContentEvent textRect(true, eQueryTextRect, aEvent->mWidget); + WidgetQueryContentEvent::Options options(*aEvent); + textRect.InitForQueryTextRect(offset, 1, options); + rv = OnQueryTextRect(&textRect); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(textRect.mSucceeded, NS_ERROR_FAILURE); + + // currently, we don't need to get the actual text. + aEvent->mReply.mOffset = offset; + aEvent->mReply.mRect = textRect.mReply.mRect; + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +ContentEventHandler::OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent) +{ + NS_ASSERTION(aEvent, "aEvent must not be null"); + + nsresult rv = InitBasic(); + if (NS_FAILED(rv)) { + return rv; + } + + aEvent->mSucceeded = false; + aEvent->mReply.mWidgetIsHit = false; + + NS_ENSURE_TRUE(aEvent->mWidget, NS_ERROR_FAILURE); + + nsIDocument* doc = mPresShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + nsIFrame* docFrame = mPresShell->GetRootFrame(); + NS_ENSURE_TRUE(docFrame, NS_ERROR_FAILURE); + + LayoutDeviceIntPoint eventLoc = + aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset(); + nsIntRect docFrameRect = docFrame->GetScreenRect(); // Returns CSS pixels + CSSIntPoint eventLocCSS( + mPresContext->DevPixelsToIntCSSPixels(eventLoc.x) - docFrameRect.x, + mPresContext->DevPixelsToIntCSSPixels(eventLoc.y) - docFrameRect.y); + + Element* contentUnderMouse = + doc->ElementFromPointHelper(eventLocCSS.x, eventLocCSS.y, false, false); + if (contentUnderMouse) { + nsIWidget* targetWidget = nullptr; + nsIFrame* targetFrame = contentUnderMouse->GetPrimaryFrame(); + nsIObjectFrame* pluginFrame = do_QueryFrame(targetFrame); + if (pluginFrame) { + targetWidget = pluginFrame->GetWidget(); + } else if (targetFrame) { + targetWidget = targetFrame->GetNearestWidget(); + } + if (aEvent->mWidget == targetWidget) { + aEvent->mReply.mWidgetIsHit = true; + } + } + + aEvent->mSucceeded = true; + return NS_OK; +} + +/* static */ nsresult +ContentEventHandler::GetFlatTextLengthInRange( + const NodePosition& aStartPosition, + const NodePosition& aEndPosition, + nsIContent* aRootContent, + uint32_t* aLength, + LineBreakType aLineBreakType, + bool aIsRemovingNode /* = false */) +{ + if (NS_WARN_IF(!aRootContent) || NS_WARN_IF(!aStartPosition.IsValid()) || + NS_WARN_IF(!aEndPosition.IsValid()) || NS_WARN_IF(!aLength)) { + return NS_ERROR_INVALID_ARG; + } + + if (aStartPosition == aEndPosition) { + *aLength = 0; + return NS_OK; + } + + // Don't create nsContentIterator instance until it's really necessary since + // destroying without initializing causes unexpected NS_ASSERTION() call. + nsCOMPtr<nsIContentIterator> iter; + + // Working with ContentIterator, we may need to adjust the end position for + // including it forcibly. + NodePosition endPosition(aEndPosition); + + // This may be called for retrieving the text of removed nodes. Even in this + // case, the node thinks it's still in the tree because UnbindFromTree() will + // be called after here. However, the node was already removed from the + // array of children of its parent. So, be careful to handle this case. + if (aIsRemovingNode) { + DebugOnly<nsIContent*> parent = aStartPosition.mNode->GetParent(); + MOZ_ASSERT(parent && parent->IndexOf(aStartPosition.mNode) == -1, + "At removing the node, the node shouldn't be in the array of children " + "of its parent"); + MOZ_ASSERT(aStartPosition.mNode == endPosition.mNode, + "At removing the node, start and end node should be same"); + MOZ_ASSERT(aStartPosition.mOffset == 0, + "When the node is being removed, the start offset should be 0"); + MOZ_ASSERT(static_cast<uint32_t>(endPosition.mOffset) == + endPosition.mNode->GetChildCount(), + "When the node is being removed, the end offset should be child count"); + iter = NS_NewPreContentIterator(); + nsresult rv = iter->Init(aStartPosition.mNode); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + RefPtr<nsRange> prev = new nsRange(aRootContent); + nsresult rv = aStartPosition.SetToRangeStart(prev); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // When the end position is immediately after non-root element's open tag, + // we need to include a line break caused by the open tag. + if (endPosition.mNode != aRootContent && + endPosition.IsImmediatelyAfterOpenTag()) { + if (endPosition.mNode->HasChildren()) { + // When the end node has some children, move the end position to before + // the open tag of its first child. + nsINode* firstChild = endPosition.mNode->GetFirstChild(); + if (NS_WARN_IF(!firstChild)) { + return NS_ERROR_FAILURE; + } + endPosition = NodePositionBefore(firstChild, 0); + } else { + // When the end node is empty, move the end position after the node. + nsIContent* parentContent = endPosition.mNode->GetParent(); + if (NS_WARN_IF(!parentContent)) { + return NS_ERROR_FAILURE; + } + int32_t indexInParent = parentContent->IndexOf(endPosition.mNode); + if (NS_WARN_IF(indexInParent < 0)) { + return NS_ERROR_FAILURE; + } + endPosition = NodePositionBefore(parentContent, indexInParent + 1); + } + } + + if (endPosition.OffsetIsValid()) { + // Offset is within node's length; set end of range to that offset + rv = endPosition.SetToRangeEnd(prev); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + iter = NS_NewPreContentIterator(); + rv = iter->Init(prev); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else if (endPosition.mNode != aRootContent) { + // Offset is past node's length; set end of range to end of node + rv = endPosition.SetToRangeEndAfter(prev); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + iter = NS_NewPreContentIterator(); + rv = iter->Init(prev); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + // Offset is past the root node; set end of range to end of root node + iter = NS_NewPreContentIterator(); + rv = iter->Init(aRootContent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + *aLength = 0; + for (; !iter->IsDone(); iter->Next()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + if (!node->IsContent()) { + continue; + } + nsIContent* content = node->AsContent(); + + if (node->IsNodeOfType(nsINode::eTEXT)) { + // Note: our range always starts from offset 0 + if (node == endPosition.mNode) { + *aLength += GetTextLength(content, aLineBreakType, + endPosition.mOffset); + } else { + *aLength += GetTextLength(content, aLineBreakType); + } + } else if (ShouldBreakLineBefore(content, aRootContent)) { + // If the start position is start of this node but doesn't include the + // open tag, don't append the line break length. + if (node == aStartPosition.mNode && !aStartPosition.IsBeforeOpenTag()) { + continue; + } + // If the end position is before the open tag, don't append the line + // break length. + if (node == endPosition.mNode && endPosition.IsBeforeOpenTag()) { + continue; + } + *aLength += GetBRLength(aLineBreakType); + } + } + return NS_OK; +} + +nsresult +ContentEventHandler::GetStartOffset(nsRange* aRange, + uint32_t* aOffset, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aRange); + return GetFlatTextLengthInRange( + NodePosition(mRootContent, 0), + NodePosition(aRange->GetStartParent(), aRange->StartOffset()), + mRootContent, aOffset, aLineBreakType); +} + +nsresult +ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aRange) +{ + MOZ_ASSERT(aRange); + MOZ_ASSERT(aRange->Collapsed()); + + if (!aRange || !aRange->Collapsed()) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsINode> parentNode = aRange->GetStartParent(); + int32_t offsetInParentNode = aRange->StartOffset(); + if (NS_WARN_IF(!parentNode) || NS_WARN_IF(offsetInParentNode < 0)) { + return NS_ERROR_INVALID_ARG; + } + + // If the node is text node, we don't need to modify aRange. + if (parentNode->IsNodeOfType(nsINode::eTEXT)) { + return NS_OK; + } + + // If the parent is not a text node but it has a text node at the offset, + // we should adjust the range into the text node. + // NOTE: This is emulating similar situation of EditorBase. + nsINode* childNode = nullptr; + int32_t offsetInChildNode = -1; + if (!offsetInParentNode && parentNode->HasChildren()) { + // If the range is the start of the parent, adjusted the range to the + // start of the first child. + childNode = parentNode->GetFirstChild(); + offsetInChildNode = 0; + } else if (static_cast<uint32_t>(offsetInParentNode) < + parentNode->GetChildCount()) { + // If the range is next to a child node, adjust the range to the end of + // the previous child. + childNode = parentNode->GetChildAt(offsetInParentNode - 1); + offsetInChildNode = childNode->Length(); + } + + // But if the found node isn't a text node, we cannot modify the range. + if (!childNode || !childNode->IsNodeOfType(nsINode::eTEXT) || + NS_WARN_IF(offsetInChildNode < 0)) { + return NS_OK; + } + + nsresult rv = aRange->Set(childNode, offsetInChildNode, + childNode, offsetInChildNode); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +nsresult +ContentEventHandler::GetStartFrameAndOffset(const nsRange* aRange, + nsIFrame*& aFrame, + int32_t& aOffsetInFrame) +{ + MOZ_ASSERT(aRange); + + aFrame = nullptr; + aOffsetInFrame = -1; + + nsINode* node = aRange->GetStartParent(); + if (NS_WARN_IF(!node) || + NS_WARN_IF(!node->IsNodeOfType(nsINode::eCONTENT))) { + return NS_ERROR_FAILURE; + } + nsIContent* content = static_cast<nsIContent*>(node); + RefPtr<nsFrameSelection> fs = mPresShell->FrameSelection(); + aFrame = fs->GetFrameForNodeOffset(content, aRange->StartOffset(), + fs->GetHint(), &aOffsetInFrame); + if (NS_WARN_IF(!aFrame)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame, + nsRect& aRect) +{ + NS_ASSERTION(aFrame, "aFrame must not be null"); + + nsPresContext* thisPC = aFrame->PresContext(); + nsPresContext* rootPC = thisPC->GetRootPresContext(); + if (NS_WARN_IF(!rootPC)) { + return NS_ERROR_FAILURE; + } + nsIFrame* rootFrame = rootPC->PresShell()->GetRootFrame(); + if (NS_WARN_IF(!rootFrame)) { + return NS_ERROR_FAILURE; + } + + aRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, aRect, rootFrame); + + // TransformFrameRectToAncestor returned the rect in the ancestor's appUnits, + // but we want it in aFrame's units (in case of different full-zoom factors), + // so convert back. + aRect = aRect.ScaleToOtherAppUnitsRoundOut(rootPC->AppUnitsPerDevPixel(), + thisPC->AppUnitsPerDevPixel()); + + return NS_OK; +} + +static void AdjustRangeForSelection(nsIContent* aRoot, + nsINode** aNode, + int32_t* aNodeOffset) +{ + nsINode* node = *aNode; + int32_t nodeOffset = *aNodeOffset; + if (aRoot == node || NS_WARN_IF(!node->GetParent()) || + !node->IsNodeOfType(nsINode::eTEXT)) { + return; + } + + // When the offset is at the end of the text node, set it to after the + // text node, to make sure the caret is drawn on a new line when the last + // character of the text node is '\n' in <textarea>. + int32_t textLength = + static_cast<int32_t>(static_cast<nsIContent*>(node)->TextLength()); + MOZ_ASSERT(nodeOffset <= textLength, "Offset is past length of text node"); + if (nodeOffset != textLength) { + return; + } + + nsIContent* aRootParent = aRoot->GetParent(); + if (NS_WARN_IF(!aRootParent)) { + return; + } + // If the root node is not an anonymous div of <textarea>, we don't need to + // do this hack. If you did this, ContentEventHandler couldn't distinguish + // if the range includes open tag of the next node in some cases, e.g., + // textNode]<p></p> vs. textNode<p>]</p> + if (!aRootParent->IsHTMLElement(nsGkAtoms::textarea)) { + return; + } + + *aNode = node->GetParent(); + MOZ_ASSERT((*aNode)->IndexOf(node) != -1); + *aNodeOffset = (*aNode)->IndexOf(node) + 1; +} + +nsresult +ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent) +{ + aEvent->mSucceeded = false; + + // Get selection to manipulate + // XXX why do we need to get them from ISM? This method should work fine + // without ISM. + nsCOMPtr<nsISelection> sel; + nsresult rv = + IMEStateManager::GetFocusSelectionAndRoot(getter_AddRefs(sel), + getter_AddRefs(mRootContent)); + mSelection = sel ? sel->AsSelection() : nullptr; + if (rv != NS_ERROR_NOT_AVAILABLE) { + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = Init(aEvent); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Get range from offset and length + RefPtr<nsRange> range = new nsRange(mRootContent); + rv = SetRangeFromFlatTextOffset(range, aEvent->mOffset, aEvent->mLength, + GetLineBreakType(aEvent), + aEvent->mExpandToClusterBoundary); + NS_ENSURE_SUCCESS(rv, rv); + + nsINode* startNode = range->GetStartParent(); + nsINode* endNode = range->GetEndParent(); + int32_t startNodeOffset = range->StartOffset(); + int32_t endNodeOffset = range->EndOffset(); + AdjustRangeForSelection(mRootContent, &startNode, &startNodeOffset); + AdjustRangeForSelection(mRootContent, &endNode, &endNodeOffset); + if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode) || + NS_WARN_IF(startNodeOffset < 0) || NS_WARN_IF(endNodeOffset < 0)) { + return NS_ERROR_UNEXPECTED; + } + + mSelection->StartBatchChanges(); + + // Clear selection first before setting + rv = mSelection->RemoveAllRanges(); + // Need to call EndBatchChanges at the end even if call failed + if (NS_SUCCEEDED(rv)) { + if (aEvent->mReversed) { + rv = mSelection->Collapse(endNode, endNodeOffset); + } else { + rv = mSelection->Collapse(startNode, startNodeOffset); + } + if (NS_SUCCEEDED(rv) && + (startNode != endNode || startNodeOffset != endNodeOffset)) { + if (aEvent->mReversed) { + rv = mSelection->Extend(startNode, startNodeOffset); + } else { + rv = mSelection->Extend(endNode, endNodeOffset); + } + } + } + + // Pass the eSetSelection events reason along with the BatchChange-end + // selection change notifications. + mSelection->EndBatchChangesInternal(aEvent->mReason); + NS_ENSURE_SUCCESS(rv, rv); + + mSelection->ScrollIntoViewInternal( + nsISelectionController::SELECTION_FOCUS_REGION, + false, nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis()); + aEvent->mSucceeded = true; + return NS_OK; +} + +nsRect +ContentEventHandler::FrameRelativeRect::RectRelativeTo( + nsIFrame* aDestFrame) const +{ + if (!mBaseFrame || NS_WARN_IF(!aDestFrame)) { + return nsRect(); + } + + if (NS_WARN_IF(aDestFrame->PresContext() != mBaseFrame->PresContext())) { + return nsRect(); + } + + if (aDestFrame == mBaseFrame) { + return mRect; + } + + nsIFrame* rootFrame = mBaseFrame->PresContext()->PresShell()->GetRootFrame(); + nsRect baseFrameRectInRootFrame = + nsLayoutUtils::TransformFrameRectToAncestor(mBaseFrame, nsRect(), + rootFrame); + nsRect destFrameRectInRootFrame = + nsLayoutUtils::TransformFrameRectToAncestor(aDestFrame, nsRect(), + rootFrame); + nsPoint difference = + destFrameRectInRootFrame.TopLeft() - baseFrameRectInRootFrame.TopLeft(); + return mRect - difference; +} + +} // namespace mozilla diff --git a/dom/events/ContentEventHandler.h b/dom/events/ContentEventHandler.h new file mode 100644 index 000000000..31ec40caf --- /dev/null +++ b/dom/events/ContentEventHandler.h @@ -0,0 +1,433 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ContentEventHandler_h_ +#define mozilla_ContentEventHandler_h_ + +#include "mozilla/EventForwards.h" +#include "mozilla/dom/Selection.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsINode.h" +#include "nsISelectionController.h" +#include "nsRange.h" + +class nsPresContext; + +struct nsRect; + +namespace mozilla { + +enum LineBreakType +{ + LINE_BREAK_TYPE_NATIVE, + LINE_BREAK_TYPE_XP +}; + +/* + * Query Content Event Handler + * ContentEventHandler is a helper class for EventStateManager. + * The platforms request some content informations, e.g., the selected text, + * the offset of the selected text and the text for specified range. + * This class answers to NS_QUERY_* events from actual contents. + */ + +class MOZ_STACK_CLASS ContentEventHandler +{ +public: + typedef dom::Selection Selection; + + explicit ContentEventHandler(nsPresContext* aPresContext); + + // Handle aEvent in the current process. + nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); + + // eQuerySelectedText event handler + nsresult OnQuerySelectedText(WidgetQueryContentEvent* aEvent); + // eQueryTextContent event handler + nsresult OnQueryTextContent(WidgetQueryContentEvent* aEvent); + // eQueryCaretRect event handler + nsresult OnQueryCaretRect(WidgetQueryContentEvent* aEvent); + // eQueryTextRect event handler + nsresult OnQueryTextRect(WidgetQueryContentEvent* aEvent); + // eQueryTextRectArray event handler + nsresult OnQueryTextRectArray(WidgetQueryContentEvent* aEvent); + // eQueryEditorRect event handler + nsresult OnQueryEditorRect(WidgetQueryContentEvent* aEvent); + // eQueryContentState event handler + nsresult OnQueryContentState(WidgetQueryContentEvent* aEvent); + // eQuerySelectionAsTransferable event handler + nsresult OnQuerySelectionAsTransferable(WidgetQueryContentEvent* aEvent); + // eQueryCharacterAtPoint event handler + nsresult OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent); + // eQueryDOMWidgetHittest event handler + nsresult OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent); + + // NS_SELECTION_* event + nsresult OnSelectionEvent(WidgetSelectionEvent* aEvent); + +protected: + nsPresContext* mPresContext; + nsCOMPtr<nsIPresShell> mPresShell; + // mSelection is typically normal selection but if OnQuerySelectedText() + // is called, i.e., handling eQuerySelectedText, it's the specified selection + // by WidgetQueryContentEvent::mInput::mSelectionType. + RefPtr<Selection> mSelection; + // mFirstSelectedRange is the first selected range of mSelection. If + // mSelection is normal selection, this must not be nullptr if Init() + // succeed. Otherwise, this may be nullptr if there are no selection + // ranges. + RefPtr<nsRange> mFirstSelectedRange; + nsCOMPtr<nsIContent> mRootContent; + + nsresult Init(WidgetQueryContentEvent* aEvent); + nsresult Init(WidgetSelectionEvent* aEvent); + + nsresult InitBasic(); + nsresult InitCommon(SelectionType aSelectionType = SelectionType::eNormal); + /** + * InitRootContent() computes the root content of current focused editor. + * + * @param aNormalSelection This must be a Selection instance whose type is + * SelectionType::eNormal. + */ + nsresult InitRootContent(Selection* aNormalSelection); + +public: + // FlatText means the text that is generated from DOM tree. The BR elements + // are replaced to native linefeeds. Other elements are ignored. + + // NodePosition stores a pair of node and offset in the node. + // When mNode is an element and mOffset is 0, the start position means after + // the open tag of mNode. + // This is useful to receive one or more sets of them instead of nsRange. + struct NodePosition + { + nsCOMPtr<nsINode> mNode; + int32_t mOffset; + // Only when mNode is an element node and mOffset is 0, mAfterOpenTag is + // referred. + bool mAfterOpenTag; + + NodePosition() + : mOffset(-1) + , mAfterOpenTag(true) + { + } + + NodePosition(nsINode* aNode, int32_t aOffset) + : mNode(aNode) + , mOffset(aOffset) + , mAfterOpenTag(true) + { + } + + explicit NodePosition(const nsIFrame::ContentOffsets& aContentOffsets) + : mNode(aContentOffsets.content) + , mOffset(aContentOffsets.offset) + , mAfterOpenTag(true) + { + } + + protected: + NodePosition(nsINode* aNode, int32_t aOffset, bool aAfterOpenTag) + : mNode(aNode) + , mOffset(aOffset) + , mAfterOpenTag(aAfterOpenTag) + { + } + + public: + bool operator==(const NodePosition& aOther) const + { + return mNode == aOther.mNode && + mOffset == aOther.mOffset && + mAfterOpenTag == aOther.mAfterOpenTag; + } + + bool IsValid() const + { + return mNode && mOffset >= 0; + } + bool OffsetIsValid() const + { + return IsValid() && static_cast<uint32_t>(mOffset) <= mNode->Length(); + } + bool IsBeforeOpenTag() const + { + return IsValid() && mNode->IsElement() && !mOffset && !mAfterOpenTag; + } + bool IsImmediatelyAfterOpenTag() const + { + return IsValid() && mNode->IsElement() && !mOffset && mAfterOpenTag; + } + nsresult SetToRangeStart(nsRange* aRange) const + { + nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode)); + return aRange->SetStart(domNode, mOffset); + } + nsresult SetToRangeEnd(nsRange* aRange) const + { + nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode)); + return aRange->SetEnd(domNode, mOffset); + } + nsresult SetToRangeEndAfter(nsRange* aRange) const + { + nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode)); + return aRange->SetEndAfter(domNode); + } + }; + + // NodePositionBefore isn't good name if mNode isn't an element node nor + // mOffset is not 0, though, when mNode is an element node and mOffset is 0, + // this is treated as before the open tag of mNode. + struct NodePositionBefore final : public NodePosition + { + NodePositionBefore(nsINode* aNode, int32_t aOffset) + : NodePosition(aNode, aOffset, false) + { + } + }; + + // Get the flatten text length in the range. + // @param aStartPosition Start node and offset in the node of the range. + // @param aEndPosition End node and offset in the node of the range. + // @param aRootContent The root content of the editor or document. + // aRootContent won't cause any text including + // line breaks. + // @param aLength The result of the flatten text length of the + // range. + // @param aLineBreakType Whether this computes flatten text length with + // native line breakers on the platform or + // with XP line breaker (\n). + // @param aIsRemovingNode Should be true only when this is called from + // nsIMutationObserver::ContentRemoved(). + // When this is true, aStartPosition.mNode should + // be the root node of removing nodes and mOffset + // should be 0 and aEndPosition.mNode should be + // same as aStartPosition.mNode and mOffset should + // be number of the children of mNode. + static nsresult GetFlatTextLengthInRange(const NodePosition& aStartPosition, + const NodePosition& aEndPosition, + nsIContent* aRootContent, + uint32_t* aLength, + LineBreakType aLineBreakType, + bool aIsRemovingNode = false); + // Computes the native text length between aStartOffset and aEndOffset of + // aContent. aContent must be a text node. + static uint32_t GetNativeTextLength(nsIContent* aContent, + uint32_t aStartOffset, + uint32_t aEndOffset); + // Get the native text length of aContent. aContent must be a text node. + static uint32_t GetNativeTextLength(nsIContent* aContent, + uint32_t aMaxLength = UINT32_MAX); + // Get the native text length which is inserted before aContent. + // aContent should be an element. + static uint32_t GetNativeTextLengthBefore(nsIContent* aContent, + nsINode* aRootNode); + +protected: + // Get the text length of aContent. aContent must be a text node. + static uint32_t GetTextLength(nsIContent* aContent, + LineBreakType aLineBreakType, + uint32_t aMaxLength = UINT32_MAX); + // Get the text length of a given range of a content node in + // the given line break type. + static uint32_t GetTextLengthInRange(nsIContent* aContent, + uint32_t aXPStartOffset, + uint32_t aXPEndOffset, + LineBreakType aLineBreakType); + // Get the contents in aContent (meaning all children of aContent) as plain + // text. E.g., specifying mRootContent gets whole text in it. + // Note that the result is not same as .textContent. The result is + // optimized for native IMEs. For example, <br> element and some block + // elements causes "\n" (or "\r\n"), see also ShouldBreakLineBefore(). + nsresult GenerateFlatTextContent(nsIContent* aContent, + nsAFlatString& aString, + LineBreakType aLineBreakType); + // Get the contents of aRange as plain text. + nsresult GenerateFlatTextContent(nsRange* aRange, + nsAFlatString& aString, + LineBreakType aLineBreakType); + // Get offset of start of aRange. Note that the result includes the length + // of line breaker caused by the start of aContent because aRange never + // includes the line breaker caused by its start node. + nsresult GetStartOffset(nsRange* aRange, + uint32_t* aOffset, + LineBreakType aLineBreakType); + // Check if we should insert a line break before aContent. + // This should return false only when aContent is an html element which + // is typically used in a paragraph like <em>. + static bool ShouldBreakLineBefore(nsIContent* aContent, + nsINode* aRootNode); + // Get the line breaker length. + static inline uint32_t GetBRLength(LineBreakType aLineBreakType); + static LineBreakType GetLineBreakType(WidgetQueryContentEvent* aEvent); + static LineBreakType GetLineBreakType(WidgetSelectionEvent* aEvent); + static LineBreakType GetLineBreakType(bool aUseNativeLineBreak); + // Returns focused content (including its descendant documents). + nsIContent* GetFocusedContent(); + // Returns true if the content is a plugin host. + bool IsPlugin(nsIContent* aContent); + // QueryContentRect() sets the rect of aContent's frame(s) to aEvent. + nsresult QueryContentRect(nsIContent* aContent, + WidgetQueryContentEvent* aEvent); + // Make the DOM range from the offset of FlatText and the text length. + // If aExpandToClusterBoundaries is true, the start offset and the end one are + // expanded to nearest cluster boundaries. + nsresult SetRangeFromFlatTextOffset(nsRange* aRange, + uint32_t aOffset, + uint32_t aLength, + LineBreakType aLineBreakType, + bool aExpandToClusterBoundaries, + uint32_t* aNewOffset = nullptr, + nsIContent** aLastTextNode = nullptr); + // If the aRange isn't in text node but next to a text node, this method + // modifies it in the text node. Otherwise, not modified. + nsresult AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aCollapsedRange); + // Find the first frame for the range and get the start offset in it. + nsresult GetStartFrameAndOffset(const nsRange* aRange, + nsIFrame*& aFrame, + int32_t& aOffsetInFrame); + // Convert the frame relative offset to be relative to the root frame of the + // root presContext (but still measured in appUnits of aFrame's presContext). + nsresult ConvertToRootRelativeOffset(nsIFrame* aFrame, + nsRect& aRect); + // Expand aXPOffset to the nearest offset in cluster boundary. aForward is + // true, it is expanded to forward. + nsresult ExpandToClusterBoundary(nsIContent* aContent, bool aForward, + uint32_t* aXPOffset); + + typedef nsTArray<mozilla::FontRange> FontRangeArray; + static void AppendFontRanges(FontRangeArray& aFontRanges, + nsIContent* aContent, + int32_t aBaseOffset, + int32_t aXPStartOffset, + int32_t aXPEndOffset, + LineBreakType aLineBreakType); + nsresult GenerateFlatFontRanges(nsRange* aRange, + FontRangeArray& aFontRanges, + uint32_t& aLength, + LineBreakType aLineBreakType); + nsresult QueryTextRectByRange(nsRange* aRange, + LayoutDeviceIntRect& aRect, + WritingMode& aWritingMode); + + // Returns a node and position in the node for computing text rect. + NodePosition GetNodePositionHavingFlatText(const NodePosition& aNodePosition); + NodePosition GetNodePositionHavingFlatText(nsINode* aNode, + int32_t aNodeOffset); + + struct MOZ_STACK_CLASS FrameAndNodeOffset final + { + // mFrame is safe since this can live in only stack class and + // ContentEventHandler doesn't modify layout after + // ContentEventHandler::Init() flushes pending layout. In other words, + // this struct shouldn't be used before calling + // ContentEventHandler::Init(). + nsIFrame* mFrame; + // offset in the node of mFrame + int32_t mOffsetInNode; + + FrameAndNodeOffset() + : mFrame(nullptr) + , mOffsetInNode(-1) + { + } + + FrameAndNodeOffset(nsIFrame* aFrame, int32_t aStartOffsetInNode) + : mFrame(aFrame) + , mOffsetInNode(aStartOffsetInNode) + { + } + + nsIFrame* operator->() { return mFrame; } + const nsIFrame* operator->() const { return mFrame; } + operator nsIFrame*() { return mFrame; } + operator const nsIFrame*() const { return mFrame; } + bool IsValid() const { return mFrame && mOffsetInNode >= 0; } + }; + // Get first frame after the start of the given range for computing text rect. + // This returns invalid FrameAndNodeOffset if there is no content which + // should affect to computing text rect in the range. mOffsetInNode is start + // offset in the frame. + FrameAndNodeOffset GetFirstFrameInRangeForTextRect(nsRange* aRange); + + // Get last frame before the end of the given range for computing text rect. + // This returns invalid FrameAndNodeOffset if there is no content which + // should affect to computing text rect in the range. mOffsetInNode is end + // offset in the frame. + FrameAndNodeOffset GetLastFrameInRangeForTextRect(nsRange* aRange); + + struct MOZ_STACK_CLASS FrameRelativeRect final + { + // mRect is relative to the mBaseFrame's position. + nsRect mRect; + nsIFrame* mBaseFrame; + + FrameRelativeRect() + : mBaseFrame(nullptr) + { + } + + explicit FrameRelativeRect(nsIFrame* aBaseFrame) + : mBaseFrame(aBaseFrame) + { + } + + FrameRelativeRect(const nsRect& aRect, nsIFrame* aBaseFrame) + : mRect(aRect) + , mBaseFrame(aBaseFrame) + { + } + + bool IsValid() const { return mBaseFrame != nullptr; } + + // Returns an nsRect relative to aBaseFrame instead of mBaseFrame. + nsRect RectRelativeTo(nsIFrame* aBaseFrame) const; + }; + + // Returns a rect for line breaker before the node of aFrame (If aFrame is + // a <br> frame or a block level frame, it causes a line break at its + // element's open tag, see also ShouldBreakLineBefore()). Note that this + // doesn't check if aFrame should cause line break in non-debug build. + FrameRelativeRect GetLineBreakerRectBefore(nsIFrame* aFrame); + + // Returns a line breaker rect after aTextContent as there is a line breaker + // immediately after aTextContent. This is useful when following block + // element causes a line break before it and it needs to compute the line + // breaker's rect. For example, if there is |<p>abc</p><p>def</p>|, the + // rect of 2nd <p>'s line breaker should be at right of "c" in the first + // <p>, not the start of 2nd <p>. The result is relative to the last text + // frame which represents the last character of aTextContent. + FrameRelativeRect GuessLineBreakerRectAfter(nsIContent* aTextContent); + + // Returns a guessed first rect. I.e., it may be different from actual + // caret when selection is collapsed at start of aFrame. For example, this + // guess the caret rect only with the content box of aFrame and its font + // height like: + // +-aFrame----------------- (border box) + // | + // | +--------------------- (content box) + // | | I + // ^ guessed caret rect + // However, actual caret is computed with more information like line-height, + // child frames of aFrame etc. But this does not emulate actual caret + // behavior exactly for simpler and faster code because it's difficult and + // we're not sure it's worthwhile to do it with complicated implementation. + FrameRelativeRect GuessFirstCaretRectIn(nsIFrame* aFrame); + + // Make aRect non-empty. If width and/or height is 0, these methods set them + // to 1. Note that it doesn't set nsRect's width nor height to one device + // pixel because using nsRect::ToOutsidePixels() makes actual width or height + // to 2 pixels because x and y may not be aligned to device pixels. + void EnsureNonEmptyRect(nsRect& aRect) const; + void EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const; +}; + +} // namespace mozilla + +#endif // mozilla_ContentEventHandler_h_ diff --git a/dom/events/CustomEvent.cpp b/dom/events/CustomEvent.cpp new file mode 100644 index 000000000..3b3fb3fdd --- /dev/null +++ b/dom/events/CustomEvent.cpp @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CustomEvent.h" +#include "mozilla/dom/CustomEventBinding.h" + +#include "mozilla/dom/BindingUtils.h" +#include "nsContentUtils.h" +#include "nsIXPConnect.h" + +using namespace mozilla; +using namespace mozilla::dom; + +CustomEvent::CustomEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent) + : Event(aOwner, aPresContext, aEvent) + , mDetail(JS::NullValue()) +{ + mozilla::HoldJSObjects(this); +} + +CustomEvent::~CustomEvent() +{ + mozilla::DropJSObjects(this); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(CustomEvent) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CustomEvent, Event) + tmp->mDetail.setUndefined(); + mozilla::DropJSObjects(this); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CustomEvent, Event) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CustomEvent, Event) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDetail) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_ADDREF_INHERITED(CustomEvent, Event) +NS_IMPL_RELEASE_INHERITED(CustomEvent, Event) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CustomEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMCustomEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +already_AddRefed<CustomEvent> +CustomEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const CustomEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<CustomEvent> e = new CustomEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + JS::Rooted<JS::Value> detail(aGlobal.Context(), aParam.mDetail); + e->InitCustomEvent(aGlobal.Context(), aType, aParam.mBubbles, aParam.mCancelable, detail, aRv); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +JSObject* +CustomEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return mozilla::dom::CustomEventBinding::Wrap(aCx, this, aGivenProto); +} + +NS_IMETHODIMP +CustomEvent::InitCustomEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsIVariant* aDetail) +{ + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + + AutoJSAPI jsapi; + NS_ENSURE_STATE(jsapi.Init(GetParentObject())); + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> detail(cx); + + if (!aDetail) { + detail = JS::NullValue(); + } else if (NS_WARN_IF(!VariantToJsval(cx, aDetail, &detail))) { + JS_ClearPendingException(cx); + return NS_ERROR_FAILURE; + } + + Event::InitEvent(aType, aCanBubble, aCancelable); + mDetail = detail; + + return NS_OK; +} + +void +CustomEvent::InitCustomEvent(JSContext* aCx, + const nsAString& aType, + bool aCanBubble, + bool aCancelable, + JS::Handle<JS::Value> aDetail, + ErrorResult& aRv) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + Event::InitEvent(aType, aCanBubble, aCancelable); + mDetail = aDetail; +} + +NS_IMETHODIMP +CustomEvent::GetDetail(nsIVariant** aDetail) +{ + if (mDetail.isNull()) { + *aDetail = nullptr; + return NS_OK; + } + + AutoJSAPI jsapi; + NS_ENSURE_STATE(jsapi.Init(GetParentObject())); + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> detail(cx, mDetail); + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + + if (NS_WARN_IF(!xpc)) { + return NS_ERROR_FAILURE; + } + + return xpc->JSToVariant(cx, detail, aDetail); +} + +void +CustomEvent::GetDetail(JSContext* aCx, + JS::MutableHandle<JS::Value> aRetval) +{ + aRetval.set(mDetail); +} + +already_AddRefed<CustomEvent> +NS_NewDOMCustomEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent) +{ + RefPtr<CustomEvent> it = + new CustomEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/CustomEvent.h b/dom/events/CustomEvent.h new file mode 100644 index 000000000..d14243cff --- /dev/null +++ b/dom/events/CustomEvent.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CustomEvent_h__ +#define CustomEvent_h__ + +#include "mozilla/dom/Event.h" +#include "nsIDOMCustomEvent.h" + +namespace mozilla { +namespace dom { + +struct CustomEventInit; + +class CustomEvent final : public Event, + public nsIDOMCustomEvent +{ +private: + virtual ~CustomEvent(); + + JS::Heap<JS::Value> mDetail; + +public: + explicit CustomEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext = nullptr, + mozilla::WidgetEvent* aEvent = nullptr); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(CustomEvent, Event) + NS_FORWARD_TO_EVENT + NS_DECL_NSIDOMCUSTOMEVENT + + static already_AddRefed<CustomEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const CustomEventInit& aParam, + ErrorResult& aRv); + + virtual JSObject* + WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + void + GetDetail(JSContext* aCx, + JS::MutableHandle<JS::Value> aRetval); + + void + InitCustomEvent(JSContext* aCx, + const nsAString& aType, + bool aCanBubble, + bool aCancelable, + JS::Handle<JS::Value> aDetail, + ErrorResult& aRv); +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::CustomEvent> +NS_NewDOMCustomEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent); + +#endif // CustomEvent_h__ diff --git a/dom/events/DOMEventTargetHelper.cpp b/dom/events/DOMEventTargetHelper.cpp new file mode 100644 index 000000000..f8a5227d6 --- /dev/null +++ b/dom/events/DOMEventTargetHelper.cpp @@ -0,0 +1,409 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsContentUtils.h" +#include "nsIDocument.h" +#include "mozilla/Sprintf.h" +#include "nsGlobalWindow.h" +#include "ScriptSettings.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/Likely.h" + +namespace mozilla { + +using namespace dom; + +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(DOMEventTargetHelper) + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + char name[512]; + nsAutoString uri; + if (tmp->mOwnerWindow && tmp->mOwnerWindow->GetExtantDoc()) { + Unused << tmp->mOwnerWindow->GetExtantDoc()->GetDocumentURI(uri); + } + + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(tmp, &participant); + + SprintfLiteral(name, "%s %s", + participant->ClassName(), + NS_ConvertUTF16toUTF8(uri).get()); + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(DOMEventTargetHelper, tmp->mRefCnt.get()) + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(DOMEventTargetHelper) + if (tmp->IsBlack() || tmp->IsCertainlyAliveForCC()) { + if (tmp->mListenerManager) { + tmp->mListenerManager->MarkForCC(); + } + if (!tmp->IsBlack() && tmp->PreservingWrapper()) { + // This marks the wrapper black. + tmp->GetWrapper(); + } + return true; + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(DOMEventTargetHelper) + return tmp->IsBlackAndDoesNotNeedTracing(tmp); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(DOMEventTargetHelper) + return tmp->IsBlack(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMEventTargetHelper) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget) + NS_INTERFACE_MAP_ENTRY(dom::EventTarget) + NS_INTERFACE_MAP_ENTRY(DOMEventTargetHelper) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(DOMEventTargetHelper, + LastRelease()) + +NS_IMPL_DOMTARGET_DEFAULTS(DOMEventTargetHelper) + +DOMEventTargetHelper::~DOMEventTargetHelper() +{ + if (nsPIDOMWindowInner* owner = GetOwner()) { + nsGlobalWindow::Cast(owner)->RemoveEventTargetObject(this); + } + if (mListenerManager) { + mListenerManager->Disconnect(); + } + ReleaseWrapper(this); +} + +void +DOMEventTargetHelper::BindToOwner(nsPIDOMWindowInner* aOwner) +{ + nsCOMPtr<nsIGlobalObject> glob = do_QueryInterface(aOwner); + BindToOwner(glob); +} + +void +DOMEventTargetHelper::BindToOwner(nsIGlobalObject* aOwner) +{ + nsCOMPtr<nsIGlobalObject> parentObject = do_QueryReferent(mParentObject); + if (parentObject) { + if (mOwnerWindow) { + nsGlobalWindow::Cast(mOwnerWindow)->RemoveEventTargetObject(this); + mOwnerWindow = nullptr; + } + mParentObject = nullptr; + mHasOrHasHadOwnerWindow = false; + } + if (aOwner) { + mParentObject = do_GetWeakReference(aOwner); + MOZ_ASSERT(mParentObject, "All nsIGlobalObjects must support nsISupportsWeakReference"); + // Let's cache the result of this QI for fast access and off main thread usage + mOwnerWindow = nsCOMPtr<nsPIDOMWindowInner>(do_QueryInterface(aOwner)).get(); + if (mOwnerWindow) { + mHasOrHasHadOwnerWindow = true; + nsGlobalWindow::Cast(mOwnerWindow)->AddEventTargetObject(this); + } + } +} + +void +DOMEventTargetHelper::BindToOwner(DOMEventTargetHelper* aOther) +{ + if (mOwnerWindow) { + nsGlobalWindow::Cast(mOwnerWindow)->RemoveEventTargetObject(this); + mOwnerWindow = nullptr; + mParentObject = nullptr; + mHasOrHasHadOwnerWindow = false; + } + if (aOther) { + mHasOrHasHadOwnerWindow = aOther->HasOrHasHadOwner(); + if (aOther->GetParentObject()) { + mParentObject = do_GetWeakReference(aOther->GetParentObject()); + MOZ_ASSERT(mParentObject, "All nsIGlobalObjects must support nsISupportsWeakReference"); + // Let's cache the result of this QI for fast access and off main thread usage + mOwnerWindow = nsCOMPtr<nsPIDOMWindowInner>(do_QueryInterface(aOther->GetParentObject())).get(); + if (mOwnerWindow) { + mHasOrHasHadOwnerWindow = true; + nsGlobalWindow::Cast(mOwnerWindow)->AddEventTargetObject(this); + } + } + } +} + +void +DOMEventTargetHelper::DisconnectFromOwner() +{ + mOwnerWindow = nullptr; + mParentObject = nullptr; + // Event listeners can't be handled anymore, so we can release them here. + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } +} + +nsPIDOMWindowInner* +DOMEventTargetHelper::GetWindowIfCurrent() const +{ + if (NS_FAILED(CheckInnerWindowCorrectness())) { + return nullptr; + } + + return GetOwner(); +} + +nsIDocument* +DOMEventTargetHelper::GetDocumentIfCurrent() const +{ + nsPIDOMWindowInner* win = GetWindowIfCurrent(); + if (!win) { + return nullptr; + } + + return win->GetDoc(); +} + +NS_IMETHODIMP +DOMEventTargetHelper::RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture) +{ + EventListenerManager* elm = GetExistingListenerManager(); + if (elm) { + elm->RemoveEventListener(aType, aListener, aUseCapture); + } + + return NS_OK; +} + +NS_IMPL_REMOVE_SYSTEM_EVENT_LISTENER(DOMEventTargetHelper) + +NS_IMETHODIMP +DOMEventTargetHelper::AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted, + uint8_t aOptionalArgc) +{ + NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1, + "Won't check if this is chrome, you want to set " + "aWantsUntrusted to false or make the aWantsUntrusted " + "explicit by making aOptionalArgc non-zero."); + + if (aOptionalArgc < 2) { + nsresult rv = WantsUntrusted(&aWantsUntrusted); + NS_ENSURE_SUCCESS(rv, rv); + } + + EventListenerManager* elm = GetOrCreateListenerManager(); + NS_ENSURE_STATE(elm); + elm->AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted); + return NS_OK; +} + +void +DOMEventTargetHelper::AddEventListener(const nsAString& aType, + EventListener* aListener, + const AddEventListenerOptionsOrBoolean& aOptions, + const Nullable<bool>& aWantsUntrusted, + ErrorResult& aRv) +{ + bool wantsUntrusted; + if (aWantsUntrusted.IsNull()) { + nsresult rv = WantsUntrusted(&wantsUntrusted); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + } else { + wantsUntrusted = aWantsUntrusted.Value(); + } + + EventListenerManager* elm = GetOrCreateListenerManager(); + if (!elm) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + elm->AddEventListener(aType, aListener, aOptions, wantsUntrusted); +} + +NS_IMETHODIMP +DOMEventTargetHelper::AddSystemEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted, + uint8_t aOptionalArgc) +{ + NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1, + "Won't check if this is chrome, you want to set " + "aWantsUntrusted to false or make the aWantsUntrusted " + "explicit by making aOptionalArgc non-zero."); + + if (aOptionalArgc < 2) { + nsresult rv = WantsUntrusted(&aWantsUntrusted); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_AddSystemEventListener(this, aType, aListener, aUseCapture, + aWantsUntrusted); +} + +NS_IMETHODIMP +DOMEventTargetHelper::DispatchEvent(nsIDOMEvent* aEvent, bool* aRetVal) +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsresult rv = + EventDispatcher::DispatchDOMEvent(this, nullptr, aEvent, nullptr, &status); + + *aRetVal = (status != nsEventStatus_eConsumeNoDefault); + return rv; +} + +nsresult +DOMEventTargetHelper::DispatchTrustedEvent(const nsAString& aEventName) +{ + RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); + event->InitEvent(aEventName, false, false); + + return DispatchTrustedEvent(event); +} + +nsresult +DOMEventTargetHelper::DispatchTrustedEvent(nsIDOMEvent* event) +{ + event->SetTrusted(true); + + bool dummy; + return DispatchEvent(event, &dummy); +} + +nsresult +DOMEventTargetHelper::SetEventHandler(nsIAtom* aType, + JSContext* aCx, + const JS::Value& aValue) +{ + RefPtr<EventHandlerNonNull> handler; + JS::Rooted<JSObject*> callable(aCx); + if (aValue.isObject() && JS::IsCallable(callable = &aValue.toObject())) { + handler = new EventHandlerNonNull(aCx, callable, dom::GetIncumbentGlobal()); + } + SetEventHandler(aType, EmptyString(), handler); + return NS_OK; +} + +void +DOMEventTargetHelper::GetEventHandler(nsIAtom* aType, + JSContext* aCx, + JS::Value* aValue) +{ + EventHandlerNonNull* handler = GetEventHandler(aType, EmptyString()); + if (handler) { + *aValue = JS::ObjectValue(*handler->Callable()); + } else { + *aValue = JS::NullValue(); + } +} + +nsresult +DOMEventTargetHelper::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + aVisitor.mCanHandle = true; + aVisitor.mParentTarget = nullptr; + return NS_OK; +} + +nsresult +DOMEventTargetHelper::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + return NS_OK; +} + +nsresult +DOMEventTargetHelper::DispatchDOMEvent(WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsPresContext* aPresContext, + nsEventStatus* aEventStatus) +{ + return EventDispatcher::DispatchDOMEvent(this, aEvent, aDOMEvent, + aPresContext, aEventStatus); +} + +EventListenerManager* +DOMEventTargetHelper::GetOrCreateListenerManager() +{ + if (!mListenerManager) { + mListenerManager = new EventListenerManager(this); + } + + return mListenerManager; +} + +EventListenerManager* +DOMEventTargetHelper::GetExistingListenerManager() const +{ + return mListenerManager; +} + +nsIScriptContext* +DOMEventTargetHelper::GetContextForEventHandlers(nsresult* aRv) +{ + *aRv = CheckInnerWindowCorrectness(); + if (NS_FAILED(*aRv)) { + return nullptr; + } + nsPIDOMWindowInner* owner = GetOwner(); + return owner ? nsGlobalWindow::Cast(owner)->GetContextInternal() + : nullptr; +} + +nsresult +DOMEventTargetHelper::WantsUntrusted(bool* aRetVal) +{ + nsresult rv = CheckInnerWindowCorrectness(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent(); + // We can let listeners on workers to always handle all the events. + *aRetVal = (doc && !nsContentUtils::IsChromeDoc(doc)) || !NS_IsMainThread(); + return rv; +} + +void +DOMEventTargetHelper::EventListenerAdded(nsIAtom* aType) +{ + ErrorResult rv; + EventListenerWasAdded(Substring(nsDependentAtomString(aType), 2), rv); +} + +void +DOMEventTargetHelper::EventListenerRemoved(nsIAtom* aType) +{ + ErrorResult rv; + EventListenerWasRemoved(Substring(nsDependentAtomString(aType), 2), rv); +} + +} // namespace mozilla diff --git a/dom/events/DOMEventTargetHelper.h b/dom/events/DOMEventTargetHelper.h new file mode 100644 index 000000000..c5a0611c9 --- /dev/null +++ b/dom/events/DOMEventTargetHelper.h @@ -0,0 +1,310 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DOMEventTargetHelper_h_ +#define mozilla_DOMEventTargetHelper_h_ + +#include "nsCOMPtr.h" +#include "nsGkAtoms.h" +#include "nsCycleCollectionParticipant.h" +#include "nsPIDOMWindow.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsIWeakReferenceUtils.h" +#include "MainThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/dom/EventTarget.h" + +struct JSCompartment; +class nsIDocument; + +namespace mozilla { + +class ErrorResult; + +#define NS_DOMEVENTTARGETHELPER_IID \ +{ 0xa28385c6, 0x9451, 0x4d7e, \ + { 0xa3, 0xdd, 0xf4, 0xb6, 0x87, 0x2f, 0xa4, 0x76 } } + +class DOMEventTargetHelper : public dom::EventTarget +{ +public: + DOMEventTargetHelper() + : mParentObject(nullptr) + , mOwnerWindow(nullptr) + , mHasOrHasHadOwnerWindow(false) + { + } + explicit DOMEventTargetHelper(nsPIDOMWindowInner* aWindow) + : mParentObject(nullptr) + , mOwnerWindow(nullptr) + , mHasOrHasHadOwnerWindow(false) + { + BindToOwner(aWindow); + } + explicit DOMEventTargetHelper(nsIGlobalObject* aGlobalObject) + : mParentObject(nullptr) + , mOwnerWindow(nullptr) + , mHasOrHasHadOwnerWindow(false) + { + BindToOwner(aGlobalObject); + } + explicit DOMEventTargetHelper(DOMEventTargetHelper* aOther) + : mParentObject(nullptr) + , mOwnerWindow(nullptr) + , mHasOrHasHadOwnerWindow(false) + { + BindToOwner(aOther); + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(DOMEventTargetHelper) + + NS_DECL_NSIDOMEVENTTARGET + + virtual EventListenerManager* GetExistingListenerManager() const override; + virtual EventListenerManager* GetOrCreateListenerManager() override; + + using dom::EventTarget::RemoveEventListener; + virtual void AddEventListener(const nsAString& aType, + dom::EventListener* aListener, + const dom::AddEventListenerOptionsOrBoolean& aOptions, + const dom::Nullable<bool>& aWantsUntrusted, + ErrorResult& aRv) override; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMEVENTTARGETHELPER_IID) + + void GetParentObject(nsIScriptGlobalObject **aParentObject) + { + if (mParentObject) { + CallQueryInterface(mParentObject, aParentObject); + } else { + *aParentObject = nullptr; + } + } + + static DOMEventTargetHelper* FromSupports(nsISupports* aSupports) + { + dom::EventTarget* target = static_cast<dom::EventTarget*>(aSupports); +#ifdef DEBUG + { + nsCOMPtr<dom::EventTarget> target_qi = do_QueryInterface(aSupports); + + // If this assertion fires the QI implementation for the object in + // question doesn't use the EventTarget pointer as the + // nsISupports pointer. That must be fixed, or we'll crash... + NS_ASSERTION(target_qi == target, "Uh, fix QI!"); + } +#endif + + return static_cast<DOMEventTargetHelper*>(target); + } + + bool HasListenersFor(const nsAString& aType) + { + return mListenerManager && mListenerManager->HasListenersFor(aType); + } + + bool HasListenersFor(nsIAtom* aTypeWithOn) + { + return mListenerManager && mListenerManager->HasListenersFor(aTypeWithOn); + } + + nsresult SetEventHandler(nsIAtom* aType, + JSContext* aCx, + const JS::Value& aValue); + using dom::EventTarget::SetEventHandler; + void GetEventHandler(nsIAtom* aType, + JSContext* aCx, + JS::Value* aValue); + using dom::EventTarget::GetEventHandler; + virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override + { + return nsPIDOMWindowOuter::GetFromCurrentInner(GetOwner()); + } + + nsresult CheckInnerWindowCorrectness() const + { + NS_ENSURE_STATE(!mHasOrHasHadOwnerWindow || mOwnerWindow); + if (mOwnerWindow && !mOwnerWindow->IsCurrentInnerWindow()) { + return NS_ERROR_FAILURE; + } + return NS_OK; + } + + nsPIDOMWindowInner* GetOwner() const { return mOwnerWindow; } + // Like GetOwner, but only returns non-null if the window being returned is + // current (in the "current document" sense of the HTML spec). + nsPIDOMWindowInner* GetWindowIfCurrent() const; + // Returns the document associated with this event target, if that document is + // the current document of its browsing context. Will return null otherwise. + nsIDocument* GetDocumentIfCurrent() const; + void BindToOwner(nsIGlobalObject* aOwner); + void BindToOwner(nsPIDOMWindowInner* aOwner); + void BindToOwner(DOMEventTargetHelper* aOther); + virtual void DisconnectFromOwner(); + nsIGlobalObject* GetParentObject() const + { + return GetOwnerGlobal(); + } + virtual nsIGlobalObject* GetOwnerGlobal() const override + { + nsCOMPtr<nsIGlobalObject> parentObject = do_QueryReferent(mParentObject); + return parentObject; + } + bool HasOrHasHadOwner() { return mHasOrHasHadOwnerWindow; } + + virtual void EventListenerAdded(nsIAtom* aType) override; + virtual void EventListenerRemoved(nsIAtom* aType) override; + virtual void EventListenerWasAdded(const nsAString& aType, + ErrorResult& aRv, + JSCompartment* aCompartment = nullptr) {} + virtual void EventListenerWasRemoved(const nsAString& aType, + ErrorResult& aRv, + JSCompartment* aCompartment = nullptr) {} + + // Dispatch a trusted, non-cancellable and non-bubbling event to |this|. + nsresult DispatchTrustedEvent(const nsAString& aEventName); +protected: + virtual ~DOMEventTargetHelper(); + + nsresult WantsUntrusted(bool* aRetVal); + + // If this method returns true your object is kept alive until it returns + // false. You can use this method instead using + // NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN macro. + virtual bool IsCertainlyAliveForCC() const + { + return false; + } + + RefPtr<EventListenerManager> mListenerManager; + // Make |event| trusted and dispatch |aEvent| to |this|. + nsresult DispatchTrustedEvent(nsIDOMEvent* aEvent); + + virtual void LastRelease() {} +private: + // Inner window or sandbox. + nsWeakPtr mParentObject; + // mParentObject pre QI-ed and cached (inner window) + // (it is needed for off main thread access) + // It is obtained in BindToOwner and reset in DisconnectFromOwner. + nsPIDOMWindowInner* MOZ_NON_OWNING_REF mOwnerWindow; + bool mHasOrHasHadOwnerWindow; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DOMEventTargetHelper, + NS_DOMEVENTTARGETHELPER_IID) + +} // namespace mozilla + +// XPIDL event handlers +#define NS_IMPL_EVENT_HANDLER(_class, _event) \ + NS_IMETHODIMP _class::GetOn##_event(JSContext* aCx, \ + JS::MutableHandle<JS::Value> aValue) \ + { \ + GetEventHandler(nsGkAtoms::on##_event, aCx, aValue.address()); \ + return NS_OK; \ + } \ + NS_IMETHODIMP _class::SetOn##_event(JSContext* aCx, \ + JS::Handle<JS::Value> aValue) \ + { \ + return SetEventHandler(nsGkAtoms::on##_event, aCx, aValue); \ + } + +#define NS_IMPL_FORWARD_EVENT_HANDLER(_class, _event, _baseclass) \ + NS_IMETHODIMP _class::GetOn##_event(JSContext* aCx, \ + JS::MutableHandle<JS::Value> aValue) \ + { \ + return _baseclass::GetOn##_event(aCx, aValue); \ + } \ + NS_IMETHODIMP _class::SetOn##_event(JSContext* aCx, \ + JS::Handle<JS::Value> aValue) \ + { \ + return _baseclass::SetOn##_event(aCx, aValue); \ + } + +// WebIDL event handlers +#define IMPL_EVENT_HANDLER(_event) \ + inline mozilla::dom::EventHandlerNonNull* GetOn##_event() \ + { \ + if (NS_IsMainThread()) { \ + return GetEventHandler(nsGkAtoms::on##_event, EmptyString()); \ + } \ + return GetEventHandler(nullptr, NS_LITERAL_STRING(#_event)); \ + } \ + inline void SetOn##_event(mozilla::dom::EventHandlerNonNull* aCallback) \ + { \ + if (NS_IsMainThread()) { \ + SetEventHandler(nsGkAtoms::on##_event, EmptyString(), aCallback); \ + } else { \ + SetEventHandler(nullptr, NS_LITERAL_STRING(#_event), aCallback); \ + } \ + } + +/* 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 + * want to override it. + */ +#define NS_FORWARD_NSIDOMEVENTTARGET_NOPREHANDLEEVENT(_to) \ + NS_IMETHOD AddEventListener(const nsAString & type, nsIDOMEventListener *listener, bool useCapture, bool wantsUntrusted, uint8_t _argc) { \ + return _to AddEventListener(type, listener, useCapture, wantsUntrusted, _argc); \ + } \ + NS_IMETHOD AddSystemEventListener(const nsAString & type, nsIDOMEventListener *listener, bool aUseCapture, bool aWantsUntrusted, uint8_t _argc) { \ + return _to AddSystemEventListener(type, listener, aUseCapture, aWantsUntrusted, _argc); \ + } \ + NS_IMETHOD RemoveEventListener(const nsAString & type, nsIDOMEventListener *listener, bool useCapture) { \ + return _to RemoveEventListener(type, listener, useCapture); \ + } \ + NS_IMETHOD RemoveSystemEventListener(const nsAString & type, nsIDOMEventListener *listener, bool aUseCapture) { \ + return _to RemoveSystemEventListener(type, listener, aUseCapture); \ + } \ + NS_IMETHOD DispatchEvent(nsIDOMEvent *evt, bool *_retval) { \ + return _to DispatchEvent(evt, _retval); \ + } \ + virtual mozilla::dom::EventTarget* GetTargetForDOMEvent() { \ + return _to GetTargetForDOMEvent(); \ + } \ + virtual mozilla::dom::EventTarget* GetTargetForEventTargetChain() { \ + return _to GetTargetForEventTargetChain(); \ + } \ + virtual nsresult WillHandleEvent( \ + mozilla::EventChainPostVisitor & aVisitor) { \ + return _to WillHandleEvent(aVisitor); \ + } \ + virtual nsresult PostHandleEvent( \ + mozilla::EventChainPostVisitor & aVisitor) { \ + return _to PostHandleEvent(aVisitor); \ + } \ + virtual nsresult DispatchDOMEvent(mozilla::WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent, nsPresContext* aPresContext, nsEventStatus* aEventStatus) { \ + return _to DispatchDOMEvent(aEvent, aDOMEvent, aPresContext, aEventStatus); \ + } \ + virtual mozilla::EventListenerManager* GetOrCreateListenerManager() { \ + return _to GetOrCreateListenerManager(); \ + } \ + virtual mozilla::EventListenerManager* GetExistingListenerManager() const { \ + return _to GetExistingListenerManager(); \ + } \ + virtual nsIScriptContext * GetContextForEventHandlers(nsresult *aRv) { \ + return _to GetContextForEventHandlers(aRv); \ + } + +#define NS_REALLY_FORWARD_NSIDOMEVENTTARGET(_class) \ + using _class::AddEventListener; \ + using _class::RemoveEventListener; \ + NS_FORWARD_NSIDOMEVENTTARGET(_class::) \ + virtual mozilla::EventListenerManager* \ + GetOrCreateListenerManager() override { \ + return _class::GetOrCreateListenerManager(); \ + } \ + virtual mozilla::EventListenerManager* \ + GetExistingListenerManager() const override { \ + return _class::GetExistingListenerManager(); \ + } + +#endif // mozilla_DOMEventTargetHelper_h_ diff --git a/dom/events/DataContainerEvent.cpp b/dom/events/DataContainerEvent.cpp new file mode 100644 index 000000000..0f9d41924 --- /dev/null +++ b/dom/events/DataContainerEvent.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/DataContainerEvent.h" +#include "nsContentUtils.h" +#include "nsIDocument.h" +#include "nsIXPConnect.h" + +namespace mozilla { +namespace dom { + +DataContainerEvent::DataContainerEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) + : Event(aOwner, aPresContext, aEvent) +{ + if (nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aOwner)) { + if (nsIDocument* doc = win->GetExtantDoc()) { + doc->WarnOnceAbout(nsIDocument::eDataContainerEvent); + } + } +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(DataContainerEvent) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DataContainerEvent, Event) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mData) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DataContainerEvent, Event) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ADDREF_INHERITED(DataContainerEvent, Event) +NS_IMPL_RELEASE_INHERITED(DataContainerEvent, Event) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DataContainerEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMDataContainerEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMETHODIMP +DataContainerEvent::GetData(const nsAString& aKey, nsIVariant** aData) +{ + NS_ENSURE_ARG_POINTER(aData); + + mData.Get(aKey, aData); + return NS_OK; +} + +NS_IMETHODIMP +DataContainerEvent::SetData(const nsAString& aKey, nsIVariant* aData) +{ + NS_ENSURE_ARG(aData); + + // Make sure this event isn't already being dispatched. + NS_ENSURE_STATE(!mEvent->mFlags.mIsBeingDispatched); + mData.Put(aKey, aData); + return NS_OK; +} + +void +DataContainerEvent::SetData(JSContext* aCx, const nsAString& aKey, + JS::Handle<JS::Value> aVal, + ErrorResult& aRv) +{ + if (!nsContentUtils::XPConnect()) { + aRv = NS_ERROR_FAILURE; + return; + } + nsCOMPtr<nsIVariant> val; + nsresult rv = + nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(val)); + if (NS_FAILED(rv)) { + aRv = rv; + return; + } + aRv = SetData(aKey, val); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<DataContainerEvent> +NS_NewDOMDataContainerEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + RefPtr<DataContainerEvent> it = + new DataContainerEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} + diff --git a/dom/events/DataContainerEvent.h b/dom/events/DataContainerEvent.h new file mode 100644 index 000000000..c118cf5ab --- /dev/null +++ b/dom/events/DataContainerEvent.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DataContainerEvent_h_ +#define mozilla_dom_DataContainerEvent_h_ + +#include "mozilla/dom/DataContainerEventBinding.h" +#include "mozilla/dom/Event.h" +#include "nsIDOMDataContainerEvent.h" +#include "nsInterfaceHashtable.h" + +namespace mozilla { +namespace dom { + +class DataContainerEvent : public Event, + public nsIDOMDataContainerEvent +{ +public: + DataContainerEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DataContainerEvent, Event) + + NS_FORWARD_TO_EVENT + + NS_DECL_NSIDOMDATACONTAINEREVENT + + virtual JSObject* + WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return DataContainerEventBinding::Wrap(aCx, this, aGivenProto); + } + + already_AddRefed<nsIVariant> GetData(const nsAString& aKey) + { + nsCOMPtr<nsIVariant> val; + GetData(aKey, getter_AddRefs(val)); + return val.forget(); + } + + void SetData(JSContext* aCx, const nsAString& aKey, + JS::Handle<JS::Value> aVal, ErrorResult& aRv); + +protected: + ~DataContainerEvent() {} + +private: + nsInterfaceHashtable<nsStringHashKey, nsIVariant> mData; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::DataContainerEvent> +NS_NewDOMDataContainerEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent); + +#endif // mozilla_dom_DataContainerEvent_h_ diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp new file mode 100644 index 000000000..2c6ecdd56 --- /dev/null +++ b/dom/events/DataTransfer.cpp @@ -0,0 +1,1565 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/BasicEvents.h" + +#include "DataTransfer.h" + +#include "nsIDOMDocument.h" +#include "nsISupportsPrimitives.h" +#include "nsIScriptSecurityManager.h" +#include "mozilla/dom/DOMStringList.h" +#include "nsArray.h" +#include "nsError.h" +#include "nsIDragService.h" +#include "nsIClipboard.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsIBinaryInputStream.h" +#include "nsIBinaryOutputStream.h" +#include "nsIStorageStream.h" +#include "nsStringStream.h" +#include "nsCRT.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptContext.h" +#include "nsIDocument.h" +#include "nsIScriptGlobalObject.h" +#include "nsVariant.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/DataTransferBinding.h" +#include "mozilla/dom/DataTransferItemList.h" +#include "mozilla/dom/Directory.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FileList.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/OSFileSystem.h" +#include "mozilla/dom/Promise.h" +#include "nsNetUtil.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragTarget) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragImage) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DataTransfer) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mItems) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragImage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DataTransfer) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransfer) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransfer) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransfer) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(mozilla::dom::DataTransfer) + NS_INTERFACE_MAP_ENTRY(nsIDOMDataTransfer) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDataTransfer) +NS_INTERFACE_MAP_END + +// the size of the array +const char DataTransfer::sEffects[8][9] = { + "none", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all" +}; + +// Used for custom clipboard types. +enum CustomClipboardTypeId { + eCustomClipboardTypeId_None, + eCustomClipboardTypeId_String +}; + +DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage, + bool aIsExternal, int32_t aClipboardType) + : mParent(aParent) + , mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE) + , mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) + , mEventMessage(aEventMessage) + , mCursorState(false) + , mReadOnly(true) + , mIsExternal(aIsExternal) + , mUserCancelled(false) + , mIsCrossDomainSubFrameDrop(false) + , mClipboardType(aClipboardType) + , mDragImageX(0) + , mDragImageY(0) +{ + mItems = new DataTransferItemList(this, aIsExternal); + // For these events, we want to be able to add data to the data transfer, so + // clear the readonly state. Otherwise, the data is already present. For + // external usage, cache the data from the native clipboard or drag. + if (aEventMessage == eCut || + aEventMessage == eCopy || + aEventMessage == eDragStart) { + mReadOnly = false; + } else if (mIsExternal) { + if (aEventMessage == ePaste) { + CacheExternalClipboardFormats(); + } else if (aEventMessage >= eDragDropEventFirst && + aEventMessage <= eDragDropEventLast) { + CacheExternalDragFormats(); + } + } +} + +DataTransfer::DataTransfer(nsISupports* aParent, + EventMessage aEventMessage, + const uint32_t aEffectAllowed, + bool aCursorState, + bool aIsExternal, + bool aUserCancelled, + bool aIsCrossDomainSubFrameDrop, + int32_t aClipboardType, + DataTransferItemList* aItems, + Element* aDragImage, + uint32_t aDragImageX, + uint32_t aDragImageY) + : mParent(aParent) + , mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE) + , mEffectAllowed(aEffectAllowed) + , mEventMessage(aEventMessage) + , mCursorState(aCursorState) + , mReadOnly(true) + , mIsExternal(aIsExternal) + , mUserCancelled(aUserCancelled) + , mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop) + , mClipboardType(aClipboardType) + , mDragImage(aDragImage) + , mDragImageX(aDragImageX) + , mDragImageY(aDragImageY) +{ + MOZ_ASSERT(mParent); + MOZ_ASSERT(aItems); + + // We clone the items array after everything else, so that it has a valid + // mParent value + mItems = aItems->Clone(this); + // The items are copied from aItems into mItems. There is no need to copy + // the actual data in the items as the data transfer will be read only. The + // dragstart event is the only time when items are + // modifiable, but those events should have been using the first constructor + // above. + NS_ASSERTION(aEventMessage != eDragStart, + "invalid event type for DataTransfer constructor"); +} + +DataTransfer::~DataTransfer() +{} + +// static +already_AddRefed<DataTransfer> +DataTransfer::Constructor(const GlobalObject& aGlobal, + const nsAString& aEventType, bool aIsExternal, + ErrorResult& aRv) +{ + nsAutoCString onEventType("on"); + AppendUTF16toUTF8(aEventType, onEventType); + nsCOMPtr<nsIAtom> eventTypeAtom = NS_Atomize(onEventType); + if (!eventTypeAtom) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + EventMessage eventMessage = nsContentUtils::GetEventMessage(eventTypeAtom); + RefPtr<DataTransfer> transfer = new DataTransfer(aGlobal.GetAsSupports(), + eventMessage, aIsExternal, + -1); + return transfer.forget(); +} + +JSObject* +DataTransfer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return DataTransferBinding::Wrap(aCx, this, aGivenProto); +} + +NS_IMETHODIMP +DataTransfer::GetDropEffect(nsAString& aDropEffect) +{ + nsString dropEffect; + GetDropEffect(dropEffect); + aDropEffect = dropEffect; + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::SetDropEffect(const nsAString& aDropEffect) +{ + // the drop effect can only be 'none', 'copy', 'move' or 'link'. + for (uint32_t e = 0; e <= nsIDragService::DRAGDROP_ACTION_LINK; e++) { + if (aDropEffect.EqualsASCII(sEffects[e])) { + // don't allow copyMove + if (e != (nsIDragService::DRAGDROP_ACTION_COPY | + nsIDragService::DRAGDROP_ACTION_MOVE)) { + mDropEffect = e; + } + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::GetEffectAllowed(nsAString& aEffectAllowed) +{ + nsString effectAllowed; + GetEffectAllowed(effectAllowed); + aEffectAllowed = effectAllowed; + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::SetEffectAllowed(const nsAString& aEffectAllowed) +{ + if (aEffectAllowed.EqualsLiteral("uninitialized")) { + mEffectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED; + return NS_OK; + } + + static_assert(nsIDragService::DRAGDROP_ACTION_NONE == 0, + "DRAGDROP_ACTION_NONE constant is wrong"); + static_assert(nsIDragService::DRAGDROP_ACTION_COPY == 1, + "DRAGDROP_ACTION_COPY constant is wrong"); + static_assert(nsIDragService::DRAGDROP_ACTION_MOVE == 2, + "DRAGDROP_ACTION_MOVE constant is wrong"); + static_assert(nsIDragService::DRAGDROP_ACTION_LINK == 4, + "DRAGDROP_ACTION_LINK constant is wrong"); + + for (uint32_t e = 0; e < ArrayLength(sEffects); e++) { + if (aEffectAllowed.EqualsASCII(sEffects[e])) { + mEffectAllowed = e; + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::GetDropEffectInt(uint32_t* aDropEffect) +{ + *aDropEffect = mDropEffect; + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::SetDropEffectInt(uint32_t aDropEffect) +{ + mDropEffect = aDropEffect; + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::GetEffectAllowedInt(uint32_t* aEffectAllowed) +{ + *aEffectAllowed = mEffectAllowed; + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::SetEffectAllowedInt(uint32_t aEffectAllowed) +{ + mEffectAllowed = aEffectAllowed; + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::GetMozUserCancelled(bool* aUserCancelled) +{ + *aUserCancelled = MozUserCancelled(); + return NS_OK; +} + +already_AddRefed<FileList> +DataTransfer::GetFiles(nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + return mItems->Files(&aSubjectPrincipal); +} + +NS_IMETHODIMP +DataTransfer::GetFiles(nsIDOMFileList** aFileList) +{ + if (!aFileList) { + return NS_ERROR_FAILURE; + } + + // The XPCOM interface is only avaliable to system code, and thus we can + // assume the system principal. This is consistent with the previous behavour + // of this function, which also assumed the system principal. + // + // This code is also called from C++ code, which expects it to have a System + // Principal, and thus the SubjectPrincipal cannot be used. + RefPtr<FileList> files = mItems->Files(nsContentUtils::GetSystemPrincipal()); + + files.forget(aFileList); + return NS_OK; +} + +void +DataTransfer::GetTypes(nsTArray<nsString>& aTypes, + nsIPrincipal& aSubjectPrincipal) const +{ + // When called from bindings, aTypes will be empty, but since we might have + // Gecko-internal callers too, clear it to be safe. + aTypes.Clear(); + + const nsTArray<RefPtr<DataTransferItem>>* items = mItems->MozItemsAt(0); + if (NS_WARN_IF(!items)) { + return; + } + + for (uint32_t i = 0; i < items->Length(); i++) { + DataTransferItem* item = items->ElementAt(i); + MOZ_ASSERT(item); + + if (item->ChromeOnly() && !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) { + continue; + } + + // NOTE: The reason why we get the internal type here is because we want + // kFileMime to appear in the types list for backwards compatibility + // reasons. + nsAutoString type; + item->GetInternalType(type); + if (item->Kind() == DataTransferItem::KIND_STRING || type.EqualsASCII(kFileMime)) { + // If the entry has kind KIND_STRING, we want to add it to the list. + aTypes.AppendElement(type); + } + } + + for (uint32_t i = 0; i < mItems->Length(); ++i) { + bool found = false; + DataTransferItem* item = mItems->IndexedGetter(i, found); + MOZ_ASSERT(found); + if (item->Kind() != DataTransferItem::KIND_FILE) { + continue; + } + aTypes.AppendElement(NS_LITERAL_STRING("Files")); + break; + } +} + +void +DataTransfer::GetData(const nsAString& aFormat, nsAString& aData, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + // return an empty string if data for the format was not found + aData.Truncate(); + + nsCOMPtr<nsIVariant> data; + nsresult rv = + GetDataAtInternal(aFormat, 0, &aSubjectPrincipal, + getter_AddRefs(data)); + if (NS_FAILED(rv)) { + if (rv != NS_ERROR_DOM_INDEX_SIZE_ERR) { + aRv.Throw(rv); + } + return; + } + + if (data) { + nsAutoString stringdata; + data->GetAsAString(stringdata); + + // for the URL type, parse out the first URI from the list. The URIs are + // separated by newlines + nsAutoString lowercaseFormat; + nsContentUtils::ASCIIToLower(aFormat, lowercaseFormat); + + if (lowercaseFormat.EqualsLiteral("url")) { + int32_t lastidx = 0, idx; + int32_t length = stringdata.Length(); + while (lastidx < length) { + idx = stringdata.FindChar('\n', lastidx); + // lines beginning with # are comments + if (stringdata[lastidx] == '#') { + if (idx == -1) { + break; + } + } + else { + if (idx == -1) { + aData.Assign(Substring(stringdata, lastidx)); + } else { + aData.Assign(Substring(stringdata, lastidx, idx - lastidx)); + } + aData = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(aData, + true); + return; + } + lastidx = idx + 1; + } + } + else { + aData = stringdata; + } + } +} + +void +DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + RefPtr<nsVariantCC> variant = new nsVariantCC(); + variant->SetAsAString(aData); + + aRv = SetDataAtInternal(aFormat, variant, 0, &aSubjectPrincipal); +} + +void +DataTransfer::ClearData(const Optional<nsAString>& aFormat, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (mReadOnly) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (MozItemCount() == 0) { + return; + } + + if (aFormat.WasPassed()) { + MozClearDataAtHelper(aFormat.Value(), 0, aSubjectPrincipal, aRv); + } else { + MozClearDataAtHelper(EmptyString(), 0, aSubjectPrincipal, aRv); + } +} + +NS_IMETHODIMP +DataTransfer::GetMozItemCount(uint32_t* aCount) +{ + *aCount = MozItemCount(); + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::GetMozCursor(nsAString& aCursorState) +{ + nsString cursor; + GetMozCursor(cursor); + aCursorState = cursor; + return NS_OK; +} + +NS_IMETHODIMP +DataTransfer::SetMozCursor(const nsAString& aCursorState) +{ + // Lock the cursor to an arrow during the drag. + mCursorState = aCursorState.EqualsLiteral("default"); + + return NS_OK; +} + +already_AddRefed<nsINode> +DataTransfer::GetMozSourceNode() +{ + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (!dragSession) { + return nullptr; + } + + nsCOMPtr<nsIDOMNode> sourceNode; + dragSession->GetSourceNode(getter_AddRefs(sourceNode)); + nsCOMPtr<nsINode> node = do_QueryInterface(sourceNode); + if (node && !nsContentUtils::LegacyIsCallerNativeCode() + && !nsContentUtils::CanCallerAccess(node)) { + return nullptr; + } + + return node.forget(); +} + +NS_IMETHODIMP +DataTransfer::GetMozSourceNode(nsIDOMNode** aSourceNode) +{ + nsCOMPtr<nsINode> sourceNode = GetMozSourceNode(); + if (!sourceNode) { + *aSourceNode = nullptr; + return NS_OK; + } + + return CallQueryInterface(sourceNode, aSourceNode); +} + +already_AddRefed<DOMStringList> +DataTransfer::MozTypesAt(uint32_t aIndex, ErrorResult& aRv) const +{ + // Only the first item is valid for clipboard events + if (aIndex > 0 && + (mEventMessage == eCut || mEventMessage == eCopy || + mEventMessage == ePaste)) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMStringList> types = new DOMStringList(); + if (aIndex < MozItemCount()) { + // note that you can retrieve the types regardless of their principal + const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(aIndex); + + bool addFile = false; + for (uint32_t i = 0; i < items.Length(); i++) { + if (items[i]->ChromeOnly() && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + continue; + } + + // NOTE: The reason why we get the internal type here is because we want + // kFileMime to appear in the types list for backwards compatibility + // reasons. + nsAutoString type; + items[i]->GetInternalType(type); + if (NS_WARN_IF(!types->Add(type))) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (items[i]->Kind() == DataTransferItem::KIND_FILE) { + addFile = true; + } + } + + if (addFile) { + types->Add(NS_LITERAL_STRING("Files")); + } + } + + return types.forget(); +} + +NS_IMETHODIMP +DataTransfer::MozTypesAt(uint32_t aIndex, nsISupports** aTypes) +{ + ErrorResult rv; + RefPtr<DOMStringList> types = MozTypesAt(aIndex, rv); + types.forget(aTypes); + return rv.StealNSResult(); +} + +nsresult +DataTransfer::GetDataAtNoSecurityCheck(const nsAString& aFormat, + uint32_t aIndex, + nsIVariant** aData) +{ + return GetDataAtInternal(aFormat, aIndex, + nsContentUtils::GetSystemPrincipal(), aData); +} + +nsresult +DataTransfer::GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex, + nsIPrincipal* aSubjectPrincipal, + nsIVariant** aData) +{ + *aData = nullptr; + + if (aFormat.IsEmpty()) { + return NS_OK; + } + + if (aIndex >= MozItemCount()) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + // Only the first item is valid for clipboard events + if (aIndex > 0 && + (mEventMessage == eCut || mEventMessage == eCopy || + mEventMessage == ePaste)) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + nsAutoString format; + GetRealFormat(aFormat, format); + + MOZ_ASSERT(aSubjectPrincipal); + + RefPtr<DataTransferItem> item = mItems->MozItemByTypeAt(format, aIndex); + if (!item) { + // The index exists but there's no data for the specified format, in this + // case we just return undefined + return NS_OK; + } + + // If we have chrome only content, and we aren't chrome, don't allow access + if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal) && item->ChromeOnly()) { + return NS_OK; + } + + // DataTransferItem::Data() handles the principal checks + ErrorResult result; + nsCOMPtr<nsIVariant> data = item->Data(aSubjectPrincipal, result); + if (NS_WARN_IF(!data || result.Failed())) { + return result.StealNSResult(); + } + + data.forget(aData); + return NS_OK; +} + +void +DataTransfer::MozGetDataAt(JSContext* aCx, const nsAString& aFormat, + uint32_t aIndex, + JS::MutableHandle<JS::Value> aRetval, + nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv) +{ + nsCOMPtr<nsIVariant> data; + aRv = GetDataAtInternal(aFormat, aIndex, &aSubjectPrincipal, + getter_AddRefs(data)); + if (aRv.Failed()) { + return; + } + + if (!data) { + aRetval.setNull(); + return; + } + + JS::Rooted<JS::Value> result(aCx); + if (!VariantToJsval(aCx, data, aRetval)) { + aRv = NS_ERROR_FAILURE; + return; + } +} + +/* static */ bool +DataTransfer::PrincipalMaySetData(const nsAString& aType, + nsIVariant* aData, + nsIPrincipal* aPrincipal) +{ + if (!nsContentUtils::IsSystemPrincipal(aPrincipal)) { + DataTransferItem::eKind kind = DataTransferItem::KindFromData(aData); + if (kind == DataTransferItem::KIND_OTHER) { + NS_WARNING("Disallowing adding non string/file types to DataTransfer"); + return false; + } + + if (aType.EqualsASCII(kFileMime) || + aType.EqualsASCII(kFilePromiseMime)) { + NS_WARNING("Disallowing adding x-moz-file or x-moz-file-promize types to DataTransfer"); + return false; + } + } + return true; +} + +void +DataTransfer::TypesListMayHaveChanged() +{ + DataTransferBinding::ClearCachedTypesValue(this); +} + +nsresult +DataTransfer::SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aSubjectPrincipal) +{ + if (aFormat.IsEmpty()) { + return NS_OK; + } + + if (mReadOnly) { + return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; + } + + // Specifying an index less than the current length will replace an existing + // item. Specifying an index equal to the current length will add a new item. + if (aIndex > MozItemCount()) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + // Only the first item is valid for clipboard events + if (aIndex > 0 && + (mEventMessage == eCut || mEventMessage == eCopy || + mEventMessage == ePaste)) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + // Don't allow the custom type to be assigned. + if (aFormat.EqualsLiteral(kCustomTypesMime)) { + return NS_ERROR_TYPE_ERR; + } + + if (!PrincipalMaySetData(aFormat, aData, aSubjectPrincipal)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal); +} + +void +DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat, + JS::Handle<JS::Value> aData, uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + nsCOMPtr<nsIVariant> data; + aRv = nsContentUtils::XPConnect()->JSValToVariant(aCx, aData, + getter_AddRefs(data)); + if (!aRv.Failed()) { + aRv = SetDataAtInternal(aFormat, data, aIndex, &aSubjectPrincipal); + } +} + +void +DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (mReadOnly) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (aIndex >= MozItemCount()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + // Only the first item is valid for clipboard events + if (aIndex > 0 && + (mEventMessage == eCut || mEventMessage == eCopy || + mEventMessage == ePaste)) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + MozClearDataAtHelper(aFormat, aIndex, aSubjectPrincipal, aRv); + + // If we just cleared the 0-th index, and there are still more than 1 indexes + // remaining, MozClearDataAt should cause the 1st index to become the 0th + // index. This should _only_ happen when the MozClearDataAt function is + // explicitly called by script, as this behavior is inconsistent with spec. + // (however, so is the MozClearDataAt API) + + if (aIndex == 0 && mItems->MozItemCount() > 1 && + mItems->MozItemsAt(0)->Length() == 0) { + mItems->PopIndexZero(); + } +} + +void +DataTransfer::MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + MOZ_ASSERT(!mReadOnly); + MOZ_ASSERT(aIndex < MozItemCount()); + MOZ_ASSERT(aIndex == 0 || + (mEventMessage != eCut && mEventMessage != eCopy && + mEventMessage != ePaste)); + + nsAutoString format; + GetRealFormat(aFormat, format); + + mItems->MozRemoveByTypeAt(format, aIndex, aSubjectPrincipal, aRv); +} + +void +DataTransfer::SetDragImage(Element& aImage, int32_t aX, int32_t aY, + ErrorResult& aRv) +{ + if (mReadOnly) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + mDragImage = &aImage; + mDragImageX = aX; + mDragImageY = aY; +} + +NS_IMETHODIMP +DataTransfer::SetDragImage(nsIDOMElement* aImage, int32_t aX, int32_t aY) +{ + ErrorResult rv; + nsCOMPtr<Element> image = do_QueryInterface(aImage); + if (image) { + SetDragImage(*image, aX, aY, rv); + } + return rv.StealNSResult(); +} + +already_AddRefed<Promise> +DataTransfer::GetFilesAndDirectories(nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + nsCOMPtr<nsINode> parentNode = do_QueryInterface(mParent); + if (!parentNode) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<nsIGlobalObject> global = parentNode->OwnerDoc()->GetScopeObject(); + MOZ_ASSERT(global); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr<Promise> p = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr<FileList> files = mItems->Files(&aSubjectPrincipal); + if (NS_WARN_IF(!files)) { + return nullptr; + } + + Sequence<RefPtr<File>> filesSeq; + files->ToSequence(filesSeq, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + p->MaybeResolve(filesSeq); + + return p.forget(); +} + +already_AddRefed<Promise> +DataTransfer::GetFiles(bool aRecursiveFlag, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + // Currently we don't support directories. + return GetFilesAndDirectories(aSubjectPrincipal, aRv); +} + +void +DataTransfer::AddElement(Element& aElement, ErrorResult& aRv) +{ + if (mReadOnly) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + mDragTarget = &aElement; +} + +NS_IMETHODIMP +DataTransfer::AddElement(nsIDOMElement* aElement) +{ + NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); + + nsCOMPtr<Element> element = do_QueryInterface(aElement); + NS_ENSURE_TRUE(element, NS_ERROR_INVALID_ARG); + + ErrorResult rv; + AddElement(*element, rv); + return rv.StealNSResult(); +} + +nsresult +DataTransfer::Clone(nsISupports* aParent, EventMessage aEventMessage, + bool aUserCancelled, bool aIsCrossDomainSubFrameDrop, + DataTransfer** aNewDataTransfer) +{ + RefPtr<DataTransfer> newDataTransfer = + new DataTransfer(aParent, aEventMessage, mEffectAllowed, mCursorState, + mIsExternal, aUserCancelled, aIsCrossDomainSubFrameDrop, + mClipboardType, mItems, mDragImage, mDragImageX, + mDragImageY); + + newDataTransfer.forget(aNewDataTransfer); + return NS_OK; +} + +already_AddRefed<nsIArray> +DataTransfer::GetTransferables(nsIDOMNode* aDragTarget) +{ + MOZ_ASSERT(aDragTarget); + + nsCOMPtr<nsINode> dragNode = do_QueryInterface(aDragTarget); + if (!dragNode) { + return nullptr; + } + + nsIDocument* doc = dragNode->GetComposedDoc(); + if (!doc) { + return nullptr; + } + + return GetTransferables(doc->GetLoadContext()); +} + +already_AddRefed<nsIArray> +DataTransfer::GetTransferables(nsILoadContext* aLoadContext) +{ + nsCOMPtr<nsIMutableArray> transArray = nsArray::Create(); + if (!transArray) { + return nullptr; + } + + uint32_t count = MozItemCount(); + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsITransferable> transferable = GetTransferable(i, aLoadContext); + if (transferable) { + transArray->AppendElement(transferable, /*weak =*/ false); + } + } + + return transArray.forget(); +} + +already_AddRefed<nsITransferable> +DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext) +{ + if (aIndex >= MozItemCount()) { + return nullptr; + } + + const nsTArray<RefPtr<DataTransferItem>>& item = *mItems->MozItemsAt(aIndex); + uint32_t count = item.Length(); + if (!count) { + return nullptr; + } + + nsCOMPtr<nsITransferable> transferable = + do_CreateInstance("@mozilla.org/widget/transferable;1"); + if (!transferable) { + return nullptr; + } + transferable->Init(aLoadContext); + + nsCOMPtr<nsIStorageStream> storageStream; + nsCOMPtr<nsIBinaryOutputStream> stream; + + bool added = false; + bool handlingCustomFormats = true; + + // When writing the custom data, we need to ensure that there is sufficient + // space for a (uint32_t) data ending type, and the null byte character at + // the end of the nsCString. We claim that space upfront and store it in + // baseLength. This value will be set to zero if a write error occurs + // indicating that the data and length are no longer valid. + const uint32_t baseLength = sizeof(uint32_t) + 1; + uint32_t totalCustomLength = baseLength; + + const char* knownFormats[] = { + kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime, + kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime, + kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime, + kFileMime, kFilePromiseMime, kFilePromiseURLMime, + kFilePromiseDestFilename, kFilePromiseDirectoryMime, + kMozTextInternal, kHTMLContext, kHTMLInfo }; + + /* + * Two passes are made here to iterate over all of the types. First, look for + * any types that are not in the list of known types. For this pass, + * handlingCustomFormats will be true. Data that corresponds to unknown types + * will be pulled out and inserted into a single type (kCustomTypesMime) by + * writing the data into a stream. + * + * The second pass will iterate over the formats looking for known types. + * These are added as is. The unknown types are all then inserted as a single + * type (kCustomTypesMime) in the same position of the first custom type. This + * model is used to maintain the format order as best as possible. + * + * The format of the kCustomTypesMime type is one or more of the following + * stored sequentially: + * <32-bit> type (only none or string is supported) + * <32-bit> length of format + * <wide string> format + * <32-bit> length of data + * <wide string> data + * A type of eCustomClipboardTypeId_None ends the list, without any following + * data. + */ + do { + for (uint32_t f = 0; f < count; f++) { + RefPtr<DataTransferItem> formatitem = item[f]; + nsCOMPtr<nsIVariant> variant = formatitem->DataNoSecurityCheck(); + if (!variant) { // skip empty items + continue; + } + + nsAutoString type; + formatitem->GetInternalType(type); + + // If the data is of one of the well-known formats, use it directly. + bool isCustomFormat = true; + for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) { + if (type.EqualsASCII(knownFormats[f])) { + isCustomFormat = false; + break; + } + } + + uint32_t lengthInBytes; + nsCOMPtr<nsISupports> convertedData; + + if (handlingCustomFormats) { + if (!ConvertFromVariant(variant, getter_AddRefs(convertedData), + &lengthInBytes)) { + continue; + } + + // When handling custom types, add the data to the stream if this is a + // custom type. If totalCustomLength is 0, then a write error occurred + // on a previous item, so ignore any others. + if (isCustomFormat && totalCustomLength > 0) { + // If it isn't a string, just ignore it. The dataTransfer is cached in + // the drag sesion during drag-and-drop, so non-strings will be + // available when dragging locally. + nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData)); + if (str) { + nsAutoString data; + str->GetData(data); + + if (!stream) { + // Create a storage stream to write to. + NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream)); + + nsCOMPtr<nsIOutputStream> outputStream; + storageStream->GetOutputStream(0, getter_AddRefs(outputStream)); + + stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + stream->SetOutputStream(outputStream); + } + + CheckedInt<uint32_t> formatLength = + CheckedInt<uint32_t>(type.Length()) * sizeof(nsString::char_type); + + // The total size of the stream is the format length, the data + // length, two integers to hold the lengths and one integer for + // the string flag. Guard against large data by ignoring any that + // don't fit. + CheckedInt<uint32_t> newSize = formatLength + totalCustomLength + + lengthInBytes + (sizeof(uint32_t) * 3); + if (newSize.isValid()) { + // If a write error occurs, set totalCustomLength to 0 so that + // further processing gets ignored. + nsresult rv = stream->Write32(eCustomClipboardTypeId_String); + if (NS_WARN_IF(NS_FAILED(rv))) { + totalCustomLength = 0; + continue; + } + rv = stream->Write32(formatLength.value()); + if (NS_WARN_IF(NS_FAILED(rv))) { + totalCustomLength = 0; + continue; + } + rv = stream->WriteBytes((const char *)type.get(), formatLength.value()); + if (NS_WARN_IF(NS_FAILED(rv))) { + totalCustomLength = 0; + continue; + } + rv = stream->Write32(lengthInBytes); + if (NS_WARN_IF(NS_FAILED(rv))) { + totalCustomLength = 0; + continue; + } + rv = stream->WriteBytes((const char *)data.get(), lengthInBytes); + if (NS_WARN_IF(NS_FAILED(rv))) { + totalCustomLength = 0; + continue; + } + + totalCustomLength = newSize.value(); + } + } + } + } else if (isCustomFormat && stream) { + // This is the second pass of the loop (handlingCustomFormats is false). + // When encountering the first custom format, append all of the stream + // at this position. If totalCustomLength is 0 indicating a write error + // occurred, or no data has been added to it, don't output anything, + if (totalCustomLength > baseLength) { + // Write out an end of data terminator. + nsresult rv = stream->Write32(eCustomClipboardTypeId_None); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIInputStream> inputStream; + storageStream->NewInputStream(0, getter_AddRefs(inputStream)); + + RefPtr<nsStringBuffer> stringBuffer = + nsStringBuffer::Alloc(totalCustomLength); + + // Subtract off the null terminator when reading. + totalCustomLength--; + + // Read the data from the stream and add a null-terminator as + // ToString needs it. + uint32_t amountRead; + rv = inputStream->Read(static_cast<char*>(stringBuffer->Data()), + totalCustomLength, &amountRead); + if (NS_SUCCEEDED(rv)) { + static_cast<char*>(stringBuffer->Data())[amountRead] = 0; + + nsCString str; + stringBuffer->ToString(totalCustomLength, str); + nsCOMPtr<nsISupportsCString> + strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); + strSupports->SetData(str); + + nsresult rv = transferable->SetTransferData(kCustomTypesMime, + strSupports, + totalCustomLength); + if (NS_FAILED(rv)) { + return nullptr; + } + + added = true; + } + } + } + + // Clear the stream so it doesn't get used again. + stream = nullptr; + } else { + // This is the second pass of the loop and a known type is encountered. + // Add it as is. + if (!ConvertFromVariant(variant, getter_AddRefs(convertedData), + &lengthInBytes)) { + continue; + } + + // The underlying drag code uses text/unicode, so use that instead of + // text/plain + const char* format; + NS_ConvertUTF16toUTF8 utf8format(type); + if (utf8format.EqualsLiteral(kTextMime)) { + format = kUnicodeMime; + } else { + format = utf8format.get(); + } + + // If a converter is set for a format, set the converter for the + // transferable and don't add the item + nsCOMPtr<nsIFormatConverter> converter = + do_QueryInterface(convertedData); + if (converter) { + transferable->AddDataFlavor(format); + transferable->SetConverter(converter); + continue; + } + + nsresult rv = transferable->SetTransferData(format, convertedData, + lengthInBytes); + if (NS_FAILED(rv)) { + return nullptr; + } + + added = true; + } + } + + handlingCustomFormats = !handlingCustomFormats; + } while (!handlingCustomFormats); + + // only return the transferable if data was successfully added to it + if (added) { + return transferable.forget(); + } + + return nullptr; +} + +bool +DataTransfer::ConvertFromVariant(nsIVariant* aVariant, + nsISupports** aSupports, + uint32_t* aLength) const +{ + *aSupports = nullptr; + *aLength = 0; + + uint16_t type; + aVariant->GetDataType(&type); + if (type == nsIDataType::VTYPE_INTERFACE || + type == nsIDataType::VTYPE_INTERFACE_IS) { + nsCOMPtr<nsISupports> data; + if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) { + return false; + } + + nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data); + if (fdp) { + // for flavour data providers, use kFlavorHasDataProvider (which has the + // value 0) as the length. + fdp.forget(aSupports); + *aLength = nsITransferable::kFlavorHasDataProvider; + } + else { + // wrap the item in an nsISupportsInterfacePointer + nsCOMPtr<nsISupportsInterfacePointer> ptrSupports = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID); + if (!ptrSupports) { + return false; + } + + ptrSupports->SetData(data); + ptrSupports.forget(aSupports); + + *aLength = sizeof(nsISupportsInterfacePointer *); + } + + return true; + } + + char16_t* chrs; + uint32_t len = 0; + nsresult rv = aVariant->GetAsWStringWithSize(&len, &chrs); + if (NS_FAILED(rv)) { + return false; + } + + nsAutoString str; + str.Adopt(chrs, len); + + nsCOMPtr<nsISupportsString> + strSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + if (!strSupports) { + return false; + } + + strSupports->SetData(str); + + strSupports.forget(aSupports); + + // each character is two bytes + *aLength = str.Length() << 1; + + return true; +} + +void +DataTransfer::ClearAll() +{ + mItems->ClearAllItems(); +} + +uint32_t +DataTransfer::MozItemCount() const +{ + return mItems->MozItemCount(); +} + +nsresult +DataTransfer::SetDataWithPrincipal(const nsAString& aFormat, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal) +{ + nsAutoString format; + GetRealFormat(aFormat, format); + + ErrorResult rv; + RefPtr<DataTransferItem> item = + mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal, + /* aInsertOnly = */ false, + /* aHidden= */ false, + rv); + return rv.StealNSResult(); +} + +void +DataTransfer::SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal, + bool aHidden) +{ + if (aFormat.EqualsLiteral(kCustomTypesMime)) { + FillInExternalCustomTypes(aData, aIndex, aPrincipal); + } else { + nsAutoString format; + GetRealFormat(aFormat, format); + + ErrorResult rv; + RefPtr<DataTransferItem> item = + mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal, + /* aInsertOnly = */ false, aHidden, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + } + } +} + +void +DataTransfer::GetRealFormat(const nsAString& aInFormat, + nsAString& aOutFormat) const +{ + // treat text/unicode as equivalent to text/plain + nsAutoString lowercaseFormat; + nsContentUtils::ASCIIToLower(aInFormat, lowercaseFormat); + if (lowercaseFormat.EqualsLiteral("text") || + lowercaseFormat.EqualsLiteral("text/unicode")) { + aOutFormat.AssignLiteral("text/plain"); + return; + } + + if (lowercaseFormat.EqualsLiteral("url")) { + aOutFormat.AssignLiteral("text/uri-list"); + return; + } + + aOutFormat.Assign(lowercaseFormat); +} + +nsresult +DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex, + nsIPrincipal* aPrincipal, bool aHidden) +{ + ErrorResult rv; + RefPtr<DataTransferItem> item; + + if (strcmp(aFormat, kUnicodeMime) == 0) { + item = mItems->SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr, + aIndex, aPrincipal, false, aHidden, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + return NS_OK; + } + + if (strcmp(aFormat, kURLDataMime) == 0) { + item = mItems->SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr, + aIndex, aPrincipal, false, aHidden, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + return NS_OK; + } + + nsAutoString format; + GetRealFormat(NS_ConvertUTF8toUTF16(aFormat), format); + item = mItems->SetDataWithPrincipal(format, nullptr, aIndex, + aPrincipal, false, aHidden, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + return NS_OK; +} + +void +DataTransfer::CacheExternalDragFormats() +{ + // Called during the constructor to cache the formats available from an + // external drag. The data associated with each format will be set to null. + // This data will instead only be retrieved in FillInExternalDragData when + // asked for, as it may be time consuming for the source application to + // generate it. + + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (!dragSession) { + return; + } + + // make sure that the system principal is used for external drags + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + nsCOMPtr<nsIPrincipal> sysPrincipal; + ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal)); + + // there isn't a way to get a list of the formats that might be available on + // all platforms, so just check for the types that can actually be imported + // XXXndeakin there are some other formats but those are platform specific. + // NOTE: kFileMime must have index 0 + const char* formats[] = { kFileMime, kHTMLMime, kURLMime, kURLDataMime, + kUnicodeMime, kPNGImageMime }; + + uint32_t count; + dragSession->GetNumDropItems(&count); + for (uint32_t c = 0; c < count; c++) { + bool hasFileData = false; + dragSession->IsDataFlavorSupported(kFileMime, &hasFileData); + + // First, check for the special format that holds custom types. + bool supported; + dragSession->IsDataFlavorSupported(kCustomTypesMime, &supported); + if (supported) { + FillInExternalCustomTypes(c, sysPrincipal); + } + + for (uint32_t f = 0; f < ArrayLength(formats); f++) { + // IsDataFlavorSupported doesn't take an index as an argument and just + // checks if any of the items support a particular flavor, even though + // the GetData method does take an index. Here, we just assume that + // every item being dragged has the same set of flavors. + bool supported; + dragSession->IsDataFlavorSupported(formats[f], &supported); + // if the format is supported, add an item to the array with null as + // the data. When retrieved, GetRealData will read the data. + if (supported) { + CacheExternalData(formats[f], c, sysPrincipal, /* hidden = */ f && hasFileData); + } + } + } +} + +void +DataTransfer::CacheExternalClipboardFormats() +{ + NS_ASSERTION(mEventMessage == ePaste, + "caching clipboard data for invalid event"); + + // Called during the constructor for paste events to cache the formats + // available on the clipboard. As with CacheExternalDragFormats, the + // data will only be retrieved when needed. + + nsCOMPtr<nsIClipboard> clipboard = + do_GetService("@mozilla.org/widget/clipboard;1"); + if (!clipboard || mClipboardType < 0) { + return; + } + + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + nsCOMPtr<nsIPrincipal> sysPrincipal; + ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal)); + + // Check if the clipboard has any files + bool hasFileData = false; + const char *fileMime[] = { kFileMime }; + clipboard->HasDataMatchingFlavors(fileMime, 1, mClipboardType, &hasFileData); + + // We will be ignoring any application/x-moz-file files found in the paste + // datatransfer within e10s, as they will fail to be sent over IPC. Because of + // that, we will unset hasFileData, whether or not it would have been set. + // (bug 1308007) + if (XRE_IsContentProcess()) { + hasFileData = false; + } + + // there isn't a way to get a list of the formats that might be available on + // all platforms, so just check for the types that can actually be imported. + // NOTE: kCustomTypesMime must have index 0, kFileMime index 1 + const char* formats[] = { kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime, + kURLMime, kURLDataMime, kUnicodeMime, kPNGImageMime }; + + for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) { + // check each format one at a time + bool supported; + clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType, + &supported); + // if the format is supported, add an item to the array with null as + // the data. When retrieved, GetRealData will read the data. + if (supported) { + if (f == 0) { + FillInExternalCustomTypes(0, sysPrincipal); + } else { + // In non-e10s we support pasting files from explorer.exe. + // Unfortunately, we fail to send that data over IPC in e10s, so we + // don't want to add the item to the DataTransfer and end up producing a + // null `application/x-moz-file`. (bug 1308007) + if (XRE_IsContentProcess() && f == 1) { + continue; + } + + // If we aren't the file data, and we have file data, we want to be hidden + CacheExternalData(formats[f], 0, sysPrincipal, /* hidden = */ f != 1 && hasFileData); + } + } + } +} + +void +DataTransfer::FillAllExternalData() +{ + if (mIsExternal) { + for (uint32_t i = 0; i < MozItemCount(); ++i) { + const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(i); + for (uint32_t j = 0; j < items.Length(); ++j) { + MOZ_ASSERT(items[j]->Index() == i); + + items[j]->FillInExternalData(); + } + } + } +} + +void +DataTransfer::FillInExternalCustomTypes(uint32_t aIndex, + nsIPrincipal* aPrincipal) +{ + RefPtr<DataTransferItem> item = new DataTransferItem(this, + NS_LITERAL_STRING(kCustomTypesMime), + DataTransferItem::KIND_STRING); + item->SetIndex(aIndex); + + nsCOMPtr<nsIVariant> variant = item->DataNoSecurityCheck(); + if (!variant) { + return; + } + + FillInExternalCustomTypes(variant, aIndex, aPrincipal); +} + +void +DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, + nsIPrincipal* aPrincipal) +{ + char* chrs; + uint32_t len = 0; + nsresult rv = aData->GetAsStringWithSize(&len, &chrs); + if (NS_FAILED(rv)) { + return; + } + + nsAutoCString str; + str.Adopt(chrs, len); + + nsCOMPtr<nsIInputStream> stringStream; + NS_NewCStringInputStream(getter_AddRefs(stringStream), str); + + nsCOMPtr<nsIBinaryInputStream> stream = + do_CreateInstance("@mozilla.org/binaryinputstream;1"); + if (!stream) { + return; + } + + rv = stream->SetInputStream(stringStream); + NS_ENSURE_SUCCESS_VOID(rv); + + uint32_t type; + do { + rv = stream->Read32(&type); + NS_ENSURE_SUCCESS_VOID(rv); + if (type == eCustomClipboardTypeId_String) { + uint32_t formatLength; + rv = stream->Read32(&formatLength); + NS_ENSURE_SUCCESS_VOID(rv); + char* formatBytes; + rv = stream->ReadBytes(formatLength, &formatBytes); + NS_ENSURE_SUCCESS_VOID(rv); + nsAutoString format; + format.Adopt(reinterpret_cast<char16_t*>(formatBytes), + formatLength / sizeof(char16_t)); + + uint32_t dataLength; + rv = stream->Read32(&dataLength); + NS_ENSURE_SUCCESS_VOID(rv); + char* dataBytes; + rv = stream->ReadBytes(dataLength, &dataBytes); + NS_ENSURE_SUCCESS_VOID(rv); + nsAutoString data; + data.Adopt(reinterpret_cast<char16_t*>(dataBytes), + dataLength / sizeof(char16_t)); + + RefPtr<nsVariantCC> variant = new nsVariantCC(); + rv = variant->SetAsAString(data); + NS_ENSURE_SUCCESS_VOID(rv); + + SetDataWithPrincipal(format, variant, aIndex, aPrincipal); + } + } while (type != eCustomClipboardTypeId_None); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/DataTransfer.h b/dom/events/DataTransfer.h new file mode 100644 index 000000000..7c6b0b8c1 --- /dev/null +++ b/dom/events/DataTransfer.h @@ -0,0 +1,382 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DataTransfer_h +#define mozilla_dom_DataTransfer_h + +#include "nsString.h" +#include "nsTArray.h" +#include "nsIVariant.h" +#include "nsIPrincipal.h" +#include "nsIDOMDataTransfer.h" +#include "nsIDOMElement.h" +#include "nsIDragService.h" +#include "nsCycleCollectionParticipant.h" + +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/dom/File.h" + +class nsINode; +class nsITransferable; +class nsILoadContext; + +namespace mozilla { + +class EventStateManager; + +namespace dom { + +class DataTransferItem; +class DataTransferItemList; +class DOMStringList; +class Element; +class FileList; +class Promise; +template<typename T> class Optional; + +#define NS_DATATRANSFER_IID \ +{ 0x6c5f90d1, 0xa886, 0x42c8, \ + { 0x85, 0x06, 0x10, 0xbe, 0x5c, 0x0d, 0xc6, 0x77 } } + +class DataTransfer final : public nsIDOMDataTransfer, + public nsWrapperCache +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_DATATRANSFER_IID) + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIDOMDATATRANSFER + + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransfer) + + friend class mozilla::EventStateManager; + + static DataTransfer* Cast(nsIDOMDataTransfer* aArg) + { + return static_cast<DataTransfer*>(aArg); + } + +protected: + + // hide the default constructor + DataTransfer(); + + // this constructor is used only by the Clone method to copy the fields as + // needed to a new data transfer. + DataTransfer(nsISupports* aParent, + EventMessage aEventMessage, + const uint32_t aEffectAllowed, + bool aCursorState, + bool aIsExternal, + bool aUserCancelled, + bool aIsCrossDomainSubFrameDrop, + int32_t aClipboardType, + DataTransferItemList* aItems, + Element* aDragImage, + uint32_t aDragImageX, + uint32_t aDragImageY); + + ~DataTransfer(); + + static const char sEffects[8][9]; + +public: + // Constructor for DataTransfer. + // + // aIsExternal must only be true when used to create a dataTransfer for a + // paste or a drag that was started without using a data transfer. The + // latter will occur when an external drag occurs, that is, a drag where the + // source is another application, or a drag is started by calling the drag + // service directly. For clipboard operations, aClipboardType indicates + // which clipboard to use, from nsIClipboard, or -1 for non-clipboard + // operations, or if access to the system clipboard should not be allowed. + DataTransfer(nsISupports* aParent, EventMessage aEventMessage, + bool aIsExternal, int32_t aClipboardType); + + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsISupports* GetParentObject() const + { + return mParent; + } + + void SetParentObject(nsISupports* aNewParent) + { + MOZ_ASSERT(aNewParent); + // Setting the parent after we've been wrapped is pointless, so + // make sure we aren't wrapped yet. + MOZ_ASSERT(!GetWrapperPreserveColor()); + mParent = aNewParent; + } + + static already_AddRefed<DataTransfer> + Constructor(const GlobalObject& aGlobal, const nsAString& aEventType, + bool aIsExternal, ErrorResult& aRv); + + void GetDropEffect(nsString& aDropEffect) + { + aDropEffect.AssignASCII(sEffects[mDropEffect]); + } + + void GetEffectAllowed(nsString& aEffectAllowed) + { + if (mEffectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) { + aEffectAllowed.AssignLiteral("uninitialized"); + } else { + aEffectAllowed.AssignASCII(sEffects[mEffectAllowed]); + } + } + + void SetDragImage(Element& aElement, int32_t aX, int32_t aY, + ErrorResult& aRv); + + void GetTypes(nsTArray<nsString>& aTypes, + nsIPrincipal& aSubjectPrincipal) const; + + void GetData(const nsAString& aFormat, nsAString& aData, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + + void SetData(const nsAString& aFormat, const nsAString& aData, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + + void ClearData(const mozilla::dom::Optional<nsAString>& aFormat, + nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + + already_AddRefed<FileList> + GetFiles(nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + + already_AddRefed<Promise> + GetFilesAndDirectories(nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + + already_AddRefed<Promise> + GetFiles(bool aRecursiveFlag, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + + + void AddElement(Element& aElement, mozilla::ErrorResult& aRv); + + uint32_t MozItemCount() const; + + void GetMozCursor(nsString& aCursor) + { + if (mCursorState) { + aCursor.AssignLiteral("default"); + } else { + aCursor.AssignLiteral("auto"); + } + } + + already_AddRefed<DOMStringList> MozTypesAt(uint32_t aIndex, + mozilla::ErrorResult& aRv) const; + + void MozClearDataAt(const nsAString& aFormat, uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + + void MozSetDataAt(JSContext* aCx, const nsAString& aFormat, + JS::Handle<JS::Value> aData, uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + + void MozGetDataAt(JSContext* aCx, const nsAString& aFormat, + uint32_t aIndex, JS::MutableHandle<JS::Value> aRetval, + nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + + bool MozUserCancelled() const + { + return mUserCancelled; + } + + already_AddRefed<nsINode> GetMozSourceNode(); + + mozilla::dom::Element* GetDragTarget() const + { + return mDragTarget; + } + + nsresult GetDataAtNoSecurityCheck(const nsAString& aFormat, uint32_t aIndex, + nsIVariant** aData); + + DataTransferItemList* Items() const { + return mItems; + } + + // a readonly dataTransfer cannot have new data added or existing data + // removed. Only the dropEffect and effectAllowed may be modified. + bool IsReadOnly() const { + return mReadOnly; + } + void SetReadOnly() { + mReadOnly = true; + } + + int32_t ClipboardType() const { + return mClipboardType; + } + EventMessage GetEventMessage() const { + return mEventMessage; + } + bool IsCrossDomainSubFrameDrop() const { + return mIsCrossDomainSubFrameDrop; + } + + // converts the data into an array of nsITransferable objects to be used for + // drag and drop or clipboard operations. + already_AddRefed<nsIArray> GetTransferables(nsIDOMNode* aDragTarget); + + already_AddRefed<nsIArray> + GetTransferables(nsILoadContext* aLoadContext); + + // converts the data for a single item at aIndex into an nsITransferable + // object. + already_AddRefed<nsITransferable> + GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext); + + // converts the data in the variant to an nsISupportString if possible or + // an nsISupports or null otherwise. + bool ConvertFromVariant(nsIVariant* aVariant, + nsISupports** aSupports, + uint32_t* aLength) const; + + // clears all of the data + void ClearAll(); + + // Similar to SetData except also specifies the principal to store. + // aData may be null when called from CacheExternalDragFormats or + // CacheExternalClipboardFormats. + nsresult SetDataWithPrincipal(const nsAString& aFormat, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal); + + // Variation of SetDataWithPrincipal with handles extracting + // kCustomTypesMime data into separate types. + void SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal, + bool aHidden); + + // returns a weak reference to the drag image + Element* GetDragImage(int32_t* aX, int32_t* aY) const + { + *aX = mDragImageX; + *aY = mDragImageY; + return mDragImage; + } + + nsresult Clone(nsISupports* aParent, EventMessage aEventMessage, + bool aUserCancelled, bool aIsCrossDomainSubFrameDrop, + DataTransfer** aResult); + + // converts some formats used for compatibility in aInFormat into aOutFormat. + // Text and text/unicode become text/plain, and URL becomes text/uri-list + void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) const; + + static bool PrincipalMaySetData(const nsAString& aFormat, + nsIVariant* aData, + nsIPrincipal* aPrincipal); + + // Notify the DataTransfer that the list returned from GetTypes may have + // changed. This can happen due to items we care about for purposes of + // GetTypes being added or removed or changing item kinds. + void TypesListMayHaveChanged(); + +protected: + + // caches text and uri-list data formats that exist in the drag service or + // clipboard for retrieval later. + nsresult CacheExternalData(const char* aFormat, uint32_t aIndex, + nsIPrincipal* aPrincipal, bool aHidden); + + // caches the formats that exist in the drag service that were added by an + // external drag + void CacheExternalDragFormats(); + + // caches the formats that exist in the clipboard + void CacheExternalClipboardFormats(); + + FileList* GetFilesInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal); + nsresult GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex, + nsIPrincipal* aSubjectPrincipal, + nsIVariant** aData); + + nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, + uint32_t aIndex, nsIPrincipal* aSubjectPrincipal); + + friend class ContentParent; + + void FillAllExternalData(); + + void FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal); + + void FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, + nsIPrincipal* aPrincipal); + + void MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + mozilla::ErrorResult& aRv); + + nsCOMPtr<nsISupports> mParent; + + // the drop effect and effect allowed + uint32_t mDropEffect; + uint32_t mEffectAllowed; + + // the event message this data transfer is for. This will correspond to an + // event->mMessage value. + EventMessage mEventMessage; + + // Indicates the behavior of the cursor during drag operations + bool mCursorState; + + // readonly data transfers may not be modified except the drop effect and + // effect allowed. + bool mReadOnly; + + // true for drags started without a data transfer, for example, those from + // another application. + bool mIsExternal; + + // true if the user cancelled the drag. Used only for the dragend event. + bool mUserCancelled; + + // true if this is a cross-domain drop from a subframe where access to the + // data should be prevented + bool mIsCrossDomainSubFrameDrop; + + // Indicates which clipboard type to use for clipboard operations. Ignored for + // drag and drop. + int32_t mClipboardType; + + // The items contained with the DataTransfer + RefPtr<DataTransferItemList> mItems; + + // the target of the drag. The drag and dragend events will fire at this. + nsCOMPtr<mozilla::dom::Element> mDragTarget; + + // the custom drag image and coordinates within the image. If mDragImage is + // null, the default image is created from the drag target. + nsCOMPtr<mozilla::dom::Element> mDragImage; + uint32_t mDragImageX; + uint32_t mDragImageY; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DataTransfer, NS_DATATRANSFER_IID) + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_DataTransfer_h */ diff --git a/dom/events/DataTransferItem.cpp b/dom/events/DataTransferItem.cpp new file mode 100644 index 000000000..cc4496871 --- /dev/null +++ b/dom/events/DataTransferItem.cpp @@ -0,0 +1,556 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DataTransferItem.h" +#include "DataTransferItemList.h" + +#include "mozilla/ContentEvents.h" +#include "mozilla/EventForwards.h" +#include "mozilla/dom/DataTransferItemBinding.h" +#include "mozilla/dom/Directory.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/FileSystem.h" +#include "mozilla/dom/FileSystemDirectoryEntry.h" +#include "mozilla/dom/FileSystemFileEntry.h" +#include "nsIClipboard.h" +#include "nsISupportsPrimitives.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsContentUtils.h" +#include "nsVariant.h" + +namespace { + +struct FileMimeNameData +{ + const char* mMimeName; + const char* mFileName; +}; + +FileMimeNameData kFileMimeNameMap[] = { + { kFileMime, "GenericFileName" }, + { kPNGImageMime, "GenericImageNamePNG" }, +}; + +} // anonymous namespace + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem, mData, + mPrincipal, mDataTransfer, mCachedFile) +NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +DataTransferItem::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return DataTransferItemBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DataTransferItem> +DataTransferItem::Clone(DataTransfer* aDataTransfer) const +{ + MOZ_ASSERT(aDataTransfer); + + RefPtr<DataTransferItem> it = new DataTransferItem(aDataTransfer, mType); + + // Copy over all of the fields + it->mKind = mKind; + it->mIndex = mIndex; + it->mData = mData; + it->mPrincipal = mPrincipal; + it->mChromeOnly = mChromeOnly; + + return it.forget(); +} + +void +DataTransferItem::SetData(nsIVariant* aData) +{ + // Invalidate our file cache, we will regenerate it with the new data + mCachedFile = nullptr; + + if (!aData) { + // We are holding a temporary null which will later be filled. + // These are provided by the system, and have guaranteed properties about + // their kind based on their type. + MOZ_ASSERT(!mType.IsEmpty()); + + mKind = KIND_STRING; + for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) { + if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) { + mKind = KIND_FILE; + break; + } + } + + mData = nullptr; + return; + } + + mData = aData; + mKind = KindFromData(mData); +} + +/* static */ DataTransferItem::eKind +DataTransferItem::KindFromData(nsIVariant* aData) +{ + nsCOMPtr<nsISupports> supports; + nsresult rv = aData->GetAsISupports(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) { + // Check if we have one of the supported file data formats + if (nsCOMPtr<nsIDOMBlob>(do_QueryInterface(supports)) || + nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) || + nsCOMPtr<nsIFile>(do_QueryInterface(supports))) { + return KIND_FILE; + } + } + + nsAutoString string; + // If we can't get the data type as a string, that means that the object + // should be considered to be of the "other" type. This is impossible + // through the APIs defined by the spec, but we provide extra Moz* APIs, + // which allow setting of non-string data. We determine whether we can + // consider it a string, by calling GetAsAString, and checking for success. + rv = aData->GetAsAString(string); + if (NS_SUCCEEDED(rv)) { + return KIND_STRING; + } + + return KIND_OTHER; +} + +void +DataTransferItem::FillInExternalData() +{ + if (mData) { + return; + } + + NS_ConvertUTF16toUTF8 utf8format(mType); + const char* format = utf8format.get(); + if (strcmp(format, "text/plain") == 0) { + format = kUnicodeMime; + } else if (strcmp(format, "text/uri-list") == 0) { + format = kURLDataMime; + } + + nsCOMPtr<nsITransferable> trans = + do_CreateInstance("@mozilla.org/widget/transferable;1"); + if (NS_WARN_IF(!trans)) { + return; + } + + trans->Init(nullptr); + trans->AddDataFlavor(format); + + if (mDataTransfer->GetEventMessage() == ePaste) { + MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0"); + + nsCOMPtr<nsIClipboard> clipboard = + do_GetService("@mozilla.org/widget/clipboard;1"); + if (!clipboard || mDataTransfer->ClipboardType() < 0) { + return; + } + + nsresult rv = clipboard->GetData(trans, mDataTransfer->ClipboardType()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } else { + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (!dragSession) { + return; + } + + nsresult rv = dragSession->GetData(trans, mIndex); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + + uint32_t length = 0; + nsCOMPtr<nsISupports> data; + nsresult rv = trans->GetTransferData(format, getter_AddRefs(data), &length); + if (NS_WARN_IF(NS_FAILED(rv) || !data)) { + return; + } + + // Fill the variant + RefPtr<nsVariantCC> variant = new nsVariantCC(); + + eKind oldKind = Kind(); + if (oldKind == KIND_FILE) { + // Because this is an external piece of data, mType is one of kFileMime, + // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types + // are passed in as a nsIInputStream which must be converted to a + // dom::File before storing. + if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) { + RefPtr<File> file = CreateFileFromInputStream(istream); + if (NS_WARN_IF(!file)) { + return; + } + data = do_QueryObject(file); + } + + variant->SetAsISupports(data); + } else { + // We have an external piece of string data. Extract it and store it in the variant + MOZ_ASSERT(oldKind == KIND_STRING); + + nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data); + if (supportsstr) { + nsAutoString str; + supportsstr->GetData(str); + variant->SetAsAString(str); + } else { + nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data); + if (supportscstr) { + nsAutoCString str; + supportscstr->GetData(str); + variant->SetAsACString(str); + } + } + } + + SetData(variant); + + if (oldKind != Kind()) { + NS_WARNING("Clipboard data provided by the OS does not match predicted kind"); + mDataTransfer->TypesListMayHaveChanged(); + } +} + +void +DataTransferItem::GetType(nsAString& aType) +{ + // If we don't have a File, we can just put whatever our recorded internal + // type is. + if (Kind() != KIND_FILE) { + aType = mType; + return; + } + + // If we do have a File, then we need to look at our File object to discover + // what its mime type is. We can use the System Principal here, as this + // information should be avaliable even if the data is currently inaccessible + // (for example during a dragover). + // + // XXX: This seems inefficient, as it seems like we should be able to get this + // data without getting the entire File object, which may require talking to + // the OS. + ErrorResult rv; + RefPtr<File> file = GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv); + MOZ_ASSERT(!rv.Failed(), "Failed to get file data with system principal"); + + // If we don't actually have a file, fall back to returning the internal type. + if (NS_WARN_IF(!file)) { + aType = mType; + return; + } + + file->GetType(aType); +} + +already_AddRefed<File> +DataTransferItem::GetAsFile(nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + // This is done even if we have an mCachedFile, as it performs the necessary + // permissions checks to ensure that we are allowed to access this type. + nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv); + if (NS_WARN_IF(!data || aRv.Failed())) { + return nullptr; + } + + // We have to check our kind after getting the data, because if we have + // external data and the OS lied to us (which unfortunately does happen + // sometimes), then we might not have the same type of data as we did coming + // into this function. + if (NS_WARN_IF(mKind != KIND_FILE)) { + return nullptr; + } + + // Generate the dom::File from the stored data, caching it so that the + // same object is returned in the future. + if (!mCachedFile) { + nsCOMPtr<nsISupports> supports; + aRv = data->GetAsISupports(getter_AddRefs(supports)); + MOZ_ASSERT(!aRv.Failed() && supports, + "File objects should be stored as nsISupports variants"); + if (aRv.Failed() || !supports) { + return nullptr; + } + + if (nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(supports)) { + Blob* blob = static_cast<Blob*>(domBlob.get()); + mCachedFile = blob->ToFile(); + } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) { + MOZ_ASSERT(blobImpl->IsFile()); + mCachedFile = File::Create(mDataTransfer, blobImpl); + } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) { + mCachedFile = File::CreateFromFile(mDataTransfer, ifile); + } else { + MOZ_ASSERT(false, "One of the above code paths should be taken"); + return nullptr; + } + } + + RefPtr<File> file = mCachedFile; + return file.forget(); +} + +already_AddRefed<FileSystemEntry> +DataTransferItem::GetAsEntry(nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + RefPtr<File> file = GetAsFile(aSubjectPrincipal, aRv); + if (NS_WARN_IF(aRv.Failed()) || !file) { + return nullptr; + } + + nsCOMPtr<nsIGlobalObject> global; + // This is annoying, but DataTransfer may have various things as parent. + nsCOMPtr<EventTarget> target = + do_QueryInterface(mDataTransfer->GetParentObject()); + if (target) { + global = target->GetOwnerGlobal(); + } else { + nsCOMPtr<nsIDOMEvent> event = + do_QueryInterface(mDataTransfer->GetParentObject()); + if (event) { + global = event->InternalDOMEvent()->GetParentObject(); + } + } + + if (!global) { + return nullptr; + } + + RefPtr<FileSystem> fs = FileSystem::Create(global); + RefPtr<FileSystemEntry> entry; + BlobImpl* impl = file->Impl(); + MOZ_ASSERT(impl); + + if (impl->IsDirectory()) { + nsAutoString fullpath; + impl->GetMozFullPathInternal(fullpath, aRv); + if (aRv.Failed()) { + aRv.SuppressException(); + return nullptr; + } + + nsCOMPtr<nsIFile> directoryFile; + // fullPath is already in unicode, we don't have to use + // NS_NewNativeLocalFile. + nsresult rv = NS_NewLocalFile(fullpath, true, + getter_AddRefs(directoryFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + RefPtr<Directory> directory = Directory::Create(global, directoryFile); + entry = new FileSystemDirectoryEntry(global, directory, nullptr, fs); + } else { + entry = new FileSystemFileEntry(global, file, nullptr, fs); + } + + Sequence<RefPtr<FileSystemEntry>> entries; + if (!entries.AppendElement(entry, fallible)) { + return nullptr; + } + + fs->CreateRoot(entries); + return entry.forget(); +} + +already_AddRefed<File> +DataTransferItem::CreateFileFromInputStream(nsIInputStream* aStream) +{ + const char* key = nullptr; + for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) { + if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) { + key = kFileMimeNameMap[i].mFileName; + break; + } + } + if (!key) { + MOZ_ASSERT_UNREACHABLE("Unsupported mime type"); + key = "GenericFileName"; + } + + nsXPIDLString fileName; + nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + key, fileName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + uint64_t available; + rv = aStream->Available(&available); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + void* data = nullptr; + rv = NS_ReadInputStreamToBuffer(aStream, &data, available); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return File::CreateMemoryFile(mDataTransfer, data, available, fileName, + mType, PR_Now()); +} + +void +DataTransferItem::GetAsString(FunctionStringCallback* aCallback, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (!aCallback) { + return; + } + + // Theoretically this should be done inside of the runnable, as it might be an + // expensive operation on some systems, however we wouldn't get access to the + // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method. + nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv); + if (NS_WARN_IF(!data || aRv.Failed())) { + return; + } + + // We have to check our kind after getting the data, because if we have + // external data and the OS lied to us (which unfortunately does happen + // sometimes), then we might not have the same type of data as we did coming + // into this function. + if (NS_WARN_IF(mKind != KIND_STRING)) { + return; + } + + nsAutoString stringData; + nsresult rv = data->GetAsAString(stringData); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Dispatch the callback to the main thread + class GASRunnable final : public Runnable + { + public: + GASRunnable(FunctionStringCallback* aCallback, + const nsAString& aStringData) + : mCallback(aCallback), mStringData(aStringData) + {} + + NS_IMETHOD Run() override + { + ErrorResult rv; + mCallback->Call(mStringData, rv); + NS_WARNING_ASSERTION(!rv.Failed(), "callback failed"); + return rv.StealNSResult(); + } + private: + RefPtr<FunctionStringCallback> mCallback; + nsString mStringData; + }; + + RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData); + rv = NS_DispatchToMainThread(runnable); + if (NS_FAILED(rv)) { + NS_WARNING("NS_DispatchToMainThread Failed in " + "DataTransferItem::GetAsString!"); + } +} + +already_AddRefed<nsIVariant> +DataTransferItem::DataNoSecurityCheck() +{ + if (!mData) { + FillInExternalData(); + } + nsCOMPtr<nsIVariant> data = mData; + return data.forget(); +} + +already_AddRefed<nsIVariant> +DataTransferItem::Data(nsIPrincipal* aPrincipal, ErrorResult& aRv) +{ + MOZ_ASSERT(aPrincipal); + + nsCOMPtr<nsIVariant> variant = DataNoSecurityCheck(); + + // If the inbound principal is system, we can skip the below checks, as + // they will trivially succeed. + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { + return variant.forget(); + } + + MOZ_ASSERT(!ChromeOnly(), "Non-chrome code shouldn't see a ChromeOnly DataTransferItem"); + if (ChromeOnly()) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + bool checkItemPrincipal = mDataTransfer->IsCrossDomainSubFrameDrop() || + (mDataTransfer->GetEventMessage() != eDrop && + mDataTransfer->GetEventMessage() != ePaste); + + // Check if the caller is allowed to access the drag data. Callers with + // chrome privileges can always read the data. During the + // drop event, allow retrieving the data except in the case where the + // source of the drag is in a child frame of the caller. In that case, + // we only allow access to data of the same principal. During other events, + // only allow access to the data with the same principal. + // + // We don't want to fail with an exception in this siutation, rather we want + // to just pretend as though the stored data is "nullptr". This is consistent + // with Chrome's behavior and is less surprising for web applications which + // don't expect execptions to be raised when performing certain operations. + if (Principal() && checkItemPrincipal && + !aPrincipal->Subsumes(Principal())) { + return nullptr; + } + + if (!variant) { + return nullptr; + } + + nsCOMPtr<nsISupports> data; + nsresult rv = variant->GetAsISupports(getter_AddRefs(data)); + if (NS_SUCCEEDED(rv) && data) { + nsCOMPtr<EventTarget> pt = do_QueryInterface(data); + if (pt) { + nsIScriptContext* c = pt->GetContextForEventHandlers(&rv); + if (NS_WARN_IF(NS_FAILED(rv) || !c)) { + return nullptr; + } + + nsIGlobalObject* go = c->GetGlobalObject(); + if (NS_WARN_IF(!go)) { + return nullptr; + } + + nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go); + MOZ_ASSERT(sp, "This cannot fail on the main thread."); + + nsIPrincipal* dataPrincipal = sp->GetPrincipal(); + if (NS_WARN_IF(!dataPrincipal || !aPrincipal->Equals(dataPrincipal))) { + return nullptr; + } + } + } + + return variant.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/DataTransferItem.h b/dom/events/DataTransferItem.h new file mode 100644 index 000000000..ccd4bafa1 --- /dev/null +++ b/dom/events/DataTransferItem.h @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DataTransferItem_h +#define mozilla_dom_DataTransferItem_h + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/DOMString.h" +#include "mozilla/dom/File.h" + +namespace mozilla { +namespace dom { + +class FileSystemEntry; +class FunctionStringCallback; + +class DataTransferItem final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransferItem); + +public: + // The spec only talks about the "file" and "string" kinds. Due to the Moz* + // APIs, it is possible to attach any type to a DataTransferItem, meaning that + // we can have other kinds then just FILE and STRING. These others are simply + // marked as "other" and can only be produced throug the Moz* APIs. + enum eKind { + KIND_FILE, + KIND_STRING, + KIND_OTHER, + }; + + DataTransferItem(DataTransfer* aDataTransfer, const nsAString& aType, + eKind aKind = KIND_OTHER) + : mIndex(0) + , mChromeOnly(false) + , mKind(aKind) + , mType(aType) + , mDataTransfer(aDataTransfer) + { + MOZ_ASSERT(mDataTransfer, "Must be associated with a DataTransfer"); + } + + already_AddRefed<DataTransferItem> Clone(DataTransfer* aDataTransfer) const; + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + void GetAsString(FunctionStringCallback* aCallback, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + + void GetKind(nsAString& aKind) const + { + switch (mKind) { + case KIND_FILE: + aKind = NS_LITERAL_STRING("file"); + return; + case KIND_STRING: + aKind = NS_LITERAL_STRING("string"); + return; + default: + aKind = NS_LITERAL_STRING("other"); + return; + } + } + + void GetInternalType(nsAString& aType) const + { + aType = mType; + } + + void GetType(nsAString& aType); + + eKind Kind() const + { + return mKind; + } + + already_AddRefed<File> + GetAsFile(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv); + + already_AddRefed<FileSystemEntry> + GetAsEntry(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv); + + DataTransfer* GetParentObject() const + { + return mDataTransfer; + } + + nsIPrincipal* Principal() const + { + return mPrincipal; + } + void SetPrincipal(nsIPrincipal* aPrincipal) + { + mPrincipal = aPrincipal; + } + + already_AddRefed<nsIVariant> DataNoSecurityCheck(); + already_AddRefed<nsIVariant> Data(nsIPrincipal* aPrincipal, ErrorResult& aRv); + + // Note: This can modify the mKind. Callers of this method must let the + // relevant DataTransfer know, because its types list can change as a result. + void SetData(nsIVariant* aData); + + uint32_t Index() const + { + return mIndex; + } + void SetIndex(uint32_t aIndex) + { + mIndex = aIndex; + } + void FillInExternalData(); + + bool ChromeOnly() const + { + return mChromeOnly; + } + void SetChromeOnly(bool aChromeOnly) + { + mChromeOnly = aChromeOnly; + } + + static eKind KindFromData(nsIVariant* aData); + +private: + ~DataTransferItem() {} + already_AddRefed<File> CreateFileFromInputStream(nsIInputStream* aStream); + + // The index in the 2d mIndexedItems array + uint32_t mIndex; + + bool mChromeOnly; + eKind mKind; + const nsString mType; + nsCOMPtr<nsIVariant> mData; + nsCOMPtr<nsIPrincipal> mPrincipal; + RefPtr<DataTransfer> mDataTransfer; + + // File cache for nsIFile application/x-moz-file entries. + RefPtr<File> mCachedFile; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_DataTransferItem_h */ diff --git a/dom/events/DataTransferItemList.cpp b/dom/events/DataTransferItemList.cpp new file mode 100644 index 000000000..ecea4968c --- /dev/null +++ b/dom/events/DataTransferItemList.cpp @@ -0,0 +1,583 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DataTransferItemList.h" + +#include "nsContentUtils.h" +#include "nsIGlobalObject.h" +#include "nsIClipboard.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsISupportsPrimitives.h" +#include "nsQueryObject.h" +#include "nsVariant.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/EventForwards.h" +#include "mozilla/storage/Variant.h" +#include "mozilla/dom/DataTransferItemListBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItemList, mDataTransfer, mItems, + mIndexedItems, mFiles) +NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItemList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItemList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItemList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +DataTransferItemList::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) +{ + return DataTransferItemListBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DataTransferItemList> +DataTransferItemList::Clone(DataTransfer* aDataTransfer) const +{ + RefPtr<DataTransferItemList> list = + new DataTransferItemList(aDataTransfer, mIsExternal); + + // We need to clone the mItems and mIndexedItems lists while keeping the same + // correspondences between the mIndexedItems and mItems lists (namely, if an + // item is in mIndexedItems, and mItems it must have the same new identity) + + // First, we copy over indexedItems, and clone every entry. Then, we go over + // mItems. For every entry, we use its mIndex property to locate it in + // mIndexedItems on the original DataTransferItemList, and then copy over the + // reference from the same index pair on the new DataTransferItemList + + list->mIndexedItems.SetLength(mIndexedItems.Length()); + list->mItems.SetLength(mItems.Length()); + + // Copy over mIndexedItems, cloning every entry + for (uint32_t i = 0; i < mIndexedItems.Length(); i++) { + const nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i]; + nsTArray<RefPtr<DataTransferItem>>& newItems = list->mIndexedItems[i]; + newItems.SetLength(items.Length()); + for (uint32_t j = 0; j < items.Length(); j++) { + newItems[j] = items[j]->Clone(aDataTransfer); + } + } + + // Copy over mItems, getting the actual entries from mIndexedItems + for (uint32_t i = 0; i < mItems.Length(); i++) { + uint32_t index = mItems[i]->Index(); + MOZ_ASSERT(index < mIndexedItems.Length()); + uint32_t subIndex = mIndexedItems[index].IndexOf(mItems[i]); + + // Copy over the reference + list->mItems[i] = list->mIndexedItems[index][subIndex]; + } + + return list.forget(); +} + +void +DataTransferItemList::Remove(uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (mDataTransfer->IsReadOnly()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + if (aIndex >= Length()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + ClearDataHelper(mItems[aIndex], aIndex, -1, aSubjectPrincipal, aRv); +} + +DataTransferItem* +DataTransferItemList::IndexedGetter(uint32_t aIndex, bool& aFound) const +{ + if (aIndex >= mItems.Length()) { + aFound = false; + return nullptr; + } + + MOZ_ASSERT(mItems[aIndex]); + aFound = true; + return mItems[aIndex]; +} + +uint32_t +DataTransferItemList::MozItemCount() const +{ + uint32_t length = mIndexedItems.Length(); + // XXX: Compat hack - Index 0 always exists due to changes in internals, but + // if it is empty, scripts using the moz* APIs should see it as not existing. + if (length == 1 && mIndexedItems[0].IsEmpty()) { + return 0; + } + return length; +} + +void +DataTransferItemList::Clear(nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (NS_WARN_IF(mDataTransfer->IsReadOnly())) { + return; + } + + uint32_t count = Length(); + for (uint32_t i = 0; i < count; i++) { + // We always remove the last item first, to avoid moving items around in + // memory as much + Remove(Length() - 1, aSubjectPrincipal, aRv); + ENSURE_SUCCESS_VOID(aRv); + } + + MOZ_ASSERT(Length() == 0); +} + +DataTransferItem* +DataTransferItemList::Add(const nsAString& aData, + const nsAString& aType, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (NS_WARN_IF(mDataTransfer->IsReadOnly())) { + return nullptr; + } + + nsCOMPtr<nsIVariant> data(new storage::TextVariant(aData)); + + nsAutoString format; + mDataTransfer->GetRealFormat(aType, format); + + if (!DataTransfer::PrincipalMaySetData(format, data, &aSubjectPrincipal)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + // We add the textual data to index 0. We set aInsertOnly to true, as we don't + // want to update an existing entry if it is already present, as per the spec. + RefPtr<DataTransferItem> item = + SetDataWithPrincipal(format, data, 0, &aSubjectPrincipal, + /* aInsertOnly = */ true, + /* aHidden = */ false, + aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + MOZ_ASSERT(item->Kind() != DataTransferItem::KIND_FILE); + + return item; +} + +DataTransferItem* +DataTransferItemList::Add(File& aData, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (mDataTransfer->IsReadOnly()) { + return nullptr; + } + + nsCOMPtr<nsISupports> supports = do_QueryObject(&aData); + nsCOMPtr<nsIWritableVariant> data = new nsVariant(); + data->SetAsISupports(supports); + + nsAutoString type; + aData.GetType(type); + + if (!DataTransfer::PrincipalMaySetData(type, data, &aSubjectPrincipal)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + // We need to add this as a new item, as multiple files can't exist in the + // same item in the Moz DataTransfer layout. It will be appended at the end of + // the internal specced layout. + uint32_t index = mIndexedItems.Length(); + RefPtr<DataTransferItem> item = + SetDataWithPrincipal(type, data, index, &aSubjectPrincipal, + /* aInsertOnly = */ true, + /* aHidden = */ false, + aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + MOZ_ASSERT(item->Kind() == DataTransferItem::KIND_FILE); + + return item; +} + +already_AddRefed<FileList> +DataTransferItemList::Files(nsIPrincipal* aPrincipal) +{ + // The DataTransfer can hold data with varying principals, coming from + // different windows. This means that permissions checks need to be made when + // accessing data from the DataTransfer. With the accessor methods, this is + // checked by DataTransferItem::Data(), however with files, we keep a cached + // live copy of the files list for spec compliance. + // + // A DataTransfer is only exposed to one webpage, and chrome code. The chrome + // code should be able to see all files on the DataTransfer, while the webpage + // should only be able to see the files it can see. As chrome code doesn't + // need as strict spec compliance as web visible code, we generate a new + // FileList object every time you access the Files list from chrome code, but + // re-use the cached one when accessing from non-chrome code. + // + // It is not legal to expose an identical DataTransfer object is to multiple + // different principals without using the `Clone` method or similar to copy it + // first. If that happens, this method will assert, and return nullptr in + // release builds. If this functionality is required in the future, a more + // advanced caching mechanism for the FileList objects will be required. + RefPtr<FileList> files; + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { + files = new FileList(static_cast<nsIDOMDataTransfer*>(mDataTransfer)); + GenerateFiles(files, aPrincipal); + return files.forget(); + } + + if (!mFiles) { + mFiles = new FileList(static_cast<nsIDOMDataTransfer*>(mDataTransfer)); + mFilesPrincipal = aPrincipal; + RegenerateFiles(); + } + + if (!aPrincipal->Subsumes(mFilesPrincipal)) { + MOZ_ASSERT(false, "This DataTransfer should only be accessed by the system " + "and a single principal"); + return nullptr; + } + + files = mFiles; + return files.forget(); +} + +void +DataTransferItemList::MozRemoveByTypeAt(const nsAString& aType, + uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + if (NS_WARN_IF(mDataTransfer->IsReadOnly() || + aIndex >= mIndexedItems.Length())) { + return; + } + + bool removeAll = aType.IsEmpty(); + + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex]; + uint32_t count = items.Length(); + // We remove the last item of the list repeatedly - that way we don't + // have to worry about modifying the loop iterator + if (removeAll) { + for (uint32_t i = 0; i < count; ++i) { + uint32_t index = items.Length() - 1; + MOZ_ASSERT(index == count - i - 1); + + ClearDataHelper(items[index], -1, index, aSubjectPrincipal, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } + + // items is no longer a valid reference, as removing the last element from + // it via ClearDataHelper invalidated it. so we can't MOZ_ASSERT that the + // length is now 0. + return; + } + + for (uint32_t i = 0; i < count; ++i) { + // NOTE: As this is a moz-prefixed API, it works based on internal types. + nsAutoString type; + items[i]->GetInternalType(type); + if (type == aType) { + ClearDataHelper(items[i], -1, i, aSubjectPrincipal, aRv); + return; + } + } +} + +DataTransferItem* +DataTransferItemList::MozItemByTypeAt(const nsAString& aType, uint32_t aIndex) +{ + if (NS_WARN_IF(aIndex >= mIndexedItems.Length())) { + return nullptr; + } + + uint32_t count = mIndexedItems[aIndex].Length(); + for (uint32_t i = 0; i < count; i++) { + RefPtr<DataTransferItem> item = mIndexedItems[aIndex][i]; + // NOTE: As this is a moz-prefixed API it works on internal types + nsString type; + item->GetInternalType(type); + if (type.Equals(aType)) { + return item; + } + } + + return nullptr; +} + +already_AddRefed<DataTransferItem> +DataTransferItemList::SetDataWithPrincipal(const nsAString& aType, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal, + bool aInsertOnly, + bool aHidden, + ErrorResult& aRv) +{ + if (aIndex < mIndexedItems.Length()) { + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex]; + uint32_t count = items.Length(); + for (uint32_t i = 0; i < count; i++) { + RefPtr<DataTransferItem> item = items[i]; + nsString type; + item->GetInternalType(type); + if (type.Equals(aType)) { + if (NS_WARN_IF(aInsertOnly)) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + // don't allow replacing data that has a stronger principal + bool subsumes; + if (NS_WARN_IF(item->Principal() && aPrincipal && + (NS_FAILED(aPrincipal->Subsumes(item->Principal(), + &subsumes)) + || !subsumes))) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + item->SetPrincipal(aPrincipal); + + DataTransferItem::eKind oldKind = item->Kind(); + item->SetData(aData); + if (oldKind != item->Kind()) { + // Types list may have changed, even if aIndex == 0. + mDataTransfer->TypesListMayHaveChanged(); + } + + if (aIndex != 0) { + // If the item changes from being a file to not a file or vice-versa, + // its presence in the mItems array may need to change. + if (item->Kind() == DataTransferItem::KIND_FILE && + oldKind != DataTransferItem::KIND_FILE) { + // not file => file + mItems.AppendElement(item); + } else if (item->Kind() != DataTransferItem::KIND_FILE && + oldKind == DataTransferItem::KIND_FILE) { + // file => not file + mItems.RemoveElement(item); + } + } + + // Regenerate the Files array if we have modified a file's status + if (item->Kind() == DataTransferItem::KIND_FILE || + oldKind == DataTransferItem::KIND_FILE) { + RegenerateFiles(); + } + + return item.forget(); + } + } + } else { + // Make sure that we aren't adding past the end of the mIndexedItems array. + // XXX Should this be a MOZ_ASSERT instead? + aIndex = mIndexedItems.Length(); + } + + // Add the new item + RefPtr<DataTransferItem> item = AppendNewItem(aIndex, aType, aData, aPrincipal, aHidden); + + if (item->Kind() == DataTransferItem::KIND_FILE) { + RegenerateFiles(); + } + + return item.forget(); +} + +DataTransferItem* +DataTransferItemList::AppendNewItem(uint32_t aIndex, + const nsAString& aType, + nsIVariant* aData, + nsIPrincipal* aPrincipal, + bool aHidden) +{ + if (mIndexedItems.Length() <= aIndex) { + MOZ_ASSERT(mIndexedItems.Length() == aIndex); + mIndexedItems.AppendElement(); + } + RefPtr<DataTransferItem> item = new DataTransferItem(mDataTransfer, aType); + item->SetIndex(aIndex); + item->SetPrincipal(aPrincipal); + item->SetData(aData); + item->SetChromeOnly(aHidden); + + mIndexedItems[aIndex].AppendElement(item); + + // We only want to add the item to the main mItems list if the index we are + // adding to is 0, or the item we are adding is a file. If we add an item + // which is not a file to a non-zero index, invariants could be broken. + // (namely the invariant that there are not 2 non-file entries in the items + // array with the same type). + // + // We also want to update our DataTransfer's type list any time we're adding a + // KIND_FILE item, or an item at index 0. + if (item->Kind() == DataTransferItem::KIND_FILE || aIndex == 0) { + if (!aHidden) { + mItems.AppendElement(item); + } + mDataTransfer->TypesListMayHaveChanged(); + } + + return item; +} + +const nsTArray<RefPtr<DataTransferItem>>* +DataTransferItemList::MozItemsAt(uint32_t aIndex) // -- INDEXED +{ + if (aIndex >= mIndexedItems.Length()) { + return nullptr; + } + + return &mIndexedItems[aIndex]; +} + +void +DataTransferItemList::PopIndexZero() +{ + MOZ_ASSERT(mIndexedItems.Length() > 1); + MOZ_ASSERT(mIndexedItems[0].IsEmpty()); + + mIndexedItems.RemoveElementAt(0); + + // Update the index of every element which has now been shifted + for (uint32_t i = 0; i < mIndexedItems.Length(); i++) { + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i]; + for (uint32_t j = 0; j < items.Length(); j++) { + items[j]->SetIndex(i); + } + } +} + +void +DataTransferItemList::ClearAllItems() +{ + // We always need to have index 0, so don't delete that one + mItems.Clear(); + mIndexedItems.Clear(); + mIndexedItems.SetLength(1); + mDataTransfer->TypesListMayHaveChanged(); + + // Re-generate files (into an empty list) + RegenerateFiles(); +} + +void +DataTransferItemList::ClearDataHelper(DataTransferItem* aItem, + uint32_t aIndexHint, + uint32_t aMozOffsetHint, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + MOZ_ASSERT(aItem); + if (NS_WARN_IF(mDataTransfer->IsReadOnly())) { + return; + } + + if (aItem->Principal() && !aSubjectPrincipal.Subsumes(aItem->Principal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + // Check if the aIndexHint is actually the index, and then remove the item + // from aItems + bool found; + if (IndexedGetter(aIndexHint, found) == aItem) { + mItems.RemoveElementAt(aIndexHint); + } else { + mItems.RemoveElement(aItem); + } + + // Check if the aMozIndexHint and aMozOffsetHint are actually the index and + // offset, and then remove them from mIndexedItems + MOZ_ASSERT(aItem->Index() < mIndexedItems.Length()); + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aItem->Index()]; + if (aMozOffsetHint < items.Length() && aItem == items[aMozOffsetHint]) { + items.RemoveElementAt(aMozOffsetHint); + } else { + items.RemoveElement(aItem); + } + + mDataTransfer->TypesListMayHaveChanged(); + + // Check if we should remove the index. We never remove index 0. + if (items.Length() == 0 && aItem->Index() != 0) { + mIndexedItems.RemoveElementAt(aItem->Index()); + + // Update the index of every element which has now been shifted + for (uint32_t i = aItem->Index(); i < mIndexedItems.Length(); i++) { + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i]; + for (uint32_t j = 0; j < items.Length(); j++) { + items[j]->SetIndex(i); + } + } + } + + // Give the removed item the invalid index + aItem->SetIndex(-1); + + if (aItem->Kind() == DataTransferItem::KIND_FILE) { + RegenerateFiles(); + } +} + +void +DataTransferItemList::RegenerateFiles() +{ + // We don't want to regenerate the files list unless we already have a files + // list. That way we can avoid the unnecessary work if the user never touches + // the files list. + if (mFiles) { + // We clear the list rather than performing smaller updates, because it + // simplifies the logic greatly on this code path, which should be very + // infrequently used. + mFiles->Clear(); + + DataTransferItemList::GenerateFiles(mFiles, mFilesPrincipal); + } +} + +void +DataTransferItemList::GenerateFiles(FileList* aFiles, + nsIPrincipal* aFilesPrincipal) +{ + MOZ_ASSERT(aFiles); + MOZ_ASSERT(aFilesPrincipal); + uint32_t count = Length(); + for (uint32_t i = 0; i < count; i++) { + bool found; + RefPtr<DataTransferItem> item = IndexedGetter(i, found); + MOZ_ASSERT(found); + + if (item->Kind() == DataTransferItem::KIND_FILE) { + IgnoredErrorResult rv; + RefPtr<File> file = item->GetAsFile(*aFilesPrincipal, rv); + if (NS_WARN_IF(rv.Failed() || !file)) { + continue; + } + aFiles->Append(file); + } + } +} + +} // namespace mozilla +} // namespace dom diff --git a/dom/events/DataTransferItemList.h b/dom/events/DataTransferItemList.h new file mode 100644 index 000000000..230df73a8 --- /dev/null +++ b/dom/events/DataTransferItemList.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DataTransferItemList_h +#define mozilla_dom_DataTransferItemList_h + +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/DataTransferItem.h" +#include "mozilla/dom/FileList.h" + +namespace mozilla { +namespace dom { + +class DataTransferItem; + +class DataTransferItemList final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransferItemList); + + DataTransferItemList(DataTransfer* aDataTransfer, bool aIsExternal) + : mDataTransfer(aDataTransfer) + , mIsExternal(aIsExternal) + { + MOZ_ASSERT(aDataTransfer); + // We always allocate an index 0 in our DataTransferItemList. This is done + // in order to maintain the invariants according to the spec. Mainly, within + // the spec's list, there is intended to be a single copy of each mime type, + // for string typed items. File typed items are allowed to have duplicates. + // In the old moz* system, this was modeled by having multiple indexes, each + // of which was independent. Files were fetched from all indexes, but + // strings were only fetched from the first index. In order to maintain this + // correlation and avoid breaking code with the new changes, index 0 is now + // always present and used to store strings, and all file items are given + // their own index starting at index 1. + mIndexedItems.SetLength(1); + } + + already_AddRefed<DataTransferItemList> Clone(DataTransfer* aDataTransfer) const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + uint32_t Length() const + { + return mItems.Length(); + }; + + DataTransferItem* Add(const nsAString& aData, const nsAString& aType, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& rv); + DataTransferItem* Add(File& aData, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + + void Remove(uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + + DataTransferItem* IndexedGetter(uint32_t aIndex, bool& aFound) const; + + DataTransfer* GetParentObject() const + { + return mDataTransfer; + } + + void Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv); + + already_AddRefed<DataTransferItem> + SetDataWithPrincipal(const nsAString& aType, nsIVariant* aData, + uint32_t aIndex, nsIPrincipal* aPrincipal, + bool aInsertOnly, bool aHidden, ErrorResult& aRv); + + already_AddRefed<FileList> Files(nsIPrincipal* aPrincipal); + + // Moz-style helper methods for interacting with the stored data + void MozRemoveByTypeAt(const nsAString& aType, uint32_t aIndex, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + DataTransferItem* MozItemByTypeAt(const nsAString& aType, uint32_t aIndex); + const nsTArray<RefPtr<DataTransferItem>>* MozItemsAt(uint32_t aIndex); + uint32_t MozItemCount() const; + + // Causes everything in indexes above 0 to shift down one index. + void PopIndexZero(); + + // Delete every item in the DataTransferItemList, without checking for + // permissions or read-only status (for internal use only). + void ClearAllItems(); + +private: + void ClearDataHelper(DataTransferItem* aItem, uint32_t aIndexHint, + uint32_t aMozOffsetHint, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + DataTransferItem* AppendNewItem(uint32_t aIndex, const nsAString& aType, + nsIVariant* aData, nsIPrincipal* aPrincipal, + bool aHidden); + void RegenerateFiles(); + void GenerateFiles(FileList* aFiles, nsIPrincipal* aFilesPrincipal); + + ~DataTransferItemList() {} + + RefPtr<DataTransfer> mDataTransfer; + bool mIsExternal; + RefPtr<FileList> mFiles; + // The principal for which mFiles is cached + nsCOMPtr<nsIPrincipal> mFilesPrincipal; + // mItems is the list of items that corresponds to the spec concept of a + // DataTransferItemList. That is, this is the thing the spec's indexed getter + // operates on. The items in here are a subset of the items present in the + // arrays that live in mIndexedItems. + nsTArray<RefPtr<DataTransferItem>> mItems; + // mIndexedItems represents all our items. For any given index, all items at + // that index have different types in the GetType() sense. That means that + // representing multiple items with the same type (e.g. multiple files) + // requires using multiple indices. + // + // There is always a (possibly empty) list of items at index 0, so + // mIndexedItems.Length() >= 1 at all times. + nsTArray<nsTArray<RefPtr<DataTransferItem>>> mIndexedItems; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_DataTransferItemList_h diff --git a/dom/events/DeviceMotionEvent.cpp b/dom/events/DeviceMotionEvent.cpp new file mode 100644 index 000000000..f8729c18d --- /dev/null +++ b/dom/events/DeviceMotionEvent.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/DeviceMotionEvent.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace dom { + +/****************************************************************************** + * DeviceMotionEvent + *****************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_INHERITED(DeviceMotionEvent, Event, + mAcceleration, + mAccelerationIncludingGravity, + mRotationRate) + +NS_IMPL_ADDREF_INHERITED(DeviceMotionEvent, Event) +NS_IMPL_RELEASE_INHERITED(DeviceMotionEvent, Event) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DeviceMotionEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +void +DeviceMotionEvent::InitDeviceMotionEvent( + const nsAString& aType, + bool aCanBubble, + bool aCancelable, + const DeviceAccelerationInit& aAcceleration, + const DeviceAccelerationInit& aAccelIncludingGravity, + const DeviceRotationRateInit& aRotationRate, + Nullable<double> aInterval) +{ + InitDeviceMotionEvent(aType, aCanBubble, aCancelable, aAcceleration, + aAccelIncludingGravity, aRotationRate, aInterval, + Nullable<uint64_t>()); +} + +void +DeviceMotionEvent::InitDeviceMotionEvent( + const nsAString& aType, + bool aCanBubble, + bool aCancelable, + const DeviceAccelerationInit& aAcceleration, + const DeviceAccelerationInit& aAccelIncludingGravity, + const DeviceRotationRateInit& aRotationRate, + Nullable<double> aInterval, + Nullable<uint64_t> aTimeStamp) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + Event::InitEvent(aType, aCanBubble, aCancelable); + + mAcceleration = new DeviceAcceleration(this, aAcceleration.mX, + aAcceleration.mY, + aAcceleration.mZ); + + mAccelerationIncludingGravity = + new DeviceAcceleration(this, aAccelIncludingGravity.mX, + aAccelIncludingGravity.mY, + aAccelIncludingGravity.mZ); + + mRotationRate = new DeviceRotationRate(this, aRotationRate.mAlpha, + aRotationRate.mBeta, + aRotationRate.mGamma); + mInterval = aInterval; + if (!aTimeStamp.IsNull()) { + mEvent->mTime = aTimeStamp.Value(); + } +} + +already_AddRefed<DeviceMotionEvent> +DeviceMotionEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const DeviceMotionEventInit& aEventInitDict, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<DeviceMotionEvent> e = new DeviceMotionEvent(t, nullptr, nullptr); + e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable); + bool trusted = e->Init(t); + + e->mAcceleration = new DeviceAcceleration(e, + aEventInitDict.mAcceleration.mX, + aEventInitDict.mAcceleration.mY, + aEventInitDict.mAcceleration.mZ); + + e->mAccelerationIncludingGravity = new DeviceAcceleration(e, + aEventInitDict.mAccelerationIncludingGravity.mX, + aEventInitDict.mAccelerationIncludingGravity.mY, + aEventInitDict.mAccelerationIncludingGravity.mZ); + + e->mRotationRate = new DeviceRotationRate(e, + aEventInitDict.mRotationRate.mAlpha, + aEventInitDict.mRotationRate.mBeta, + aEventInitDict.mRotationRate.mGamma); + + e->mInterval = aEventInitDict.mInterval; + e->SetTrusted(trusted); + e->SetComposed(aEventInitDict.mComposed); + return e.forget(); +} + +/****************************************************************************** + * DeviceAcceleration + *****************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DeviceAcceleration, mOwner) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DeviceAcceleration, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DeviceAcceleration, Release) + +DeviceAcceleration::DeviceAcceleration(DeviceMotionEvent* aOwner, + Nullable<double> aX, + Nullable<double> aY, + Nullable<double> aZ) + : mOwner(aOwner) + , mX(aX) + , mY(aY) + , mZ(aZ) +{ +} + +DeviceAcceleration::~DeviceAcceleration() +{ +} + +/****************************************************************************** + * DeviceRotationRate + *****************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DeviceRotationRate, mOwner) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DeviceRotationRate, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DeviceRotationRate, Release) + +DeviceRotationRate::DeviceRotationRate(DeviceMotionEvent* aOwner, + Nullable<double> aAlpha, + Nullable<double> aBeta, + Nullable<double> aGamma) + : mOwner(aOwner) + , mAlpha(aAlpha) + , mBeta(aBeta) + , mGamma(aGamma) +{ +} + +DeviceRotationRate::~DeviceRotationRate() +{ +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<DeviceMotionEvent> +NS_NewDOMDeviceMotionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + RefPtr<DeviceMotionEvent> it = + new DeviceMotionEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/DeviceMotionEvent.h b/dom/events/DeviceMotionEvent.h new file mode 100644 index 000000000..6e6f3d9e5 --- /dev/null +++ b/dom/events/DeviceMotionEvent.h @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DeviceMotionEvent_h_ +#define mozilla_dom_DeviceMotionEvent_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/DeviceMotionEventBinding.h" +#include "mozilla/dom/Event.h" + +namespace mozilla { +namespace dom { + +class DeviceRotationRate final : public nsWrapperCache +{ +public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DeviceRotationRate) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DeviceRotationRate) + + DeviceRotationRate(DeviceMotionEvent* aOwner, + Nullable<double> aAlpha, Nullable<double> aBeta, + Nullable<double> aGamma); + + DeviceMotionEvent* GetParentObject() const + { + return mOwner; + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return DeviceRotationRateBinding::Wrap(aCx, this, aGivenProto); + } + + Nullable<double> GetAlpha() const { return mAlpha; } + Nullable<double> GetBeta() const { return mBeta; } + Nullable<double> GetGamma() const { return mGamma; } + +private: + ~DeviceRotationRate(); + +protected: + RefPtr<DeviceMotionEvent> mOwner; + Nullable<double> mAlpha, mBeta, mGamma; +}; + +class DeviceAcceleration final : public nsWrapperCache +{ +public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DeviceAcceleration) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DeviceAcceleration) + + DeviceAcceleration(DeviceMotionEvent* aOwner, + Nullable<double> aX, Nullable<double> aY, + Nullable<double> aZ); + + DeviceMotionEvent* GetParentObject() const + { + return mOwner; + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return DeviceAccelerationBinding::Wrap(aCx, this, aGivenProto); + } + + Nullable<double> GetX() const { return mX; } + Nullable<double> GetY() const { return mY; } + Nullable<double> GetZ() const { return mZ; } + +private: + ~DeviceAcceleration(); + +protected: + RefPtr<DeviceMotionEvent> mOwner; + Nullable<double> mX, mY, mZ; +}; + +class DeviceMotionEvent final : public Event +{ +public: + + DeviceMotionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) + : Event(aOwner, aPresContext, aEvent) + { + } + + NS_DECL_ISUPPORTS_INHERITED + + // Forward to Event + NS_FORWARD_TO_EVENT + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeviceMotionEvent, Event) + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return DeviceMotionEventBinding::Wrap(aCx, this, aGivenProto); + } + + DeviceAcceleration* GetAcceleration() const + { + return mAcceleration; + } + + DeviceAcceleration* GetAccelerationIncludingGravity() const + { + return mAccelerationIncludingGravity; + } + + DeviceRotationRate* GetRotationRate() const + { + return mRotationRate; + } + + Nullable<double> GetInterval() const + { + return mInterval; + } + + void InitDeviceMotionEvent( + const nsAString& aType, + bool aCanBubble, + bool aCancelable, + const DeviceAccelerationInit& aAcceleration, + const DeviceAccelerationInit& aAccelerationIncludingGravity, + const DeviceRotationRateInit& aRotationRate, + Nullable<double> aInterval); + + void InitDeviceMotionEvent( + const nsAString& aType, + bool aCanBubble, + bool aCancelable, + const DeviceAccelerationInit& aAcceleration, + const DeviceAccelerationInit& aAccelerationIncludingGravity, + const DeviceRotationRateInit& aRotationRate, + Nullable<double> aInterval, + Nullable<uint64_t> aTimeStamp); + + static already_AddRefed<DeviceMotionEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const DeviceMotionEventInit& aEventInitDict, + ErrorResult& aRv); + +protected: + ~DeviceMotionEvent() {} + + RefPtr<DeviceAcceleration> mAcceleration; + RefPtr<DeviceAcceleration> mAccelerationIncludingGravity; + RefPtr<DeviceRotationRate> mRotationRate; + Nullable<double> mInterval; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::DeviceMotionEvent> +NS_NewDOMDeviceMotionEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent); + +#endif // mozilla_dom_DeviceMotionEvent_h_ diff --git a/dom/events/DragEvent.cpp b/dom/events/DragEvent.cpp new file mode 100644 index 000000000..9419ad9e9 --- /dev/null +++ b/dom/events/DragEvent.cpp @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/DragEvent.h" +#include "mozilla/MouseEvents.h" +#include "nsContentUtils.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +DragEvent::DragEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetDragEvent* aEvent) + : MouseEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetDragEvent(false, eVoidEvent, nullptr)) +{ + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); + mEvent->AsMouseEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + } +} + +NS_IMPL_ADDREF_INHERITED(DragEvent, MouseEvent) +NS_IMPL_RELEASE_INHERITED(DragEvent, MouseEvent) + +NS_INTERFACE_MAP_BEGIN(DragEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMDragEvent) +NS_INTERFACE_MAP_END_INHERITING(MouseEvent) + +void +DragEvent::InitDragEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + uint16_t aButton, + EventTarget* aRelatedTarget, + DataTransfer* aDataTransfer) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, + aView, aDetail, aScreenX, aScreenY, + aClientX, aClientY, aCtrlKey, aAltKey, + aShiftKey, aMetaKey, aButton, aRelatedTarget); + if (mEventIsInternal) { + mEvent->AsDragEvent()->mDataTransfer = aDataTransfer; + } +} + +NS_IMETHODIMP +DragEvent::GetDataTransfer(nsIDOMDataTransfer** aDataTransfer) +{ + NS_IF_ADDREF(*aDataTransfer = GetDataTransfer()); + return NS_OK; +} + +DataTransfer* +DragEvent::GetDataTransfer() +{ + // the dataTransfer field of the event caches the DataTransfer associated + // with the drag. It is initialized when an attempt is made to retrieve it + // rather that when the event is created to avoid duplicating the data when + // no listener ever uses it. + if (!mEvent || mEvent->mClass != eDragEventClass) { + NS_WARNING("Tried to get dataTransfer from non-drag event!"); + return nullptr; + } + + WidgetDragEvent* dragEvent = mEvent->AsDragEvent(); + // for synthetic events, just use the supplied data transfer object even if null + if (!mEventIsInternal) { + nsresult rv = nsContentUtils::SetDataTransferInEvent(dragEvent); + NS_ENSURE_SUCCESS(rv, nullptr); + } + + return dragEvent->mDataTransfer; +} + +// static +already_AddRefed<DragEvent> +DragEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const DragEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<DragEvent> e = new DragEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitDragEvent(aType, aParam.mBubbles, aParam.mCancelable, + aParam.mView, aParam.mDetail, aParam.mScreenX, + aParam.mScreenY, aParam.mClientX, aParam.mClientY, + aParam.mCtrlKey, aParam.mAltKey, aParam.mShiftKey, + aParam.mMetaKey, aParam.mButton, aParam.mRelatedTarget, + aParam.mDataTransfer); + e->InitializeExtraMouseEventDictionaryMembers(aParam); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<DragEvent> +NS_NewDOMDragEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetDragEvent* aEvent) +{ + RefPtr<DragEvent> event = + new DragEvent(aOwner, aPresContext, aEvent); + return event.forget(); +} diff --git a/dom/events/DragEvent.h b/dom/events/DragEvent.h new file mode 100644 index 000000000..552b4b7f2 --- /dev/null +++ b/dom/events/DragEvent.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DragEvent_h_ +#define mozilla_dom_DragEvent_h_ + +#include "nsIDOMDragEvent.h" +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/DragEventBinding.h" +#include "mozilla/EventForwards.h" + +namespace mozilla { +namespace dom { + +class DataTransfer; + +class DragEvent : public MouseEvent, + public nsIDOMDragEvent +{ +public: + DragEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetDragEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIDOMDRAGEVENT + + NS_FORWARD_TO_MOUSEEVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return DragEventBinding::Wrap(aCx, this, aGivenProto); + } + + DataTransfer* GetDataTransfer(); + + void InitDragEvent(const nsAString& aType, + bool aCanBubble, bool aCancelable, + nsGlobalWindow* aView, int32_t aDetail, + int32_t aScreenX, int32_t aScreenY, + int32_t aClientX, int32_t aClientY, + bool aCtrlKey, bool aAltKey, bool aShiftKey, + bool aMetaKey, uint16_t aButton, + EventTarget* aRelatedTarget, + DataTransfer* aDataTransfer); + + static already_AddRefed<DragEvent> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const DragEventInit& aParam, + ErrorResult& aRv); + +protected: + ~DragEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::DragEvent> +NS_NewDOMDragEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetDragEvent* aEvent); + +#endif // mozilla_dom_DragEvent_h_ diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp new file mode 100644 index 000000000..a85a0d66b --- /dev/null +++ b/dom/events/Event.cpp @@ -0,0 +1,1305 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccessCheck.h" +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TouchEvents.h" +#include "nsContentUtils.h" +#include "nsCOMPtr.h" +#include "nsDeviceContext.h" +#include "nsError.h" +#include "nsGlobalWindow.h" +#include "nsIFrame.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" +#include "nsIScrollableFrame.h" +#include "nsJSEnvironment.h" +#include "nsLayoutUtils.h" +#include "nsPIWindowRoot.h" +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +namespace workers { +extern bool IsCurrentThreadRunningChromeWorker(); +} // namespace workers + +static char *sPopupAllowedEvents; + +static bool sReturnHighResTimeStamp = false; +static bool sReturnHighResTimeStampIsSet = false; + +Event::Event(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + ConstructorInit(aOwner, aPresContext, aEvent); +} + +Event::Event(nsPIDOMWindowInner* aParent) +{ + ConstructorInit(nsGlobalWindow::Cast(aParent), nullptr, nullptr); +} + +void +Event::ConstructorInit(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + SetOwner(aOwner); + mIsMainThreadEvent = NS_IsMainThread(); + + if (mIsMainThreadEvent && !sReturnHighResTimeStampIsSet) { + Preferences::AddBoolVarCache(&sReturnHighResTimeStamp, + "dom.event.highrestimestamp.enabled", + sReturnHighResTimeStamp); + sReturnHighResTimeStampIsSet = true; + } + + mPrivateDataDuplicated = false; + mWantsPopupControlCheck = false; + + if (aEvent) { + mEvent = aEvent; + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + /* + A derived class might want to allocate its own type of aEvent + (derived from WidgetEvent). To do this, it should take care to pass + a non-nullptr aEvent to this ctor, e.g.: + + FooEvent::FooEvent(..., WidgetEvent* aEvent) + : Event(..., aEvent ? aEvent : new WidgetEvent()) + + Then, to override the mEventIsInternal assignments done by the + base ctor, it should do this in its own ctor: + + FooEvent::FooEvent(..., WidgetEvent* aEvent) + ... + { + ... + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + } + ... + } + */ + mEvent = new WidgetEvent(false, eVoidEvent); + mEvent->mTime = PR_Now(); + } + + InitPresContextData(aPresContext); +} + +void +Event::InitPresContextData(nsPresContext* aPresContext) +{ + mPresContext = aPresContext; + // Get the explicit original target (if it's anonymous make it null) + { + nsCOMPtr<nsIContent> content = GetTargetFromFrame(); + mExplicitOriginalTarget = content; + if (content && content->IsInAnonymousSubtree()) { + mExplicitOriginalTarget = nullptr; + } + } +} + +Event::~Event() +{ + NS_ASSERT_OWNINGTHREAD(Event); + + if (mEventIsInternal && mEvent) { + delete mEvent; + } +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Event) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIDOMEvent) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Event) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Event) + +NS_IMPL_CYCLE_COLLECTION_CLASS(Event) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Event) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event) + if (tmp->mEventIsInternal) { + tmp->mEvent->mTarget = nullptr; + tmp->mEvent->mCurrentTarget = nullptr; + tmp->mEvent->mOriginalTarget = 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: + tmp->mEvent->AsClipboardEvent()->mClipboardData = nullptr; + break; + case eMutationEventClass: + tmp->mEvent->AsMutationEvent()->mRelatedNode = nullptr; + break; + case eFocusEventClass: + tmp->mEvent->AsFocusEvent()->mRelatedTarget = nullptr; + break; + default: + break; + } + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mExplicitOriginalTarget); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event) + if (tmp->mEventIsInternal) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mCurrentTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalTarget) + 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: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mClipboardData"); + cb.NoteXPCOMChild(tmp->mEvent->AsClipboardEvent()->mClipboardData); + break; + case eMutationEventClass: + 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; + } + } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExplicitOriginalTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + + +JSObject* +Event::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return WrapObjectInternal(aCx, aGivenProto); +} + +JSObject* +Event::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return EventBinding::Wrap(aCx, this, aGivenProto); +} + +bool +Event::IsChrome(JSContext* aCx) const +{ + return mIsMainThreadEvent ? + xpc::AccessCheck::isChrome(js::GetContextCompartment(aCx)) : + mozilla::dom::workers::IsCurrentThreadRunningChromeWorker(); +} + +// nsIDOMEventInterface +NS_IMETHODIMP +Event::GetType(nsAString& aType) +{ + if (!mIsMainThreadEvent || !mEvent->mSpecifiedEventTypeString.IsEmpty()) { + aType = mEvent->mSpecifiedEventTypeString; + return NS_OK; + } + const char* name = GetEventName(mEvent->mMessage); + + if (name) { + CopyASCIItoUTF16(name, aType); + return NS_OK; + } else if (mEvent->mMessage == eUnidentifiedEvent && + mEvent->mSpecifiedEventType) { + // Remove "on" + aType = Substring(nsDependentAtomString(mEvent->mSpecifiedEventType), 2); + mEvent->mSpecifiedEventTypeString = aType; + return NS_OK; + } + + aType.Truncate(); + return NS_OK; +} + +static EventTarget* +GetDOMEventTarget(nsIDOMEventTarget* aTarget) +{ + return aTarget ? aTarget->GetTargetForDOMEvent() : nullptr; +} + +EventTarget* +Event::GetTarget() const +{ + return GetDOMEventTarget(mEvent->mTarget); +} + +NS_IMETHODIMP +Event::GetTarget(nsIDOMEventTarget** aTarget) +{ + NS_IF_ADDREF(*aTarget = GetTarget()); + return NS_OK; +} + +EventTarget* +Event::GetCurrentTarget() const +{ + return GetDOMEventTarget(mEvent->mCurrentTarget); +} + +NS_IMETHODIMP +Event::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget) +{ + NS_IF_ADDREF(*aCurrentTarget = GetCurrentTarget()); + return NS_OK; +} + +// +// Get the actual event target node (may have been retargeted for mouse events) +// +already_AddRefed<nsIContent> +Event::GetTargetFromFrame() +{ + if (!mPresContext) { return nullptr; } + + // Get the mTarget frame (have to get the ESM first) + nsIFrame* targetFrame = mPresContext->EventStateManager()->GetEventTarget(); + if (!targetFrame) { return nullptr; } + + // get the real content + nsCOMPtr<nsIContent> realEventContent; + targetFrame->GetContentForEvent(mEvent, getter_AddRefs(realEventContent)); + return realEventContent.forget(); +} + +EventTarget* +Event::GetExplicitOriginalTarget() const +{ + if (mExplicitOriginalTarget) { + return mExplicitOriginalTarget; + } + return GetTarget(); +} + +NS_IMETHODIMP +Event::GetExplicitOriginalTarget(nsIDOMEventTarget** aRealEventTarget) +{ + NS_IF_ADDREF(*aRealEventTarget = GetExplicitOriginalTarget()); + return NS_OK; +} + +EventTarget* +Event::GetOriginalTarget() const +{ + if (mEvent->mOriginalTarget) { + return GetDOMEventTarget(mEvent->mOriginalTarget); + } + + return GetTarget(); +} + +NS_IMETHODIMP +Event::GetOriginalTarget(nsIDOMEventTarget** aOriginalTarget) +{ + NS_IF_ADDREF(*aOriginalTarget = GetOriginalTarget()); + return NS_OK; +} + +EventTarget* +Event::GetComposedTarget() const +{ + EventTarget* et = GetOriginalTarget(); + nsCOMPtr<nsIContent> content = do_QueryInterface(et); + if (!content) { + return et; + } + nsIContent* nonChrome = content->FindFirstNonChromeOnlyAccessContent(); + return nonChrome ? + static_cast<EventTarget*>(nonChrome) : + static_cast<EventTarget*>(content->GetComposedDoc()); +} + +NS_IMETHODIMP_(void) +Event::SetTrusted(bool aTrusted) +{ + mEvent->mFlags.mIsTrusted = aTrusted; +} + +bool +Event::Init(mozilla::dom::EventTarget* aGlobal) +{ + if (!mIsMainThreadEvent) { + return nsContentUtils::ThreadsafeIsCallerChrome(); + } + bool trusted = false; + nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(aGlobal); + if (w) { + nsCOMPtr<nsIDocument> d = w->GetExtantDoc(); + if (d) { + trusted = nsContentUtils::IsChromeDoc(d); + nsIPresShell* s = d->GetShell(); + if (s) { + InitPresContextData(s->GetPresContext()); + } + } + } + return trusted; +} + +// static +already_AddRefed<Event> +Event::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const EventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<Event> e = new Event(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +uint16_t +Event::EventPhase() const +{ + // Note, remember to check that this works also + // if or when Bug 235441 is fixed. + if ((mEvent->mCurrentTarget && + mEvent->mCurrentTarget == mEvent->mTarget) || + mEvent->mFlags.InTargetPhase()) { + return nsIDOMEvent::AT_TARGET; + } + if (mEvent->mFlags.mInCapturePhase) { + return nsIDOMEvent::CAPTURING_PHASE; + } + if (mEvent->mFlags.mInBubblingPhase) { + return nsIDOMEvent::BUBBLING_PHASE; + } + return nsIDOMEvent::NONE; +} + +NS_IMETHODIMP +Event::GetEventPhase(uint16_t* aEventPhase) +{ + *aEventPhase = EventPhase(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetBubbles(bool* aBubbles) +{ + *aBubbles = Bubbles(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetCancelable(bool* aCancelable) +{ + *aCancelable = Cancelable(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetTimeStamp(uint64_t* aTimeStamp) +{ + *aTimeStamp = mEvent->mTime; + return NS_OK; +} + +NS_IMETHODIMP +Event::StopPropagation() +{ + mEvent->StopPropagation(); + return NS_OK; +} + +NS_IMETHODIMP +Event::StopImmediatePropagation() +{ + mEvent->StopImmediatePropagation(); + return NS_OK; +} + +NS_IMETHODIMP +Event::StopCrossProcessForwarding() +{ + mEvent->StopCrossProcessForwarding(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetIsTrusted(bool* aIsTrusted) +{ + *aIsTrusted = IsTrusted(); + return NS_OK; +} + +NS_IMETHODIMP +Event::PreventDefault() +{ + // This method is called only from C++ code which must handle default action + // of this event. So, pass true always. + PreventDefaultInternal(true); + return NS_OK; +} + +void +Event::PreventDefault(JSContext* aCx) +{ + MOZ_ASSERT(aCx, "JS context must be specified"); + + // Note that at handling default action, another event may be dispatched. + // Then, JS in content mey be call preventDefault() + // even in the event is in system event group. Therefore, don't refer + // mInSystemGroup here. + PreventDefaultInternal(IsChrome(aCx)); +} + +void +Event::PreventDefaultInternal(bool aCalledByDefaultHandler) +{ + if (!mEvent->mFlags.mCancelable) { + return; + } + if (mEvent->mFlags.mInPassiveListener) { + nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(mOwner)); + if (win) { + if (nsIDocument* doc = win->GetExtantDoc()) { + nsString type; + GetType(type); + const char16_t* params[] = { type.get() }; + doc->WarnOnceAbout(nsIDocument::ePreventDefaultFromPassiveListener, + false, params, ArrayLength(params)); + } + } + return; + } + + mEvent->PreventDefault(aCalledByDefaultHandler); + + if (!IsTrusted()) { + return; + } + + WidgetDragEvent* dragEvent = mEvent->AsDragEvent(); + if (!dragEvent) { + return; + } + + nsCOMPtr<nsINode> node = do_QueryInterface(mEvent->mCurrentTarget); + if (!node) { + nsCOMPtr<nsPIDOMWindowOuter> win = + do_QueryInterface(mEvent->mCurrentTarget); + if (!win) { + return; + } + node = win->GetExtantDoc(); + } + if (!nsContentUtils::IsChromeDoc(node->OwnerDoc())) { + dragEvent->mDefaultPreventedOnContent = true; + } +} + +void +Event::SetEventType(const nsAString& aEventTypeArg) +{ + if (mIsMainThreadEvent) { + mEvent->mSpecifiedEventTypeString.Truncate(); + mEvent->mSpecifiedEventType = + nsContentUtils::GetEventMessageAndAtom(aEventTypeArg, mEvent->mClass, + &(mEvent->mMessage)); + mEvent->SetDefaultComposed(); + } else { + mEvent->mSpecifiedEventType = nullptr; + mEvent->mMessage = eUnidentifiedEvent; + mEvent->mSpecifiedEventTypeString = aEventTypeArg; + mEvent->SetComposed(aEventTypeArg); + } + mEvent->SetDefaultComposedInNativeAnonymousContent(); +} + +void +Event::InitEvent(const nsAString& aEventTypeArg, + bool aCanBubbleArg, + bool aCancelableArg) +{ + // Make sure this event isn't already being dispatched. + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + if (IsTrusted()) { + // Ensure the caller is permitted to dispatch trusted DOM events. + if (!nsContentUtils::ThreadsafeIsCallerChrome()) { + SetTrusted(false); + } + } + + SetEventType(aEventTypeArg); + + mEvent->mFlags.mBubbles = aCanBubbleArg; + mEvent->mFlags.mCancelable = aCancelableArg; + + mEvent->mFlags.mDefaultPrevented = false; + mEvent->mFlags.mDefaultPreventedByContent = false; + mEvent->mFlags.mDefaultPreventedByChrome = false; + mEvent->mFlags.mPropagationStopped = false; + mEvent->mFlags.mImmediatePropagationStopped = false; + + // Clearing the old targets, so that the event is targeted correctly when + // re-dispatching it. + mEvent->mTarget = nullptr; + mEvent->mOriginalTarget = nullptr; +} + +NS_IMETHODIMP +Event::DuplicatePrivateData() +{ + NS_ASSERTION(mEvent, "No WidgetEvent for Event duplication!"); + if (mEventIsInternal) { + return NS_OK; + } + + mEvent = mEvent->Duplicate(); + mPresContext = nullptr; + mEventIsInternal = true; + mPrivateDataDuplicated = true; + + return NS_OK; +} + +NS_IMETHODIMP +Event::SetTarget(nsIDOMEventTarget* aTarget) +{ + mEvent->mTarget = do_QueryInterface(aTarget); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +Event::IsDispatchStopped() +{ + return mEvent->PropagationStopped(); +} + +NS_IMETHODIMP_(WidgetEvent*) +Event::WidgetEventPtr() +{ + return mEvent; +} + +NS_IMETHODIMP_(Event*) +Event::InternalDOMEvent() +{ + return this; +} + +// return true if eventName is contained within events, delimited by +// spaces +static bool +PopupAllowedForEvent(const char *eventName) +{ + if (!sPopupAllowedEvents) { + Event::PopupAllowedEventsChanged(); + + if (!sPopupAllowedEvents) { + return false; + } + } + + nsDependentCString events(sPopupAllowedEvents); + + nsAFlatCString::const_iterator start, end; + nsAFlatCString::const_iterator startiter(events.BeginReading(start)); + events.EndReading(end); + + while (startiter != end) { + nsAFlatCString::const_iterator enditer(end); + + if (!FindInReadable(nsDependentCString(eventName), startiter, enditer)) + return false; + + // the match is surrounded by spaces, or at a string boundary + if ((startiter == start || *--startiter == ' ') && + (enditer == end || *enditer == ' ')) { + return true; + } + + // Move on and see if there are other matches. (The delimitation + // requirement makes it pointless to begin the next search before + // the end of the invalid match just found.) + startiter = enditer; + } + + return false; +} + +// static +PopupControlState +Event::GetEventPopupControlState(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent) +{ + // generally if an event handler is running, new windows are disallowed. + // check for exceptions: + PopupControlState abuse = openAbused; + + if (aDOMEvent && aDOMEvent->InternalDOMEvent()->GetWantsPopupControlCheck()) { + nsAutoString type; + aDOMEvent->GetType(type); + if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) { + return openAllowed; + } + } + + switch(aEvent->mClass) { + case eBasicEventClass: + // For these following events only allow popups if they're + // triggered while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eFormSelect: + if (PopupAllowedForEvent("select")) { + abuse = openControlled; + } + break; + case eFormChange: + if (PopupAllowedForEvent("change")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eEditorInputEventClass: + // For this following event only allow popups if it's triggered + // while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eEditorInput: + if (PopupAllowedForEvent("input")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eInputEventClass: + // For this following event only allow popups if it's triggered + // while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eFormChange: + if (PopupAllowedForEvent("change")) { + abuse = openControlled; + } + break; + case eXULCommand: + abuse = openControlled; + break; + default: + break; + } + } + break; + case eKeyboardEventClass: + if (aEvent->IsTrusted()) { + uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode; + switch(aEvent->mMessage) { + case eKeyPress: + // return key on focused button. see note at eMouseClick. + if (key == NS_VK_RETURN) { + abuse = openAllowed; + } else if (PopupAllowedForEvent("keypress")) { + abuse = openControlled; + } + break; + case eKeyUp: + // space key on focused button. see note at eMouseClick. + if (key == NS_VK_SPACE) { + abuse = openAllowed; + } else if (PopupAllowedForEvent("keyup")) { + abuse = openControlled; + } + break; + case eKeyDown: + if (PopupAllowedForEvent("keydown")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eTouchEventClass: + if (aEvent->IsTrusted()) { + switch (aEvent->mMessage) { + case eTouchStart: + if (PopupAllowedForEvent("touchstart")) { + abuse = openControlled; + } + break; + case eTouchEnd: + if (PopupAllowedForEvent("touchend")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eMouseEventClass: + if (aEvent->IsTrusted() && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) { + switch(aEvent->mMessage) { + case eMouseUp: + if (PopupAllowedForEvent("mouseup")) { + abuse = openControlled; + } + break; + case eMouseDown: + if (PopupAllowedForEvent("mousedown")) { + abuse = openControlled; + } + break; + case eMouseClick: + /* Click events get special treatment because of their + historical status as a more legitimate event handler. If + click popups are enabled in the prefs, clear the popup + status completely. */ + if (PopupAllowedForEvent("click")) { + abuse = openAllowed; + } + break; + case eMouseDoubleClick: + if (PopupAllowedForEvent("dblclick")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eFormEventClass: + // For these following events only allow popups if they're + // triggered while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eFormSubmit: + if (PopupAllowedForEvent("submit")) { + abuse = openControlled; + } + break; + case eFormReset: + if (PopupAllowedForEvent("reset")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + default: + break; + } + + return abuse; +} + +// static +void +Event::PopupAllowedEventsChanged() +{ + if (sPopupAllowedEvents) { + free(sPopupAllowedEvents); + } + + nsAdoptingCString str = Preferences::GetCString("dom.popup_allowed_events"); + + // We'll want to do this even if str is empty to avoid looking up + // this pref all the time if it's not set. + sPopupAllowedEvents = ToNewCString(str); +} + +// static +void +Event::Shutdown() +{ + if (sPopupAllowedEvents) { + free(sPopupAllowedEvents); + } +} + +// static +CSSIntPoint +Event::GetScreenCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint) +{ + if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode() && + nsContentUtils::ResistFingerprinting()) { + // When resisting fingerprinting, return client coordinates instead. + return GetClientCoords(aPresContext, aEvent, aPoint, CSSIntPoint(0, 0)); + } + + if (EventStateManager::sIsPointerLocked) { + return EventStateManager::sLastScreenPoint; + } + + if (!aEvent || + (aEvent->mClass != eMouseEventClass && + aEvent->mClass != eMouseScrollEventClass && + aEvent->mClass != eWheelEventClass && + aEvent->mClass != ePointerEventClass && + aEvent->mClass != eTouchEventClass && + aEvent->mClass != eDragEventClass && + aEvent->mClass != eSimpleGestureEventClass)) { + return CSSIntPoint(0, 0); + } + + // Doing a straight conversion from LayoutDeviceIntPoint to CSSIntPoint + // seem incorrect, but it is needed to maintain legacy functionality. + WidgetGUIEvent* guiEvent = aEvent->AsGUIEvent(); + if (!aPresContext || !(guiEvent && guiEvent->mWidget)) { + return CSSIntPoint(aPoint.x, aPoint.y); + } + + nsPoint pt = + LayoutDevicePixel::ToAppUnits(aPoint, aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()); + + if (nsIPresShell* ps = aPresContext->GetPresShell()) { + pt = pt.RemoveResolution(nsLayoutUtils::GetCurrentAPZResolutionScale(ps)); + } + + pt += LayoutDevicePixel::ToAppUnits(guiEvent->mWidget->WidgetToScreenOffset(), + aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()); + + return CSSPixel::FromAppUnitsRounded(pt); +} + +// static +CSSIntPoint +Event::GetPageCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint) +{ + CSSIntPoint pagePoint = + Event::GetClientCoords(aPresContext, aEvent, aPoint, aDefaultPoint); + + // If there is some scrolling, add scroll info to client point. + if (aPresContext && aPresContext->GetPresShell()) { + nsIPresShell* shell = aPresContext->GetPresShell(); + nsIScrollableFrame* scrollframe = shell->GetRootScrollFrameAsScrollable(); + if (scrollframe) { + pagePoint += CSSIntPoint::FromAppUnitsRounded(scrollframe->GetScrollPosition()); + } + } + + return pagePoint; +} + +// static +CSSIntPoint +Event::GetClientCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint) +{ + if (EventStateManager::sIsPointerLocked) { + return EventStateManager::sLastClientPoint; + } + + if (!aEvent || + (aEvent->mClass != eMouseEventClass && + aEvent->mClass != eMouseScrollEventClass && + aEvent->mClass != eWheelEventClass && + aEvent->mClass != eTouchEventClass && + aEvent->mClass != eDragEventClass && + aEvent->mClass != ePointerEventClass && + aEvent->mClass != eSimpleGestureEventClass) || + !aPresContext || + !aEvent->AsGUIEvent()->mWidget) { + return aDefaultPoint; + } + + nsIPresShell* shell = aPresContext->GetPresShell(); + if (!shell) { + return CSSIntPoint(0, 0); + } + nsIFrame* rootFrame = shell->GetRootFrame(); + if (!rootFrame) { + return CSSIntPoint(0, 0); + } + nsPoint pt = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, aPoint, rootFrame); + + return CSSIntPoint::FromAppUnitsRounded(pt); +} + +// static +CSSIntPoint +Event::GetOffsetCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint) +{ + if (!aEvent->mTarget) { + return GetPageCoords(aPresContext, aEvent, aPoint, aDefaultPoint); + } + nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mTarget); + if (!content || !aPresContext) { + return CSSIntPoint(0, 0); + } + nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell(); + if (!shell) { + return CSSIntPoint(0, 0); + } + shell->FlushPendingNotifications(Flush_Layout); + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + return CSSIntPoint(0, 0); + } + nsIFrame* rootFrame = shell->GetRootFrame(); + if (!rootFrame) { + return CSSIntPoint(0, 0); + } + CSSIntPoint clientCoords = + GetClientCoords(aPresContext, aEvent, aPoint, aDefaultPoint); + nsPoint pt = CSSPixel::ToAppUnits(clientCoords); + if (nsLayoutUtils::TransformPoint(rootFrame, frame, pt) == + nsLayoutUtils::TRANSFORM_SUCCEEDED) { + pt -= frame->GetPaddingRectRelativeToSelf().TopLeft(); + return CSSPixel::FromAppUnitsRounded(pt); + } + return CSSIntPoint(0, 0); +} + +// To be called ONLY by Event::GetType (which has the additional +// logic for handling user-defined events). +// static +const char* +Event::GetEventName(EventMessage aEventType) +{ + switch(aEventType) { +#define MESSAGE_TO_EVENT(name_, _message, _type, _struct) \ + case _message: return #name_; +#include "mozilla/EventNameList.h" +#undef MESSAGE_TO_EVENT + default: + break; + } + // XXXldb We can hit this case for WidgetEvent objects that we didn't + // create and that are not user defined events since this function and + // SetEventType are incomplete. (But fixing that requires fixing the + // arrays in nsEventListenerManager too, since the events for which + // this is a problem generally *are* created by Event.) + return nullptr; +} + +bool +Event::DefaultPrevented(JSContext* aCx) const +{ + MOZ_ASSERT(aCx, "JS context must be specified"); + + NS_ENSURE_TRUE(mEvent, false); + + // If preventDefault() has never been called, just return false. + if (!mEvent->DefaultPrevented()) { + return false; + } + + // If preventDefault() has been called by content, return true. Otherwise, + // i.e., preventDefault() has been called by chrome, return true only when + // this is called by chrome. + return mEvent->DefaultPreventedByContent() || IsChrome(aCx); +} + +double +Event::TimeStamp() const +{ + if (!sReturnHighResTimeStamp) { + return static_cast<double>(mEvent->mTime); + } + + if (mEvent->mTimeStamp.IsNull()) { + return 0.0; + } + + if (mIsMainThreadEvent) { + if (NS_WARN_IF(!mOwner)) { + return 0.0; + } + + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(mOwner); + if (NS_WARN_IF(!win)) { + return 0.0; + } + + Performance* perf = win->GetPerformance(); + if (NS_WARN_IF(!perf)) { + return 0.0; + } + + return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->mTimeStamp); + } + + // For dedicated workers, we should make times relative to the navigation + // start of the document that created the worker, which is the same as the + // timebase for performance.now(). + workers::WorkerPrivate* workerPrivate = + workers::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + TimeDuration duration = + mEvent->mTimeStamp - workerPrivate->NowBaseTimeStamp(); + return duration.ToMilliseconds(); +} + +bool +Event::GetPreventDefault() const +{ + nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(mOwner)); + if (win) { + if (nsIDocument* doc = win->GetExtantDoc()) { + doc->WarnOnceAbout(nsIDocument::eGetPreventDefault); + } + } + // GetPreventDefault() is legacy and Gecko specific method. Although, + // the result should be same as defaultPrevented, we don't need to break + // backward compatibility of legacy method. Let's behave traditionally. + return DefaultPrevented(); +} + +NS_IMETHODIMP +Event::GetPreventDefault(bool* aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + *aReturn = GetPreventDefault(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetDefaultPrevented(bool* aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + // This method must be called by only event handlers implemented by C++. + // Then, the handlers must handle default action. So, this method don't need + // to check if preventDefault() has been called by content or chrome. + *aReturn = DefaultPrevented(); + return NS_OK; +} + +NS_IMETHODIMP_(void) +Event::Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType) +{ + if (aSerializeInterfaceType) { + IPC::WriteParam(aMsg, NS_LITERAL_STRING("event")); + } + + nsString type; + GetType(type); + IPC::WriteParam(aMsg, type); + + IPC::WriteParam(aMsg, Bubbles()); + IPC::WriteParam(aMsg, Cancelable()); + IPC::WriteParam(aMsg, IsTrusted()); + IPC::WriteParam(aMsg, Composed()); + + // No timestamp serialization for now! +} + +NS_IMETHODIMP_(bool) +Event::Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) +{ + nsString type; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &type), false); + + bool bubbles = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &bubbles), false); + + bool cancelable = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &cancelable), false); + + bool trusted = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &trusted), false); + + bool composed = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &composed), false); + + InitEvent(type, bubbles, cancelable); + SetTrusted(trusted); + SetComposed(composed); + + return true; +} + +NS_IMETHODIMP_(void) +Event::SetOwner(mozilla::dom::EventTarget* aOwner) +{ + mOwner = nullptr; + + if (!aOwner) { + return; + } + + nsCOMPtr<nsINode> n = do_QueryInterface(aOwner); + if (n) { + mOwner = n->OwnerDoc()->GetScopeObject(); + return; + } + + nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(aOwner); + if (w) { + mOwner = do_QueryInterface(w); + return; + } + + nsCOMPtr<DOMEventTargetHelper> eth = do_QueryInterface(aOwner); + if (eth) { + mOwner = eth->GetParentObject(); + return; + } + +#ifdef DEBUG + nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(aOwner); + MOZ_ASSERT(root, "Unexpected EventTarget!"); +#endif +} + +// static +nsIContent* +Event::GetShadowRelatedTarget(nsIContent* aCurrentTarget, + nsIContent* aRelatedTarget) +{ + if (!aCurrentTarget || !aRelatedTarget) { + return nullptr; + } + + // Walk up the ancestor node trees of the related target until + // we encounter the node tree of the current target in order + // to find the adjusted related target. Walking up the tree may + // not find a common ancestor node tree if the related target is in + // an ancestor tree, but in that case it does not need to be adjusted. + ShadowRoot* currentTargetShadow = aCurrentTarget->GetContainingShadow(); + if (!currentTargetShadow) { + return nullptr; + } + + nsIContent* relatedTarget = aCurrentTarget; + while (relatedTarget) { + ShadowRoot* ancestorShadow = relatedTarget->GetContainingShadow(); + if (currentTargetShadow == ancestorShadow) { + return relatedTarget; + } + + // Didn't find the ancestor tree, thus related target does not have to + // adjusted. + if (!ancestorShadow) { + return nullptr; + } + + relatedTarget = ancestorShadow->GetHost(); + } + + return nullptr; +} + +NS_IMETHODIMP +Event::GetCancelBubble(bool* aCancelBubble) +{ + NS_ENSURE_ARG_POINTER(aCancelBubble); + *aCancelBubble = CancelBubble(); + return NS_OK; +} + +NS_IMETHODIMP +Event::SetCancelBubble(bool aCancelBubble) +{ + if (aCancelBubble) { + mEvent->StopPropagation(); + } + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<Event> +NS_NewDOMEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + RefPtr<Event> it = new Event(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/Event.h b/dom/events/Event.h new file mode 100644 index 000000000..4ac6a68d5 --- /dev/null +++ b/dom/events/Event.h @@ -0,0 +1,421 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_Event_h_ +#define mozilla_dom_Event_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/BasicEvents.h" +#include "nsIDOMEvent.h" +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsPIDOMWindow.h" +#include "nsPoint.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/EventBinding.h" +#include "nsIScriptGlobalObject.h" +#include "Units.h" +#include "js/TypeDecls.h" +#include "nsIGlobalObject.h" + +class nsIContent; +class nsIDOMEventTarget; +class nsPresContext; + +namespace mozilla { +namespace dom { + +class EventTarget; +class EventMessageAutoOverride; +class WantsPopupControlCheck; +#define GENERATED_EVENT(EventClass_) class EventClass_; +#include "mozilla/dom/GeneratedEventList.h" +#undef GENERATED_EVENT +// ExtendableEvent is a ServiceWorker event that is not +// autogenerated since it has some extra methods. +namespace workers { +class ExtendableEvent; +} // namespace workers + +// Dummy class so we can cast through it to get from nsISupports to +// Event subclasses with only two non-ambiguous static casts. +class EventBase : public nsIDOMEvent +{ +}; + +class Event : public EventBase, + public nsWrapperCache +{ +public: + Event(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent); + explicit Event(nsPIDOMWindowInner* aWindow); + +protected: + virtual ~Event(); + +private: + void ConstructorInit(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent); + +public: + static Event* FromSupports(nsISupports* aSupports) + { + nsIDOMEvent* event = + static_cast<nsIDOMEvent*>(aSupports); +#ifdef DEBUG + { + nsCOMPtr<nsIDOMEvent> target_qi = + do_QueryInterface(aSupports); + + // If this assertion fires the QI implementation for the object in + // question doesn't use the nsIDOMEvent pointer as the + // nsISupports pointer. That must be fixed, or we'll crash... + MOZ_ASSERT(target_qi == event, "Uh, fix QI!"); + } +#endif + return static_cast<Event*>(event); + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Event) + + nsIGlobalObject* GetParentObject() + { + return mOwner; + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final; + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto); + +#define GENERATED_EVENT(EventClass_) \ + virtual EventClass_* As##EventClass_() \ + { \ + return nullptr; \ + } +#include "mozilla/dom/GeneratedEventList.h" +#undef GENERATED_EVENT + + // ExtendableEvent is a ServiceWorker event that is not + // autogenerated since it has some extra methods. + virtual workers::ExtendableEvent* AsExtendableEvent() + { + return nullptr; + } + + // nsIDOMEvent Interface + NS_DECL_NSIDOMEVENT + + void InitPresContextData(nsPresContext* aPresContext); + + // Returns true if the event should be trusted. + bool Init(EventTarget* aGlobal); + + static PopupControlState GetEventPopupControlState(WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent = nullptr); + + static void PopupAllowedEventsChanged(); + + static void Shutdown(); + + static const char* GetEventName(EventMessage aEventType); + static CSSIntPoint GetClientCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint); + static CSSIntPoint GetPageCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint); + static CSSIntPoint GetScreenCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint); + static CSSIntPoint GetOffsetCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint); + + static already_AddRefed<Event> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const EventInit& aParam, + ErrorResult& aRv); + + // Implemented as xpidl method + // void GetType(nsString& aRetval) {} + + EventTarget* GetTarget() const; + EventTarget* GetCurrentTarget() const; + + uint16_t EventPhase() const; + + // xpidl implementation + // void StopPropagation(); + + // xpidl implementation + // void StopImmediatePropagation(); + + bool Bubbles() const + { + return mEvent->mFlags.mBubbles; + } + + bool Cancelable() const + { + return mEvent->mFlags.mCancelable; + } + + bool Composed() const + { + return mEvent->mFlags.mComposed; + } + + bool CancelBubble() const + { + return mEvent->PropagationStopped(); + } + + // xpidl implementation + // void PreventDefault(); + + // You MUST NOT call PreventDefaultJ(JSContext*) from C++ code. A call of + // this method always sets Event.defaultPrevented true for web contents. + // If default action handler calls this, web applications meet wrong + // defaultPrevented value. + virtual void PreventDefault(JSContext* aCx); + + // You MUST NOT call DefaultPrevented(JSContext*) from C++ code. This may + // return false even if PreventDefault() has been called. + // See comments in its implementation for the detail. + bool DefaultPrevented(JSContext* aCx) const; + + bool DefaultPrevented() const + { + return mEvent->DefaultPrevented(); + } + + bool DefaultPreventedByChrome() const + { + return mEvent->mFlags.mDefaultPreventedByChrome; + } + + bool DefaultPreventedByContent() const + { + return mEvent->mFlags.mDefaultPreventedByContent; + } + + bool MultipleActionsPrevented() const + { + return mEvent->mFlags.mMultipleActionsPrevented; + } + + bool IsTrusted() const + { + return mEvent->IsTrusted(); + } + + bool IsSynthesized() const + { + return mEvent->mFlags.mIsSynthesizedForTests; + } + + double TimeStamp() const; + + EventTarget* GetOriginalTarget() const; + EventTarget* GetExplicitOriginalTarget() const; + EventTarget* GetComposedTarget() const; + + bool GetPreventDefault() const; + + /** + * @param aCalledByDefaultHandler Should be true when this is called by + * C++ or Chrome. Otherwise, e.g., called + * by a call of Event.preventDefault() in + * content script, false. + */ + void PreventDefaultInternal(bool aCalledByDefaultHandler); + + bool IsMainThreadEvent() + { + return mIsMainThreadEvent; + } + + /** + * For a given current target, returns the related target adjusted with + * shadow DOM retargeting rules. Returns nullptr if related target + * is not adjusted. + */ + static nsIContent* GetShadowRelatedTarget(nsIContent* aCurrentTarget, + nsIContent* aRelatedTarget); + + void MarkUninitialized() + { + mEvent->mMessage = eVoidEvent; + mEvent->mSpecifiedEventTypeString.Truncate(); + mEvent->mSpecifiedEventType = nullptr; + } + +protected: + + // Internal helper functions + void SetEventType(const nsAString& aEventTypeArg); + already_AddRefed<nsIContent> GetTargetFromFrame(); + + friend class EventMessageAutoOverride; + friend class WantsPopupControlCheck; + void SetWantsPopupControlCheck(bool aCheck) + { + mWantsPopupControlCheck = aCheck; + } + + bool GetWantsPopupControlCheck() + { + return IsTrusted() && mWantsPopupControlCheck; + } + + /** + * IsChrome() returns true if aCx is chrome context or the event is created + * in chrome's thread. Otherwise, false. + */ + bool IsChrome(JSContext* aCx) const; + + void SetComposed(bool aComposed) + { + mEvent->SetComposed(aComposed); + } + + mozilla::WidgetEvent* mEvent; + RefPtr<nsPresContext> mPresContext; + nsCOMPtr<EventTarget> mExplicitOriginalTarget; + nsCOMPtr<nsIGlobalObject> mOwner; + bool mEventIsInternal; + bool mPrivateDataDuplicated; + bool mIsMainThreadEvent; + // True when popup control check should rely on event.type, not + // WidgetEvent.mMessage. + bool mWantsPopupControlCheck; +}; + +/** + * RAII helper-class to override an event's message (i.e. its DOM-exposed + * type), for as long as the object is alive. Restores the original + * EventMessage when destructed. + * + * Notable requirements: + * - The original & overriding messages must be known (not eUnidentifiedEvent). + * - The original & overriding messages must be different. + * - The passed-in nsIDOMEvent must outlive this RAII helper. + */ +class MOZ_RAII EventMessageAutoOverride +{ +public: + explicit EventMessageAutoOverride(nsIDOMEvent* aEvent, + EventMessage aOverridingMessage) + : mEvent(aEvent->InternalDOMEvent()), + mOrigMessage(mEvent->mEvent->mMessage) + { + MOZ_ASSERT(aOverridingMessage != mOrigMessage, + "Don't use this class if you're not actually overriding"); + MOZ_ASSERT(aOverridingMessage != eUnidentifiedEvent, + "Only use this class with a valid overriding EventMessage"); + MOZ_ASSERT(mOrigMessage != eUnidentifiedEvent && + mEvent->mEvent->mSpecifiedEventTypeString.IsEmpty(), + "Only use this class on events whose overridden type is " + "known (so we can restore it properly)"); + + mEvent->mEvent->mMessage = aOverridingMessage; + } + + ~EventMessageAutoOverride() + { + mEvent->mEvent->mMessage = mOrigMessage; + } + +protected: + // Non-owning ref, which should be safe since we're a stack-allocated object + // with limited lifetime. Whoever creates us should keep mEvent alive. + Event* const MOZ_NON_OWNING_REF mEvent; + const EventMessage mOrigMessage; +}; + +class MOZ_STACK_CLASS WantsPopupControlCheck +{ +public: + explicit WantsPopupControlCheck(nsIDOMEvent* aEvent) : + mEvent(aEvent->InternalDOMEvent()) + { + mOriginalWantsPopupControlCheck = mEvent->GetWantsPopupControlCheck(); + mEvent->SetWantsPopupControlCheck(mEvent->IsTrusted()); + } + + ~WantsPopupControlCheck() + { + mEvent->SetWantsPopupControlCheck(mOriginalWantsPopupControlCheck); + } + +private: + Event* mEvent; + bool mOriginalWantsPopupControlCheck; +}; + +} // namespace dom +} // namespace mozilla + +#define NS_FORWARD_TO_EVENT \ + NS_FORWARD_NSIDOMEVENT(Event::) \ + virtual void PreventDefault(JSContext* aCx) override { Event::PreventDefault(aCx); } + +#define NS_FORWARD_NSIDOMEVENT_NO_SERIALIZATION_NO_DUPLICATION(_to) \ + NS_IMETHOD GetType(nsAString& aType) override { return _to GetType(aType); } \ + NS_IMETHOD GetTarget(nsIDOMEventTarget** aTarget) override { return _to GetTarget(aTarget); } \ + NS_IMETHOD GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget) override { return _to GetCurrentTarget(aCurrentTarget); } \ + NS_IMETHOD GetEventPhase(uint16_t* aEventPhase) override { return _to GetEventPhase(aEventPhase); } \ + NS_IMETHOD GetBubbles(bool* aBubbles) override { return _to GetBubbles(aBubbles); } \ + NS_IMETHOD GetCancelable(bool* aCancelable) override { return _to GetCancelable(aCancelable); } \ + NS_IMETHOD GetTimeStamp(DOMTimeStamp* aTimeStamp) override { return _to GetTimeStamp(aTimeStamp); } \ + NS_IMETHOD StopPropagation(void) override { return _to StopPropagation(); } \ + NS_IMETHOD StopCrossProcessForwarding(void) override { return _to StopCrossProcessForwarding(); } \ + NS_IMETHOD PreventDefault(void) override { return _to PreventDefault(); } \ + void InitEvent(const nsAString& eventTypeArg, bool canBubbleArg, bool cancelableArg) override { _to InitEvent(eventTypeArg, canBubbleArg, cancelableArg); } \ + NS_IMETHOD GetDefaultPrevented(bool* aDefaultPrevented) override { return _to GetDefaultPrevented(aDefaultPrevented); } \ + NS_IMETHOD StopImmediatePropagation(void) override { return _to StopImmediatePropagation(); } \ + NS_IMETHOD GetOriginalTarget(nsIDOMEventTarget** aOriginalTarget) override { return _to GetOriginalTarget(aOriginalTarget); } \ + NS_IMETHOD GetExplicitOriginalTarget(nsIDOMEventTarget** aExplicitOriginalTarget) override { return _to GetExplicitOriginalTarget(aExplicitOriginalTarget); } \ + NS_IMETHOD GetPreventDefault(bool* aRetval) override { return _to GetPreventDefault(aRetval); } \ + NS_IMETHOD GetIsTrusted(bool* aIsTrusted) override { return _to GetIsTrusted(aIsTrusted); } \ + NS_IMETHOD SetTarget(nsIDOMEventTarget* aTarget) override { return _to SetTarget(aTarget); } \ + NS_IMETHOD_(bool) IsDispatchStopped(void) override { return _to IsDispatchStopped(); } \ + NS_IMETHOD_(WidgetEvent*) WidgetEventPtr(void) override { return _to WidgetEventPtr(); } \ + NS_IMETHOD_(void) SetTrusted(bool aTrusted) override { _to SetTrusted(aTrusted); } \ + NS_IMETHOD_(void) SetOwner(EventTarget* aOwner) override { _to SetOwner(aOwner); } \ + NS_IMETHOD_(Event*) InternalDOMEvent() override { return _to InternalDOMEvent(); } \ + NS_IMETHOD GetCancelBubble(bool* aCancelBubble) override { return _to GetCancelBubble(aCancelBubble); } \ + NS_IMETHOD SetCancelBubble(bool aCancelBubble) override { return _to SetCancelBubble(aCancelBubble); } + +#define NS_FORWARD_TO_EVENT_NO_SERIALIZATION_NO_DUPLICATION \ + NS_FORWARD_NSIDOMEVENT_NO_SERIALIZATION_NO_DUPLICATION(Event::) \ + virtual void PreventDefault(JSContext* aCx) override { Event::PreventDefault(aCx); } + +inline nsISupports* +ToSupports(mozilla::dom::Event* e) +{ + return static_cast<nsIDOMEvent*>(e); +} + +inline nsISupports* +ToCanonicalSupports(mozilla::dom::Event* e) +{ + return static_cast<nsIDOMEvent*>(e); +} + +already_AddRefed<mozilla::dom::Event> +NS_NewDOMEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent); + +#endif // mozilla_dom_Event_h_ diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp new file mode 100644 index 000000000..a1d0675ae --- /dev/null +++ b/dom/events/EventDispatcher.cpp @@ -0,0 +1,1068 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include <new> +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsINode.h" +#include "nsPIDOMWindow.h" +#include "AnimationEvent.h" +#include "BeforeAfterKeyboardEvent.h" +#include "BeforeUnloadEvent.h" +#include "ClipboardEvent.h" +#include "CommandEvent.h" +#include "CompositionEvent.h" +#include "DataContainerEvent.h" +#include "DeviceMotionEvent.h" +#include "DragEvent.h" +#include "GeckoProfiler.h" +#include "KeyboardEvent.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/dom/CloseEvent.h" +#include "mozilla/dom/CustomEvent.h" +#include "mozilla/dom/DeviceOrientationEvent.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/FocusEvent.h" +#include "mozilla/dom/HashChangeEvent.h" +#include "mozilla/dom/InputEvent.h" +#include "mozilla/dom/MessageEvent.h" +#include "mozilla/dom/MouseScrollEvent.h" +#include "mozilla/dom/MutationEvent.h" +#include "mozilla/dom/NotifyPaintEvent.h" +#include "mozilla/dom/PageTransitionEvent.h" +#include "mozilla/dom/PointerEvent.h" +#include "mozilla/dom/PopStateEvent.h" +#include "mozilla/dom/ScrollAreaEvent.h" +#include "mozilla/dom/SimpleGestureEvent.h" +#include "mozilla/dom/StorageEvent.h" +#include "mozilla/dom/SVGZoomEvent.h" +#include "mozilla/dom/TimeEvent.h" +#include "mozilla/dom/TouchEvent.h" +#include "mozilla/dom/TransitionEvent.h" +#include "mozilla/dom/WheelEvent.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/XULCommandEvent.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/Unused.h" + +#ifdef MOZ_TASK_TRACER +#include "GeckoTaskTracer.h" +#include "mozilla/dom/Element.h" +using namespace mozilla::tasktracer; +#endif + +namespace mozilla { + +using namespace dom; + +class ELMCreationDetector +{ +public: + ELMCreationDetector() + // We can do this optimization only in the main thread. + : mNonMainThread(!NS_IsMainThread()) + , mInitialCount(mNonMainThread ? + 0 : EventListenerManager::sMainThreadCreatedCount) + { + } + + bool MayHaveNewListenerManager() + { + return mNonMainThread || + mInitialCount != EventListenerManager::sMainThreadCreatedCount; + } + + bool IsMainThread() + { + return !mNonMainThread; + } + +private: + bool mNonMainThread; + uint32_t mInitialCount; +}; + +static bool IsEventTargetChrome(EventTarget* aEventTarget, + nsIDocument** aDocument = nullptr) +{ + if (aDocument) { + *aDocument = nullptr; + } + + if (NS_WARN_IF(!aEventTarget)) { + return false; + } + + nsCOMPtr<nsIDocument> doc = do_QueryInterface(aEventTarget); + if (!doc) { + nsCOMPtr<nsINode> node = do_QueryInterface(aEventTarget); + if (node) { + doc = node->OwnerDoc(); + } else { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aEventTarget); + if (!window) { + return false; + } + doc = window->GetExtantDoc(); + } + if (!doc) { + return false; + } + } + bool isChrome = nsContentUtils::IsChromeDoc(doc); + if (aDocument) { + doc.swap(*aDocument); + } + 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 +{ +private: + explicit EventTargetChainItem(EventTarget* aTarget); +public: + EventTargetChainItem() + : mFlags(0) + , mItemFlags(0) + { + } + + static EventTargetChainItem* Create(nsTArray<EventTargetChainItem>& aChain, + EventTarget* aTarget, + EventTargetChainItem* aChild = nullptr) + { + MOZ_ASSERT(!aChild || &aChain.ElementAt(aChain.Length() - 1) == aChild); + return new (aChain.AppendElement()) EventTargetChainItem(aTarget); + } + + static void DestroyLast(nsTArray<EventTargetChainItem>& aChain, + EventTargetChainItem* aItem) + { + uint32_t lastIndex = aChain.Length() - 1; + MOZ_ASSERT(&aChain[lastIndex] == aItem); + aChain.RemoveElementAt(lastIndex); + } + + bool IsValid() + { + NS_WARNING_ASSERTION(!!(mTarget), "Event target is not valid!"); + return !!(mTarget); + } + + EventTarget* GetNewTarget() + { + return mNewTarget; + } + + void SetNewTarget(EventTarget* aNewTarget) + { + mNewTarget = aNewTarget; + } + + void SetForceContentDispatch(bool aForce) + { + if (aForce) { + mFlags |= NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH; + } else { + mFlags &= ~NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH; + } + } + + bool ForceContentDispatch() + { + return !!(mFlags & NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH); + } + + void SetWantsWillHandleEvent(bool aWants) + { + if (aWants) { + mFlags |= NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT; + } else { + mFlags &= ~NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT; + } + } + + bool WantsWillHandleEvent() + { + return !!(mFlags & NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT); + } + + void SetMayHaveListenerManager(bool aMayHave) + { + if (aMayHave) { + mFlags |= NS_TARGET_CHAIN_MAY_HAVE_MANAGER; + } else { + mFlags &= ~NS_TARGET_CHAIN_MAY_HAVE_MANAGER; + } + } + + bool MayHaveListenerManager() + { + return !!(mFlags & NS_TARGET_CHAIN_MAY_HAVE_MANAGER); + } + + EventTarget* CurrentTarget() + { + return mTarget; + } + + /** + * Dispatches event through the event target chain. + * Handles capture, target and bubble phases both in default + * and system event group and calls also PostHandleEvent for each + * item in the chain. + */ + static void HandleEventTargetChain(nsTArray<EventTargetChainItem>& aChain, + EventChainPostVisitor& aVisitor, + EventDispatchingCallback* aCallback, + ELMCreationDetector& aCd); + + /** + * Resets aVisitor object and calls PreHandleEvent. + * Copies mItemFlags and mItemData to the current EventTargetChainItem. + */ + void PreHandleEvent(EventChainPreVisitor& aVisitor); + + /** + * If the current item in the event target chain has an event listener + * manager, this method calls EventListenerManager::HandleEvent(). + */ + void HandleEvent(EventChainPostVisitor& aVisitor, + ELMCreationDetector& aCd) + { + if (WantsWillHandleEvent()) { + mTarget->WillHandleEvent(aVisitor); + } + if (aVisitor.mEvent->PropagationStopped()) { + return; + } + if (aVisitor.mEvent->mFlags.mOnlySystemGroupDispatchInContent && + !aVisitor.mEvent->mFlags.mInSystemGroup && + !IsCurrentTargetChrome()) { + return; + } + if (!mManager) { + if (!MayHaveListenerManager() && !aCd.MayHaveNewListenerManager()) { + return; + } + mManager = mTarget->GetExistingListenerManager(); + } + if (mManager) { + NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, + "CurrentTarget should be null!"); + mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent, + &aVisitor.mDOMEvent, + CurrentTarget(), + &aVisitor.mEventStatus); + NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, + "CurrentTarget should be null!"); + } + } + + /** + * Copies mItemFlags and mItemData to aVisitor and calls PostHandleEvent. + */ + void PostHandleEvent(EventChainPostVisitor& aVisitor); + +private: + nsCOMPtr<EventTarget> mTarget; + uint16_t mFlags; + uint16_t mItemFlags; + nsCOMPtr<nsISupports> mItemData; + // Event retargeting must happen whenever mNewTarget is non-null. + nsCOMPtr<EventTarget> mNewTarget; + // Cache mTarget's event listener manager. + RefPtr<EventListenerManager> mManager; + + bool IsCurrentTargetChrome() + { + if (!(mFlags & NS_TARGET_CHAIN_CHECKED_IF_CHROME)) { + mFlags |= NS_TARGET_CHAIN_CHECKED_IF_CHROME; + if (IsEventTargetChrome(mTarget)) { + mFlags |= NS_TARGET_CHAIN_IS_CHROME_CONTENT; + } + } + return !!(mFlags & NS_TARGET_CHAIN_IS_CHROME_CONTENT); + } +}; + +EventTargetChainItem::EventTargetChainItem(EventTarget* aTarget) + : mTarget(aTarget) + , mFlags(0) + , mItemFlags(0) +{ + MOZ_ASSERT(!aTarget || mTarget == aTarget->GetTargetForEventTargetChain()); +} + +void +EventTargetChainItem::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + aVisitor.Reset(); + Unused << mTarget->PreHandleEvent(aVisitor); + SetForceContentDispatch(aVisitor.mForceContentDispatch); + SetWantsWillHandleEvent(aVisitor.mWantsWillHandleEvent); + SetMayHaveListenerManager(aVisitor.mMayHaveListenerManager); + mItemFlags = aVisitor.mItemFlags; + mItemData = aVisitor.mItemData; +} + +void +EventTargetChainItem::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + aVisitor.mItemFlags = mItemFlags; + aVisitor.mItemData = mItemData; + mTarget->PostHandleEvent(aVisitor); +} + +void +EventTargetChainItem::HandleEventTargetChain( + nsTArray<EventTargetChainItem>& aChain, + EventChainPostVisitor& aVisitor, + EventDispatchingCallback* aCallback, + ELMCreationDetector& aCd) +{ + // Save the target so that it can be restored later. + nsCOMPtr<EventTarget> firstTarget = aVisitor.mEvent->mTarget; + uint32_t chainLength = aChain.Length(); + + // Capture + aVisitor.mEvent->mFlags.mInCapturePhase = true; + aVisitor.mEvent->mFlags.mInBubblingPhase = false; + for (uint32_t i = chainLength - 1; i > 0; --i) { + EventTargetChainItem& item = aChain[i]; + if ((!aVisitor.mEvent->mFlags.mNoContentDispatch || + item.ForceContentDispatch()) && + !aVisitor.mEvent->PropagationStopped()) { + item.HandleEvent(aVisitor, aCd); + } + + if (item.GetNewTarget()) { + // item is at anonymous boundary. Need to retarget for the child items. + for (uint32_t j = i; j > 0; --j) { + uint32_t childIndex = j - 1; + EventTarget* newTarget = aChain[childIndex].GetNewTarget(); + if (newTarget) { + aVisitor.mEvent->mTarget = newTarget; + break; + } + } + } + } + + // Target + aVisitor.mEvent->mFlags.mInBubblingPhase = true; + EventTargetChainItem& targetItem = aChain[0]; + if (!aVisitor.mEvent->PropagationStopped() && + (!aVisitor.mEvent->mFlags.mNoContentDispatch || + targetItem.ForceContentDispatch())) { + targetItem.HandleEvent(aVisitor, aCd); + } + if (aVisitor.mEvent->mFlags.mInSystemGroup) { + targetItem.PostHandleEvent(aVisitor); + } + + // Bubble + aVisitor.mEvent->mFlags.mInCapturePhase = false; + for (uint32_t i = 1; i < chainLength; ++i) { + EventTargetChainItem& item = aChain[i]; + EventTarget* newTarget = item.GetNewTarget(); + if (newTarget) { + // Item is at anonymous boundary. Need to retarget for the current item + // and for parent items. + aVisitor.mEvent->mTarget = newTarget; + } + + if (aVisitor.mEvent->mFlags.mBubbles || newTarget) { + if ((!aVisitor.mEvent->mFlags.mNoContentDispatch || + item.ForceContentDispatch()) && + !aVisitor.mEvent->PropagationStopped()) { + item.HandleEvent(aVisitor, aCd); + } + if (aVisitor.mEvent->mFlags.mInSystemGroup) { + item.PostHandleEvent(aVisitor); + } + } + } + aVisitor.mEvent->mFlags.mInBubblingPhase = false; + + if (!aVisitor.mEvent->mFlags.mInSystemGroup) { + // Dispatch to the system event group. Make sure to clear the + // STOP_DISPATCH flag since this resets for each event group. + aVisitor.mEvent->mFlags.mPropagationStopped = false; + aVisitor.mEvent->mFlags.mImmediatePropagationStopped = false; + + // Setting back the original target of the event. + aVisitor.mEvent->mTarget = aVisitor.mEvent->mOriginalTarget; + + // Special handling if PresShell (or some other caller) + // used a callback object. + if (aCallback) { + aCallback->HandleEvent(aVisitor); + } + + // Retarget for system event group (which does the default handling too). + // Setting back the target which was used also for default event group. + aVisitor.mEvent->mTarget = firstTarget; + aVisitor.mEvent->mFlags.mInSystemGroup = true; + HandleEventTargetChain(aChain, + aVisitor, + aCallback, + aCd); + aVisitor.mEvent->mFlags.mInSystemGroup = false; + + // After dispatch, clear all the propagation flags so that + // system group listeners don't affect to the event. + aVisitor.mEvent->mFlags.mPropagationStopped = false; + aVisitor.mEvent->mFlags.mImmediatePropagationStopped = false; + } +} + +static nsTArray<EventTargetChainItem>* sCachedMainThreadChain = nullptr; + +/* static */ void +EventDispatcher::Shutdown() +{ + delete sCachedMainThreadChain; + sCachedMainThreadChain = nullptr; +} + +EventTargetChainItem* +EventTargetChainItemForChromeTarget(nsTArray<EventTargetChainItem>& aChain, + nsINode* aNode, + EventTargetChainItem* aChild = nullptr) +{ + if (!aNode->IsInComposedDoc()) { + return nullptr; + } + nsPIDOMWindowInner* win = aNode->OwnerDoc()->GetInnerWindow(); + EventTarget* piTarget = win ? win->GetParentTarget() : nullptr; + NS_ENSURE_TRUE(piTarget, nullptr); + + EventTargetChainItem* etci = + EventTargetChainItem::Create(aChain, + piTarget->GetTargetForEventTargetChain(), + aChild); + if (!etci->IsValid()) { + EventTargetChainItem::DestroyLast(aChain, etci); + return nullptr; + } + return etci; +} + +/* static */ nsresult +EventDispatcher::Dispatch(nsISupports* aTarget, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsEventStatus* aEventStatus, + EventDispatchingCallback* aCallback, + nsTArray<EventTarget*>* aTargets) +{ + PROFILER_LABEL("EventDispatcher", "Dispatch", + js::ProfileEntry::Category::EVENTS); + + NS_ASSERTION(aEvent, "Trying to dispatch without WidgetEvent!"); + NS_ENSURE_TRUE(!aEvent->mFlags.mIsBeingDispatched, + NS_ERROR_DOM_INVALID_STATE_ERR); + NS_ASSERTION(!aTargets || !aEvent->mMessage, "Wrong parameters!"); + + // If we're dispatching an already created DOMEvent object, make + // sure it is initialized! + // If aTargets is non-null, the event isn't going to be dispatched. + NS_ENSURE_TRUE(aEvent->mMessage || !aDOMEvent || aTargets, + NS_ERROR_DOM_INVALID_STATE_ERR); + +#ifdef MOZ_TASK_TRACER + { + if (aDOMEvent) { + nsAutoString eventType; + aDOMEvent->GetType(eventType); + + nsCOMPtr<Element> element = do_QueryInterface(aTarget); + nsAutoString elementId; + nsAutoString elementTagName; + if (element) { + element->GetId(elementId); + element->GetTagName(elementTagName); + } + AddLabel("Event [%s] dispatched at target [id:%s tag:%s]", + NS_ConvertUTF16toUTF8(eventType).get(), + NS_ConvertUTF16toUTF8(elementId).get(), + NS_ConvertUTF16toUTF8(elementTagName).get()); + } + } +#endif + + nsCOMPtr<EventTarget> target = do_QueryInterface(aTarget); + + bool retargeted = false; + + if (aEvent->mFlags.mRetargetToNonNativeAnonymous) { + nsCOMPtr<nsIContent> content = do_QueryInterface(target); + if (content && content->IsInNativeAnonymousSubtree()) { + nsCOMPtr<EventTarget> newTarget = + do_QueryInterface(content->FindFirstNonChromeOnlyAccessContent()); + NS_ENSURE_STATE(newTarget); + + aEvent->mOriginalTarget = target; + target = newTarget; + retargeted = true; + } + } + + if (aEvent->mFlags.mOnlyChromeDispatch) { + nsCOMPtr<nsIDocument> doc; + if (!IsEventTargetChrome(target, getter_AddRefs(doc)) && doc) { + nsPIDOMWindowInner* win = doc->GetInnerWindow(); + // If we can't dispatch the event to chrome, do nothing. + EventTarget* piTarget = win ? win->GetParentTarget() : nullptr; + if (!piTarget) { + return NS_OK; + } + + // Set the target to be the original dispatch target, + aEvent->mTarget = target; + // but use chrome event handler or TabChildGlobal for event target chain. + target = piTarget; + } else if (NS_WARN_IF(!doc)) { + return NS_ERROR_UNEXPECTED; + } + } + +#ifdef DEBUG + if (aEvent->mMessage != eVoidEvent && + !nsContentUtils::IsSafeToRunScript()) { + nsresult rv = NS_ERROR_FAILURE; + if (target->GetContextForEventHandlers(&rv) || + NS_FAILED(rv)) { + nsCOMPtr<nsINode> node = do_QueryInterface(target); + if (node && nsContentUtils::IsChromeDoc(node->OwnerDoc())) { + NS_WARNING("Fix the caller!"); + } else { + NS_ERROR("This is unsafe! Fix the caller!"); + } + } + } + + if (aDOMEvent) { + WidgetEvent* innerEvent = aDOMEvent->WidgetEventPtr(); + NS_ASSERTION(innerEvent == aEvent, + "The inner event of aDOMEvent is not the same as aEvent!"); + } +#endif + + nsresult rv = NS_OK; + bool externalDOMEvent = !!(aDOMEvent); + + // If we have a PresContext, make sure it doesn't die before + // event dispatching is finished. + RefPtr<nsPresContext> kungFuDeathGrip(aPresContext); + + ELMCreationDetector cd; + nsTArray<EventTargetChainItem> chain; + if (cd.IsMainThread()) { + if (!sCachedMainThreadChain) { + sCachedMainThreadChain = new nsTArray<EventTargetChainItem>(); + } + chain.SwapElements(*sCachedMainThreadChain); + chain.SetCapacity(128); + } + + // 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; + } + + // Make sure that nsIDOMEvent::target and nsIDOMEvent::originalTarget + // point to the last item in the chain. + if (!aEvent->mTarget) { + // Note, CurrentTarget() points always to the object returned by + // GetTargetForEventTargetChain(). + aEvent->mTarget = targetEtci->CurrentTarget(); + } else { + // XXX But if the target is already set, use that. This is a hack + // for the 'load', 'beforeunload' and 'unload' events, + // which are dispatched to |window| but have document as their target. + // + // Make sure that the event target points to the right object. + aEvent->mTarget = aEvent->mTarget->GetTargetForEventTargetChain(); + NS_ENSURE_STATE(aEvent->mTarget); + } + + if (retargeted) { + aEvent->mOriginalTarget = + aEvent->mOriginalTarget->GetTargetForEventTargetChain(); + NS_ENSURE_STATE(aEvent->mOriginalTarget); + } + else { + aEvent->mOriginalTarget = aEvent->mTarget; + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mOriginalTarget); + bool isInAnon = (content && (content->IsInAnonymousSubtree() || + content->IsInShadowTree())); + + aEvent->mFlags.mIsBeingDispatched = true; + + // Create visitor object and start event dispatching. + // PreHandleEvent 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) { + // 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; + EventTargetChainItem* parentEtci = + EventTargetChainItem::Create(chain, preVisitor.mParentTarget, topEtci); + if (!parentEtci->IsValid()) { + EventTargetChainItem::DestroyLast(chain, parentEtci); + rv = NS_ERROR_FAILURE; + break; + } + + // Item needs event retargetting. + if (preVisitor.mEventTargetAtParent) { + // Need to set the target of the event + // so that also the next retargeting works. + preVisitor.mEvent->mTarget = preVisitor.mEventTargetAtParent; + parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent); + } + + parentEtci->PreHandleEvent(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; + } + } + } + } + break; + } + } + if (NS_SUCCEEDED(rv)) { + if (aTargets) { + aTargets->Clear(); + uint32_t numTargets = chain.Length(); + EventTarget** targets = aTargets->AppendElements(numTargets); + for (uint32_t i = 0; i < numTargets; ++i) { + targets[i] = chain[i].CurrentTarget()->GetTargetForDOMEvent(); + } + } else { + // Event target chain is created. Handle the chain. + EventChainPostVisitor postVisitor(preVisitor); + EventTargetChainItem::HandleEventTargetChain(chain, postVisitor, + aCallback, cd); + + preVisitor.mEventStatus = postVisitor.mEventStatus; + // If the DOM event was created during event flow. + if (!preVisitor.mDOMEvent && postVisitor.mDOMEvent) { + preVisitor.mDOMEvent = postVisitor.mDOMEvent; + } + } + } + } + + // Note, EventTargetChainItem objects are deleted when the chain goes out of + // the scope. + + aEvent->mFlags.mIsBeingDispatched = false; + aEvent->mFlags.mDispatchedAtLeastOnce = true; + + if (!externalDOMEvent && preVisitor.mDOMEvent) { + // An dom::Event was created while dispatching the event. + // Duplicate private data if someone holds a pointer to it. + nsrefcnt rc = 0; + NS_RELEASE2(preVisitor.mDOMEvent, rc); + if (preVisitor.mDOMEvent) { + preVisitor.mDOMEvent->DuplicatePrivateData(); + } + } + + if (aEventStatus) { + *aEventStatus = preVisitor.mEventStatus; + } + + if (cd.IsMainThread() && chain.Capacity() == 128 && sCachedMainThreadChain) { + chain.ClearAndRetainStorage(); + chain.SwapElements(*sCachedMainThreadChain); + } + + return rv; +} + +/* static */ nsresult +EventDispatcher::DispatchDOMEvent(nsISupports* aTarget, + WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsPresContext* aPresContext, + nsEventStatus* aEventStatus) +{ + if (aDOMEvent) { + WidgetEvent* innerEvent = aDOMEvent->WidgetEventPtr(); + NS_ENSURE_TRUE(innerEvent, NS_ERROR_ILLEGAL_VALUE); + + bool dontResetTrusted = false; + if (innerEvent->mFlags.mDispatchedAtLeastOnce) { + innerEvent->mTarget = nullptr; + innerEvent->mOriginalTarget = nullptr; + } else { + aDOMEvent->GetIsTrusted(&dontResetTrusted); + } + + if (!dontResetTrusted) { + //Check security state to determine if dispatcher is trusted + bool trusted = NS_IsMainThread() ? nsContentUtils::LegacyIsCallerChromeOrNativeCode() + : mozilla::dom::workers::IsCurrentThreadRunningChromeWorker(); + aDOMEvent->SetTrusted(trusted); + } + + return EventDispatcher::Dispatch(aTarget, aPresContext, innerEvent, + aDOMEvent, aEventStatus); + } else if (aEvent) { + return EventDispatcher::Dispatch(aTarget, aPresContext, aEvent, + aDOMEvent, aEventStatus); + } + return NS_ERROR_ILLEGAL_VALUE; +} + +/* static */ already_AddRefed<dom::Event> +EventDispatcher::CreateEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + const nsAString& aEventType) +{ + if (aEvent) { + switch(aEvent->mClass) { + case eMutationEventClass: + return NS_NewDOMMutationEvent(aOwner, aPresContext, + aEvent->AsMutationEvent()); + case eGUIEventClass: + case eScrollPortEventClass: + case eUIEventClass: + return NS_NewDOMUIEvent(aOwner, aPresContext, aEvent->AsGUIEvent()); + case eScrollAreaEventClass: + return NS_NewDOMScrollAreaEvent(aOwner, aPresContext, + aEvent->AsScrollAreaEvent()); + case eKeyboardEventClass: + return NS_NewDOMKeyboardEvent(aOwner, aPresContext, + aEvent->AsKeyboardEvent()); + case eBeforeAfterKeyboardEventClass: + return NS_NewDOMBeforeAfterKeyboardEvent(aOwner, aPresContext, + aEvent->AsBeforeAfterKeyboardEvent()); + case eCompositionEventClass: + return NS_NewDOMCompositionEvent(aOwner, aPresContext, + aEvent->AsCompositionEvent()); + case eMouseEventClass: + return NS_NewDOMMouseEvent(aOwner, aPresContext, aEvent->AsMouseEvent()); + case eFocusEventClass: + return NS_NewDOMFocusEvent(aOwner, aPresContext, aEvent->AsFocusEvent()); + case eMouseScrollEventClass: + return NS_NewDOMMouseScrollEvent(aOwner, aPresContext, + aEvent->AsMouseScrollEvent()); + case eWheelEventClass: + return NS_NewDOMWheelEvent(aOwner, aPresContext, aEvent->AsWheelEvent()); + case eEditorInputEventClass: + return NS_NewDOMInputEvent(aOwner, aPresContext, + aEvent->AsEditorInputEvent()); + case eDragEventClass: + return NS_NewDOMDragEvent(aOwner, aPresContext, aEvent->AsDragEvent()); + case eClipboardEventClass: + return NS_NewDOMClipboardEvent(aOwner, aPresContext, + aEvent->AsClipboardEvent()); + case eSVGZoomEventClass: + return NS_NewDOMSVGZoomEvent(aOwner, aPresContext, + aEvent->AsSVGZoomEvent()); + case eSMILTimeEventClass: + return NS_NewDOMTimeEvent(aOwner, aPresContext, + aEvent->AsSMILTimeEvent()); + case eCommandEventClass: + return NS_NewDOMCommandEvent(aOwner, aPresContext, + aEvent->AsCommandEvent()); + case eSimpleGestureEventClass: + return NS_NewDOMSimpleGestureEvent(aOwner, aPresContext, + aEvent->AsSimpleGestureEvent()); + case ePointerEventClass: + return NS_NewDOMPointerEvent(aOwner, aPresContext, + aEvent->AsPointerEvent()); + case eTouchEventClass: + return NS_NewDOMTouchEvent(aOwner, aPresContext, aEvent->AsTouchEvent()); + case eTransitionEventClass: + return NS_NewDOMTransitionEvent(aOwner, aPresContext, + aEvent->AsTransitionEvent()); + case eAnimationEventClass: + return NS_NewDOMAnimationEvent(aOwner, aPresContext, + aEvent->AsAnimationEvent()); + default: + // For all other types of events, create a vanilla event object. + return NS_NewDOMEvent(aOwner, aPresContext, aEvent); + } + } + + // And if we didn't get an event, check the type argument. + +#define LOG_EVENT_CREATION(name) mozilla::Telemetry::Accumulate( \ + mozilla::Telemetry::CREATE_EVENT_##name, true); + + if (aEventType.LowerCaseEqualsLiteral("mouseevent")) { + LOG_EVENT_CREATION(MOUSEEVENT); + return NS_NewDOMMouseEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("mouseevents")) { + LOG_EVENT_CREATION(MOUSEEVENTS); + return NS_NewDOMMouseEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("popupevents")) { + LOG_EVENT_CREATION(POPUPEVENTS); + return NS_NewDOMMouseEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("mousescrollevents")) { + LOG_EVENT_CREATION(MOUSESCROLLEVENTS); + return NS_NewDOMMouseScrollEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("dragevent")) { + LOG_EVENT_CREATION(DRAGEVENT); + return NS_NewDOMDragEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("dragevents")) { + LOG_EVENT_CREATION(DRAGEVENTS); + return NS_NewDOMDragEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("keyboardevent")) { + LOG_EVENT_CREATION(KEYBOARDEVENT); + return NS_NewDOMKeyboardEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("keyevents")) { + LOG_EVENT_CREATION(KEYEVENTS); + return NS_NewDOMKeyboardEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("compositionevent")) { + LOG_EVENT_CREATION(COMPOSITIONEVENT); + return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("textevent")) { + LOG_EVENT_CREATION(TEXTEVENT); + return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("textevents")) { + LOG_EVENT_CREATION(TEXTEVENTS); + return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("mutationevent")) { + LOG_EVENT_CREATION(MUTATIONEVENT); + return NS_NewDOMMutationEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("mutationevents")) { + LOG_EVENT_CREATION(MUTATIONEVENTS); + return NS_NewDOMMutationEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("deviceorientationevent")) { + LOG_EVENT_CREATION(DEVICEORIENTATIONEVENT); + DeviceOrientationEventInit init; + RefPtr<Event> event = + DeviceOrientationEvent::Constructor(aOwner, EmptyString(), init); + event->MarkUninitialized(); + return event.forget(); + } + if (aEventType.LowerCaseEqualsLiteral("devicemotionevent")) { + LOG_EVENT_CREATION(DEVICEMOTIONEVENT); + return NS_NewDOMDeviceMotionEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("uievent")) { + LOG_EVENT_CREATION(UIEVENT); + return NS_NewDOMUIEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("uievents")) { + LOG_EVENT_CREATION(UIEVENTS); + return NS_NewDOMUIEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("event")) { + LOG_EVENT_CREATION(EVENT); + return NS_NewDOMEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("events")) { + LOG_EVENT_CREATION(EVENTS); + return NS_NewDOMEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("htmlevents")) { + LOG_EVENT_CREATION(HTMLEVENTS); + return NS_NewDOMEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("svgevent")) { + LOG_EVENT_CREATION(SVGEVENT); + return NS_NewDOMEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("svgevents")) { + LOG_EVENT_CREATION(SVGEVENTS); + return NS_NewDOMEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("svgzoomevent")) { + LOG_EVENT_CREATION(SVGZOOMEVENT); + return NS_NewDOMSVGZoomEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("svgzoomevents")) { + LOG_EVENT_CREATION(SVGZOOMEVENTS); + return NS_NewDOMSVGZoomEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("timeevent")) { + LOG_EVENT_CREATION(TIMEEVENT); + return NS_NewDOMTimeEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("timeevents")) { + LOG_EVENT_CREATION(TIMEEVENTS); + return NS_NewDOMTimeEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("xulcommandevent")) { + LOG_EVENT_CREATION(XULCOMMANDEVENT); + return NS_NewDOMXULCommandEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("xulcommandevents")) { + LOG_EVENT_CREATION(XULCOMMANDEVENTS); + return NS_NewDOMXULCommandEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("commandevent")) { + LOG_EVENT_CREATION(COMMANDEVENT); + return NS_NewDOMCommandEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("commandevents")) { + LOG_EVENT_CREATION(COMMANDEVENTS); + return NS_NewDOMCommandEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("datacontainerevent")) { + LOG_EVENT_CREATION(DATACONTAINEREVENT); + return NS_NewDOMDataContainerEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("datacontainerevents")) { + LOG_EVENT_CREATION(DATACONTAINEREVENTS); + return NS_NewDOMDataContainerEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("messageevent")) { + LOG_EVENT_CREATION(MESSAGEEVENT); + RefPtr<Event> event = new MessageEvent(aOwner, aPresContext, nullptr); + return event.forget(); + } + if (aEventType.LowerCaseEqualsLiteral("notifypaintevent")) { + LOG_EVENT_CREATION(NOTIFYPAINTEVENT); + return NS_NewDOMNotifyPaintEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("simplegestureevent")) { + LOG_EVENT_CREATION(SIMPLEGESTUREEVENT); + return NS_NewDOMSimpleGestureEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("beforeunloadevent")) { + LOG_EVENT_CREATION(BEFOREUNLOADEVENT); + return NS_NewDOMBeforeUnloadEvent(aOwner, aPresContext, nullptr); + } + // XXXkhuey this is broken + if (aEventType.LowerCaseEqualsLiteral("pagetransition")) { + LOG_EVENT_CREATION(PAGETRANSITION); + PageTransitionEventInit init; + RefPtr<Event> event = + PageTransitionEvent::Constructor(aOwner, EmptyString(), init); + event->MarkUninitialized(); + return event.forget(); + } + if (aEventType.LowerCaseEqualsLiteral("scrollareaevent")) { + LOG_EVENT_CREATION(SCROLLAREAEVENT); + return NS_NewDOMScrollAreaEvent(aOwner, aPresContext, nullptr); + } + // XXXkhuey Chrome supports popstateevent here, even though it provides no + // initPopStateEvent method. This is nuts ... but copying it is unlikely to + // break the web. + if (aEventType.LowerCaseEqualsLiteral("popstateevent")) { + LOG_EVENT_CREATION(POPSTATEEVENT); + AutoJSContext cx; + RootedDictionary<PopStateEventInit> init(cx); + RefPtr<Event> event = + PopStateEvent::Constructor(aOwner, EmptyString(), init); + event->MarkUninitialized(); + return event.forget(); + } + if (aEventType.LowerCaseEqualsLiteral("touchevent") && + TouchEvent::PrefEnabled(nsContentUtils::GetDocShellForEventTarget(aOwner))) { + LOG_EVENT_CREATION(TOUCHEVENT); + return NS_NewDOMTouchEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("hashchangeevent")) { + LOG_EVENT_CREATION(HASHCHANGEEVENT); + HashChangeEventInit init; + RefPtr<Event> event = + HashChangeEvent::Constructor(aOwner, EmptyString(), init); + event->MarkUninitialized(); + return event.forget(); + } + if (aEventType.LowerCaseEqualsLiteral("customevent")) { + LOG_EVENT_CREATION(CUSTOMEVENT); + return NS_NewDOMCustomEvent(aOwner, aPresContext, nullptr); + } + if (aEventType.LowerCaseEqualsLiteral("storageevent")) { + LOG_EVENT_CREATION(STORAGEEVENT); + return NS_NewDOMStorageEvent(aOwner); + } + +#undef LOG_EVENT_CREATION + + // NEW EVENT TYPES SHOULD NOT BE ADDED HERE; THEY SHOULD USE ONLY EVENT + // CONSTRUCTORS + + return nullptr; +} + +} // namespace mozilla diff --git a/dom/events/EventDispatcher.h b/dom/events/EventDispatcher.h new file mode 100644 index 000000000..3c754033d --- /dev/null +++ b/dom/events/EventDispatcher.h @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef MOZILLA_INTERNAL_API +#ifndef mozilla_EventDispatcher_h_ +#define mozilla_EventDispatcher_h_ + +#include "mozilla/EventForwards.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +// Microsoft's API Name hackery sucks +#undef CreateEvent + +class nsIContent; +class nsIDOMEvent; +class nsPresContext; + +template<class E> class nsCOMArray; + +namespace mozilla { +namespace dom { +class Event; +class EventTarget; +} // namespace dom + +/** + * 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 + * 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. + * + * The capture, target and bubble phases of the event dispatch are handled + * by iterating through the event target chain. Iteration happens twice, + * first for the default event group and then for the system event group. + * While dispatching the event for the system event group PostHandleEvent + * is called right after calling event listener for the current event target. + */ + +class EventChainVisitor +{ +public: + EventChainVisitor(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsEventStatus aEventStatus = nsEventStatus_eIgnore) + : mPresContext(aPresContext) + , mEvent(aEvent) + , mDOMEvent(aDOMEvent) + , mEventStatus(aEventStatus) + , mItemFlags(0) + { + } + + /** + * The prescontext, possibly nullptr. + */ + nsPresContext* const mPresContext; + + /** + * The WidgetEvent which is being dispatched. Never nullptr. + */ + WidgetEvent* const mEvent; + + /** + * The DOM Event assiciated with the mEvent. Possibly nullptr if a DOM Event + * is not (yet) created. + */ + nsIDOMEvent* mDOMEvent; + + /** + * The status of the event. + * @see nsEventStatus.h + */ + nsEventStatus mEventStatus; + + /** + * Bits for items in the event target chain. + * Set in PreHandleEvent() 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 + * use these bits. + * + * @note Using uint16_t because that is used also in EventTargetChainItem. + */ + uint16_t mItemFlags; + + /** + * Data for items in the event target chain. + * Set in PreHandleEvent() 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 + * use this. + */ + nsCOMPtr<nsISupports> mItemData; +}; + +class EventChainPreVisitor : public EventChainVisitor +{ +public: + EventChainPreVisitor(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsEventStatus aEventStatus, + bool aIsInAnon) + : EventChainVisitor(aPresContext, aEvent, aDOMEvent, aEventStatus) + , mCanHandle(true) + , mAutomaticChromeDispatch(true) + , mForceContentDispatch(false) + , mRelatedTargetIsInAnon(false) + , mOriginalTargetIsInAnon(aIsInAnon) + , mWantsWillHandleEvent(false) + , mMayHaveListenerManager(true) + , mParentTarget(nullptr) + , mEventTargetAtParent(nullptr) + { + } + + void Reset() + { + mItemFlags = 0; + mItemData = nullptr; + mCanHandle = true; + mAutomaticChromeDispatch = true; + mForceContentDispatch = false; + mWantsWillHandleEvent = false; + mMayHaveListenerManager = true; + 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 + * construction of the event target chain is complete. The target that sets + * mCanHandle to false is NOT included in the event target chain. + */ + bool mCanHandle; + + /** + * If mCanHandle is false and mAutomaticChromeDispatch is also false + * event will not be dispatched to the chrome event handler. + */ + bool mAutomaticChromeDispatch; + + /** + * If mForceContentDispatch is set to true, + * content dispatching is not disabled for this event target. + * FIXME! This is here for backward compatibility. Bug 329119 + */ + bool mForceContentDispatch; + + /** + * true if it is known that related target is or is a descendant of an + * element which is anonymous for events. + */ + bool mRelatedTargetIsInAnon; + + /** + * true if the original target of the event is inside anonymous content. + * This is set before calling PreHandleEvent on event targets. + */ + bool mOriginalTargetIsInAnon; + + /** + * Whether or not nsIDOMEventTarget::WillHandleEvent will be + * called. Default is false; + */ + bool mWantsWillHandleEvent; + + /** + * If it is known that the current target doesn't have a listener manager + * when PreHandleEvent is called, set this to false. + */ + bool mMayHaveListenerManager; + + /** + * Parent item in the event target chain. + */ + dom::EventTarget* mParentTarget; + + /** + * 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 +{ +public: + explicit EventChainPostVisitor(EventChainVisitor& aOther) + : EventChainVisitor(aOther.mPresContext, aOther.mEvent, + aOther.mDOMEvent, aOther.mEventStatus) + { + } +}; + +/** + * If an EventDispatchingCallback object is passed to Dispatch, + * its HandleEvent method is called after handling the default event group, + * before handling the system event group. + * This is used in nsPresShell. + */ +class MOZ_STACK_CLASS EventDispatchingCallback +{ +public: + virtual void HandleEvent(EventChainPostVisitor& aVisitor) = 0; +}; + +/** + * The generic class for event dispatching. + * Must not be used outside Gecko! + */ +class EventDispatcher +{ +public: + /** + * aTarget should QI to EventTarget. + * If the target of aEvent is set before calling this method, the target of + * aEvent is used as the target (unless there is event + * retargeting) and the originalTarget of the DOM Event. + * aTarget is always used as the starting point for constructing the event + * target chain, no matter what the value of aEvent->mTarget is. + * In other words, aEvent->mTarget is only a property of the event and it has + * nothing to do with the construction of the event target chain. + * Neither aTarget nor aEvent is allowed to be nullptr. + * + * If aTargets is non-null, event target chain will be created, but + * event won't be handled. In this case aEvent->mMessage should be + * eVoidEvent. + * @note Use this method when dispatching a WidgetEvent. + */ + static nsresult Dispatch(nsISupports* aTarget, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent = nullptr, + nsEventStatus* aEventStatus = nullptr, + EventDispatchingCallback* aCallback = nullptr, + nsTArray<dom::EventTarget*>* aTargets = nullptr); + + /** + * Dispatches an event. + * If aDOMEvent is not nullptr, it is used for dispatching + * (aEvent can then be nullptr) and (if aDOMEvent is not |trusted| already), + * the |trusted| flag is set based on the UniversalXPConnect capability. + * Otherwise this works like EventDispatcher::Dispatch. + * @note Use this method when dispatching nsIDOMEvent. + */ + static nsresult DispatchDOMEvent(nsISupports* aTarget, + WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsPresContext* aPresContext, + nsEventStatus* aEventStatus); + + /** + * Creates a DOM Event. Returns null if the event type is unsupported. + */ + static already_AddRefed<dom::Event> CreateEvent(dom::EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + const nsAString& aEventType); + + /** + * Called at shutting down. + */ + static void Shutdown(); +}; + +} // namespace mozilla + +#endif // mozilla_EventDispatcher_h_ +#endif diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp new file mode 100644 index 000000000..c8db4f2a1 --- /dev/null +++ b/dom/events/EventListenerManager.cpp @@ -0,0 +1,1803 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Microsoft's API Name hackery sucks +#undef CreateEvent + +#include "mozilla/AddonPathService.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventListenerManager.h" +#ifdef MOZ_B2G +#include "mozilla/Hal.h" +#endif // #ifdef MOZ_B2G +#include "mozilla/HalSensor.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/JSEventHandler.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventTargetBinding.h" +#include "mozilla/dom/TouchEvent.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/EventTimelineMarker.h" + +#include "EventListenerService.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsDOMCID.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsHtml5Atoms.h" +#include "nsIContent.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIDocument.h" +#include "nsIDOMEventListener.h" +#include "nsIScriptGlobalObject.h" +#include "nsISupports.h" +#include "nsIXPConnect.h" +#include "nsJSUtils.h" +#include "nsNameSpaceManager.h" +#include "nsPIDOMWindow.h" +#include "nsSandboxFlags.h" +#include "xpcpublic.h" +#include "nsIFrame.h" +#include "nsDisplayList.h" + +namespace mozilla { + +using namespace dom; +using namespace hal; + +#define EVENT_TYPE_EQUALS(ls, message, userType, typeString, allEvents) \ + ((ls->mEventMessage == message && \ + (ls->mEventMessage != eUnidentifiedEvent || \ + (mIsMainThreadELM && ls->mTypeAtom == userType) || \ + (!mIsMainThreadELM && ls->mTypeString.Equals(typeString)))) || \ + (allEvents && ls->mAllEvents)) + +static const uint32_t kAllMutationBits = + NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED | + NS_EVENT_BITS_MUTATION_NODEINSERTED | + NS_EVENT_BITS_MUTATION_NODEREMOVED | + NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT | + NS_EVENT_BITS_MUTATION_NODEINSERTEDINTODOCUMENT | + NS_EVENT_BITS_MUTATION_ATTRMODIFIED | + NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED; + +static uint32_t +MutationBitForEventType(EventMessage aEventType) +{ + switch (aEventType) { + case eLegacySubtreeModified: + return NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED; + case eLegacyNodeInserted: + return NS_EVENT_BITS_MUTATION_NODEINSERTED; + case eLegacyNodeRemoved: + return NS_EVENT_BITS_MUTATION_NODEREMOVED; + case eLegacyNodeRemovedFromDocument: + return NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT; + case eLegacyNodeInsertedIntoDocument: + return NS_EVENT_BITS_MUTATION_NODEINSERTEDINTODOCUMENT; + case eLegacyAttrModified: + return NS_EVENT_BITS_MUTATION_ATTRMODIFIED; + case eLegacyCharacterDataModified: + return NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED; + default: + break; + } + return 0; +} + +uint32_t EventListenerManager::sMainThreadCreatedCount = 0; + +static bool +IsWebkitPrefixSupportEnabled() +{ + static bool sIsWebkitPrefixSupportEnabled; + static bool sIsPrefCached = false; + + if (!sIsPrefCached) { + sIsPrefCached = true; + Preferences::AddBoolVarCache(&sIsWebkitPrefixSupportEnabled, + "layout.css.prefixes.webkit"); + } + + return sIsWebkitPrefixSupportEnabled; +} + +static bool +IsPrefixedPointerLockEnabled() +{ + static bool sIsPrefixedPointerLockEnabled; + static bool sIsPrefCached = false; + if (!sIsPrefCached) { + sIsPrefCached = true; + Preferences::AddBoolVarCache(&sIsPrefixedPointerLockEnabled, + "pointer-lock-api.prefixed.enabled"); + } + return sIsPrefixedPointerLockEnabled; +} + +EventListenerManagerBase::EventListenerManagerBase() + : mNoListenerForEvent(eVoidEvent) + , mMayHavePaintEventListener(false) + , mMayHaveMutationListeners(false) + , mMayHaveCapturingListeners(false) + , mMayHaveSystemGroupListeners(false) + , mMayHaveTouchEventListener(false) + , mMayHaveMouseEnterLeaveEventListener(false) + , mMayHavePointerEnterLeaveEventListener(false) + , mMayHaveKeyEventListener(false) + , mMayHaveInputOrCompositionEventListener(false) + , mClearingListeners(false) + , mIsMainThreadELM(NS_IsMainThread()) +{ + static_assert(sizeof(EventListenerManagerBase) == sizeof(uint32_t), + "Keep the size of EventListenerManagerBase size compact!"); +} + +EventListenerManager::EventListenerManager(EventTarget* aTarget) + : EventListenerManagerBase() + , mTarget(aTarget) +{ + NS_ASSERTION(aTarget, "unexpected null pointer"); + + if (mIsMainThreadELM) { + ++sMainThreadCreatedCount; + } +} + +EventListenerManager::~EventListenerManager() +{ + // If your code fails this assertion, a possible reason is that + // a class did not call our Disconnect() manually. Note that + // this class can have Disconnect called in one of two ways: + // if it is part of a cycle, then in Unlink() (such a cycle + // would be with one of the listeners, not mTarget which is weak). + // If not part of a cycle, then Disconnect must be called manually, + // typically from the destructor of the owner class (mTarget). + // XXX azakai: Is there any reason to not just call Disconnect + // from right here, if not previously called? + NS_ASSERTION(!mTarget, "didn't call Disconnect"); + RemoveAllListeners(); +} + +void +EventListenerManager::RemoveAllListeners() +{ + if (mClearingListeners) { + return; + } + mClearingListeners = true; + mListeners.Clear(); + mClearingListeners = false; +} + +void +EventListenerManager::Shutdown() +{ + Event::Shutdown(); +} + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EventListenerManager, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EventListenerManager, Release) + +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + EventListenerManager::Listener& aField, + const char* aName, + unsigned aFlags) +{ + if (MOZ_UNLIKELY(aCallback.WantDebugInfo())) { + nsAutoCString name; + name.AppendASCII(aName); + if (aField.mTypeAtom) { + name.AppendASCII(" event="); + name.Append(nsAtomCString(aField.mTypeAtom)); + name.AppendASCII(" listenerType="); + name.AppendInt(aField.mListenerType); + name.AppendASCII(" "); + } + CycleCollectionNoteChild(aCallback, aField.mListener.GetISupports(), name.get(), + aFlags); + } else { + CycleCollectionNoteChild(aCallback, aField.mListener.GetISupports(), aName, + aFlags); + } +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerManager) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EventListenerManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EventListenerManager) + tmp->Disconnect(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + + +nsPIDOMWindowInner* +EventListenerManager::GetInnerWindowForTarget() +{ + nsCOMPtr<nsINode> node = do_QueryInterface(mTarget); + if (node) { + // XXX sXBL/XBL2 issue -- do we really want the owner here? What + // if that's the XBL document? + return node->OwnerDoc()->GetInnerWindow(); + } + + nsCOMPtr<nsPIDOMWindowInner> window = GetTargetAsInnerWindow(); + return window; +} + +already_AddRefed<nsPIDOMWindowInner> +EventListenerManager::GetTargetAsInnerWindow() const +{ + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mTarget); + return window.forget(); +} + +void +EventListenerManager::AddEventListenerInternal( + EventListenerHolder aListenerHolder, + EventMessage aEventMessage, + nsIAtom* aTypeAtom, + const nsAString& aTypeString, + const EventListenerFlags& aFlags, + bool aHandler, + bool aAllEvents) +{ + MOZ_ASSERT(// Main thread + (NS_IsMainThread() && aEventMessage && aTypeAtom) || + // non-main-thread + (!NS_IsMainThread() && aEventMessage) || + aAllEvents, "Missing type"); // all-events listener + + if (!aListenerHolder || mClearingListeners) { + return; + } + + // Since there is no public API to call us with an EventListenerHolder, we + // know that there's an EventListenerHolder on the stack holding a strong ref + // to the listener. + + Listener* listener; + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; i++) { + listener = &mListeners.ElementAt(i); + // mListener == aListenerHolder is the last one, since it can be a bit slow. + if (listener->mListenerIsHandler == aHandler && + listener->mFlags.EqualsForAddition(aFlags) && + EVENT_TYPE_EQUALS(listener, aEventMessage, aTypeAtom, aTypeString, + aAllEvents) && + listener->mListener == aListenerHolder) { + return; + } + } + + mNoListenerForEvent = eVoidEvent; + mNoListenerForEventAtom = nullptr; + + listener = aAllEvents ? mListeners.InsertElementAt(0) : + mListeners.AppendElement(); + listener->mEventMessage = aEventMessage; + listener->mTypeString = aTypeString; + listener->mTypeAtom = aTypeAtom; + listener->mFlags = aFlags; + listener->mListenerIsHandler = aHandler; + listener->mHandlerIsString = false; + listener->mAllEvents = aAllEvents; + listener->mIsChrome = mIsMainThreadELM && + nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + + // Detect the type of event listener. + nsCOMPtr<nsIXPConnectWrappedJS> wjs; + if (aFlags.mListenerIsJSListener) { + MOZ_ASSERT(!aListenerHolder.HasWebIDLCallback()); + listener->mListenerType = Listener::eJSEventListener; + } else if (aListenerHolder.HasWebIDLCallback()) { + listener->mListenerType = Listener::eWebIDLListener; + } else if ((wjs = do_QueryInterface(aListenerHolder.GetXPCOMCallback()))) { + listener->mListenerType = Listener::eWrappedJSListener; + } else { + listener->mListenerType = Listener::eNativeListener; + } + listener->mListener = Move(aListenerHolder); + + + if (aFlags.mInSystemGroup) { + mMayHaveSystemGroupListeners = true; + } + if (aFlags.mCapture) { + mMayHaveCapturingListeners = true; + } + + if (aEventMessage == eAfterPaint) { + mMayHavePaintEventListener = true; + if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) { + window->SetHasPaintEventListeners(); + } + } else if (aEventMessage >= eLegacyMutationEventFirst && + aEventMessage <= eLegacyMutationEventLast) { + // For mutation listeners, we need to update the global bit on the DOM window. + // Otherwise we won't actually fire the mutation event. + mMayHaveMutationListeners = true; + // Go from our target to the nearest enclosing DOM window. + if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) { + nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); + if (doc) { + doc->WarnOnceAbout(nsIDocument::eMutationEvent); + } + // If aEventMessage is eLegacySubtreeModified, we need to listen all + // mutations. nsContentUtils::HasMutationListeners relies on this. + window->SetMutationListeners( + (aEventMessage == eLegacySubtreeModified) ? + kAllMutationBits : MutationBitForEventType(aEventMessage)); + } + } else if (aTypeAtom == nsGkAtoms::ondeviceorientation) { + EnableDevice(eDeviceOrientation); + } else if (aTypeAtom == nsGkAtoms::onabsolutedeviceorientation) { + EnableDevice(eAbsoluteDeviceOrientation); + } else if (aTypeAtom == nsGkAtoms::ondeviceproximity || aTypeAtom == nsGkAtoms::onuserproximity) { + EnableDevice(eDeviceProximity); + } else if (aTypeAtom == nsGkAtoms::ondevicelight) { + EnableDevice(eDeviceLight); + } else if (aTypeAtom == nsGkAtoms::ondevicemotion) { + EnableDevice(eDeviceMotion); +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + } else if (aTypeAtom == nsGkAtoms::onorientationchange) { + EnableDevice(eOrientationChange); +#endif +#ifdef MOZ_B2G + } else if (aTypeAtom == nsGkAtoms::onmoztimechange) { + EnableDevice(eTimeChange); + } else if (aTypeAtom == nsGkAtoms::onmoznetworkupload) { + EnableDevice(eNetworkUpload); + } else if (aTypeAtom == nsGkAtoms::onmoznetworkdownload) { + EnableDevice(eNetworkDownload); +#endif // MOZ_B2G + } else if (aTypeAtom == nsGkAtoms::ontouchstart || + aTypeAtom == nsGkAtoms::ontouchend || + aTypeAtom == nsGkAtoms::ontouchmove || + aTypeAtom == nsGkAtoms::ontouchcancel) { + mMayHaveTouchEventListener = true; + nsPIDOMWindowInner* window = GetInnerWindowForTarget(); + // we don't want touchevent listeners added by scrollbars to flip this flag + // so we ignore listeners created with system event flag + if (window && !aFlags.mInSystemGroup) { + window->SetHasTouchEventListeners(); + } + } else if (aEventMessage >= ePointerEventFirst && + aEventMessage <= ePointerEventLast) { + nsPIDOMWindowInner* window = GetInnerWindowForTarget(); + if (aTypeAtom == nsGkAtoms::onpointerenter || + aTypeAtom == nsGkAtoms::onpointerleave) { + mMayHavePointerEnterLeaveEventListener = true; + if (window) { +#ifdef DEBUG + nsCOMPtr<nsIDocument> d = window->GetExtantDoc(); + NS_WARNING_ASSERTION( + !nsContentUtils::IsChromeDoc(d), + "Please do not use pointerenter/leave events in chrome. " + "They are slower than pointerover/out!"); +#endif + window->SetHasPointerEnterLeaveEventListeners(); + } + } + } else if (aTypeAtom == nsGkAtoms::onmouseenter || + aTypeAtom == nsGkAtoms::onmouseleave) { + mMayHaveMouseEnterLeaveEventListener = true; + if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) { +#ifdef DEBUG + nsCOMPtr<nsIDocument> d = window->GetExtantDoc(); + NS_WARNING_ASSERTION( + !nsContentUtils::IsChromeDoc(d), + "Please do not use mouseenter/leave events in chrome. " + "They are slower than mouseover/out!"); +#endif + window->SetHasMouseEnterLeaveEventListeners(); + } +#ifdef MOZ_GAMEPAD + } else if (aEventMessage >= eGamepadEventFirst && + aEventMessage <= eGamepadEventLast) { + if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) { + window->SetHasGamepadEventListener(); + } +#endif + } else if (aTypeAtom == nsGkAtoms::onkeydown || + aTypeAtom == nsGkAtoms::onkeypress || + aTypeAtom == nsGkAtoms::onkeyup) { + if (!aFlags.mInSystemGroup) { + mMayHaveKeyEventListener = true; + } + } else if (aTypeAtom == nsGkAtoms::oncompositionend || + aTypeAtom == nsGkAtoms::oncompositionstart || + aTypeAtom == nsGkAtoms::oncompositionupdate || + aTypeAtom == nsGkAtoms::oninput) { + if (!aFlags.mInSystemGroup) { + mMayHaveInputOrCompositionEventListener = true; + } + } + + if (IsApzAwareListener(listener)) { + ProcessApzAwareEventListenerAdd(); + } + + if (aTypeAtom && mTarget) { + mTarget->EventListenerAdded(aTypeAtom); + } + + if (mIsMainThreadELM && mTarget) { + EventListenerService::NotifyAboutMainThreadListenerChange(mTarget, + aTypeAtom); + } +} + +void +EventListenerManager::ProcessApzAwareEventListenerAdd() +{ + // Mark the node as having apz aware listeners + nsCOMPtr<nsINode> node = do_QueryInterface(mTarget); + if (node) { + node->SetMayBeApzAware(); + } + + // Schedule a paint so event regions on the layer tree gets updated + nsIDocument* doc = nullptr; + if (node) { + doc = node->OwnerDoc(); + } + if (!doc) { + if (nsCOMPtr<nsPIDOMWindowInner> window = GetTargetAsInnerWindow()) { + doc = window->GetExtantDoc(); + } + } + if (!doc) { + if (nsCOMPtr<DOMEventTargetHelper> helper = do_QueryInterface(mTarget)) { + if (nsPIDOMWindowInner* window = helper->GetOwner()) { + doc = window->GetExtantDoc(); + } + } + } + + if (doc && nsDisplayListBuilder::LayerEventRegionsEnabled()) { + nsIPresShell* ps = doc->GetShell(); + if (ps) { + nsIFrame* f = ps->GetRootFrame(); + if (f) { + f->SchedulePaint(); + } + } + } +} + +bool +EventListenerManager::IsDeviceType(EventMessage aEventMessage) +{ + switch (aEventMessage) { + case eDeviceOrientation: + case eAbsoluteDeviceOrientation: + case eDeviceMotion: + case eDeviceLight: + case eDeviceProximity: + case eUserProximity: +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + case eOrientationChange: +#endif +#ifdef MOZ_B2G + case eTimeChange: + case eNetworkUpload: + case eNetworkDownload: +#endif + return true; + default: + break; + } + return false; +} + +void +EventListenerManager::EnableDevice(EventMessage aEventMessage) +{ + nsCOMPtr<nsPIDOMWindowInner> window = GetTargetAsInnerWindow(); + if (!window) { + return; + } + + switch (aEventMessage) { + case eDeviceOrientation: +#ifdef MOZ_WIDGET_ANDROID + // Falls back to SENSOR_ROTATION_VECTOR and SENSOR_ORIENTATION if + // unavailable on device. + window->EnableDeviceSensor(SENSOR_GAME_ROTATION_VECTOR); +#else + window->EnableDeviceSensor(SENSOR_ORIENTATION); +#endif + break; + case eAbsoluteDeviceOrientation: +#ifdef MOZ_WIDGET_ANDROID + // Falls back to SENSOR_ORIENTATION if unavailable on device. + window->EnableDeviceSensor(SENSOR_ROTATION_VECTOR); +#else + window->EnableDeviceSensor(SENSOR_ORIENTATION); +#endif + break; + case eDeviceProximity: + case eUserProximity: + window->EnableDeviceSensor(SENSOR_PROXIMITY); + break; + case eDeviceLight: + window->EnableDeviceSensor(SENSOR_LIGHT); + break; + case eDeviceMotion: + window->EnableDeviceSensor(SENSOR_ACCELERATION); + window->EnableDeviceSensor(SENSOR_LINEAR_ACCELERATION); + window->EnableDeviceSensor(SENSOR_GYROSCOPE); + break; +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + case eOrientationChange: + window->EnableOrientationChangeListener(); + break; +#endif +#ifdef MOZ_B2G + case eTimeChange: + window->EnableTimeChangeNotifications(); + break; + case eNetworkUpload: + case eNetworkDownload: + window->EnableNetworkEvent(aEventMessage); + break; +#endif + default: + NS_WARNING("Enabling an unknown device sensor."); + break; + } +} + +void +EventListenerManager::DisableDevice(EventMessage aEventMessage) +{ + nsCOMPtr<nsPIDOMWindowInner> window = GetTargetAsInnerWindow(); + if (!window) { + return; + } + + switch (aEventMessage) { + case eDeviceOrientation: +#ifdef MOZ_WIDGET_ANDROID + // Disable all potential fallback sensors. + window->DisableDeviceSensor(SENSOR_GAME_ROTATION_VECTOR); + window->DisableDeviceSensor(SENSOR_ROTATION_VECTOR); +#endif + window->DisableDeviceSensor(SENSOR_ORIENTATION); + break; + case eAbsoluteDeviceOrientation: +#ifdef MOZ_WIDGET_ANDROID + window->DisableDeviceSensor(SENSOR_ROTATION_VECTOR); +#endif + window->DisableDeviceSensor(SENSOR_ORIENTATION); + break; + case eDeviceMotion: + window->DisableDeviceSensor(SENSOR_ACCELERATION); + window->DisableDeviceSensor(SENSOR_LINEAR_ACCELERATION); + window->DisableDeviceSensor(SENSOR_GYROSCOPE); + break; + case eDeviceProximity: + case eUserProximity: + window->DisableDeviceSensor(SENSOR_PROXIMITY); + break; + case eDeviceLight: + window->DisableDeviceSensor(SENSOR_LIGHT); + break; +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + case eOrientationChange: + window->DisableOrientationChangeListener(); + break; +#endif +#ifdef MOZ_B2G + case eTimeChange: + window->DisableTimeChangeNotifications(); + break; + case eNetworkUpload: + case eNetworkDownload: + window->DisableNetworkEvent(aEventMessage); + break; +#endif // MOZ_B2G + default: + NS_WARNING("Disabling an unknown device sensor."); + break; + } +} + +void +EventListenerManager::NotifyEventListenerRemoved(nsIAtom* aUserType) +{ + // If the following code is changed, other callsites of EventListenerRemoved + // and NotifyAboutMainThreadListenerChange should be changed too. + mNoListenerForEvent = eVoidEvent; + mNoListenerForEventAtom = nullptr; + if (mTarget && aUserType) { + mTarget->EventListenerRemoved(aUserType); + } + if (mIsMainThreadELM && mTarget) { + EventListenerService::NotifyAboutMainThreadListenerChange(mTarget, + aUserType); + } +} + +void +EventListenerManager::RemoveEventListenerInternal( + EventListenerHolder aListenerHolder, + EventMessage aEventMessage, + nsIAtom* aUserType, + const nsAString& aTypeString, + const EventListenerFlags& aFlags, + bool aAllEvents) +{ + if (!aListenerHolder || !aEventMessage || mClearingListeners) { + return; + } + + Listener* listener; + + uint32_t count = mListeners.Length(); + bool deviceType = IsDeviceType(aEventMessage); + + RefPtr<EventListenerManager> kungFuDeathGrip(this); + + for (uint32_t i = 0; i < count; ++i) { + listener = &mListeners.ElementAt(i); + if (EVENT_TYPE_EQUALS(listener, aEventMessage, aUserType, aTypeString, + aAllEvents)) { + if (listener->mListener == aListenerHolder && + listener->mFlags.EqualsForRemoval(aFlags)) { + mListeners.RemoveElementAt(i); + NotifyEventListenerRemoved(aUserType); + if (!aAllEvents && deviceType) { + DisableDevice(aEventMessage); + } + return; + } + } + } + +} + +bool +EventListenerManager::ListenerCanHandle(const Listener* aListener, + const WidgetEvent* aEvent, + EventMessage aEventMessage) const + +{ + MOZ_ASSERT(aEventMessage == aEvent->mMessage || + aEventMessage == GetLegacyEventMessage(aEvent->mMessage), + "aEvent and aEventMessage should agree, modulo legacyness"); + + // The listener has been removed, it cannot handle anything. + if (aListener->mListenerType == Listener::eNoListener) { + return false; + } + // This is slightly different from EVENT_TYPE_EQUALS in that it returns + // true even when aEvent->mMessage == eUnidentifiedEvent and + // aListener=>mEventMessage != eUnidentifiedEvent as long as the atoms are + // the same + if (aListener->mAllEvents) { + return true; + } + if (aEvent->mMessage == eUnidentifiedEvent) { + if (mIsMainThreadELM) { + return aListener->mTypeAtom == aEvent->mSpecifiedEventType; + } + return aListener->mTypeString.Equals(aEvent->mSpecifiedEventTypeString); + } + if (!nsContentUtils::IsUnprefixedFullscreenApiEnabled() && + aEvent->IsTrusted() && (aEventMessage == eFullscreenChange || + aEventMessage == eFullscreenError)) { + // If unprefixed Fullscreen API is not enabled, don't dispatch it + // to the content. + if (!aEvent->mFlags.mInSystemGroup && !aListener->mIsChrome) { + return false; + } + } + MOZ_ASSERT(mIsMainThreadELM); + return aListener->mEventMessage == aEventMessage; +} + +void +EventListenerManager::AddEventListenerByType( + EventListenerHolder aListenerHolder, + const nsAString& aType, + const EventListenerFlags& aFlags) +{ + nsCOMPtr<nsIAtom> atom; + EventMessage message = mIsMainThreadELM ? + nsContentUtils::GetEventMessageAndAtomForListener(aType, + getter_AddRefs(atom)) : + eUnidentifiedEvent; + AddEventListenerInternal(Move(aListenerHolder), + message, atom, aType, aFlags); +} + +void +EventListenerManager::RemoveEventListenerByType( + EventListenerHolder aListenerHolder, + const nsAString& aType, + const EventListenerFlags& aFlags) +{ + nsCOMPtr<nsIAtom> atom; + EventMessage message = mIsMainThreadELM ? + nsContentUtils::GetEventMessageAndAtomForListener(aType, + getter_AddRefs(atom)) : + eUnidentifiedEvent; + RemoveEventListenerInternal(Move(aListenerHolder), + message, atom, aType, aFlags); +} + +EventListenerManager::Listener* +EventListenerManager::FindEventHandler(EventMessage aEventMessage, + nsIAtom* aTypeAtom, + const nsAString& aTypeString) +{ + // Run through the listeners for this type and see if a script + // listener is registered + Listener* listener; + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + listener = &mListeners.ElementAt(i); + if (listener->mListenerIsHandler && + EVENT_TYPE_EQUALS(listener, aEventMessage, aTypeAtom, aTypeString, + false)) { + return listener; + } + } + return nullptr; +} + +EventListenerManager::Listener* +EventListenerManager::SetEventHandlerInternal( + nsIAtom* aName, + const nsAString& aTypeString, + const TypedEventHandler& aTypedHandler, + bool aPermitUntrustedEvents) +{ + MOZ_ASSERT(aName || !aTypeString.IsEmpty()); + + EventMessage eventMessage = nsContentUtils::GetEventMessage(aName); + Listener* listener = FindEventHandler(eventMessage, aName, aTypeString); + + if (!listener) { + // If we didn't find a script listener or no listeners existed + // create and add a new one. + EventListenerFlags flags; + flags.mListenerIsJSListener = true; + + nsCOMPtr<JSEventHandler> jsEventHandler; + NS_NewJSEventHandler(mTarget, aName, + aTypedHandler, getter_AddRefs(jsEventHandler)); + AddEventListenerInternal(EventListenerHolder(jsEventHandler), + eventMessage, aName, aTypeString, flags, true); + + listener = FindEventHandler(eventMessage, aName, aTypeString); + } else { + JSEventHandler* jsEventHandler = listener->GetJSEventHandler(); + MOZ_ASSERT(jsEventHandler, + "How can we have an event handler with no JSEventHandler?"); + + bool same = jsEventHandler->GetTypedEventHandler() == aTypedHandler; + // Possibly the same listener, but update still the context and scope. + jsEventHandler->SetHandler(aTypedHandler); + if (mTarget && !same && aName) { + mTarget->EventListenerRemoved(aName); + mTarget->EventListenerAdded(aName); + } + if (mIsMainThreadELM && mTarget) { + EventListenerService::NotifyAboutMainThreadListenerChange(mTarget, aName); + } + } + + // Set flag to indicate possible need for compilation later + listener->mHandlerIsString = !aTypedHandler.HasEventHandler(); + if (aPermitUntrustedEvents) { + listener->mFlags.mAllowUntrustedEvents = true; + } + + return listener; +} + +nsresult +EventListenerManager::SetEventHandler(nsIAtom* aName, + const nsAString& aBody, + bool aDeferCompilation, + bool aPermitUntrustedEvents, + Element* aElement) +{ + nsCOMPtr<nsIDocument> doc; + nsCOMPtr<nsIScriptGlobalObject> global = + GetScriptGlobalAndDocument(getter_AddRefs(doc)); + + if (!global) { + // This can happen; for example this document might have been + // loaded as data. + return NS_OK; + } + +#ifdef DEBUG + if (nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global)) { + MOZ_ASSERT(win->IsInnerWindow(), "We should not have an outer window here!"); + } +#endif + + nsresult rv = NS_OK; + // return early preventing the event listener from being added + // 'doc' is fetched above + if (doc) { + // Don't allow adding an event listener if the document is sandboxed + // without 'allow-scripts'. + if (doc->HasScriptsBlockedBySandbox()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // Perform CSP check + nsCOMPtr<nsIContentSecurityPolicy> csp; + rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); + NS_ENSURE_SUCCESS(rv, rv); + + if (csp) { + // let's generate a script sample and pass it as aContent, + // it will not match the hash, but allows us to pass + // the script sample in aContent. + nsAutoString scriptSample, attr, tagName(NS_LITERAL_STRING("UNKNOWN")); + aName->ToString(attr); + nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mTarget)); + if (domNode) { + domNode->GetNodeName(tagName); + } + // build a "script sample" based on what we know about this element + scriptSample.Assign(attr); + scriptSample.AppendLiteral(" attribute on "); + scriptSample.Append(tagName); + scriptSample.AppendLiteral(" element"); + + bool allowsInlineScript = true; + rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT, + EmptyString(), // aNonce + true, // aParserCreated (true because attribute event handler) + scriptSample, + 0, // aLineNumber + &allowsInlineScript); + NS_ENSURE_SUCCESS(rv, rv); + + // return early if CSP wants us to block inline scripts + if (!allowsInlineScript) { + return NS_OK; + } + } + } + + // This might be the first reference to this language in the global + // We must init the language before we attempt to fetch its context. + if (NS_FAILED(global->EnsureScriptEnvironment())) { + NS_WARNING("Failed to setup script environment for this language"); + // but fall through and let the inevitable failure below handle it. + } + + nsIScriptContext* context = global->GetScriptContext(); + NS_ENSURE_TRUE(context, NS_ERROR_FAILURE); + NS_ENSURE_STATE(global->GetGlobalJSObject()); + + Listener* listener = SetEventHandlerInternal(aName, + EmptyString(), + TypedEventHandler(), + aPermitUntrustedEvents); + + if (!aDeferCompilation) { + return CompileEventHandlerInternal(listener, &aBody, aElement); + } + + return NS_OK; +} + +void +EventListenerManager::RemoveEventHandler(nsIAtom* aName, + const nsAString& aTypeString) +{ + if (mClearingListeners) { + return; + } + + EventMessage eventMessage = nsContentUtils::GetEventMessage(aName); + Listener* listener = FindEventHandler(eventMessage, aName, aTypeString); + + if (listener) { + mListeners.RemoveElementAt(uint32_t(listener - &mListeners.ElementAt(0))); + NotifyEventListenerRemoved(aName); + if (IsDeviceType(eventMessage)) { + DisableDevice(eventMessage); + } + } +} + +nsresult +EventListenerManager::CompileEventHandlerInternal(Listener* aListener, + const nsAString* aBody, + Element* aElement) +{ + MOZ_ASSERT(aListener->GetJSEventHandler()); + MOZ_ASSERT(aListener->mHandlerIsString, "Why are we compiling a non-string JS listener?"); + JSEventHandler* jsEventHandler = aListener->GetJSEventHandler(); + MOZ_ASSERT(!jsEventHandler->GetTypedEventHandler().HasEventHandler(), + "What is there to compile?"); + + nsresult result = NS_OK; + nsCOMPtr<nsIDocument> doc; + nsCOMPtr<nsIScriptGlobalObject> global = + GetScriptGlobalAndDocument(getter_AddRefs(doc)); + NS_ENSURE_STATE(global); + + // Activate JSAPI, and make sure that exceptions are reported on the right + // Window. + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(global))) { + return NS_ERROR_UNEXPECTED; + } + JSContext* cx = jsapi.cx(); + + nsCOMPtr<nsIAtom> typeAtom = aListener->mTypeAtom; + nsIAtom* attrName = typeAtom; + + // Flag us as not a string so we don't keep trying to compile strings which + // can't be compiled. + aListener->mHandlerIsString = false; + + // mTarget may not be an Element if it's a window and we're + // getting an inline event listener forwarded from <html:body> or + // <html:frameset> or <xul:window> or the like. + // XXX I don't like that we have to reference content from + // here. The alternative is to store the event handler string on + // the JSEventHandler itself, and that still doesn't address + // the arg names issue. + nsCOMPtr<Element> element = do_QueryInterface(mTarget); + MOZ_ASSERT(element || aBody, "Where will we get our body?"); + nsAutoString handlerBody; + const nsAString* body = aBody; + if (!aBody) { + if (aListener->mTypeAtom == nsGkAtoms::onSVGLoad) { + attrName = nsGkAtoms::onload; + } else if (aListener->mTypeAtom == nsGkAtoms::onSVGUnload) { + attrName = nsGkAtoms::onunload; + } else if (aListener->mTypeAtom == nsGkAtoms::onSVGResize) { + attrName = nsGkAtoms::onresize; + } else if (aListener->mTypeAtom == nsGkAtoms::onSVGScroll) { + attrName = nsGkAtoms::onscroll; + } else if (aListener->mTypeAtom == nsGkAtoms::onSVGZoom) { + attrName = nsGkAtoms::onzoom; + } else if (aListener->mTypeAtom == nsGkAtoms::onbeginEvent) { + attrName = nsGkAtoms::onbegin; + } else if (aListener->mTypeAtom == nsGkAtoms::onrepeatEvent) { + attrName = nsGkAtoms::onrepeat; + } else if (aListener->mTypeAtom == nsGkAtoms::onendEvent) { + attrName = nsGkAtoms::onend; + } + + element->GetAttr(kNameSpaceID_None, attrName, handlerBody); + body = &handlerBody; + aElement = element; + } + aListener = nullptr; + + uint32_t lineNo = 0; + nsAutoCString url (NS_LITERAL_CSTRING("-moz-evil:lying-event-listener")); + MOZ_ASSERT(body); + MOZ_ASSERT(aElement); + nsIURI *uri = aElement->OwnerDoc()->GetDocumentURI(); + if (uri) { + uri->GetSpec(url); + lineNo = 1; + } + + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(mTarget); + uint32_t argCount; + const char **argNames; + nsContentUtils::GetEventArgNames(aElement->GetNameSpaceID(), + typeAtom, win, + &argCount, &argNames); + + JSAddonId *addonId = MapURIToAddonID(uri); + + // Wrap the event target, so that we can use it as the scope for the event + // handler. Note that mTarget is different from aElement in the <body> case, + // where mTarget is a Window. + // + // The wrapScope doesn't really matter here, because the target will create + // its reflector in the proper scope, and then we'll enter that compartment. + JS::Rooted<JSObject*> wrapScope(cx, global->GetGlobalJSObject()); + JS::Rooted<JS::Value> v(cx); + { + JSAutoCompartment ac(cx, wrapScope); + nsresult rv = nsContentUtils::WrapNative(cx, mTarget, &v, + /* aAllowWrapping = */ false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (addonId) { + JS::Rooted<JSObject*> vObj(cx, &v.toObject()); + JS::Rooted<JSObject*> addonScope(cx, xpc::GetAddonScope(cx, vObj, addonId)); + if (!addonScope) { + return NS_ERROR_FAILURE; + } + JSAutoCompartment ac(cx, addonScope); + + // Wrap our event target into the addon scope, since that's where we want to + // do all our work. + if (!JS_WrapValue(cx, &v)) { + return NS_ERROR_FAILURE; + } + } + JS::Rooted<JSObject*> target(cx, &v.toObject()); + JSAutoCompartment ac(cx, target); + + // Now that we've entered the compartment we actually care about, create our + // scope chain. Note that we start with |element|, not aElement, because + // mTarget is different from aElement in the <body> case, where mTarget is a + // Window, and in that case we do not want the scope chain to include the body + // or the document. + JS::AutoObjectVector scopeChain(cx); + if (!nsJSUtils::GetScopeChainForElement(cx, element, scopeChain)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsDependentAtomString str(attrName); + // Most of our names are short enough that we don't even have to malloc + // the JS string stuff, so don't worry about playing games with + // refcounting XPCOM stringbuffers. + JS::Rooted<JSString*> jsStr(cx, JS_NewUCStringCopyN(cx, + str.BeginReading(), + str.Length())); + NS_ENSURE_TRUE(jsStr, NS_ERROR_OUT_OF_MEMORY); + + // Get the reflector for |aElement|, so that we can pass to setElement. + if (NS_WARN_IF(!GetOrCreateDOMReflector(cx, aElement, &v))) { + return NS_ERROR_FAILURE; + } + JS::CompileOptions options(cx); + options.setIntroductionType("eventHandler") + .setFileAndLine(url.get(), lineNo) + .setVersion(JSVERSION_DEFAULT) + .setElement(&v.toObject()) + .setElementAttributeName(jsStr); + + JS::Rooted<JSObject*> handler(cx); + result = nsJSUtils::CompileFunction(jsapi, scopeChain, options, + nsAtomCString(typeAtom), + argCount, argNames, *body, handler.address()); + NS_ENSURE_SUCCESS(result, result); + NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE); + + if (jsEventHandler->EventName() == nsGkAtoms::onerror && win) { + RefPtr<OnErrorEventHandlerNonNull> handlerCallback = + new OnErrorEventHandlerNonNull(nullptr, handler, /* aIncumbentGlobal = */ nullptr); + jsEventHandler->SetHandler(handlerCallback); + } else if (jsEventHandler->EventName() == nsGkAtoms::onbeforeunload && win) { + RefPtr<OnBeforeUnloadEventHandlerNonNull> handlerCallback = + new OnBeforeUnloadEventHandlerNonNull(nullptr, handler, /* aIncumbentGlobal = */ nullptr); + jsEventHandler->SetHandler(handlerCallback); + } else { + RefPtr<EventHandlerNonNull> handlerCallback = + new EventHandlerNonNull(nullptr, handler, /* aIncumbentGlobal = */ nullptr); + jsEventHandler->SetHandler(handlerCallback); + } + + return result; +} + +nsresult +EventListenerManager::HandleEventSubType(Listener* aListener, + nsIDOMEvent* aDOMEvent, + EventTarget* aCurrentTarget) +{ + nsresult result = NS_OK; + // strong ref + EventListenerHolder listenerHolder(aListener->mListener.Clone()); + + // If this is a script handler and we haven't yet + // compiled the event handler itself + if ((aListener->mListenerType == Listener::eJSEventListener) && + aListener->mHandlerIsString) { + result = CompileEventHandlerInternal(aListener, nullptr, nullptr); + aListener = nullptr; + } + + if (NS_SUCCEEDED(result)) { + if (mIsMainThreadELM) { + nsContentUtils::EnterMicroTask(); + } + // nsIDOMEvent::currentTarget is set in EventDispatcher. + if (listenerHolder.HasWebIDLCallback()) { + ErrorResult rv; + listenerHolder.GetWebIDLCallback()-> + HandleEvent(aCurrentTarget, *(aDOMEvent->InternalDOMEvent()), rv); + result = rv.StealNSResult(); + } else { + result = listenerHolder.GetXPCOMCallback()->HandleEvent(aDOMEvent); + } + if (mIsMainThreadELM) { + nsContentUtils::LeaveMicroTask(); + } + } + + return result; +} + +EventMessage +EventListenerManager::GetLegacyEventMessage(EventMessage aEventMessage) const +{ + // (If we're off-main-thread, we can't check the pref; so we just behave as + // if it's disabled.) + if (mIsMainThreadELM) { + if (IsWebkitPrefixSupportEnabled()) { + // webkit-prefixed legacy events: + if (aEventMessage == eTransitionEnd) { + return eWebkitTransitionEnd; + } + if (aEventMessage == eAnimationStart) { + return eWebkitAnimationStart; + } + if (aEventMessage == eAnimationEnd) { + return eWebkitAnimationEnd; + } + if (aEventMessage == eAnimationIteration) { + return eWebkitAnimationIteration; + } + } + if (IsPrefixedPointerLockEnabled()) { + if (aEventMessage == ePointerLockChange) { + return eMozPointerLockChange; + } + if (aEventMessage == ePointerLockError) { + return eMozPointerLockError; + } + } + } + + switch (aEventMessage) { + case eFullscreenChange: + return eMozFullscreenChange; + case eFullscreenError: + return eMozFullscreenError; + default: + return aEventMessage; + } +} + +/** +* Causes a check for event listeners and processing by them if they exist. +* @param an event listener +*/ + +void +EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent** aDOMEvent, + EventTarget* aCurrentTarget, + nsEventStatus* aEventStatus) +{ + //Set the value of the internal PreventDefault flag properly based on aEventStatus + if (!aEvent->DefaultPrevented() && + *aEventStatus == nsEventStatus_eConsumeNoDefault) { + // Assume that if only aEventStatus claims that the event has already been + // consumed, the consumer is default event handler. + aEvent->PreventDefault(); + } + + Maybe<nsAutoPopupStatePusher> popupStatePusher; + if (mIsMainThreadELM) { + popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent, *aDOMEvent)); + } + + bool hasListener = false; + bool hasListenerForCurrentGroup = false; + bool usingLegacyMessage = false; + bool hasRemovedListener = false; + EventMessage eventMessage = aEvent->mMessage; + + while (true) { + nsAutoTObserverArray<Listener, 2>::EndLimitedIterator iter(mListeners); + Maybe<EventMessageAutoOverride> legacyAutoOverride; + while (iter.HasMore()) { + if (aEvent->mFlags.mImmediatePropagationStopped) { + break; + } + Listener* listener = &iter.GetNext(); + // Check that the phase is same in event and event listener. + // Handle only trusted events, except when listener permits untrusted events. + if (ListenerCanHandle(listener, aEvent, eventMessage)) { + hasListener = true; + hasListenerForCurrentGroup = hasListenerForCurrentGroup || + listener->mFlags.mInSystemGroup == aEvent->mFlags.mInSystemGroup; + if (listener->IsListening(aEvent) && + (aEvent->IsTrusted() || listener->mFlags.mAllowUntrustedEvents)) { + if (!*aDOMEvent) { + // This is tiny bit slow, but happens only once per event. + nsCOMPtr<EventTarget> et = + do_QueryInterface(aEvent->mOriginalTarget); + RefPtr<Event> event = EventDispatcher::CreateEvent(et, aPresContext, + aEvent, + EmptyString()); + event.forget(aDOMEvent); + } + if (*aDOMEvent) { + if (!aEvent->mCurrentTarget) { + aEvent->mCurrentTarget = aCurrentTarget->GetTargetForDOMEvent(); + if (!aEvent->mCurrentTarget) { + break; + } + } + if (usingLegacyMessage && !legacyAutoOverride) { + // Override the aDOMEvent's event-message (its .type) until we + // finish traversing listeners (when legacyAutoOverride destructs) + legacyAutoOverride.emplace(*aDOMEvent, eventMessage); + } + + // Maybe add a marker to the docshell's timeline, but only + // bother with all the logic if some docshell is recording. + nsCOMPtr<nsIDocShell> docShell; + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + bool needsEndEventMarker = false; + + if (mIsMainThreadELM && + listener->mListenerType != Listener::eNativeListener) { + docShell = nsContentUtils::GetDocShellForEventTarget(mTarget); + if (docShell) { + if (timelines && timelines->HasConsumer(docShell)) { + needsEndEventMarker = true; + nsAutoString typeStr; + (*aDOMEvent)->GetType(typeStr); + uint16_t phase; + (*aDOMEvent)->GetEventPhase(&phase); + timelines->AddMarkerForDocShell(docShell, Move( + MakeUnique<EventTimelineMarker>( + typeStr, phase, MarkerTracingType::START))); + } + } + } + + aEvent->mFlags.mInPassiveListener = listener->mFlags.mPassive; + Maybe<Listener> listenerHolder; + if (listener->mFlags.mOnce) { + // Move the listener to the stack before handling the event. + // The order is important, otherwise the listener could be + // called again inside the listener. + listenerHolder.emplace(Move(*listener)); + listener = listenerHolder.ptr(); + hasRemovedListener = true; + } + if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) { + aEvent->mFlags.mExceptionWasRaised = true; + } + aEvent->mFlags.mInPassiveListener = false; + + if (needsEndEventMarker) { + timelines->AddMarkerForDocShell( + docShell, "DOMEvent", MarkerTracingType::END); + } + } + } + } + } + + // If we didn't find any matching listeners, and our event has a legacy + // version, we'll now switch to looking for that legacy version and we'll + // recheck our listeners. + if (hasListenerForCurrentGroup || usingLegacyMessage) { + // (No need to recheck listeners, because we already found a match, or we + // already rechecked them.) + break; + } + EventMessage legacyEventMessage = GetLegacyEventMessage(eventMessage); + if (legacyEventMessage == eventMessage) { + break; // There's no legacy version of our event; no need to recheck. + } + MOZ_ASSERT(GetLegacyEventMessage(legacyEventMessage) == legacyEventMessage, + "Legacy event messages should not themselves have legacy versions"); + + // Recheck our listeners, using the legacy event message we just looked up: + eventMessage = legacyEventMessage; + usingLegacyMessage = true; + } + + aEvent->mCurrentTarget = nullptr; + + if (hasRemovedListener) { + // If there are any once listeners replaced with a placeholder in + // the loop above, we need to clean up them here. Note that, this + // could clear once listeners handled in some outer level as well, + // but that should not affect the result. + mListeners.RemoveElementsBy([](const Listener& aListener) { + return aListener.mListenerType == Listener::eNoListener; + }); + NotifyEventListenerRemoved(aEvent->mSpecifiedEventType); + if (IsDeviceType(aEvent->mMessage)) { + // This is a device-type event, we need to check whether we can + // disable device after removing the once listeners. + bool hasAnyListener = false; + nsAutoTObserverArray<Listener, 2>::ForwardIterator iter(mListeners); + while (iter.HasMore()) { + Listener* listener = &iter.GetNext(); + if (EVENT_TYPE_EQUALS(listener, aEvent->mMessage, + aEvent->mSpecifiedEventType, + aEvent->mSpecifiedEventTypeString, + /* all events */ false)) { + hasAnyListener = true; + break; + } + } + if (!hasAnyListener) { + DisableDevice(aEvent->mMessage); + } + } + } + + if (mIsMainThreadELM && !hasListener) { + mNoListenerForEvent = aEvent->mMessage; + mNoListenerForEventAtom = aEvent->mSpecifiedEventType; + } + + if (aEvent->DefaultPrevented()) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } +} + +void +EventListenerManager::Disconnect() +{ + mTarget = nullptr; + RemoveAllListeners(); +} + +void +EventListenerManager::AddEventListener( + const nsAString& aType, + EventListenerHolder aListenerHolder, + bool aUseCapture, + bool aWantsUntrusted) +{ + EventListenerFlags flags; + flags.mCapture = aUseCapture; + flags.mAllowUntrustedEvents = aWantsUntrusted; + return AddEventListenerByType(Move(aListenerHolder), aType, flags); +} + +void +EventListenerManager::AddEventListener( + const nsAString& aType, + EventListenerHolder aListenerHolder, + const dom::AddEventListenerOptionsOrBoolean& aOptions, + bool aWantsUntrusted) +{ + EventListenerFlags flags; + if (aOptions.IsBoolean()) { + flags.mCapture = aOptions.GetAsBoolean(); + } else { + const auto& options = aOptions.GetAsAddEventListenerOptions(); + flags.mCapture = options.mCapture; + flags.mInSystemGroup = options.mMozSystemGroup; + flags.mPassive = options.mPassive; + flags.mOnce = options.mOnce; + } + flags.mAllowUntrustedEvents = aWantsUntrusted; + return AddEventListenerByType(Move(aListenerHolder), aType, flags); +} + +void +EventListenerManager::RemoveEventListener( + const nsAString& aType, + EventListenerHolder aListenerHolder, + bool aUseCapture) +{ + EventListenerFlags flags; + flags.mCapture = aUseCapture; + RemoveEventListenerByType(Move(aListenerHolder), aType, flags); +} + +void +EventListenerManager::RemoveEventListener( + const nsAString& aType, + EventListenerHolder aListenerHolder, + const dom::EventListenerOptionsOrBoolean& aOptions) +{ + EventListenerFlags flags; + if (aOptions.IsBoolean()) { + flags.mCapture = aOptions.GetAsBoolean(); + } else { + const auto& options = aOptions.GetAsEventListenerOptions(); + flags.mCapture = options.mCapture; + flags.mInSystemGroup = options.mMozSystemGroup; + } + RemoveEventListenerByType(Move(aListenerHolder), aType, flags); +} + +void +EventListenerManager::AddListenerForAllEvents(nsIDOMEventListener* aDOMListener, + bool aUseCapture, + bool aWantsUntrusted, + bool aSystemEventGroup) +{ + EventListenerFlags flags; + flags.mCapture = aUseCapture; + flags.mAllowUntrustedEvents = aWantsUntrusted; + flags.mInSystemGroup = aSystemEventGroup; + AddEventListenerInternal(EventListenerHolder(aDOMListener), eAllEvents, + nullptr, EmptyString(), flags, false, true); +} + +void +EventListenerManager::RemoveListenerForAllEvents( + nsIDOMEventListener* aDOMListener, + bool aUseCapture, + bool aSystemEventGroup) +{ + EventListenerFlags flags; + flags.mCapture = aUseCapture; + flags.mInSystemGroup = aSystemEventGroup; + RemoveEventListenerInternal(EventListenerHolder(aDOMListener), eAllEvents, + nullptr, EmptyString(), flags, true); +} + +bool +EventListenerManager::HasMutationListeners() +{ + if (mMayHaveMutationListeners) { + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &mListeners.ElementAt(i); + if (listener->mEventMessage >= eLegacyMutationEventFirst && + listener->mEventMessage <= eLegacyMutationEventLast) { + return true; + } + } + } + + return false; +} + +uint32_t +EventListenerManager::MutationListenerBits() +{ + uint32_t bits = 0; + if (mMayHaveMutationListeners) { + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &mListeners.ElementAt(i); + if (listener->mEventMessage >= eLegacyMutationEventFirst && + listener->mEventMessage <= eLegacyMutationEventLast) { + if (listener->mEventMessage == eLegacySubtreeModified) { + return kAllMutationBits; + } + bits |= MutationBitForEventType(listener->mEventMessage); + } + } + } + return bits; +} + +bool +EventListenerManager::HasListenersFor(const nsAString& aEventName) +{ + if (mIsMainThreadELM) { + nsCOMPtr<nsIAtom> atom = NS_Atomize(NS_LITERAL_STRING("on") + aEventName); + return HasListenersFor(atom); + } + + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &mListeners.ElementAt(i); + if (listener->mTypeString == aEventName) { + return true; + } + } + return false; +} + +bool +EventListenerManager::HasListenersFor(nsIAtom* aEventNameWithOn) +{ +#ifdef DEBUG + nsAutoString name; + aEventNameWithOn->ToString(name); +#endif + NS_ASSERTION(StringBeginsWith(name, NS_LITERAL_STRING("on")), + "Event name does not start with 'on'"); + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &mListeners.ElementAt(i); + if (listener->mTypeAtom == aEventNameWithOn) { + return true; + } + } + return false; +} + +bool +EventListenerManager::HasListeners() +{ + return !mListeners.IsEmpty(); +} + +nsresult +EventListenerManager::GetListenerInfo(nsCOMArray<nsIEventListenerInfo>* aList) +{ + nsCOMPtr<EventTarget> target = do_QueryInterface(mTarget); + NS_ENSURE_STATE(target); + aList->Clear(); + nsAutoTObserverArray<Listener, 2>::ForwardIterator iter(mListeners); + while (iter.HasMore()) { + const Listener& listener = iter.GetNext(); + // If this is a script handler and we haven't yet + // compiled the event handler itself go ahead and compile it + if (listener.mListenerType == Listener::eJSEventListener && + listener.mHandlerIsString) { + CompileEventHandlerInternal(const_cast<Listener*>(&listener), nullptr, + nullptr); + } + nsAutoString eventType; + if (listener.mAllEvents) { + eventType.SetIsVoid(true); + } else { + eventType.Assign(Substring(nsDependentAtomString(listener.mTypeAtom), 2)); + } + // EventListenerInfo is defined in XPCOM, so we have to go ahead + // and convert to an XPCOM callback here... + RefPtr<EventListenerInfo> info = + new EventListenerInfo(eventType, listener.mListener.ToXPCOMCallback(), + listener.mFlags.mCapture, + listener.mFlags.mAllowUntrustedEvents, + listener.mFlags.mInSystemGroup); + aList->AppendElement(info.forget()); + } + return NS_OK; +} + +bool +EventListenerManager::HasUnloadListeners() +{ + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &mListeners.ElementAt(i); + if (listener->mEventMessage == eUnload || + listener->mEventMessage == eBeforeUnload) { + return true; + } + } + return false; +} + +void +EventListenerManager::SetEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString, + EventHandlerNonNull* aHandler) +{ + if (!aHandler) { + RemoveEventHandler(aEventName, aTypeString); + return; + } + + // Untrusted events are always permitted for non-chrome script + // handlers. + SetEventHandlerInternal(aEventName, aTypeString, TypedEventHandler(aHandler), + !mIsMainThreadELM || + !nsContentUtils::IsCallerChrome()); +} + +void +EventListenerManager::SetEventHandler(OnErrorEventHandlerNonNull* aHandler) +{ + if (mIsMainThreadELM) { + if (!aHandler) { + RemoveEventHandler(nsGkAtoms::onerror, EmptyString()); + return; + } + + // Untrusted events are always permitted for non-chrome script + // handlers. + SetEventHandlerInternal(nsGkAtoms::onerror, EmptyString(), + TypedEventHandler(aHandler), + !nsContentUtils::IsCallerChrome()); + } else { + if (!aHandler) { + RemoveEventHandler(nullptr, NS_LITERAL_STRING("error")); + return; + } + + // Untrusted events are always permitted. + SetEventHandlerInternal(nullptr, NS_LITERAL_STRING("error"), + TypedEventHandler(aHandler), true); + } +} + +void +EventListenerManager::SetEventHandler( + OnBeforeUnloadEventHandlerNonNull* aHandler) +{ + if (!aHandler) { + RemoveEventHandler(nsGkAtoms::onbeforeunload, EmptyString()); + return; + } + + // Untrusted events are always permitted for non-chrome script + // handlers. + SetEventHandlerInternal(nsGkAtoms::onbeforeunload, EmptyString(), + TypedEventHandler(aHandler), + !mIsMainThreadELM || + !nsContentUtils::IsCallerChrome()); +} + +const TypedEventHandler* +EventListenerManager::GetTypedEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString) +{ + EventMessage eventMessage = nsContentUtils::GetEventMessage(aEventName); + Listener* listener = FindEventHandler(eventMessage, aEventName, aTypeString); + + if (!listener) { + return nullptr; + } + + JSEventHandler* jsEventHandler = listener->GetJSEventHandler(); + + if (listener->mHandlerIsString) { + CompileEventHandlerInternal(listener, nullptr, nullptr); + } + + const TypedEventHandler& typedHandler = + jsEventHandler->GetTypedEventHandler(); + return typedHandler.HasEventHandler() ? &typedHandler : nullptr; +} + +size_t +EventListenerManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + n += mListeners.ShallowSizeOfExcludingThis(aMallocSizeOf); + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + JSEventHandler* jsEventHandler = + mListeners.ElementAt(i).GetJSEventHandler(); + if (jsEventHandler) { + n += jsEventHandler->SizeOfIncludingThis(aMallocSizeOf); + } + } + return n; +} + +void +EventListenerManager::MarkForCC() +{ + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + const Listener& listener = mListeners.ElementAt(i); + JSEventHandler* jsEventHandler = listener.GetJSEventHandler(); + if (jsEventHandler) { + const TypedEventHandler& typedHandler = + jsEventHandler->GetTypedEventHandler(); + if (typedHandler.HasEventHandler()) { + typedHandler.Ptr()->MarkForCC(); + } + } else if (listener.mListenerType == Listener::eWrappedJSListener) { + xpc_TryUnmarkWrappedGrayObject(listener.mListener.GetXPCOMCallback()); + } else if (listener.mListenerType == Listener::eWebIDLListener) { + listener.mListener.GetWebIDLCallback()->MarkForCC(); + } + } + if (mRefCnt.IsPurple()) { + mRefCnt.RemovePurple(); + } +} + +void +EventListenerManager::TraceListeners(JSTracer* aTrc) +{ + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + const Listener& listener = mListeners.ElementAt(i); + JSEventHandler* jsEventHandler = listener.GetJSEventHandler(); + if (jsEventHandler) { + const TypedEventHandler& typedHandler = + jsEventHandler->GetTypedEventHandler(); + if (typedHandler.HasEventHandler()) { + mozilla::TraceScriptHolder(typedHandler.Ptr(), aTrc); + } + } else if (listener.mListenerType == Listener::eWebIDLListener) { + mozilla::TraceScriptHolder(listener.mListener.GetWebIDLCallback(), aTrc); + } + // We might have eWrappedJSListener, but that is the legacy type for + // JS implemented event listeners, and trickier to handle here. + } +} + +bool +EventListenerManager::HasApzAwareListeners() +{ + uint32_t count = mListeners.Length(); + for (uint32_t i = 0; i < count; ++i) { + Listener* listener = &mListeners.ElementAt(i); + if (IsApzAwareListener(listener)) { + return true; + } + } + return false; +} + +bool +EventListenerManager::IsApzAwareListener(Listener* aListener) +{ + return !aListener->mFlags.mPassive && IsApzAwareEvent(aListener->mTypeAtom); +} + +bool +EventListenerManager::IsApzAwareEvent(nsIAtom* aEvent) +{ + if (aEvent == nsGkAtoms::onwheel || + aEvent == nsGkAtoms::onDOMMouseScroll || + aEvent == nsHtml5Atoms::onmousewheel || + aEvent == nsGkAtoms::onMozMousePixelScroll) { + return true; + } + // In theory we should schedule a repaint if the touch event pref changes, + // because the event regions might be out of date. In practice that seems like + // overkill because users generally shouldn't be flipping this pref, much + // less expecting touch listeners on the page to immediately start preventing + // scrolling without so much as a repaint. Tests that we write can work + // around this constraint easily enough. + if (aEvent == nsGkAtoms::ontouchstart || + aEvent == nsGkAtoms::ontouchmove) { + return TouchEvent::PrefEnabled( + nsContentUtils::GetDocShellForEventTarget(mTarget)); + } + return false; +} + +already_AddRefed<nsIScriptGlobalObject> +EventListenerManager::GetScriptGlobalAndDocument(nsIDocument** aDoc) +{ + nsCOMPtr<nsINode> node(do_QueryInterface(mTarget)); + nsCOMPtr<nsIDocument> doc; + nsCOMPtr<nsIScriptGlobalObject> global; + if (node) { + // Try to get context from doc + // XXX sXBL/XBL2 issue -- do we really want the owner here? What + // if that's the XBL document? + doc = node->OwnerDoc(); + if (doc->IsLoadedAsData()) { + return nullptr; + } + + // We want to allow compiling an event handler even in an unloaded + // document, so use GetScopeObject here, not GetScriptHandlingObject. + global = do_QueryInterface(doc->GetScopeObject()); + } else { + if (nsCOMPtr<nsPIDOMWindowInner> win = GetTargetAsInnerWindow()) { + doc = win->GetExtantDoc(); + global = do_QueryInterface(win); + } else { + global = do_QueryInterface(mTarget); + } + } + + doc.forget(aDoc); + return global.forget(); +} + +} // namespace mozilla diff --git a/dom/events/EventListenerManager.h b/dom/events/EventListenerManager.h new file mode 100644 index 000000000..6b0927788 --- /dev/null +++ b/dom/events/EventListenerManager.h @@ -0,0 +1,658 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_EventListenerManager_h_ +#define mozilla_EventListenerManager_h_ + +#include "mozilla/BasicEvents.h" +#include "mozilla/dom/EventListenerBinding.h" +#include "mozilla/JSEventHandler.h" +#include "mozilla/MemoryReporting.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsGkAtoms.h" +#include "nsIDOMEventListener.h" +#include "nsTObserverArray.h" + +class nsIDocShell; +class nsIDOMEvent; +class nsIEventListenerInfo; +class nsPIDOMWindowInner; +class JSTracer; + +struct EventTypeData; + +template<class T> class nsCOMArray; + +namespace mozilla { + +class ELMCreationDetector; +class EventListenerManager; + +namespace dom { +class EventTarget; +class Element; +} // namespace dom + +typedef dom::CallbackObjectHolder<dom::EventListener, + nsIDOMEventListener> EventListenerHolder; + +struct EventListenerFlags +{ + friend class EventListenerManager; +private: + // If mListenerIsJSListener is true, the listener is implemented by JS. + // Otherwise, it's implemented by native code or JS but it's wrapped. + bool mListenerIsJSListener : 1; + +public: + // If mCapture is true, it means the listener captures the event. Otherwise, + // it's listening at bubbling phase. + bool mCapture : 1; + // If mInSystemGroup is true, the listener is listening to the events in the + // system group. + bool mInSystemGroup : 1; + // If mAllowUntrustedEvents is true, the listener is listening to the + // untrusted events too. + bool mAllowUntrustedEvents : 1; + // If mPassive is true, the listener will not be calling preventDefault on the + // event. (If it does call preventDefault, we should ignore it). + bool mPassive : 1; + // If mOnce is true, the listener will be removed from the manager before it + // is invoked, so that it would only be invoked once. + bool mOnce : 1; + + EventListenerFlags() : + mListenerIsJSListener(false), + mCapture(false), mInSystemGroup(false), mAllowUntrustedEvents(false), + mPassive(false), mOnce(false) + { + } + + bool EqualsForAddition(const EventListenerFlags& aOther) const + { + return (mCapture == aOther.mCapture && + mInSystemGroup == aOther.mInSystemGroup && + mListenerIsJSListener == aOther.mListenerIsJSListener && + mAllowUntrustedEvents == aOther.mAllowUntrustedEvents); + // Don't compare mPassive or mOnce + } + + bool EqualsForRemoval(const EventListenerFlags& aOther) const + { + return (mCapture == aOther.mCapture && + mInSystemGroup == aOther.mInSystemGroup && + mListenerIsJSListener == aOther.mListenerIsJSListener); + // Don't compare mAllowUntrustedEvents, mPassive, or mOnce + } +}; + +inline EventListenerFlags TrustedEventsAtBubble() +{ + EventListenerFlags flags; + return flags; +} + +inline EventListenerFlags TrustedEventsAtCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + return flags; +} + +inline EventListenerFlags AllEventsAtBubble() +{ + EventListenerFlags flags; + flags.mAllowUntrustedEvents = true; + return flags; +} + +inline EventListenerFlags AllEventsAtCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + flags.mAllowUntrustedEvents = true; + return flags; +} + +inline EventListenerFlags TrustedEventsAtSystemGroupBubble() +{ + EventListenerFlags flags; + flags.mInSystemGroup = true; + return flags; +} + +inline EventListenerFlags TrustedEventsAtSystemGroupCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + flags.mInSystemGroup = true; + return flags; +} + +inline EventListenerFlags AllEventsAtSystemGroupBubble() +{ + EventListenerFlags flags; + flags.mInSystemGroup = true; + flags.mAllowUntrustedEvents = true; + return flags; +} + +inline EventListenerFlags AllEventsAtSystemGroupCapture() +{ + EventListenerFlags flags; + flags.mCapture = true; + flags.mInSystemGroup = true; + flags.mAllowUntrustedEvents = true; + return flags; +} + +class EventListenerManagerBase +{ +protected: + EventListenerManagerBase(); + + EventMessage mNoListenerForEvent; + uint16_t mMayHavePaintEventListener : 1; + uint16_t mMayHaveMutationListeners : 1; + uint16_t mMayHaveCapturingListeners : 1; + uint16_t mMayHaveSystemGroupListeners : 1; + uint16_t mMayHaveTouchEventListener : 1; + uint16_t mMayHaveMouseEnterLeaveEventListener : 1; + uint16_t mMayHavePointerEnterLeaveEventListener : 1; + uint16_t mMayHaveKeyEventListener : 1; + uint16_t mMayHaveInputOrCompositionEventListener : 1; + uint16_t mClearingListeners : 1; + uint16_t mIsMainThreadELM : 1; + // uint16_t mUnused : 5; +}; + +/* + * Event listener manager + */ + +class EventListenerManager final : public EventListenerManagerBase +{ + ~EventListenerManager(); + +public: + struct Listener + { + EventListenerHolder mListener; + nsCOMPtr<nsIAtom> mTypeAtom; // for the main thread + nsString mTypeString; // for non-main-threads + EventMessage mEventMessage; + + enum ListenerType : uint8_t + { + eNoListener, + eNativeListener, + eJSEventListener, + eWrappedJSListener, + eWebIDLListener, + }; + ListenerType mListenerType; + + bool mListenerIsHandler : 1; + bool mHandlerIsString : 1; + bool mAllEvents : 1; + bool mIsChrome : 1; + + EventListenerFlags mFlags; + + JSEventHandler* GetJSEventHandler() const + { + return (mListenerType == eJSEventListener) ? + static_cast<JSEventHandler*>(mListener.GetXPCOMCallback()) : + nullptr; + } + + Listener() + : mEventMessage(eVoidEvent) + , mListenerType(eNoListener) + , mListenerIsHandler(false) + , mHandlerIsString(false) + , mAllEvents(false) + , mIsChrome(false) + { + } + + Listener(Listener&& aOther) + : mListener(Move(aOther.mListener)) + , mTypeAtom(aOther.mTypeAtom.forget()) + , mTypeString(aOther.mTypeString) + , mEventMessage(aOther.mEventMessage) + , mListenerType(aOther.mListenerType) + , mListenerIsHandler(aOther.mListenerIsHandler) + , mHandlerIsString(aOther.mHandlerIsString) + , mAllEvents(aOther.mAllEvents) + , mIsChrome(aOther.mIsChrome) + { + aOther.mTypeString.Truncate(); + aOther.mEventMessage = eVoidEvent; + aOther.mListenerType = eNoListener; + aOther.mListenerIsHandler = false; + aOther.mHandlerIsString = false; + aOther.mAllEvents = false; + aOther.mIsChrome = false; + } + + ~Listener() + { + if ((mListenerType == eJSEventListener) && mListener) { + static_cast<JSEventHandler*>( + mListener.GetXPCOMCallback())->Disconnect(); + } + } + + MOZ_ALWAYS_INLINE bool IsListening(const WidgetEvent* aEvent) const + { + if (mFlags.mInSystemGroup != aEvent->mFlags.mInSystemGroup) { + return false; + } + // FIXME Should check !mFlags.mCapture when the event is in target + // phase because capture phase event listeners should not be fired. + // But it breaks at least <xul:dialog>'s buttons. Bug 235441. + return ((mFlags.mCapture && aEvent->mFlags.mInCapturePhase) || + (!mFlags.mCapture && aEvent->mFlags.mInBubblingPhase)); + } + }; + + explicit EventListenerManager(dom::EventTarget* aTarget); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(EventListenerManager) + + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(EventListenerManager) + + void AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted) + { + AddEventListener(aType, EventListenerHolder(aListener), + aUseCapture, aWantsUntrusted); + } + void AddEventListener(const nsAString& aType, + dom::EventListener* aListener, + const dom::AddEventListenerOptionsOrBoolean& aOptions, + bool aWantsUntrusted) + { + AddEventListener(aType, EventListenerHolder(aListener), + aOptions, aWantsUntrusted); + } + void RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture) + { + RemoveEventListener(aType, EventListenerHolder(aListener), aUseCapture); + } + void RemoveEventListener(const nsAString& aType, + dom::EventListener* aListener, + const dom::EventListenerOptionsOrBoolean& aOptions) + { + RemoveEventListener(aType, EventListenerHolder(aListener), aOptions); + } + + void AddListenerForAllEvents(nsIDOMEventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted, + bool aSystemEventGroup); + void RemoveListenerForAllEvents(nsIDOMEventListener* aListener, + bool aUseCapture, + bool aSystemEventGroup); + + /** + * Sets events listeners of all types. + * @param an event listener + */ + void AddEventListenerByType(nsIDOMEventListener *aListener, + const nsAString& type, + const EventListenerFlags& aFlags) + { + AddEventListenerByType(EventListenerHolder(aListener), type, aFlags); + } + void AddEventListenerByType(EventListenerHolder aListener, + const nsAString& type, + const EventListenerFlags& aFlags); + void RemoveEventListenerByType(nsIDOMEventListener *aListener, + const nsAString& type, + const EventListenerFlags& aFlags) + { + RemoveEventListenerByType(EventListenerHolder(aListener), type, aFlags); + } + void RemoveEventListenerByType(EventListenerHolder aListener, + const nsAString& type, + const EventListenerFlags& aFlags); + + /** + * Sets the current "inline" event listener for aName to be a + * function compiled from aFunc if !aDeferCompilation. If + * aDeferCompilation, then we assume that we can get the string from + * mTarget later and compile lazily. + * + * aElement, if not null, is the element the string is associated with. + */ + // XXXbz does that play correctly with nodes being adopted across + // documents? Need to double-check the spec here. + nsresult SetEventHandler(nsIAtom *aName, + const nsAString& aFunc, + bool aDeferCompilation, + bool aPermitUntrustedEvents, + dom::Element* aElement); + /** + * Remove the current "inline" event listener for aName. + */ + void RemoveEventHandler(nsIAtom *aName, const nsAString& aTypeString); + + void HandleEvent(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent** aDOMEvent, + dom::EventTarget* aCurrentTarget, + nsEventStatus* aEventStatus) + { + if (mListeners.IsEmpty() || aEvent->PropagationStopped()) { + return; + } + + if (!mMayHaveCapturingListeners && !aEvent->mFlags.mInBubblingPhase) { + return; + } + + if (!mMayHaveSystemGroupListeners && aEvent->mFlags.mInSystemGroup) { + return; + } + + // Check if we already know that there is no event listener for the event. + if (mNoListenerForEvent == aEvent->mMessage && + (mNoListenerForEvent != eUnidentifiedEvent || + mNoListenerForEventAtom == aEvent->mSpecifiedEventType)) { + return; + } + HandleEventInternal(aPresContext, aEvent, aDOMEvent, aCurrentTarget, + aEventStatus); + } + + /** + * Tells the event listener manager that its target (which owns it) is + * no longer using it (and could go away). + */ + void Disconnect(); + + /** + * Allows us to quickly determine if we have mutation listeners registered. + */ + bool HasMutationListeners(); + + /** + * Allows us to quickly determine whether we have unload or beforeunload + * listeners registered. + */ + bool HasUnloadListeners(); + + /** + * Returns the mutation bits depending on which mutation listeners are + * registered to this listener manager. + * @note If a listener is an nsIDOMMutationListener, all possible mutation + * event bits are returned. All bits are also returned if one of the + * event listeners is registered to handle DOMSubtreeModified events. + */ + uint32_t MutationListenerBits(); + + /** + * Returns true if there is at least one event listener for aEventName. + */ + bool HasListenersFor(const nsAString& aEventName); + + /** + * Returns true if there is at least one event listener for aEventNameWithOn. + * Note that aEventNameWithOn must start with "on"! + */ + bool HasListenersFor(nsIAtom* aEventNameWithOn); + + /** + * Returns true if there is at least one event listener. + */ + bool HasListeners(); + + /** + * Sets aList to the list of nsIEventListenerInfo objects representing the + * listeners managed by this listener manager. + */ + nsresult GetListenerInfo(nsCOMArray<nsIEventListenerInfo>* aList); + + uint32_t GetIdentifierForEvent(nsIAtom* aEvent); + + static void Shutdown(); + + /** + * Returns true if there may be a paint event listener registered, + * false if there definitely isn't. + */ + bool MayHavePaintEventListener() { return mMayHavePaintEventListener; } + + /** + * Returns true if there may be a touch event listener registered, + * false if there definitely isn't. + */ + bool MayHaveTouchEventListener() { return mMayHaveTouchEventListener; } + + bool MayHaveMouseEnterLeaveEventListener() { return mMayHaveMouseEnterLeaveEventListener; } + bool MayHavePointerEnterLeaveEventListener() { return mMayHavePointerEnterLeaveEventListener; } + + /** + * Returns true if there may be a key event listener (keydown, keypress, + * or keyup) registered, or false if there definitely isn't. + */ + bool MayHaveKeyEventListener() { return mMayHaveKeyEventListener; } + + /** + * Returns true if there may be an advanced input event listener (input, + * compositionstart, compositionupdate, or compositionend) registered, + * or false if there definitely isn't. + */ + bool MayHaveInputOrCompositionEventListener() { return mMayHaveInputOrCompositionEventListener; } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + + uint32_t ListenerCount() const + { + return mListeners.Length(); + } + + void MarkForCC(); + + void TraceListeners(JSTracer* aTrc); + + dom::EventTarget* GetTarget() { return mTarget; } + + bool HasApzAwareListeners(); + bool IsApzAwareListener(Listener* aListener); + bool IsApzAwareEvent(nsIAtom* aEvent); + +protected: + void HandleEventInternal(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIDOMEvent** aDOMEvent, + dom::EventTarget* aCurrentTarget, + nsEventStatus* aEventStatus); + + nsresult HandleEventSubType(Listener* aListener, + nsIDOMEvent* aDOMEvent, + dom::EventTarget* aCurrentTarget); + + /** + * If the given EventMessage has a legacy version that we support, then this + * function returns that legacy version. Otherwise, this function simply + * returns the passed-in EventMessage. + */ + EventMessage GetLegacyEventMessage(EventMessage aEventMessage) const; + + void ProcessApzAwareEventListenerAdd(); + + /** + * Compile the "inline" event listener for aListener. The + * body of the listener can be provided in aBody; if this is null we + * will look for it on mTarget. If aBody is provided, aElement should be + * as well; otherwise it will also be inferred from mTarget. + */ + nsresult CompileEventHandlerInternal(Listener* aListener, + const nsAString* aBody, + dom::Element* aElement); + + /** + * Find the Listener for the "inline" event listener for aTypeAtom. + */ + Listener* FindEventHandler(EventMessage aEventMessage, + nsIAtom* aTypeAtom, + const nsAString& aTypeString); + + /** + * Set the "inline" event listener for aName to aHandler. aHandler may be + * have no actual handler set to indicate that we should lazily get and + * compile the string for this listener, but in that case aContext and + * aScopeGlobal must be non-null. Otherwise, aContext and aScopeGlobal are + * allowed to be null. + */ + Listener* SetEventHandlerInternal(nsIAtom* aName, + const nsAString& aTypeString, + const TypedEventHandler& aHandler, + bool aPermitUntrustedEvents); + + bool IsDeviceType(EventMessage aEventMessage); + void EnableDevice(EventMessage aEventMessage); + void DisableDevice(EventMessage aEventMessage); + +public: + /** + * Set the "inline" event listener for aEventName to aHandler. If + * aHandler is null, this will actually remove the event listener + */ + void SetEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString, + dom::EventHandlerNonNull* aHandler); + void SetEventHandler(dom::OnErrorEventHandlerNonNull* aHandler); + void SetEventHandler(dom::OnBeforeUnloadEventHandlerNonNull* aHandler); + + /** + * Get the value of the "inline" event listener for aEventName. + * This may cause lazy compilation if the listener is uncompiled. + * + * Note: It's the caller's responsibility to make sure to call the right one + * of these methods. In particular, "onerror" events use + * OnErrorEventHandlerNonNull for some event targets and EventHandlerNonNull + * for others. + */ + dom::EventHandlerNonNull* GetEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString) + { + const TypedEventHandler* typedHandler = + GetTypedEventHandler(aEventName, aTypeString); + return typedHandler ? typedHandler->NormalEventHandler() : nullptr; + } + + dom::OnErrorEventHandlerNonNull* GetOnErrorEventHandler() + { + const TypedEventHandler* typedHandler = mIsMainThreadELM ? + GetTypedEventHandler(nsGkAtoms::onerror, EmptyString()) : + GetTypedEventHandler(nullptr, NS_LITERAL_STRING("error")); + return typedHandler ? typedHandler->OnErrorEventHandler() : nullptr; + } + + dom::OnBeforeUnloadEventHandlerNonNull* GetOnBeforeUnloadEventHandler() + { + const TypedEventHandler* typedHandler = + GetTypedEventHandler(nsGkAtoms::onbeforeunload, EmptyString()); + return typedHandler ? typedHandler->OnBeforeUnloadEventHandler() : nullptr; + } + +protected: + /** + * Helper method for implementing the various Get*EventHandler above. Will + * return null if we don't have an event handler for this event name. + */ + const TypedEventHandler* GetTypedEventHandler(nsIAtom* aEventName, + const nsAString& aTypeString); + + void AddEventListener(const nsAString& aType, + EventListenerHolder aListener, + const dom::AddEventListenerOptionsOrBoolean& aOptions, + bool aWantsUntrusted); + void AddEventListener(const nsAString& aType, + EventListenerHolder aListener, + bool aUseCapture, + bool aWantsUntrusted); + void RemoveEventListener(const nsAString& aType, + EventListenerHolder aListener, + const dom::EventListenerOptionsOrBoolean& aOptions); + void RemoveEventListener(const nsAString& aType, + EventListenerHolder aListener, + bool aUseCapture); + + void AddEventListenerInternal(EventListenerHolder aListener, + EventMessage aEventMessage, + nsIAtom* aTypeAtom, + const nsAString& aTypeString, + const EventListenerFlags& aFlags, + bool aHandler = false, + bool aAllEvents = false); + void RemoveEventListenerInternal(EventListenerHolder aListener, + EventMessage aEventMessage, + nsIAtom* aUserType, + const nsAString& aTypeString, + const EventListenerFlags& aFlags, + bool aAllEvents = false); + void RemoveAllListeners(); + void NotifyEventListenerRemoved(nsIAtom* aUserType); + const EventTypeData* GetTypeDataForIID(const nsIID& aIID); + const EventTypeData* GetTypeDataForEventName(nsIAtom* aName); + nsPIDOMWindowInner* GetInnerWindowForTarget(); + already_AddRefed<nsPIDOMWindowInner> GetTargetAsInnerWindow() const; + + bool ListenerCanHandle(const Listener* aListener, + const WidgetEvent* aEvent, + EventMessage aEventMessage) const; + + // BE AWARE, a lot of instances of EventListenerManager will be created. + // Therefor, we need to keep this class compact. When you add integer + // members, please add them to EventListemerManagerBase and check the size + // at build time. + + already_AddRefed<nsIScriptGlobalObject> + GetScriptGlobalAndDocument(nsIDocument** aDoc); + + nsAutoTObserverArray<Listener, 2> mListeners; + dom::EventTarget* MOZ_NON_OWNING_REF mTarget; + nsCOMPtr<nsIAtom> mNoListenerForEventAtom; + + friend class ELMCreationDetector; + static uint32_t sMainThreadCreatedCount; +}; + +} // namespace mozilla + +/** + * NS_AddSystemEventListener() is a helper function for implementing + * EventTarget::AddSystemEventListener(). + */ +inline nsresult +NS_AddSystemEventListener(mozilla::dom::EventTarget* aTarget, + const nsAString& aType, + nsIDOMEventListener *aListener, + bool aUseCapture, + bool aWantsUntrusted) +{ + mozilla::EventListenerManager* listenerManager = + aTarget->GetOrCreateListenerManager(); + NS_ENSURE_STATE(listenerManager); + mozilla::EventListenerFlags flags; + flags.mInSystemGroup = true; + flags.mCapture = aUseCapture; + flags.mAllowUntrustedEvents = aWantsUntrusted; + listenerManager->AddEventListenerByType(aListener, aType, flags); + return NS_OK; +} + +#endif // mozilla_EventListenerManager_h_ diff --git a/dom/events/EventListenerService.cpp b/dom/events/EventListenerService.cpp new file mode 100644 index 000000000..18a61b149 --- /dev/null +++ b/dom/events/EventListenerService.cpp @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EventListenerService.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/JSEventHandler.h" +#include "mozilla/Maybe.h" +#include "nsCOMArray.h" +#include "nsDOMClassInfoID.h" +#include "nsIXPConnect.h" +#include "nsJSUtils.h" +#include "nsMemory.h" +#include "nsServiceManagerUtils.h" +#include "nsArray.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +using namespace dom; + +/****************************************************************************** + * mozilla::EventListenerChange + ******************************************************************************/ + +NS_IMPL_ISUPPORTS(EventListenerChange, nsIEventListenerChange) + +EventListenerChange::~EventListenerChange() +{ +} + +EventListenerChange::EventListenerChange(dom::EventTarget* aTarget) : + mTarget(aTarget) +{ + mChangedListenerNames = nsArrayBase::Create(); +} + +void +EventListenerChange::AddChangedListenerName(nsIAtom* aEventName) +{ + mChangedListenerNames->AppendElement(aEventName, false); +} + +NS_IMETHODIMP +EventListenerChange::GetTarget(nsIDOMEventTarget** aTarget) +{ + NS_ENSURE_ARG_POINTER(aTarget); + NS_ADDREF(*aTarget = mTarget); + return NS_OK; +} + +NS_IMETHODIMP +EventListenerChange::GetChangedListenerNames(nsIArray** aEventNames) +{ + NS_ENSURE_ARG_POINTER(aEventNames); + NS_ADDREF(*aEventNames = mChangedListenerNames); + return NS_OK; +} + +/****************************************************************************** + * mozilla::EventListenerInfo + ******************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION(EventListenerInfo, mListener) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventListenerInfo) + NS_INTERFACE_MAP_ENTRY(nsIEventListenerInfo) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(EventListenerInfo) +NS_IMPL_CYCLE_COLLECTING_RELEASE(EventListenerInfo) + +NS_IMETHODIMP +EventListenerInfo::GetType(nsAString& aType) +{ + aType = mType; + return NS_OK; +} + +NS_IMETHODIMP +EventListenerInfo::GetCapturing(bool* aCapturing) +{ + *aCapturing = mCapturing; + return NS_OK; +} + +NS_IMETHODIMP +EventListenerInfo::GetAllowsUntrusted(bool* aAllowsUntrusted) +{ + *aAllowsUntrusted = mAllowsUntrusted; + return NS_OK; +} + +NS_IMETHODIMP +EventListenerInfo::GetInSystemEventGroup(bool* aInSystemEventGroup) +{ + *aInSystemEventGroup = mInSystemEventGroup; + return NS_OK; +} + +NS_IMETHODIMP +EventListenerInfo::GetListenerObject(JSContext* aCx, + JS::MutableHandle<JS::Value> aObject) +{ + Maybe<JSAutoCompartment> ac; + GetJSVal(aCx, ac, aObject); + return NS_OK; +} + +/****************************************************************************** + * mozilla::EventListenerService + ******************************************************************************/ + +NS_IMPL_ISUPPORTS(EventListenerService, nsIEventListenerService) + +bool +EventListenerInfo::GetJSVal(JSContext* aCx, + Maybe<JSAutoCompartment>& aAc, + JS::MutableHandle<JS::Value> aJSVal) +{ + aJSVal.setNull(); + nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(mListener); + if (wrappedJS) { + JS::Rooted<JSObject*> object(aCx, wrappedJS->GetJSObject()); + if (!object) { + return false; + } + aAc.emplace(aCx, object); + aJSVal.setObject(*object); + return true; + } + + nsCOMPtr<JSEventHandler> jsHandler = do_QueryInterface(mListener); + if (jsHandler && jsHandler->GetTypedEventHandler().HasEventHandler()) { + JS::Handle<JSObject*> handler = + jsHandler->GetTypedEventHandler().Ptr()->Callable(); + if (handler) { + aAc.emplace(aCx, handler); + aJSVal.setObject(*handler); + return true; + } + } + return false; +} + +NS_IMETHODIMP +EventListenerInfo::ToSource(nsAString& aResult) +{ + aResult.SetIsVoid(true); + + AutoSafeJSContext cx; + Maybe<JSAutoCompartment> ac; + JS::Rooted<JS::Value> v(cx); + if (GetJSVal(cx, ac, &v)) { + JSString* str = JS_ValueToSource(cx, v); + if (str) { + nsAutoJSString autoStr; + if (autoStr.init(cx, str)) { + aResult.Assign(autoStr); + } + } + } + return NS_OK; +} + +EventListenerService* +EventListenerService::sInstance = nullptr; + +EventListenerService::EventListenerService() +{ + MOZ_ASSERT(!sInstance); + sInstance = this; +} + +EventListenerService::~EventListenerService() +{ + MOZ_ASSERT(sInstance == this); + sInstance = nullptr; +} + +NS_IMETHODIMP +EventListenerService::GetListenerInfoFor(nsIDOMEventTarget* aEventTarget, + uint32_t* aCount, + nsIEventListenerInfo*** aOutArray) +{ + NS_ENSURE_ARG_POINTER(aEventTarget); + *aCount = 0; + *aOutArray = nullptr; + nsCOMArray<nsIEventListenerInfo> listenerInfos; + + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aEventTarget); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_NO_INTERFACE); + + EventListenerManager* elm = eventTarget->GetExistingListenerManager(); + if (elm) { + elm->GetListenerInfo(&listenerInfos); + } + + int32_t count = listenerInfos.Count(); + if (count == 0) { + return NS_OK; + } + + *aOutArray = + static_cast<nsIEventListenerInfo**>( + moz_xmalloc(sizeof(nsIEventListenerInfo*) * count)); + NS_ENSURE_TRUE(*aOutArray, NS_ERROR_OUT_OF_MEMORY); + + for (int32_t i = 0; i < count; ++i) { + NS_ADDREF((*aOutArray)[i] = listenerInfos[i]); + } + *aCount = count; + return NS_OK; +} + +NS_IMETHODIMP +EventListenerService::GetEventTargetChainFor(nsIDOMEventTarget* aEventTarget, + bool aComposed, + uint32_t* aCount, + nsIDOMEventTarget*** aOutArray) +{ + *aCount = 0; + *aOutArray = nullptr; + NS_ENSURE_ARG(aEventTarget); + WidgetEvent event(true, eVoidEvent); + event.SetComposed(aComposed); + nsTArray<EventTarget*> targets; + nsresult rv = EventDispatcher::Dispatch(aEventTarget, nullptr, &event, + nullptr, nullptr, nullptr, &targets); + NS_ENSURE_SUCCESS(rv, rv); + int32_t count = targets.Length(); + if (count == 0) { + return NS_OK; + } + + *aOutArray = + static_cast<nsIDOMEventTarget**>( + moz_xmalloc(sizeof(nsIDOMEventTarget*) * count)); + NS_ENSURE_TRUE(*aOutArray, NS_ERROR_OUT_OF_MEMORY); + + for (int32_t i = 0; i < count; ++i) { + NS_ADDREF((*aOutArray)[i] = targets[i]); + } + *aCount = count; + + return NS_OK; +} + +NS_IMETHODIMP +EventListenerService::HasListenersFor(nsIDOMEventTarget* aEventTarget, + const nsAString& aType, + bool* aRetVal) +{ + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aEventTarget); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_NO_INTERFACE); + + EventListenerManager* elm = eventTarget->GetExistingListenerManager(); + *aRetVal = elm && elm->HasListenersFor(aType); + return NS_OK; +} + +NS_IMETHODIMP +EventListenerService::AddSystemEventListener(nsIDOMEventTarget *aTarget, + const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture) +{ + NS_PRECONDITION(aTarget, "Missing target"); + NS_PRECONDITION(aListener, "Missing listener"); + + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_NO_INTERFACE); + + EventListenerManager* manager = eventTarget->GetOrCreateListenerManager(); + NS_ENSURE_STATE(manager); + + EventListenerFlags flags = + aUseCapture ? TrustedEventsAtSystemGroupCapture() : + TrustedEventsAtSystemGroupBubble(); + manager->AddEventListenerByType(aListener, aType, flags); + return NS_OK; +} + +NS_IMETHODIMP +EventListenerService::RemoveSystemEventListener(nsIDOMEventTarget *aTarget, + const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture) +{ + NS_PRECONDITION(aTarget, "Missing target"); + NS_PRECONDITION(aListener, "Missing listener"); + + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_NO_INTERFACE); + + EventListenerManager* manager = eventTarget->GetExistingListenerManager(); + if (manager) { + EventListenerFlags flags = + aUseCapture ? TrustedEventsAtSystemGroupCapture() : + TrustedEventsAtSystemGroupBubble(); + manager->RemoveEventListenerByType(aListener, aType, flags); + } + + return NS_OK; +} + +NS_IMETHODIMP +EventListenerService::AddListenerForAllEvents(nsIDOMEventTarget* aTarget, + nsIDOMEventListener* aListener, + bool aUseCapture, + bool aWantsUntrusted, + bool aSystemEventGroup) +{ + NS_ENSURE_STATE(aTarget && aListener); + + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_NO_INTERFACE); + + EventListenerManager* manager = eventTarget->GetOrCreateListenerManager(); + NS_ENSURE_STATE(manager); + manager->AddListenerForAllEvents(aListener, aUseCapture, aWantsUntrusted, + aSystemEventGroup); + return NS_OK; +} + +NS_IMETHODIMP +EventListenerService::RemoveListenerForAllEvents(nsIDOMEventTarget* aTarget, + nsIDOMEventListener* aListener, + bool aUseCapture, + bool aSystemEventGroup) +{ + NS_ENSURE_STATE(aTarget && aListener); + + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_NO_INTERFACE); + + EventListenerManager* manager = eventTarget->GetExistingListenerManager(); + if (manager) { + manager->RemoveListenerForAllEvents(aListener, aUseCapture, aSystemEventGroup); + } + return NS_OK; +} + +NS_IMETHODIMP +EventListenerService::AddListenerChangeListener(nsIListenerChangeListener* aListener) +{ + if (!mChangeListeners.Contains(aListener)) { + mChangeListeners.AppendElement(aListener); + } + return NS_OK; +}; + +NS_IMETHODIMP +EventListenerService::RemoveListenerChangeListener(nsIListenerChangeListener* aListener) +{ + mChangeListeners.RemoveElement(aListener); + return NS_OK; +}; + +void +EventListenerService::NotifyAboutMainThreadListenerChangeInternal(dom::EventTarget* aTarget, + nsIAtom* aName) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (mChangeListeners.IsEmpty()) { + return; + } + + if (!mPendingListenerChanges) { + mPendingListenerChanges = nsArrayBase::Create(); + NS_DispatchToCurrentThread(NewRunnableMethod(this, + &EventListenerService::NotifyPendingChanges)); + } + + RefPtr<EventListenerChange> changes = mPendingListenerChangesSet.Get(aTarget); + if (!changes) { + changes = new EventListenerChange(aTarget); + mPendingListenerChanges->AppendElement(changes, false); + mPendingListenerChangesSet.Put(aTarget, changes); + } + changes->AddChangedListenerName(aName); +} + +void +EventListenerService::NotifyPendingChanges() +{ + nsCOMPtr<nsIMutableArray> changes; + mPendingListenerChanges.swap(changes); + mPendingListenerChangesSet.Clear(); + + nsTObserverArray<nsCOMPtr<nsIListenerChangeListener>>::EndLimitedIterator + iter(mChangeListeners); + while (iter.HasMore()) { + nsCOMPtr<nsIListenerChangeListener> listener = iter.GetNext(); + listener->ListenersChanged(changes); + } +} + +} // namespace mozilla + +nsresult +NS_NewEventListenerService(nsIEventListenerService** aResult) +{ + *aResult = new mozilla::EventListenerService(); + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/dom/events/EventListenerService.h b/dom/events/EventListenerService.h new file mode 100644 index 000000000..7c478fd01 --- /dev/null +++ b/dom/events/EventListenerService.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_EventListenerService_h_ +#define mozilla_EventListenerService_h_ + +#include "jsapi.h" +#include "mozilla/Attributes.h" +#include "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDOMEventListener.h" +#include "nsIEventListenerService.h" +#include "nsString.h" +#include "nsTObserverArray.h" +#include "nsDataHashtable.h" +#include "nsGkAtoms.h" + +class nsIMutableArray; + +namespace mozilla { +namespace dom { +class EventTarget; +} // namespace dom + +template<typename T> +class Maybe; + +class EventListenerChange final : public nsIEventListenerChange +{ +public: + explicit EventListenerChange(dom::EventTarget* aTarget); + + void AddChangedListenerName(nsIAtom* aEventName); + + NS_DECL_ISUPPORTS + NS_DECL_NSIEVENTLISTENERCHANGE + +protected: + virtual ~EventListenerChange(); + nsCOMPtr<dom::EventTarget> mTarget; + nsCOMPtr<nsIMutableArray> mChangedListenerNames; + +}; + +class EventListenerInfo final : public nsIEventListenerInfo +{ +public: + EventListenerInfo(const nsAString& aType, + already_AddRefed<nsIDOMEventListener> aListener, + bool aCapturing, + bool aAllowsUntrusted, + bool aInSystemEventGroup) + : mType(aType) + , mListener(aListener) + , mCapturing(aCapturing) + , mAllowsUntrusted(aAllowsUntrusted) + , mInSystemEventGroup(aInSystemEventGroup) + { + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(EventListenerInfo) + NS_DECL_NSIEVENTLISTENERINFO + +protected: + virtual ~EventListenerInfo() {} + + bool GetJSVal(JSContext* aCx, + Maybe<JSAutoCompartment>& aAc, + JS::MutableHandle<JS::Value> aJSVal); + + nsString mType; + // nsReftPtr because that is what nsListenerStruct uses too. + RefPtr<nsIDOMEventListener> mListener; + bool mCapturing; + bool mAllowsUntrusted; + bool mInSystemEventGroup; +}; + +class EventListenerService final : public nsIEventListenerService +{ + ~EventListenerService(); +public: + EventListenerService(); + NS_DECL_ISUPPORTS + NS_DECL_NSIEVENTLISTENERSERVICE + + static void NotifyAboutMainThreadListenerChange(dom::EventTarget* aTarget, + nsIAtom* aName) + { + if (sInstance) { + sInstance->NotifyAboutMainThreadListenerChangeInternal(aTarget, aName); + } + } + + void NotifyPendingChanges(); +private: + void NotifyAboutMainThreadListenerChangeInternal(dom::EventTarget* aTarget, + nsIAtom* aName); + nsTObserverArray<nsCOMPtr<nsIListenerChangeListener>> mChangeListeners; + nsCOMPtr<nsIMutableArray> mPendingListenerChanges; + nsDataHashtable<nsISupportsHashKey, RefPtr<EventListenerChange>> mPendingListenerChangesSet; + + static EventListenerService* sInstance; +}; + +} // namespace mozilla + +#endif // mozilla_EventListenerService_h_ diff --git a/dom/events/EventNameList.h b/dom/events/EventNameList.h new file mode 100644 index 000000000..b1be6dd76 --- /dev/null +++ b/dom/events/EventNameList.h @@ -0,0 +1,1108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file contains the list of event names that are exposed via IDL + * on various objects. It is designed to be used as inline input to + * various consumers through the magic of C preprocessing. + * + * Each entry consists of 4 pieces of information: + * 1) The name of the event + * 2) The event message + * 3) The event type (see the EventNameType enum in nsContentUtils.h) + * 4) The event struct type for this event. + * Items 2-4 might be empty strings for events for which they don't make sense. + * + * Event names that are exposed as content attributes on HTML elements + * and as IDL attributes on Elements, Documents and Windows and have + * no forwarding behavior should be enclosed in the EVENT macro. + * + * Event names that are exposed as content attributes on HTML elements + * and as IDL attributes on Elements, Documents and Windows and are + * forwarded from <body> and <frameset> to the Window should be + * enclosed in the FORWARDED_EVENT macro. If this macro is not + * defined, it will be defined to be equivalent to EVENT. + * + * Event names that are exposed as IDL attributes on Windows only + * should be enclosed in the WINDOW_ONLY_EVENT macro. If this macro + * is not defined, it will be defined to the empty string. + * + * Event names that are exposed as content and IDL attributes on + * <body> and <frameset>, which forward them to the Window, and are + * exposed as IDL attributes on the Window should be enclosed in the + * WINDOW_EVENT macro. If this macro is not defined, it will be + * defined to be equivalent to WINDOW_ONLY_EVENT. + * + * Touch-specific event names should be enclosed in TOUCH_EVENT. They + * are otherwise equivalent to those enclosed in EVENT. If + * TOUCH_EVENT is not defined, it will be defined to the empty string. + * + * Event names that are only exposed as IDL attributes on Documents + * should be enclosed in the DOCUMENT_ONLY_EVENT macro. If this macro is + * not defined, it will be defined to the empty string. + * + * Event names that are not exposed as IDL attributes at all should be + * enclosed in NON_IDL_EVENT. If NON_IDL_EVENT is not defined, it + * will be defined to the empty string. + * + * If you change which macros event names are enclosed in, please + * update the tests for bug 689564 and bug 659350 as needed. + */ + +#ifdef MESSAGE_TO_EVENT +#ifdef EVENT +#error "Don't define EVENT" +#endif /* EVENT */ +#ifdef WINDOW_ONLY_EVENT +#error "Don't define WINDOW_ONLY_EVENT" +#endif /* WINDOW_ONLY_EVENT */ +#ifdef TOUCH_EVENT +#error "Don't define TOUCH_EVENT" +#endif /* TOUCH_EVENT */ +#ifdef DOCUMENT_ONLY_EVENT +#error "Don't define DOCUMENT_ONLY_EVENT" +#endif /* DOCUMENT_ONLY_EVENT */ +#ifdef NON_IDL_EVENT +#error "Don't define NON_IDL_EVENT" +#endif /* NON_IDL_EVENT */ + +#define EVENT MESSAGE_TO_EVENT +#define WINDOW_ONLY_EVENT MESSAGE_TO_EVENT +#define TOUCH_EVENT MESSAGE_TO_EVENT +#define DOCUMENT_ONLY_EVENT MESSAGE_TO_EVENT +#define NON_IDL_EVENT MESSAGE_TO_EVENT +#endif /* MESSAGE_TO_EVENT */ + +#ifdef DEFINED_FORWARDED_EVENT +#error "Don't define DEFINED_FORWARDED_EVENT" +#endif /* DEFINED_FORWARDED_EVENT */ + +#ifndef FORWARDED_EVENT +#define FORWARDED_EVENT EVENT +#define DEFINED_FORWARDED_EVENT +#endif /* FORWARDED_EVENT */ + +#ifdef DEFINED_WINDOW_ONLY_EVENT +#error "Don't define DEFINED_WINDOW_ONLY_EVENT" +#endif /* DEFINED_WINDOW_ONLY_EVENT */ + +#ifndef WINDOW_ONLY_EVENT +#define WINDOW_ONLY_EVENT(_name, _message, _type, _struct) +#define DEFINED_WINDOW_ONLY_EVENT +#endif /* WINDOW_ONLY_EVENT */ + +#ifdef DEFINED_WINDOW_EVENT +#error "Don't define DEFINED_WINDOW_EVENT" +#endif /* DEFINED_WINDOW_EVENT */ + +#ifndef WINDOW_EVENT +#define WINDOW_EVENT WINDOW_ONLY_EVENT +#define DEFINED_WINDOW_EVENT +#endif /* WINDOW_EVENT */ + +#ifdef DEFINED_TOUCH_EVENT +#error "Don't define DEFINED_TOUCH_EVENT" +#endif /* DEFINED_TOUCH_EVENT */ + +#ifndef TOUCH_EVENT +#define TOUCH_EVENT(_name, _message, _type, _struct) +#define DEFINED_TOUCH_EVENT +#endif /* TOUCH_EVENT */ + +#ifdef DEFINED_DOCUMENT_ONLY_EVENT +#error "Don't define DEFINED_DOCUMENT_ONLY_EVENT" +#endif /* DEFINED_DOCUMENT_ONLY_EVENT */ + +#ifndef DOCUMENT_ONLY_EVENT +#define DOCUMENT_ONLY_EVENT(_name, _message, _type, _struct) +#define DEFINED_DOCUMENT_ONLY_EVENT +#endif /* DOCUMENT_ONLY_EVENT */ + +#ifdef DEFINED_NON_IDL_EVENT +#error "Don't define DEFINED_NON_IDL_EVENT" +#endif /* DEFINED_NON_IDL_EVENT */ + +#ifndef NON_IDL_EVENT +#define NON_IDL_EVENT(_name, _message, _type, _struct) +#define DEFINED_NON_IDL_EVENT +#endif /* NON_IDL_EVENT */ + +#ifdef DEFINED_ERROR_EVENT +#error "Don't define DEFINED_ERROR_EVENT" +#endif /* DEFINED_ERROR_EVENT */ + +#ifndef ERROR_EVENT +#define ERROR_EVENT FORWARDED_EVENT +#define DEFINED_ERROR_EVENT +#endif /* ERROR_EVENT */ + +#ifdef DEFINED_BEFOREUNLOAD_EVENT +#error "Don't define DEFINED_BEFOREUNLOAD_EVENT" +#endif /* DEFINED_BEFOREUNLOAD_EVENT */ + +#ifndef BEFOREUNLOAD_EVENT +#define BEFOREUNLOAD_EVENT WINDOW_EVENT +#define DEFINED_BEFOREUNLOAD_EVENT +#endif /* BEFOREUNLOAD_EVENT */ + +EVENT(abort, + eImageAbort, + EventNameType_All, + eBasicEventClass) +EVENT(canplay, + eCanPlay, + EventNameType_HTML, + eBasicEventClass) +EVENT(canplaythrough, + eCanPlayThrough, + EventNameType_HTML, + eBasicEventClass) +EVENT(change, + eFormChange, + EventNameType_HTMLXUL, + eBasicEventClass) +EVENT(click, + eMouseClick, + EventNameType_All, + eMouseEventClass) +EVENT(contextmenu, + eContextMenu, + EventNameType_HTMLXUL, + eMouseEventClass) +NON_IDL_EVENT(mouselongtap, + eMouseLongTap, + EventNameType_HTMLXUL, + eMouseEventClass) +// Not supported yet +// EVENT(cuechange) +EVENT(dblclick, + eMouseDoubleClick, + EventNameType_HTMLXUL, + eMouseEventClass) +EVENT(drag, + eDrag, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(dragend, + eDragEnd, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(dragenter, + eDragEnter, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(dragexit, + eDragExit, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(dragleave, + eDragLeave, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(dragover, + eDragOver, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(dragstart, + eDragStart, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(drop, + eDrop, + EventNameType_HTMLXUL, + eDragEventClass) +EVENT(durationchange, + eDurationChange, + EventNameType_HTML, + eBasicEventClass) +EVENT(emptied, + eEmptied, + EventNameType_HTML, + eBasicEventClass) +EVENT(ended, + eEnded, + EventNameType_HTML, + eBasicEventClass) +EVENT(fullscreenchange, + eFullscreenChange, + EventNameType_HTML, + eBasicEventClass) +EVENT(fullscreenerror, + eFullscreenError, + EventNameType_HTML, + eBasicEventClass) +EVENT(input, + eEditorInput, + EventNameType_HTMLXUL, + eEditorInputEventClass) +EVENT(invalid, + eFormInvalid, + EventNameType_HTMLXUL, + eBasicEventClass) +EVENT(keydown, + eKeyDown, + EventNameType_HTMLXUL, + eKeyboardEventClass) +EVENT(keypress, + eKeyPress, + EventNameType_HTMLXUL, + eKeyboardEventClass) +EVENT(keyup, + eKeyUp, + EventNameType_HTMLXUL, + eKeyboardEventClass) +EVENT(mozkeydownonplugin, + eKeyDownOnPlugin, + EventNameType_None, + eKeyboardEventClass) +EVENT(mozkeyuponplugin, + eKeyUpOnPlugin, + EventNameType_None, + eKeyboardEventClass) +NON_IDL_EVENT(mozbrowserbeforekeydown, + eBeforeKeyDown, + EventNameType_None, + eBeforeAfterKeyboardEventClass) +NON_IDL_EVENT(mozbrowserafterkeydown, + eAfterKeyDown, + EventNameType_None, + eBeforeAfterKeyboardEventClass) +NON_IDL_EVENT(mozbrowserbeforekeyup, + eBeforeKeyUp, + EventNameType_None, + eBeforeAfterKeyboardEventClass) +NON_IDL_EVENT(mozbrowserafterkeyup, + eAfterKeyUp, + EventNameType_None, + eBeforeAfterKeyboardEventClass) +NON_IDL_EVENT(mozaccesskeynotfound, + eAccessKeyNotFound, + EventNameType_None, + eKeyboardEventClass) +EVENT(loadeddata, + eLoadedData, + EventNameType_HTML, + eBasicEventClass) +EVENT(loadedmetadata, + eLoadedMetaData, + EventNameType_HTML, + eBasicEventClass) +EVENT(loadend, + eLoadEnd, + EventNameType_HTML, + eBasicEventClass) +EVENT(loadstart, + eLoadStart, + EventNameType_HTML, + eBasicEventClass) +EVENT(mousedown, + eMouseDown, + EventNameType_All, + eMouseEventClass) +EVENT(mouseenter, + eMouseEnter, + EventNameType_All, + eMouseEventClass) +EVENT(mouseleave, + eMouseLeave, + EventNameType_All, + eMouseEventClass) +EVENT(mousemove, + eMouseMove, + EventNameType_All, + eMouseEventClass) +EVENT(mouseout, + eMouseOut, + EventNameType_All, + eMouseEventClass) +EVENT(mouseover, + eMouseOver, + EventNameType_All, + eMouseEventClass) +EVENT(mouseup, + eMouseUp, + EventNameType_All, + eMouseEventClass) +EVENT(mozfullscreenchange, + eMozFullscreenChange, + EventNameType_HTML, + eBasicEventClass) +EVENT(mozfullscreenerror, + eMozFullscreenError, + EventNameType_HTML, + eBasicEventClass) +EVENT(mozpointerlockchange, + eMozPointerLockChange, + EventNameType_HTML, + eBasicEventClass) +EVENT(mozpointerlockerror, + eMozPointerLockError, + EventNameType_HTML, + eBasicEventClass) +EVENT(pointerlockchange, + ePointerLockChange, + EventNameType_HTML, + eBasicEventClass) +EVENT(pointerlockerror, + ePointerLockError, + EventNameType_HTML, + eBasicEventClass) +EVENT(pointerdown, + ePointerDown, + EventNameType_All, + ePointerEventClass) +EVENT(pointermove, + ePointerMove, + EventNameType_All, + ePointerEventClass) +EVENT(pointerup, + ePointerUp, + EventNameType_All, + ePointerEventClass) +EVENT(pointercancel, + ePointerCancel, + EventNameType_All, + ePointerEventClass) +EVENT(pointerover, + ePointerOver, + EventNameType_All, + ePointerEventClass) +EVENT(pointerout, + ePointerOut, + EventNameType_All, + ePointerEventClass) +EVENT(pointerenter, + ePointerEnter, + EventNameType_All, + ePointerEventClass) +EVENT(pointerleave, + ePointerLeave, + EventNameType_All, + ePointerEventClass) +EVENT(gotpointercapture, + ePointerGotCapture, + EventNameType_All, + ePointerEventClass) +EVENT(lostpointercapture, + ePointerLostCapture, + EventNameType_All, + ePointerEventClass) +EVENT(selectstart, + eSelectStart, + EventNameType_HTMLXUL, + eBasicEventClass) + +// Not supported yet; probably never because "wheel" is a better idea. +// EVENT(mousewheel) +EVENT(pause, + ePause, + EventNameType_HTML, + eBasicEventClass) +EVENT(play, + ePlay, + EventNameType_HTML, + eBasicEventClass) +EVENT(playing, + ePlaying, + EventNameType_HTML, + eBasicEventClass) +EVENT(progress, + eProgress, + EventNameType_HTML, + eBasicEventClass) +EVENT(ratechange, + eRateChange, + EventNameType_HTML, + eBasicEventClass) +EVENT(reset, + eFormReset, + EventNameType_HTMLXUL, + eBasicEventClass) +EVENT(seeked, + eSeeked, + EventNameType_HTML, + eBasicEventClass) +EVENT(seeking, + eSeeking, + EventNameType_HTML, + eBasicEventClass) +EVENT(select, + eFormSelect, + EventNameType_HTMLXUL, + eBasicEventClass) +EVENT(show, + eShow, + EventNameType_HTML, + eBasicEventClass) +EVENT(stalled, + eStalled, + EventNameType_HTML, + eBasicEventClass) +EVENT(submit, + eFormSubmit, + EventNameType_HTMLXUL, + eBasicEventClass) +EVENT(suspend, + eSuspend, + EventNameType_HTML, + eBasicEventClass) +EVENT(timeupdate, + eTimeUpdate, + EventNameType_HTML, + eBasicEventClass) +EVENT(toggle, + eToggle, + EventNameType_HTML, + eBasicEventClass) +EVENT(volumechange, + eVolumeChange, + EventNameType_HTML, + eBasicEventClass) +EVENT(waiting, + eWaiting, + EventNameType_HTML, + eBasicEventClass) +EVENT(wheel, + eWheel, + EventNameType_All, + eWheelEventClass) +EVENT(copy, + eCopy, + EventNameType_HTMLXUL, + eClipboardEventClass) +EVENT(cut, + eCut, + EventNameType_HTMLXUL, + eClipboardEventClass) +EVENT(paste, + ePaste, + EventNameType_HTMLXUL, + eClipboardEventClass) +// Gecko-specific extensions that apply to elements +EVENT(beforescriptexecute, + eBeforeScriptExecute, + EventNameType_HTMLXUL, + eBasicEventClass) +EVENT(afterscriptexecute, + eAfterScriptExecute, + EventNameType_HTMLXUL, + eBasicEventClass) + +FORWARDED_EVENT(blur, + eBlur, + EventNameType_HTMLXUL, + eFocusEventClass) +ERROR_EVENT(error, + eLoadError, + EventNameType_All, + eBasicEventClass) +FORWARDED_EVENT(focus, + eFocus, + EventNameType_HTMLXUL, + eFocusEventClass) +FORWARDED_EVENT(focusin, + eFocusIn, + EventNameType_HTMLXUL, + eFocusEventClass) +FORWARDED_EVENT(focusout, + eFocusOut, + EventNameType_HTMLXUL, + eFocusEventClass) +FORWARDED_EVENT(load, + eLoad, + EventNameType_All, + eBasicEventClass) +FORWARDED_EVENT(resize, + eResize, + EventNameType_All, + eBasicEventClass) +FORWARDED_EVENT(scroll, + eScroll, + (EventNameType_HTMLXUL | EventNameType_SVGSVG), + eBasicEventClass) + +WINDOW_EVENT(afterprint, + eAfterPrint, + EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +WINDOW_EVENT(beforeprint, + eBeforePrint, + EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +BEFOREUNLOAD_EVENT(beforeunload, + eBeforeUnload, + EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +WINDOW_EVENT(hashchange, + eHashChange, + EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +WINDOW_EVENT(languagechange, + eLanguageChange, + EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +// XXXbz Should the onmessage attribute on <body> really not work? If so, do we +// need a different macro to flag things like that (IDL, but not content +// attributes on body/frameset), or is just using EventNameType_None enough? +WINDOW_EVENT(message, + eMessage, + EventNameType_None, + eBasicEventClass) +WINDOW_EVENT(offline, + eOffline, + EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +WINDOW_EVENT(online, + eOnline, + EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) +WINDOW_EVENT(orientationchange, + eOrientationChange, + EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +#endif +WINDOW_EVENT(pagehide, + ePageHide, + EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +WINDOW_EVENT(pageshow, + ePageShow, + EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +WINDOW_EVENT(popstate, + ePopState, + EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +// Not supported yet +// WINDOW_EVENT(redo) +WINDOW_EVENT(storage, + eStorage, + EventNameType_HTMLBodyOrFramesetOnly, + eBasicEventClass) +// Not supported yet +// WINDOW_EVENT(undo) +WINDOW_EVENT(unload, + eUnload, + (EventNameType_XUL | EventNameType_SVGSVG | + EventNameType_HTMLBodyOrFramesetOnly), + eBasicEventClass) + +WINDOW_ONLY_EVENT(devicemotion, + eDeviceMotion, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(deviceorientation, + eDeviceOrientation, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(absolutedeviceorientation, + eAbsoluteDeviceOrientation, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(deviceproximity, + eDeviceProximity, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(userproximity, + eUserProximity, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(devicelight, + eDeviceLight, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(vrdisplayconnect, + eVRDisplayConnect, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(vrdisplaydisconnect, + eVRDisplayDisconnect, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(vrdisplaypresentchange, + eVRDisplayPresentChange, + EventNameType_None, + eBasicEventClass) +// Install events as per W3C Manifest spec +WINDOW_ONLY_EVENT(appinstalled, + eAppInstalled, + EventNameType_None, + eBasicEventClass) + + +#ifdef MOZ_B2G +WINDOW_ONLY_EVENT(moztimechange, + eTimeChange, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(moznetworkupload, + eNetworkUpload, + EventNameType_None, + eBasicEventClass) +WINDOW_ONLY_EVENT(moznetworkdownload, + eNetworkDownload, + EventNameType_None, + eBasicEventClass) +#endif // MOZ_B2G + +TOUCH_EVENT(touchstart, + eTouchStart, + EventNameType_All, + eTouchEventClass) +TOUCH_EVENT(touchend, + eTouchEnd, + EventNameType_All, + eTouchEventClass) +TOUCH_EVENT(touchmove, + eTouchMove, + EventNameType_All, + eTouchEventClass ) +TOUCH_EVENT(touchcancel, + eTouchCancel, + EventNameType_All, + eTouchEventClass) + +DOCUMENT_ONLY_EVENT(readystatechange, + eReadyStateChange, + EventNameType_HTMLXUL, + eBasicEventClass) +DOCUMENT_ONLY_EVENT(selectionchange, + eSelectionChange, + EventNameType_HTMLXUL, + eBasicEventClass) + +NON_IDL_EVENT(MozMouseHittest, + eMouseHitTest, + EventNameType_None, + eMouseEventClass) + +NON_IDL_EVENT(DOMAttrModified, + eLegacyAttrModified, + EventNameType_HTMLXUL, + eMutationEventClass) +NON_IDL_EVENT(DOMCharacterDataModified, + eLegacyCharacterDataModified, + EventNameType_HTMLXUL, + eMutationEventClass) +NON_IDL_EVENT(DOMNodeInserted, + eLegacyNodeInserted, + EventNameType_HTMLXUL, + eMutationEventClass) +NON_IDL_EVENT(DOMNodeRemoved, + eLegacyNodeRemoved, + EventNameType_HTMLXUL, + eMutationEventClass) +NON_IDL_EVENT(DOMNodeInsertedIntoDocument, + eLegacyNodeInsertedIntoDocument, + EventNameType_HTMLXUL, + eMutationEventClass) +NON_IDL_EVENT(DOMNodeRemovedFromDocument, + eLegacyNodeRemovedFromDocument, + EventNameType_HTMLXUL, + eMutationEventClass) +NON_IDL_EVENT(DOMSubtreeModified, + eLegacySubtreeModified, + EventNameType_HTMLXUL, + eMutationEventClass) + +NON_IDL_EVENT(DOMActivate, + eLegacyDOMActivate, + EventNameType_HTMLXUL, + eUIEventClass) +NON_IDL_EVENT(DOMFocusIn, + eLegacyDOMFocusIn, + EventNameType_HTMLXUL, + eUIEventClass) +NON_IDL_EVENT(DOMFocusOut, + eLegacyDOMFocusOut, + EventNameType_HTMLXUL, + eUIEventClass) + +NON_IDL_EVENT(DOMMouseScroll, + eLegacyMouseLineOrPageScroll, + EventNameType_HTMLXUL, + eMouseScrollEventClass) +NON_IDL_EVENT(MozMousePixelScroll, + eLegacyMousePixelScroll, + EventNameType_HTMLXUL, + eMouseScrollEventClass) + +NON_IDL_EVENT(open, + eOpen, + EventNameType_None, + eBasicEventClass) + +NON_IDL_EVENT(dataavailable, + eMediaRecorderDataAvailable, + EventNameType_None, + eBasicEventClass) + +NON_IDL_EVENT(stop, + eMediaRecorderStop, + EventNameType_None, + eBasicEventClass) + +NON_IDL_EVENT(warning, + eMediaRecorderWarning, + EventNameType_None, + eBasicEventClass) + +NON_IDL_EVENT(speakerforcedchange, + eSpeakerForcedChange, + EventNameType_None, + eBasicEventClass) + +// Events that only have on* attributes on XUL elements + + // "text" event is legacy event for modifying composition string in EditorBase. + // This shouldn't be used by web/xul apps. "compositionupdate" should be + // used instead. +NON_IDL_EVENT(text, + eCompositionChange, + EventNameType_XUL, + eCompositionEventClass) +NON_IDL_EVENT(compositionstart, + eCompositionStart, + EventNameType_XUL, + eCompositionEventClass) +NON_IDL_EVENT(compositionupdate, + eCompositionUpdate, + EventNameType_XUL, + eCompositionEventClass) +NON_IDL_EVENT(compositionend, + eCompositionEnd, + EventNameType_XUL, + eCompositionEventClass) +NON_IDL_EVENT(command, + eXULCommand, + EventNameType_XUL, + eInputEventClass) +NON_IDL_EVENT(close, + eWindowClose, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(popupshowing, + eXULPopupShowing, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(popupshown, + eXULPopupShown, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(popuppositioned, + eXULPopupPositioned, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(popuphiding, + eXULPopupHiding, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(popuphidden, + eXULPopupHidden, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(broadcast, + eXULBroadcast, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(commandupdate, + eXULCommandUpdate, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(overflow, + eScrollPortOverflow, + EventNameType_XUL, + eBasicEventClass) +NON_IDL_EVENT(underflow, + eScrollPortUnderflow, + EventNameType_XUL, + eBasicEventClass) + +// Various SVG events +NON_IDL_EVENT(SVGLoad, + eSVGLoad, + EventNameType_None, + eBasicEventClass) +NON_IDL_EVENT(SVGUnload, + eSVGUnload, + EventNameType_None, + eBasicEventClass) +NON_IDL_EVENT(SVGResize, + eSVGResize, + EventNameType_None, + eBasicEventClass) +NON_IDL_EVENT(SVGScroll, + eSVGScroll, + EventNameType_None, + eBasicEventClass) + +NON_IDL_EVENT(SVGZoom, + eSVGZoom, + EventNameType_None, + eSVGZoomEventClass) + +// Only map the ID to the real event name when MESSAGE_TO_EVENT is defined. +#ifndef MESSAGE_TO_EVENT +// This is a bit hackish, but SVG's event names are weird. +NON_IDL_EVENT(zoom, + eSVGZoom, + EventNameType_SVGSVG, + eBasicEventClass) +#endif +// Only map the ID to the real event name when MESSAGE_TO_EVENT is defined. +#ifndef MESSAGE_TO_EVENT +NON_IDL_EVENT(begin, + eSMILBeginEvent, + EventNameType_SMIL, + eBasicEventClass) +#endif +NON_IDL_EVENT(beginEvent, + eSMILBeginEvent, + EventNameType_None, + eSMILTimeEventClass) +// Only map the ID to the real event name when MESSAGE_TO_EVENT is defined. +#ifndef MESSAGE_TO_EVENT +NON_IDL_EVENT(end, + eSMILEndEvent, + EventNameType_SMIL, + eBasicEventClass) +#endif +NON_IDL_EVENT(endEvent, + eSMILEndEvent, + EventNameType_None, + eSMILTimeEventClass) +// Only map the ID to the real event name when MESSAGE_TO_EVENT is defined. +#ifndef MESSAGE_TO_EVENT +NON_IDL_EVENT(repeat, + eSMILRepeatEvent, + EventNameType_SMIL, + eBasicEventClass) +#endif +NON_IDL_EVENT(repeatEvent, + eSMILRepeatEvent, + EventNameType_None, + eSMILTimeEventClass) + +NON_IDL_EVENT(MozAfterPaint, + eAfterPaint, + EventNameType_None, + eBasicEventClass) + +NON_IDL_EVENT(MozScrolledAreaChanged, + eScrolledAreaChanged, + EventNameType_None, + eScrollAreaEventClass) + +#ifdef MOZ_GAMEPAD +NON_IDL_EVENT(gamepadbuttondown, + eGamepadButtonDown, + EventNameType_None, + eBasicEventClass) +NON_IDL_EVENT(gamepadbuttonup, + eGamepadButtonUp, + EventNameType_None, + eBasicEventClass) +NON_IDL_EVENT(gamepadaxismove, + eGamepadAxisMove, + EventNameType_None, + eBasicEventClass) +NON_IDL_EVENT(gamepadconnected, + eGamepadConnected, + EventNameType_None, + eBasicEventClass) +NON_IDL_EVENT(gamepaddisconnected, + eGamepadDisconnected, + EventNameType_None, + eBasicEventClass) +#endif + +// Simple gesture events +NON_IDL_EVENT(MozSwipeGestureMayStart, + eSwipeGestureMayStart, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozSwipeGestureStart, + eSwipeGestureStart, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozSwipeGestureUpdate, + eSwipeGestureUpdate, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozSwipeGestureEnd, + eSwipeGestureEnd, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozSwipeGesture, + eSwipeGesture, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozMagnifyGestureStart, + eMagnifyGestureStart, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozMagnifyGestureUpdate, + eMagnifyGestureUpdate, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozMagnifyGesture, + eMagnifyGesture, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozRotateGestureStart, + eRotateGestureStart, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozRotateGestureUpdate, + eRotateGestureUpdate, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozRotateGesture, + eRotateGesture, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozTapGesture, + eTapGesture, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozPressTapGesture, + ePressTapGesture, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozEdgeUIStarted, + eEdgeUIStarted, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozEdgeUICanceled, + eEdgeUICanceled, + EventNameType_None, + eSimpleGestureEventClass) +NON_IDL_EVENT(MozEdgeUICompleted, + eEdgeUICompleted, + EventNameType_None, + eSimpleGestureEventClass) + +// CSS Transition & Animation events: +EVENT(transitionstart, + eTransitionStart, + EventNameType_All, + eTransitionEventClass) +EVENT(transitionrun, + eTransitionRun, + EventNameType_All, + eTransitionEventClass) +EVENT(transitionend, + eTransitionEnd, + EventNameType_All, + eTransitionEventClass) +EVENT(animationstart, + eAnimationStart, + EventNameType_All, + eAnimationEventClass) +EVENT(animationend, + eAnimationEnd, + EventNameType_All, + eAnimationEventClass) +EVENT(animationiteration, + eAnimationIteration, + EventNameType_All, + eAnimationEventClass) + +// Webkit-prefixed versions of Transition & Animation events, for web compat: +EVENT(webkitAnimationEnd, + eWebkitAnimationEnd, + EventNameType_All, + eAnimationEventClass) +EVENT(webkitAnimationIteration, + eWebkitAnimationIteration, + EventNameType_All, + eAnimationEventClass) +EVENT(webkitAnimationStart, + eWebkitAnimationStart, + EventNameType_All, + eAnimationEventClass) +EVENT(webkitTransitionEnd, + eWebkitTransitionEnd, + EventNameType_All, + eTransitionEventClass) +#ifndef MESSAGE_TO_EVENT +EVENT(webkitanimationend, + eWebkitAnimationEnd, + EventNameType_All, + eAnimationEventClass) +EVENT(webkitanimationiteration, + eWebkitAnimationIteration, + EventNameType_All, + eAnimationEventClass) +EVENT(webkitanimationstart, + eWebkitAnimationStart, + EventNameType_All, + eAnimationEventClass) +EVENT(webkittransitionend, + eWebkitTransitionEnd, + EventNameType_All, + eTransitionEventClass) +#endif + +NON_IDL_EVENT(audioprocess, + eAudioProcess, + EventNameType_None, + eBasicEventClass) + +NON_IDL_EVENT(complete, + eAudioComplete, + EventNameType_None, + eBasicEventClass) + +#ifdef DEFINED_FORWARDED_EVENT +#undef DEFINED_FORWARDED_EVENT +#undef FORWARDED_EVENT +#endif /* DEFINED_FORWARDED_EVENT */ + +#ifdef DEFINED_WINDOW_EVENT +#undef DEFINED_WINDOW_EVENT +#undef WINDOW_EVENT +#endif /* DEFINED_WINDOW_EVENT */ + +#ifdef DEFINED_WINDOW_ONLY_EVENT +#undef DEFINED_WINDOW_ONLY_EVENT +#undef WINDOW_ONLY_EVENT +#endif /* DEFINED_WINDOW_ONLY_EVENT */ + +#ifdef DEFINED_TOUCH_EVENT +#undef DEFINED_TOUCH_EVENT +#undef TOUCH_EVENT +#endif /* DEFINED_TOUCH_EVENT */ + +#ifdef DEFINED_DOCUMENT_ONLY_EVENT +#undef DEFINED_DOCUMENT_ONLY_EVENT +#undef DOCUMENT_ONLY_EVENT +#endif /* DEFINED_DOCUMENT_ONLY_EVENT */ + +#ifdef DEFINED_NON_IDL_EVENT +#undef DEFINED_NON_IDL_EVENT +#undef NON_IDL_EVENT +#endif /* DEFINED_NON_IDL_EVENT */ + +#ifdef DEFINED_ERROR_EVENT +#undef DEFINED_ERROR_EVENT +#undef ERROR_EVENT +#endif /* DEFINED_ERROR_EVENT */ + +#ifdef DEFINED_BEFOREUNLOAD_EVENT +#undef DEFINED_BEFOREUNLOAD_EVENT +#undef BEFOREUNLOAD_EVENT +#endif /* BEFOREUNLOAD_EVENT */ + +#ifdef MESSAGE_TO_EVENT +#undef EVENT +#undef WINDOW_ONLY_EVENT +#undef TOUCH_EVENT +#undef DOCUMENT_ONLY_EVENT +#undef NON_IDL_EVENT +#endif /* MESSAGE_TO_EVENT */ + diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp new file mode 100644 index 000000000..659629066 --- /dev/null +++ b/dom/events/EventStateManager.cpp @@ -0,0 +1,6002 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/DragEvent.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/dom/UIEvent.h" + +#include "ContentEventHandler.h" +#include "IMEContentObserver.h" +#include "WheelHandlingHelper.h" + +#include "nsCOMPtr.h" +#include "nsFocusManager.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIDocument.h" +#include "nsIFrame.h" +#include "nsIWidget.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsGkAtoms.h" +#include "nsIFormControl.h" +#include "nsIComboboxControlFrame.h" +#include "nsIScrollableFrame.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMXULControlElement.h" +#include "nsNameSpaceManager.h" +#include "nsIBaseWindow.h" +#include "nsISelection.h" +#include "nsITextControlElement.h" +#include "nsFrameSelection.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" +#include "nsIWebNavigation.h" +#include "nsIContentViewer.h" +#include "nsFrameManager.h" +#include "nsITabChild.h" +#include "nsPluginFrame.h" +#include "nsMenuPopupFrame.h" + +#include "nsIDOMXULElement.h" +#include "nsIDOMKeyEvent.h" +#include "nsIObserverService.h" +#include "nsIDocShell.h" +#include "nsIDOMWheelEvent.h" +#include "nsIDOMUIEvent.h" +#include "nsIMozBrowserFrame.h" + +#include "nsSubDocumentFrame.h" +#include "nsLayoutUtils.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsUnicharUtils.h" +#include "nsContentUtils.h" + +#include "imgIContainer.h" +#include "nsIProperties.h" +#include "nsISupportsPrimitives.h" + +#include "nsServiceManagerUtils.h" +#include "nsITimer.h" +#include "nsFontMetrics.h" +#include "nsIDOMXULDocument.h" +#include "nsIDragService.h" +#include "nsIDragSession.h" +#include "mozilla/dom/DataTransfer.h" +#include "nsContentAreaDragDrop.h" +#ifdef MOZ_XUL +#include "nsTreeBodyFrame.h" +#endif +#include "nsIController.h" +#include "nsICommandParams.h" +#include "mozilla/Services.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/HTMLLabelElement.h" + +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "GeckoProfiler.h" +#include "Units.h" +#include "mozilla/layers/APZCTreeManager.h" + +#ifdef XP_MACOSX +#import <ApplicationServices/ApplicationServices.h> +#endif + +namespace mozilla { + +using namespace dom; + +//#define DEBUG_DOCSHELL_FOCUS + +#define NS_USER_INTERACTION_INTERVAL 5000 // ms + +static const LayoutDeviceIntPoint kInvalidRefPoint = LayoutDeviceIntPoint(-1,-1); + +static uint32_t gMouseOrKeyboardEventCounter = 0; +static nsITimer* gUserInteractionTimer = nullptr; +static nsITimerCallback* gUserInteractionTimerCallback = nullptr; + +static const double kCursorLoadingTimeout = 1000; // ms +static nsWeakFrame gLastCursorSourceFrame; +static TimeStamp gLastCursorUpdateTime; + +static inline int32_t +RoundDown(double aDouble) +{ + return (aDouble > 0) ? static_cast<int32_t>(floor(aDouble)) : + static_cast<int32_t>(ceil(aDouble)); +} + +#ifdef DEBUG_DOCSHELL_FOCUS +static void +PrintDocTree(nsIDocShellTreeItem* aParentItem, int aLevel) +{ + for (int32_t i=0;i<aLevel;i++) printf(" "); + + int32_t childWebshellCount; + aParentItem->GetChildCount(&childWebshellCount); + nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(aParentItem)); + int32_t type = aParentItem->ItemType(); + nsCOMPtr<nsIPresShell> presShell = parentAsDocShell->GetPresShell(); + RefPtr<nsPresContext> presContext; + parentAsDocShell->GetPresContext(getter_AddRefs(presContext)); + nsCOMPtr<nsIContentViewer> cv; + parentAsDocShell->GetContentViewer(getter_AddRefs(cv)); + nsCOMPtr<nsIDOMDocument> domDoc; + if (cv) + cv->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + nsCOMPtr<nsIDOMWindow> domwin = doc ? doc->GetWindow() : nullptr; + nsIURI* uri = doc ? doc->GetDocumentURI() : nullptr; + + printf("DS %p Type %s Cnt %d Doc %p DW %p EM %p%c", + static_cast<void*>(parentAsDocShell.get()), + type==nsIDocShellTreeItem::typeChrome?"Chrome":"Content", + childWebshellCount, static_cast<void*>(doc.get()), + static_cast<void*>(domwin.get()), + static_cast<void*>(presContext ? presContext->EventStateManager() : nullptr), + uri ? ' ' : '\n'); + if (uri) { + nsAutoCString spec; + uri->GetSpec(spec); + printf("\"%s\"\n", spec.get()); + } + + if (childWebshellCount > 0) { + for (int32_t i = 0; i < childWebshellCount; i++) { + nsCOMPtr<nsIDocShellTreeItem> child; + aParentItem->GetChildAt(i, getter_AddRefs(child)); + PrintDocTree(child, aLevel + 1); + } + } +} + +static void +PrintDocTreeAll(nsIDocShellTreeItem* aItem) +{ + nsCOMPtr<nsIDocShellTreeItem> item = aItem; + for(;;) { + nsCOMPtr<nsIDocShellTreeItem> parent; + item->GetParent(getter_AddRefs(parent)); + if (!parent) + break; + item = parent; + } + + PrintDocTree(item, 0); +} +#endif + +// mask values for ui.key.chromeAccess and ui.key.contentAccess +#define NS_MODIFIER_SHIFT 1 +#define NS_MODIFIER_CONTROL 2 +#define NS_MODIFIER_ALT 4 +#define NS_MODIFIER_META 8 +#define NS_MODIFIER_OS 16 + +/******************************************************************/ +/* mozilla::UITimerCallback */ +/******************************************************************/ + +class UITimerCallback final : public nsITimerCallback +{ +public: + UITimerCallback() : mPreviousCount(0) {} + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK +private: + ~UITimerCallback() {} + uint32_t mPreviousCount; +}; + +NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback) + +// If aTimer is nullptr, this method always sends "user-interaction-inactive" +// notification. +NS_IMETHODIMP +UITimerCallback::Notify(nsITimer* aTimer) +{ + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (!obs) + return NS_ERROR_FAILURE; + if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) { + gMouseOrKeyboardEventCounter = 0; + obs->NotifyObservers(nullptr, "user-interaction-inactive", nullptr); + if (gUserInteractionTimer) { + gUserInteractionTimer->Cancel(); + NS_RELEASE(gUserInteractionTimer); + } + } else { + obs->NotifyObservers(nullptr, "user-interaction-active", nullptr); + EventStateManager::UpdateUserActivityTimer(); + } + mPreviousCount = gMouseOrKeyboardEventCounter; + return NS_OK; +} + +/******************************************************************/ +/* mozilla::OverOutElementsWrapper */ +/******************************************************************/ + +OverOutElementsWrapper::OverOutElementsWrapper() + : mLastOverFrame(nullptr) +{ +} + +OverOutElementsWrapper::~OverOutElementsWrapper() +{ +} + +NS_IMPL_CYCLE_COLLECTION(OverOutElementsWrapper, + mLastOverElement, + mFirstOverEventElement, + mFirstOutEventElement) +NS_IMPL_CYCLE_COLLECTING_ADDREF(OverOutElementsWrapper) +NS_IMPL_CYCLE_COLLECTING_RELEASE(OverOutElementsWrapper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OverOutElementsWrapper) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/******************************************************************/ +/* mozilla::EventStateManager */ +/******************************************************************/ + +static uint32_t sESMInstanceCount = 0; +static bool sPointerEventEnabled = false; + +uint64_t EventStateManager::sUserInputCounter = 0; +int32_t EventStateManager::sUserInputEventDepth = 0; +bool EventStateManager::sNormalLMouseEventInProcess = false; +EventStateManager* EventStateManager::sActiveESM = nullptr; +nsIDocument* EventStateManager::sMouseOverDocument = nullptr; +nsWeakFrame EventStateManager::sLastDragOverFrame = nullptr; +LayoutDeviceIntPoint EventStateManager::sPreLockPoint = LayoutDeviceIntPoint(0, 0); +LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint; +CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0); +LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint; +CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0); +bool EventStateManager::sIsPointerLocked = false; +// Reference to the pointer locked element. +nsWeakPtr EventStateManager::sPointerLockedElement; +// Reference to the document which requested pointer lock. +nsWeakPtr EventStateManager::sPointerLockedDoc; +nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr; +TimeStamp EventStateManager::sLatestUserInputStart; +TimeStamp EventStateManager::sHandlingInputStart; + +EventStateManager::WheelPrefs* + EventStateManager::WheelPrefs::sInstance = nullptr; +bool EventStateManager::WheelPrefs::sWheelEventsEnabledOnPlugins = true; +EventStateManager::DeltaAccumulator* + EventStateManager::DeltaAccumulator::sInstance = nullptr; + +EventStateManager::EventStateManager() + : mLockCursor(0) + , mLastFrameConsumedSetCursor(false) + , mCurrentTarget(nullptr) + // init d&d gesture state machine variables + , mGestureDownPoint(0,0) + , mPresContext(nullptr) + , mLClickCount(0) + , mMClickCount(0) + , mRClickCount(0) + , mInTouchDrag(false) + , m_haveShutdown(false) +{ + if (sESMInstanceCount == 0) { + gUserInteractionTimerCallback = new UITimerCallback(); + if (gUserInteractionTimerCallback) + NS_ADDREF(gUserInteractionTimerCallback); + UpdateUserActivityTimer(); + } + ++sESMInstanceCount; + + static bool sAddedPointerEventEnabled = false; + if (!sAddedPointerEventEnabled) { + Preferences::AddBoolVarCache(&sPointerEventEnabled, + "dom.w3c_pointer_events.enabled", false); + sAddedPointerEventEnabled = true; + } +} + +nsresult +EventStateManager::UpdateUserActivityTimer() +{ + if (!gUserInteractionTimerCallback) + return NS_OK; + + if (!gUserInteractionTimer) + CallCreateInstance("@mozilla.org/timer;1", &gUserInteractionTimer); + + if (gUserInteractionTimer) { + gUserInteractionTimer->InitWithCallback(gUserInteractionTimerCallback, + NS_USER_INTERACTION_INTERVAL, + nsITimer::TYPE_ONE_SHOT); + } + return NS_OK; +} + +nsresult +EventStateManager::Init() +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + + if (sESMInstanceCount == 1) { + Prefs::Init(); + } + + return NS_OK; +} + +EventStateManager::~EventStateManager() +{ + ReleaseCurrentIMEContentObserver(); + + if (sActiveESM == this) { + sActiveESM = nullptr; + } + if (Prefs::ClickHoldContextMenu()) + KillClickHoldTimer(); + + if (mDocument == sMouseOverDocument) + sMouseOverDocument = nullptr; + + --sESMInstanceCount; + if(sESMInstanceCount == 0) { + WheelTransaction::Shutdown(); + if (gUserInteractionTimerCallback) { + gUserInteractionTimerCallback->Notify(nullptr); + NS_RELEASE(gUserInteractionTimerCallback); + } + if (gUserInteractionTimer) { + gUserInteractionTimer->Cancel(); + NS_RELEASE(gUserInteractionTimer); + } + Prefs::Shutdown(); + WheelPrefs::Shutdown(); + DeltaAccumulator::Shutdown(); + } + + if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) { + sDragOverContent = nullptr; + } + + if (!m_haveShutdown) { + Shutdown(); + + // Don't remove from Observer service in Shutdown because Shutdown also + // gets called from xpcom shutdown observer. And we don't want to remove + // from the service in that case. + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + } + +} + +nsresult +EventStateManager::Shutdown() +{ + m_haveShutdown = true; + return NS_OK; +} + +NS_IMETHODIMP +EventStateManager::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t *someData) +{ + if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Shutdown(); + } + + return NS_OK; +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventStateManager) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(EventStateManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(EventStateManager) + +NS_IMPL_CYCLE_COLLECTION(EventStateManager, + mCurrentTargetContent, + mGestureDownContent, + mGestureDownFrameOwner, + mLastLeftMouseDownContent, + mLastLeftMouseDownContentParent, + mLastMiddleMouseDownContent, + mLastMiddleMouseDownContentParent, + mLastRightMouseDownContent, + mLastRightMouseDownContentParent, + mActiveContent, + mHoverContent, + mURLTargetContent, + mMouseEnterLeaveHelper, + mPointersEnterLeaveHelper, + mDocument, + mIMEContentObserver, + mAccessKeys) + +void +EventStateManager::ReleaseCurrentIMEContentObserver() +{ + if (mIMEContentObserver) { + mIMEContentObserver->DisconnectFromEventStateManager(); + } + mIMEContentObserver = nullptr; +} + +void +EventStateManager::OnStartToObserveContent( + IMEContentObserver* aIMEContentObserver) +{ + if (mIMEContentObserver == aIMEContentObserver) { + return; + } + ReleaseCurrentIMEContentObserver(); + mIMEContentObserver = aIMEContentObserver; +} + +void +EventStateManager::OnStopObservingContent( + IMEContentObserver* aIMEContentObserver) +{ + aIMEContentObserver->DisconnectFromEventStateManager(); + NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver); + mIMEContentObserver = nullptr; +} + +void +EventStateManager::TryToFlushPendingNotificationsToIME() +{ + if (mIMEContentObserver) { + mIMEContentObserver->TryToFlushPendingNotifications(); + } +} + +static bool +IsMessageMouseUserActivity(EventMessage aMessage) +{ + return aMessage == eMouseMove || + aMessage == eMouseUp || + aMessage == eMouseDown || + aMessage == eMouseDoubleClick || + aMessage == eMouseClick || + aMessage == eMouseActivate || + aMessage == eMouseLongTap; +} + +static bool +IsMessageGamepadUserActivity(EventMessage aMessage) +{ +#ifndef MOZ_GAMEPAD + return false; +#else + return aMessage == eGamepadButtonDown || + aMessage == eGamepadButtonUp || + aMessage == eGamepadAxisMove; +#endif +} + +nsresult +EventStateManager::PreHandleEvent(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIFrame* aTargetFrame, + nsIContent* aTargetContent, + nsEventStatus* aStatus) +{ + NS_ENSURE_ARG_POINTER(aStatus); + NS_ENSURE_ARG(aPresContext); + if (!aEvent) { + NS_ERROR("aEvent is null. This should never happen."); + return NS_ERROR_NULL_POINTER; + } + + NS_WARNING_ASSERTION( + !aTargetFrame || !aTargetFrame->GetContent() || + aTargetFrame->GetContent() == aTargetContent || + aTargetFrame->GetContent()->GetFlattenedTreeParent() == aTargetContent || + aTargetFrame->IsGeneratedContentFrame(), + "aTargetFrame should be related with aTargetContent"); +#if DEBUG + if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) { + nsCOMPtr<nsIContent> targetContent; + aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); + MOZ_ASSERT(aTargetContent == targetContent, + "Unexpected target for generated content frame!"); + } +#endif + + mCurrentTarget = aTargetFrame; + mCurrentTargetContent = nullptr; + + // Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading + // a page when user is not active doesn't change the state to active. + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (aEvent->IsTrusted() && + ((mouseEvent && mouseEvent->IsReal() && + IsMessageMouseUserActivity(mouseEvent->mMessage)) || + aEvent->mClass == eWheelEventClass || + aEvent->mClass == ePointerEventClass || + aEvent->mClass == eTouchEventClass || + aEvent->mClass == eKeyboardEventClass || + IsMessageGamepadUserActivity(aEvent->mMessage))) { + if (gMouseOrKeyboardEventCounter == 0) { + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "user-interaction-active", nullptr); + UpdateUserActivityTimer(); + } + } + ++gMouseOrKeyboardEventCounter; + + nsCOMPtr<nsINode> node = do_QueryInterface(aTargetContent); + if (node && + (aEvent->mMessage == eKeyUp || aEvent->mMessage == eMouseUp || + aEvent->mMessage == eWheel || aEvent->mMessage == eTouchEnd || + aEvent->mMessage == ePointerUp)) { + nsIDocument* doc = node->OwnerDoc(); + while (doc && !doc->UserHasInteracted()) { + doc->SetUserHasInteracted(true); + doc = nsContentUtils::IsChildOfSameType(doc) ? + doc->GetParentDocument() : nullptr; + } + } + } + + WheelTransaction::OnEvent(aEvent); + + // Focus events don't necessarily need a frame. + if (!mCurrentTarget && !aTargetContent) { + NS_ERROR("mCurrentTarget and aTargetContent are null"); + return NS_ERROR_NULL_POINTER; + } +#ifdef DEBUG + if (aEvent->HasDragEventMessage() && sIsPointerLocked) { + NS_ASSERTION(sIsPointerLocked, + "sIsPointerLocked is true. Drag events should be suppressed when " + "the pointer is locked."); + } +#endif + // Store last known screenPoint and clientPoint so pointer lock + // can use these values as constants. + if (aEvent->IsTrusted() && + ((mouseEvent && mouseEvent->IsReal()) || + aEvent->mClass == eWheelEventClass) && + !sIsPointerLocked) { + sLastScreenPoint = + Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint); + sLastClientPoint = + Event::GetClientCoords(aPresContext, aEvent, aEvent->mRefPoint, + CSSIntPoint(0, 0)); + } + + *aStatus = nsEventStatus_eIgnore; + + if (aEvent->mClass == eQueryContentEventClass) { + HandleQueryContentEvent(aEvent->AsQueryContentEvent()); + return NS_OK; + } + + WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); + if (touchEvent && mInTouchDrag) { + if (touchEvent->mMessage == eTouchMove) { + GenerateDragGesture(aPresContext, touchEvent); + } else { + mInTouchDrag = false; + StopTrackingDragGesture(); + } + } + + switch (aEvent->mMessage) { + case eContextMenu: + if (sIsPointerLocked) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + break; + case eMouseTouchDrag: + mInTouchDrag = true; + BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame); + break; + case eMouseDown: { + switch (mouseEvent->button) { + case WidgetMouseEvent::eLeftButton: + BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame); + mLClickCount = mouseEvent->mClickCount; + SetClickCount(mouseEvent, aStatus); + sNormalLMouseEventInProcess = true; + break; + case WidgetMouseEvent::eMiddleButton: + mMClickCount = mouseEvent->mClickCount; + SetClickCount(mouseEvent, aStatus); + break; + case WidgetMouseEvent::eRightButton: + mRClickCount = mouseEvent->mClickCount; + SetClickCount(mouseEvent, aStatus); + break; + } + break; + } + case eMouseUp: { + switch (mouseEvent->button) { + case WidgetMouseEvent::eLeftButton: + if (Prefs::ClickHoldContextMenu()) { + KillClickHoldTimer(); + } + StopTrackingDragGesture(); + sNormalLMouseEventInProcess = false; + // then fall through... + MOZ_FALLTHROUGH; + case WidgetMouseEvent::eRightButton: + case WidgetMouseEvent::eMiddleButton: + SetClickCount(mouseEvent, aStatus); + break; + } + break; + } + case eMouseEnterIntoWidget: + // In some cases on e10s eMouseEnterIntoWidget + // event was sent twice into child process of content. + // (From specific widget code (sending is not permanent) and + // from ESM::DispatchMouseOrPointerEvent (sending is permanent)). + // Flag mNoCrossProcessBoundaryForwarding helps to + // suppress sending accidental event from widget code. + aEvent->StopCrossProcessForwarding(); + break; + case eMouseExitFromWidget: + // If this is a remote frame, we receive eMouseExitFromWidget from the + // parent the mouse exits our content. Since the parent may update the + // cursor while the mouse is outside our frame, and since PuppetWidget + // caches the current cursor internally, re-entering our content (say from + // over a window edge) wont update the cursor if the cached value and the + // current cursor match. So when the mouse exits a remote frame, clear the + // cached widget cursor so a proper update will occur when the mouse + // re-enters. + if (XRE_IsContentProcess()) { + ClearCachedWidgetCursor(mCurrentTarget); + } + + // Flag helps to suppress double event sending into process of content. + // For more information see comment above, at eMouseEnterIntoWidget case. + aEvent->StopCrossProcessForwarding(); + + // If the event is not a top-level window exit, then it's not + // really an exit --- we may have traversed widget boundaries but + // we're still in our toplevel window. + if (mouseEvent->mExitFrom != WidgetMouseEvent::eTopLevel) { + // Treat it as a synthetic move so we don't generate spurious + // "exit" or "move" events. Any necessary "out" or "over" events + // will be generated by GenerateMouseEnterExit + mouseEvent->mMessage = eMouseMove; + mouseEvent->mReason = WidgetMouseEvent::eSynthesized; + // then fall through... + } else { + if (sPointerEventEnabled) { + // We should synthetize corresponding pointer events + GeneratePointerEnterExit(ePointerLeave, mouseEvent); + } + GenerateMouseEnterExit(mouseEvent); + //This is a window level mouse exit event and should stop here + aEvent->mMessage = eVoidEvent; + break; + } + MOZ_FALLTHROUGH; + case eMouseMove: + case ePointerDown: + case ePointerMove: { + // on the Mac, GenerateDragGesture() may not return until the drag + // has completed and so |aTargetFrame| may have been deleted (moving + // a bookmark, for example). If this is the case, however, we know + // that ClearFrameRefs() has been called and it cleared out + // |mCurrentTarget|. As a result, we should pass |mCurrentTarget| + // into UpdateCursor(). + GenerateDragGesture(aPresContext, mouseEvent); + UpdateCursor(aPresContext, aEvent, mCurrentTarget, aStatus); + GenerateMouseEnterExit(mouseEvent); + // Flush pending layout changes, so that later mouse move events + // will go to the right nodes. + FlushPendingEvents(aPresContext); + break; + } + case eDragStart: + if (Prefs::ClickHoldContextMenu()) { + // an external drag gesture event came in, not generated internally + // by Gecko. Make sure we get rid of the click-hold timer. + KillClickHoldTimer(); + } + break; + case eDragOver: + // Send the enter/exit events before eDrop. + GenerateDragDropEnterExit(aPresContext, aEvent->AsDragEvent()); + break; + + case eKeyPress: + { + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); + + int32_t modifierMask = 0; + if (keyEvent->IsShift()) + modifierMask |= NS_MODIFIER_SHIFT; + if (keyEvent->IsControl()) + modifierMask |= NS_MODIFIER_CONTROL; + if (keyEvent->IsAlt()) + modifierMask |= NS_MODIFIER_ALT; + if (keyEvent->IsMeta()) + modifierMask |= NS_MODIFIER_META; + if (keyEvent->IsOS()) + modifierMask |= NS_MODIFIER_OS; + + // Prevent keyboard scrolling while an accesskey modifier is in use. + if (modifierMask) { + bool matchesContentAccessKey = (modifierMask == Prefs::ContentAccessModifierMask()); + + if (modifierMask == Prefs::ChromeAccessModifierMask() || + matchesContentAccessKey) { + AutoTArray<uint32_t, 10> accessCharCodes; + keyEvent->GetAccessKeyCandidates(accessCharCodes); + + if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes, + modifierMask, matchesContentAccessKey)) { + *aStatus = nsEventStatus_eConsumeNoDefault; + } + } + } + } + // then fall through... + MOZ_FALLTHROUGH; + case eBeforeKeyDown: + case eKeyDown: + case eAfterKeyDown: + case eBeforeKeyUp: + case eKeyUp: + case eAfterKeyUp: + { + nsIContent* content = GetFocusedContent(); + if (content) + mCurrentTargetContent = content; + + // NOTE: Don't refer TextComposition::IsComposing() since UI Events + // defines that KeyboardEvent.isComposing is true when it's + // dispatched after compositionstart and compositionend. + // TextComposition::IsComposing() is false even before + // compositionend if there is no composing string. + // And also don't expose other document's composition state. + // A native IME context is typically shared by multiple documents. + // So, don't use GetTextCompositionFor(nsIWidget*) here. + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(aPresContext); + aEvent->AsKeyboardEvent()->mIsComposing = !!composition; + } + break; + case eWheel: + case eWheelOperationStart: + case eWheelOperationEnd: + { + NS_ASSERTION(aEvent->IsTrusted(), + "Untrusted wheel event shouldn't be here"); + + nsIContent* content = GetFocusedContent(); + if (content) { + mCurrentTargetContent = content; + } + + if (aEvent->mMessage != eWheel) { + break; + } + + WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent(); + WheelPrefs::GetInstance()->ApplyUserPrefsToDelta(wheelEvent); + + // If we won't dispatch a DOM event for this event, nothing to do anymore. + if (!wheelEvent->IsAllowedToDispatchDOMEvent()) { + break; + } + + // Init lineOrPageDelta values for line scroll events for some devices + // on some platforms which might dispatch wheel events which don't have + // lineOrPageDelta values. And also, if delta values are customized by + // prefs, this recomputes them. + DeltaAccumulator::GetInstance()-> + InitLineOrPageDelta(aTargetFrame, this, wheelEvent); + } + break; + case eSetSelection: + IMEStateManager::HandleSelectionEvent(aPresContext, GetFocusedContent(), + aEvent->AsSelectionEvent()); + break; + case eContentCommandCut: + case eContentCommandCopy: + case eContentCommandPaste: + case eContentCommandDelete: + case eContentCommandUndo: + case eContentCommandRedo: + case eContentCommandPasteTransferable: + case eContentCommandLookUpDictionary: + DoContentCommandEvent(aEvent->AsContentCommandEvent()); + break; + case eContentCommandScroll: + DoContentCommandScrollEvent(aEvent->AsContentCommandEvent()); + break; + case eCompositionStart: + if (aEvent->IsTrusted()) { + // If the event is trusted event, set the selected text to data of + // composition event. + WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent(); + WidgetQueryContentEvent selectedText(true, eQuerySelectedText, + compositionEvent->mWidget); + HandleQueryContentEvent(&selectedText); + NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text"); + compositionEvent->mData = selectedText.mReply.mString; + } + break; + default: + break; + } + return NS_OK; +} + +void +EventStateManager::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent) +{ + switch (aEvent->mMessage) { + case eQuerySelectedText: + case eQueryTextContent: + case eQueryCaretRect: + case eQueryTextRect: + case eQueryEditorRect: + if (!IsTargetCrossProcess(aEvent)) { + break; + } + // Will not be handled locally, remote the event + GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent); + return; + // Following events have not been supported in e10s mode yet. + case eQueryContentState: + case eQuerySelectionAsTransferable: + case eQueryCharacterAtPoint: + case eQueryDOMWidgetHittest: + case eQueryTextRectArray: + break; + default: + return; + } + + // If there is an IMEContentObserver, we need to handle QueryContentEvent + // with it. + if (mIMEContentObserver) { + RefPtr<IMEContentObserver> contentObserver = mIMEContentObserver; + contentObserver->HandleQueryContentEvent(aEvent); + return; + } + + ContentEventHandler handler(mPresContext); + handler.HandleQueryContentEvent(aEvent); +} + +// static +int32_t +EventStateManager::GetAccessModifierMaskFor(nsISupports* aDocShell) +{ + nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell)); + if (!treeItem) + return -1; // invalid modifier + + switch (treeItem->ItemType()) { + case nsIDocShellTreeItem::typeChrome: + return Prefs::ChromeAccessModifierMask(); + + case nsIDocShellTreeItem::typeContent: + return Prefs::ContentAccessModifierMask(); + + default: + return -1; // invalid modifier + } +} + +static bool +IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsAString& aKey) +{ + // Use GetAttr because we want Unicode case=insensitive matching + // XXXbz shouldn't this be case-sensitive, per spec? + nsString contentKey; + if (!aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, contentKey) || + !contentKey.Equals(aKey, nsCaseInsensitiveStringComparator())) + return false; + + nsCOMPtr<nsIDOMXULDocument> xulDoc = + do_QueryInterface(aContent->OwnerDoc()); + if (!xulDoc && !aContent->IsXULElement()) + return true; + + // For XUL we do visibility checks. + if (!aFrame) + return false; + + if (aFrame->IsFocusable()) + return true; + + if (!aFrame->IsVisibleConsideringAncestors()) + return false; + + // XUL controls can be activated. + nsCOMPtr<nsIDOMXULControlElement> control(do_QueryInterface(aContent)); + if (control) + return true; + + // HTML area, label and legend elements are never focusable, so + // we need to check for them explicitly before giving up. + if (aContent->IsAnyOfHTMLElements(nsGkAtoms::area, + nsGkAtoms::label, + nsGkAtoms::legend)) { + return true; + } + + // XUL label elements are never focusable, so we need to check for them + // explicitly before giving up. + if (aContent->IsXULElement(nsGkAtoms::label)) { + return true; + } + + return false; +} + +bool +EventStateManager::ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes, + bool aIsTrustedEvent) +{ + int32_t count, start = -1; + nsIContent* focusedContent = GetFocusedContent(); + if (focusedContent) { + start = mAccessKeys.IndexOf(focusedContent); + if (start == -1 && focusedContent->GetBindingParent()) + start = mAccessKeys.IndexOf(focusedContent->GetBindingParent()); + } + nsIContent *content; + nsIFrame *frame; + int32_t length = mAccessKeys.Count(); + for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) { + uint32_t ch = aAccessCharCodes[i]; + nsAutoString accessKey; + AppendUCS4ToUTF16(ch, accessKey); + for (count = 1; count <= length; ++count) { + content = mAccessKeys[(start + count) % length]; + frame = content->GetPrimaryFrame(); + if (IsAccessKeyTarget(content, frame, accessKey)) { + bool shouldActivate = Prefs::KeyCausesActivation(); + while (shouldActivate && ++count <= length) { + nsIContent *oc = mAccessKeys[(start + count) % length]; + nsIFrame *of = oc->GetPrimaryFrame(); + if (IsAccessKeyTarget(oc, of, accessKey)) + shouldActivate = false; + } + + bool focusChanged = false; + if (shouldActivate) { + focusChanged = content->PerformAccesskey(shouldActivate, aIsTrustedEvent); + } else { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content); + fm->SetFocus(element, nsIFocusManager::FLAG_BYKEY); + focusChanged = true; + } + } + + if (focusChanged && aIsTrustedEvent) { + // If this is a child process, inform the parent that we want the focus, but + // pass false since we don't want to change the window order. + nsIDocShell* docShell = mPresContext->GetDocShell(); + nsCOMPtr<nsITabChild> child = + docShell ? docShell->GetTabChild() : nullptr; + if (child) { + child->SendRequestFocus(false); + } + } + + return true; + } + } + } + return false; +} + +// static +void +EventStateManager::GetAccessKeyLabelPrefix(Element* aElement, nsAString& aPrefix) +{ + aPrefix.Truncate(); + nsAutoString separator, modifierText; + nsContentUtils::GetModifierSeparatorText(separator); + + nsCOMPtr<nsISupports> container = aElement->OwnerDoc()->GetDocShell(); + int32_t modifierMask = GetAccessModifierMaskFor(container); + + if (modifierMask == -1) { + return; + } + + if (modifierMask & NS_MODIFIER_CONTROL) { + nsContentUtils::GetControlText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_META) { + nsContentUtils::GetMetaText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_OS) { + nsContentUtils::GetOSText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_ALT) { + nsContentUtils::GetAltText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_SHIFT) { + nsContentUtils::GetShiftText(modifierText); + aPrefix.Append(modifierText + separator); + } +} + +struct MOZ_STACK_CLASS AccessKeyInfo +{ + WidgetKeyboardEvent* event; + nsTArray<uint32_t>& charCodes; + int32_t modifierMask; + + AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes, int32_t aModifierMask) + : event(aEvent) + , charCodes(aCharCodes) + , modifierMask(aModifierMask) + { + } +}; + +static bool +HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg) +{ + AccessKeyInfo* accessKeyInfo = static_cast<AccessKeyInfo*>(aArg); + + // Only forward accesskeys for the active tab. + bool active; + aTabParent->GetDocShellIsActive(&active); + if (active) { + accessKeyInfo->event->mAccessKeyForwardedToChild = true; + aTabParent->HandleAccessKey(*accessKeyInfo->event, + accessKeyInfo->charCodes, + accessKeyInfo->modifierMask); + return true; + } + + return false; +} + +bool +EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext, + nsTArray<uint32_t>& aAccessCharCodes, + bool aMatchesContentAccessKey, + nsIDocShellTreeItem* aBubbledFrom, + ProcessingAccessKeyState aAccessKeyState, + int32_t aModifierMask) +{ + EnsureDocument(mPresContext); + nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell(); + if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) { + return false; + } + + // Alt or other accesskey modifier is down, we may need to do an accesskey. + if (mAccessKeys.Count() > 0 && + aModifierMask == GetAccessModifierMaskFor(docShell)) { + // Someone registered an accesskey. Find and activate it. + if (ExecuteAccessKey(aAccessCharCodes, aEvent->IsTrusted())) { + return true; + } + } + + int32_t childCount; + docShell->GetChildCount(&childCount); + for (int32_t counter = 0; counter < childCount; counter++) { + // Not processing the child which bubbles up the handling + nsCOMPtr<nsIDocShellTreeItem> subShellItem; + docShell->GetChildAt(counter, getter_AddRefs(subShellItem)); + if (aAccessKeyState == eAccessKeyProcessingUp && + subShellItem == aBubbledFrom) { + continue; + } + + nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem); + if (subDS && IsShellVisible(subDS)) { + nsCOMPtr<nsIPresShell> subPS = subDS->GetPresShell(); + + // Docshells need not have a presshell (eg. display:none + // iframes, docshells in transition between documents, etc). + if (!subPS) { + // Oh, well. Just move on to the next child + continue; + } + + nsPresContext *subPC = subPS->GetPresContext(); + + EventStateManager* esm = + static_cast<EventStateManager*>(subPC->EventStateManager()); + + if (esm && + esm->HandleAccessKey(aEvent, subPC, aAccessCharCodes, + aMatchesContentAccessKey, nullptr, + eAccessKeyProcessingDown, aModifierMask)) { + return true; + } + } + }// if end . checking all sub docshell ends here. + + // bubble up the process to the parent docshell if necessary + if (eAccessKeyProcessingDown != aAccessKeyState) { + nsCOMPtr<nsIDocShellTreeItem> parentShellItem; + docShell->GetParent(getter_AddRefs(parentShellItem)); + nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem); + if (parentDS) { + nsCOMPtr<nsIPresShell> parentPS = parentDS->GetPresShell(); + NS_ASSERTION(parentPS, "Our PresShell exists but the parent's does not?"); + + nsPresContext *parentPC = parentPS->GetPresContext(); + NS_ASSERTION(parentPC, "PresShell without PresContext"); + + EventStateManager* esm = + static_cast<EventStateManager*>(parentPC->EventStateManager()); + if (esm && + esm->HandleAccessKey(aEvent, parentPC, aAccessCharCodes, + aMatchesContentAccessKey, docShell, + eAccessKeyProcessingDown, aModifierMask)) { + return true; + } + } + }// if end. bubble up process + + // If the content access key modifier is pressed, try remote children + if (aMatchesContentAccessKey && mDocument && mDocument->GetWindow()) { + // If the focus is currently on a node with a TabParent, the key event will + // get forwarded to the child process and HandleAccessKey called from there. + // If focus is somewhere else, then we need to check the remote children. + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr; + if (TabParent::GetFrom(focusedContent)) { + // A remote child process is focused. The key event should get sent to + // the child process. + aEvent->mAccessKeyForwardedToChild = true; + } else { + AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes, aModifierMask); + nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(), + HandleAccessKeyInRemoteChild, &accessKeyInfo); + } + } + + return false; +}// end of HandleAccessKey + +bool +EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent, + nsFrameLoader* aFrameLoader, + nsEventStatus *aStatus) { + TabParent* remote = TabParent::GetFrom(aFrameLoader); + if (!remote) { + return false; + } + + switch (aEvent->mClass) { + case eMouseEventClass: { + return remote->SendRealMouseEvent(*aEvent->AsMouseEvent()); + } + case eKeyboardEventClass: { + return remote->SendRealKeyEvent(*aEvent->AsKeyboardEvent()); + } + case eWheelEventClass: { + return remote->SendMouseWheelEvent(*aEvent->AsWheelEvent()); + } + case eTouchEventClass: { + // Let the child process synthesize a mouse event if needed, and + // ensure we don't synthesize one in this process. + *aStatus = nsEventStatus_eConsumeNoDefault; + return remote->SendRealTouchEvent(*aEvent->AsTouchEvent()); + } + case eDragEventClass: { + RefPtr<TabParent> tabParent = remote; + if (tabParent->Manager()->IsContentParent()) { + tabParent->Manager()->AsContentParent()->MaybeInvokeDragSession(tabParent); + } + + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE; + uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE; + if (dragSession) { + dragSession->DragEventDispatchedToChildProcess(); + dragSession->GetDragAction(&action); + nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer; + dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer)); + if (initialDataTransfer) { + initialDataTransfer->GetDropEffectInt(&dropEffect); + } + } + + bool retval = tabParent->SendRealDragEvent(*aEvent->AsDragEvent(), + action, dropEffect); + + return retval; + } + case ePluginEventClass: { + *aStatus = nsEventStatus_eConsumeNoDefault; + return remote->SendPluginEvent(*aEvent->AsPluginEvent()); + } + default: { + MOZ_CRASH("Attempt to send non-whitelisted event?"); + } + } +} + +bool +EventStateManager::IsRemoteTarget(nsIContent* target) { + if (!target) { + return false; + } + + // <browser/iframe remote=true> from XUL + if (target->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::iframe) && + target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote, + nsGkAtoms::_true, eIgnoreCase)) { + return true; + } + + // <frame/iframe mozbrowser/mozapp> + nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(target); + if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) { + return !!TabParent::GetFrom(target); + } + + return false; +} + +static bool +CrossProcessSafeEvent(const WidgetEvent& aEvent) +{ + switch (aEvent.mClass) { + case eKeyboardEventClass: + case eWheelEventClass: + return true; + case eMouseEventClass: + switch (aEvent.mMessage) { + case eMouseDown: + case eMouseUp: + case eMouseMove: + case eContextMenu: + case eMouseEnterIntoWidget: + case eMouseExitFromWidget: + case eMouseTouchDrag: + return true; + default: + return false; + } + case eTouchEventClass: + switch (aEvent.mMessage) { + case eTouchStart: + case eTouchMove: + case eTouchEnd: + case eTouchCancel: + return true; + default: + return false; + } + case eDragEventClass: + switch (aEvent.mMessage) { + case eDragOver: + case eDragExit: + case eDrop: + return true; + default: + return false; + } + default: + return false; + } +} + +bool +EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent, + nsEventStatus *aStatus) { + if (*aStatus == nsEventStatus_eConsumeNoDefault || + aEvent->mFlags.mNoCrossProcessBoundaryForwarding || + !CrossProcessSafeEvent(*aEvent)) { + return false; + } + + // Collect the remote event targets we're going to forward this + // event to. + // + // NB: the elements of |targets| must be unique, for correctness. + AutoTArray<nsCOMPtr<nsIContent>, 1> targets; + if (aEvent->mClass != eTouchEventClass || aEvent->mMessage == eTouchStart) { + // If this event only has one target, and it's remote, add it to + // the array. + nsIFrame* frame = + aEvent->mMessage == eDragExit ? sLastDragOverFrame.GetFrame() : GetEventTarget(); + nsIContent* target = frame ? frame->GetContent() : nullptr; + if (IsRemoteTarget(target)) { + targets.AppendElement(target); + } + } else { + // This is a touch event with possibly multiple touch points. + // Each touch point may have its own target. So iterate through + // all of them and collect the unique set of targets for event + // forwarding. + // + // This loop is similar to the one used in + // PresShell::DispatchTouchEvent(). + const WidgetTouchEvent::TouchArray& touches = + aEvent->AsTouchEvent()->mTouches; + for (uint32_t i = 0; i < touches.Length(); ++i) { + Touch* touch = touches[i]; + // NB: the |mChanged| check is an optimization, subprocesses can + // compute this for themselves. If the touch hasn't changed, we + // may be able to avoid forwarding the event entirely (which is + // not free). + if (!touch || !touch->mChanged) { + continue; + } + nsCOMPtr<EventTarget> targetPtr = touch->mTarget; + if (!targetPtr) { + continue; + } + nsCOMPtr<nsIContent> target = do_QueryInterface(targetPtr); + if (IsRemoteTarget(target) && !targets.Contains(target)) { + targets.AppendElement(target); + } + } + } + + if (targets.Length() == 0) { + return false; + } + + // Look up the frame loader for all the remote targets we found, and + // then dispatch the event to the remote content they represent. + bool dispatched = false; + for (uint32_t i = 0; i < targets.Length(); ++i) { + nsIContent* target = targets[i]; + nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(target); + if (!loaderOwner) { + continue; + } + + RefPtr<nsFrameLoader> frameLoader = loaderOwner->GetFrameLoader(); + if (!frameLoader) { + continue; + } + + uint32_t eventMode; + frameLoader->GetEventMode(&eventMode); + if (eventMode == nsIFrameLoader::EVENT_MODE_DONT_FORWARD_TO_CHILD) { + continue; + } + + dispatched |= DispatchCrossProcessEvent(aEvent, frameLoader, aStatus); + } + return dispatched; +} + +// +// CreateClickHoldTimer +// +// Fire off a timer for determining if the user wants click-hold. This timer +// is a one-shot that will be cancelled when the user moves enough to fire +// a drag. +// +void +EventStateManager::CreateClickHoldTimer(nsPresContext* inPresContext, + nsIFrame* inDownFrame, + WidgetGUIEvent* inMouseDownEvent) +{ + if (!inMouseDownEvent->IsTrusted() || + IsRemoteTarget(mGestureDownContent) || + sIsPointerLocked) { + return; + } + + // just to be anal (er, safe) + if (mClickHoldTimer) { + mClickHoldTimer->Cancel(); + mClickHoldTimer = nullptr; + } + + // if content clicked on has a popup, don't even start the timer + // since we'll end up conflicting and both will show. + if (mGestureDownContent) { + // check for the |popup| attribute + if (nsContentUtils::HasNonEmptyAttr(mGestureDownContent, kNameSpaceID_None, + nsGkAtoms::popup)) + return; + + // check for a <menubutton> like bookmarks + if (mGestureDownContent->IsXULElement(nsGkAtoms::menubutton)) + return; + } + + mClickHoldTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mClickHoldTimer) { + int32_t clickHoldDelay = + Preferences::GetInt("ui.click_hold_context_menus.delay", 500); + mClickHoldTimer->InitWithFuncCallback(sClickHoldCallback, this, + clickHoldDelay, + nsITimer::TYPE_ONE_SHOT); + } +} // CreateClickHoldTimer + +// +// KillClickHoldTimer +// +// Stop the timer that would show the context menu dead in its tracks +// +void +EventStateManager::KillClickHoldTimer() +{ + if (mClickHoldTimer) { + mClickHoldTimer->Cancel(); + mClickHoldTimer = nullptr; + } +} + +// +// sClickHoldCallback +// +// This fires after the mouse has been down for a certain length of time. +// +void +EventStateManager::sClickHoldCallback(nsITimer* aTimer, void* aESM) +{ + RefPtr<EventStateManager> self = static_cast<EventStateManager*>(aESM); + if (self) { + self->FireContextClick(); + } + + // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling ClosePopup(); + +} // sAutoHideCallback + +// +// FireContextClick +// +// If we're this far, our timer has fired, which means the mouse has been down +// for a certain period of time and has not moved enough to generate a dragGesture. +// We can be certain the user wants a context-click at this stage, so generate +// a dom event and fire it in. +// +// After the event fires, check if PreventDefault() has been set on the event which +// means that someone either ate the event or put up a context menu. This is our cue +// to stop tracking the drag gesture. If we always did this, draggable items w/out +// a context menu wouldn't be draggable after a certain length of time, which is +// _not_ what we want. +// +void +EventStateManager::FireContextClick() +{ + if (!mGestureDownContent || !mPresContext || sIsPointerLocked) { + return; + } + +#ifdef XP_MACOSX + // Hack to ensure that we don't show a context menu when the user + // let go of the mouse after a long cpu-hogging operation prevented + // us from handling any OS events. See bug 117589. + if (!CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft)) + return; +#endif + + nsEventStatus status = nsEventStatus_eIgnore; + + // Dispatch to the DOM. We have to fake out the ESM and tell it that the + // current target frame is actually where the mouseDown occurred, otherwise it + // will use the frame the mouse is currently over which may or may not be + // the same. (Note: saari and I have decided that we don't have to reset |mCurrentTarget| + // when we're through because no one else is doing anything more with this + // event and it will get reset on the very next event to the correct frame). + mCurrentTarget = mPresContext->GetPrimaryFrameFor(mGestureDownContent); + // make sure the widget sticks around + nsCOMPtr<nsIWidget> targetWidget; + if (mCurrentTarget && (targetWidget = mCurrentTarget->GetNearestWidget())) { + NS_ASSERTION(mPresContext == mCurrentTarget->PresContext(), + "a prescontext returned a primary frame that didn't belong to it?"); + + // before dispatching, check that we're not on something that + // doesn't get a context menu + bool allowedToDispatch = true; + + if (mGestureDownContent->IsAnyOfXULElements(nsGkAtoms::scrollbar, + nsGkAtoms::scrollbarbutton, + nsGkAtoms::button)) { + allowedToDispatch = false; + } else if (mGestureDownContent->IsXULElement(nsGkAtoms::toolbarbutton)) { + // a <toolbarbutton> that has the container attribute set + // will already have its own dropdown. + if (nsContentUtils::HasNonEmptyAttr(mGestureDownContent, + kNameSpaceID_None, nsGkAtoms::container)) { + allowedToDispatch = false; + } else { + // If the toolbar button has an open menu, don't attempt to open + // a second menu + if (mGestureDownContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, + nsGkAtoms::_true, eCaseMatters)) { + allowedToDispatch = false; + } + } + } + else if (mGestureDownContent->IsHTMLElement()) { + nsCOMPtr<nsIFormControl> formCtrl(do_QueryInterface(mGestureDownContent)); + + if (formCtrl) { + allowedToDispatch = formCtrl->IsTextOrNumberControl(/*aExcludePassword*/ false) || + formCtrl->GetType() == NS_FORM_INPUT_FILE; + } + else if (mGestureDownContent->IsAnyOfHTMLElements(nsGkAtoms::applet, + nsGkAtoms::embed, + nsGkAtoms::object, + nsGkAtoms::label)) { + allowedToDispatch = false; + } + } + + if (allowedToDispatch) { + // init the event while mCurrentTarget is still good + WidgetMouseEvent event(true, eContextMenu, targetWidget, + WidgetMouseEvent::eReal); + event.mClickCount = 1; + FillInEventFromGestureDown(&event); + + // stop selection tracking, we're in control now + if (mCurrentTarget) + { + RefPtr<nsFrameSelection> frameSel = + mCurrentTarget->GetFrameSelection(); + + if (frameSel && frameSel->GetDragState()) { + // note that this can cause selection changed events to fire if we're in + // a text field, which will null out mCurrentTarget + frameSel->SetDragState(false); + } + } + + nsIDocument* doc = mGestureDownContent->GetComposedDoc(); + AutoHandlingUserInputStatePusher userInpStatePusher(true, &event, doc); + + // dispatch to DOM + EventDispatcher::Dispatch(mGestureDownContent, mPresContext, &event, + nullptr, &status); + + // We don't need to dispatch to frame handling because no frames + // watch eContextMenu except for nsMenuFrame and that's only for + // dismissal. That's just as well since we don't really know + // which frame to send it to. + } + } + + // now check if the event has been handled. If so, stop tracking a drag + if (status == nsEventStatus_eConsumeNoDefault) { + StopTrackingDragGesture(); + } + + KillClickHoldTimer(); + +} // FireContextClick + +// +// BeginTrackingDragGesture +// +// Record that the mouse has gone down and that we should move to TRACKING state +// of d&d gesture tracker. +// +// We also use this to track click-hold context menus. When the mouse goes down, +// fire off a short timer. If the timer goes off and we have yet to fire the +// drag gesture (ie, the mouse hasn't moved a certain distance), then we can +// assume the user wants a click-hold, so fire a context-click event. We only +// want to cancel the drag gesture if the context-click event is handled. +// +void +EventStateManager::BeginTrackingDragGesture(nsPresContext* aPresContext, + WidgetMouseEvent* inDownEvent, + nsIFrame* inDownFrame) +{ + if (!inDownEvent->mWidget) { + return; + } + + // Note that |inDownEvent| could be either a mouse down event or a + // synthesized mouse move event. + mGestureDownPoint = + inDownEvent->mRefPoint + inDownEvent->mWidget->WidgetToScreenOffset(); + + if (inDownFrame) { + inDownFrame->GetContentForEvent(inDownEvent, + getter_AddRefs(mGestureDownContent)); + + mGestureDownFrameOwner = inDownFrame->GetContent(); + if (!mGestureDownFrameOwner) { + mGestureDownFrameOwner = mGestureDownContent; + } + } + mGestureModifiers = inDownEvent->mModifiers; + mGestureDownButtons = inDownEvent->buttons; + + if (inDownEvent->mMessage != eMouseTouchDrag && Prefs::ClickHoldContextMenu()) { + // fire off a timer to track click-hold + CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent); + } +} + +void +EventStateManager::BeginTrackingRemoteDragGesture(nsIContent* aContent) +{ + mGestureDownContent = aContent; + mGestureDownFrameOwner = aContent; +} + +// +// StopTrackingDragGesture +// +// Record that the mouse has gone back up so that we should leave the TRACKING +// state of d&d gesture tracker and return to the START state. +// +void +EventStateManager::StopTrackingDragGesture() +{ + mGestureDownContent = nullptr; + mGestureDownFrameOwner = nullptr; +} + +void +EventStateManager::FillInEventFromGestureDown(WidgetMouseEvent* aEvent) +{ + NS_ASSERTION(aEvent->mWidget == mCurrentTarget->GetNearestWidget(), + "Incorrect widget in event"); + + // Set the coordinates in the new event to the coordinates of + // the old event, adjusted for the fact that the widget might be + // different + aEvent->mRefPoint = + mGestureDownPoint - aEvent->mWidget->WidgetToScreenOffset(); + aEvent->mModifiers = mGestureModifiers; + aEvent->buttons = mGestureDownButtons; +} + +// +// GenerateDragGesture +// +// If we're in the TRACKING state of the d&d gesture tracker, check the current position +// of the mouse in relation to the old one. If we've moved a sufficient amount from +// the mouse down, then fire off a drag gesture event. +void +EventStateManager::GenerateDragGesture(nsPresContext* aPresContext, + WidgetInputEvent* aEvent) +{ + NS_ASSERTION(aPresContext, "This shouldn't happen."); + if (IsTrackingDragGesture()) { + mCurrentTarget = mGestureDownFrameOwner->GetPrimaryFrame(); + + if (!mCurrentTarget || !mCurrentTarget->GetNearestWidget()) { + StopTrackingDragGesture(); + return; + } + + // Check if selection is tracking drag gestures, if so + // don't interfere! + if (mCurrentTarget) + { + RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection(); + if (frameSel && frameSel->GetDragState()) { + StopTrackingDragGesture(); + return; + } + } + + // If non-native code is capturing the mouse don't start a drag. + if (nsIPresShell::IsMouseCapturePreventingDrag()) { + StopTrackingDragGesture(); + return; + } + + static int32_t pixelThresholdX = 0; + static int32_t pixelThresholdY = 0; + + if (!pixelThresholdX) { + pixelThresholdX = + LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdX, 0); + pixelThresholdY = + LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdY, 0); + if (!pixelThresholdX) + pixelThresholdX = 5; + if (!pixelThresholdY) + pixelThresholdY = 5; + } + + // fire drag gesture if mouse has moved enough + LayoutDeviceIntPoint pt = aEvent->mWidget->WidgetToScreenOffset() + + (aEvent->AsTouchEvent() ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint + : aEvent->mRefPoint); + LayoutDeviceIntPoint distance = pt - mGestureDownPoint; + if (Abs(distance.x) > AssertedCast<uint32_t>(pixelThresholdX) || + Abs(distance.y) > AssertedCast<uint32_t>(pixelThresholdY)) { + if (Prefs::ClickHoldContextMenu()) { + // stop the click-hold before we fire off the drag gesture, in case + // it takes a long time + KillClickHoldTimer(); + } + + nsCOMPtr<nsIDocShell> docshell = aPresContext->GetDocShell(); + if (!docshell) { + return; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = docshell->GetWindow(); + if (!window) + return; + + RefPtr<DataTransfer> dataTransfer = + new DataTransfer(window, eDragStart, false, -1); + + nsCOMPtr<nsISelection> selection; + nsCOMPtr<nsIContent> eventContent, targetContent; + mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent)); + if (eventContent) + DetermineDragTargetAndDefaultData(window, eventContent, dataTransfer, + getter_AddRefs(selection), + getter_AddRefs(targetContent)); + + // Stop tracking the drag gesture now. This should stop us from + // reentering GenerateDragGesture inside DOM event processing. + StopTrackingDragGesture(); + + if (!targetContent) + return; + + // Use our targetContent, now that we've determined it, as the + // parent object of the DataTransfer. + dataTransfer->SetParentObject(targetContent); + + sLastDragOverFrame = nullptr; + nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget(); + + // get the widget from the target frame + WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget); + FillInEventFromGestureDown(&startEvent); + + startEvent.mDataTransfer = dataTransfer; + if (aEvent->AsMouseEvent()) { + startEvent.inputSource = aEvent->AsMouseEvent()->inputSource; + } else if (aEvent->AsTouchEvent()) { + startEvent.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + } else { + MOZ_ASSERT(false); + } + + // Dispatch to the DOM. By setting mCurrentTarget we are faking + // out the ESM and telling it that the current target frame is + // actually where the mouseDown occurred, otherwise it will use + // the frame the mouse is currently over which may or may not be + // the same. (Note: saari and I have decided that we don't have + // to reset |mCurrentTarget| when we're through because no one + // else is doing anything more with this event and it will get + // reset on the very next event to the correct frame). + + // Hold onto old target content through the event and reset after. + nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent; + + // Set the current target to the content for the mouse down + mCurrentTargetContent = targetContent; + + // Dispatch the dragstart event to the DOM. + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent, + nullptr, &status); + + WidgetDragEvent* event = &startEvent; + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + // Emit observer event to allow addons to modify the DataTransfer object. + if (observerService) { + observerService->NotifyObservers(dataTransfer, + "on-datatransfer-available", + nullptr); + } + + // now that the dataTransfer has been updated in the dragstart and + // draggesture events, make it read only so that the data doesn't + // change during the drag. + dataTransfer->SetReadOnly(); + + if (status != nsEventStatus_eConsumeNoDefault) { + bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer, + targetContent, selection); + if (dragStarted) { + sActiveESM = nullptr; + aEvent->StopPropagation(); + } + } + + // Reset mCurretTargetContent to what it was + mCurrentTargetContent = targetBeforeEvent; + } + + // Now flush all pending notifications, for better responsiveness + // while dragging. + FlushPendingEvents(aPresContext); + } +} // GenerateDragGesture + +void +EventStateManager::DetermineDragTargetAndDefaultData(nsPIDOMWindowOuter* aWindow, + nsIContent* aSelectionTarget, + DataTransfer* aDataTransfer, + nsISelection** aSelection, + nsIContent** aTargetNode) +{ + *aTargetNode = nullptr; + + // GetDragData determines if a selection, link or image in the content + // should be dragged, and places the data associated with the drag in the + // data transfer. + // mGestureDownContent is the node where the mousedown event for the drag + // occurred, and aSelectionTarget is the node to use when a selection is used + bool canDrag; + nsCOMPtr<nsIContent> dragDataNode; + bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0; + nsresult rv = nsContentAreaDragDrop::GetDragData(aWindow, mGestureDownContent, + aSelectionTarget, wasAlt, + aDataTransfer, &canDrag, aSelection, + getter_AddRefs(dragDataNode)); + if (NS_FAILED(rv) || !canDrag) + return; + + // if GetDragData returned a node, use that as the node being dragged. + // Otherwise, if a selection is being dragged, use the node within the + // selection that was dragged. Otherwise, just use the mousedown target. + nsIContent* dragContent = mGestureDownContent; + if (dragDataNode) + dragContent = dragDataNode; + else if (*aSelection) + dragContent = aSelectionTarget; + + nsIContent* originalDragContent = dragContent; + + // If a selection isn't being dragged, look for an ancestor with the + // draggable property set. If one is found, use that as the target of the + // drag instead of the node that was clicked on. If a draggable node wasn't + // found, just use the clicked node. + if (!*aSelection) { + while (dragContent) { + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(dragContent); + if (htmlElement) { + bool draggable = false; + htmlElement->GetDraggable(&draggable); + if (draggable) + break; + } + else { + nsCOMPtr<nsIDOMXULElement> xulElement = do_QueryInterface(dragContent); + if (xulElement) { + // All XUL elements are draggable, so if a XUL element is + // encountered, stop looking for draggable nodes and just use the + // original clicked node instead. + // XXXndeakin + // In the future, we will want to improve this so that XUL has a + // better way to specify whether something is draggable than just + // on/off. + dragContent = mGestureDownContent; + break; + } + // otherwise, it's not an HTML or XUL element, so just keep looking + } + dragContent = dragContent->GetParent(); + } + } + + // if no node in the hierarchy was found to drag, but the GetDragData method + // returned a node, use that returned node. Otherwise, nothing is draggable. + if (!dragContent && dragDataNode) + dragContent = dragDataNode; + + if (dragContent) { + // if an ancestor node was used instead, clear the drag data + // XXXndeakin rework this a bit. Find a way to just not call GetDragData if we don't need to. + if (dragContent != originalDragContent) + aDataTransfer->ClearAll(); + *aTargetNode = dragContent; + NS_ADDREF(*aTargetNode); + } +} + +bool +EventStateManager::DoDefaultDragStart(nsPresContext* aPresContext, + WidgetDragEvent* aDragEvent, + DataTransfer* aDataTransfer, + nsIContent* aDragTarget, + nsISelection* aSelection) +{ + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (!dragService) + return false; + + // Default handling for the dragstart event. + // + // First, check if a drag session already exists. This means that the drag + // service was called directly within a draggesture handler. In this case, + // don't do anything more, as it is assumed that the handler is managing + // drag and drop manually. Make sure to return true to indicate that a drag + // began. + nsCOMPtr<nsIDragSession> dragSession; + dragService->GetCurrentSession(getter_AddRefs(dragSession)); + if (dragSession) + return true; + + // No drag session is currently active, so check if a handler added + // any items to be dragged. If not, there isn't anything to drag. + uint32_t count = 0; + if (aDataTransfer) + aDataTransfer->GetMozItemCount(&count); + if (!count) + return false; + + // Get the target being dragged, which may not be the same as the + // target of the mouse event. If one wasn't set in the + // aDataTransfer during the event handler, just use the original + // target instead. + nsCOMPtr<nsIContent> dragTarget = aDataTransfer->GetDragTarget(); + if (!dragTarget) { + dragTarget = aDragTarget; + if (!dragTarget) + return false; + } + + // check which drag effect should initially be used. If the effect was not + // set, just use all actions, otherwise Windows won't allow a drop. + uint32_t action; + aDataTransfer->GetEffectAllowedInt(&action); + if (action == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) + action = nsIDragService::DRAGDROP_ACTION_COPY | + nsIDragService::DRAGDROP_ACTION_MOVE | + nsIDragService::DRAGDROP_ACTION_LINK; + + // get any custom drag image that was set + int32_t imageX, imageY; + Element* dragImage = aDataTransfer->GetDragImage(&imageX, &imageY); + + nsCOMPtr<nsIArray> transArray = + aDataTransfer->GetTransferables(dragTarget->AsDOMNode()); + if (!transArray) + return false; + + // XXXndeakin don't really want to create a new drag DOM event + // here, but we need something to pass to the InvokeDragSession + // methods. + RefPtr<DragEvent> event = + NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent); + + // Use InvokeDragSessionWithSelection if a selection is being dragged, + // such that the image can be generated from the selected text. However, + // use InvokeDragSessionWithImage if a custom image was set or something + // other than a selection is being dragged. + if (!dragImage && aSelection) { + dragService->InvokeDragSessionWithSelection(aSelection, transArray, + action, event, aDataTransfer); + } + else { + // if dragging within a XUL tree and no custom drag image was + // set, the region argument to InvokeDragSessionWithImage needs + // to be set to the area encompassing the selected rows of the + // tree to ensure that the drag feedback gets clipped to those + // rows. For other content, region should be null. + nsCOMPtr<nsIScriptableRegion> region; +#ifdef MOZ_XUL + if (dragTarget && !dragImage) { + if (dragTarget->NodeInfo()->Equals(nsGkAtoms::treechildren, + kNameSpaceID_XUL)) { + nsTreeBodyFrame* treeBody = + do_QueryFrame(dragTarget->GetPrimaryFrame()); + if (treeBody) { + treeBody->GetSelectionRegion(getter_AddRefs(region)); + } + } + } +#endif + + dragService->InvokeDragSessionWithImage(dragTarget->AsDOMNode(), transArray, + region, action, + dragImage ? dragImage->AsDOMNode() : + nullptr, + imageX, imageY, event, + aDataTransfer); + } + + return true; +} + +nsresult +EventStateManager::GetContentViewer(nsIContentViewer** aCv) +{ + *aCv = nullptr; + + nsPIDOMWindowOuter* window = mDocument->GetWindow(); + if (!window) return NS_ERROR_FAILURE; + nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot(); + if (!rootWindow) return NS_ERROR_FAILURE; + + TabChild* tabChild = TabChild::GetFrom(rootWindow); + if (!tabChild) { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) return NS_ERROR_FAILURE; + + nsCOMPtr<mozIDOMWindowProxy> activeWindow; + fm->GetActiveWindow(getter_AddRefs(activeWindow)); + if (rootWindow != activeWindow) return NS_OK; + } else { + if (!tabChild->ParentIsActive()) return NS_OK; + } + + nsCOMPtr<nsPIDOMWindowOuter> contentWindow = nsGlobalWindow::Cast(rootWindow)->GetContent(); + if (!contentWindow) return NS_ERROR_FAILURE; + + nsIDocument *doc = contentWindow->GetDoc(); + if (!doc) return NS_ERROR_FAILURE; + + nsCOMPtr<nsISupports> container = doc->GetContainer(); + if (!container) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(container); + docshell->GetContentViewer(aCv); + if (!*aCv) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +EventStateManager::ChangeTextSize(int32_t change) +{ + nsCOMPtr<nsIContentViewer> cv; + nsresult rv = GetContentViewer(getter_AddRefs(cv)); + NS_ENSURE_SUCCESS(rv, rv); + + if (cv) { + float textzoom; + float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100; + float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100; + cv->GetTextZoom(&textzoom); + textzoom += ((float)change) / 10; + if (textzoom < zoomMin) + textzoom = zoomMin; + else if (textzoom > zoomMax) + textzoom = zoomMax; + cv->SetTextZoom(textzoom); + } + + return NS_OK; +} + +nsresult +EventStateManager::ChangeFullZoom(int32_t change) +{ + nsCOMPtr<nsIContentViewer> cv; + nsresult rv = GetContentViewer(getter_AddRefs(cv)); + NS_ENSURE_SUCCESS(rv, rv); + + if (cv) { + float fullzoom; + float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100; + float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100; + cv->GetFullZoom(&fullzoom); + fullzoom += ((float)change) / 10; + if (fullzoom < zoomMin) + fullzoom = zoomMin; + else if (fullzoom > zoomMax) + fullzoom = zoomMax; + cv->SetFullZoom(fullzoom); + } + + return NS_OK; +} + +void +EventStateManager::DoScrollHistory(int32_t direction) +{ + nsCOMPtr<nsISupports> pcContainer(mPresContext->GetContainerWeak()); + if (pcContainer) { + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(pcContainer)); + if (webNav) { + // positive direction to go back one step, nonpositive to go forward + if (direction > 0) + webNav->GoBack(); + else + webNav->GoForward(); + } + } +} + +void +EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame, + int32_t adjustment) +{ + // Exclude form controls and content in chrome docshells. + nsIContent *content = aTargetFrame->GetContent(); + if (content && + !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) && + !nsContentUtils::IsInChromeDocshell(content->OwnerDoc())) + { + // positive adjustment to decrease zoom, negative to increase + int32_t change = (adjustment > 0) ? -1 : 1; + + EnsureDocument(mPresContext); + if (Preferences::GetBool("browser.zoom.full") || content->OwnerDoc()->IsSyntheticDocument()) { + ChangeFullZoom(change); + } else { + ChangeTextSize(change); + } + nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument), + NS_LITERAL_STRING("ZoomChangeUsingMouseWheel"), + true, true); + } +} + +static nsIFrame* +GetParentFrameToScroll(nsIFrame* aFrame) +{ + if (!aFrame) + return nullptr; + + if (aFrame->StyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED && + nsLayoutUtils::IsReallyFixedPos(aFrame)) + return aFrame->PresContext()->GetPresShell()->GetRootScrollFrame(); + + return aFrame->GetParent(); +} + +/*static*/ bool +EventStateManager::CanVerticallyScrollFrameWithWheel(nsIFrame* aFrame) +{ + nsIContent* c = aFrame->GetContent(); + if (!c) { + return true; + } + nsCOMPtr<nsITextControlElement> ctrl = + do_QueryInterface(c->IsInAnonymousSubtree() ? c->GetBindingParent() : c); + if (ctrl && ctrl->IsSingleLineTextControl()) { + return false; + } + return true; +} + +void +EventStateManager::DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + nsEventStatus* aStatus) +{ + MOZ_ASSERT(aEvent); + MOZ_ASSERT(aStatus); + + if (!aTargetFrame || *aStatus == nsEventStatus_eConsumeNoDefault) { + return; + } + + // Ignore mouse wheel transaction for computing legacy mouse wheel + // events' delta value. + nsIFrame* scrollFrame = + ComputeScrollTarget(aTargetFrame, aEvent, + COMPUTE_LEGACY_MOUSE_SCROLL_EVENT_TARGET); + + nsIScrollableFrame* scrollTarget = do_QueryFrame(scrollFrame); + nsPresContext* pc = + scrollFrame ? scrollFrame->PresContext() : aTargetFrame->PresContext(); + + // DOM event's delta vales are computed from CSS pixels. + nsSize scrollAmount = GetScrollAmount(pc, aEvent, scrollTarget); + nsIntSize scrollAmountInCSSPixels( + nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.width), + nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.height)); + + // XXX We don't deal with fractional amount in legacy event, though the + // default action handler (DoScrollText()) deals with it. + // If we implemented such strict computation, we would need additional + // accumulated delta values. It would made the code more complicated. + // And also it would computes different delta values from older version. + // It doesn't make sense to implement such code for legacy events and + // rare cases. + int32_t scrollDeltaX, scrollDeltaY, pixelDeltaX, pixelDeltaY; + switch (aEvent->mDeltaMode) { + case nsIDOMWheelEvent::DOM_DELTA_PAGE: + scrollDeltaX = + !aEvent->mLineOrPageDeltaX ? 0 : + (aEvent->mLineOrPageDeltaX > 0 ? nsIDOMUIEvent::SCROLL_PAGE_DOWN : + nsIDOMUIEvent::SCROLL_PAGE_UP); + scrollDeltaY = + !aEvent->mLineOrPageDeltaY ? 0 : + (aEvent->mLineOrPageDeltaY > 0 ? nsIDOMUIEvent::SCROLL_PAGE_DOWN : + nsIDOMUIEvent::SCROLL_PAGE_UP); + pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width); + pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height); + break; + + case nsIDOMWheelEvent::DOM_DELTA_LINE: + scrollDeltaX = aEvent->mLineOrPageDeltaX; + scrollDeltaY = aEvent->mLineOrPageDeltaY; + pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width); + pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height); + break; + + case nsIDOMWheelEvent::DOM_DELTA_PIXEL: + scrollDeltaX = aEvent->mLineOrPageDeltaX; + scrollDeltaY = aEvent->mLineOrPageDeltaY; + pixelDeltaX = RoundDown(aEvent->mDeltaX); + pixelDeltaY = RoundDown(aEvent->mDeltaY); + break; + + default: + MOZ_CRASH("Invalid deltaMode value comes"); + } + + // Send the legacy events in following order: + // 1. Vertical scroll + // 2. Vertical pixel scroll (even if #1 isn't consumed) + // 3. Horizontal scroll (even if #1 and/or #2 are consumed) + // 4. Horizontal pixel scroll (even if #3 isn't consumed) + + nsWeakFrame targetFrame(aTargetFrame); + + MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault && + !aEvent->DefaultPrevented(), + "If you make legacy events dispatched for default prevented wheel " + "event, you need to initialize stateX and stateY"); + EventState stateX, stateY; + if (scrollDeltaY) { + SendLineScrollEvent(aTargetFrame, aEvent, stateY, + scrollDeltaY, DELTA_DIRECTION_Y); + if (!targetFrame.IsAlive()) { + *aStatus = nsEventStatus_eConsumeNoDefault; + return; + } + } + + if (pixelDeltaY) { + SendPixelScrollEvent(aTargetFrame, aEvent, stateY, + pixelDeltaY, DELTA_DIRECTION_Y); + if (!targetFrame.IsAlive()) { + *aStatus = nsEventStatus_eConsumeNoDefault; + return; + } + } + + if (scrollDeltaX) { + SendLineScrollEvent(aTargetFrame, aEvent, stateX, + scrollDeltaX, DELTA_DIRECTION_X); + if (!targetFrame.IsAlive()) { + *aStatus = nsEventStatus_eConsumeNoDefault; + return; + } + } + + if (pixelDeltaX) { + SendPixelScrollEvent(aTargetFrame, aEvent, stateX, + pixelDeltaX, DELTA_DIRECTION_X); + if (!targetFrame.IsAlive()) { + *aStatus = nsEventStatus_eConsumeNoDefault; + return; + } + } + + if (stateY.mDefaultPrevented) { + *aStatus = nsEventStatus_eConsumeNoDefault; + aEvent->PreventDefault(!stateY.mDefaultPreventedByContent); + } + + if (stateX.mDefaultPrevented) { + *aStatus = nsEventStatus_eConsumeNoDefault; + aEvent->PreventDefault(!stateX.mDefaultPreventedByContent); + } +} + +void +EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + EventState& aState, + int32_t aDelta, + DeltaDirection aDeltaDirection) +{ + nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent(); + if (!targetContent) + targetContent = GetFocusedContent(); + if (!targetContent) + return; + + while (targetContent->IsNodeOfType(nsINode::eTEXT)) { + targetContent = targetContent->GetParent(); + } + + WidgetMouseScrollEvent event(aEvent->IsTrusted(), + eLegacyMouseLineOrPageScroll, aEvent->mWidget); + event.mFlags.mDefaultPrevented = aState.mDefaultPrevented; + event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent; + event.mRefPoint = aEvent->mRefPoint; + event.mTime = aEvent->mTime; + event.mTimeStamp = aEvent->mTimeStamp; + event.mModifiers = aEvent->mModifiers; + event.buttons = aEvent->buttons; + event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X); + event.mDelta = aDelta; + event.inputSource = aEvent->inputSource; + + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(), + &event, nullptr, &status); + aState.mDefaultPrevented = + event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault; + aState.mDefaultPreventedByContent = event.DefaultPreventedByContent(); +} + +void +EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + EventState& aState, + int32_t aPixelDelta, + DeltaDirection aDeltaDirection) +{ + nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent(); + if (!targetContent) { + targetContent = GetFocusedContent(); + if (!targetContent) + return; + } + + while (targetContent->IsNodeOfType(nsINode::eTEXT)) { + targetContent = targetContent->GetParent(); + } + + WidgetMouseScrollEvent event(aEvent->IsTrusted(), + eLegacyMousePixelScroll, aEvent->mWidget); + event.mFlags.mDefaultPrevented = aState.mDefaultPrevented; + event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent; + event.mRefPoint = aEvent->mRefPoint; + event.mTime = aEvent->mTime; + event.mTimeStamp = aEvent->mTimeStamp; + event.mModifiers = aEvent->mModifiers; + event.buttons = aEvent->buttons; + event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X); + event.mDelta = aPixelDelta; + event.inputSource = aEvent->inputSource; + + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(), + &event, nullptr, &status); + aState.mDefaultPrevented = + event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault; + aState.mDefaultPreventedByContent = event.DefaultPreventedByContent(); +} + +nsIFrame* +EventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + ComputeScrollTargetOptions aOptions) +{ + return ComputeScrollTarget(aTargetFrame, aEvent->mDeltaX, aEvent->mDeltaY, + aEvent, aOptions); +} + +// Overload ComputeScrollTarget method to allow passing "test" dx and dy when looking +// for which scrollbarmediators to activate when two finger down on trackpad +// and before any actual motion +nsIFrame* +EventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame, + double aDirectionX, + double aDirectionY, + WidgetWheelEvent* aEvent, + ComputeScrollTargetOptions aOptions) +{ + if ((aOptions & INCLUDE_PLUGIN_AS_TARGET) && + !WheelPrefs::WheelEventsEnabledOnPlugins()) { + aOptions = RemovePluginFromTarget(aOptions); + } + + if (aOptions & PREFER_MOUSE_WHEEL_TRANSACTION) { + // If the user recently scrolled with the mousewheel, then they probably + // want to scroll the same view as before instead of the view under the + // cursor. WheelTransaction tracks the frame currently being + // scrolled with the mousewheel. We consider the transaction ended when the + // mouse moves more than "mousewheel.transaction.ignoremovedelay" + // milliseconds after the last scroll operation, or any time the mouse moves + // out of the frame, or when more than "mousewheel.transaction.timeout" + // milliseconds have passed after the last operation, even if the mouse + // hasn't moved. + nsIFrame* lastScrollFrame = WheelTransaction::GetTargetFrame(); + if (lastScrollFrame) { + if (aOptions & INCLUDE_PLUGIN_AS_TARGET) { + nsPluginFrame* pluginFrame = do_QueryFrame(lastScrollFrame); + if (pluginFrame && + pluginFrame->WantsToHandleWheelEventAsDefaultAction()) { + return lastScrollFrame; + } + } + nsIScrollableFrame* scrollableFrame = + lastScrollFrame->GetScrollTargetFrame(); + if (scrollableFrame) { + nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame); + MOZ_ASSERT(frameToScroll); + return frameToScroll; + } + } + } + + // If the event doesn't cause scroll actually, we cannot find scroll target + // because we check if the event can cause scroll actually on each found + // scrollable frame. + if (!aDirectionX && !aDirectionY) { + return nullptr; + } + + bool checkIfScrollableX = + aDirectionX && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS); + bool checkIfScrollableY = + aDirectionY && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS); + + nsIFrame* scrollFrame = + !(aOptions & START_FROM_PARENT) ? aTargetFrame : + GetParentFrameToScroll(aTargetFrame); + for (; scrollFrame; scrollFrame = GetParentFrameToScroll(scrollFrame)) { + // Check whether the frame wants to provide us with a scrollable view. + nsIScrollableFrame* scrollableFrame = scrollFrame->GetScrollTargetFrame(); + if (!scrollableFrame) { + // If the frame is a plugin frame, then, the plugin content may handle + // wheel events. Only when the caller computes the scroll target for + // default action handling, we should assume the plugin frame as + // scrollable if the plugin wants to handle wheel events as default + // action. + if (aOptions & INCLUDE_PLUGIN_AS_TARGET) { + nsPluginFrame* pluginFrame = do_QueryFrame(scrollFrame); + if (pluginFrame && + pluginFrame->WantsToHandleWheelEventAsDefaultAction()) { + return scrollFrame; + } + } + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame); + if (menuPopupFrame) { + return nullptr; + } + continue; + } + + nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame); + MOZ_ASSERT(frameToScroll); + + // Don't scroll vertically by mouse-wheel on a single-line text control. + if (checkIfScrollableY) { + if (!CanVerticallyScrollFrameWithWheel(scrollFrame)) { + continue; + } + } + + if (!checkIfScrollableX && !checkIfScrollableY) { + return frameToScroll; + } + + ScrollbarStyles ss = scrollableFrame->GetScrollbarStyles(); + bool hiddenForV = (NS_STYLE_OVERFLOW_HIDDEN == ss.mVertical); + bool hiddenForH = (NS_STYLE_OVERFLOW_HIDDEN == ss.mHorizontal); + if ((hiddenForV && hiddenForH) || + (checkIfScrollableY && !checkIfScrollableX && hiddenForV) || + (checkIfScrollableX && !checkIfScrollableY && hiddenForH)) { + continue; + } + + // For default action, we should climb up the tree if cannot scroll it + // by the event actually. + bool canScroll = WheelHandlingUtils::CanScrollOn(scrollableFrame, + aDirectionX, aDirectionY); + // Comboboxes need special care. + nsIComboboxControlFrame* comboBox = do_QueryFrame(scrollFrame); + if (comboBox) { + if (comboBox->IsDroppedDown()) { + // Don't propagate to parent when drop down menu is active. + return canScroll ? frameToScroll : nullptr; + } + // Always propagate when not dropped down (even if focused). + continue; + } + + if (canScroll) { + return frameToScroll; + } + } + + nsIFrame* newFrame = nsLayoutUtils::GetCrossDocParentFrame( + aTargetFrame->PresContext()->FrameManager()->GetRootFrame()); + aOptions = + static_cast<ComputeScrollTargetOptions>(aOptions & ~START_FROM_PARENT); + return newFrame ? ComputeScrollTarget(newFrame, aEvent, aOptions) : nullptr; +} + +nsSize +EventStateManager::GetScrollAmount(nsPresContext* aPresContext, + WidgetWheelEvent* aEvent, + nsIScrollableFrame* aScrollableFrame) +{ + MOZ_ASSERT(aPresContext); + MOZ_ASSERT(aEvent); + + bool isPage = (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE); + if (aScrollableFrame) { + return isPage ? aScrollableFrame->GetPageScrollAmount() : + aScrollableFrame->GetLineScrollAmount(); + } + + // If there is no scrollable frame and page scrolling, use view port size. + if (isPage) { + return aPresContext->GetVisibleArea().Size(); + } + + // If there is no scrollable frame, we should use root frame's information. + nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame(); + if (!rootFrame) { + return nsSize(0, 0); + } + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetInflatedFontMetricsForFrame(rootFrame); + NS_ENSURE_TRUE(fm, nsSize(0, 0)); + return nsSize(fm->AveCharWidth(), fm->MaxHeight()); +} + +void +EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame, + WidgetWheelEvent* aEvent) +{ + MOZ_ASSERT(aScrollableFrame); + MOZ_ASSERT(aEvent); + + nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame); + MOZ_ASSERT(scrollFrame); + + nsWeakFrame scrollFrameWeak(scrollFrame); + if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak)) { + return; + } + + // Default action's actual scroll amount should be computed from device + // pixels. + nsPresContext* pc = scrollFrame->PresContext(); + nsSize scrollAmount = GetScrollAmount(pc, aEvent, aScrollableFrame); + nsIntSize scrollAmountInDevPixels( + pc->AppUnitsToDevPixels(scrollAmount.width), + pc->AppUnitsToDevPixels(scrollAmount.height)); + nsIntPoint actualDevPixelScrollAmount = + DeltaAccumulator::GetInstance()-> + ComputeScrollAmountForDefaultAction(aEvent, scrollAmountInDevPixels); + + // Don't scroll around the axis whose overflow style is hidden. + ScrollbarStyles overflowStyle = aScrollableFrame->GetScrollbarStyles(); + if (overflowStyle.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) { + actualDevPixelScrollAmount.x = 0; + } + if (overflowStyle.mVertical == NS_STYLE_OVERFLOW_HIDDEN) { + actualDevPixelScrollAmount.y = 0; + } + + nsIScrollbarMediator::ScrollSnapMode snapMode = nsIScrollbarMediator::DISABLE_SNAP; + nsIAtom* origin = nullptr; + switch (aEvent->mDeltaMode) { + case nsIDOMWheelEvent::DOM_DELTA_LINE: + origin = nsGkAtoms::mouseWheel; + snapMode = nsIScrollableFrame::ENABLE_SNAP; + break; + case nsIDOMWheelEvent::DOM_DELTA_PAGE: + origin = nsGkAtoms::pages; + snapMode = nsIScrollableFrame::ENABLE_SNAP; + break; + case nsIDOMWheelEvent::DOM_DELTA_PIXEL: + origin = nsGkAtoms::pixels; + break; + default: + MOZ_CRASH("Invalid deltaMode value comes"); + } + + // We shouldn't scroll more one page at once except when over one page scroll + // is allowed for the event. + nsSize pageSize = aScrollableFrame->GetPageScrollAmount(); + nsIntSize devPixelPageSize(pc->AppUnitsToDevPixels(pageSize.width), + pc->AppUnitsToDevPixels(pageSize.height)); + if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedX(aEvent) && + DeprecatedAbs(actualDevPixelScrollAmount.x) > devPixelPageSize.width) { + actualDevPixelScrollAmount.x = + (actualDevPixelScrollAmount.x >= 0) ? devPixelPageSize.width : + -devPixelPageSize.width; + } + + if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedY(aEvent) && + DeprecatedAbs(actualDevPixelScrollAmount.y) > devPixelPageSize.height) { + actualDevPixelScrollAmount.y = + (actualDevPixelScrollAmount.y >= 0) ? devPixelPageSize.height : + -devPixelPageSize.height; + } + + bool isDeltaModePixel = + (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL); + + nsIScrollableFrame::ScrollMode mode; + switch (aEvent->mScrollType) { + case WidgetWheelEvent::SCROLL_DEFAULT: + if (isDeltaModePixel) { + mode = nsIScrollableFrame::NORMAL; + } else if (aEvent->mFlags.mHandledByAPZ) { + mode = nsIScrollableFrame::SMOOTH_MSD; + } else { + mode = nsIScrollableFrame::SMOOTH; + } + break; + case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY: + mode = nsIScrollableFrame::INSTANT; + break; + case WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY: + mode = nsIScrollableFrame::NORMAL; + break; + case WidgetWheelEvent::SCROLL_SMOOTHLY: + mode = nsIScrollableFrame::SMOOTH; + break; + default: + MOZ_CRASH("Invalid mScrollType value comes"); + } + + nsIScrollableFrame::ScrollMomentum momentum = + aEvent->mIsMomentum ? nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT + : nsIScrollableFrame::NOT_MOMENTUM; + + nsIntPoint overflow; + aScrollableFrame->ScrollBy(actualDevPixelScrollAmount, + nsIScrollableFrame::DEVICE_PIXELS, + mode, &overflow, origin, momentum, snapMode); + + if (!scrollFrameWeak.IsAlive()) { + // If the scroll causes changing the layout, we can think that the event + // has been completely consumed by the content. Then, users probably don't + // want additional action. + aEvent->mOverflowDeltaX = aEvent->mOverflowDeltaY = 0; + } else if (isDeltaModePixel) { + aEvent->mOverflowDeltaX = overflow.x; + aEvent->mOverflowDeltaY = overflow.y; + } else { + aEvent->mOverflowDeltaX = + static_cast<double>(overflow.x) / scrollAmountInDevPixels.width; + aEvent->mOverflowDeltaY = + static_cast<double>(overflow.y) / scrollAmountInDevPixels.height; + } + + // If CSS overflow properties caused not to scroll, the overflowDelta* values + // should be same as delta* values since they may be used as gesture event by + // widget. However, if there is another scrollable element in the ancestor + // along the axis, probably users don't want the operation to cause + // additional action such as moving history. In such case, overflowDelta + // values should stay zero. + if (scrollFrameWeak.IsAlive()) { + if (aEvent->mDeltaX && + overflowStyle.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && + !ComputeScrollTarget(scrollFrame, aEvent, + COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS)) { + aEvent->mOverflowDeltaX = aEvent->mDeltaX; + } + if (aEvent->mDeltaY && + overflowStyle.mVertical == NS_STYLE_OVERFLOW_HIDDEN && + !ComputeScrollTarget(scrollFrame, aEvent, + COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS)) { + aEvent->mOverflowDeltaY = aEvent->mDeltaY; + } + } + + NS_ASSERTION(aEvent->mOverflowDeltaX == 0 || + (aEvent->mOverflowDeltaX > 0) == (aEvent->mDeltaX > 0), + "The sign of mOverflowDeltaX is different from the scroll direction"); + NS_ASSERTION(aEvent->mOverflowDeltaY == 0 || + (aEvent->mOverflowDeltaY > 0) == (aEvent->mDeltaY > 0), + "The sign of mOverflowDeltaY is different from the scroll direction"); + + WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(aEvent); +} + +void +EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent, + nsIFrame* targetFrame) +{ + + NS_ASSERTION(aEvent->mMessage == eGestureNotify, + "DecideGestureEvent called with a non-gesture event"); + + /* Check the ancestor tree to decide if any frame is willing* to receive + * a MozPixelScroll event. If that's the case, the current touch gesture + * will be used as a pan gesture; otherwise it will be a regular + * mousedown/mousemove/click event. + * + * *willing: determine if it makes sense to pan the element using scroll events: + * - For web content: if there are any visible scrollbars on the touch point + * - For XUL: if it's an scrollable element that can currently scroll in some + * direction. + * + * Note: we'll have to one-off various cases to ensure a good usable behavior + */ + WidgetGestureNotifyEvent::PanDirection panDirection = + WidgetGestureNotifyEvent::ePanNone; + bool displayPanFeedback = false; + for (nsIFrame* current = targetFrame; current; + current = nsLayoutUtils::GetCrossDocParentFrame(current)) { + + // e10s - mark remote content as pannable. This is a work around since + // we don't have access to remote frame scroll info here. Apz data may + // assist is solving this. + if (current && IsRemoteTarget(current->GetContent())) { + panDirection = WidgetGestureNotifyEvent::ePanBoth; + // We don't know when we reach bounds, so just disable feedback for now. + displayPanFeedback = false; + break; + } + + nsIAtom* currentFrameType = current->GetType(); + + // Scrollbars should always be draggable + if (currentFrameType == nsGkAtoms::scrollbarFrame) { + panDirection = WidgetGestureNotifyEvent::ePanNone; + break; + } + +#ifdef MOZ_XUL + // Special check for trees + nsTreeBodyFrame* treeFrame = do_QueryFrame(current); + if (treeFrame) { + if (treeFrame->GetHorizontalOverflow()) { + panDirection = WidgetGestureNotifyEvent::ePanHorizontal; + } + if (treeFrame->GetVerticalOverflow()) { + panDirection = WidgetGestureNotifyEvent::ePanVertical; + } + break; + } +#endif + + nsIScrollableFrame* scrollableFrame = do_QueryFrame(current); + if (scrollableFrame) { + if (current->IsFrameOfType(nsIFrame::eXULBox)) { + displayPanFeedback = true; + + nsRect scrollRange = scrollableFrame->GetScrollRange(); + bool canScrollHorizontally = scrollRange.width > 0; + + if (targetFrame->GetType() == nsGkAtoms::menuFrame) { + // menu frames report horizontal scroll when they have submenus + // and we don't want that + canScrollHorizontally = false; + displayPanFeedback = false; + } + + // Vertical panning has priority over horizontal panning, so + // when vertical movement is possible we can just finish the loop. + if (scrollRange.height > 0) { + panDirection = WidgetGestureNotifyEvent::ePanVertical; + break; + } + + if (canScrollHorizontally) { + panDirection = WidgetGestureNotifyEvent::ePanHorizontal; + displayPanFeedback = false; + } + } else { //Not a XUL box + uint32_t scrollbarVisibility = scrollableFrame->GetScrollbarVisibility(); + + //Check if we have visible scrollbars + if (scrollbarVisibility & nsIScrollableFrame::VERTICAL) { + panDirection = WidgetGestureNotifyEvent::ePanVertical; + displayPanFeedback = true; + break; + } + + if (scrollbarVisibility & nsIScrollableFrame::HORIZONTAL) { + panDirection = WidgetGestureNotifyEvent::ePanHorizontal; + displayPanFeedback = true; + } + } + } //scrollableFrame + } //ancestor chain + aEvent->mDisplayPanFeedback = displayPanFeedback; + aEvent->mPanDirection = panDirection; +} + +#ifdef XP_MACOSX +static bool +NodeAllowsClickThrough(nsINode* aNode) +{ + while (aNode) { + if (aNode->IsXULElement()) { + mozilla::dom::Element* element = aNode->AsElement(); + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::always, &nsGkAtoms::never, nullptr}; + switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::clickthrough, + strings, eCaseMatters)) { + case 0: + return true; + case 1: + return false; + } + } + aNode = nsContentUtils::GetCrossDocParentNode(aNode); + } + return true; +} +#endif + +void +EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent, + nsEventStatus& aStatus, + bool dispatchedToContentProcess) +{ + if (aStatus == nsEventStatus_eConsumeNoDefault || + aKeyboardEvent->mInputMethodAppState == WidgetKeyboardEvent::eHandling) { + return; + } + + // XXX Currently, our automated tests don't support mKeyNameIndex. + // Therefore, we still need to handle this with keyCode. + switch(aKeyboardEvent->mKeyCode) { + case NS_VK_TAB: + case NS_VK_F6: + // This is to prevent keyboard scrolling while alt modifier in use. + if (!aKeyboardEvent->IsAlt()) { + aStatus = nsEventStatus_eConsumeNoDefault; + + // Handling the tab event after it was sent to content is bad, + // because to the FocusManager the remote-browser looks like one + // element, so we would just move the focus to the next element + // in chrome, instead of handling it in content. + if (dispatchedToContentProcess) + break; + + EnsureDocument(mPresContext); + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && mDocument) { + // Shift focus forward or back depending on shift key + bool isDocMove = + aKeyboardEvent->IsControl() || aKeyboardEvent->mKeyCode == NS_VK_F6; + uint32_t dir = aKeyboardEvent->IsShift() ? + (isDocMove ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARDDOC) : + static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARD)) : + (isDocMove ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARDDOC) : + static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARD)); + nsCOMPtr<nsIDOMElement> result; + fm->MoveFocus(mDocument->GetWindow(), nullptr, dir, + nsIFocusManager::FLAG_BYKEY, + getter_AddRefs(result)); + } + } + return; + case 0: + // We handle keys with no specific keycode value below. + break; + default: + return; + } + + switch(aKeyboardEvent->mKeyNameIndex) { + case KEY_NAME_INDEX_ZoomIn: + case KEY_NAME_INDEX_ZoomOut: + ChangeFullZoom( + aKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_ZoomIn ? 1 : -1); + aStatus = nsEventStatus_eConsumeNoDefault; + break; + default: + break; + } +} + +nsresult +EventStateManager::PostHandleEvent(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIFrame* aTargetFrame, + nsEventStatus* aStatus) +{ + NS_ENSURE_ARG(aPresContext); + NS_ENSURE_ARG_POINTER(aStatus); + + mCurrentTarget = aTargetFrame; + mCurrentTargetContent = nullptr; + + bool dispatchedToContentProcess = HandleCrossProcessEvent(aEvent, aStatus); + // NOTE: the above call may have destroyed aTargetFrame, please use + // mCurrentTarget henceforth. This is to avoid using it accidentally: + aTargetFrame = nullptr; + + // Most of the events we handle below require a frame. + // Add special cases here. + if (!mCurrentTarget && aEvent->mMessage != eMouseUp && + aEvent->mMessage != eMouseDown) { + return NS_OK; + } + + //Keep the prescontext alive, we might need it after event dispatch + RefPtr<nsPresContext> presContext = aPresContext; + nsresult ret = NS_OK; + + switch (aEvent->mMessage) { + case eMouseDown: + { + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->button == WidgetMouseEvent::eLeftButton && + !sNormalLMouseEventInProcess) { + // We got a mouseup event while a mousedown event was being processed. + // Make sure that the capturing content is cleared. + nsIPresShell::SetCapturingContent(nullptr, 0); + break; + } + + // For remote content, capture the event in the parent process at the + // <xul:browser remote> element. This will ensure that subsequent mousemove/mouseup + // events will continue to be dispatched to this element and therefore forwarded + // to the child. + if (dispatchedToContentProcess && !nsIPresShell::GetCapturingContent()) { + nsIContent* content = mCurrentTarget ? mCurrentTarget->GetContent() : nullptr; + nsIPresShell::SetCapturingContent(content, 0); + } + + nsCOMPtr<nsIContent> activeContent; + if (nsEventStatus_eConsumeNoDefault != *aStatus) { + nsCOMPtr<nsIContent> newFocus; + bool suppressBlur = false; + if (mCurrentTarget) { + mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus)); + const nsStyleUserInterface* ui = mCurrentTarget->StyleUserInterface(); + activeContent = mCurrentTarget->GetContent(); + + // In some cases, we do not want to even blur the current focused + // element. Those cases are: + // 1. -moz-user-focus CSS property is set to 'ignore'; + // 2. Element with NS_EVENT_STATE_DISABLED + // (aka :disabled pseudo-class for HTML element); + // 3. XUL control element has the disabled property set to 'true'. + // + // We can't use nsIFrame::IsFocusable() because we want to blur when + // we click on a visibility: none element. + // We can't use nsIContent::IsFocusable() because we want to blur when + // we click on a non-focusable element like a <div>. + // We have to use |aEvent->mTarget| to not make sure we do not check + // an anonymous node of the targeted element. + suppressBlur = (ui->mUserFocus == StyleUserFocus::Ignore); + + if (!suppressBlur) { + nsCOMPtr<Element> element = do_QueryInterface(aEvent->mTarget); + suppressBlur = element && + element->State().HasState(NS_EVENT_STATE_DISABLED); + } + + if (!suppressBlur) { + nsCOMPtr<nsIDOMXULControlElement> xulControl = + do_QueryInterface(aEvent->mTarget); + if (xulControl) { + bool disabled; + xulControl->GetDisabled(&disabled); + suppressBlur = disabled; + } + } + } + + if (!suppressBlur) { + suppressBlur = nsContentUtils::IsUserFocusIgnored(activeContent); + } + + nsIFrame* currFrame = mCurrentTarget; + + // When a root content which isn't editable but has an editable HTML + // <body> element is clicked, we should redirect the focus to the + // the <body> element. E.g., when an user click bottom of the editor + // where is outside of the <body> element, the <body> should be focused + // and the user can edit immediately after that. + // + // NOTE: The newFocus isn't editable that also means it's not in + // designMode. In designMode, all contents are not focusable. + if (newFocus && !newFocus->IsEditable()) { + nsIDocument *doc = newFocus->GetComposedDoc(); + if (doc && newFocus == doc->GetRootElement()) { + nsIContent *bodyContent = + nsLayoutUtils::GetEditableRootContentByContentEditable(doc); + if (bodyContent) { + nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame(); + if (bodyFrame) { + currFrame = bodyFrame; + newFocus = bodyContent; + } + } + } + } + + // When the mouse is pressed, the default action is to focus the + // target. Look for the nearest enclosing focusable frame. + while (currFrame) { + // If the mousedown happened inside a popup, don't + // try to set focus on one of its containing elements + const nsStyleDisplay* display = currFrame->StyleDisplay(); + if (display->mDisplay == StyleDisplay::Popup) { + newFocus = nullptr; + break; + } + + int32_t tabIndexUnused; + if (currFrame->IsFocusable(&tabIndexUnused, true)) { + newFocus = currFrame->GetContent(); + nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(newFocus)); + if (domElement) + break; + } + currFrame = currFrame->GetParent(); + } + + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + // if something was found to focus, focus it. Otherwise, if the + // element that was clicked doesn't have -moz-user-focus: ignore, + // clear the existing focus. For -moz-user-focus: ignore, the focus + // is just left as is. + // Another effect of mouse clicking, handled in nsSelection, is that + // it should update the caret position to where the mouse was + // clicked. Because the focus is cleared when clicking on a + // non-focusable node, the next press of the tab key will cause + // focus to be shifted from the caret position instead of the root. + if (newFocus && currFrame) { + // use the mouse flag and the noscroll flag so that the content + // doesn't unexpectedly scroll when clicking an element that is + // only half visible + uint32_t flags = nsIFocusManager::FLAG_BYMOUSE | + nsIFocusManager::FLAG_NOSCROLL; + // If this was a touch-generated event, pass that information: + if (mouseEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { + flags |= nsIFocusManager::FLAG_BYTOUCH; + } + nsCOMPtr<nsIDOMElement> newFocusElement = do_QueryInterface(newFocus); + fm->SetFocus(newFocusElement, flags); + } + else if (!suppressBlur) { + // clear the focus within the frame and then set it as the + // focused frame + EnsureDocument(mPresContext); + if (mDocument) { +#ifdef XP_MACOSX + if (!activeContent || !activeContent->IsXULElement()) +#endif + fm->ClearFocus(mDocument->GetWindow()); + fm->SetFocusedWindow(mDocument->GetWindow()); + } + } + } + + // The rest is left button-specific. + if (mouseEvent->button != WidgetMouseEvent::eLeftButton) { + break; + } + + if (activeContent) { + // The nearest enclosing element goes into the + // :active state. If we fail the QI to DOMElement, + // then we know we're only a node, and that we need + // to obtain our parent element and put it into :active + // instead. + nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(activeContent)); + if (!elt) { + nsIContent* par = activeContent->GetParent(); + if (par) + activeContent = par; + } + } + } + else { + // if we're here, the event handler returned false, so stop + // any of our own processing of a drag. Workaround for bug 43258. + StopTrackingDragGesture(); + + // When the event was cancelled, there is currently a chrome document + // focused and a mousedown just occurred on a content document, ensure + // that the window that was clicked is focused. + EnsureDocument(mPresContext); + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (mDocument && fm) { + nsCOMPtr<mozIDOMWindowProxy> window; + fm->GetFocusedWindow(getter_AddRefs(window)); + auto* currentWindow = nsPIDOMWindowOuter::From(window); + if (currentWindow && mDocument->GetWindow() && + currentWindow != mDocument->GetWindow() && + !nsContentUtils::IsChromeDoc(mDocument)) { + nsCOMPtr<nsPIDOMWindowOuter> currentTop; + nsCOMPtr<nsPIDOMWindowOuter> newTop; + currentTop = currentWindow->GetTop(); + newTop = mDocument->GetWindow()->GetTop(); + nsCOMPtr<nsIDocument> currentDoc = currentWindow->GetExtantDoc(); + if (nsContentUtils::IsChromeDoc(currentDoc) || + (currentTop && newTop && currentTop != newTop)) { + fm->SetFocusedWindow(mDocument->GetWindow()); + } + } + } + } + SetActiveManager(this, activeContent); + } + break; + case ePointerCancel: { + if(WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { + GenerateMouseEnterExit(mouseEvent); + } + // After firing the pointercancel event, a user agent must also fire a + // pointerout event followed by a pointerleave event. + MOZ_FALLTHROUGH; + } + case ePointerUp: { + WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent(); + // After UP/Cancel Touch pointers become invalid so we can remove relevant helper from Table + // Mouse/Pen pointers are valid all the time (not only between down/up) + if (pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { + mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId); + GenerateMouseEnterExit(pointerEvent); + } + break; + } + case eMouseUp: + { + ClearGlobalActiveContent(this); + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent && mouseEvent->IsReal()) { + if (!mCurrentTarget) { + GetEventTarget(); + } + // Make sure to dispatch the click even if there is no frame for + // the current target element. This is required for Web compatibility. + ret = CheckForAndDispatchClick(mouseEvent, aStatus); + } + + nsIPresShell *shell = presContext->GetPresShell(); + if (shell) { + RefPtr<nsFrameSelection> frameSelection = shell->FrameSelection(); + frameSelection->SetDragState(false); + } + } + break; + case eWheelOperationEnd: + { + MOZ_ASSERT(aEvent->IsTrusted()); + ScrollbarsForWheel::MayInactivate(); + WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent(); + nsIScrollableFrame* scrollTarget = + do_QueryFrame(ComputeScrollTarget(mCurrentTarget, wheelEvent, + COMPUTE_DEFAULT_ACTION_TARGET)); + if (scrollTarget) { + scrollTarget->ScrollSnap(); + } + } + break; + case eWheel: + case eWheelOperationStart: + { + MOZ_ASSERT(aEvent->IsTrusted()); + + if (*aStatus == nsEventStatus_eConsumeNoDefault) { + ScrollbarsForWheel::Inactivate(); + break; + } + + WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent(); + + // Check if the frame to scroll before checking the default action + // because if the scroll target is a plugin, the default action should be + // chosen by the plugin rather than by our prefs. + nsIFrame* frameToScroll = + ComputeScrollTarget(mCurrentTarget, wheelEvent, + COMPUTE_DEFAULT_ACTION_TARGET); + nsPluginFrame* pluginFrame = do_QueryFrame(frameToScroll); + + // When APZ is enabled, the actual scroll animation might be handled by + // the compositor. + WheelPrefs::Action action; + if (pluginFrame) { + MOZ_ASSERT(pluginFrame->WantsToHandleWheelEventAsDefaultAction()); + action = WheelPrefs::ACTION_SEND_TO_PLUGIN; + } else if (wheelEvent->mFlags.mHandledByAPZ) { + action = WheelPrefs::ACTION_NONE; + } else { + action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent); + } + switch (action) { + case WheelPrefs::ACTION_SCROLL: { + // For scrolling of default action, we should honor the mouse wheel + // transaction. + + ScrollbarsForWheel::PrepareToScrollText(this, mCurrentTarget, wheelEvent); + + if (aEvent->mMessage != eWheel || + (!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) { + break; + } + + nsIScrollableFrame* scrollTarget = do_QueryFrame(frameToScroll); + ScrollbarsForWheel::SetActiveScrollTarget(scrollTarget); + + nsIFrame* rootScrollFrame = !mCurrentTarget ? nullptr : + mCurrentTarget->PresContext()->PresShell()->GetRootScrollFrame(); + nsIScrollableFrame* rootScrollableFrame = nullptr; + if (rootScrollFrame) { + rootScrollableFrame = do_QueryFrame(rootScrollFrame); + } + if (!scrollTarget || scrollTarget == rootScrollableFrame) { + wheelEvent->mViewPortIsOverscrolled = true; + } + wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX; + wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY; + WheelPrefs::GetInstance()-> + CancelApplyingUserPrefsFromOverflowDelta(wheelEvent); + if (scrollTarget) { + DoScrollText(scrollTarget, wheelEvent); + } else { + WheelTransaction::EndTransaction(); + ScrollbarsForWheel::Inactivate(); + } + break; + } + case WheelPrefs::ACTION_HISTORY: { + // If this event doesn't cause eLegacyMouseLineOrPageScroll event or + // the direction is oblique, don't perform history back/forward. + int32_t intDelta = wheelEvent->GetPreferredIntDelta(); + if (!intDelta) { + break; + } + DoScrollHistory(intDelta); + break; + } + case WheelPrefs::ACTION_ZOOM: { + // If this event doesn't cause eLegacyMouseLineOrPageScroll event or + // the direction is oblique, don't perform zoom in/out. + int32_t intDelta = wheelEvent->GetPreferredIntDelta(); + if (!intDelta) { + break; + } + DoScrollZoom(mCurrentTarget, intDelta); + break; + } + case WheelPrefs::ACTION_SEND_TO_PLUGIN: + MOZ_ASSERT(pluginFrame); + + if (wheelEvent->mMessage != eWheel || + (!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) { + break; + } + + MOZ_ASSERT(static_cast<void*>(frameToScroll) == + static_cast<void*>(pluginFrame)); + if (!WheelTransaction::WillHandleDefaultAction(wheelEvent, + frameToScroll)) { + break; + } + + pluginFrame->HandleWheelEventAsDefaultAction(wheelEvent); + break; + case WheelPrefs::ACTION_NONE: + default: + bool allDeltaOverflown = false; + if (wheelEvent->mFlags.mHandledByAPZ) { + if (wheelEvent->mCanTriggerSwipe) { + // For events that can trigger swipes, APZ needs to know whether + // scrolling is possible in the requested direction. It does this + // by looking at the scroll overflow values on mCanTriggerSwipe + // events after they have been processed. + allDeltaOverflown = + !ComputeScrollTarget(mCurrentTarget, wheelEvent, + COMPUTE_DEFAULT_ACTION_TARGET); + } + } else { + // The event was processed neither by APZ nor by us, so all of the + // delta values must be overflown delta values. + allDeltaOverflown = true; + } + + if (!allDeltaOverflown) { + break; + } + wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX; + wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY; + WheelPrefs::GetInstance()-> + CancelApplyingUserPrefsFromOverflowDelta(wheelEvent); + wheelEvent->mViewPortIsOverscrolled = true; + break; + } + *aStatus = nsEventStatus_eConsumeNoDefault; + } + break; + + case eGestureNotify: + { + if (nsEventStatus_eConsumeNoDefault != *aStatus) { + DecideGestureEvent(aEvent->AsGestureNotifyEvent(), mCurrentTarget); + } + } + break; + + case eDragEnter: + case eDragOver: + { + NS_ASSERTION(aEvent->mClass == eDragEventClass, "Expected a drag event"); + + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (!dragSession) + break; + + // Reset the flag. + dragSession->SetOnlyChromeDrop(false); + if (mPresContext) { + EnsureDocument(mPresContext); + } + bool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument); + + // the initial dataTransfer is the one from the dragstart event that + // was set on the dragSession when the drag began. + nsCOMPtr<nsIDOMDataTransfer> dataTransfer; + nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer; + dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer)); + + WidgetDragEvent *dragEvent = aEvent->AsDragEvent(); + + // collect any changes to moz cursor settings stored in the event's + // data transfer. + UpdateDragDataTransfer(dragEvent); + + // cancelling a dragenter or dragover event means that a drop should be + // allowed, so update the dropEffect and the canDrop state to indicate + // that a drag is allowed. If the event isn't cancelled, a drop won't be + // allowed. Essentially, to allow a drop somewhere, specify the effects + // using the effectAllowed and dropEffect properties in a dragenter or + // dragover event and cancel the event. To not allow a drop somewhere, + // don't cancel the event or set the effectAllowed or dropEffect to + // "none". This way, if the event is just ignored, no drop will be + // allowed. + uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE; + uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE; + if (nsEventStatus_eConsumeNoDefault == *aStatus) { + // if the event has a dataTransfer set, use it. + if (dragEvent->mDataTransfer) { + // get the dataTransfer and the dropEffect that was set on it + dataTransfer = do_QueryInterface(dragEvent->mDataTransfer); + dataTransfer->GetDropEffectInt(&dropEffect); + } + else { + // if dragEvent->mDataTransfer is null, it means that no attempt was + // made to access the dataTransfer during the event, yet the event + // was cancelled. Instead, use the initial data transfer available + // from the drag session. The drop effect would not have been + // initialized (which is done in DragEvent::GetDataTransfer), + // so set it from the drag action. We'll still want to filter it + // based on the effectAllowed below. + dataTransfer = initialDataTransfer; + + dragSession->GetDragAction(&action); + + // filter the drop effect based on the action. Use UNINITIALIZED as + // any effect is allowed. + dropEffect = nsContentUtils::FilterDropEffect(action, + nsIDragService::DRAGDROP_ACTION_UNINITIALIZED); + } + + // At this point, if the dataTransfer is null, it means that the + // drag was originally started by directly calling the drag service. + // Just assume that all effects are allowed. + uint32_t effectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED; + if (dataTransfer) + dataTransfer->GetEffectAllowedInt(&effectAllowed); + + // set the drag action based on the drop effect and effect allowed. + // The drop effect field on the drag transfer object specifies the + // desired current drop effect. However, it cannot be used if the + // effectAllowed state doesn't include that type of action. If the + // dropEffect is "none", then the action will be 'none' so a drop will + // not be allowed. + if (effectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED || + dropEffect & effectAllowed) + action = dropEffect; + + if (action == nsIDragService::DRAGDROP_ACTION_NONE) + dropEffect = nsIDragService::DRAGDROP_ACTION_NONE; + + // inform the drag session that a drop is allowed on this node. + dragSession->SetDragAction(action); + dragSession->SetCanDrop(action != nsIDragService::DRAGDROP_ACTION_NONE); + + // For now, do this only for dragover. + //XXXsmaug dragenter needs some more work. + if (aEvent->mMessage == eDragOver && !isChromeDoc) { + // Someone has called preventDefault(), check whether is was on + // content or chrome. + dragSession->SetOnlyChromeDrop( + !dragEvent->mDefaultPreventedOnContent); + } + } else if (aEvent->mMessage == eDragOver && !isChromeDoc) { + // No one called preventDefault(), so handle drop only in chrome. + dragSession->SetOnlyChromeDrop(true); + } + if (ContentChild* child = ContentChild::GetSingleton()) { + child->SendUpdateDropEffect(action, dropEffect); + } + if (dispatchedToContentProcess) { + dragSession->SetCanDrop(true); + } else if (initialDataTransfer) { + // Now set the drop effect in the initial dataTransfer. This ensures + // that we can get the desired drop effect in the drop event. For events + // dispatched to content, the content process will take care of setting + // this. + initialDataTransfer->SetDropEffectInt(dropEffect); + } + } + break; + + case eDrop: + { + sLastDragOverFrame = nullptr; + ClearGlobalActiveContent(this); + break; + } + case eDragExit: + // 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()); + break; + + case eBeforeKeyUp: + case eKeyUp: + case eAfterKeyUp: + break; + + case eKeyPress: + { + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); + PostHandleKeyboardEvent(keyEvent, *aStatus, dispatchedToContentProcess); + } + break; + + case eMouseEnterIntoWidget: + if (mCurrentTarget) { + nsCOMPtr<nsIContent> targetContent; + mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); + SetContentState(targetContent, NS_EVENT_STATE_HOVER); + } + break; + +#ifdef XP_MACOSX + case eMouseActivate: + if (mCurrentTarget) { + nsCOMPtr<nsIContent> targetContent; + mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); + if (!NodeAllowsClickThrough(targetContent)) { + *aStatus = nsEventStatus_eConsumeNoDefault; + } + } + break; +#endif + + default: + break; + } + + //Reset target frame to null to avoid mistargeting after reentrant event + mCurrentTarget = nullptr; + mCurrentTargetContent = nullptr; + + return ret; +} + +TabParent* +EventStateManager::GetCrossProcessTarget() +{ + return IMEStateManager::GetActiveTabParent(); +} + +bool +EventStateManager::IsTargetCrossProcess(WidgetGUIEvent* aEvent) +{ + // Check to see if there is a focused, editable content in chrome, + // in that case, do not forward IME events to content + nsIContent *focusedContent = GetFocusedContent(); + if (focusedContent && focusedContent->IsEditable()) + return false; + return IMEStateManager::GetActiveTabParent() != nullptr; +} + +void +EventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext) +{ + IMEStateManager::OnDestroyPresContext(aPresContext); + if (mHoverContent) { + // Bug 70855: Presentation is going away, possibly for a reframe. + // Reset the hover state so that if we're recreating the presentation, + // we won't have the old hover state still set in the new presentation, + // as if the new presentation is resized, a new element may be hovered. + SetContentState(nullptr, NS_EVENT_STATE_HOVER); + } + mPointersEnterLeaveHelper.Clear(); +} + +void +EventStateManager::SetPresContext(nsPresContext* aPresContext) +{ + mPresContext = aPresContext; +} + +void +EventStateManager::ClearFrameRefs(nsIFrame* aFrame) +{ + if (aFrame && aFrame == mCurrentTarget) { + mCurrentTargetContent = aFrame->GetContent(); + } +} + +void +EventStateManager::UpdateCursor(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIFrame* aTargetFrame, + nsEventStatus* aStatus) +{ + if (aTargetFrame && IsRemoteTarget(aTargetFrame->GetContent())) { + return; + } + + int32_t cursor = NS_STYLE_CURSOR_DEFAULT; + imgIContainer* container = nullptr; + bool haveHotspot = false; + float hotspotX = 0.0f, hotspotY = 0.0f; + + //If cursor is locked just use the locked one + if (mLockCursor) { + cursor = mLockCursor; + } + //If not locked, look for correct cursor + else if (aTargetFrame) { + nsIFrame::Cursor framecursor; + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, + aTargetFrame); + // Avoid setting cursor when the mouse is over a windowless pluign. + if (NS_FAILED(aTargetFrame->GetCursor(pt, framecursor))) { + if (XRE_IsContentProcess()) { + mLastFrameConsumedSetCursor = true; + } + return; + } + // Make sure cursors get reset after the mouse leaves a + // windowless plugin frame. + if (mLastFrameConsumedSetCursor) { + ClearCachedWidgetCursor(aTargetFrame); + mLastFrameConsumedSetCursor = false; + } + // If the current cursor is from the same frame, and it is now + // loading some new image for the cursor, we should wait for a + // while rather than taking its fallback cursor directly. + if (framecursor.mLoading && + gLastCursorSourceFrame == aTargetFrame && + TimeStamp::NowLoRes() - gLastCursorUpdateTime < + TimeDuration::FromMilliseconds(kCursorLoadingTimeout)) { + return; + } + cursor = framecursor.mCursor; + container = framecursor.mContainer; + haveHotspot = framecursor.mHaveHotspot; + hotspotX = framecursor.mHotspotX; + hotspotY = framecursor.mHotspotY; + } + + if (nsContentUtils::UseActivityCursor()) { + // Check whether or not to show the busy cursor + nsCOMPtr<nsIDocShell> docShell(aPresContext->GetDocShell()); + if (!docShell) return; + uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE; + docShell->GetBusyFlags(&busyFlags); + + // Show busy cursor everywhere before page loads + // and just replace the arrow cursor after page starts loading + if (busyFlags & nsIDocShell::BUSY_FLAGS_BUSY && + (cursor == NS_STYLE_CURSOR_AUTO || cursor == NS_STYLE_CURSOR_DEFAULT)) + { + cursor = NS_STYLE_CURSOR_SPINNING; + container = nullptr; + } + } + + if (aTargetFrame) { + SetCursor(cursor, container, haveHotspot, hotspotX, hotspotY, + aTargetFrame->GetNearestWidget(), false); + gLastCursorSourceFrame = aTargetFrame; + gLastCursorUpdateTime = TimeStamp::NowLoRes(); + } + + if (mLockCursor || NS_STYLE_CURSOR_AUTO != cursor) { + *aStatus = nsEventStatus_eConsumeDoDefault; + } +} + +void +EventStateManager::ClearCachedWidgetCursor(nsIFrame* aTargetFrame) +{ + if (!aTargetFrame) { + return; + } + nsIWidget* aWidget = aTargetFrame->GetNearestWidget(); + if (!aWidget) { + return; + } + aWidget->ClearCachedCursor(); +} + +nsresult +EventStateManager::SetCursor(int32_t aCursor, imgIContainer* aContainer, + bool aHaveHotspot, + float aHotspotX, float aHotspotY, + nsIWidget* aWidget, bool aLockCursor) +{ + EnsureDocument(mPresContext); + NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); + sMouseOverDocument = mDocument.get(); + + nsCursor c; + + NS_ENSURE_TRUE(aWidget, NS_ERROR_FAILURE); + if (aLockCursor) { + if (NS_STYLE_CURSOR_AUTO != aCursor) { + mLockCursor = aCursor; + } + else { + //If cursor style is set to auto we unlock the cursor again. + mLockCursor = 0; + } + } + switch (aCursor) { + default: + case NS_STYLE_CURSOR_AUTO: + case NS_STYLE_CURSOR_DEFAULT: + c = eCursor_standard; + break; + case NS_STYLE_CURSOR_POINTER: + c = eCursor_hyperlink; + break; + case NS_STYLE_CURSOR_CROSSHAIR: + c = eCursor_crosshair; + break; + case NS_STYLE_CURSOR_MOVE: + c = eCursor_move; + break; + case NS_STYLE_CURSOR_TEXT: + c = eCursor_select; + break; + case NS_STYLE_CURSOR_WAIT: + c = eCursor_wait; + break; + case NS_STYLE_CURSOR_HELP: + c = eCursor_help; + break; + case NS_STYLE_CURSOR_N_RESIZE: + c = eCursor_n_resize; + break; + case NS_STYLE_CURSOR_S_RESIZE: + c = eCursor_s_resize; + break; + case NS_STYLE_CURSOR_W_RESIZE: + c = eCursor_w_resize; + break; + case NS_STYLE_CURSOR_E_RESIZE: + c = eCursor_e_resize; + break; + case NS_STYLE_CURSOR_NW_RESIZE: + c = eCursor_nw_resize; + break; + case NS_STYLE_CURSOR_SE_RESIZE: + c = eCursor_se_resize; + break; + case NS_STYLE_CURSOR_NE_RESIZE: + c = eCursor_ne_resize; + break; + case NS_STYLE_CURSOR_SW_RESIZE: + c = eCursor_sw_resize; + break; + case NS_STYLE_CURSOR_COPY: // CSS3 + c = eCursor_copy; + break; + case NS_STYLE_CURSOR_ALIAS: + c = eCursor_alias; + break; + case NS_STYLE_CURSOR_CONTEXT_MENU: + c = eCursor_context_menu; + break; + case NS_STYLE_CURSOR_CELL: + c = eCursor_cell; + break; + case NS_STYLE_CURSOR_GRAB: + c = eCursor_grab; + break; + case NS_STYLE_CURSOR_GRABBING: + c = eCursor_grabbing; + break; + case NS_STYLE_CURSOR_SPINNING: + c = eCursor_spinning; + break; + case NS_STYLE_CURSOR_ZOOM_IN: + c = eCursor_zoom_in; + break; + case NS_STYLE_CURSOR_ZOOM_OUT: + c = eCursor_zoom_out; + break; + case NS_STYLE_CURSOR_NOT_ALLOWED: + c = eCursor_not_allowed; + break; + case NS_STYLE_CURSOR_COL_RESIZE: + c = eCursor_col_resize; + break; + case NS_STYLE_CURSOR_ROW_RESIZE: + c = eCursor_row_resize; + break; + case NS_STYLE_CURSOR_NO_DROP: + c = eCursor_no_drop; + break; + case NS_STYLE_CURSOR_VERTICAL_TEXT: + c = eCursor_vertical_text; + break; + case NS_STYLE_CURSOR_ALL_SCROLL: + c = eCursor_all_scroll; + break; + case NS_STYLE_CURSOR_NESW_RESIZE: + c = eCursor_nesw_resize; + break; + case NS_STYLE_CURSOR_NWSE_RESIZE: + c = eCursor_nwse_resize; + break; + case NS_STYLE_CURSOR_NS_RESIZE: + c = eCursor_ns_resize; + break; + case NS_STYLE_CURSOR_EW_RESIZE: + c = eCursor_ew_resize; + break; + case NS_STYLE_CURSOR_NONE: + c = eCursor_none; + break; + } + + // First, try the imgIContainer, if non-null + nsresult rv = NS_ERROR_FAILURE; + if (aContainer) { + uint32_t hotspotX, hotspotY; + + // css3-ui says to use the CSS-specified hotspot if present, + // otherwise use the intrinsic hotspot, otherwise use the top left + // corner. + if (aHaveHotspot) { + int32_t imgWidth, imgHeight; + aContainer->GetWidth(&imgWidth); + aContainer->GetHeight(&imgHeight); + + // XXX std::max(NS_lround(x), 0)? + hotspotX = aHotspotX > 0.0f + ? uint32_t(aHotspotX + 0.5f) : uint32_t(0); + if (hotspotX >= uint32_t(imgWidth)) + hotspotX = imgWidth - 1; + hotspotY = aHotspotY > 0.0f + ? uint32_t(aHotspotY + 0.5f) : uint32_t(0); + if (hotspotY >= uint32_t(imgHeight)) + hotspotY = imgHeight - 1; + } else { + hotspotX = 0; + hotspotY = 0; + nsCOMPtr<nsIProperties> props(do_QueryInterface(aContainer)); + if (props) { + nsCOMPtr<nsISupportsPRUint32> hotspotXWrap, hotspotYWrap; + + props->Get("hotspotX", NS_GET_IID(nsISupportsPRUint32), getter_AddRefs(hotspotXWrap)); + props->Get("hotspotY", NS_GET_IID(nsISupportsPRUint32), getter_AddRefs(hotspotYWrap)); + + if (hotspotXWrap) + hotspotXWrap->GetData(&hotspotX); + if (hotspotYWrap) + hotspotYWrap->GetData(&hotspotY); + } + } + + rv = aWidget->SetCursor(aContainer, hotspotX, hotspotY); + } + + if (NS_FAILED(rv)) + aWidget->SetCursor(c); + + return NS_OK; +} + +class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback +{ +public: + explicit ESMEventCB(nsIContent* aTarget) : mTarget(aTarget) {} + + virtual void HandleEvent(EventChainPostVisitor& aVisitor) + { + if (aVisitor.mPresContext) { + nsIFrame* frame = aVisitor.mPresContext->GetPrimaryFrameFor(mTarget); + if (frame) { + frame->HandleEvent(aVisitor.mPresContext, + aVisitor.mEvent->AsGUIEvent(), + &aVisitor.mEventStatus); + } + } + } + + nsCOMPtr<nsIContent> mTarget; +}; + +/*static*/ bool +EventStateManager::IsHandlingUserInput() +{ + if (sUserInputEventDepth <= 0) { + return false; + } + + TimeDuration timeout = nsContentUtils::HandlingUserInputTimeout(); + return timeout <= TimeDuration(0) || + (TimeStamp::Now() - sHandlingInputStart) <= timeout; +} + +static void +CreateMouseOrPointerWidgetEvent(WidgetMouseEvent* aMouseEvent, + EventMessage aMessage, + nsIContent* aRelatedContent, + nsAutoPtr<WidgetMouseEvent>& aNewEvent) +{ + WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent(); + if (sourcePointer) { + PROFILER_LABEL("Input", "DispatchPointerEvent", + js::ProfileEntry::Category::EVENTS); + + nsAutoPtr<WidgetPointerEvent> newPointerEvent; + newPointerEvent = + new WidgetPointerEvent(aMouseEvent->IsTrusted(), aMessage, + aMouseEvent->mWidget); + newPointerEvent->mIsPrimary = sourcePointer->mIsPrimary; + newPointerEvent->mWidth = sourcePointer->mWidth; + newPointerEvent->mHeight = sourcePointer->mHeight; + newPointerEvent->inputSource = sourcePointer->inputSource; + newPointerEvent->relatedTarget = + nsIPresShell::GetPointerCapturingContent(sourcePointer->pointerId) + ? nullptr + : aRelatedContent; + aNewEvent = newPointerEvent.forget(); + } else { + aNewEvent = + new WidgetMouseEvent(aMouseEvent->IsTrusted(), aMessage, + aMouseEvent->mWidget, WidgetMouseEvent::eReal); + aNewEvent->relatedTarget = aRelatedContent; + } + aNewEvent->mRefPoint = aMouseEvent->mRefPoint; + aNewEvent->mModifiers = aMouseEvent->mModifiers; + aNewEvent->button = aMouseEvent->button; + aNewEvent->buttons = aMouseEvent->buttons; + aNewEvent->pressure = aMouseEvent->pressure; + aNewEvent->mPluginEvent = aMouseEvent->mPluginEvent; + aNewEvent->inputSource = aMouseEvent->inputSource; + aNewEvent->pointerId = aMouseEvent->pointerId; +} + +nsIFrame* +EventStateManager::DispatchMouseOrPointerEvent(WidgetMouseEvent* aMouseEvent, + EventMessage aMessage, + nsIContent* aTargetContent, + nsIContent* aRelatedContent) +{ + // http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html#methods + // "[When the mouse is locked on an element...e]vents that require the concept + // of a mouse cursor must not be dispatched (for example: mouseover, mouseout). + if (sIsPointerLocked && + (aMessage == eMouseLeave || + aMessage == eMouseEnter || + aMessage == eMouseOver || + aMessage == eMouseOut)) { + mCurrentTargetContent = nullptr; + nsCOMPtr<Element> pointerLockedElement = + do_QueryReferent(EventStateManager::sPointerLockedElement); + if (!pointerLockedElement) { + NS_WARNING("Should have pointer locked element, but didn't."); + return nullptr; + } + nsCOMPtr<nsIContent> content = do_QueryInterface(pointerLockedElement); + return mPresContext->GetPrimaryFrameFor(content); + } + + mCurrentTargetContent = nullptr; + + if (!aTargetContent) { + return nullptr; + } + + nsCOMPtr<nsIContent> targetContent = aTargetContent; + nsCOMPtr<nsIContent> relatedContent = aRelatedContent; + + nsAutoPtr<WidgetMouseEvent> dispatchEvent; + CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage, + relatedContent, dispatchEvent); + + nsWeakFrame previousTarget = mCurrentTarget; + mCurrentTargetContent = targetContent; + + nsIFrame* targetFrame = nullptr; + + if (aMouseEvent->AsMouseEvent()) { + PROFILER_LABEL("Input", "DispatchMouseEvent", + js::ProfileEntry::Category::EVENTS); + } + + nsEventStatus status = nsEventStatus_eIgnore; + ESMEventCB callback(targetContent); + EventDispatcher::Dispatch(targetContent, mPresContext, dispatchEvent, nullptr, + &status, &callback); + + if (mPresContext) { + // Although the primary frame was checked in event callback, it may not be + // the same object after event dispatch and handling, so refetch it. + targetFrame = mPresContext->GetPrimaryFrameFor(targetContent); + + // If we are entering/leaving remote content, dispatch a mouse enter/exit + // event to the remote frame. + if (IsRemoteTarget(targetContent)) { + if (aMessage == eMouseOut) { + // For remote content, send a "top-level" widget mouse exit event. + nsAutoPtr<WidgetMouseEvent> remoteEvent; + CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget, + relatedContent, remoteEvent); + remoteEvent->mExitFrom = WidgetMouseEvent::eTopLevel; + + // mCurrentTarget is set to the new target, so we must reset it to the + // old target and then dispatch a cross-process event. (mCurrentTarget + // will be set back below.) HandleCrossProcessEvent will query for the + // proper target via GetEventTarget which will return mCurrentTarget. + mCurrentTarget = targetFrame; + HandleCrossProcessEvent(remoteEvent, &status); + } else if (aMessage == eMouseOver) { + nsAutoPtr<WidgetMouseEvent> remoteEvent; + CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseEnterIntoWidget, + relatedContent, remoteEvent); + HandleCrossProcessEvent(remoteEvent, &status); + } + } + } + + mCurrentTargetContent = nullptr; + mCurrentTarget = previousTarget; + + return targetFrame; +} + +class EnterLeaveDispatcher +{ +public: + EnterLeaveDispatcher(EventStateManager* aESM, + nsIContent* aTarget, nsIContent* aRelatedTarget, + WidgetMouseEvent* aMouseEvent, + EventMessage aEventMessage) + : mESM(aESM) + , mMouseEvent(aMouseEvent) + , mEventMessage(aEventMessage) + { + nsPIDOMWindowInner* win = + aTarget ? aTarget->OwnerDoc()->GetInnerWindow() : nullptr; + if (aMouseEvent->AsPointerEvent() ? win && win->HasPointerEnterLeaveEventListeners() : + win && win->HasMouseEnterLeaveEventListeners()) { + mRelatedTarget = aRelatedTarget ? + aRelatedTarget->FindFirstNonChromeOnlyAccessContent() : nullptr; + nsINode* commonParent = nullptr; + if (aTarget && aRelatedTarget) { + commonParent = + nsContentUtils::GetCommonAncestor(aTarget, aRelatedTarget); + } + nsIContent* current = aTarget; + // Note, it is ok if commonParent is null! + while (current && current != commonParent) { + if (!current->ChromeOnlyAccess()) { + mTargets.AppendObject(current); + } + // mouseenter/leave is fired only on elements. + current = current->GetParent(); + } + } + } + + ~EnterLeaveDispatcher() + { + if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) { + for (int32_t i = mTargets.Count() - 1; i >= 0; --i) { + mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage, + mTargets[i], mRelatedTarget); + } + } else { + for (int32_t i = 0; i < mTargets.Count(); ++i) { + mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage, + mTargets[i], mRelatedTarget); + } + } + } + + EventStateManager* mESM; + nsCOMArray<nsIContent> mTargets; + nsCOMPtr<nsIContent> mRelatedTarget; + WidgetMouseEvent* mMouseEvent; + EventMessage mEventMessage; +}; + +void +EventStateManager::NotifyMouseOut(WidgetMouseEvent* aMouseEvent, + nsIContent* aMovingInto) +{ + OverOutElementsWrapper* wrapper = GetWrapperByEventID(aMouseEvent); + + if (!wrapper->mLastOverElement) + return; + // Before firing mouseout, check for recursion + if (wrapper->mLastOverElement == wrapper->mFirstOutEventElement) + return; + + if (wrapper->mLastOverFrame) { + // if the frame is associated with a subdocument, + // tell the subdocument that we're moving out of it + nsSubDocumentFrame* subdocFrame = do_QueryFrame(wrapper->mLastOverFrame.GetFrame()); + if (subdocFrame) { + nsCOMPtr<nsIDocShell> docshell; + subdocFrame->GetDocShell(getter_AddRefs(docshell)); + if (docshell) { + RefPtr<nsPresContext> presContext; + docshell->GetPresContext(getter_AddRefs(presContext)); + + if (presContext) { + EventStateManager* kidESM = presContext->EventStateManager(); + // Not moving into any element in this subdocument + kidESM->NotifyMouseOut(aMouseEvent, nullptr); + } + } + } + } + // That could have caused DOM events which could wreak havoc. Reverify + // things and be careful. + if (!wrapper->mLastOverElement) + return; + + // Store the first mouseOut event we fire and don't refire mouseOut + // to that element while the first mouseOut is still ongoing. + wrapper->mFirstOutEventElement = wrapper->mLastOverElement; + + // Don't touch hover state if aMovingInto is non-null. Caller will update + // hover state itself, and we have optimizations for hover switching between + // two nearby elements both deep in the DOM tree that would be defeated by + // switching the hover state to null here. + bool isPointer = aMouseEvent->mClass == ePointerEventClass; + if (!aMovingInto && !isPointer) { + // Unset :hover + SetContentState(nullptr, NS_EVENT_STATE_HOVER); + } + + // In case we go out from capturing element (retargetedByPointerCapture is true) + // we should dispatch ePointerLeave event and only for capturing element. + RefPtr<nsIContent> movingInto = aMouseEvent->retargetedByPointerCapture + ? wrapper->mLastOverElement->GetParent() + : aMovingInto; + + EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement, + movingInto, aMouseEvent, + isPointer ? ePointerLeave : eMouseLeave); + + // Fire mouseout + DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut, + wrapper->mLastOverElement, aMovingInto); + + wrapper->mLastOverFrame = nullptr; + wrapper->mLastOverElement = nullptr; + + // Turn recursion protection back off + wrapper->mFirstOutEventElement = nullptr; +} + +void +EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent, + nsIContent* aContent) +{ + NS_ASSERTION(aContent, "Mouse must be over something"); + + // If pointer capture is set, we should suppress pointerover/pointerenter events + // for all elements except element which have pointer capture. + bool dispatch = !aMouseEvent->retargetedByPointerCapture; + + OverOutElementsWrapper* wrapper = GetWrapperByEventID(aMouseEvent); + + if (wrapper->mLastOverElement == aContent && dispatch) + return; + + // Before firing mouseover, check for recursion + if (aContent == wrapper->mFirstOverEventElement) + return; + + // Check to see if we're a subdocument and if so update the parent + // document's ESM state to indicate that the mouse is over the + // content associated with our subdocument. + EnsureDocument(mPresContext); + if (nsIDocument *parentDoc = mDocument->GetParentDocument()) { + if (nsIContent *docContent = parentDoc->FindContentForSubDocument(mDocument)) { + if (nsIPresShell *parentShell = parentDoc->GetShell()) { + EventStateManager* parentESM = + parentShell->GetPresContext()->EventStateManager(); + parentESM->NotifyMouseOver(aMouseEvent, docContent); + } + } + } + // Firing the DOM event in the parent document could cause all kinds + // of havoc. Reverify and take care. + if (wrapper->mLastOverElement == aContent && dispatch) + return; + + // Remember mLastOverElement as the related content for the + // DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it, bug 298477. + nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement; + + bool isPointer = aMouseEvent->mClass == ePointerEventClass; + + Maybe<EnterLeaveDispatcher> enterDispatcher; + if (dispatch) { + enterDispatcher.emplace(this, aContent, lastOverElement, aMouseEvent, + isPointer ? ePointerEnter : eMouseEnter); + } + + NotifyMouseOut(aMouseEvent, aContent); + + // Store the first mouseOver event we fire and don't refire mouseOver + // to that element while the first mouseOver is still ongoing. + wrapper->mFirstOverEventElement = aContent; + + if (!isPointer) { + SetContentState(aContent, NS_EVENT_STATE_HOVER); + } + + if (dispatch) { + // Fire mouseover + wrapper->mLastOverFrame = + DispatchMouseOrPointerEvent(aMouseEvent, + isPointer ? ePointerOver : eMouseOver, + aContent, lastOverElement); + wrapper->mLastOverElement = aContent; + } else { + wrapper->mLastOverFrame = nullptr; + wrapper->mLastOverElement = nullptr; + } + + // Turn recursion protection back off + wrapper->mFirstOverEventElement = nullptr; +} + +// Returns the center point of the window's client area. This is +// in widget coordinates, i.e. relative to the widget's top-left +// corner, not in screen coordinates, the same units that UIEvent:: +// refpoint is in. It may not be the exact center of the window if +// the platform requires rounding the coordinate. +static LayoutDeviceIntPoint +GetWindowClientRectCenter(nsIWidget* aWidget) +{ + NS_ENSURE_TRUE(aWidget, LayoutDeviceIntPoint(0, 0)); + + LayoutDeviceIntRect rect = aWidget->GetClientBounds(); + LayoutDeviceIntPoint point(rect.x + rect.width / 2, + rect.y + rect.height / 2); + int32_t round = aWidget->RoundsWidgetCoordinatesTo(); + point.x = point.x / round * round; + point.y = point.y / round * round; + return point - aWidget->WidgetToScreenOffset(); +} + +void +EventStateManager::GeneratePointerEnterExit(EventMessage aMessage, + WidgetMouseEvent* aEvent) +{ + WidgetPointerEvent pointerEvent(*aEvent); + pointerEvent.mMessage = aMessage; + GenerateMouseEnterExit(&pointerEvent); +} + +void +EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent) +{ + EnsureDocument(mPresContext); + if (!mDocument) + return; + + // Hold onto old target content through the event and reset after. + nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent; + + switch(aMouseEvent->mMessage) { + case eMouseMove: + { + // Mouse movement is reported on the MouseEvent.movement{X,Y} fields. + // Movement is calculated in UIEvent::GetMovementPoint() as: + // previous_mousemove_mRefPoint - current_mousemove_mRefPoint. + if (sIsPointerLocked && aMouseEvent->mWidget) { + // The pointer is locked. If the pointer is not located at the center of + // the window, dispatch a synthetic mousemove to return the pointer there. + // Doing this between "real" pointer moves gives the impression that the + // (locked) pointer can continue moving and won't stop at the screen + // boundary. We cancel the synthetic event so that we don't end up + // dispatching the centering move event to content. + LayoutDeviceIntPoint center = + GetWindowClientRectCenter(aMouseEvent->mWidget); + aMouseEvent->mLastRefPoint = center; + if (aMouseEvent->mRefPoint != center) { + // Mouse move doesn't finish at the center of the window. Dispatch a + // synthetic native mouse event to move the pointer back to the center + // of the window, to faciliate more movement. But first, record that + // we've dispatched a synthetic mouse movement, so we can cancel it + // in the other branch here. + sSynthCenteringPoint = center; + aMouseEvent->mWidget->SynthesizeNativeMouseMove( + center + aMouseEvent->mWidget->WidgetToScreenOffset(), nullptr); + } else if (aMouseEvent->mRefPoint == sSynthCenteringPoint) { + // This is the "synthetic native" event we dispatched to re-center the + // pointer. Cancel it so we don't expose the centering move to content. + aMouseEvent->StopPropagation(); + // Clear sSynthCenteringPoint so we don't cancel other events + // targeted at the center. + sSynthCenteringPoint = kInvalidRefPoint; + } + } else if (sLastRefPoint == kInvalidRefPoint) { + // We don't have a valid previous mousemove mRefPoint. This is either + // the first move we've encountered, or the mouse has just re-entered + // the application window. We should report (0,0) movement for this + // case, so make the current and previous mRefPoints the same. + aMouseEvent->mLastRefPoint = aMouseEvent->mRefPoint; + } else { + aMouseEvent->mLastRefPoint = sLastRefPoint; + } + + // Update the last known mRefPoint with the current mRefPoint. + sLastRefPoint = aMouseEvent->mRefPoint; + } + MOZ_FALLTHROUGH; + case ePointerMove: + case ePointerDown: + { + // Get the target content target (mousemove target == mouseover target) + nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent); + if (!targetElement) { + // We're always over the document root, even if we're only + // over dead space in a page (whose frame is not associated with + // any content) or in print preview dead space + targetElement = mDocument->GetRootElement(); + } + if (targetElement) { + NotifyMouseOver(aMouseEvent, targetElement); + } + } + break; + case ePointerUp: + { + // Get the target content target (mousemove target == mouseover target) + nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent); + if (!targetElement) { + // We're always over the document root, even if we're only + // over dead space in a page (whose frame is not associated with + // any content) or in print preview dead space + targetElement = mDocument->GetRootElement(); + } + if (targetElement) { + OverOutElementsWrapper* helper = GetWrapperByEventID(aMouseEvent); + if (helper) { + helper->mLastOverElement = targetElement; + } + NotifyMouseOut(aMouseEvent, nullptr); + } + } + break; + case ePointerLeave: + case ePointerCancel: + case eMouseExitFromWidget: + { + // This is actually the window mouse exit or pointer leave event. We're not moving + // into any new element. + + OverOutElementsWrapper* helper = GetWrapperByEventID(aMouseEvent); + if (helper->mLastOverFrame && + nsContentUtils::GetTopLevelWidget(aMouseEvent->mWidget) != + nsContentUtils::GetTopLevelWidget(helper->mLastOverFrame->GetNearestWidget())) { + // the Mouse/PointerOut event widget doesn't have same top widget with + // mLastOverFrame, it's a spurious event for mLastOverFrame + break; + } + + // Reset sLastRefPoint, so that we'll know not to report any + // movement the next time we re-enter the window. + sLastRefPoint = kInvalidRefPoint; + + NotifyMouseOut(aMouseEvent, nullptr); + } + break; + default: + break; + } + + // reset mCurretTargetContent to what it was + mCurrentTargetContent = targetBeforeEvent; +} + +OverOutElementsWrapper* +EventStateManager::GetWrapperByEventID(WidgetMouseEvent* aEvent) +{ + WidgetPointerEvent* pointer = aEvent->AsPointerEvent(); + if (!pointer) { + MOZ_ASSERT(aEvent->AsMouseEvent() != nullptr); + if (!mMouseEnterLeaveHelper) { + mMouseEnterLeaveHelper = new OverOutElementsWrapper(); + } + return mMouseEnterLeaveHelper; + } + RefPtr<OverOutElementsWrapper> helper; + if (!mPointersEnterLeaveHelper.Get(pointer->pointerId, getter_AddRefs(helper))) { + helper = new OverOutElementsWrapper(); + mPointersEnterLeaveHelper.Put(pointer->pointerId, helper); + } + + return helper; +} + +/* static */ void +EventStateManager::SetPointerLock(nsIWidget* aWidget, + nsIContent* aElement) +{ + // NOTE: aElement will be nullptr when unlocking. + sIsPointerLocked = !!aElement; + + // Reset mouse wheel transaction + WheelTransaction::EndTransaction(); + + // Deal with DnD events + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + + if (sIsPointerLocked) { + MOZ_ASSERT(aWidget, "Locking pointer requires a widget"); + + // Store the last known ref point so we can reposition the pointer after unlock. + sPreLockPoint = sLastRefPoint; + + // Fire a synthetic mouse move to ensure event state is updated. We first + // set the mouse to the center of the window, so that the mouse event + // doesn't report any movement. + sLastRefPoint = GetWindowClientRectCenter(aWidget); + aWidget->SynthesizeNativeMouseMove( + sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr); + + // Suppress DnD + if (dragService) { + dragService->Suppress(); + } + } else { + // Unlocking, so return pointer to the original position by firing a + // synthetic mouse event. We first reset sLastRefPoint to its + // pre-pointerlock position, so that the synthetic mouse event reports + // no movement. + sLastRefPoint = sPreLockPoint; + // Reset SynthCenteringPoint to invalid so that next time we start + // locking pointer, it has its initial value. + sSynthCenteringPoint = kInvalidRefPoint; + if (aWidget) { + aWidget->SynthesizeNativeMouseMove( + sPreLockPoint + aWidget->WidgetToScreenOffset(), nullptr); + } + + // Unsuppress DnD + if (dragService) { + dragService->Unsuppress(); + } + } +} + +void +EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext, + WidgetDragEvent* aDragEvent) +{ + //Hold onto old target content through the event and reset after. + nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent; + + switch(aDragEvent->mMessage) { + case eDragOver: + { + // when dragging from one frame to another, events are fired in the + // order: dragexit, dragenter, dragleave + if (sLastDragOverFrame != mCurrentTarget) { + //We'll need the content, too, to check if it changed separately from the frames. + nsCOMPtr<nsIContent> lastContent; + nsCOMPtr<nsIContent> targetContent; + mCurrentTarget->GetContentForEvent(aDragEvent, + getter_AddRefs(targetContent)); + + if (sLastDragOverFrame) { + //The frame has changed but the content may not have. Check before dispatching to content + sLastDragOverFrame->GetContentForEvent(aDragEvent, + getter_AddRefs(lastContent)); + + FireDragEnterOrExit(sLastDragOverFrame->PresContext(), + aDragEvent, eDragExit, + targetContent, lastContent, sLastDragOverFrame); + } + + FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter, + lastContent, targetContent, mCurrentTarget); + + if (sLastDragOverFrame) { + FireDragEnterOrExit(sLastDragOverFrame->PresContext(), + aDragEvent, eDragLeave, + targetContent, lastContent, sLastDragOverFrame); + } + + sLastDragOverFrame = mCurrentTarget; + } + } + break; + + case eDragExit: + { + //This is actually the window mouse exit event. + if (sLastDragOverFrame) { + nsCOMPtr<nsIContent> lastContent; + sLastDragOverFrame->GetContentForEvent(aDragEvent, + getter_AddRefs(lastContent)); + + RefPtr<nsPresContext> lastDragOverFramePresContext = sLastDragOverFrame->PresContext(); + FireDragEnterOrExit(lastDragOverFramePresContext, + aDragEvent, eDragExit, + nullptr, lastContent, sLastDragOverFrame); + FireDragEnterOrExit(lastDragOverFramePresContext, + aDragEvent, eDragLeave, + nullptr, lastContent, sLastDragOverFrame); + + sLastDragOverFrame = nullptr; + } + } + break; + + default: + break; + } + + //reset mCurretTargetContent to what it was + mCurrentTargetContent = targetBeforeEvent; + + // Now flush all pending notifications, for better responsiveness. + FlushPendingEvents(aPresContext); +} + +void +EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext, + WidgetDragEvent* aDragEvent, + EventMessage aMessage, + nsIContent* aRelatedTarget, + nsIContent* aTargetContent, + nsWeakFrame& aTargetFrame) +{ + 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; + + mCurrentTargetContent = aTargetContent; + + if (aTargetContent != aRelatedTarget) { + //XXX This event should still go somewhere!! + if (aTargetContent) { + EventDispatcher::Dispatch(aTargetContent, aPresContext, &event, + nullptr, &status); + } + + // adjust the drag hover if the dragenter event was cancelled or this is a drag exit + if (status == nsEventStatus_eConsumeNoDefault || aMessage == eDragExit) { + SetContentState((aMessage == eDragEnter) ? aTargetContent : nullptr, + NS_EVENT_STATE_DRAGOVER); + } + + // collect any changes to moz cursor settings stored in the event's + // data transfer. + if (aMessage == eDragLeave || aMessage == eDragExit || + aMessage == eDragEnter) { + UpdateDragDataTransfer(&event); + } + } + + // Finally dispatch the event to the frame + if (aTargetFrame) + aTargetFrame->HandleEvent(aPresContext, &event, &status); +} + +void +EventStateManager::UpdateDragDataTransfer(WidgetDragEvent* dragEvent) +{ + NS_ASSERTION(dragEvent, "drag event is null in UpdateDragDataTransfer!"); + if (!dragEvent->mDataTransfer) { + return; + } + + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + + if (dragSession) { + // the initial dataTransfer is the one from the dragstart event that + // was set on the dragSession when the drag began. + nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer; + dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer)); + if (initialDataTransfer) { + // retrieve the current moz cursor setting and save it. + nsAutoString mozCursor; + dragEvent->mDataTransfer->GetMozCursor(mozCursor); + initialDataTransfer->SetMozCursor(mozCursor); + } + } +} + +nsresult +EventStateManager::SetClickCount(WidgetMouseEvent* aEvent, + nsEventStatus* aStatus) +{ + nsCOMPtr<nsIContent> mouseContent; + nsIContent* mouseContentParent = nullptr; + if (mCurrentTarget) { + mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent)); + } + if (mouseContent) { + if (mouseContent->IsNodeOfType(nsINode::eTEXT)) { + mouseContent = mouseContent->GetParent(); + } + if (mouseContent && mouseContent->IsRootOfNativeAnonymousSubtree()) { + mouseContentParent = mouseContent->GetParent(); + } + } + + switch (aEvent->button) { + case WidgetMouseEvent::eLeftButton: + if (aEvent->mMessage == eMouseDown) { + mLastLeftMouseDownContent = mouseContent; + mLastLeftMouseDownContentParent = mouseContentParent; + } else if (aEvent->mMessage == eMouseUp) { + if (mLastLeftMouseDownContent == mouseContent || + mLastLeftMouseDownContentParent == mouseContent || + mLastLeftMouseDownContent == mouseContentParent) { + aEvent->mClickCount = mLClickCount; + mLClickCount = 0; + } else { + aEvent->mClickCount = 0; + } + mLastLeftMouseDownContent = nullptr; + mLastLeftMouseDownContentParent = nullptr; + } + break; + + case WidgetMouseEvent::eMiddleButton: + if (aEvent->mMessage == eMouseDown) { + mLastMiddleMouseDownContent = mouseContent; + mLastMiddleMouseDownContentParent = mouseContentParent; + } else if (aEvent->mMessage == eMouseUp) { + if (mLastMiddleMouseDownContent == mouseContent || + mLastMiddleMouseDownContentParent == mouseContent || + mLastMiddleMouseDownContent == mouseContentParent) { + aEvent->mClickCount = mMClickCount; + mMClickCount = 0; + } else { + aEvent->mClickCount = 0; + } + mLastMiddleMouseDownContent = nullptr; + mLastMiddleMouseDownContentParent = nullptr; + } + break; + + case WidgetMouseEvent::eRightButton: + if (aEvent->mMessage == eMouseDown) { + mLastRightMouseDownContent = mouseContent; + mLastRightMouseDownContentParent = mouseContentParent; + } else if (aEvent->mMessage == eMouseUp) { + if (mLastRightMouseDownContent == mouseContent || + mLastRightMouseDownContentParent == mouseContent || + mLastRightMouseDownContent == mouseContentParent) { + aEvent->mClickCount = mRClickCount; + mRClickCount = 0; + } else { + aEvent->mClickCount = 0; + } + mLastRightMouseDownContent = nullptr; + mLastRightMouseDownContentParent = nullptr; + } + break; + } + + return NS_OK; +} + +nsresult +EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent, + nsEventStatus* aStatus) +{ + nsresult ret = NS_OK; + + //If mouse is still over same element, clickcount will be > 1. + //If it has moved it will be zero, so no click. + if (aEvent->mClickCount) { + //Check that the window isn't disabled before firing a click + //(see bug 366544). + if (aEvent->mWidget && !aEvent->mWidget->IsEnabled()) { + return ret; + } + //fire click + bool notDispatchToContents = + (aEvent->button == WidgetMouseEvent::eMiddleButton || + aEvent->button == WidgetMouseEvent::eRightButton); + + WidgetMouseEvent event(aEvent->IsTrusted(), eMouseClick, + aEvent->mWidget, WidgetMouseEvent::eReal); + event.mRefPoint = aEvent->mRefPoint; + event.mClickCount = aEvent->mClickCount; + event.mModifiers = aEvent->mModifiers; + event.buttons = aEvent->buttons; + event.mTime = aEvent->mTime; + event.mTimeStamp = aEvent->mTimeStamp; + event.mFlags.mNoContentDispatch = notDispatchToContents; + event.button = aEvent->button; + event.inputSource = aEvent->inputSource; + + nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell(); + if (presShell) { + nsCOMPtr<nsIContent> mouseContent = GetEventTargetContent(aEvent); + // Click events apply to *elements* not nodes. At this point the target + // content may have been reset to some non-element content, and so we need + // to walk up the closest ancestor element, just like we do in + // nsPresShell::HandlePositionedEvent. + while (mouseContent && !mouseContent->IsElement()) { + mouseContent = mouseContent->GetParent(); + } + + if (!mouseContent && !mCurrentTarget) { + return NS_OK; + } + + // HandleEvent clears out mCurrentTarget which we might need again + nsWeakFrame currentTarget = mCurrentTarget; + ret = presShell->HandleEventWithTarget(&event, currentTarget, + mouseContent, aStatus); + if (NS_SUCCEEDED(ret) && aEvent->mClickCount == 2 && + mouseContent && mouseContent->IsInComposedDoc()) { + //fire double click + WidgetMouseEvent event2(aEvent->IsTrusted(), eMouseDoubleClick, + aEvent->mWidget, WidgetMouseEvent::eReal); + event2.mRefPoint = aEvent->mRefPoint; + event2.mClickCount = aEvent->mClickCount; + event2.mModifiers = aEvent->mModifiers; + event2.buttons = aEvent->buttons; + event2.mFlags.mNoContentDispatch = notDispatchToContents; + event2.button = aEvent->button; + event2.inputSource = aEvent->inputSource; + + ret = presShell->HandleEventWithTarget(&event2, currentTarget, + mouseContent, aStatus); + } + } + } + return ret; +} + +nsIFrame* +EventStateManager::GetEventTarget() +{ + nsIPresShell *shell; + if (mCurrentTarget || + !mPresContext || + !(shell = mPresContext->GetPresShell())) { + return mCurrentTarget; + } + + if (mCurrentTargetContent) { + mCurrentTarget = mPresContext->GetPrimaryFrameFor(mCurrentTargetContent); + if (mCurrentTarget) { + return mCurrentTarget; + } + } + + nsIFrame* frame = shell->GetEventTargetFrame(); + return (mCurrentTarget = frame); +} + +already_AddRefed<nsIContent> +EventStateManager::GetEventTargetContent(WidgetEvent* aEvent) +{ + if (aEvent && + (aEvent->mMessage == eFocus || aEvent->mMessage == eBlur)) { + nsCOMPtr<nsIContent> content = GetFocusedContent(); + return content.forget(); + } + + if (mCurrentTargetContent) { + nsCOMPtr<nsIContent> content = mCurrentTargetContent; + return content.forget(); + } + + nsCOMPtr<nsIContent> content; + + nsIPresShell *presShell = mPresContext->GetPresShell(); + if (presShell) { + content = presShell->GetEventTargetContent(aEvent); + } + + // Some events here may set mCurrentTarget but not set the corresponding + // event target in the PresShell. + if (!content && mCurrentTarget) { + mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(content)); + } + + return content.forget(); +} + +static Element* +GetLabelTarget(nsIContent* aPossibleLabel) +{ + mozilla::dom::HTMLLabelElement* label = + mozilla::dom::HTMLLabelElement::FromContent(aPossibleLabel); + if (!label) + return nullptr; + + return label->GetLabeledElement(); +} + +static nsIContent* FindCommonAncestor(nsIContent *aNode1, nsIContent *aNode2) +{ + // Find closest common ancestor + if (aNode1 && aNode2) { + // Find the nearest common ancestor by counting the distance to the + // root and then walking up again, in pairs. + int32_t offset = 0; + nsIContent *anc1 = aNode1; + for (;;) { + ++offset; + nsIContent* parent = anc1->GetFlattenedTreeParent(); + if (!parent) + break; + anc1 = parent; + } + nsIContent *anc2 = aNode2; + for (;;) { + --offset; + nsIContent* parent = anc2->GetFlattenedTreeParent(); + if (!parent) + break; + anc2 = parent; + } + if (anc1 == anc2) { + anc1 = aNode1; + anc2 = aNode2; + while (offset > 0) { + anc1 = anc1->GetFlattenedTreeParent(); + --offset; + } + while (offset < 0) { + anc2 = anc2->GetFlattenedTreeParent(); + ++offset; + } + while (anc1 != anc2) { + anc1 = anc1->GetFlattenedTreeParent(); + anc2 = anc2->GetFlattenedTreeParent(); + } + return anc1; + } + } + return nullptr; +} + +/* static */ +void +EventStateManager::SetFullScreenState(Element* aElement, bool aIsFullScreen) +{ + DoStateChange(aElement, NS_EVENT_STATE_FULL_SCREEN, aIsFullScreen); +} + +/* static */ +inline void +EventStateManager::DoStateChange(Element* aElement, EventStates aState, + bool aAddState) +{ + if (aAddState) { + aElement->AddStates(aState); + } else { + aElement->RemoveStates(aState); + } +} + +/* static */ +inline void +EventStateManager::DoStateChange(nsIContent* aContent, EventStates aState, + bool aStateAdded) +{ + if (aContent->IsElement()) { + DoStateChange(aContent->AsElement(), aState, aStateAdded); + } +} + +/* static */ +void +EventStateManager::UpdateAncestorState(nsIContent* aStartNode, + nsIContent* aStopBefore, + EventStates aState, + bool aAddState) +{ + for (; aStartNode && aStartNode != aStopBefore; + aStartNode = aStartNode->GetFlattenedTreeParent()) { + // We might be starting with a non-element (e.g. a text node) and + // if someone is doing something weird might be ending with a + // non-element too (e.g. a document fragment) + if (!aStartNode->IsElement()) { + continue; + } + Element* element = aStartNode->AsElement(); + DoStateChange(element, aState, aAddState); + Element* labelTarget = GetLabelTarget(element); + if (labelTarget) { + DoStateChange(labelTarget, aState, aAddState); + } + } + + if (aAddState) { + // We might be in a situation where a node was in hover both + // because it was hovered and because the label for it was + // hovered, and while we stopped hovering the node the label is + // still hovered. Or we might have had two nested labels for the + // same node, and while one is no longer hovered the other still + // is. In that situation, the label that's still hovered will be + // aStopBefore or some ancestor of it, and the call we just made + // to UpdateAncestorState with aAddState = false would have + // removed the hover state from the node. But the node should + // still be in hover state. To handle this situation we need to + // keep walking up the tree and any time we find a label mark its + // corresponding node as still in our state. + for ( ; aStartNode; aStartNode = aStartNode->GetFlattenedTreeParent()) { + if (!aStartNode->IsElement()) { + continue; + } + + Element* labelTarget = GetLabelTarget(aStartNode->AsElement()); + if (labelTarget && !labelTarget->State().HasState(aState)) { + DoStateChange(labelTarget, aState, true); + } + } + } +} + +bool +EventStateManager::SetContentState(nsIContent* aContent, EventStates aState) +{ + // We manage 4 states here: ACTIVE, HOVER, DRAGOVER, URLTARGET + // The input must be exactly one of them. + NS_PRECONDITION(aState == NS_EVENT_STATE_ACTIVE || + aState == NS_EVENT_STATE_HOVER || + aState == NS_EVENT_STATE_DRAGOVER || + aState == NS_EVENT_STATE_URLTARGET, + "Unexpected state"); + + nsCOMPtr<nsIContent> notifyContent1; + nsCOMPtr<nsIContent> notifyContent2; + bool updateAncestors; + + if (aState == NS_EVENT_STATE_HOVER || aState == NS_EVENT_STATE_ACTIVE) { + // Hover and active are hierarchical + updateAncestors = true; + + // check to see that this state is allowed by style. Check dragover too? + // XXX Is this even what we want? + if (mCurrentTarget) + { + const nsStyleUserInterface* ui = mCurrentTarget->StyleUserInterface(); + if (ui->mUserInput == StyleUserInput::None) { + return false; + } + } + + if (aState == NS_EVENT_STATE_ACTIVE) { + // Editable content can never become active since their default actions + // are disabled. Watch out for editable content in native anonymous + // subtrees though, as they belong to text controls. + if (aContent && aContent->IsEditable() && + !aContent->IsInNativeAnonymousSubtree()) { + aContent = nullptr; + } + if (aContent != mActiveContent) { + notifyContent1 = aContent; + notifyContent2 = mActiveContent; + mActiveContent = aContent; + } + } else { + NS_ASSERTION(aState == NS_EVENT_STATE_HOVER, "How did that happen?"); + nsIContent* newHover; + + if (mPresContext->IsDynamic()) { + newHover = aContent; + } else { + NS_ASSERTION(!aContent || + aContent->GetComposedDoc() == + mPresContext->PresShell()->GetDocument(), + "Unexpected document"); + nsIFrame *frame = aContent ? aContent->GetPrimaryFrame() : nullptr; + if (frame && nsLayoutUtils::IsViewportScrollbarFrame(frame)) { + // The scrollbars of viewport should not ignore the hover state. + // Because they are *not* the content of the web page. + newHover = aContent; + } else { + // All contents of the web page should ignore the hover state. + newHover = nullptr; + } + } + + if (newHover != mHoverContent) { + notifyContent1 = newHover; + notifyContent2 = mHoverContent; + mHoverContent = newHover; + } + } + } else { + updateAncestors = false; + if (aState == NS_EVENT_STATE_DRAGOVER) { + if (aContent != sDragOverContent) { + notifyContent1 = aContent; + notifyContent2 = sDragOverContent; + sDragOverContent = aContent; + } + } else if (aState == NS_EVENT_STATE_URLTARGET) { + if (aContent != mURLTargetContent) { + notifyContent1 = aContent; + notifyContent2 = mURLTargetContent; + mURLTargetContent = aContent; + } + } + } + + // We need to keep track of which of notifyContent1 and notifyContent2 is + // getting the state set and which is getting it unset. If both are + // non-null, then notifyContent1 is having the state set and notifyContent2 + // is having it unset. But if one of them is null, we need to keep track of + // the right thing for notifyContent1 explicitly. + bool content1StateSet = true; + if (!notifyContent1) { + // This is ok because FindCommonAncestor wouldn't find anything + // anyway if notifyContent1 is null. + notifyContent1 = notifyContent2; + notifyContent2 = nullptr; + content1StateSet = false; + } + + if (notifyContent1 && mPresContext) { + EnsureDocument(mPresContext); + if (mDocument) { + nsAutoScriptBlocker scriptBlocker; + + if (updateAncestors) { + nsCOMPtr<nsIContent> commonAncestor = + FindCommonAncestor(notifyContent1, notifyContent2); + if (notifyContent2) { + // It's very important to first notify the state removal and + // then the state addition, because due to labels it's + // possible that we're removing state from some element but + // then adding it again (say because mHoverContent changed + // from a control to its label). + UpdateAncestorState(notifyContent2, commonAncestor, aState, false); + } + UpdateAncestorState(notifyContent1, commonAncestor, aState, + content1StateSet); + } else { + if (notifyContent2) { + DoStateChange(notifyContent2, aState, false); + } + DoStateChange(notifyContent1, aState, content1StateSet); + } + } + } + + return true; +} + +void +EventStateManager::ResetLastOverForContent( + const uint32_t& aIdx, + RefPtr<OverOutElementsWrapper>& aElemWrapper, + nsIContent* aContent) +{ + if (aElemWrapper && aElemWrapper->mLastOverElement && + nsContentUtils::ContentIsDescendantOf(aElemWrapper->mLastOverElement, + aContent)) { + aElemWrapper->mLastOverElement = nullptr; + } +} + +void +EventStateManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent) +{ + /* + * Anchor and area elements when focused or hovered might make the UI to show + * the current link. We want to make sure that the UI gets informed when they + * are actually removed from the DOM. + */ + if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) && + (aContent->AsElement()->State().HasAtLeastOneOfStates(NS_EVENT_STATE_FOCUS | + NS_EVENT_STATE_HOVER))) { + nsGenericHTMLElement* element = static_cast<nsGenericHTMLElement*>(aContent); + element->LeaveLink( + element->GetPresContext(nsGenericHTMLElement::eForComposedDoc)); + } + + IMEStateManager::OnRemoveContent(mPresContext, aContent); + + // inform the focus manager that the content is being removed. If this + // content is focused, the focus will be removed without firing events. + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) + fm->ContentRemoved(aDocument, aContent); + + if (mHoverContent && + nsContentUtils::ContentIsDescendantOf(mHoverContent, aContent)) { + // Since hover is hierarchical, set the current hover to the + // content's parent node. + SetContentState(aContent->GetParent(), NS_EVENT_STATE_HOVER); + } + + if (mActiveContent && + nsContentUtils::ContentIsDescendantOf(mActiveContent, aContent)) { + // Active is hierarchical, so set the current active to the + // content's parent node. + SetContentState(aContent->GetParent(), NS_EVENT_STATE_ACTIVE); + } + + if (sDragOverContent && + sDragOverContent->OwnerDoc() == aContent->OwnerDoc() && + nsContentUtils::ContentIsDescendantOf(sDragOverContent, aContent)) { + sDragOverContent = nullptr; + } + + // See bug 292146 for why we want to null this out + ResetLastOverForContent(0, mMouseEnterLeaveHelper, aContent); + for (auto iter = mPointersEnterLeaveHelper.Iter(); + !iter.Done(); + iter.Next()) { + ResetLastOverForContent(iter.Key(), iter.Data(), aContent); + } +} + +bool +EventStateManager::EventStatusOK(WidgetGUIEvent* aEvent) +{ + return !(aEvent->mMessage == eMouseDown && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && + !sNormalLMouseEventInProcess); +} + +//------------------------------------------- +// Access Key Registration +//------------------------------------------- +void +EventStateManager::RegisterAccessKey(nsIContent* aContent, uint32_t aKey) +{ + if (aContent && mAccessKeys.IndexOf(aContent) == -1) + mAccessKeys.AppendObject(aContent); +} + +void +EventStateManager::UnregisterAccessKey(nsIContent* aContent, uint32_t aKey) +{ + if (aContent) + mAccessKeys.RemoveObject(aContent); +} + +uint32_t +EventStateManager::GetRegisteredAccessKey(nsIContent* aContent) +{ + MOZ_ASSERT(aContent); + + if (mAccessKeys.IndexOf(aContent) == -1) + return 0; + + nsAutoString accessKey; + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey); + return accessKey.First(); +} + +void +EventStateManager::EnsureDocument(nsPresContext* aPresContext) +{ + if (!mDocument) + mDocument = aPresContext->Document(); +} + +void +EventStateManager::FlushPendingEvents(nsPresContext* aPresContext) +{ + NS_PRECONDITION(nullptr != aPresContext, "nullptr ptr"); + nsIPresShell *shell = aPresContext->GetPresShell(); + if (shell) { + shell->FlushPendingNotifications(Flush_InterruptibleLayout); + } +} + +nsIContent* +EventStateManager::GetFocusedContent() +{ + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + EnsureDocument(mPresContext); + if (!fm || !mDocument) + return nullptr; + + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + return nsFocusManager::GetFocusedDescendant(mDocument->GetWindow(), false, + getter_AddRefs(focusedWindow)); +} + +//------------------------------------------------------- +// Return true if the docshell is visible + +bool +EventStateManager::IsShellVisible(nsIDocShell* aShell) +{ + NS_ASSERTION(aShell, "docshell is null"); + + nsCOMPtr<nsIBaseWindow> basewin = do_QueryInterface(aShell); + if (!basewin) + return true; + + bool isVisible = true; + basewin->GetVisibility(&isVisible); + + // We should be doing some additional checks here so that + // we don't tab into hidden tabs of tabbrowser. -bryner + + return isVisible; +} + +nsresult +EventStateManager::DoContentCommandEvent(WidgetContentCommandEvent* aEvent) +{ + EnsureDocument(mPresContext); + NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> window(mDocument->GetWindow()); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot(); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + const char* cmd; + switch (aEvent->mMessage) { + case eContentCommandCut: + cmd = "cmd_cut"; + break; + case eContentCommandCopy: + cmd = "cmd_copy"; + break; + case eContentCommandPaste: + cmd = "cmd_paste"; + break; + case eContentCommandDelete: + cmd = "cmd_delete"; + break; + case eContentCommandUndo: + cmd = "cmd_undo"; + break; + case eContentCommandRedo: + cmd = "cmd_redo"; + break; + case eContentCommandPasteTransferable: + cmd = "cmd_pasteTransferable"; + break; + case eContentCommandLookUpDictionary: + cmd = "cmd_lookUpDictionary"; + break; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + nsCOMPtr<nsIController> controller; + nsresult rv = root->GetControllerForCommand(cmd, getter_AddRefs(controller)); + NS_ENSURE_SUCCESS(rv, rv); + if (!controller) { + // When GetControllerForCommand succeeded but there is no controller, the + // command isn't supported. + aEvent->mIsEnabled = false; + } else { + bool canDoIt; + rv = controller->IsCommandEnabled(cmd, &canDoIt); + NS_ENSURE_SUCCESS(rv, rv); + aEvent->mIsEnabled = canDoIt; + if (canDoIt && !aEvent->mOnlyEnabledCheck) { + switch (aEvent->mMessage) { + case eContentCommandPasteTransferable: { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr; + RefPtr<TabParent> remote = TabParent::GetFrom(focusedContent); + if (remote) { + NS_ENSURE_TRUE(remote->Manager()->IsContentParent(), NS_ERROR_FAILURE); + + nsCOMPtr<nsITransferable> transferable = aEvent->mTransferable; + IPCDataTransfer ipcDataTransfer; + ContentParent* cp = remote->Manager()->AsContentParent(); + nsContentUtils::TransferableToIPCTransferable(transferable, + &ipcDataTransfer, + false, nullptr, + cp); + bool isPrivateData = false; + transferable->GetIsPrivateData(&isPrivateData); + nsCOMPtr<nsIPrincipal> requestingPrincipal; + transferable->GetRequestingPrincipal(getter_AddRefs(requestingPrincipal)); + remote->SendPasteTransferable(ipcDataTransfer, isPrivateData, + IPC::Principal(requestingPrincipal)); + rv = NS_OK; + } else { + nsCOMPtr<nsICommandController> commandController = do_QueryInterface(controller); + NS_ENSURE_STATE(commandController); + + nsCOMPtr<nsICommandParams> params = do_CreateInstance("@mozilla.org/embedcomp/command-params;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = params->SetISupportsValue("transferable", aEvent->mTransferable); + NS_ENSURE_SUCCESS(rv, rv); + + rv = commandController->DoCommandWithParams(cmd, params); + } + break; + } + + case eContentCommandLookUpDictionary: { + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller); + if (NS_WARN_IF(!commandController)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsICommandParams> params = + do_CreateInstance("@mozilla.org/embedcomp/command-params;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = params->SetLongValue("x", aEvent->mRefPoint.x); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = params->SetLongValue("y", aEvent->mRefPoint.y); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = commandController->DoCommandWithParams(cmd, params); + break; + } + + default: + rv = controller->DoCommand(cmd); + break; + } + NS_ENSURE_SUCCESS(rv, rv); + } + } + aEvent->mSucceeded = true; + return NS_OK; +} + +nsresult +EventStateManager::DoContentCommandScrollEvent( + WidgetContentCommandEvent* aEvent) +{ + NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE); + nsIPresShell* ps = mPresContext->GetPresShell(); + NS_ENSURE_TRUE(ps, NS_ERROR_NOT_AVAILABLE); + NS_ENSURE_TRUE(aEvent->mScroll.mAmount != 0, NS_ERROR_INVALID_ARG); + + nsIScrollableFrame::ScrollUnit scrollUnit; + switch (aEvent->mScroll.mUnit) { + case WidgetContentCommandEvent::eCmdScrollUnit_Line: + scrollUnit = nsIScrollableFrame::LINES; + break; + case WidgetContentCommandEvent::eCmdScrollUnit_Page: + scrollUnit = nsIScrollableFrame::PAGES; + break; + case WidgetContentCommandEvent::eCmdScrollUnit_Whole: + scrollUnit = nsIScrollableFrame::WHOLE; + break; + default: + return NS_ERROR_INVALID_ARG; + } + + aEvent->mSucceeded = true; + + nsIScrollableFrame* sf = + ps->GetFrameToScrollAsScrollable(nsIPresShell::eEither); + aEvent->mIsEnabled = sf ? + (aEvent->mScroll.mIsHorizontal ? + WheelHandlingUtils::CanScrollOn(sf, aEvent->mScroll.mAmount, 0) : + WheelHandlingUtils::CanScrollOn(sf, 0, aEvent->mScroll.mAmount)) : false; + + if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) { + return NS_OK; + } + + nsIntPoint pt(0, 0); + if (aEvent->mScroll.mIsHorizontal) { + pt.x = aEvent->mScroll.mAmount; + } else { + pt.y = aEvent->mScroll.mAmount; + } + + // The caller may want synchronous scrolling. + sf->ScrollBy(pt, scrollUnit, nsIScrollableFrame::INSTANT); + return NS_OK; +} + +void +EventStateManager::SetActiveManager(EventStateManager* aNewESM, + nsIContent* aContent) +{ + if (sActiveESM && aNewESM != sActiveESM) { + sActiveESM->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE); + } + sActiveESM = aNewESM; + if (sActiveESM && aContent) { + sActiveESM->SetContentState(aContent, NS_EVENT_STATE_ACTIVE); + } +} + +void +EventStateManager::ClearGlobalActiveContent(EventStateManager* aClearer) +{ + if (aClearer) { + aClearer->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE); + if (sDragOverContent) { + aClearer->SetContentState(nullptr, NS_EVENT_STATE_DRAGOVER); + } + } + if (sActiveESM && aClearer != sActiveESM) { + sActiveESM->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE); + } + sActiveESM = nullptr; +} + +/******************************************************************/ +/* mozilla::EventStateManager::DeltaAccumulator */ +/******************************************************************/ + +void +EventStateManager::DeltaAccumulator::InitLineOrPageDelta( + nsIFrame* aTargetFrame, + EventStateManager* aESM, + WidgetWheelEvent* aEvent) +{ + MOZ_ASSERT(aESM); + MOZ_ASSERT(aEvent); + + // Reset if the previous wheel event is too old. + if (!mLastTime.IsNull()) { + TimeDuration duration = TimeStamp::Now() - mLastTime; + if (duration.ToMilliseconds() > WheelTransaction::GetTimeoutTime()) { + Reset(); + } + } + // If we have accumulated delta, we may need to reset it. + if (IsInTransaction()) { + // If wheel event type is changed, reset the values. + if (mHandlingDeltaMode != aEvent->mDeltaMode || + mIsNoLineOrPageDeltaDevice != aEvent->mIsNoLineOrPageDelta) { + Reset(); + } else { + // If the delta direction is changed, we should reset only the + // accumulated values. + if (mX && aEvent->mDeltaX && ((aEvent->mDeltaX > 0.0) != (mX > 0.0))) { + mX = mPendingScrollAmountX = 0.0; + } + if (mY && aEvent->mDeltaY && ((aEvent->mDeltaY > 0.0) != (mY > 0.0))) { + mY = mPendingScrollAmountY = 0.0; + } + } + } + + mHandlingDeltaMode = aEvent->mDeltaMode; + mIsNoLineOrPageDeltaDevice = aEvent->mIsNoLineOrPageDelta; + + // If it's handling neither a device that does not provide line or page deltas + // nor delta values multiplied by prefs, we must not modify lineOrPageDelta + // values. + if (!mIsNoLineOrPageDeltaDevice && + !EventStateManager::WheelPrefs::GetInstance()-> + NeedToComputeLineOrPageDelta(aEvent)) { + // Set the delta values to mX and mY. They would be used when above block + // resets mX/mY/mPendingScrollAmountX/mPendingScrollAmountY if the direction + // is changed. + // NOTE: We shouldn't accumulate the delta values, it might could cause + // overflow even though it's not a realistic situation. + if (aEvent->mDeltaX) { + mX = aEvent->mDeltaX; + } + if (aEvent->mDeltaY) { + mY = aEvent->mDeltaY; + } + mLastTime = TimeStamp::Now(); + return; + } + + mX += aEvent->mDeltaX; + mY += aEvent->mDeltaY; + + if (mHandlingDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) { + // Records pixel delta values and init mLineOrPageDeltaX and + // mLineOrPageDeltaY for wheel events which are caused by pixel only + // devices. Ignore mouse wheel transaction for computing this. The + // lineOrPageDelta values will be used by dispatching legacy + // eMouseScrollEventClass (DOMMouseScroll) but not be used for scrolling + // of default action. The transaction should be used only for the default + // action. + nsIFrame* frame = + aESM->ComputeScrollTarget(aTargetFrame, aEvent, + COMPUTE_LEGACY_MOUSE_SCROLL_EVENT_TARGET); + nsPresContext* pc = + frame ? frame->PresContext() : aTargetFrame->PresContext(); + nsIScrollableFrame* scrollTarget = do_QueryFrame(frame); + nsSize scrollAmount = aESM->GetScrollAmount(pc, aEvent, scrollTarget); + nsIntSize scrollAmountInCSSPixels( + nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.width), + nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.height)); + + aEvent->mLineOrPageDeltaX = RoundDown(mX) / scrollAmountInCSSPixels.width; + aEvent->mLineOrPageDeltaY = RoundDown(mY) / scrollAmountInCSSPixels.height; + + mX -= aEvent->mLineOrPageDeltaX * scrollAmountInCSSPixels.width; + mY -= aEvent->mLineOrPageDeltaY * scrollAmountInCSSPixels.height; + } else { + aEvent->mLineOrPageDeltaX = RoundDown(mX); + aEvent->mLineOrPageDeltaY = RoundDown(mY); + mX -= aEvent->mLineOrPageDeltaX; + mY -= aEvent->mLineOrPageDeltaY; + } + + mLastTime = TimeStamp::Now(); +} + +void +EventStateManager::DeltaAccumulator::Reset() +{ + mX = mY = 0.0; + mPendingScrollAmountX = mPendingScrollAmountY = 0.0; + mHandlingDeltaMode = UINT32_MAX; + mIsNoLineOrPageDeltaDevice = false; +} + +nsIntPoint +EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction( + WidgetWheelEvent* aEvent, + const nsIntSize& aScrollAmountInDevPixels) +{ + MOZ_ASSERT(aEvent); + + // If the wheel event is line scroll and the delta value is computed from + // system settings, allow to override the system speed. + bool allowScrollSpeedOverride = + (!aEvent->mCustomizedByUserPrefs && + aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE); + DeltaValues acceleratedDelta = + WheelTransaction::AccelerateWheelDelta(aEvent, allowScrollSpeedOverride); + + nsIntPoint result(0, 0); + if (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) { + mPendingScrollAmountX += acceleratedDelta.deltaX; + mPendingScrollAmountY += acceleratedDelta.deltaY; + } else { + mPendingScrollAmountX += + aScrollAmountInDevPixels.width * acceleratedDelta.deltaX; + mPendingScrollAmountY += + aScrollAmountInDevPixels.height * acceleratedDelta.deltaY; + } + result.x = RoundDown(mPendingScrollAmountX); + result.y = RoundDown(mPendingScrollAmountY); + mPendingScrollAmountX -= result.x; + mPendingScrollAmountY -= result.y; + + return result; +} + +/******************************************************************/ +/* mozilla::EventStateManager::WheelPrefs */ +/******************************************************************/ + +// static +EventStateManager::WheelPrefs* +EventStateManager::WheelPrefs::GetInstance() +{ + if (!sInstance) { + sInstance = new WheelPrefs(); + } + return sInstance; +} + +// static +void +EventStateManager::WheelPrefs::Shutdown() +{ + delete sInstance; + sInstance = nullptr; +} + +// static +void +EventStateManager::WheelPrefs::OnPrefChanged(const char* aPrefName, + void* aClosure) +{ + // forget all prefs, it's not problem for performance. + sInstance->Reset(); + DeltaAccumulator::GetInstance()->Reset(); +} + +EventStateManager::WheelPrefs::WheelPrefs() +{ + Reset(); + Preferences::RegisterCallback(OnPrefChanged, "mousewheel.", nullptr); + Preferences::AddBoolVarCache(&sWheelEventsEnabledOnPlugins, + "plugin.mousewheel.enabled", + true); +} + +EventStateManager::WheelPrefs::~WheelPrefs() +{ + Preferences::UnregisterCallback(OnPrefChanged, "mousewheel.", nullptr); +} + +void +EventStateManager::WheelPrefs::Reset() +{ + memset(mInit, 0, sizeof(mInit)); + +} + +EventStateManager::WheelPrefs::Index +EventStateManager::WheelPrefs::GetIndexFor(WidgetWheelEvent* aEvent) +{ + if (!aEvent) { + return INDEX_DEFAULT; + } + + Modifiers modifiers = + (aEvent->mModifiers & (MODIFIER_ALT | + MODIFIER_CONTROL | + MODIFIER_META | + MODIFIER_SHIFT | + MODIFIER_OS)); + + switch (modifiers) { + case MODIFIER_ALT: + return INDEX_ALT; + case MODIFIER_CONTROL: + return INDEX_CONTROL; + case MODIFIER_META: + return INDEX_META; + case MODIFIER_SHIFT: + return INDEX_SHIFT; + case MODIFIER_OS: + return INDEX_OS; + default: + // If two or more modifier keys are pressed, we should use default + // settings. + return INDEX_DEFAULT; + } +} + +void +EventStateManager::WheelPrefs::GetBasePrefName( + EventStateManager::WheelPrefs::Index aIndex, + nsACString& aBasePrefName) +{ + aBasePrefName.AssignLiteral("mousewheel."); + switch (aIndex) { + case INDEX_ALT: + aBasePrefName.AppendLiteral("with_alt."); + break; + case INDEX_CONTROL: + aBasePrefName.AppendLiteral("with_control."); + break; + case INDEX_META: + aBasePrefName.AppendLiteral("with_meta."); + break; + case INDEX_SHIFT: + aBasePrefName.AppendLiteral("with_shift."); + break; + case INDEX_OS: + aBasePrefName.AppendLiteral("with_win."); + break; + case INDEX_DEFAULT: + default: + aBasePrefName.AppendLiteral("default."); + break; + } +} + +void +EventStateManager::WheelPrefs::Init(EventStateManager::WheelPrefs::Index aIndex) +{ + if (mInit[aIndex]) { + return; + } + mInit[aIndex] = true; + + nsAutoCString basePrefName; + GetBasePrefName(aIndex, basePrefName); + + nsAutoCString prefNameX(basePrefName); + prefNameX.AppendLiteral("delta_multiplier_x"); + mMultiplierX[aIndex] = + static_cast<double>(Preferences::GetInt(prefNameX.get(), 100)) / 100; + + nsAutoCString prefNameY(basePrefName); + prefNameY.AppendLiteral("delta_multiplier_y"); + mMultiplierY[aIndex] = + static_cast<double>(Preferences::GetInt(prefNameY.get(), 100)) / 100; + + nsAutoCString prefNameZ(basePrefName); + prefNameZ.AppendLiteral("delta_multiplier_z"); + mMultiplierZ[aIndex] = + static_cast<double>(Preferences::GetInt(prefNameZ.get(), 100)) / 100; + + nsAutoCString prefNameAction(basePrefName); + prefNameAction.AppendLiteral("action"); + int32_t action = Preferences::GetInt(prefNameAction.get(), ACTION_SCROLL); + if (action < int32_t(ACTION_NONE) || action > int32_t(ACTION_LAST)) { + NS_WARNING("Unsupported action pref value, replaced with 'Scroll'."); + action = ACTION_SCROLL; + } + mActions[aIndex] = static_cast<Action>(action); + + // Compute action values overridden by .override_x pref. + // At present, override is possible only for the x-direction + // because this pref is introduced mainly for tilt wheels. + prefNameAction.AppendLiteral(".override_x"); + int32_t actionOverrideX = Preferences::GetInt(prefNameAction.get(), -1); + if (actionOverrideX < -1 || actionOverrideX > int32_t(ACTION_LAST)) { + NS_WARNING("Unsupported action override pref value, didn't override."); + actionOverrideX = -1; + } + mOverriddenActionsX[aIndex] = (actionOverrideX == -1) + ? static_cast<Action>(action) + : static_cast<Action>(actionOverrideX); +} + +void +EventStateManager::WheelPrefs::ApplyUserPrefsToDelta(WidgetWheelEvent* aEvent) +{ + if (aEvent->mCustomizedByUserPrefs) { + return; + } + + Index index = GetIndexFor(aEvent); + Init(index); + + aEvent->mDeltaX *= mMultiplierX[index]; + aEvent->mDeltaY *= mMultiplierY[index]; + aEvent->mDeltaZ *= mMultiplierZ[index]; + + // If the multiplier is 1.0 or -1.0, i.e., it doesn't change the absolute + // value, we should use lineOrPageDelta values which were set by widget. + // Otherwise, we need to compute them from accumulated delta values. + if (!NeedToComputeLineOrPageDelta(aEvent)) { + aEvent->mLineOrPageDeltaX *= static_cast<int32_t>(mMultiplierX[index]); + aEvent->mLineOrPageDeltaY *= static_cast<int32_t>(mMultiplierY[index]); + } else { + aEvent->mLineOrPageDeltaX = 0; + aEvent->mLineOrPageDeltaY = 0; + } + + aEvent->mCustomizedByUserPrefs = + ((mMultiplierX[index] != 1.0) || (mMultiplierY[index] != 1.0) || + (mMultiplierZ[index] != 1.0)); +} + +void +EventStateManager::WheelPrefs::CancelApplyingUserPrefsFromOverflowDelta( + WidgetWheelEvent* aEvent) +{ + Index index = GetIndexFor(aEvent); + Init(index); + + // XXX If the multiplier pref value is negative, the scroll direction was + // changed and caused to scroll different direction. In such case, + // this method reverts the sign of overflowDelta. Does it make widget + // happy? Although, widget can know the pref applied delta values by + // referrencing the deltaX and deltaY of the event. + + if (mMultiplierX[index]) { + aEvent->mOverflowDeltaX /= mMultiplierX[index]; + } + if (mMultiplierY[index]) { + aEvent->mOverflowDeltaY /= mMultiplierY[index]; + } +} + +EventStateManager::WheelPrefs::Action +EventStateManager::WheelPrefs::ComputeActionFor(WidgetWheelEvent* aEvent) +{ + Index index = GetIndexFor(aEvent); + Init(index); + + bool deltaXPreferred = + (Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaY) && + Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaZ)); + Action* actions = deltaXPreferred ? mOverriddenActionsX : mActions; + if (actions[index] == ACTION_NONE || actions[index] == ACTION_SCROLL) { + return actions[index]; + } + + // Momentum events shouldn't run special actions. + if (aEvent->mIsMomentum) { + // Use the default action. Note that user might kill the wheel scrolling. + Init(INDEX_DEFAULT); + return (actions[INDEX_DEFAULT] == ACTION_SCROLL) ? ACTION_SCROLL : + ACTION_NONE; + } + + return actions[index]; +} + +bool +EventStateManager::WheelPrefs::NeedToComputeLineOrPageDelta( + WidgetWheelEvent* aEvent) +{ + Index index = GetIndexFor(aEvent); + Init(index); + + return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) || + (mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0); +} + +void +EventStateManager::WheelPrefs::GetUserPrefsForEvent(WidgetWheelEvent* aEvent, + double* aOutMultiplierX, + double* aOutMultiplierY) +{ + Index index = GetIndexFor(aEvent); + Init(index); + + *aOutMultiplierX = mMultiplierX[index]; + *aOutMultiplierY = mMultiplierY[index]; +} + +// static +bool +EventStateManager::WheelPrefs::WheelEventsEnabledOnPlugins() +{ + if (!sInstance) { + GetInstance(); // initializing sWheelEventsEnabledOnPlugins + } + return sWheelEventsEnabledOnPlugins; +} + +bool +EventStateManager::WheelEventIsScrollAction(WidgetWheelEvent* aEvent) +{ + return aEvent->mMessage == eWheel && + WheelPrefs::GetInstance()->ComputeActionFor(aEvent) == WheelPrefs::ACTION_SCROLL; +} + +void +EventStateManager::GetUserPrefsForWheelEvent(WidgetWheelEvent* aEvent, + double* aOutMultiplierX, + double* aOutMultiplierY) +{ + WheelPrefs::GetInstance()->GetUserPrefsForEvent( + aEvent, aOutMultiplierX, aOutMultiplierY); +} + +bool +EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX( + WidgetWheelEvent* aEvent) +{ + Index index = GetIndexFor(aEvent); + Init(index); + return Abs(mMultiplierX[index]) >= + MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL; +} + +bool +EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY( + WidgetWheelEvent* aEvent) +{ + Index index = GetIndexFor(aEvent); + Init(index); + return Abs(mMultiplierY[index]) >= + MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL; +} + +/******************************************************************/ +/* mozilla::EventStateManager::Prefs */ +/******************************************************************/ + +bool EventStateManager::Prefs::sKeyCausesActivation = true; +bool EventStateManager::Prefs::sClickHoldContextMenu = false; +int32_t EventStateManager::Prefs::sGenericAccessModifierKey = -1; +int32_t EventStateManager::Prefs::sChromeAccessModifierMask = 0; +int32_t EventStateManager::Prefs::sContentAccessModifierMask = 0; + +// static +void +EventStateManager::Prefs::Init() +{ + DebugOnly<nsresult> rv = Preferences::RegisterCallback(OnChange, "dom.popup_allowed_events"); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"dom.popup_allowed_events\""); + + static bool sPrefsAlreadyCached = false; + if (sPrefsAlreadyCached) { + return; + } + + rv = Preferences::AddBoolVarCache(&sKeyCausesActivation, + "accessibility.accesskeycausesactivation", + sKeyCausesActivation); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"accessibility.accesskeycausesactivation\""); + rv = Preferences::AddBoolVarCache(&sClickHoldContextMenu, + "ui.click_hold_context_menus", + sClickHoldContextMenu); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"ui.click_hold_context_menus\""); + rv = Preferences::AddIntVarCache(&sGenericAccessModifierKey, + "ui.key.generalAccessKey", + sGenericAccessModifierKey); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"ui.key.generalAccessKey\""); + rv = Preferences::AddIntVarCache(&sChromeAccessModifierMask, + "ui.key.chromeAccess", + sChromeAccessModifierMask); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"ui.key.chromeAccess\""); + rv = Preferences::AddIntVarCache(&sContentAccessModifierMask, + "ui.key.contentAccess", + sContentAccessModifierMask); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"ui.key.contentAccess\""); + sPrefsAlreadyCached = true; +} + +// static +void +EventStateManager::Prefs::OnChange(const char* aPrefName, void*) +{ + nsDependentCString prefName(aPrefName); + if (prefName.EqualsLiteral("dom.popup_allowed_events")) { + Event::PopupAllowedEventsChanged(); + } +} + +// static +void +EventStateManager::Prefs::Shutdown() +{ + Preferences::UnregisterCallback(OnChange, "dom.popup_allowed_events"); +} + +// static +int32_t +EventStateManager::Prefs::ChromeAccessModifierMask() +{ + return GetAccessModifierMask(nsIDocShellTreeItem::typeChrome); +} + +// static +int32_t +EventStateManager::Prefs::ContentAccessModifierMask() +{ + return GetAccessModifierMask(nsIDocShellTreeItem::typeContent); +} + +// static +int32_t +EventStateManager::Prefs::GetAccessModifierMask(int32_t aItemType) +{ + switch (sGenericAccessModifierKey) { + case -1: break; // use the individual prefs + case nsIDOMKeyEvent::DOM_VK_SHIFT: return NS_MODIFIER_SHIFT; + case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL; + case nsIDOMKeyEvent::DOM_VK_ALT: return NS_MODIFIER_ALT; + case nsIDOMKeyEvent::DOM_VK_META: return NS_MODIFIER_META; + case nsIDOMKeyEvent::DOM_VK_WIN: return NS_MODIFIER_OS; + default: return 0; + } + + switch (aItemType) { + case nsIDocShellTreeItem::typeChrome: + return sChromeAccessModifierMask; + case nsIDocShellTreeItem::typeContent: + return sContentAccessModifierMask; + default: + return 0; + } +} + +/******************************************************************/ +/* mozilla::AutoHandlingUserInputStatePusher */ +/******************************************************************/ + +AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher( + bool aIsHandlingUserInput, + WidgetEvent* aEvent, + nsIDocument* aDocument) : + mIsHandlingUserInput(aIsHandlingUserInput), + mIsMouseDown(aEvent && aEvent->mMessage == eMouseDown), + mResetFMMouseButtonHandlingState(false) +{ + if (!aIsHandlingUserInput) { + return; + } + EventStateManager::StartHandlingUserInput(); + if (mIsMouseDown) { + nsIPresShell::SetCapturingContent(nullptr, 0); + nsIPresShell::AllowMouseCapture(true); + } + if (!aDocument || !aEvent || !aEvent->IsTrusted()) { + return; + } + mResetFMMouseButtonHandlingState = + (aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp); + if (mResetFMMouseButtonHandlingState) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ENSURE_TRUE_VOID(fm); + // If it's in modal state, mouse button event handling may be nested. + // E.g., a modal dialog is opened at mousedown or mouseup event handler + // and the dialog is clicked. Therefore, we should store current + // mouse button event handling document if nsFocusManager already has it. + mMouseButtonEventHandlingDocument = + fm->SetMouseButtonHandlingDocument(aDocument); + } +} + +AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher() +{ + if (!mIsHandlingUserInput) { + return; + } + EventStateManager::StopHandlingUserInput(); + if (mIsMouseDown) { + nsIPresShell::AllowMouseCapture(false); + } + if (mResetFMMouseButtonHandlingState) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ENSURE_TRUE_VOID(fm); + nsCOMPtr<nsIDocument> handlingDocument = + fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument); + } +} + +} // namespace mozilla diff --git a/dom/events/EventStateManager.h b/dom/events/EventStateManager.h new file mode 100644 index 000000000..49ecf0586 --- /dev/null +++ b/dom/events/EventStateManager.h @@ -0,0 +1,1051 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_EventStateManager_h_ +#define mozilla_EventStateManager_h_ + +#include "mozilla/EventForwards.h" + +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/TimeStamp.h" +#include "nsIFrame.h" +#include "Units.h" + +class nsFrameLoader; +class nsIContent; +class nsIDocument; +class nsIDocShell; +class nsIDocShellTreeItem; +class imgIContainer; +class EnterLeaveDispatcher; +class nsIContentViewer; +class nsIScrollableFrame; +class nsITimer; +class nsPresContext; + +namespace mozilla { + +class EnterLeaveDispatcher; +class EventStates; +class IMEContentObserver; +class ScrollbarsForWheel; +class WheelTransaction; + +namespace dom { +class DataTransfer; +class Element; +class TabParent; +} // namespace dom + +class OverOutElementsWrapper final : public nsISupports +{ + ~OverOutElementsWrapper(); + +public: + OverOutElementsWrapper(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(OverOutElementsWrapper) + + nsWeakFrame mLastOverFrame; + + nsCOMPtr<nsIContent> mLastOverElement; + + // The last element on which we fired a over event, or null if + // the last over event we fired has finished processing. + nsCOMPtr<nsIContent> mFirstOverEventElement; + + // The last element on which we fired a out event, or null if + // the last out event we fired has finished processing. + nsCOMPtr<nsIContent> mFirstOutEventElement; +}; + +class EventStateManager : public nsSupportsWeakReference, + public nsIObserver +{ + friend class mozilla::EnterLeaveDispatcher; + friend class mozilla::ScrollbarsForWheel; + friend class mozilla::WheelTransaction; + + virtual ~EventStateManager(); + +public: + EventStateManager(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsresult Init(); + nsresult Shutdown(); + + /* The PreHandleEvent method is called before event dispatch to either + * the DOM or frames. Any processing which must not be prevented or + * cancelled should occur here. Any processing which is intended to + * be conditional based on either DOM or frame processing should occur in + * PostHandleEvent. Any centralized event processing which must occur before + * DOM or frame event handling should occur here as well. + */ + nsresult PreHandleEvent(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIFrame* aTargetFrame, + nsIContent* aTargetContent, + nsEventStatus* aStatus); + + /* The PostHandleEvent method should contain all system processing which + * should occur conditionally based on DOM or frame processing. It should + * also contain any centralized event processing which must occur after + * DOM and frame processing. + */ + nsresult PostHandleEvent(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIFrame* aTargetFrame, + nsEventStatus* aStatus); + + void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent, + nsEventStatus& aStatus, + bool dispatchedToContentProcess); + + /** + * DispatchLegacyMouseScrollEvents() dispatches eLegacyMouseLineOrPageScroll + * event and eLegacyMousePixelScroll event for compatibility with old Gecko. + */ + void DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + nsEventStatus* aStatus); + + void NotifyDestroyPresContext(nsPresContext* aPresContext); + void SetPresContext(nsPresContext* aPresContext); + void ClearFrameRefs(nsIFrame* aFrame); + + nsIFrame* GetEventTarget(); + already_AddRefed<nsIContent> GetEventTargetContent(WidgetEvent* aEvent); + + /** + * Notify that the given NS_EVENT_STATE_* bit has changed for this content. + * @param aContent Content which has changed states + * @param aState Corresponding state flags such as NS_EVENT_STATE_FOCUS + * @return Whether the content was able to change all states. Returns false + * if a resulting DOM event causes the content node passed in + * to not change states. Note, the frame for the content may + * change as a result of the content state change, because of + * frame reconstructions that may occur, but this does not + * affect the return value. + */ + bool SetContentState(nsIContent* aContent, EventStates aState); + void ContentRemoved(nsIDocument* aDocument, nsIContent* aContent); + bool EventStatusOK(WidgetGUIEvent* aEvent); + + /** + * EventStateManager stores IMEContentObserver while it's observing contents. + * Following mehtods are called by IMEContentObserver when it starts to + * observe or stops observing the content. + */ + void OnStartToObserveContent(IMEContentObserver* aIMEContentObserver); + void OnStopObservingContent(IMEContentObserver* aIMEContentObserver); + + /** + * TryToFlushPendingNotificationsToIME() suggests flushing pending + * notifications to IME to IMEContentObserver. + */ + void TryToFlushPendingNotificationsToIME(); + + /** + * Register accesskey on the given element. When accesskey is activated then + * the element will be notified via nsIContent::PerformAccesskey() method. + * + * @param aContent the given element + * @param aKey accesskey + */ + void RegisterAccessKey(nsIContent* aContent, uint32_t aKey); + + /** + * Unregister accesskey for the given element. + * + * @param aContent the given element + * @param aKey accesskey + */ + void UnregisterAccessKey(nsIContent* aContent, uint32_t aKey); + + /** + * Get accesskey registered on the given element or 0 if there is none. + * + * @param aContent the given element (must not be null) + * @return registered accesskey + */ + uint32_t GetRegisteredAccessKey(nsIContent* aContent); + + static void GetAccessKeyLabelPrefix(dom::Element* aElement, nsAString& aPrefix); + + bool HandleAccessKey(WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext, + nsTArray<uint32_t>& aAccessCharCodes, + int32_t aModifierMask, + bool aMatchesContentAccessKey) + { + return HandleAccessKey(aEvent, aPresContext, aAccessCharCodes, + aMatchesContentAccessKey, nullptr, + eAccessKeyProcessingNormal, aModifierMask); + } + + nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer, + bool aHaveHotspot, float aHotspotX, float aHotspotY, + nsIWidget* aWidget, bool aLockCursor); + + static void StartHandlingUserInput() + { + ++sUserInputEventDepth; + ++sUserInputCounter; + if (sUserInputEventDepth == 1) { + sLatestUserInputStart = sHandlingInputStart = TimeStamp::Now(); + } + } + + static void StopHandlingUserInput() + { + --sUserInputEventDepth; + if (sUserInputEventDepth == 0) { + sHandlingInputStart = TimeStamp(); + } + } + + /** + * Returns true if the current code is being executed as a result of + * user input. This includes anything that is initiated by user, + * with the exception of page load events or mouse over events. If + * this method is called from asynchronously executed code, such as + * during layout reflows, it will return false. If more time has + * elapsed since the user input than is specified by the + * dom.event.handling-user-input-time-limit pref (default 1 second), + * this function also returns false. + */ + static bool IsHandlingUserInput(); + + /** + * Get the number of user inputs handled since process start. This + * includes anything that is initiated by user, with the exception + * of page load events or mouse over events. + */ + static uint64_t UserInputCount() + { + return sUserInputCounter; + } + + /** + * Get the timestamp at which the latest user input was handled. + * + * Guaranteed to be monotonic. Until the first user input, return + * the epoch. + */ + static TimeStamp LatestUserInputStart() + { + return sLatestUserInputStart; + } + + nsPresContext* GetPresContext() { return mPresContext; } + + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EventStateManager, + nsIObserver) + + static nsIDocument* sMouseOverDocument; + + static EventStateManager* GetActiveEventStateManager() { return sActiveESM; } + + // Sets aNewESM to be the active event state manager, and + // if aContent is non-null, marks the object as active. + static void SetActiveManager(EventStateManager* aNewESM, + nsIContent* aContent); + + // Sets the full-screen event state on aElement to aIsFullScreen. + static void SetFullScreenState(dom::Element* aElement, bool aIsFullScreen); + + static bool IsRemoteTarget(nsIContent* aTarget); + + // Returns true if the given WidgetWheelEvent will resolve to a scroll action. + static bool WheelEventIsScrollAction(WidgetWheelEvent* aEvent); + + // Returns user-set multipliers for a wheel event. + static void GetUserPrefsForWheelEvent(WidgetWheelEvent* aEvent, + double* aOutMultiplierX, + double* aOutMultiplierY); + + // Returns whether or not a frame can be vertically scrolled with a mouse + // wheel (as opposed to, say, a selection or touch scroll). + static bool CanVerticallyScrollFrameWithWheel(nsIFrame* aFrame); + + // Holds the point in screen coords that a mouse event was dispatched to, + // before we went into pointer lock mode. This is constantly updated while + // the pointer is not locked, but we don't update it while the pointer is + // locked. This is used by dom::Event::GetScreenCoords() to make mouse + // events' screen coord appear frozen at the last mouse position while + // the pointer is locked. + static CSSIntPoint sLastScreenPoint; + + // Holds the point in client coords of the last mouse event. Used by + // dom::Event::GetClientCoords() to make mouse events' client coords appear + // frozen at the last mouse position while the pointer is locked. + static CSSIntPoint sLastClientPoint; + + static bool sIsPointerLocked; + static nsWeakPtr sPointerLockedElement; + static nsWeakPtr sPointerLockedDoc; + + /** + * If the absolute values of mMultiplierX and/or mMultiplierY are equal or + * larger than this value, the computed scroll amount isn't rounded down to + * the page width or height. + */ + enum { + MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL = 1000 + }; + +protected: + /** + * Prefs class capsules preference management. + */ + class Prefs + { + public: + static bool KeyCausesActivation() { return sKeyCausesActivation; } + static bool ClickHoldContextMenu() { return sClickHoldContextMenu; } + static int32_t ChromeAccessModifierMask(); + static int32_t ContentAccessModifierMask(); + + static void Init(); + static void OnChange(const char* aPrefName, void*); + static void Shutdown(); + + private: + static bool sKeyCausesActivation; + static bool sClickHoldContextMenu; + static int32_t sGenericAccessModifierKey; + static int32_t sChromeAccessModifierMask; + static int32_t sContentAccessModifierMask; + + static int32_t GetAccessModifierMask(int32_t aItemType); + }; + + /** + * Get appropriate access modifier mask for the aDocShell. Returns -1 if + * access key isn't available. + */ + static int32_t GetAccessModifierMaskFor(nsISupports* aDocShell); + + /* + * If aTargetFrame's widget has a cached cursor value, resets the cursor + * such that the next call to SetCursor on the widget will force an update + * of the native cursor. For use in getting puppet widget to update its + * cursor between mouse exit / enter transitions. This call basically wraps + * nsIWidget ClearCachedCursor. + */ + void ClearCachedWidgetCursor(nsIFrame* aTargetFrame); + + void UpdateCursor(nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsIFrame* aTargetFrame, + nsEventStatus* aStatus); + /** + * Turn a GUI mouse/pointer event into a mouse/pointer event targeted at the specified + * content. This returns the primary frame for the content (or null + * if it goes away during the event). + */ + nsIFrame* DispatchMouseOrPointerEvent(WidgetMouseEvent* aMouseEvent, + EventMessage aMessage, + nsIContent* aTargetContent, + nsIContent* aRelatedContent); + /** + * Synthesize DOM pointerover and pointerout events + */ + void GeneratePointerEnterExit(EventMessage aMessage, + WidgetMouseEvent* aEvent); + /** + * Synthesize DOM and frame mouseover and mouseout events from this + * MOUSE_MOVE or MOUSE_EXIT event. + */ + void GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent); + /** + * Tell this ESM and ESMs in parent documents that the mouse is + * over some content in this document. + */ + void NotifyMouseOver(WidgetMouseEvent* aMouseEvent, + nsIContent* aContent); + /** + * Tell this ESM and ESMs in affected child documents that the mouse + * has exited this document's currently hovered content. + * @param aMouseEvent the event that triggered the mouseout + * @param aMovingInto the content node we've moved into. This is used to set + * the relatedTarget for mouseout events. Also, if it's non-null + * NotifyMouseOut will NOT change the current hover content to null; + * in that case the caller is responsible for updating hover state. + */ + void NotifyMouseOut(WidgetMouseEvent* aMouseEvent, + nsIContent* aMovingInto); + void GenerateDragDropEnterExit(nsPresContext* aPresContext, + WidgetDragEvent* aDragEvent); + + /** + * Return mMouseEnterLeaveHelper or relevant mPointersEnterLeaveHelper elements wrapper. + * If mPointersEnterLeaveHelper does not contain wrapper for pointerId it create new one + */ + OverOutElementsWrapper* GetWrapperByEventID(WidgetMouseEvent* aMouseEvent); + + /** + * Fire the dragenter and dragexit/dragleave events when the mouse moves to a + * new target. + * + * @param aRelatedTarget relatedTarget to set for the event + * @param aTargetContent target to set for the event + * @param aTargetFrame target frame for the event + */ + void FireDragEnterOrExit(nsPresContext* aPresContext, + WidgetDragEvent* aDragEvent, + EventMessage aMessage, + nsIContent* aRelatedTarget, + nsIContent* aTargetContent, + nsWeakFrame& aTargetFrame); + /** + * Update the initial drag session data transfer with any changes that occur + * on cloned data transfer objects used for events. + */ + void UpdateDragDataTransfer(WidgetDragEvent* dragEvent); + + nsresult SetClickCount(WidgetMouseEvent* aEvent, nsEventStatus* aStatus); + nsresult CheckForAndDispatchClick(WidgetMouseEvent* aEvent, + nsEventStatus* aStatus); + void EnsureDocument(nsPresContext* aPresContext); + void FlushPendingEvents(nsPresContext* aPresContext); + + /** + * The phases of HandleAccessKey processing. See below. + */ + typedef enum { + eAccessKeyProcessingNormal = 0, + eAccessKeyProcessingUp, + eAccessKeyProcessingDown + } ProcessingAccessKeyState; + + /** + * Access key handling. If there is registered content for the accesskey + * given by the key event and modifier mask then call + * content.PerformAccesskey(), otherwise call HandleAccessKey() recursively, + * on descendant docshells first, then on the ancestor (with |aBubbledFrom| + * set to the docshell associated with |this|), until something matches. + * + * @param aEvent the keyboard event triggering the acccess key + * @param aPresContext the presentation context + * @param aAccessCharCodes list of charcode candidates + * @param aMatchesContentAccessKey true if the content accesskey modifier is pressed + * @param aBubbledFrom is used by an ancestor to avoid calling HandleAccessKey() + * on the child the call originally came from, i.e. this is the child + * that recursively called us in its Up phase. The initial caller + * passes |nullptr| here. This is to avoid an infinite loop. + * @param aAccessKeyState Normal, Down or Up processing phase (see enums + * above). The initial event receiver uses 'normal', then 'down' when + * processing children and Up when recursively calling its ancestor. + * @param aModifierMask modifier mask for the key event + */ + bool HandleAccessKey(WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext, + nsTArray<uint32_t>& aAccessCharCodes, + bool aMatchesContentAccessKey, + nsIDocShellTreeItem* aBubbledFrom, + ProcessingAccessKeyState aAccessKeyState, + int32_t aModifierMask); + + bool ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes, + bool aIsTrustedEvent); + + //--------------------------------------------- + // DocShell Focus Traversal Methods + //--------------------------------------------- + + nsIContent* GetFocusedContent(); + bool IsShellVisible(nsIDocShell* aShell); + + // These functions are for mousewheel and pixel scrolling + + class WheelPrefs + { + public: + static WheelPrefs* GetInstance(); + static void Shutdown(); + + /** + * ApplyUserPrefsToDelta() overrides the wheel event's delta values with + * user prefs. + */ + void ApplyUserPrefsToDelta(WidgetWheelEvent* aEvent); + + /** + * Returns whether or not ApplyUserPrefsToDelta() would change the delta + * values of an event. + */ + void GetUserPrefsForEvent(WidgetWheelEvent* aEvent, + double* aOutMultiplierX, + double* aOutMultiplierY); + + /** + * If ApplyUserPrefsToDelta() changed the delta values with customized + * prefs, the overflowDelta values would be inflated. + * CancelApplyingUserPrefsFromOverflowDelta() cancels the inflation. + */ + void CancelApplyingUserPrefsFromOverflowDelta(WidgetWheelEvent* aEvent); + + /** + * Computes the default action for the aEvent with the prefs. + */ + enum Action : uint8_t + { + ACTION_NONE = 0, + ACTION_SCROLL, + ACTION_HISTORY, + ACTION_ZOOM, + ACTION_LAST = ACTION_ZOOM, + // Following actions are used only by internal processing. So, cannot + // specified by prefs. + ACTION_SEND_TO_PLUGIN + }; + Action ComputeActionFor(WidgetWheelEvent* aEvent); + + /** + * NeedToComputeLineOrPageDelta() returns if the aEvent needs to be + * computed the lineOrPageDelta values. + */ + bool NeedToComputeLineOrPageDelta(WidgetWheelEvent* aEvent); + + /** + * IsOverOnePageScrollAllowed*() checks whether wheel scroll amount should + * be rounded down to the page width/height (false) or not (true). + */ + bool IsOverOnePageScrollAllowedX(WidgetWheelEvent* aEvent); + bool IsOverOnePageScrollAllowedY(WidgetWheelEvent* aEvent); + + /** + * WheelEventsEnabledOnPlugins() returns true if user wants to use mouse + * wheel on plugins. + */ + static bool WheelEventsEnabledOnPlugins(); + + private: + WheelPrefs(); + ~WheelPrefs(); + + static void OnPrefChanged(const char* aPrefName, void* aClosure); + + enum Index + { + INDEX_DEFAULT = 0, + INDEX_ALT, + INDEX_CONTROL, + INDEX_META, + INDEX_SHIFT, + INDEX_OS, + COUNT_OF_MULTIPLIERS + }; + + /** + * GetIndexFor() returns the index of the members which should be used for + * the aEvent. When only one modifier key of MODIFIER_ALT, + * MODIFIER_CONTROL, MODIFIER_META, MODIFIER_SHIFT or MODIFIER_OS is + * pressed, returns the index for the modifier. Otherwise, this return the + * default index which is used at either no modifier key is pressed or + * two or modifier keys are pressed. + */ + Index GetIndexFor(WidgetWheelEvent* aEvent); + + /** + * GetPrefNameBase() returns the base pref name for aEvent. + * It's decided by GetModifierForPref() which modifier should be used for + * the aEvent. + * + * @param aBasePrefName The result, must be "mousewheel.with_*." or + * "mousewheel.default.". + */ + void GetBasePrefName(Index aIndex, nsACString& aBasePrefName); + + void Init(Index aIndex); + + void Reset(); + + bool mInit[COUNT_OF_MULTIPLIERS]; + double mMultiplierX[COUNT_OF_MULTIPLIERS]; + double mMultiplierY[COUNT_OF_MULTIPLIERS]; + double mMultiplierZ[COUNT_OF_MULTIPLIERS]; + Action mActions[COUNT_OF_MULTIPLIERS]; + /** + * action values overridden by .override_x pref. + * If an .override_x value is -1, same as the + * corresponding mActions value. + */ + Action mOverriddenActionsX[COUNT_OF_MULTIPLIERS]; + + static WheelPrefs* sInstance; + + static bool sWheelEventsEnabledOnPlugins; + }; + + /** + * DeltaDirection is used for specifying whether the called method should + * handle vertical delta or horizontal delta. + * This is clearer than using bool. + */ + enum DeltaDirection + { + DELTA_DIRECTION_X = 0, + DELTA_DIRECTION_Y + }; + + struct MOZ_STACK_CLASS EventState + { + bool mDefaultPrevented; + bool mDefaultPreventedByContent; + + EventState() : + mDefaultPrevented(false), mDefaultPreventedByContent(false) + { + } + }; + + /** + * SendLineScrollEvent() dispatches a DOMMouseScroll event for the + * WidgetWheelEvent. This method shouldn't be called for non-trusted + * wheel event because it's not necessary for compatiblity. + * + * @param aTargetFrame The event target of wheel event. + * @param aEvent The original Wheel event. + * @param aState The event which should be set to the dispatching + * event. This also returns the dispatched event + * state. + * @param aDelta The delta value of the event. + * @param aDeltaDirection The X/Y direction of dispatching event. + */ + void SendLineScrollEvent(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + EventState& aState, + int32_t aDelta, + DeltaDirection aDeltaDirection); + + /** + * SendPixelScrollEvent() dispatches a MozMousePixelScroll event for the + * WidgetWheelEvent. This method shouldn't be called for non-trusted + * wheel event because it's not necessary for compatiblity. + * + * @param aTargetFrame The event target of wheel event. + * @param aEvent The original Wheel event. + * @param aState The event which should be set to the dispatching + * event. This also returns the dispatched event + * state. + * @param aPixelDelta The delta value of the event. + * @param aDeltaDirection The X/Y direction of dispatching event. + */ + void SendPixelScrollEvent(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + EventState& aState, + int32_t aPixelDelta, + DeltaDirection aDeltaDirection); + + /** + * ComputeScrollTarget() returns the scrollable frame which should be + * scrolled. + * + * @param aTargetFrame The event target of the wheel event. + * @param aEvent The handling mouse wheel event. + * @param aOptions The options for finding the scroll target. + * Callers should use COMPUTE_*. + * @return The scrollable frame which should be scrolled. + */ + // These flags are used in ComputeScrollTarget(). Callers should use + // COMPUTE_*. + enum + { + PREFER_MOUSE_WHEEL_TRANSACTION = 0x00000001, + PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS = 0x00000002, + PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS = 0x00000004, + START_FROM_PARENT = 0x00000008, + INCLUDE_PLUGIN_AS_TARGET = 0x00000010 + }; + enum ComputeScrollTargetOptions + { + // At computing scroll target for legacy mouse events, we should return + // first scrollable element even when it's not scrollable to the direction. + COMPUTE_LEGACY_MOUSE_SCROLL_EVENT_TARGET = 0, + // Default action prefers the scrolled element immediately before if it's + // still under the mouse cursor. Otherwise, it prefers the nearest + // scrollable ancestor which will be scrolled actually. + COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN = + (PREFER_MOUSE_WHEEL_TRANSACTION | + PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS | + PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS), + // When this is specified, the result may be nsPluginFrame. In such case, + // the frame doesn't have nsIScrollableFrame interface. + COMPUTE_DEFAULT_ACTION_TARGET = + (COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN | + INCLUDE_PLUGIN_AS_TARGET), + // Look for the nearest scrollable ancestor which can be scrollable with + // aEvent. + COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS = + (PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS | START_FROM_PARENT), + COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS = + (PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS | START_FROM_PARENT) + }; + static ComputeScrollTargetOptions RemovePluginFromTarget( + ComputeScrollTargetOptions aOptions) + { + switch (aOptions) { + case COMPUTE_DEFAULT_ACTION_TARGET: + return COMPUTE_DEFAULT_ACTION_TARGET_EXCEPT_PLUGIN; + default: + MOZ_ASSERT(!(aOptions & INCLUDE_PLUGIN_AS_TARGET)); + return aOptions; + } + } + nsIFrame* ComputeScrollTarget(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent, + ComputeScrollTargetOptions aOptions); + + nsIFrame* ComputeScrollTarget(nsIFrame* aTargetFrame, + double aDirectionX, + double aDirectionY, + WidgetWheelEvent* aEvent, + ComputeScrollTargetOptions aOptions); + + /** + * GetScrollAmount() returns the scroll amount in app uints of one line or + * one page. If the wheel event scrolls a page, returns the page width and + * height. Otherwise, returns line height for both its width and height. + * + * @param aScrollableFrame A frame which will be scrolled by the event. + * The result of ComputeScrollTarget() is + * expected for this value. + * This can be nullptr if there is no scrollable + * frame. Then, this method uses root frame's + * line height or visible area's width and height. + */ + nsSize GetScrollAmount(nsPresContext* aPresContext, + WidgetWheelEvent* aEvent, + nsIScrollableFrame* aScrollableFrame); + + /** + * DoScrollText() scrolls the scrollable frame for aEvent. + */ + void DoScrollText(nsIScrollableFrame* aScrollableFrame, + WidgetWheelEvent* aEvent); + + void DoScrollHistory(int32_t direction); + void DoScrollZoom(nsIFrame *aTargetFrame, int32_t adjustment); + nsresult GetContentViewer(nsIContentViewer** aCv); + nsresult ChangeTextSize(int32_t change); + nsresult ChangeFullZoom(int32_t change); + + /** + * DeltaAccumulator class manages delta values for dispatching DOMMouseScroll + * event. If wheel events are caused by pixel scroll only devices or + * the delta values are customized by prefs, this class stores the delta + * values and set lineOrPageDelta values. + */ + class DeltaAccumulator + { + public: + static DeltaAccumulator* GetInstance() + { + if (!sInstance) { + sInstance = new DeltaAccumulator; + } + return sInstance; + } + + static void Shutdown() + { + delete sInstance; + sInstance = nullptr; + } + + bool IsInTransaction() { return mHandlingDeltaMode != UINT32_MAX; } + + /** + * InitLineOrPageDelta() stores pixel delta values of WidgetWheelEvents + * which are caused if it's needed. And if the accumulated delta becomes a + * line height, sets lineOrPageDeltaX and lineOrPageDeltaY automatically. + */ + void InitLineOrPageDelta(nsIFrame* aTargetFrame, + EventStateManager* aESM, + WidgetWheelEvent* aEvent); + + /** + * Reset() resets all members. + */ + void Reset(); + + /** + * ComputeScrollAmountForDefaultAction() computes the default action's + * scroll amount in device pixels with mPendingScrollAmount*. + */ + nsIntPoint ComputeScrollAmountForDefaultAction( + WidgetWheelEvent* aEvent, + const nsIntSize& aScrollAmountInDevPixels); + + private: + DeltaAccumulator() : + mX(0.0), mY(0.0), mPendingScrollAmountX(0.0), mPendingScrollAmountY(0.0), + mHandlingDeltaMode(UINT32_MAX), mIsNoLineOrPageDeltaDevice(false) + { + } + + double mX; + double mY; + + // When default action of a wheel event is scroll but some delta values + // are ignored because the computed amount values are not integer, the + // fractional values are saved by these members. + double mPendingScrollAmountX; + double mPendingScrollAmountY; + + TimeStamp mLastTime; + + uint32_t mHandlingDeltaMode; + bool mIsNoLineOrPageDeltaDevice; + + static DeltaAccumulator* sInstance; + }; + + // end mousewheel functions + + /* + * When a touch gesture is about to start, this function determines what + * kind of gesture interaction we will want to use, based on what is + * underneath the initial touch point. + * Currently it decides between panning (finger scrolling) or dragging + * the target element, as well as the orientation to trigger panning and + * display visual boundary feedback. The decision is stored back in aEvent. + */ + void DecideGestureEvent(WidgetGestureNotifyEvent* aEvent, + nsIFrame* targetFrame); + + // routines for the d&d gesture tracking state machine + void BeginTrackingDragGesture(nsPresContext* aPresContext, + WidgetMouseEvent* aDownEvent, + nsIFrame* aDownFrame); + + friend class mozilla::dom::TabParent; + void BeginTrackingRemoteDragGesture(nsIContent* aContent); + void StopTrackingDragGesture(); + void GenerateDragGesture(nsPresContext* aPresContext, + WidgetInputEvent* aEvent); + + /** + * Determine which node the drag should be targeted at. + * This is either the node clicked when there is a selection, or, for HTML, + * the element with a draggable property set to true. + * + * aSelectionTarget - target to check for selection + * aDataTransfer - data transfer object that will contain the data to drag + * aSelection - [out] set to the selection to be dragged + * aTargetNode - [out] the draggable node, or null if there isn't one + */ + void DetermineDragTargetAndDefaultData(nsPIDOMWindowOuter* aWindow, + nsIContent* aSelectionTarget, + dom::DataTransfer* aDataTransfer, + nsISelection** aSelection, + nsIContent** aTargetNode); + + /* + * Perform the default handling for the dragstart event and set up a + * drag for aDataTransfer if it contains any data. Returns true if a drag has + * started. + * + * aDragEvent - the dragstart event + * aDataTransfer - the data transfer that holds the data to be dragged + * aDragTarget - the target of the drag + * aSelection - the selection to be dragged + */ + bool DoDefaultDragStart(nsPresContext* aPresContext, + WidgetDragEvent* aDragEvent, + dom::DataTransfer* aDataTransfer, + nsIContent* aDragTarget, + nsISelection* aSelection); + + bool IsTrackingDragGesture ( ) const { return mGestureDownContent != nullptr; } + /** + * Set the fields of aEvent to reflect the mouse position and modifier keys + * that were set when the user first pressed the mouse button (stored by + * BeginTrackingDragGesture). aEvent->mWidget must be + * mCurrentTarget->GetNearestWidget(). + */ + void FillInEventFromGestureDown(WidgetMouseEvent* aEvent); + + nsresult DoContentCommandEvent(WidgetContentCommandEvent* aEvent); + nsresult DoContentCommandScrollEvent(WidgetContentCommandEvent* aEvent); + + dom::TabParent *GetCrossProcessTarget(); + bool IsTargetCrossProcess(WidgetGUIEvent* aEvent); + + bool DispatchCrossProcessEvent(WidgetEvent* aEvent, + nsFrameLoader* aRemote, + nsEventStatus *aStatus); + bool HandleCrossProcessEvent(WidgetEvent* aEvent, + nsEventStatus* aStatus); + + void ReleaseCurrentIMEContentObserver(); + + void HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); + +private: + static inline void DoStateChange(dom::Element* aElement, + EventStates aState, bool aAddState); + static inline void DoStateChange(nsIContent* aContent, EventStates aState, + bool aAddState); + static void UpdateAncestorState(nsIContent* aStartNode, + nsIContent* aStopBefore, + EventStates aState, + bool aAddState); + static void ResetLastOverForContent(const uint32_t& aIdx, + RefPtr<OverOutElementsWrapper>& aChunk, + nsIContent* aClosure); + + int32_t mLockCursor; + bool mLastFrameConsumedSetCursor; + + // Last mouse event mRefPoint (the offset from the widget's origin in + // device pixels) when mouse was locked, used to restore mouse position + // after unlocking. + static LayoutDeviceIntPoint sPreLockPoint; + + // Stores the mRefPoint of the last synthetic mouse move we dispatched + // to re-center the mouse when we were pointer locked. If this is (-1,-1) it + // means we've not recently dispatched a centering event. We use this to + // detect when we receive the synth event, so we can cancel and not send it + // to content. + static LayoutDeviceIntPoint sSynthCenteringPoint; + + nsWeakFrame mCurrentTarget; + nsCOMPtr<nsIContent> mCurrentTargetContent; + static nsWeakFrame sLastDragOverFrame; + + // Stores the mRefPoint (the offset from the widget's origin in device + // pixels) of the last mouse event. + static LayoutDeviceIntPoint sLastRefPoint; + + // member variables for the d&d gesture state machine + LayoutDeviceIntPoint mGestureDownPoint; // screen coordinates + // The content to use as target if we start a d&d (what we drag). + nsCOMPtr<nsIContent> mGestureDownContent; + // The content of the frame where the mouse-down event occurred. It's the same + // as the target in most cases but not always - for example when dragging + // an <area> of an image map this is the image. (bug 289667) + nsCOMPtr<nsIContent> mGestureDownFrameOwner; + // State of keys when the original gesture-down happened + Modifiers mGestureModifiers; + uint16_t mGestureDownButtons; + + nsCOMPtr<nsIContent> mLastLeftMouseDownContent; + nsCOMPtr<nsIContent> mLastLeftMouseDownContentParent; + nsCOMPtr<nsIContent> mLastMiddleMouseDownContent; + nsCOMPtr<nsIContent> mLastMiddleMouseDownContentParent; + nsCOMPtr<nsIContent> mLastRightMouseDownContent; + nsCOMPtr<nsIContent> mLastRightMouseDownContentParent; + + nsCOMPtr<nsIContent> mActiveContent; + nsCOMPtr<nsIContent> mHoverContent; + static nsCOMPtr<nsIContent> sDragOverContent; + nsCOMPtr<nsIContent> mURLTargetContent; + + nsPresContext* mPresContext; // Not refcnted + nsCOMPtr<nsIDocument> mDocument; // Doesn't necessarily need to be owner + + RefPtr<IMEContentObserver> mIMEContentObserver; + + uint32_t mLClickCount; + uint32_t mMClickCount; + uint32_t mRClickCount; + + bool mInTouchDrag; + + bool m_haveShutdown; + + // Time at which we began handling user input. Reset to the epoch + // once we have finished handling user input. + static TimeStamp sHandlingInputStart; + + // Time at which we began handling the latest user input. Not reset + // at the end of the input. + static TimeStamp sLatestUserInputStart; + + RefPtr<OverOutElementsWrapper> mMouseEnterLeaveHelper; + nsRefPtrHashtable<nsUint32HashKey, OverOutElementsWrapper> mPointersEnterLeaveHelper; + +public: + static nsresult UpdateUserActivityTimer(void); + // Array for accesskey support + nsCOMArray<nsIContent> mAccessKeys; + + // The number of user inputs handled since process start. This + // includes anything that is initiated by user, with the exception + // of page load events or mouse over events. + static uint64_t sUserInputCounter; + + // The current depth of user inputs. This includes anything that is + // initiated by user, with the exception of page load events or + // mouse over events. Incremented whenever we start handling a user + // input, decremented when we have finished handling a user + // input. This depth is *not* reset in case of nested event loops. + static int32_t sUserInputEventDepth; + + static bool sNormalLMouseEventInProcess; + + static EventStateManager* sActiveESM; + + static void ClearGlobalActiveContent(EventStateManager* aClearer); + + // Functions used for click hold context menus + nsCOMPtr<nsITimer> mClickHoldTimer; + void CreateClickHoldTimer(nsPresContext* aPresContext, + nsIFrame* aDownFrame, + WidgetGUIEvent* aMouseDownEvent); + void KillClickHoldTimer(); + void FireContextClick(); + + static void SetPointerLock(nsIWidget* aWidget, nsIContent* aElement) ; + static void sClickHoldCallback ( nsITimer* aTimer, void* aESM ) ; +}; + +/** + * This class is used while processing real user input. During this time, popups + * are allowed. For mousedown events, mouse capturing is also permitted. + */ +class AutoHandlingUserInputStatePusher +{ +public: + AutoHandlingUserInputStatePusher(bool aIsHandlingUserInput, + WidgetEvent* aEvent, + nsIDocument* aDocument); + ~AutoHandlingUserInputStatePusher(); + +protected: + bool mIsHandlingUserInput; + bool mIsMouseDown; + bool mResetFMMouseButtonHandlingState; + + nsCOMPtr<nsIDocument> mMouseButtonEventHandlingDocument; + +private: + // Hide so that this class can only be stack-allocated + static void* operator new(size_t /*size*/) CPP_THROW_NEW { return nullptr; } + static void operator delete(void* /*memory*/) {} +}; + +} // namespace mozilla + +// Click and double-click events need to be handled even for content that +// has no frame. This is required for Web compatibility. +#define NS_EVENT_NEEDS_FRAME(event) \ + (!(event)->HasPluginActivationEventMessage() && \ + (event)->mMessage != eMouseClick && \ + (event)->mMessage != eMouseDoubleClick) + +#endif // mozilla_EventStateManager_h_ diff --git a/dom/events/EventStates.h b/dom/events/EventStates.h new file mode 100644 index 000000000..2672d2897 --- /dev/null +++ b/dom/events/EventStates.h @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_EventStates_h_ +#define mozilla_EventStates_h_ + +#include "mozilla/Attributes.h" +#include "nsDebug.h" + +namespace mozilla { + +#define NS_EVENT_STATE_HIGHEST_SERVO_BIT 6 + +/** + * EventStates is the class used to represent the event states of nsIContent + * instances. These states are calculated by IntrinsicState() and + * ContentStatesChanged() has to be called when one of them changes thus + * informing the layout/style engine of the change. + * Event states are associated with pseudo-classes. + */ +class EventStates +{ +public: + typedef uint64_t InternalType; + typedef uint8_t ServoType; + + constexpr EventStates() + : mStates(0) + { + } + + // NOTE: the ideal scenario would be to have the default constructor public + // setting mStates to 0 and this constructor (without = 0) private. + // In that case, we could be sure that only macros at the end were creating + // EventStates instances with mStates set to something else than 0. + // Unfortunately, this constructor is needed at at least two places now. + explicit constexpr EventStates(InternalType aStates) + : mStates(aStates) + { + } + + EventStates constexpr operator|(const EventStates& aEventStates) const + { + return EventStates(mStates | aEventStates.mStates); + } + + EventStates& operator|=(const EventStates& aEventStates) + { + mStates |= aEventStates.mStates; + return *this; + } + + // NOTE: calling if (eventStates1 & eventStates2) will not build. + // This might work correctly if operator bool() is defined + // but using HasState, HasAllStates or HasAtLeastOneOfStates is recommended. + EventStates constexpr operator&(const EventStates& aEventStates) const + { + return EventStates(mStates & aEventStates.mStates); + } + + EventStates& operator&=(const EventStates& aEventStates) + { + mStates &= aEventStates.mStates; + return *this; + } + + bool operator==(const EventStates& aEventStates) const + { + return mStates == aEventStates.mStates; + } + + bool operator!=(const EventStates& aEventStates) const + { + return mStates != aEventStates.mStates; + } + + EventStates operator~() const + { + return EventStates(~mStates); + } + + EventStates operator^(const EventStates& aEventStates) const + { + return EventStates(mStates ^ aEventStates.mStates); + } + + EventStates& operator^=(const EventStates& aEventStates) + { + mStates ^= aEventStates.mStates; + return *this; + } + + /** + * Returns true if the EventStates instance is empty. + * A EventStates instance is empty if it contains no state. + * + * @return Whether if the object is empty. + */ + bool IsEmpty() const + { + return mStates == 0; + } + + /** + * Returns true if the EventStates instance contains the state + * contained in aEventStates. + * @note aEventStates should contain only one state. + * + * @param aEventStates The state to check. + * + * @return Whether the object has the state from aEventStates + */ + bool HasState(EventStates aEventStates) const + { +#ifdef DEBUG + // If aEventStates.mStates is a power of two, it contains only one state + // (or none, but we don't really care). + if ((aEventStates.mStates & (aEventStates.mStates - 1))) { + NS_ERROR("When calling HasState, " + "EventStates object has to contain only one state!"); + } +#endif // DEBUG + return mStates & aEventStates.mStates; + } + + /** + * Returns true if the EventStates instance contains one of the states + * contained in aEventStates. + * + * @param aEventStates The states to check. + * + * @return Whether the object has at least one state from aEventStates + */ + bool HasAtLeastOneOfStates(EventStates aEventStates) const + { + return mStates & aEventStates.mStates; + } + + /** + * Returns true if the EventStates instance contains all states + * contained in aEventStates. + * + * @param aEventStates The states to check. + * + * @return Whether the object has all states from aEventStates + */ + bool HasAllStates(EventStates aEventStates) const + { + return (mStates & aEventStates.mStates) == aEventStates.mStates; + } + + // We only need that method for inDOMUtils::GetContentState. + // If inDOMUtils::GetContentState is removed, this method should be removed. + InternalType GetInternalValue() const { + return mStates; + } + + /** + * Method used to get the appropriate state representation for Servo. + */ + ServoType ServoValue() const + { + return mStates & ((1 << (NS_EVENT_STATE_HIGHEST_SERVO_BIT + 1)) - 1); + } + +private: + InternalType mStates; +}; + +} // namespace mozilla + +/** + * The following macros are creating EventStates instance with different + * values depending of their meaning. + * Ideally, EventStates instance with values different than 0 should only be + * created that way. + */ + +// Helper to define a new EventStates macro. +#define NS_DEFINE_EVENT_STATE_MACRO(_val) \ + (mozilla::EventStates(mozilla::EventStates::InternalType(1) << _val)) + +/* + * In order to efficiently convert Gecko EventState values into Servo + * ElementState values [1], we maintain the invariant that the low bits of + * EventState can be masked off to form an ElementState (this works so + * long as Servo never supports a state that Gecko doesn't). + * + * This is unfortunately rather fragile for now, but we should soon have + * the infrastructure to statically-assert that these match up. If you + * need to change these, please notify somebody involved with Stylo. + * + * [1] https://github.com/servo/servo/blob/master/components/style/element_state.rs + */ + +// Mouse is down on content. +#define NS_EVENT_STATE_ACTIVE NS_DEFINE_EVENT_STATE_MACRO(0) +// Content has focus. +#define NS_EVENT_STATE_FOCUS NS_DEFINE_EVENT_STATE_MACRO(1) +// Mouse is hovering over content. +#define NS_EVENT_STATE_HOVER NS_DEFINE_EVENT_STATE_MACRO(2) +// Content is enabled (and can be disabled). +#define NS_EVENT_STATE_ENABLED NS_DEFINE_EVENT_STATE_MACRO(3) +// Content is disabled. +#define NS_EVENT_STATE_DISABLED NS_DEFINE_EVENT_STATE_MACRO(4) +// Content is checked. +#define NS_EVENT_STATE_CHECKED NS_DEFINE_EVENT_STATE_MACRO(5) +// Content is in the indeterminate state. +#define NS_EVENT_STATE_INDETERMINATE NS_DEFINE_EVENT_STATE_MACRO(6) + +/* + * Bits below here do not have Servo-related ordering constraints. + * + * Remember to change NS_EVENT_STATE_HIGHEST_SERVO_BIT at the top of the file if + * this changes! + */ + +// Drag is hovering over content. +#define NS_EVENT_STATE_DRAGOVER NS_DEFINE_EVENT_STATE_MACRO(7) +// Content is URL's target (ref). +#define NS_EVENT_STATE_URLTARGET NS_DEFINE_EVENT_STATE_MACRO(8) +// Content is required. +#define NS_EVENT_STATE_REQUIRED NS_DEFINE_EVENT_STATE_MACRO(9) +// Content is optional (and can be required). +#define NS_EVENT_STATE_OPTIONAL NS_DEFINE_EVENT_STATE_MACRO(10) +// Link has been visited. +#define NS_EVENT_STATE_VISITED NS_DEFINE_EVENT_STATE_MACRO(11) +// Link hasn't been visited. +#define NS_EVENT_STATE_UNVISITED NS_DEFINE_EVENT_STATE_MACRO(12) +// Content is valid (and can be invalid). +#define NS_EVENT_STATE_VALID NS_DEFINE_EVENT_STATE_MACRO(13) +// Content is invalid. +#define NS_EVENT_STATE_INVALID NS_DEFINE_EVENT_STATE_MACRO(14) +// Content value is in-range (and can be out-of-range). +#define NS_EVENT_STATE_INRANGE NS_DEFINE_EVENT_STATE_MACRO(15) +// Content value is out-of-range. +#define NS_EVENT_STATE_OUTOFRANGE NS_DEFINE_EVENT_STATE_MACRO(16) +// These two are temporary (see bug 302188) +// Content is read-only. +#define NS_EVENT_STATE_MOZ_READONLY NS_DEFINE_EVENT_STATE_MACRO(17) +// Content is editable. +#define NS_EVENT_STATE_MOZ_READWRITE NS_DEFINE_EVENT_STATE_MACRO(18) +// Content is the default one (meaning depends of the context). +#define NS_EVENT_STATE_DEFAULT NS_DEFINE_EVENT_STATE_MACRO(19) +// Content could not be rendered (image/object/etc). +#define NS_EVENT_STATE_BROKEN NS_DEFINE_EVENT_STATE_MACRO(20) +// Content disabled by the user (images turned off, say). +#define NS_EVENT_STATE_USERDISABLED NS_DEFINE_EVENT_STATE_MACRO(21) +// Content suppressed by the user (ad blocking, etc). +#define NS_EVENT_STATE_SUPPRESSED NS_DEFINE_EVENT_STATE_MACRO(22) +// Content is still loading such that there is nothing to show the +// user (eg an image which hasn't started coming in yet). +#define NS_EVENT_STATE_LOADING NS_DEFINE_EVENT_STATE_MACRO(23) +#define NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL NS_DEFINE_EVENT_STATE_MACRO(25) +// Handler for the content has been blocked. +#define NS_EVENT_STATE_HANDLER_BLOCKED NS_DEFINE_EVENT_STATE_MACRO(26) +// Handler for the content has been disabled. +#define NS_EVENT_STATE_HANDLER_DISABLED NS_DEFINE_EVENT_STATE_MACRO(27) +// Handler for the content has crashed +#define NS_EVENT_STATE_HANDLER_CRASHED NS_DEFINE_EVENT_STATE_MACRO(28) +// Content has focus and should show a ring. +#define NS_EVENT_STATE_FOCUSRING NS_DEFINE_EVENT_STATE_MACRO(29) +// Content is a submit control and the form isn't valid. +#define NS_EVENT_STATE_MOZ_SUBMITINVALID NS_DEFINE_EVENT_STATE_MACRO(30) +// UI friendly version of :invalid pseudo-class. +#define NS_EVENT_STATE_MOZ_UI_INVALID NS_DEFINE_EVENT_STATE_MACRO(31) +// UI friendly version of :valid pseudo-class. +#define NS_EVENT_STATE_MOZ_UI_VALID NS_DEFINE_EVENT_STATE_MACRO(32) +// Content is the full screen element, or a frame containing the +// current full-screen element. +#define NS_EVENT_STATE_FULL_SCREEN NS_DEFINE_EVENT_STATE_MACRO(33) +// This bit is currently free. +// #define NS_EVENT_STATE_?????????? NS_DEFINE_EVENT_STATE_MACRO(34) +// Handler for click to play plugin +#define NS_EVENT_STATE_TYPE_CLICK_TO_PLAY NS_DEFINE_EVENT_STATE_MACRO(35) +// Content is in the optimum region. +#define NS_EVENT_STATE_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(36) +// Content is in the suboptimal region. +#define NS_EVENT_STATE_SUB_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(37) +// Content is in the sub-suboptimal region. +#define NS_EVENT_STATE_SUB_SUB_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(38) +// Handler for click to play plugin (vulnerable w/update) +#define NS_EVENT_STATE_VULNERABLE_UPDATABLE NS_DEFINE_EVENT_STATE_MACRO(39) +// Handler for click to play plugin (vulnerable w/no update) +#define NS_EVENT_STATE_VULNERABLE_NO_UPDATE NS_DEFINE_EVENT_STATE_MACRO(40) +// Element is ltr (for :dir pseudo-class) +#define NS_EVENT_STATE_LTR NS_DEFINE_EVENT_STATE_MACRO(42) +// Element is rtl (for :dir pseudo-class) +#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) +// Element is transitioning for rules changed by style editor +#define NS_EVENT_STATE_STYLEEDITOR_TRANSITIONING NS_DEFINE_EVENT_STATE_MACRO(47) +// Content shows its placeholder +#define NS_EVENT_STATE_PLACEHOLDERSHOWN NS_DEFINE_EVENT_STATE_MACRO(48) +// Element has focus-within. +#define NS_EVENT_STATE_FOCUS_WITHIN NS_DEFINE_EVENT_STATE_MACRO(49) + +// 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) + +/** + * NOTE: do not go over 63 without updating EventStates::InternalType! + */ + +#define DIRECTION_STATES (NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL) + +#define ESM_MANAGED_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) + +#define INTRINSIC_STATES (~ESM_MANAGED_STATES) + +#endif // mozilla_EventStates_h_ diff --git a/dom/events/EventTarget.cpp b/dom/events/EventTarget.cpp new file mode 100644 index 000000000..6c4cbf2d0 --- /dev/null +++ b/dom/events/EventTarget.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/EventListenerManager.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/EventTargetBinding.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace dom { + +void +EventTarget::RemoveEventListener(const nsAString& aType, + EventListener* aListener, + const EventListenerOptionsOrBoolean& aOptions, + ErrorResult& aRv) +{ + EventListenerManager* elm = GetExistingListenerManager(); + if (elm) { + elm->RemoveEventListener(aType, aListener, aOptions); + } +} + +EventHandlerNonNull* +EventTarget::GetEventHandler(nsIAtom* aType, const nsAString& aTypeString) +{ + EventListenerManager* elm = GetExistingListenerManager(); + return elm ? elm->GetEventHandler(aType, aTypeString) : nullptr; +} + +void +EventTarget::SetEventHandler(const nsAString& aType, + EventHandlerNonNull* aHandler, + ErrorResult& aRv) +{ + if (!StringBeginsWith(aType, NS_LITERAL_STRING("on"))) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + if (NS_IsMainThread()) { + nsCOMPtr<nsIAtom> type = NS_Atomize(aType); + SetEventHandler(type, EmptyString(), aHandler); + return; + } + SetEventHandler(nullptr, + Substring(aType, 2), // Remove "on" + aHandler); +} + +void +EventTarget::SetEventHandler(nsIAtom* aType, const nsAString& aTypeString, + EventHandlerNonNull* aHandler) +{ + GetOrCreateListenerManager()->SetEventHandler(aType, aTypeString, aHandler); +} + +bool +EventTarget::IsApzAware() const +{ + EventListenerManager* elm = GetExistingListenerManager(); + return elm && elm->HasApzAwareListeners(); +} + +bool +EventTarget::DispatchEvent(JSContext* aCx, + Event& aEvent, + ErrorResult& aRv) +{ + bool result = false; + aRv = DispatchEvent(&aEvent, &result); + return !aEvent.DefaultPrevented(aCx); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/EventTarget.h b/dom/events/EventTarget.h new file mode 100644 index 000000000..3e6b6ca42 --- /dev/null +++ b/dom/events/EventTarget.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_EventTarget_h_ +#define mozilla_dom_EventTarget_h_ + +#include "nsIDOMEventTarget.h" +#include "nsWrapperCache.h" +#include "nsIAtom.h" + +class nsPIDOMWindowOuter; +class nsIGlobalObject; + +namespace mozilla { + +class AsyncEventDispatcher; +class ErrorResult; +class EventListenerManager; + +namespace dom { + +class AddEventListenerOptionsOrBoolean; +class Event; +class EventListener; +class EventListenerOptionsOrBoolean; +class EventHandlerNonNull; + +template <class T> struct Nullable; + +// IID for the dom::EventTarget interface +#define NS_EVENTTARGET_IID \ +{ 0xde651c36, 0x0053, 0x4c67, \ + { 0xb1, 0x3d, 0x67, 0xb9, 0x40, 0xfc, 0x82, 0xe4 } } + +class EventTarget : public nsIDOMEventTarget, + public nsWrapperCache +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_EVENTTARGET_IID) + + // WebIDL API + using nsIDOMEventTarget::AddEventListener; + using nsIDOMEventTarget::RemoveEventListener; + using nsIDOMEventTarget::DispatchEvent; + virtual void AddEventListener(const nsAString& aType, + EventListener* aCallback, + const AddEventListenerOptionsOrBoolean& aOptions, + const Nullable<bool>& aWantsUntrusted, + ErrorResult& aRv) = 0; + virtual void RemoveEventListener(const nsAString& aType, + EventListener* aCallback, + const EventListenerOptionsOrBoolean& aOptions, + ErrorResult& aRv); + bool DispatchEvent(JSContext* aCx, Event& aEvent, ErrorResult& aRv); + + // Note, this takes the type in onfoo form! + EventHandlerNonNull* GetEventHandler(const nsAString& aType) + { + nsCOMPtr<nsIAtom> type = NS_Atomize(aType); + return GetEventHandler(type, EmptyString()); + } + + // Note, this takes the type in onfoo form! + void SetEventHandler(const nsAString& aType, EventHandlerNonNull* aHandler, + ErrorResult& rv); + + // Note, for an event 'foo' aType will be 'onfoo'. + virtual void EventListenerAdded(nsIAtom* aType) {} + virtual void EventListenerRemoved(nsIAtom* aType) {} + + // Returns an outer window that corresponds to the inner window this event + // target is associated with. Will return null if the inner window is not the + // current inner or if there is no window around at all. + virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() = 0; + + // The global object this event target is associated with, if any. + // This may be an inner window or some other global object. This + // will never be an outer window. + virtual nsIGlobalObject* GetOwnerGlobal() const = 0; + + /** + * Get the event listener manager, creating it if it does not already exist. + */ + virtual EventListenerManager* GetOrCreateListenerManager() = 0; + + /** + * Get the event listener manager, returning null if it does not already + * exist. + */ + virtual EventListenerManager* GetExistingListenerManager() const = 0; + + // Called from AsyncEventDispatcher to notify it is running. + virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) {} + + virtual bool IsApzAware() const; + +protected: + EventHandlerNonNull* GetEventHandler(nsIAtom* aType, + const nsAString& aTypeString); + void SetEventHandler(nsIAtom* aType, const nsAString& aTypeString, + EventHandlerNonNull* aHandler); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(EventTarget, NS_EVENTTARGET_IID) + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_EventTarget_h_ diff --git a/dom/events/FocusEvent.cpp b/dom/events/FocusEvent.cpp new file mode 100644 index 000000000..d665710c5 --- /dev/null +++ b/dom/events/FocusEvent.cpp @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/FocusEvent.h" +#include "mozilla/ContentEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS_INHERITED(FocusEvent, UIEvent, nsIDOMFocusEvent) + +FocusEvent::FocusEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalFocusEvent* aEvent) + : UIEvent(aOwner, aPresContext, + aEvent ? aEvent : new InternalFocusEvent(false, eFocus)) +{ + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +NS_IMETHODIMP +FocusEvent::GetRelatedTarget(nsIDOMEventTarget** aRelatedTarget) +{ + NS_ENSURE_ARG_POINTER(aRelatedTarget); + NS_IF_ADDREF(*aRelatedTarget = GetRelatedTarget()); + return NS_OK; +} + +EventTarget* +FocusEvent::GetRelatedTarget() +{ + return mEvent->AsFocusEvent()->mRelatedTarget; +} + +void +FocusEvent::InitFocusEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + EventTarget* aRelatedTarget) +{ + MOZ_ASSERT(!mEvent->mFlags.mIsBeingDispatched); + + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); + mEvent->AsFocusEvent()->mRelatedTarget = aRelatedTarget; +} + +already_AddRefed<FocusEvent> +FocusEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const FocusEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<FocusEvent> e = new FocusEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitFocusEvent(aType, aParam.mBubbles, aParam.mCancelable, aParam.mView, + aParam.mDetail, aParam.mRelatedTarget); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<FocusEvent> +NS_NewDOMFocusEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalFocusEvent* aEvent) +{ + RefPtr<FocusEvent> it = new FocusEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/FocusEvent.h b/dom/events/FocusEvent.h new file mode 100644 index 000000000..596fa460c --- /dev/null +++ b/dom/events/FocusEvent.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_dom_FocusEvent_h_ +#define mozilla_dom_FocusEvent_h_ + +#include "mozilla/dom/FocusEventBinding.h" +#include "mozilla/dom/UIEvent.h" +#include "mozilla/EventForwards.h" +#include "nsIDOMFocusEvent.h" + +namespace mozilla { +namespace dom { + +class FocusEvent : public UIEvent, + public nsIDOMFocusEvent +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIDOMFOCUSEVENT + + // Forward to base class + NS_FORWARD_TO_UIEVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return FocusEventBinding::Wrap(aCx, this, aGivenProto); + } + + FocusEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalFocusEvent* aEvent); + + EventTarget* GetRelatedTarget(); + + static already_AddRefed<FocusEvent> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const FocusEventInit& aParam, + ErrorResult& aRv); +protected: + ~FocusEvent() {} + + void InitFocusEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + EventTarget* aRelatedTarget); +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::FocusEvent> +NS_NewDOMFocusEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalFocusEvent* aEvent); + +#endif // mozilla_dom_FocusEvent_h_ diff --git a/dom/events/IMEContentObserver.cpp b/dom/events/IMEContentObserver.cpp new file mode 100644 index 000000000..a690cfa64 --- /dev/null +++ b/dom/events/IMEContentObserver.cpp @@ -0,0 +1,1869 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "ContentEventHandler.h" +#include "IMEContentObserver.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Element.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "nsIAtom.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMRange.h" +#include "nsIEditorIMESupport.h" +#include "nsIFrame.h" +#include "nsINode.h" +#include "nsIPresShell.h" +#include "nsISelectionController.h" +#include "nsISelectionPrivate.h" +#include "nsISupports.h" +#include "nsIWidget.h" +#include "nsPresContext.h" +#include "nsWeakReference.h" +#include "WritingModes.h" + +namespace mozilla { + +using namespace widget; + +LazyLogModule sIMECOLog("IMEContentObserver"); + +static const char* +ToChar(bool aBool) +{ + return aBool ? "true" : "false"; +} + +class WritingModeToString final : public nsAutoCString +{ +public: + explicit WritingModeToString(const WritingMode& aWritingMode) + { + if (!aWritingMode.IsVertical()) { + AssignLiteral("Horizontal"); + return; + } + if (aWritingMode.IsVerticalLR()) { + AssignLiteral("Vertical (LR)"); + return; + } + AssignLiteral("Vertical (RL)"); + } + virtual ~WritingModeToString() {} +}; + +class SelectionChangeDataToString final : public nsAutoCString +{ +public: + explicit SelectionChangeDataToString( + const IMENotification::SelectionChangeDataBase& aData) + { + if (!aData.IsValid()) { + AppendLiteral("{ IsValid()=false }"); + return; + } + AppendPrintf("{ mOffset=%u, ", aData.mOffset); + if (aData.mString->Length() > 20) { + AppendPrintf("mString.Length()=%u, ", aData.mString->Length()); + } else { + AppendPrintf("mString=\"%s\" (Length()=%u), ", + NS_ConvertUTF16toUTF8(*aData.mString).get(), + aData.mString->Length()); + } + AppendPrintf("GetWritingMode()=%s, mReversed=%s, mCausedByComposition=%s, " + "mCausedBySelectionEvent=%s }", + WritingModeToString(aData.GetWritingMode()).get(), + ToChar(aData.mReversed), + ToChar(aData.mCausedByComposition), + ToChar(aData.mCausedBySelectionEvent)); + } + virtual ~SelectionChangeDataToString() {} +}; + +class TextChangeDataToString final : public nsAutoCString +{ +public: + explicit TextChangeDataToString( + const IMENotification::TextChangeDataBase& aData) + { + if (!aData.IsValid()) { + AppendLiteral("{ IsValid()=false }"); + return; + } + AppendPrintf("{ mStartOffset=%u, mRemovedEndOffset=%u, mAddedEndOffset=%u, " + "mCausedOnlyByComposition=%s, " + "mIncludingChangesDuringComposition=%s, " + "mIncludingChangesWithoutComposition=%s }", + aData.mStartOffset, aData.mRemovedEndOffset, + aData.mAddedEndOffset, + ToChar(aData.mCausedOnlyByComposition), + ToChar(aData.mIncludingChangesDuringComposition), + ToChar(aData.mIncludingChangesWithoutComposition)); + } + virtual ~TextChangeDataToString() {} +}; + +/****************************************************************************** + * mozilla::IMEContentObserver + ******************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver) + nsAutoScriptBlocker scriptBlocker; + + tmp->NotifyIMEOfBlur(); + tmp->UnregisterObservers(); + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootContent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditableNode) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditor) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode) + + tmp->mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING; + tmp->mESM = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWidget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedWidget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootContent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditor) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( + mStartOfRemovingTextRangeCache.mContainerNode) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver) + NS_INTERFACE_MAP_ENTRY(nsISelectionListener) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY(nsIReflowObserver) + NS_INTERFACE_MAP_ENTRY(nsIScrollObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIEditorObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver) + +IMEContentObserver::IMEContentObserver() + : mESM(nullptr) + , mSuppressNotifications(0) + , mPreCharacterDataChangeLength(-1) + , mSendingNotification(NOTIFY_IME_OF_NOTHING) + , mIsObserving(false) + , mIMEHasFocus(false) + , mNeedsToNotifyIMEOfFocusSet(false) + , mNeedsToNotifyIMEOfTextChange(false) + , mNeedsToNotifyIMEOfSelectionChange(false) + , mNeedsToNotifyIMEOfPositionChange(false) + , mNeedsToNotifyIMEOfCompositionEventHandled(false) + , mIsHandlingQueryContentEvent(false) +{ +#ifdef DEBUG + mTextChangeData.Test(); +#endif +} + +void +IMEContentObserver::Init(nsIWidget* aWidget, + nsPresContext* aPresContext, + nsIContent* aContent, + nsIEditor* aEditor) +{ + State state = GetState(); + if (NS_WARN_IF(state == eState_Observing)) { + return; // Nothing to do. + } + + bool firstInitialization = state != eState_StoppedObserving; + if (!firstInitialization) { + // If this is now trying to initialize with new contents, all observers + // should be registered again for simpler implementation. + UnregisterObservers(); + // Clear members which may not be initialized again. + Clear(); + } + + mESM = aPresContext->EventStateManager(); + mESM->OnStartToObserveContent(this); + + mWidget = aWidget; + + if (aWidget->GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) { + if (!InitWithPlugin(aPresContext, aContent)) { + Clear(); + return; + } + } else { + if (!InitWithEditor(aPresContext, aContent, aEditor)) { + Clear(); + return; + } + } + + if (firstInitialization) { + // Now, try to send NOTIFY_IME_OF_FOCUS to IME via the widget. + MaybeNotifyIMEOfFocusSet(); + // When this is called first time, IME has not received NOTIFY_IME_OF_FOCUS + // yet since NOTIFY_IME_OF_FOCUS will be sent to widget asynchronously. + // So, we need to do nothing here. After NOTIFY_IME_OF_FOCUS has been + // sent, OnIMEReceivedFocus() will be called and content, selection and/or + // position changes will be observed + return; + } + + // When this is called after editor reframing (i.e., the root editable node + // is also recreated), IME has usually received NOTIFY_IME_OF_FOCUS. In this + // case, we need to restart to observe content, selection and/or position + // changes in new root editable node. + ObserveEditableNode(); + + if (!NeedsToNotifyIMEOfSomething()) { + return; + } + + // Some change events may wait to notify IME because this was being + // initialized. It is the time to flush them. + FlushMergeableNotifications(); +} + +void +IMEContentObserver::OnIMEReceivedFocus() +{ + // While Init() notifies IME of focus, pending layout may be flushed + // because the notification may cause querying content. Then, recursive + // call of Init() with the latest content may occur. In such case, we + // shouldn't keep first initialization which notified IME of focus. + if (GetState() != eState_Initializing) { + return; + } + + // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver + // instance via IMEStateManager::UpdateIMEState(). So, this + // instance might already have been destroyed, check it. + if (!mRootContent) { + return; + } + + // Start to observe which is needed by IME when IME actually has focus. + ObserveEditableNode(); + + if (!NeedsToNotifyIMEOfSomething()) { + return; + } + + // Some change events may wait to notify IME because this was being + // initialized. It is the time to flush them. + FlushMergeableNotifications(); +} + +bool +IMEContentObserver::InitWithEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIEditor* aEditor) +{ + MOZ_ASSERT(aEditor); + + mEditableNode = + IMEStateManager::GetRootEditableNode(aPresContext, aContent); + if (NS_WARN_IF(!mEditableNode)) { + return false; + } + + mEditor = aEditor; + if (NS_WARN_IF(!mEditor)) { + return false; + } + + nsIPresShell* presShell = aPresContext->PresShell(); + + // get selection and root content + nsCOMPtr<nsISelectionController> selCon; + if (mEditableNode->IsNodeOfType(nsINode::eCONTENT)) { + nsIFrame* frame = + static_cast<nsIContent*>(mEditableNode.get())->GetPrimaryFrame(); + if (NS_WARN_IF(!frame)) { + return false; + } + + frame->GetSelectionController(aPresContext, + getter_AddRefs(selCon)); + } else { + // mEditableNode is a document + selCon = do_QueryInterface(presShell); + } + + if (NS_WARN_IF(!selCon)) { + return false; + } + + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(mSelection)); + if (NS_WARN_IF(!mSelection)) { + return false; + } + + nsCOMPtr<nsIDOMRange> selDomRange; + if (NS_SUCCEEDED(mSelection->GetRangeAt(0, getter_AddRefs(selDomRange)))) { + nsRange* selRange = static_cast<nsRange*>(selDomRange.get()); + if (NS_WARN_IF(!selRange) || NS_WARN_IF(!selRange->GetStartParent())) { + return false; + } + + mRootContent = selRange->GetStartParent()-> + GetSelectionRootContent(presShell); + } else { + mRootContent = mEditableNode->GetSelectionRootContent(presShell); + } + if (!mRootContent && mEditableNode->IsNodeOfType(nsINode::eDOCUMENT)) { + // The document node is editable, but there are no contents, this document + // is not editable. + return false; + } + + if (NS_WARN_IF(!mRootContent)) { + return false; + } + + mDocShell = aPresContext->GetDocShell(); + if (NS_WARN_IF(!mDocShell)) { + return false; + } + + MOZ_ASSERT(!WasInitializedWithPlugin()); + + return true; +} + +bool +IMEContentObserver::InitWithPlugin(nsPresContext* aPresContext, + nsIContent* aContent) +{ + if (NS_WARN_IF(!aContent) || + NS_WARN_IF(aContent->GetDesiredIMEState().mEnabled != IMEState::PLUGIN)) { + return false; + } + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (NS_WARN_IF(!frame)) { + return false; + } + nsCOMPtr<nsISelectionController> selCon; + frame->GetSelectionController(aPresContext, getter_AddRefs(selCon)); + if (NS_WARN_IF(!selCon)) { + return false; + } + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(mSelection)); + if (NS_WARN_IF(!mSelection)) { + return false; + } + + mEditor = nullptr; + mEditableNode = aContent; + mRootContent = aContent; + + mDocShell = aPresContext->GetDocShell(); + if (NS_WARN_IF(!mDocShell)) { + return false; + } + + MOZ_ASSERT(WasInitializedWithPlugin()); + + return true; +} + +bool +IMEContentObserver::WasInitializedWithPlugin() const +{ + return mDocShell && !mEditor; +} + +void +IMEContentObserver::Clear() +{ + mEditor = nullptr; + mSelection = nullptr; + mEditableNode = nullptr; + mRootContent = nullptr; + mDocShell = nullptr; +} + +void +IMEContentObserver::ObserveEditableNode() +{ + MOZ_RELEASE_ASSERT(mSelection); + MOZ_RELEASE_ASSERT(mRootContent); + MOZ_RELEASE_ASSERT(GetState() != eState_Observing); + + // If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when + // the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously), + // the update preference of mWidget may be different from after the widget + // receives NOTIFY_IME_OF_FOCUS. So, this should be called again by + // OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS. + if (!mIMEHasFocus) { + MOZ_ASSERT(!mWidget || mNeedsToNotifyIMEOfFocusSet || + mSendingNotification == NOTIFY_IME_OF_FOCUS, + "Wow, OnIMEReceivedFocus() won't be called?"); + return; + } + + mIsObserving = true; + if (mEditor) { + mEditor->AddEditorObserver(this); + } + + mUpdatePreference = mWidget->GetIMEUpdatePreference(); + if (!WasInitializedWithPlugin()) { + // Add selection change listener only when this starts to observe + // non-plugin content since we cannot detect selection changes in + // plugins. + nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); + NS_ENSURE_TRUE_VOID(selPrivate); + nsresult rv = selPrivate->AddSelectionListener(this); + NS_ENSURE_SUCCESS_VOID(rv); + } + + if (mUpdatePreference.WantTextChange()) { + // add text change observer + mRootContent->AddMutationObserver(this); + } + + if (mUpdatePreference.WantPositionChanged() && mDocShell) { + // Add scroll position listener and reflow observer to detect position and + // size changes + mDocShell->AddWeakScrollObserver(this); + mDocShell->AddWeakReflowObserver(this); + } +} + +void +IMEContentObserver::NotifyIMEOfBlur() +{ + // Prevent any notifications to be sent IME. + nsCOMPtr<nsIWidget> widget; + mWidget.swap(widget); + + // If we hasn't been set focus, we shouldn't send blur notification to IME. + if (!mIMEHasFocus) { + return; + } + + // mWidget must have been non-nullptr if IME has focus. + MOZ_RELEASE_ASSERT(widget); + + RefPtr<IMEContentObserver> kungFuDeathGrip(this); + + MOZ_LOG(sIMECOLog, LogLevel::Info, + ("0x%p IMEContentObserver::NotifyIMEOfBlur(), " + "sending NOTIFY_IME_OF_BLUR", this)); + + // For now, we need to send blur notification in any condition because + // we don't have any simple ways to send blur notification asynchronously. + // After this call, Destroy() or Unlink() will stop observing the content + // and forget everything. Therefore, if it's not safe to send notification + // when script blocker is unlocked, we cannot send blur notification after + // that and before next focus notification. + // Anyway, as far as we know, IME doesn't try to query content when it loses + // focus. So, this may not cause any problem. + mIMEHasFocus = false; + IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget); + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::NotifyIMEOfBlur(), " + "sent NOTIFY_IME_OF_BLUR", this)); +} + +void +IMEContentObserver::UnregisterObservers() +{ + if (!mIsObserving) { + return; + } + mIsObserving = false; + + if (mEditor) { + mEditor->RemoveEditorObserver(this); + } + + if (mSelection) { + nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); + if (selPrivate) { + selPrivate->RemoveSelectionListener(this); + } + mSelectionData.Clear(); + mFocusedWidget = nullptr; + } + + if (mUpdatePreference.WantTextChange() && mRootContent) { + mRootContent->RemoveMutationObserver(this); + } + + if (mUpdatePreference.WantPositionChanged() && mDocShell) { + mDocShell->RemoveWeakScrollObserver(this); + mDocShell->RemoveWeakReflowObserver(this); + } +} + +nsPresContext* +IMEContentObserver::GetPresContext() const +{ + return mESM ? mESM->GetPresContext() : nullptr; +} + +void +IMEContentObserver::Destroy() +{ + // WARNING: When you change this method, you have to check Unlink() too. + + NotifyIMEOfBlur(); + UnregisterObservers(); + Clear(); + + mWidget = nullptr; + mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING; + + if (mESM) { + mESM->OnStopObservingContent(this); + mESM = nullptr; + } +} + +bool +IMEContentObserver::Destroyed() const +{ + return !mWidget; +} + +void +IMEContentObserver::DisconnectFromEventStateManager() +{ + mESM = nullptr; +} + +bool +IMEContentObserver::MaybeReinitialize(nsIWidget* aWidget, + nsPresContext* aPresContext, + nsIContent* aContent, + nsIEditor* aEditor) +{ + if (!IsObservingContent(aPresContext, aContent)) { + return false; + } + + if (GetState() == eState_StoppedObserving) { + Init(aWidget, aPresContext, aContent, aEditor); + } + return IsManaging(aPresContext, aContent); +} + +bool +IMEContentObserver::IsManaging(nsPresContext* aPresContext, + nsIContent* aContent) const +{ + return GetState() == eState_Observing && + IsObservingContent(aPresContext, aContent); +} + +bool +IMEContentObserver::IsManaging(const TextComposition* aComposition) const +{ + if (GetState() != eState_Observing) { + return false; + } + nsPresContext* presContext = aComposition->GetPresContext(); + if (NS_WARN_IF(!presContext)) { + return false; + } + if (presContext != GetPresContext()) { + return false; // observing different document + } + nsINode* targetNode = aComposition->GetEventTargetNode(); + nsIContent* targetContent = + targetNode && targetNode->IsContent() ? targetNode->AsContent() : nullptr; + return IsObservingContent(presContext, targetContent); +} + +IMEContentObserver::State +IMEContentObserver::GetState() const +{ + if (!mSelection || !mRootContent || !mEditableNode) { + return eState_NotObserving; // failed to initialize or finalized. + } + if (!mRootContent->IsInComposedDoc()) { + // the focused editor has already been reframed. + return eState_StoppedObserving; + } + return mIsObserving ? eState_Observing : eState_Initializing; +} + +bool +IMEContentObserver::IsObservingContent(nsPresContext* aPresContext, + nsIContent* aContent) const +{ + return IsInitializedWithPlugin() ? + mRootContent == aContent && mRootContent != nullptr : + mEditableNode == IMEStateManager::GetRootEditableNode(aPresContext, + aContent); +} + +bool +IMEContentObserver::IsEditorHandlingEventForComposition() const +{ + if (!mWidget) { + return false; + } + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(mWidget); + if (!composition) { + return false; + } + return composition->IsEditorHandlingEvent(); +} + +bool +IMEContentObserver::IsEditorComposing() const +{ + // Note that don't use TextComposition here. The important thing is, + // whether the editor already started to handle composition because + // web contents can change selection, text content and/or something from + // compositionstart event listener which is run before EditorBase handles it. + nsCOMPtr<nsIEditorIMESupport> editorIMESupport = do_QueryInterface(mEditor); + if (NS_WARN_IF(!editorIMESupport)) { + return false; + } + bool isComposing = false; + nsresult rv = editorIMESupport->GetComposing(&isComposing); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + return isComposing; +} + +nsresult +IMEContentObserver::GetSelectionAndRoot(nsISelection** aSelection, + nsIContent** aRootContent) const +{ + if (!mEditableNode || !mSelection) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ASSERTION(mSelection && mRootContent, "uninitialized content observer"); + NS_ADDREF(*aSelection = mSelection); + NS_ADDREF(*aRootContent = mRootContent); + return NS_OK; +} + +nsresult +IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, + nsISelection* aSelection, + int16_t aReason) +{ + int32_t count = 0; + nsresult rv = aSelection->GetRangeCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + if (count > 0 && mWidget) { + bool causedByComposition = IsEditorHandlingEventForComposition(); + bool causedBySelectionEvent = TextComposition::IsHandlingSelectionEvent(); + bool duringComposition = IsEditorComposing(); + MaybeNotifyIMEOfSelectionChange(causedByComposition, + causedBySelectionEvent, + duringComposition); + } + return NS_OK; +} + +void +IMEContentObserver::ScrollPositionChanged() +{ + MaybeNotifyIMEOfPositionChange(); +} + +NS_IMETHODIMP +IMEContentObserver::Reflow(DOMHighResTimeStamp aStart, + DOMHighResTimeStamp aEnd) +{ + MaybeNotifyIMEOfPositionChange(); + return NS_OK; +} + +NS_IMETHODIMP +IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart, + DOMHighResTimeStamp aEnd) +{ + MaybeNotifyIMEOfPositionChange(); + return NS_OK; +} + +nsresult +IMEContentObserver::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent) +{ + // If the instance has normal selection cache and the query event queries + // normal selection's range, it should use the cached selection which was + // sent to the widget. However, if this instance has already received new + // selection change notification but hasn't updated the cache yet (i.e., + // not sending selection change notification to IME, don't use the cached + // value. Note that don't update selection cache here since if you update + // selection cache here, IMENotificationSender won't notify IME of selection + // change because it looks like that the selection isn't actually changed. + bool isSelectionCacheAvailable = + aEvent->mUseNativeLineBreak && mSelectionData.IsValid() && + !mNeedsToNotifyIMEOfSelectionChange; + if (isSelectionCacheAvailable && + aEvent->mMessage == eQuerySelectedText && + aEvent->mInput.mSelectionType == SelectionType::eNormal) { + aEvent->mReply.mContentsRoot = mRootContent; + aEvent->mReply.mHasSelection = !mSelectionData.IsCollapsed(); + aEvent->mReply.mOffset = mSelectionData.mOffset; + aEvent->mReply.mString = mSelectionData.String(); + aEvent->mReply.mWritingMode = mSelectionData.GetWritingMode(); + aEvent->mReply.mReversed = mSelectionData.mReversed; + aEvent->mSucceeded = true; + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ " + "mMessage=%s })", this, ToChar(aEvent->mMessage))); + return NS_OK; + } + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ " + "mMessage=%s })", this, ToChar(aEvent->mMessage))); + + // If we can make the event's input offset absolute with TextComposition or + // mSelection, we should set it here for reducing the cost of computing + // selection start offset. If ContentEventHandler receives a + // WidgetQueryContentEvent whose input offset is relative to insertion point, + // it computes current selection start offset (this may be expensive) and + // make the offset absolute value itself. + // Note that calling MakeOffsetAbsolute() makes the event a query event with + // absolute offset. So, ContentEventHandler doesn't pay any additional cost + // after calling MakeOffsetAbsolute() here. + if (aEvent->mInput.mRelativeToInsertionPoint && + aEvent->mInput.IsValidEventMessage(aEvent->mMessage)) { + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(aEvent->mWidget); + if (composition) { + uint32_t compositionStart = composition->NativeOffsetOfStartComposition(); + if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) { + return NS_ERROR_FAILURE; + } + } else if (isSelectionCacheAvailable) { + uint32_t selectionStart = mSelectionData.mOffset; + if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) { + return NS_ERROR_FAILURE; + } + } + } + + AutoRestore<bool> handling(mIsHandlingQueryContentEvent); + mIsHandlingQueryContentEvent = true; + ContentEventHandler handler(GetPresContext()); + nsresult rv = handler.HandleQueryContentEvent(aEvent); + if (NS_WARN_IF(Destroyed())) { + // If this has already destroyed during querying the content, the query + // is outdated even if it's succeeded. So, make the query fail. + aEvent->mSucceeded = false; + MOZ_LOG(sIMECOLog, LogLevel::Warning, + ("0x%p IMEContentObserver::HandleQueryContentEvent(), WARNING, " + "IMEContentObserver has been destroyed during the query, " + "making the query fail", this)); + return rv; + } + + if (!IsInitializedWithPlugin() && + NS_WARN_IF(aEvent->mReply.mContentsRoot != mRootContent)) { + // Focus has changed unexpectedly, so make the query fail. + aEvent->mSucceeded = false; + } + return rv; +} + +bool +IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext, + WidgetMouseEvent* aMouseEvent) +{ + if (!mUpdatePreference.WantMouseButtonEventOnChar()) { + return false; + } + if (!aMouseEvent->IsTrusted() || + aMouseEvent->DefaultPrevented() || + !aMouseEvent->mWidget) { + return false; + } + // Now, we need to notify only mouse down and mouse up event. + switch (aMouseEvent->mMessage) { + case eMouseUp: + case eMouseDown: + break; + default: + return false; + } + if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) { + return false; + } + + RefPtr<IMEContentObserver> kungFuDeathGrip(this); + + WidgetQueryContentEvent charAtPt(true, eQueryCharacterAtPoint, + aMouseEvent->mWidget); + charAtPt.mRefPoint = aMouseEvent->mRefPoint; + ContentEventHandler handler(aPresContext); + handler.OnQueryCharacterAtPoint(&charAtPt); + if (NS_WARN_IF(!charAtPt.mSucceeded) || + charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) { + return false; + } + + // The widget might be destroyed during querying the content since it + // causes flushing layout. + if (!mWidget || NS_WARN_IF(mWidget->Destroyed())) { + return false; + } + + // The result character rect is relative to the top level widget. + // We should notify it with offset in the widget. + nsIWidget* topLevelWidget = mWidget->GetTopLevelWidget(); + if (topLevelWidget && topLevelWidget != mWidget) { + charAtPt.mReply.mRect.MoveBy( + topLevelWidget->WidgetToScreenOffset() - + mWidget->WidgetToScreenOffset()); + } + // The refPt is relative to its widget. + // We should notify it with offset in the widget. + if (aMouseEvent->mWidget != mWidget) { + charAtPt.mRefPoint += aMouseEvent->mWidget->WidgetToScreenOffset() - + mWidget->WidgetToScreenOffset(); + } + + IMENotification notification(NOTIFY_IME_OF_MOUSE_BUTTON_EVENT); + notification.mMouseButtonEventData.mEventMessage = aMouseEvent->mMessage; + notification.mMouseButtonEventData.mOffset = charAtPt.mReply.mOffset; + notification.mMouseButtonEventData.mCursorPos.Set( + charAtPt.mRefPoint.ToUnknownPoint()); + notification.mMouseButtonEventData.mCharRect.Set( + charAtPt.mReply.mRect.ToUnknownRect()); + notification.mMouseButtonEventData.mButton = aMouseEvent->button; + notification.mMouseButtonEventData.mButtons = aMouseEvent->buttons; + notification.mMouseButtonEventData.mModifiers = aMouseEvent->mModifiers; + + nsresult rv = IMEStateManager::NotifyIME(notification, mWidget); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED); + if (consumed) { + aMouseEvent->PreventDefault(); + } + return consumed; +} + +void +IMEContentObserver::CharacterDataWillChange(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "character data changed for non-text node"); + MOZ_ASSERT(mPreCharacterDataChangeLength < 0, + "CharacterDataChanged() should've reset " + "mPreCharacterDataChangeLength"); + + mEndOfAddedTextCache.Clear(); + mStartOfRemovingTextRangeCache.Clear(); + mPreCharacterDataChangeLength = + ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart, + aInfo->mChangeEnd); + MOZ_ASSERT(mPreCharacterDataChangeLength >= + aInfo->mChangeEnd - aInfo->mChangeStart, + "The computed length must be same as or larger than XP length"); +} + +void +IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), + "character data changed for non-text node"); + + mEndOfAddedTextCache.Clear(); + mStartOfRemovingTextRangeCache.Clear(); + + int64_t removedLength = mPreCharacterDataChangeLength; + mPreCharacterDataChangeLength = -1; + + MOZ_ASSERT(removedLength >= 0, + "mPreCharacterDataChangeLength should've been set by " + "CharacterDataWillChange()"); + + uint32_t offset = 0; + // get offsets of change and fire notification + nsresult rv = + ContentEventHandler::GetFlatTextLengthInRange( + NodePosition(mRootContent, 0), + NodePosition(aContent, aInfo->mChangeStart), + mRootContent, &offset, LINE_BREAK_TYPE_NATIVE); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + uint32_t newLength = + ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart, + aInfo->mChangeStart + + aInfo->mReplaceLength); + + uint32_t oldEnd = offset + static_cast<uint32_t>(removedLength); + uint32_t newEnd = offset + newLength; + + TextChangeData data(offset, oldEnd, newEnd, + IsEditorHandlingEventForComposition(), + IsEditorComposing()); + MaybeNotifyIMEOfTextChange(data); +} + +void +IMEContentObserver::NotifyContentAdded(nsINode* aContainer, + int32_t aStartIndex, + int32_t aEndIndex) +{ + mStartOfRemovingTextRangeCache.Clear(); + + uint32_t offset = 0; + nsresult rv = NS_OK; + if (!mEndOfAddedTextCache.Match(aContainer, aStartIndex)) { + mEndOfAddedTextCache.Clear(); + rv = ContentEventHandler::GetFlatTextLengthInRange( + NodePosition(mRootContent, 0), + NodePositionBefore(aContainer, aStartIndex), + mRootContent, &offset, LINE_BREAK_TYPE_NATIVE); + if (NS_WARN_IF(NS_FAILED((rv)))) { + return; + } + } else { + offset = mEndOfAddedTextCache.mFlatTextLength; + } + + // get offset at the end of the last added node + uint32_t addingLength = 0; + rv = ContentEventHandler::GetFlatTextLengthInRange( + NodePositionBefore(aContainer, aStartIndex), + NodePosition(aContainer, aEndIndex), + mRootContent, &addingLength, + LINE_BREAK_TYPE_NATIVE); + if (NS_WARN_IF(NS_FAILED((rv)))) { + mEndOfAddedTextCache.Clear(); + return; + } + + // If multiple lines are being inserted in an HTML editor, next call of + // NotifyContentAdded() is for adding next node. Therefore, caching the text + // length can skip to compute the text length before the adding node and + // before of it. + mEndOfAddedTextCache.Cache(aContainer, aEndIndex, offset + addingLength); + + if (!addingLength) { + return; + } + + TextChangeData data(offset, offset, offset + addingLength, + IsEditorHandlingEventForComposition(), + IsEditorComposing()); + MaybeNotifyIMEOfTextChange(data); +} + +void +IMEContentObserver::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t aNewIndexInContainer) +{ + NotifyContentAdded(aContainer, aNewIndexInContainer, + aContainer->GetChildCount()); +} + +void +IMEContentObserver::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer) +{ + NotifyContentAdded(NODE_FROM(aContainer, aDocument), + aIndexInContainer, aIndexInContainer + 1); +} + +void +IMEContentObserver::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + mEndOfAddedTextCache.Clear(); + + nsINode* containerNode = NODE_FROM(aContainer, aDocument); + + uint32_t offset = 0; + nsresult rv = NS_OK; + if (!mStartOfRemovingTextRangeCache.Match(containerNode, aIndexInContainer)) { + // At removing a child node of aContainer, we need the line break caused + // by open tag of aContainer. Be careful when aIndexInContainer is 0. + rv = ContentEventHandler::GetFlatTextLengthInRange( + NodePosition(mRootContent, 0), + NodePosition(containerNode, aIndexInContainer), + mRootContent, &offset, LINE_BREAK_TYPE_NATIVE); + if (NS_WARN_IF(NS_FAILED(rv))) { + mStartOfRemovingTextRangeCache.Clear(); + return; + } + mStartOfRemovingTextRangeCache.Cache(containerNode, aIndexInContainer, + offset); + } else { + offset = mStartOfRemovingTextRangeCache.mFlatTextLength; + } + + // get offset at the end of the deleted node + uint32_t textLength = 0; + if (aChild->IsNodeOfType(nsINode::eTEXT)) { + textLength = ContentEventHandler::GetNativeTextLength(aChild); + } else { + uint32_t nodeLength = static_cast<int32_t>(aChild->GetChildCount()); + rv = ContentEventHandler::GetFlatTextLengthInRange( + NodePositionBefore(aChild, 0), + NodePosition(aChild, nodeLength), + mRootContent, &textLength, + LINE_BREAK_TYPE_NATIVE, true); + if (NS_WARN_IF(NS_FAILED(rv))) { + mStartOfRemovingTextRangeCache.Clear(); + return; + } + } + + if (!textLength) { + return; + } + + TextChangeData data(offset, offset + textLength, offset, + IsEditorHandlingEventForComposition(), + IsEditorComposing()); + MaybeNotifyIMEOfTextChange(data); +} + +void +IMEContentObserver::AttributeWillChange(nsIDocument* aDocument, + dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aNewValue) +{ + mPreAttrChangeLength = + ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent); +} + +void +IMEContentObserver::AttributeChanged(nsIDocument* aDocument, + dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + mEndOfAddedTextCache.Clear(); + mStartOfRemovingTextRangeCache.Clear(); + + uint32_t postAttrChangeLength = + ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent); + if (postAttrChangeLength == mPreAttrChangeLength) { + return; + } + uint32_t start; + nsresult rv = + ContentEventHandler::GetFlatTextLengthInRange( + NodePosition(mRootContent, 0), + NodePositionBefore(aElement, 0), + mRootContent, &start, LINE_BREAK_TYPE_NATIVE); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + TextChangeData data(start, start + mPreAttrChangeLength, + start + postAttrChangeLength, + IsEditorHandlingEventForComposition(), + IsEditorComposing()); + MaybeNotifyIMEOfTextChange(data); +} + +void +IMEContentObserver::SuppressNotifyingIME() +{ + mSuppressNotifications++; + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::SuppressNotifyingIME(), " + "mSuppressNotifications=%u", this, mSuppressNotifications)); +} + +void +IMEContentObserver::UnsuppressNotifyingIME() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::UnsuppressNotifyingIME(), " + "mSuppressNotifications=%u", this, mSuppressNotifications)); + + if (!mSuppressNotifications || --mSuppressNotifications) { + return; + } + FlushMergeableNotifications(); +} + +NS_IMETHODIMP +IMEContentObserver::EditAction() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::EditAction()", this)); + + mEndOfAddedTextCache.Clear(); + mStartOfRemovingTextRangeCache.Clear(); + FlushMergeableNotifications(); + return NS_OK; +} + +NS_IMETHODIMP +IMEContentObserver::BeforeEditAction() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::BeforeEditAction()", this)); + + mEndOfAddedTextCache.Clear(); + mStartOfRemovingTextRangeCache.Clear(); + return NS_OK; +} + +NS_IMETHODIMP +IMEContentObserver::CancelEditAction() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::CancelEditAction()", this)); + + mEndOfAddedTextCache.Clear(); + mStartOfRemovingTextRangeCache.Clear(); + FlushMergeableNotifications(); + return NS_OK; +} + +void +IMEContentObserver::PostFocusSetNotification() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::PostFocusSetNotification()", this)); + + mNeedsToNotifyIMEOfFocusSet = true; +} + +void +IMEContentObserver::PostTextChangeNotification() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::PostTextChangeNotification(" + "mTextChangeData=%s)", + this, TextChangeDataToString(mTextChangeData).get())); + + MOZ_ASSERT(mTextChangeData.IsValid(), + "mTextChangeData must have text change data"); + mNeedsToNotifyIMEOfTextChange = true; +} + +void +IMEContentObserver::PostSelectionChangeNotification() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::PostSelectionChangeNotification(), " + "mSelectionData={ mCausedByComposition=%s, mCausedBySelectionEvent=%s }", + this, ToChar(mSelectionData.mCausedByComposition), + ToChar(mSelectionData.mCausedBySelectionEvent))); + + mNeedsToNotifyIMEOfSelectionChange = true; +} + +void +IMEContentObserver::MaybeNotifyIMEOfFocusSet() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::MaybeNotifyIMEOfFocusSet()", this)); + + PostFocusSetNotification(); + FlushMergeableNotifications(); +} + +void +IMEContentObserver::MaybeNotifyIMEOfTextChange( + const TextChangeDataBase& aTextChangeData) +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::MaybeNotifyIMEOfTextChange(" + "aTextChangeData=%s)", + this, TextChangeDataToString(aTextChangeData).get())); + + mTextChangeData += aTextChangeData; + PostTextChangeNotification(); + FlushMergeableNotifications(); +} + +void +IMEContentObserver::MaybeNotifyIMEOfSelectionChange( + bool aCausedByComposition, + bool aCausedBySelectionEvent, + bool aOccurredDuringComposition) +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::MaybeNotifyIMEOfSelectionChange(" + "aCausedByComposition=%s, aCausedBySelectionEvent=%s, " + "aOccurredDuringComposition)", + this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent))); + + mSelectionData.AssignReason(aCausedByComposition, + aCausedBySelectionEvent, + aOccurredDuringComposition); + PostSelectionChangeNotification(); + FlushMergeableNotifications(); +} + +void +IMEContentObserver::MaybeNotifyIMEOfPositionChange() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::MaybeNotifyIMEOfPositionChange()", this)); + // If reflow is caused by ContentEventHandler during PositionChangeEvent + // sending NOTIFY_IME_OF_POSITION_CHANGE, we don't need to notify IME of it + // again since ContentEventHandler returns the result including this reflow's + // result. + if (mIsHandlingQueryContentEvent && + mSendingNotification == NOTIFY_IME_OF_POSITION_CHANGE) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::MaybeNotifyIMEOfPositionChange(), " + "ignored since caused by ContentEventHandler during sending " + "NOTIY_IME_OF_POSITION_CHANGE", this)); + return; + } + PostPositionChangeNotification(); + FlushMergeableNotifications(); +} + +void +IMEContentObserver::MaybeNotifyCompositionEventHandled() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::MaybeNotifyCompositionEventHandled()", + this)); + + PostCompositionEventHandledNotification(); + FlushMergeableNotifications(); +} + +bool +IMEContentObserver::UpdateSelectionCache() +{ + MOZ_ASSERT(IsSafeToNotifyIME()); + + if (WasInitializedWithPlugin()) { + return false; + } + + mSelectionData.ClearSelectionData(); + + // XXX Cannot we cache some information for reducing the cost to compute + // selection offset and writing mode? + WidgetQueryContentEvent selection(true, eQuerySelectedText, mWidget); + ContentEventHandler handler(GetPresContext()); + handler.OnQuerySelectedText(&selection); + if (NS_WARN_IF(!selection.mSucceeded) || + NS_WARN_IF(selection.mReply.mContentsRoot != mRootContent)) { + return false; + } + + mFocusedWidget = selection.mReply.mFocusedWidget; + mSelectionData.mOffset = selection.mReply.mOffset; + *mSelectionData.mString = selection.mReply.mString; + mSelectionData.SetWritingMode(selection.GetWritingMode()); + mSelectionData.mReversed = selection.mReply.mReversed; + + // WARNING: Don't modify the reason of selection change here. + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::UpdateSelectionCache(), " + "mSelectionData=%s", + this, SelectionChangeDataToString(mSelectionData).get())); + + return mSelectionData.IsValid(); +} + +void +IMEContentObserver::PostPositionChangeNotification() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::PostPositionChangeNotification()", this)); + + mNeedsToNotifyIMEOfPositionChange = true; +} + +void +IMEContentObserver::PostCompositionEventHandledNotification() +{ + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::" + "PostCompositionEventHandledNotification()", this)); + + mNeedsToNotifyIMEOfCompositionEventHandled = true; +} + +bool +IMEContentObserver::IsReflowLocked() const +{ + nsPresContext* presContext = GetPresContext(); + if (NS_WARN_IF(!presContext)) { + return false; + } + nsIPresShell* presShell = presContext->GetPresShell(); + if (NS_WARN_IF(!presShell)) { + return false; + } + // During reflow, we shouldn't notify IME because IME may query content + // synchronously. Then, it causes ContentEventHandler will try to flush + // pending notifications during reflow. + return presShell->IsReflowLocked(); +} + +bool +IMEContentObserver::IsSafeToNotifyIME() const +{ + // If this is already detached from the widget, this doesn't need to notify + // anything. + if (!mWidget) { + return false; + } + + // Don't notify IME of anything if it's not good time to do it. + if (mSuppressNotifications) { + return false; + } + + if (!mESM || NS_WARN_IF(!GetPresContext())) { + return false; + } + + // If it's in reflow, we should wait to finish the reflow. + // FYI: This should be called again from Reflow() or ReflowInterruptible(). + if (IsReflowLocked()) { + return false; + } + + // If we're in handling an edit action, this method will be called later. + bool isInEditAction = false; + if (mEditor && NS_SUCCEEDED(mEditor->GetIsInEditAction(&isInEditAction)) && + isInEditAction) { + return false; + } + + return true; +} + +void +IMEContentObserver::FlushMergeableNotifications() +{ + if (!IsSafeToNotifyIME()) { + // So, if this is already called, this should do nothing. + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::FlushMergeableNotifications(), " + "FAILED, due to unsafe to notify IME", this)); + return; + } + + // Notifying something may cause nested call of this method. For example, + // when somebody notified one of the notifications may dispatch query content + // event. Then, it causes flushing layout which may cause another layout + // change notification. + + if (mQueuedSender) { + // So, if this is already called, this should do nothing. + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::FlushMergeableNotifications(), " + "FAILED, due to already flushing pending notifications", this)); + return; + } + + if (!NeedsToNotifyIMEOfSomething()) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::FlushMergeableNotifications(), " + "FAILED, due to no pending notifications", this)); + return; + } + + // NOTE: Reset each pending flag because sending notification may cause + // another change. + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::FlushMergeableNotifications(), " + "creating IMENotificationSender...", this)); + + // If contents in selection range is modified, the selection range still + // has removed node from the tree. In such case, nsContentIterator won't + // work well. Therefore, we shouldn't use AddScriptRunnder() here since + // it may kick runnable event immediately after DOM tree is changed but + // the selection range isn't modified yet. + mQueuedSender = new IMENotificationSender(this); + NS_DispatchToCurrentThread(mQueuedSender); + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::FlushMergeableNotifications(), " + "finished", this)); +} + +void +IMEContentObserver::TryToFlushPendingNotifications() +{ + if (!mQueuedSender || mSendingNotification != NOTIFY_IME_OF_NOTHING) { + return; + } + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::TryToFlushPendingNotifications(), " + "performing queued IMENotificationSender forcibly", this)); + RefPtr<IMENotificationSender> queuedSender = mQueuedSender; + queuedSender->Run(); +} + +/****************************************************************************** + * mozilla::IMEContentObserver::AChangeEvent + ******************************************************************************/ + +bool +IMEContentObserver::AChangeEvent::CanNotifyIME( + ChangeEventType aChangeEventType) const +{ + if (NS_WARN_IF(!mIMEContentObserver)) { + return false; + } + if (aChangeEventType == eChangeEventType_CompositionEventHandled) { + return mIMEContentObserver->mWidget != nullptr; + } + State state = mIMEContentObserver->GetState(); + // If it's not initialized, we should do nothing. + if (state == eState_NotObserving) { + return false; + } + // If setting focus, just check the state. + if (aChangeEventType == eChangeEventType_Focus) { + return !NS_WARN_IF(mIMEContentObserver->mIMEHasFocus); + } + // If we've not notified IME of focus yet, we shouldn't notify anything. + if (!mIMEContentObserver->mIMEHasFocus) { + return false; + } + + // If IME has focus, IMEContentObserver must hold the widget. + MOZ_ASSERT(mIMEContentObserver->mWidget); + + return true; +} + +bool +IMEContentObserver::AChangeEvent::IsSafeToNotifyIME( + ChangeEventType aChangeEventType) const +{ + if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) { + return false; + } + // While we're sending a notification, we shouldn't send another notification + // recursively. + if (mIMEContentObserver->mSendingNotification != NOTIFY_IME_OF_NOTHING) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::AChangeEvent::IsSafeToNotifyIME(), " + "putting off sending notification due to detecting recursive call, " + "mIMEContentObserver={ mSendingNotification=%s }", + this, ToChar(mIMEContentObserver->mSendingNotification))); + return false; + } + State state = mIMEContentObserver->GetState(); + if (aChangeEventType == eChangeEventType_Focus) { + if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) { + return false; + } + } else if (aChangeEventType == eChangeEventType_CompositionEventHandled) { + // It doesn't need to check the observing status. + } else if (state != eState_Observing) { + return false; + } + return mIMEContentObserver->IsSafeToNotifyIME(); +} + +/****************************************************************************** + * mozilla::IMEContentObserver::IMENotificationSender + ******************************************************************************/ + +NS_IMETHODIMP +IMEContentObserver::IMENotificationSender::Run() +{ + if (NS_WARN_IF(mIsRunning)) { + MOZ_LOG(sIMECOLog, LogLevel::Error, + ("0x%p IMEContentObserver::IMENotificationSender::Run(), FAILED, " + "called recursively", this)); + return NS_OK; + } + + AutoRestore<bool> running(mIsRunning); + mIsRunning = true; + + // This instance was already performed forcibly. + if (mIMEContentObserver->mQueuedSender != this) { + return NS_OK; + } + + // NOTE: Reset each pending flag because sending notification may cause + // another change. + + if (mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet) { + mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet = false; + SendFocusSet(); + mIMEContentObserver->mQueuedSender = nullptr; + // If it's not safe to notify IME of focus, SendFocusSet() sets + // mNeedsToNotifyIMEOfFocusSet true again. For guaranteeing to send the + // focus notification later, we should put a new sender into the queue but + // this case must be rare. Note that if mIMEContentObserver is already + // destroyed, mNeedsToNotifyIMEOfFocusSet is never set true again. + if (mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet) { + MOZ_ASSERT(!mIMEContentObserver->mIMEHasFocus); + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::Run(), " + "posting IMENotificationSender to current thread", this)); + mIMEContentObserver->mQueuedSender = + new IMENotificationSender(mIMEContentObserver); + NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender); + return NS_OK; + } + // This is the first notification to IME. So, we don't need to notify + // anymore since IME starts to query content after it gets focus. + mIMEContentObserver->ClearPendingNotifications(); + return NS_OK; + } + + if (mIMEContentObserver->mNeedsToNotifyIMEOfTextChange) { + mIMEContentObserver->mNeedsToNotifyIMEOfTextChange = false; + SendTextChange(); + } + + // If a text change notification causes another text change again, we should + // notify IME of that before sending a selection change notification. + if (!mIMEContentObserver->mNeedsToNotifyIMEOfTextChange) { + // Be aware, PuppetWidget depends on the order of this. A selection change + // notification should not be sent before a text change notification because + // PuppetWidget shouldn't query new text content every selection change. + if (mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange) { + mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange = false; + SendSelectionChange(); + } + } + + // If a text change notification causes another text change again or a + // selection change notification causes either a text change or another + // selection change, we should notify IME of those before sending a position + // change notification. + if (!mIMEContentObserver->mNeedsToNotifyIMEOfTextChange && + !mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange) { + if (mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange) { + mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange = false; + SendPositionChange(); + } + } + + // Composition event handled notification should be sent after all the + // other notifications because this notifies widget of finishing all pending + // events are handled completely. + if (!mIMEContentObserver->mNeedsToNotifyIMEOfTextChange && + !mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange && + !mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange) { + if (mIMEContentObserver->mNeedsToNotifyIMEOfCompositionEventHandled) { + mIMEContentObserver->mNeedsToNotifyIMEOfCompositionEventHandled = false; + SendCompositionEventHandled(); + } + } + + mIMEContentObserver->mQueuedSender = nullptr; + + // If notifications caused some new change, we should notify them now. + if (mIMEContentObserver->NeedsToNotifyIMEOfSomething()) { + if (mIMEContentObserver->GetState() == eState_StoppedObserving) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::Run(), " + "waiting IMENotificationSender to be reinitialized", this)); + } else { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::Run(), " + "posting IMENotificationSender to current thread", this)); + mIMEContentObserver->mQueuedSender = + new IMENotificationSender(mIMEContentObserver); + NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender); + } + } + return NS_OK; +} + +void +IMEContentObserver::IMENotificationSender::SendFocusSet() +{ + if (!CanNotifyIME(eChangeEventType_Focus)) { + // If IMEContentObserver has already gone, we don't need to notify IME of + // focus. + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendFocusSet(), FAILED, due to impossible to notify IME of focus", + this)); + mIMEContentObserver->ClearPendingNotifications(); + return; + } + + if (!IsSafeToNotifyIME(eChangeEventType_Focus)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendFocusSet(), retrying to send NOTIFY_IME_OF_FOCUS...", this)); + mIMEContentObserver->PostFocusSetNotification(); + return; + } + + mIMEContentObserver->mIMEHasFocus = true; + // Initialize selection cache with the first selection data. + mIMEContentObserver->UpdateSelectionCache(); + + MOZ_LOG(sIMECOLog, LogLevel::Info, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendFocusSet(), sending NOTIFY_IME_OF_FOCUS...", this)); + + MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification == + NOTIFY_IME_OF_NOTHING); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_FOCUS; + IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS), + mIMEContentObserver->mWidget); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING; + + // nsIMEUpdatePreference referred by ObserveEditableNode() may be different + // before or after widget receives NOTIFY_IME_OF_FOCUS. Therefore, we need + // to guarantee to call ObserveEditableNode() after sending + // NOTIFY_IME_OF_FOCUS. + mIMEContentObserver->OnIMEReceivedFocus(); + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendFocusSet(), sent NOTIFY_IME_OF_FOCUS", this)); +} + +void +IMEContentObserver::IMENotificationSender::SendSelectionChange() +{ + if (!CanNotifyIME(eChangeEventType_Selection)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendSelectionChange(), FAILED, due to impossible to notify IME of " + "selection change", this)); + return; + } + + if (!IsSafeToNotifyIME(eChangeEventType_Selection)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendSelectionChange(), retrying to send " + "NOTIFY_IME_OF_SELECTION_CHANGE...", this)); + mIMEContentObserver->PostSelectionChangeNotification(); + return; + } + + SelectionChangeData lastSelChangeData = mIMEContentObserver->mSelectionData; + if (NS_WARN_IF(!mIMEContentObserver->UpdateSelectionCache())) { + MOZ_LOG(sIMECOLog, LogLevel::Error, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendSelectionChange(), FAILED, due to UpdateSelectionCache() failure", + this)); + return; + } + + // The state may be changed since querying content causes flushing layout. + if (!CanNotifyIME(eChangeEventType_Selection)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendSelectionChange(), FAILED, due to flushing layout having changed " + "something", this)); + return; + } + + // If the selection isn't changed actually, we shouldn't notify IME of + // selection change. + SelectionChangeData& newSelChangeData = mIMEContentObserver->mSelectionData; + if (lastSelChangeData.IsValid() && + lastSelChangeData.mOffset == newSelChangeData.mOffset && + lastSelChangeData.String() == newSelChangeData.String() && + lastSelChangeData.GetWritingMode() == newSelChangeData.GetWritingMode() && + lastSelChangeData.mReversed == newSelChangeData.mReversed) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendSelectionChange(), not notifying IME of " + "NOTIFY_IME_OF_SELECTION_CHANGE due to not changed actually", this)); + return; + } + + MOZ_LOG(sIMECOLog, LogLevel::Info, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendSelectionChange(), sending NOTIFY_IME_OF_SELECTION_CHANGE... " + "newSelChangeData=%s", + this, SelectionChangeDataToString(newSelChangeData).get())); + + IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE); + notification.SetData(mIMEContentObserver->mSelectionData); + + MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification == + NOTIFY_IME_OF_NOTHING); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_SELECTION_CHANGE; + IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING; + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendSelectionChange(), sent NOTIFY_IME_OF_SELECTION_CHANGE", this)); +} + +void +IMEContentObserver::IMENotificationSender::SendTextChange() +{ + if (!CanNotifyIME(eChangeEventType_Text)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendTextChange(), FAILED, due to impossible to notify IME of text " + "change", this)); + return; + } + + if (!IsSafeToNotifyIME(eChangeEventType_Text)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendTextChange(), retrying to send NOTIFY_IME_OF_TEXT_CHANGE...", + this)); + mIMEContentObserver->PostTextChangeNotification(); + return; + } + + MOZ_LOG(sIMECOLog, LogLevel::Info, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendTextChange(), sending NOTIFY_IME_OF_TEXT_CHANGE... " + "mIMEContentObserver={ mTextChangeData=%s }", + this, TextChangeDataToString(mIMEContentObserver->mTextChangeData).get())); + + IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE); + notification.SetData(mIMEContentObserver->mTextChangeData); + mIMEContentObserver->mTextChangeData.Clear(); + + MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification == + NOTIFY_IME_OF_NOTHING); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_TEXT_CHANGE; + IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING; + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendTextChange(), sent NOTIFY_IME_OF_TEXT_CHANGE", this)); +} + +void +IMEContentObserver::IMENotificationSender::SendPositionChange() +{ + if (!CanNotifyIME(eChangeEventType_Position)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendPositionChange(), FAILED, due to impossible to notify IME of " + "position change", this)); + return; + } + + if (!IsSafeToNotifyIME(eChangeEventType_Position)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendPositionChange(), retrying to send " + "NOTIFY_IME_OF_POSITION_CHANGE...", this)); + mIMEContentObserver->PostPositionChangeNotification(); + return; + } + + MOZ_LOG(sIMECOLog, LogLevel::Info, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendPositionChange(), sending NOTIFY_IME_OF_POSITION_CHANGE...", this)); + + MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification == + NOTIFY_IME_OF_NOTHING); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_POSITION_CHANGE; + IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE), + mIMEContentObserver->mWidget); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING; + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendPositionChange(), sent NOTIFY_IME_OF_POSITION_CHANGE", this)); +} + +void +IMEContentObserver::IMENotificationSender::SendCompositionEventHandled() +{ + if (!CanNotifyIME(eChangeEventType_CompositionEventHandled)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendCompositionEventHandled(), FAILED, due to impossible to notify " + "IME of composition event handled", this)); + return; + } + + if (!IsSafeToNotifyIME(eChangeEventType_CompositionEventHandled)) { + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendCompositionEventHandled(), retrying to send " + "NOTIFY_IME_OF_POSITION_CHANGE...", this)); + mIMEContentObserver->PostCompositionEventHandledNotification(); + return; + } + + MOZ_LOG(sIMECOLog, LogLevel::Info, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendCompositionEventHandled(), sending " + "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED...", this)); + + MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification == + NOTIFY_IME_OF_NOTHING); + mIMEContentObserver->mSendingNotification = + NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED; + IMEStateManager::NotifyIME( + IMENotification(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED), + mIMEContentObserver->mWidget); + mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING; + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("0x%p IMEContentObserver::IMENotificationSender::" + "SendCompositionEventHandled(), sent " + "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED", this)); +} + +} // namespace mozilla diff --git a/dom/events/IMEContentObserver.h b/dom/events/IMEContentObserver.h new file mode 100644 index 000000000..d2f65e186 --- /dev/null +++ b/dom/events/IMEContentObserver.h @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_IMEContentObserver_h_ +#define mozilla_IMEContentObserver_h_ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDocShell.h" // XXX Why does only this need to be included here? +#include "nsIEditor.h" +#include "nsIEditorObserver.h" +#include "nsIReflowObserver.h" +#include "nsISelectionListener.h" +#include "nsIScrollObserver.h" +#include "nsIWidget.h" // for nsIMEUpdatePreference +#include "nsStubMutationObserver.h" +#include "nsThreadUtils.h" +#include "nsWeakReference.h" + +class nsIContent; +class nsINode; +class nsISelection; +class nsPresContext; + +namespace mozilla { + +class EventStateManager; +class TextComposition; + +// IMEContentObserver notifies widget of any text and selection changes +// in the currently focused editor +class IMEContentObserver final : public nsISelectionListener + , public nsStubMutationObserver + , public nsIReflowObserver + , public nsIScrollObserver + , public nsSupportsWeakReference + , public nsIEditorObserver +{ +public: + typedef ContentEventHandler::NodePosition NodePosition; + typedef ContentEventHandler::NodePositionBefore NodePositionBefore; + typedef widget::IMENotification::SelectionChangeData SelectionChangeData; + typedef widget::IMENotification::TextChangeData TextChangeData; + typedef widget::IMENotification::TextChangeDataBase TextChangeDataBase; + typedef widget::IMEMessage IMEMessage; + + IMEContentObserver(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver, + nsISelectionListener) + NS_DECL_NSIEDITOROBSERVER + NS_DECL_NSISELECTIONLISTENER + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIREFLOWOBSERVER + + // nsIScrollObserver + virtual void ScrollPositionChanged() override; + + bool OnMouseButtonEvent(nsPresContext* aPresContext, + WidgetMouseEvent* aMouseEvent); + + nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); + + void Init(nsIWidget* aWidget, nsPresContext* aPresContext, + nsIContent* aContent, nsIEditor* aEditor); + void Destroy(); + bool Destroyed() const; + + /** + * IMEContentObserver is stored by EventStateManager during observing. + * DisconnectFromEventStateManager() is called when EventStateManager stops + * storing the instance. + */ + void DisconnectFromEventStateManager(); + /** + * MaybeReinitialize() tries to restart to observe the editor's root node. + * This is useful when the editor is reframed and all children are replaced + * with new node instances. + * @return Returns true if the instance is managing the content. + * Otherwise, false. + */ + bool MaybeReinitialize(nsIWidget* aWidget, + nsPresContext* aPresContext, + nsIContent* aContent, + nsIEditor* aEditor); + bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent) const; + bool IsManaging(const TextComposition* aTextComposition) const; + bool WasInitializedWithPlugin() const; + bool IsEditorHandlingEventForComposition() const; + bool KeepAliveDuringDeactive() const + { + return mUpdatePreference.WantDuringDeactive(); + } + nsIWidget* GetWidget() const { return mWidget; } + nsIEditor* GetEditor() const { return mEditor; } + void SuppressNotifyingIME(); + void UnsuppressNotifyingIME(); + nsPresContext* GetPresContext() const; + nsresult GetSelectionAndRoot(nsISelection** aSelection, + nsIContent** aRoot) const; + + /** + * TryToFlushPendingNotifications() should be called when pending events + * should be flushed. This tries to run the queued IMENotificationSender. + */ + void TryToFlushPendingNotifications(); + + /** + * MaybeNotifyCompositionEventHandled() posts composition event handled + * notification into the pseudo queue. + */ + void MaybeNotifyCompositionEventHandled(); + +private: + ~IMEContentObserver() {} + + enum State { + eState_NotObserving, + eState_Initializing, + eState_StoppedObserving, + eState_Observing + }; + State GetState() const; + bool InitWithEditor(nsPresContext* aPresContext, nsIContent* aContent, + nsIEditor* aEditor); + bool InitWithPlugin(nsPresContext* aPresContext, nsIContent* aContent); + bool IsInitializedWithPlugin() const { return !mEditor; } + void OnIMEReceivedFocus(); + void Clear(); + bool IsObservingContent(nsPresContext* aPresContext, + nsIContent* aContent) const; + bool IsReflowLocked() const; + bool IsSafeToNotifyIME() const; + bool IsEditorComposing() const; + + void PostFocusSetNotification(); + void MaybeNotifyIMEOfFocusSet(); + void PostTextChangeNotification(); + void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData); + void PostSelectionChangeNotification(); + void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition, + bool aCausedBySelectionEvent, + bool aOccurredDuringComposition); + void PostPositionChangeNotification(); + void MaybeNotifyIMEOfPositionChange(); + void PostCompositionEventHandledNotification(); + + void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd); + void ObserveEditableNode(); + /** + * NotifyIMEOfBlur() notifies IME of blur. + */ + void NotifyIMEOfBlur(); + /** + * UnregisterObservers() unregisters all listeners and observers. + */ + void UnregisterObservers(); + void FlushMergeableNotifications(); + void ClearPendingNotifications() + { + mNeedsToNotifyIMEOfFocusSet = false; + mNeedsToNotifyIMEOfTextChange = false; + mNeedsToNotifyIMEOfSelectionChange = false; + mNeedsToNotifyIMEOfPositionChange = false; + mNeedsToNotifyIMEOfCompositionEventHandled = false; + mTextChangeData.Clear(); + } + bool NeedsToNotifyIMEOfSomething() const + { + return mNeedsToNotifyIMEOfFocusSet || + mNeedsToNotifyIMEOfTextChange || + mNeedsToNotifyIMEOfSelectionChange || + mNeedsToNotifyIMEOfPositionChange || + mNeedsToNotifyIMEOfCompositionEventHandled; + } + + /** + * UpdateSelectionCache() updates mSelectionData with the latest selection. + * This should be called only when IsSafeToNotifyIME() returns true. + * + * Note that this does nothing if WasInitializedWithPlugin() returns true. + */ + bool UpdateSelectionCache(); + + nsCOMPtr<nsIWidget> mWidget; + // mFocusedWidget has the editor observed by the instance. E.g., if the + // focused editor is in XUL panel, this should be the widget of the panel. + // On the other hand, mWidget is its parent which handles IME. + nsCOMPtr<nsIWidget> mFocusedWidget; + nsCOMPtr<nsISelection> mSelection; + nsCOMPtr<nsIContent> mRootContent; + nsCOMPtr<nsINode> mEditableNode; + nsCOMPtr<nsIDocShell> mDocShell; + nsCOMPtr<nsIEditor> mEditor; + + /** + * Helper classes to notify IME. + */ + + class AChangeEvent: public Runnable + { + protected: + enum ChangeEventType + { + eChangeEventType_Focus, + eChangeEventType_Selection, + eChangeEventType_Text, + eChangeEventType_Position, + eChangeEventType_CompositionEventHandled + }; + + explicit AChangeEvent(IMEContentObserver* aIMEContentObserver) + : mIMEContentObserver(aIMEContentObserver) + { + MOZ_ASSERT(mIMEContentObserver); + } + + RefPtr<IMEContentObserver> mIMEContentObserver; + + /** + * CanNotifyIME() checks if mIMEContentObserver can and should notify IME. + */ + bool CanNotifyIME(ChangeEventType aChangeEventType) const; + + /** + * IsSafeToNotifyIME() checks if it's safe to noitify IME. + */ + bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const; + }; + + class IMENotificationSender: public AChangeEvent + { + public: + explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver) + : AChangeEvent(aIMEContentObserver) + , mIsRunning(false) + { + } + NS_IMETHOD Run() override; + + private: + void SendFocusSet(); + void SendSelectionChange(); + void SendTextChange(); + void SendPositionChange(); + void SendCompositionEventHandled(); + + bool mIsRunning; + }; + + // mQueuedSender is, it was put into the event queue but not run yet. + RefPtr<IMENotificationSender> mQueuedSender; + + /** + * FlatTextCache stores flat text length from start of the content to + * mNodeOffset of mContainerNode. + */ + struct FlatTextCache + { + // mContainerNode and mNodeOffset represent a point in DOM tree. E.g., + // if mContainerNode is a div element, mNodeOffset is index of its child. + nsCOMPtr<nsINode> mContainerNode; + int32_t mNodeOffset; + // Length of flat text generated from contents between the start of content + // and a child node whose index is mNodeOffset of mContainerNode. + uint32_t mFlatTextLength; + + FlatTextCache() + : mNodeOffset(0) + , mFlatTextLength(0) + { + } + + void Clear() + { + mContainerNode = nullptr; + mNodeOffset = 0; + mFlatTextLength = 0; + } + + void Cache(nsINode* aContainer, int32_t aNodeOffset, + uint32_t aFlatTextLength) + { + MOZ_ASSERT(aContainer, "aContainer must not be null"); + MOZ_ASSERT( + aNodeOffset <= static_cast<int32_t>(aContainer->GetChildCount()), + "aNodeOffset must be same as or less than the count of children"); + mContainerNode = aContainer; + mNodeOffset = aNodeOffset; + mFlatTextLength = aFlatTextLength; + } + + bool Match(nsINode* aContainer, int32_t aNodeOffset) const + { + return aContainer == mContainerNode && aNodeOffset == mNodeOffset; + } + }; + // mEndOfAddedTextCache caches text length from the start of content to + // the end of the last added content only while an edit action is being + // handled by the editor and no other mutation (e.g., removing node) + // occur. + FlatTextCache mEndOfAddedTextCache; + // mStartOfRemovingTextRangeCache caches text length from the start of content + // to the start of the last removed content only while an edit action is being + // handled by the editor and no other mutation (e.g., adding node) occur. + FlatTextCache mStartOfRemovingTextRangeCache; + + TextChangeData mTextChangeData; + + // mSelectionData is the last selection data which was notified. The + // selection information is modified by UpdateSelectionCache(). The reason + // of the selection change is modified by MaybeNotifyIMEOfSelectionChange(). + SelectionChangeData mSelectionData; + + EventStateManager* mESM; + + nsIMEUpdatePreference mUpdatePreference; + uint32_t mPreAttrChangeLength; + uint32_t mSuppressNotifications; + int64_t mPreCharacterDataChangeLength; + + // mSendingNotification is a notification which is now sending from + // IMENotificationSender. When the value is NOTIFY_IME_OF_NOTHING, it's + // not sending any notification. + IMEMessage mSendingNotification; + + bool mIsObserving; + bool mIMEHasFocus; + bool mNeedsToNotifyIMEOfFocusSet; + bool mNeedsToNotifyIMEOfTextChange; + bool mNeedsToNotifyIMEOfSelectionChange; + bool mNeedsToNotifyIMEOfPositionChange; + bool mNeedsToNotifyIMEOfCompositionEventHandled; + // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling + // WidgetQueryContentEvent with ContentEventHandler. + bool mIsHandlingQueryContentEvent; +}; + +} // namespace mozilla + +#endif // mozilla_IMEContentObserver_h_ diff --git a/dom/events/IMEStateManager.cpp b/dom/events/IMEStateManager.cpp new file mode 100644 index 000000000..1c8ed63f0 --- /dev/null +++ b/dom/events/IMEStateManager.cpp @@ -0,0 +1,1781 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "mozilla/IMEStateManager.h" + +#include "mozilla/Attributes.h" +#include "mozilla/EditorBase.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEvents.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/HTMLFormElement.h" +#include "mozilla/dom/TabParent.h" + +#include "HTMLInputElement.h" +#include "IMEContentObserver.h" + +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMMouseEvent.h" +#include "nsIForm.h" +#include "nsIFormControl.h" +#include "nsINode.h" +#include "nsIObserverService.h" +#include "nsIPresShell.h" +#include "nsISelection.h" +#include "nsISupports.h" +#include "nsPresContext.h" + +namespace mozilla { + +using namespace dom; +using namespace widget; + +/** + * When a method is called, log its arguments and/or related static variables + * with LogLevel::Info. However, if it puts too many logs like + * OnDestroyPresContext(), should long only when the method actually does + * something. In this case, the log should start with "<method name>". + * + * When a method quits due to unexpected situation, log the reason with + * LogLevel::Error. In this case, the log should start with + * "<method name>(), FAILED". The indent makes the log look easier. + * + * When a method does something only in some situations and it may be important + * for debug, log the information with LogLevel::Debug. In this case, the log + * should start with " <method name>(),". + */ +LazyLogModule sISMLog("IMEStateManager"); + +static const char* +GetBoolName(bool aBool) +{ + return aBool ? "true" : "false"; +} + +static const char* +GetActionCauseName(InputContextAction::Cause aCause) +{ + switch (aCause) { + case InputContextAction::CAUSE_UNKNOWN: + return "CAUSE_UNKNOWN"; + case InputContextAction::CAUSE_UNKNOWN_CHROME: + return "CAUSE_UNKNOWN_CHROME"; + case InputContextAction::CAUSE_KEY: + return "CAUSE_KEY"; + case InputContextAction::CAUSE_MOUSE: + return "CAUSE_MOUSE"; + case InputContextAction::CAUSE_TOUCH: + return "CAUSE_TOUCH"; + default: + return "illegal value"; + } +} + +static const char* +GetActionFocusChangeName(InputContextAction::FocusChange aFocusChange) +{ + switch (aFocusChange) { + case InputContextAction::FOCUS_NOT_CHANGED: + return "FOCUS_NOT_CHANGED"; + case InputContextAction::GOT_FOCUS: + return "GOT_FOCUS"; + case InputContextAction::LOST_FOCUS: + return "LOST_FOCUS"; + case InputContextAction::MENU_GOT_PSEUDO_FOCUS: + return "MENU_GOT_PSEUDO_FOCUS"; + case InputContextAction::MENU_LOST_PSEUDO_FOCUS: + return "MENU_LOST_PSEUDO_FOCUS"; + default: + return "illegal value"; + } +} + +static const char* +GetIMEStateEnabledName(IMEState::Enabled aEnabled) +{ + switch (aEnabled) { + case IMEState::DISABLED: + return "DISABLED"; + case IMEState::ENABLED: + return "ENABLED"; + case IMEState::PASSWORD: + return "PASSWORD"; + case IMEState::PLUGIN: + return "PLUGIN"; + default: + return "illegal value"; + } +} + +static const char* +GetIMEStateSetOpenName(IMEState::Open aOpen) +{ + switch (aOpen) { + case IMEState::DONT_CHANGE_OPEN_STATE: + return "DONT_CHANGE_OPEN_STATE"; + case IMEState::OPEN: + return "OPEN"; + case IMEState::CLOSED: + return "CLOSED"; + default: + return "illegal value"; + } +} + +StaticRefPtr<nsIContent> IMEStateManager::sContent; +StaticRefPtr<nsPresContext> IMEStateManager::sPresContext; +nsIWidget* IMEStateManager::sWidget = nullptr; +nsIWidget* IMEStateManager::sFocusedIMEWidget = nullptr; +nsIWidget* IMEStateManager::sActiveInputContextWidget = nullptr; +StaticRefPtr<TabParent> IMEStateManager::sActiveTabParent; +StaticRefPtr<IMEContentObserver> IMEStateManager::sActiveIMEContentObserver; +TextCompositionArray* IMEStateManager::sTextCompositions = nullptr; +bool IMEStateManager::sInstalledMenuKeyboardListener = false; +bool IMEStateManager::sIsGettingNewIMEState = false; +bool IMEStateManager::sCheckForIMEUnawareWebApps = false; +bool IMEStateManager::sRemoteHasFocus = false; + +// static +void +IMEStateManager::Init() +{ + Preferences::AddBoolVarCache( + &sCheckForIMEUnawareWebApps, + "intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition", + false); +} + +// static +void +IMEStateManager::Shutdown() +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%u", + sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0)); + + MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length()); + delete sTextCompositions; + sTextCompositions = nullptr; +} + +// static +void +IMEStateManager::OnTabParentDestroying(TabParent* aTabParent) +{ + if (sActiveTabParent != aTabParent) { + return; + } + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnTabParentDestroying(aTabParent=0x%p), " + "The active TabParent is being destroyed", aTabParent)); + + // The active remote process might have crashed. + sActiveTabParent = nullptr; + + // TODO: Need to cancel composition without TextComposition and make + // disable IME. +} + +// static +void +IMEStateManager::WidgetDestroyed(nsIWidget* aWidget) +{ + if (sWidget == aWidget) { + sWidget = nullptr; + } + if (sFocusedIMEWidget == aWidget) { + sFocusedIMEWidget = nullptr; + } + if (sActiveInputContextWidget == aWidget) { + sActiveInputContextWidget = nullptr; + } +} + +// static +void +IMEStateManager::StopIMEStateManagement() +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("StopIMEStateManagement()")); + + // NOTE: Don't set input context from here since this has already lost + // the rights to change input context. + + if (sTextCompositions && sPresContext) { + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, sPresContext); + } + sActiveInputContextWidget = nullptr; + sPresContext = nullptr; + sContent = nullptr; + sActiveTabParent = nullptr; + DestroyIMEContentObserver(); +} + +// static +void +IMEStateManager::MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget, + uint32_t aStartOffset) +{ + if (NS_WARN_IF(!sTextCompositions)) { + MOZ_LOG(sISMLog, LogLevel::Warning, + ("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), " + "called when there is no composition", aWidget, aStartOffset)); + return; + } + + RefPtr<TextComposition> composition = GetTextCompositionFor(aWidget); + if (NS_WARN_IF(!composition)) { + MOZ_LOG(sISMLog, LogLevel::Warning, + ("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), " + "called when there is no composition", aWidget, aStartOffset)); + return; + } + + if (composition->NativeOffsetOfStartComposition() == aStartOffset) { + return; + } + + MOZ_LOG(sISMLog, LogLevel::Info, + ("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), " + "old offset=%u", + aWidget, aStartOffset, composition->NativeOffsetOfStartComposition())); + composition->OnStartOffsetUpdatedInChild(aStartOffset); +} + +// static +nsresult +IMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext) +{ + NS_ENSURE_ARG_POINTER(aPresContext); + + // First, if there is a composition in the aPresContext, clean up it. + if (sTextCompositions) { + TextCompositionArray::index_type i = + sTextCompositions->IndexOf(aPresContext); + if (i != TextCompositionArray::NoIndex) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnDestroyPresContext(), " + "removing TextComposition instance from the array (index=%u)", i)); + // there should be only one composition per presContext object. + sTextCompositions->ElementAt(i)->Destroy(); + sTextCompositions->RemoveElementAt(i); + if (sTextCompositions->IndexOf(aPresContext) != + TextCompositionArray::NoIndex) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" OnDestroyPresContext(), FAILED to remove " + "TextComposition instance from the array")); + MOZ_CRASH("Failed to remove TextComposition instance from the array"); + } + } + } + + if (aPresContext != sPresContext) { + return NS_OK; + } + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnDestroyPresContext(aPresContext=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sTextCompositions=0x%p", + aPresContext, sPresContext.get(), sContent.get(), sTextCompositions)); + + DestroyIMEContentObserver(); + + if (sWidget) { + IMEState newState = GetNewIMEState(sPresContext, nullptr); + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::LOST_FOCUS); + SetIMEState(newState, nullptr, sWidget, action); + } + sWidget = nullptr; + sContent = nullptr; + sPresContext = nullptr; + sActiveTabParent = nullptr; + return NS_OK; +} + +// static +nsresult +IMEStateManager::OnRemoveContent(nsPresContext* aPresContext, + nsIContent* aContent) +{ + NS_ENSURE_ARG_POINTER(aPresContext); + + // First, if there is a composition in the aContent, clean up it. + if (sTextCompositions) { + RefPtr<TextComposition> compositionInContent = + sTextCompositions->GetCompositionInContent(aPresContext, aContent); + + if (compositionInContent) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnRemoveContent(), " + "composition is in the content")); + + // Try resetting the native IME state. Be aware, typically, this method + // is called during the content being removed. Then, the native + // composition events which are caused by following APIs are ignored due + // to unsafe to run script (in PresShell::HandleEvent()). + nsresult rv = + compositionInContent->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION); + if (NS_FAILED(rv)) { + compositionInContent->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); + } + } + } + + if (!sPresContext || !sContent || + !nsContentUtils::ContentIsDescendantOf(sContent, aContent)) { + return NS_OK; + } + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnRemoveContent(aPresContext=0x%p, aContent=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sTextCompositions=0x%p", + aPresContext, aContent, sPresContext.get(), sContent.get(), sTextCompositions)); + + DestroyIMEContentObserver(); + + // Current IME transaction should commit + if (sWidget) { + IMEState newState = GetNewIMEState(sPresContext, nullptr); + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::LOST_FOCUS); + SetIMEState(newState, nullptr, sWidget, action); + } + + sWidget = nullptr; + sContent = nullptr; + sPresContext = nullptr; + sActiveTabParent = nullptr; + + return NS_OK; +} + +// static +bool +IMEStateManager::CanHandleWith(nsPresContext* aPresContext) +{ + return aPresContext && + aPresContext->GetPresShell() && + !aPresContext->PresShell()->IsDestroying(); +} + +// static +nsresult +IMEStateManager::OnChangeFocus(nsPresContext* aPresContext, + nsIContent* aContent, + InputContextAction::Cause aCause) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnChangeFocus(aPresContext=0x%p, aContent=0x%p, aCause=%s)", + aPresContext, aContent, GetActionCauseName(aCause))); + + InputContextAction action(aCause); + return OnChangeFocusInternal(aPresContext, aContent, action); +} + +// static +nsresult +IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext, + nsIContent* aContent, + InputContextAction aAction) +{ + RefPtr<TabParent> newTabParent = TabParent::GetFrom(aContent); + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnChangeFocusInternal(aPresContext=0x%p (available: %s), " + "aContent=0x%p (TabParent=0x%p), aAction={ mCause=%s, mFocusChange=%s }), " + "sPresContext=0x%p (available: %s), sContent=0x%p, " + "sWidget=0x%p (available: %s), sActiveTabParent=0x%p, " + "sActiveIMEContentObserver=0x%p, sInstalledMenuKeyboardListener=%s", + aPresContext, GetBoolName(CanHandleWith(aPresContext)), aContent, + newTabParent.get(), GetActionCauseName(aAction.mCause), + GetActionFocusChangeName(aAction.mFocusChange), + sPresContext.get(), GetBoolName(CanHandleWith(sPresContext)), + sContent.get(), sWidget, GetBoolName(sWidget && !sWidget->Destroyed()), + sActiveTabParent.get(), sActiveIMEContentObserver.get(), + GetBoolName(sInstalledMenuKeyboardListener))); + + // If new aPresShell has been destroyed, this should handle the focus change + // as nobody is getting focus. + if (NS_WARN_IF(aPresContext && !CanHandleWith(aPresContext))) { + MOZ_LOG(sISMLog, LogLevel::Warning, + (" OnChangeFocusInternal(), called with destroyed PresShell, " + "handling this call as nobody getting focus")); + aPresContext = nullptr; + aContent = nullptr; + } + + nsCOMPtr<nsIWidget> oldWidget = sWidget; + nsCOMPtr<nsIWidget> newWidget = + aPresContext ? aPresContext->GetRootWidget() : nullptr; + bool focusActuallyChanging = + (sContent != aContent || sPresContext != aPresContext || + oldWidget != newWidget || sActiveTabParent != newTabParent); + + if (oldWidget && focusActuallyChanging) { + // If we're deactivating, we shouldn't commit composition forcibly because + // the user may want to continue the composition. + if (aPresContext) { + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget); + } + } + + if (sActiveIMEContentObserver && + (aPresContext || !sActiveIMEContentObserver->KeepAliveDuringDeactive()) && + !sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) { + DestroyIMEContentObserver(); + } + + if (!aPresContext) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), " + "no nsPresContext is being activated")); + return NS_OK; + } + + nsIContentParent* currentContentParent = + sActiveTabParent ? sActiveTabParent->Manager() : nullptr; + nsIContentParent* newContentParent = + newTabParent ? newTabParent->Manager() : nullptr; + if (sActiveTabParent && currentContentParent != newContentParent) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), notifying previous " + "focused child process of parent process or another child process " + "getting focus")); + Unused << sActiveTabParent->SendStopIMEStateManagement(); + } + + if (NS_WARN_IF(!newWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" OnChangeFocusInternal(), FAILED due to " + "no widget to manage its IME state")); + return NS_OK; + } + + // Update the cached widget since root view of the presContext may be + // changed to different view. + sWidget = newWidget; + + // If a child process has focus, we should disable IME state until the child + // process actually gets focus because if user types keys before that they + // are handled by IME. + IMEState newState = + newTabParent ? IMEState(IMEState::DISABLED) : + GetNewIMEState(aPresContext, aContent); + bool setIMEState = true; + + if (newTabParent) { + if (aAction.mFocusChange == InputContextAction::MENU_GOT_PSEUDO_FOCUS || + aAction.mFocusChange == InputContextAction::MENU_LOST_PSEUDO_FOCUS) { + // XXX When menu keyboard listener is being uninstalled, IME state needs + // to be restored by the child process asynchronously. Therefore, + // some key events which are fired immediately after closing menu + // may not be handled by IME. + Unused << newTabParent-> + SendMenuKeyboardListenerInstalled(sInstalledMenuKeyboardListener); + setIMEState = sInstalledMenuKeyboardListener; + } else if (focusActuallyChanging) { + InputContext context = newWidget->GetInputContext(); + if (context.mIMEState.mEnabled == IMEState::DISABLED) { + setIMEState = false; + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), doesn't set IME " + "state because focused element (or document) is in a child process " + "and the IME state is already disabled")); + } else { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), will disable IME " + "until new focused element (or document) in the child process " + "will get focus actually")); + } + } else { + // When focus is NOT changed actually, we shouldn't set IME state since + // that means that the window is being activated and the child process + // may have composition. Then, we shouldn't commit the composition with + // making IME state disabled. + setIMEState = false; + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), doesn't set IME " + "state because focused element (or document) is already in the child " + "process")); + } + } + + if (setIMEState) { + if (!focusActuallyChanging) { + // actual focus isn't changing, but if IME enabled state is changing, + // we should do it. + InputContext context = newWidget->GetInputContext(); + if (context.mIMEState.mEnabled == newState.mEnabled) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), " + "neither focus nor IME state is changing")); + return NS_OK; + } + aAction.mFocusChange = InputContextAction::FOCUS_NOT_CHANGED; + + // Even if focus isn't changing actually, we should commit current + // composition here since the IME state is changing. + if (sPresContext && oldWidget && !focusActuallyChanging) { + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget); + } + } else if (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED) { + // If aContent isn't null or aContent is null but editable, somebody gets + // focus. + bool gotFocus = aContent || (newState.mEnabled == IMEState::ENABLED); + aAction.mFocusChange = + gotFocus ? InputContextAction::GOT_FOCUS : + InputContextAction::LOST_FOCUS; + } + + // Update IME state for new focus widget + SetIMEState(newState, aContent, newWidget, aAction); + } + + sActiveTabParent = newTabParent; + sPresContext = aPresContext; + sContent = aContent; + + // Don't call CreateIMEContentObserver() here except when a plugin gets + // focus because it will be called from the focus event handler of focused + // editor. + if (newState.mEnabled == IMEState::PLUGIN) { + CreateIMEContentObserver(nullptr); + if (sActiveIMEContentObserver) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnChangeFocusInternal(), an " + "IMEContentObserver instance is created for plugin and trying to " + "flush its pending notifications...")); + sActiveIMEContentObserver->TryToFlushPendingNotifications(); + } + } + + return NS_OK; +} + +// static +void +IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnInstalledMenuKeyboardListener(aInstalling=%s), " + "sInstalledMenuKeyboardListener=%s", + GetBoolName(aInstalling), GetBoolName(sInstalledMenuKeyboardListener))); + + sInstalledMenuKeyboardListener = aInstalling; + + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + aInstalling ? InputContextAction::MENU_GOT_PSEUDO_FOCUS : + InputContextAction::MENU_LOST_PSEUDO_FOCUS); + OnChangeFocusInternal(sPresContext, sContent, action); +} + +// static +bool +IMEStateManager::OnMouseButtonEventInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIDOMMouseEvent* aMouseEvent) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnMouseButtonEventInEditor(aPresContext=0x%p, " + "aContent=0x%p, aMouseEvent=0x%p), sPresContext=0x%p, sContent=0x%p", + aPresContext, aContent, aMouseEvent, sPresContext.get(), sContent.get())); + + if (sPresContext != aPresContext || sContent != aContent) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnMouseButtonEventInEditor(), " + "the mouse event isn't fired on the editor managed by ISM")); + return false; + } + + if (!sActiveIMEContentObserver) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnMouseButtonEventInEditor(), " + "there is no active IMEContentObserver")); + return false; + } + + if (!sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnMouseButtonEventInEditor(), " + "the active IMEContentObserver isn't managing the editor")); + return false; + } + + WidgetMouseEvent* internalEvent = + aMouseEvent->AsEvent()->WidgetEventPtr()->AsMouseEvent(); + if (NS_WARN_IF(!internalEvent)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnMouseButtonEventInEditor(), " + "the internal event of aMouseEvent isn't WidgetMouseEvent")); + return false; + } + + bool consumed = + sActiveIMEContentObserver->OnMouseButtonEvent(aPresContext, internalEvent); + + if (MOZ_LOG_TEST(sISMLog, LogLevel::Info)) { + nsAutoString eventType; + aMouseEvent->AsEvent()->GetType(eventType); + MOZ_LOG(sISMLog, LogLevel::Info, + (" OnMouseButtonEventInEditor(), " + "mouse event (type=%s, button=%d) is %s", + NS_ConvertUTF16toUTF8(eventType).get(), internalEvent->button, + consumed ? "consumed" : "not consumed")); + } + + return consumed; +} + +// static +void +IMEStateManager::OnClickInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIDOMMouseEvent* aMouseEvent) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnClickInEditor(aPresContext=0x%p, aContent=0x%p, aMouseEvent=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s)", + aPresContext, aContent, aMouseEvent, sPresContext.get(), sContent.get(), + sWidget, GetBoolName(sWidget && !sWidget->Destroyed()))); + + if (sPresContext != aPresContext || sContent != aContent || + NS_WARN_IF(!sPresContext) || NS_WARN_IF(!sWidget) || + NS_WARN_IF(sWidget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnClickInEditor(), " + "the mouse event isn't fired on the editor managed by ISM")); + return; + } + + nsCOMPtr<nsIWidget> widget(sWidget); + + MOZ_ASSERT(!sPresContext->GetRootWidget() || + sPresContext->GetRootWidget() == widget); + + bool isTrusted; + nsresult rv = aMouseEvent->AsEvent()->GetIsTrusted(&isTrusted); + NS_ENSURE_SUCCESS_VOID(rv); + if (!isTrusted) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnClickInEditor(), " + "the mouse event isn't a trusted event")); + return; // ignore untrusted event. + } + + int16_t button; + rv = aMouseEvent->GetButton(&button); + NS_ENSURE_SUCCESS_VOID(rv); + if (button != 0) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnClickInEditor(), " + "the mouse event isn't a left mouse button event")); + return; // not a left click event. + } + + int32_t clickCount; + rv = aMouseEvent->GetDetail(&clickCount); + NS_ENSURE_SUCCESS_VOID(rv); + if (clickCount != 1) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnClickInEditor(), " + "the mouse event isn't a single click event")); + return; // should notify only first click event. + } + + uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + aMouseEvent->GetMozInputSource(&inputSource); + InputContextAction::Cause cause = + inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH ? + InputContextAction::CAUSE_TOUCH : InputContextAction::CAUSE_MOUSE; + + InputContextAction action(cause, InputContextAction::FOCUS_NOT_CHANGED); + IMEState newState = GetNewIMEState(aPresContext, aContent); + SetIMEState(newState, aContent, widget, action); +} + +// static +void +IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIEditor* aEditor) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnFocusInEditor(aPresContext=0x%p, aContent=0x%p, aEditor=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sActiveIMEContentObserver=0x%p", + aPresContext, aContent, aEditor, sPresContext.get(), sContent.get(), + sActiveIMEContentObserver.get())); + + if (sPresContext != aPresContext || sContent != aContent) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnFocusInEditor(), " + "an editor not managed by ISM gets focus")); + return; + } + + // If the IMEContentObserver instance isn't managing the editor actually, + // we need to recreate the instance. + if (sActiveIMEContentObserver) { + if (sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnFocusInEditor(), " + "the editor is already being managed by sActiveIMEContentObserver")); + return; + } + DestroyIMEContentObserver(); + } + + CreateIMEContentObserver(aEditor); + + // Let's flush the focus notification now. + if (sActiveIMEContentObserver) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" OnFocusInEditor(), new IMEContentObserver is " + "created, trying to flush pending notifications...")); + sActiveIMEContentObserver->TryToFlushPendingNotifications(); + } +} + +// static +void +IMEStateManager::OnEditorInitialized(nsIEditor* aEditor) +{ + if (!sActiveIMEContentObserver || + sActiveIMEContentObserver->GetEditor() != aEditor) { + return; + } + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnEditorInitialized(aEditor=0x%p)", + aEditor)); + + sActiveIMEContentObserver->UnsuppressNotifyingIME(); +} + +// static +void +IMEStateManager::OnEditorDestroying(nsIEditor* aEditor) +{ + if (!sActiveIMEContentObserver || + sActiveIMEContentObserver->GetEditor() != aEditor) { + return; + } + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnEditorDestroying(aEditor=0x%p)", + aEditor)); + + // The IMEContentObserver shouldn't notify IME of anything until reframing + // is finished. + sActiveIMEContentObserver->SuppressNotifyingIME(); +} + +// static +void +IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, + nsIContent* aContent, + EditorBase& aEditorBase) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("UpdateIMEState(aNewIMEState={ mEnabled=%s, " + "mOpen=%s }, aContent=0x%p, aEditorBase=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), " + "sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s", + GetIMEStateEnabledName(aNewIMEState.mEnabled), + GetIMEStateSetOpenName(aNewIMEState.mOpen), aContent, &aEditorBase, + sPresContext.get(), sContent.get(), + sWidget, GetBoolName(sWidget && !sWidget->Destroyed()), + sActiveIMEContentObserver.get(), + GetBoolName(sIsGettingNewIMEState))); + + if (sIsGettingNewIMEState) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" UpdateIMEState(), " + "does nothing because of called while getting new IME state")); + return; + } + + nsCOMPtr<nsIPresShell> presShell = aEditorBase.GetPresShell(); + if (NS_WARN_IF(!presShell)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), FAILED due to " + "editor doesn't have PresShell")); + return; + } + + nsPresContext* presContext = presShell->GetPresContext(); + if (NS_WARN_IF(!presContext)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), FAILED due to " + "editor doesn't have PresContext")); + return; + } + + // IMEStateManager::UpdateIMEState() should be called after + // IMEStateManager::OnChangeFocus() is called for setting focus to aContent + // and aEditorBase. However, when aEditorBase is an HTMLEditor, this may be + // called by nsIEditor::PostCreate() before IMEStateManager::OnChangeFocus(). + // Similarly, when aEditorBase is a TextEditor, this may be called by + // nsIEditor::SetFlags(). In such cases, this method should do nothing + // because input context should be updated when + // IMEStateManager::OnChangeFocus() is called later. + if (sPresContext != presContext) { + MOZ_LOG(sISMLog, LogLevel::Warning, + (" UpdateIMEState(), does nothing due to " + "the editor hasn't managed by IMEStateManager yet")); + return; + } + + // If IMEStateManager doesn't manage any document, this cannot update IME + // state of any widget. + if (NS_WARN_IF(!sPresContext)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), FAILED due to " + "no managing nsPresContext")); + return; + } + + if (NS_WARN_IF(!sWidget) || NS_WARN_IF(sWidget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), FAILED due to " + "the widget for the managing nsPresContext has gone")); + return; + } + + nsCOMPtr<nsIWidget> widget(sWidget); + + MOZ_ASSERT(!sPresContext->GetRootWidget() || + sPresContext->GetRootWidget() == widget); + + // Even if there is active IMEContentObserver, it may not be observing the + // editor with current editable root content due to reframed. In such case, + // We should try to reinitialize the IMEContentObserver. + if (sActiveIMEContentObserver && IsIMEObserverNeeded(aNewIMEState)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" UpdateIMEState(), try to reinitialize the " + "active IMEContentObserver")); + if (!sActiveIMEContentObserver->MaybeReinitialize(widget, sPresContext, + aContent, &aEditorBase)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), failed to reinitialize the " + "active IMEContentObserver")); + } + if (NS_WARN_IF(widget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), widget has gone during reinitializing the " + "active IMEContentObserver")); + return; + } + } + + // If there is no active IMEContentObserver or it isn't observing the + // editor correctly, we should recreate it. + bool createTextStateManager = + (!sActiveIMEContentObserver || + !sActiveIMEContentObserver->IsManaging(sPresContext, aContent)); + + bool updateIMEState = + (widget->GetInputContext().mIMEState.mEnabled != aNewIMEState.mEnabled); + if (NS_WARN_IF(widget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), widget has gone during getting input context")); + return; + } + + if (updateIMEState) { + // commit current composition before modifying IME state. + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, widget); + if (NS_WARN_IF(widget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), widget has gone during committing composition")); + return; + } + } + + if (createTextStateManager) { + DestroyIMEContentObserver(); + } + + if (updateIMEState) { + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::FOCUS_NOT_CHANGED); + SetIMEState(aNewIMEState, aContent, widget, action); + if (NS_WARN_IF(widget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" UpdateIMEState(), widget has gone during setting input context")); + return; + } + } + + if (createTextStateManager) { + // XXX In this case, it might not be enough safe to notify IME of anything. + // So, don't try to flush pending notifications of IMEContentObserver + // here. + CreateIMEContentObserver(&aEditorBase); + } +} + +// static +IMEState +IMEStateManager::GetNewIMEState(nsPresContext* aPresContext, + nsIContent* aContent) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("GetNewIMEState(aPresContext=0x%p, aContent=0x%p), " + "sInstalledMenuKeyboardListener=%s", + aPresContext, aContent, GetBoolName(sInstalledMenuKeyboardListener))); + + if (!CanHandleWith(aPresContext)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" GetNewIMEState() returns DISABLED because " + "the nsPresContext has been destroyed")); + return IMEState(IMEState::DISABLED); + } + + // On Printing or Print Preview, we don't need IME. + if (aPresContext->Type() == nsPresContext::eContext_PrintPreview || + aPresContext->Type() == nsPresContext::eContext_Print) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" GetNewIMEState() returns DISABLED because " + "the nsPresContext is for print or print preview")); + return IMEState(IMEState::DISABLED); + } + + if (sInstalledMenuKeyboardListener) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" GetNewIMEState() returns DISABLED because " + "menu keyboard listener was installed")); + return IMEState(IMEState::DISABLED); + } + + if (!aContent) { + // Even if there are no focused content, the focused document might be + // editable, such case is design mode. + nsIDocument* doc = aPresContext->Document(); + if (doc && doc->HasFlag(NODE_IS_EDITABLE)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" GetNewIMEState() returns ENABLED because " + "design mode editor has focus")); + return IMEState(IMEState::ENABLED); + } + MOZ_LOG(sISMLog, LogLevel::Debug, + (" GetNewIMEState() returns DISABLED because " + "no content has focus")); + return IMEState(IMEState::DISABLED); + } + + // nsIContent::GetDesiredIMEState() may cause a call of UpdateIMEState() + // from EditorBase::PostCreate() because GetDesiredIMEState() needs to + // retrieve an editor instance for the element if it's editable element. + // For avoiding such nested IME state updates, we should set + // sIsGettingNewIMEState here and UpdateIMEState() should check it. + GettingNewIMEStateBlocker blocker; + + IMEState newIMEState = aContent->GetDesiredIMEState(); + MOZ_LOG(sISMLog, LogLevel::Debug, + (" GetNewIMEState() returns { mEnabled=%s, " + "mOpen=%s }", + GetIMEStateEnabledName(newIMEState.mEnabled), + GetIMEStateSetOpenName(newIMEState.mOpen))); + return newIMEState; +} + +static bool +MayBeIMEUnawareWebApp(nsINode* aNode) +{ + bool haveKeyEventsListener = false; + + while (aNode) { + EventListenerManager* const mgr = aNode->GetExistingListenerManager(); + if (mgr) { + if (mgr->MayHaveInputOrCompositionEventListener()) { + return false; + } + haveKeyEventsListener |= mgr->MayHaveKeyEventListener(); + } + aNode = aNode->GetParentNode(); + } + + return haveKeyEventsListener; +} + +// static +void +IMEStateManager::SetInputContextForChildProcess( + TabParent* aTabParent, + const InputContext& aInputContext, + const InputContextAction& aAction) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("SetInputContextForChildProcess(aTabParent=0x%p, " + "aInputContext={ mIMEState={ mEnabled=%s, mOpen=%s }, " + "mHTMLInputType=\"%s\", mHTMLInputInputmode=\"%s\", mActionHint=\"%s\" }, " + "aAction={ mCause=%s, mAction=%s }), " + "sPresContext=0x%p (available: %s), sWidget=0x%p (available: %s), " + "sActiveTabParent=0x%p", + aTabParent, GetIMEStateEnabledName(aInputContext.mIMEState.mEnabled), + GetIMEStateSetOpenName(aInputContext.mIMEState.mOpen), + NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(), + NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(), + NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(), + GetActionCauseName(aAction.mCause), + GetActionFocusChangeName(aAction.mFocusChange), + sPresContext.get(), GetBoolName(CanHandleWith(sPresContext)), + sWidget, GetBoolName(sWidget && !sWidget->Destroyed()), + sActiveTabParent.get())); + + if (aTabParent != sActiveTabParent) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" SetInputContextForChildProcess(), FAILED, " + "because non-focused tab parent tries to set input context")); + return; + } + + if (NS_WARN_IF(!CanHandleWith(sPresContext))) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" SetInputContextForChildProcess(), FAILED, " + "due to no focused presContext")); + return; + } + + if (NS_WARN_IF(!sWidget) || NS_WARN_IF(sWidget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" SetInputContextForChildProcess(), FAILED, " + "due to the widget for the nsPresContext has gone")); + return; + } + + nsCOMPtr<nsIWidget> widget(sWidget); + + MOZ_ASSERT(!sPresContext->GetRootWidget() || + sPresContext->GetRootWidget() == widget); + MOZ_ASSERT(aInputContext.mOrigin == InputContext::ORIGIN_CONTENT); + + SetInputContext(widget, aInputContext, aAction); +} + +// static +void +IMEStateManager::SetIMEState(const IMEState& aState, + nsIContent* aContent, + nsIWidget* aWidget, + InputContextAction aAction) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("SetIMEState(aState={ mEnabled=%s, mOpen=%s }, " + "aContent=0x%p (TabParent=0x%p), aWidget=0x%p, aAction={ mCause=%s, " + "mFocusChange=%s })", + GetIMEStateEnabledName(aState.mEnabled), + GetIMEStateSetOpenName(aState.mOpen), aContent, + TabParent::GetFrom(aContent), aWidget, + GetActionCauseName(aAction.mCause), + GetActionFocusChangeName(aAction.mFocusChange))); + + NS_ENSURE_TRUE_VOID(aWidget); + + InputContext context; + context.mIMEState = aState; + context.mMayBeIMEUnaware = context.mIMEState.IsEditable() && + sCheckForIMEUnawareWebApps && MayBeIMEUnawareWebApp(aContent); + + if (aContent && + aContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) { + if (!aContent->IsHTMLElement(nsGkAtoms::textarea)) { + // <input type=number> has an anonymous <input type=text> descendant + // that gets focus whenever anyone tries to focus the number control. We + // need to check if aContent is one of those anonymous text controls and, + // if so, use the number control instead: + nsIContent* content = aContent; + HTMLInputElement* inputElement = + HTMLInputElement::FromContentOrNull(aContent); + if (inputElement) { + HTMLInputElement* ownerNumberControl = + inputElement->GetOwnerNumberControl(); + if (ownerNumberControl) { + content = ownerNumberControl; // an <input type=number> + } + } + content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, + context.mHTMLInputType); + } else { + context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String()); + } + + if (Preferences::GetBool("dom.forms.inputmode", false) || + nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) { + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::inputmode, + context.mHTMLInputInputmode); + } + + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::moz_action_hint, + context.mActionHint); + + // Get the input content corresponding to the focused node, + // which may be an anonymous child of the input content. + nsIContent* inputContent = aContent->FindFirstNonChromeOnlyAccessContent(); + + // If we don't have an action hint and + // return won't submit the form, use "next". + if (context.mActionHint.IsEmpty() && + inputContent->IsHTMLElement(nsGkAtoms::input)) { + bool willSubmit = false; + nsCOMPtr<nsIFormControl> control(do_QueryInterface(inputContent)); + mozilla::dom::Element* formElement = nullptr; + nsCOMPtr<nsIForm> form; + if (control) { + formElement = control->GetFormElement(); + // is this a form and does it have a default submit element? + if ((form = do_QueryInterface(formElement)) && + form->GetDefaultSubmitElement()) { + willSubmit = true; + // is this an html form and does it only have a single text input element? + } else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form) && + !static_cast<dom::HTMLFormElement*>(formElement)-> + ImplicitSubmissionIsDisabled()) { + willSubmit = true; + } + } + context.mActionHint.Assign( + willSubmit ? (control->GetType() == NS_FORM_INPUT_SEARCH ? + NS_LITERAL_STRING("search") : NS_LITERAL_STRING("go")) : + (formElement ? + NS_LITERAL_STRING("next") : EmptyString())); + } + } + + // XXX I think that we should use nsContentUtils::IsCallerChrome() instead + // of the process type. + if (aAction.mCause == InputContextAction::CAUSE_UNKNOWN && + !XRE_IsContentProcess()) { + aAction.mCause = InputContextAction::CAUSE_UNKNOWN_CHROME; + } + + SetInputContext(aWidget, context, aAction); +} + +// static +void +IMEStateManager::SetInputContext(nsIWidget* aWidget, + const InputContext& aInputContext, + const InputContextAction& aAction) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("SetInputContext(aWidget=0x%p, aInputContext={ " + "mIMEState={ mEnabled=%s, mOpen=%s }, mHTMLInputType=\"%s\", " + "mHTMLInputInputmode=\"%s\", mActionHint=\"%s\" }, " + "aAction={ mCause=%s, mAction=%s }), sActiveTabParent=0x%p", + aWidget, + GetIMEStateEnabledName(aInputContext.mIMEState.mEnabled), + GetIMEStateSetOpenName(aInputContext.mIMEState.mOpen), + NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputType).get(), + NS_ConvertUTF16toUTF8(aInputContext.mHTMLInputInputmode).get(), + NS_ConvertUTF16toUTF8(aInputContext.mActionHint).get(), + GetActionCauseName(aAction.mCause), + GetActionFocusChangeName(aAction.mFocusChange), + sActiveTabParent.get())); + + MOZ_RELEASE_ASSERT(aWidget); + + nsCOMPtr<nsIWidget> widget(aWidget); + widget->SetInputContext(aInputContext, aAction); + sActiveInputContextWidget = widget; +} + +// static +void +IMEStateManager::EnsureTextCompositionArray() +{ + if (sTextCompositions) { + return; + } + sTextCompositions = new TextCompositionArray(); +} + +// static +void +IMEStateManager::DispatchCompositionEvent( + nsINode* aEventTargetNode, + nsPresContext* aPresContext, + WidgetCompositionEvent* aCompositionEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack, + bool aIsSynthesized) +{ + RefPtr<TabParent> tabParent = + aEventTargetNode->IsContent() ? + TabParent::GetFrom(aEventTargetNode->AsContent()) : nullptr; + + MOZ_LOG(sISMLog, LogLevel::Info, + ("DispatchCompositionEvent(aNode=0x%p, " + "aPresContext=0x%p, aCompositionEvent={ mMessage=%s, " + "mNativeIMEContext={ mRawNativeIMEContext=0x%X, " + "mOriginProcessID=0x%X }, mWidget(0x%p)={ " + "GetNativeIMEContext()={ mRawNativeIMEContext=0x%X, " + "mOriginProcessID=0x%X }, Destroyed()=%s }, " + "mFlags={ mIsTrusted=%s, mPropagationStopped=%s } }, " + "aIsSynthesized=%s), tabParent=%p", + aEventTargetNode, aPresContext, + ToChar(aCompositionEvent->mMessage), + aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext, + aCompositionEvent->mNativeIMEContext.mOriginProcessID, + aCompositionEvent->mWidget.get(), + aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext, + aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID, + GetBoolName(aCompositionEvent->mWidget->Destroyed()), + GetBoolName(aCompositionEvent->mFlags.mIsTrusted), + GetBoolName(aCompositionEvent->mFlags.mPropagationStopped), + GetBoolName(aIsSynthesized), tabParent.get())); + + if (!aCompositionEvent->IsTrusted() || + aCompositionEvent->PropagationStopped()) { + return; + } + + MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionUpdate, + "compositionupdate event shouldn't be dispatched manually"); + + EnsureTextCompositionArray(); + + RefPtr<TextComposition> composition = + sTextCompositions->GetCompositionFor(aCompositionEvent); + if (!composition) { + // If synthesized event comes after delayed native composition events + // for request of commit or cancel, we should ignore it. + if (NS_WARN_IF(aIsSynthesized)) { + return; + } + MOZ_LOG(sISMLog, LogLevel::Debug, + (" DispatchCompositionEvent(), " + "adding new TextComposition to the array")); + MOZ_ASSERT(aCompositionEvent->mMessage == eCompositionStart); + composition = + new TextComposition(aPresContext, aEventTargetNode, tabParent, + aCompositionEvent); + sTextCompositions->AppendElement(composition); + } +#ifdef DEBUG + else { + MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart); + } +#endif // #ifdef DEBUG + + // Dispatch the event on composing target. + composition->DispatchCompositionEvent(aCompositionEvent, aStatus, aCallBack, + aIsSynthesized); + + // WARNING: the |composition| might have been destroyed already. + + // Remove the ended composition from the array. + // NOTE: When TextComposition is synthesizing compositionend event for + // emulating a commit, the instance shouldn't be removed from the array + // because IME may perform it later. Then, we need to ignore the + // following commit events in TextComposition::DispatchEvent(). + // However, if commit or cancel for a request is performed synchronously + // during not safe to dispatch events, PresShell must have discarded + // compositionend event. Then, the synthesized compositionend event is + // the last event for the composition. In this case, we need to + // destroy the TextComposition with synthesized compositionend event. + if ((!aIsSynthesized || + composition->WasNativeCompositionEndEventDiscarded()) && + aCompositionEvent->CausesDOMCompositionEndEvent()) { + TextCompositionArray::index_type i = + sTextCompositions->IndexOf(aCompositionEvent->mWidget); + if (i != TextCompositionArray::NoIndex) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" DispatchCompositionEvent(), " + "removing TextComposition from the array since NS_COMPOSTION_END " + "was dispatched")); + sTextCompositions->ElementAt(i)->Destroy(); + sTextCompositions->RemoveElementAt(i); + } + } +} + +// static +IMEContentObserver* +IMEStateManager::GetActiveContentObserver() +{ + return sActiveIMEContentObserver; +} + +// static +nsIContent* +IMEStateManager::GetRootContent(nsPresContext* aPresContext) +{ + nsIDocument* doc = aPresContext->Document(); + if (NS_WARN_IF(!doc)) { + return nullptr; + } + return doc->GetRootElement(); +} + +// static +void +IMEStateManager::HandleSelectionEvent(nsPresContext* aPresContext, + nsIContent* aEventTargetContent, + WidgetSelectionEvent* aSelectionEvent) +{ + nsIContent* eventTargetContent = + aEventTargetContent ? aEventTargetContent : + GetRootContent(aPresContext); + RefPtr<TabParent> tabParent = + eventTargetContent ? TabParent::GetFrom(eventTargetContent) : nullptr; + + MOZ_LOG(sISMLog, LogLevel::Info, + ("HandleSelectionEvent(aPresContext=0x%p, " + "aEventTargetContent=0x%p, aSelectionEvent={ mMessage=%s, " + "mFlags={ mIsTrusted=%s } }), tabParent=%p", + aPresContext, aEventTargetContent, + ToChar(aSelectionEvent->mMessage), + GetBoolName(aSelectionEvent->mFlags.mIsTrusted), + tabParent.get())); + + if (!aSelectionEvent->IsTrusted()) { + return; + } + + RefPtr<TextComposition> composition = sTextCompositions ? + sTextCompositions->GetCompositionFor(aSelectionEvent->mWidget) : nullptr; + if (composition) { + // When there is a composition, TextComposition should guarantee that the + // selection event will be handled in same target as composition events. + composition->HandleSelectionEvent(aSelectionEvent); + } else { + // When there is no composition, the selection event should be handled + // in the aPresContext or tabParent. + TextComposition::HandleSelectionEvent(aPresContext, tabParent, + aSelectionEvent); + } +} + +// static +void +IMEStateManager::OnCompositionEventDiscarded( + WidgetCompositionEvent* aCompositionEvent) +{ + // Note that this method is never called for synthesized events for emulating + // commit or cancel composition. + + MOZ_LOG(sISMLog, LogLevel::Info, + ("OnCompositionEventDiscarded(aCompositionEvent={ " + "mMessage=%s, mNativeIMEContext={ mRawNativeIMEContext=0x%X, " + "mOriginProcessID=0x%X }, mWidget(0x%p)={ " + "GetNativeIMEContext()={ mRawNativeIMEContext=0x%X, " + "mOriginProcessID=0x%X }, Destroyed()=%s }, " + "mFlags={ mIsTrusted=%s } })", + ToChar(aCompositionEvent->mMessage), + aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext, + aCompositionEvent->mNativeIMEContext.mOriginProcessID, + aCompositionEvent->mWidget.get(), + aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext, + aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID, + GetBoolName(aCompositionEvent->mWidget->Destroyed()), + GetBoolName(aCompositionEvent->mFlags.mIsTrusted))); + + if (!aCompositionEvent->IsTrusted()) { + return; + } + + // Ignore compositionstart for now because sTextCompositions may not have + // been created yet. + if (aCompositionEvent->mMessage == eCompositionStart) { + return; + } + + RefPtr<TextComposition> composition = + sTextCompositions->GetCompositionFor(aCompositionEvent->mWidget); + if (!composition) { + // If the PresShell has been being destroyed during composition, + // a TextComposition instance for the composition was already removed from + // the array and destroyed in OnDestroyPresContext(). Therefore, we may + // fail to retrieve a TextComposition instance here. + MOZ_LOG(sISMLog, LogLevel::Info, + (" OnCompositionEventDiscarded(), " + "TextComposition instance for the widget has already gone")); + return; + } + composition->OnCompositionEventDiscarded(aCompositionEvent); +} + +// static +nsresult +IMEStateManager::NotifyIME(IMEMessage aMessage, + nsIWidget* aWidget, + bool aOriginIsRemote) +{ + return IMEStateManager::NotifyIME(IMENotification(aMessage), aWidget, + aOriginIsRemote); +} + +// static +nsresult +IMEStateManager::NotifyIME(const IMENotification& aNotification, + nsIWidget* aWidget, + bool aOriginIsRemote) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("NotifyIME(aNotification={ mMessage=%s }, " + "aWidget=0x%p, aOriginIsRemote=%s), sFocusedIMEWidget=0x%p, " + "sRemoteHasFocus=%s", + ToChar(aNotification.mMessage), aWidget, + GetBoolName(aOriginIsRemote), sFocusedIMEWidget, + GetBoolName(sRemoteHasFocus))); + + if (NS_WARN_IF(!aWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED due to no widget")); + return NS_ERROR_INVALID_ARG; + } + + switch (aNotification.mMessage) { + case NOTIFY_IME_OF_FOCUS: { + if (sFocusedIMEWidget) { + if (NS_WARN_IF(!sRemoteHasFocus && !aOriginIsRemote)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), although, this process is " + "getting IME focus but there was focused IME widget")); + } else { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), tries to notify IME of " + "blur first because remote process's blur notification hasn't " + "been received yet...")); + } + nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget); + sFocusedIMEWidget = nullptr; + sRemoteHasFocus = false; + focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); + } + sRemoteHasFocus = aOriginIsRemote; + sFocusedIMEWidget = aWidget; + nsCOMPtr<nsIWidget> widget(aWidget); + return widget->NotifyIME(aNotification); + } + case NOTIFY_IME_OF_BLUR: { + if (!sRemoteHasFocus && aOriginIsRemote) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received blur notification " + "after another one has focus, nothing to do...")); + return NS_OK; + } + if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received blur " + "notification from this process but the remote has focus")); + return NS_OK; + } + if (!sFocusedIMEWidget && aOriginIsRemote) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received blur notification " + "but the remote has already lost focus")); + return NS_OK; + } + if (NS_WARN_IF(!sFocusedIMEWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received blur " + "notification but there is no focused IME widget")); + return NS_OK; + } + if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received blur " + "notification but there is no focused IME widget")); + return NS_OK; + } + nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget); + sFocusedIMEWidget = nullptr; + sRemoteHasFocus = false; + return focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); + } + case NOTIFY_IME_OF_SELECTION_CHANGE: + case NOTIFY_IME_OF_TEXT_CHANGE: + case NOTIFY_IME_OF_POSITION_CHANGE: + case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: { + if (!sRemoteHasFocus && aOriginIsRemote) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received content change " + "notification from the remote but it's already lost focus")); + return NS_OK; + } + if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received content " + "change notification from this process but the remote has already " + "gotten focus")); + return NS_OK; + } + if (!sFocusedIMEWidget) { + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), received content change " + "notification but there is no focused IME widget")); + return NS_OK; + } + if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED, received content " + "change notification for IME which has already lost focus, so, " + "nothing to do...")); + return NS_OK; + } + nsCOMPtr<nsIWidget> widget(aWidget); + return widget->NotifyIME(aNotification); + } + default: + // Other notifications should be sent only when there is composition. + // So, we need to handle the others below. + break; + } + + RefPtr<TextComposition> composition; + if (sTextCompositions) { + composition = sTextCompositions->GetCompositionFor(aWidget); + } + + bool isSynthesizedForTests = + composition && composition->IsSynthesizedForTests(); + + MOZ_LOG(sISMLog, LogLevel::Info, + (" NotifyIME(), composition=0x%p, " + "composition->IsSynthesizedForTests()=%s", + composition.get(), GetBoolName(isSynthesizedForTests))); + + switch (aNotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: + return composition ? + composition->RequestToCommit(aWidget, false) : NS_OK; + case REQUEST_TO_CANCEL_COMPOSITION: + return composition ? + composition->RequestToCommit(aWidget, true) : NS_OK; + default: + MOZ_CRASH("Unsupported notification"); + } + MOZ_CRASH( + "Failed to handle the notification for non-synthesized composition"); + return NS_ERROR_FAILURE; +} + +// static +nsresult +IMEStateManager::NotifyIME(IMEMessage aMessage, + nsPresContext* aPresContext, + bool aOriginIsRemote) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("NotifyIME(aMessage=%s, aPresContext=0x%p, aOriginIsRemote=%s)", + ToChar(aMessage), aPresContext, GetBoolName(aOriginIsRemote))); + + if (NS_WARN_IF(!CanHandleWith(aPresContext))) { + return NS_ERROR_INVALID_ARG; + } + + nsIWidget* widget = aPresContext->GetRootWidget(); + if (NS_WARN_IF(!widget)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" NotifyIME(), FAILED due to no widget for the " + "nsPresContext")); + return NS_ERROR_NOT_AVAILABLE; + } + return NotifyIME(aMessage, widget, aOriginIsRemote); +} + +// static +bool +IMEStateManager::IsEditable(nsINode* node) +{ + if (node->IsEditable()) { + return true; + } + // |node| might be readwrite (for example, a text control) + if (node->IsElement() && + node->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { + return true; + } + return false; +} + +// static +nsINode* +IMEStateManager::GetRootEditableNode(nsPresContext* aPresContext, + nsIContent* aContent) +{ + if (aContent) { + nsINode* root = nullptr; + nsINode* node = aContent; + while (node && IsEditable(node)) { + // If the node has independent selection like <input type="text"> or + // <textarea>, the node should be the root editable node for aContent. + // FYI: <select> element also has independent selection but IsEditable() + // returns false. + // XXX: If somebody adds new editable element which has independent + // selection but doesn't own editor, we'll need more checks here. + if (node->IsContent() && + node->AsContent()->HasIndependentSelection()) { + return node; + } + root = node; + node = node->GetParentNode(); + } + return root; + } + if (aPresContext) { + nsIDocument* document = aPresContext->Document(); + if (document && document->IsEditable()) { + return document; + } + } + return nullptr; +} + +// static +bool +IMEStateManager::IsIMEObserverNeeded(const IMEState& aState) +{ + return aState.MaybeEditable(); +} + +// static +void +IMEStateManager::DestroyIMEContentObserver() +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("DestroyIMEContentObserver(), sActiveIMEContentObserver=0x%p", + sActiveIMEContentObserver.get())); + + if (!sActiveIMEContentObserver) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" DestroyIMEContentObserver() does nothing")); + return; + } + + MOZ_LOG(sISMLog, LogLevel::Debug, + (" DestroyIMEContentObserver(), destroying " + "the active IMEContentObserver...")); + RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get(); + sActiveIMEContentObserver = nullptr; + tsm->Destroy(); +} + +// static +void +IMEStateManager::CreateIMEContentObserver(nsIEditor* aEditor) +{ + MOZ_LOG(sISMLog, LogLevel::Info, + ("CreateIMEContentObserver(aEditor=0x%p), " + "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), " + "sActiveIMEContentObserver=0x%p, " + "sActiveIMEContentObserver->IsManaging(sPresContext, sContent)=%s", + aEditor, sPresContext.get(), sContent.get(), + sWidget, GetBoolName(sWidget && !sWidget->Destroyed()), + sActiveIMEContentObserver.get(), + GetBoolName(sActiveIMEContentObserver ? + sActiveIMEContentObserver->IsManaging(sPresContext, sContent) : false))); + + if (NS_WARN_IF(sActiveIMEContentObserver)) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" CreateIMEContentObserver(), FAILED due to " + "there is already an active IMEContentObserver")); + MOZ_ASSERT(sActiveIMEContentObserver->IsManaging(sPresContext, sContent)); + return; + } + + if (!sWidget || NS_WARN_IF(sWidget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" CreateIMEContentObserver(), FAILED due to " + "the widget for the nsPresContext has gone")); + return; // Sometimes, there are no widgets. + } + + nsCOMPtr<nsIWidget> widget(sWidget); + + // If it's not text editable, we don't need to create IMEContentObserver. + if (!IsIMEObserverNeeded(widget->GetInputContext().mIMEState)) { + MOZ_LOG(sISMLog, LogLevel::Debug, + (" CreateIMEContentObserver() doesn't create " + "IMEContentObserver because of non-editable IME state")); + return; + } + + if (NS_WARN_IF(widget->Destroyed())) { + MOZ_LOG(sISMLog, LogLevel::Error, + (" CreateIMEContentObserver(), FAILED due to " + "the widget for the nsPresContext has gone")); + return; + } + + MOZ_ASSERT(sPresContext->GetRootWidget() == widget); + + MOZ_LOG(sISMLog, LogLevel::Debug, + (" CreateIMEContentObserver() is creating an " + "IMEContentObserver instance...")); + sActiveIMEContentObserver = new IMEContentObserver(); + + // IMEContentObserver::Init() might create another IMEContentObserver + // instance. So, sActiveIMEContentObserver would be replaced with new one. + // We should hold the current instance here. + RefPtr<IMEContentObserver> activeIMEContentObserver(sActiveIMEContentObserver); + activeIMEContentObserver->Init(widget, sPresContext, sContent, aEditor); +} + +// static +nsresult +IMEStateManager::GetFocusSelectionAndRoot(nsISelection** aSelection, + nsIContent** aRootContent) +{ + if (!sActiveIMEContentObserver) { + return NS_ERROR_NOT_AVAILABLE; + } + return sActiveIMEContentObserver->GetSelectionAndRoot(aSelection, + aRootContent); +} + +// static +already_AddRefed<TextComposition> +IMEStateManager::GetTextCompositionFor(nsIWidget* aWidget) +{ + if (!sTextCompositions) { + return nullptr; + } + RefPtr<TextComposition> textComposition = + sTextCompositions->GetCompositionFor(aWidget); + return textComposition.forget(); +} + +// static +already_AddRefed<TextComposition> +IMEStateManager::GetTextCompositionFor( + const WidgetCompositionEvent* aCompositionEvent) +{ + if (!sTextCompositions) { + return nullptr; + } + RefPtr<TextComposition> textComposition = + sTextCompositions->GetCompositionFor(aCompositionEvent); + return textComposition.forget(); +} + +// static +already_AddRefed<TextComposition> +IMEStateManager::GetTextCompositionFor(nsPresContext* aPresContext) +{ + if (!sTextCompositions) { + return nullptr; + } + RefPtr<TextComposition> textComposition = + sTextCompositions->GetCompositionFor(aPresContext); + return textComposition.forget(); +} + +} // namespace mozilla diff --git a/dom/events/IMEStateManager.h b/dom/events/IMEStateManager.h new file mode 100644 index 000000000..7dfc48aa5 --- /dev/null +++ b/dom/events/IMEStateManager.h @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_IMEStateManager_h_ +#define mozilla_IMEStateManager_h_ + +#include "mozilla/EventForwards.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/TabParent.h" +#include "nsIWidget.h" + +class nsIContent; +class nsIDOMMouseEvent; +class nsINode; +class nsPresContext; +class nsISelection; + +namespace mozilla { + +class EditorBase; +class EventDispatchingCallback; +class IMEContentObserver; +class TextCompositionArray; +class TextComposition; + +/** + * IMEStateManager manages InputContext (e.g., active editor type, IME enabled + * state and IME open state) of nsIWidget instances, manages IMEContentObserver + * and provides useful API for IME. + */ + +class IMEStateManager +{ + typedef dom::TabParent TabParent; + typedef widget::IMEMessage IMEMessage; + typedef widget::IMENotification IMENotification; + typedef widget::IMEState IMEState; + typedef widget::InputContext InputContext; + typedef widget::InputContextAction InputContextAction; + +public: + static void Init(); + static void Shutdown(); + + /** + * GetActiveTabParent() returns a pointer to a TabParent instance which is + * managed by the focused content (sContent). If the focused content isn't + * managing another process, this returns nullptr. + */ + static TabParent* GetActiveTabParent() + { + // If menu has pseudo focus, we should ignore active child process. + if (sInstalledMenuKeyboardListener) { + return nullptr; + } + return sActiveTabParent.get(); + } + + /** + * OnTabParentDestroying() is called when aTabParent is being destroyed. + */ + static void OnTabParentDestroying(TabParent* aTabParent); + + /** + * Called when aWidget is being deleted. + */ + static void WidgetDestroyed(nsIWidget* aWidget); + + /** + * GetWidgetForActiveInputContext() returns a widget which IMEStateManager + * is managing input context with. If a widget instance needs to cache + * the last input context for nsIWidget::GetInputContext() or something, + * it should check if its cache is valid with this method before using it + * because if this method returns another instance, it means that + * IMEStateManager may have already changed shared input context via the + * widget. + */ + static nsIWidget* GetWidgetForActiveInputContext() + { + return sActiveInputContextWidget; + } + + /** + * SetIMEContextForChildProcess() is called when aTabParent receives + * SetInputContext() from the remote process. + */ + static void SetInputContextForChildProcess(TabParent* aTabParent, + const InputContext& aInputContext, + const InputContextAction& aAction); + + /** + * StopIMEStateManagement() is called when the process should stop managing + * IME state. + */ + static void StopIMEStateManagement(); + + /** + * MaybeStartOffsetUpdatedInChild() is called when composition start offset + * is maybe updated in the child process. I.e., even if it's not updated, + * this is called and never called if the composition is in this process. + * @param aWidget The widget whose native IME context has the + * composition. + * @param aStartOffset New composition start offset with native + * linebreaks. + */ + static void MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget, + uint32_t aStartOffset); + + static nsresult OnDestroyPresContext(nsPresContext* aPresContext); + static nsresult OnRemoveContent(nsPresContext* aPresContext, + nsIContent* aContent); + /** + * OnChangeFocus() should be called when focused content is changed or + * IME enabled state is changed. If nobody has focus, set both aPresContext + * and aContent nullptr. E.g., all windows are deactivated. + */ + static nsresult OnChangeFocus(nsPresContext* aPresContext, + nsIContent* aContent, + InputContextAction::Cause aCause); + static void OnInstalledMenuKeyboardListener(bool aInstalling); + + // These two methods manage focus and selection/text observers. + // They are separate from OnChangeFocus above because this offers finer + // control compared to having the two methods incorporated into OnChangeFocus + + // Get the focused editor's selection and root + static nsresult GetFocusSelectionAndRoot(nsISelection** aSel, + nsIContent** aRoot); + // This method updates the current IME state. However, if the enabled state + // isn't changed by the new state, this method does nothing. + // Note that this method changes the IME state of the active element in the + // widget. So, the caller must have focus. + static void UpdateIMEState(const IMEState &aNewIMEState, + nsIContent* aContent, + EditorBase& aEditorBase); + + // This method is called when user operates mouse button in focused editor + // and before the editor handles it. + // Returns true if IME consumes the event. Otherwise, false. + static bool OnMouseButtonEventInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIDOMMouseEvent* aMouseEvent); + + // This method is called when user clicked in an editor. + // aContent must be: + // If the editor is for <input> or <textarea>, the element. + // If the editor is for contenteditable, the active editinghost. + // If the editor is for designMode, nullptr. + static void OnClickInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIDOMMouseEvent* aMouseEvent); + + // This method is called when editor actually gets focus. + // aContent must be: + // If the editor is for <input> or <textarea>, the element. + // If the editor is for contenteditable, the active editinghost. + // If the editor is for designMode, nullptr. + static void OnFocusInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIEditor* aEditor); + + // This method is called when the editor is initialized. + static void OnEditorInitialized(nsIEditor* aEditor); + + // This method is called when the editor is (might be temporarily) being + // destroyed. + static void OnEditorDestroying(nsIEditor* aEditor); + + /** + * All composition events must be dispatched via DispatchCompositionEvent() + * for storing the composition target and ensuring a set of composition + * events must be fired the stored target. If the stored composition event + * target is destroying, this removes the stored composition automatically. + */ + static void DispatchCompositionEvent( + nsINode* aEventTargetNode, + nsPresContext* aPresContext, + WidgetCompositionEvent* aCompositionEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack, + bool aIsSynthesized = false); + + /** + * All selection events must be handled via HandleSelectionEvent() + * because they must be handled by same target as composition events when + * there is a composition. + */ + static void HandleSelectionEvent(nsPresContext* aPresContext, + nsIContent* aEventTargetContent, + WidgetSelectionEvent* aSelectionEvent); + + /** + * This is called when PresShell ignores a composition event due to not safe + * to dispatch events. + */ + static void OnCompositionEventDiscarded( + WidgetCompositionEvent* aCompositionEvent); + + /** + * Get TextComposition from widget. + */ + static already_AddRefed<TextComposition> + GetTextCompositionFor(nsIWidget* aWidget); + + /** + * Returns TextComposition instance for the event. + */ + static already_AddRefed<TextComposition> + GetTextCompositionFor(const WidgetCompositionEvent* aCompositionEvent); + + /** + * Returns TextComposition instance for the pres context. + * Be aware, even if another pres context which shares native IME context with + * specified pres context has composition, this returns nullptr. + */ + static already_AddRefed<TextComposition> + GetTextCompositionFor(nsPresContext* aPresContext); + + /** + * Send a notification to IME. It depends on the IME or platform spec what + * will occur (or not occur). + */ + static nsresult NotifyIME(const IMENotification& aNotification, + nsIWidget* aWidget, + bool aOriginIsRemote = false); + static nsresult NotifyIME(IMEMessage aMessage, + nsIWidget* aWidget, + bool aOriginIsRemote = false); + static nsresult NotifyIME(IMEMessage aMessage, + nsPresContext* aPresContext, + bool aOriginIsRemote = false); + + static nsINode* GetRootEditableNode(nsPresContext* aPresContext, + nsIContent* aContent); + + /** + * Returns active IMEContentObserver but may be nullptr if focused content + * isn't editable or focus in a remote process. + */ + static IMEContentObserver* GetActiveContentObserver(); + +protected: + static nsresult OnChangeFocusInternal(nsPresContext* aPresContext, + nsIContent* aContent, + InputContextAction aAction); + static void SetIMEState(const IMEState &aState, + nsIContent* aContent, + nsIWidget* aWidget, + InputContextAction aAction); + static void SetInputContext(nsIWidget* aWidget, + const InputContext& aInputContext, + const InputContextAction& aAction); + static IMEState GetNewIMEState(nsPresContext* aPresContext, + nsIContent* aContent); + + static void EnsureTextCompositionArray(); + static void CreateIMEContentObserver(nsIEditor* aEditor); + static void DestroyIMEContentObserver(); + + static bool IsEditable(nsINode* node); + + static bool IsIMEObserverNeeded(const IMEState& aState); + + static nsIContent* GetRootContent(nsPresContext* aPresContext); + + /** + * CanHandleWith() returns false if aPresContext is nullptr or it's destroyed. + */ + static bool CanHandleWith(nsPresContext* aPresContext); + + // sContent and sPresContext are the focused content and PresContext. If a + // document has focus but there is no focused element, sContent may be + // nullptr. + static StaticRefPtr<nsIContent> sContent; + static StaticRefPtr<nsPresContext> sPresContext; + // sWidget is cache for the root widget of sPresContext. Even afer + // sPresContext has gone, we need to clean up some IME state on the widget + // if the widget is available. + static nsIWidget* sWidget; + // sFocusedIMEWidget is, the widget which was sent to "focus" notification + // from IMEContentObserver and not yet sent "blur" notification. + // So, if this is not nullptr, the widget needs to receive "blur" + // notification. + static nsIWidget* sFocusedIMEWidget; + // sActiveInputContextWidget is the last widget whose SetInputContext() is + // called. This is important to reduce sync IPC cost with parent process. + // If IMEStateManager set input context to different widget, PuppetWidget can + // return cached input context safely. + static nsIWidget* sActiveInputContextWidget; + static StaticRefPtr<TabParent> sActiveTabParent; + // sActiveIMEContentObserver points to the currently active + // IMEContentObserver. This is null if there is no focused editor. + static StaticRefPtr<IMEContentObserver> sActiveIMEContentObserver; + + // All active compositions in the process are stored by this array. + // When you get an item of this array and use it, please be careful. + // The instances in this array can be destroyed automatically if you do + // something to cause committing or canceling the composition. + static TextCompositionArray* sTextCompositions; + + static bool sInstalledMenuKeyboardListener; + static bool sIsGettingNewIMEState; + static bool sCheckForIMEUnawareWebApps; + static bool sRemoteHasFocus; + + class MOZ_STACK_CLASS GettingNewIMEStateBlocker final + { + public: + GettingNewIMEStateBlocker() + : mOldValue(IMEStateManager::sIsGettingNewIMEState) + { + IMEStateManager::sIsGettingNewIMEState = true; + } + ~GettingNewIMEStateBlocker() + { + IMEStateManager::sIsGettingNewIMEState = mOldValue; + } + private: + bool mOldValue; + }; +}; + +} // namespace mozilla + +#endif // mozilla_IMEStateManager_h_ diff --git a/dom/events/ImageCaptureError.cpp b/dom/events/ImageCaptureError.cpp new file mode 100644 index 000000000..c68adfa85 --- /dev/null +++ b/dom/events/ImageCaptureError.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ImageCaptureError.h" +#include "mozilla/dom/ImageCaptureErrorEventBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageCaptureError, mParent) +NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageCaptureError) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageCaptureError) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageCaptureError) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +ImageCaptureError::ImageCaptureError(nsISupports* aParent, + uint16_t aCode, + const nsAString& aMessage) + : mParent(aParent) + , mMessage(aMessage) + , mCode(aCode) +{ +} + +ImageCaptureError::~ImageCaptureError() +{ +} + +nsISupports* +ImageCaptureError::GetParentObject() const +{ + return mParent; +} + +JSObject* +ImageCaptureError::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return ImageCaptureErrorBinding::Wrap(aCx, this, aGivenProto); +} + +uint16_t +ImageCaptureError::Code() const +{ + return mCode; +} + +void +ImageCaptureError::GetMessage(nsAString& retval) const +{ + retval = mMessage; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/ImageCaptureError.h b/dom/events/ImageCaptureError.h new file mode 100644 index 000000000..fc8a97417 --- /dev/null +++ b/dom/events/ImageCaptureError.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ImageCaptureError_h +#define mozilla_dom_ImageCaptureError_h + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +/** + * This is the implementation of ImageCaptureError on W3C specification + * https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/ImageCapture.html#idl-def-ImageCaptureError. + * This object should be generated by ImageCapture object only. + */ +class ImageCaptureError final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ImageCaptureError) + + ImageCaptureError(nsISupports* aParent, uint16_t aCode, const nsAString& aMessage); + + nsISupports* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + uint16_t Code() const; + + enum { + FRAME_GRAB_ERROR = 1, + SETTINGS_ERROR = 2, + PHOTO_ERROR = 3, + ERROR_UNKNOWN = 4, + }; + + void GetMessage(nsAString& retval) const; + +private: + ~ImageCaptureError(); + + nsCOMPtr<nsISupports> mParent; + nsString mMessage; + uint16_t mCode; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ImageCaptureError_h diff --git a/dom/events/InputEvent.cpp b/dom/events/InputEvent.cpp new file mode 100644 index 000000000..d230ace39 --- /dev/null +++ b/dom/events/InputEvent.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/InputEvent.h" +#include "mozilla/TextEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +InputEvent::InputEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalEditorInputEvent* aEvent) + : UIEvent(aOwner, aPresContext, + aEvent ? aEvent : + new InternalEditorInputEvent(false, eVoidEvent, nullptr)) +{ + NS_ASSERTION(mEvent->mClass == eEditorInputEventClass, + "event type mismatch"); + + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +NS_IMPL_ADDREF_INHERITED(InputEvent, UIEvent) +NS_IMPL_RELEASE_INHERITED(InputEvent, UIEvent) + +NS_INTERFACE_MAP_BEGIN(InputEvent) +NS_INTERFACE_MAP_END_INHERITING(UIEvent) + +bool +InputEvent::IsComposing() +{ + return mEvent->AsEditorInputEvent()->mIsComposing; +} + +already_AddRefed<InputEvent> +InputEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const InputEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<InputEvent> e = new InputEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + auto* view = aParam.mView ? aParam.mView->AsInner() : nullptr; + e->InitUIEvent(aType, aParam.mBubbles, aParam.mCancelable, view, + aParam.mDetail); + InternalEditorInputEvent* internalEvent = e->mEvent->AsEditorInputEvent(); + internalEvent->mIsComposing = aParam.mIsComposing; + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<InputEvent> +NS_NewDOMInputEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalEditorInputEvent* aEvent) +{ + RefPtr<InputEvent> it = new InputEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/InputEvent.h b/dom/events/InputEvent.h new file mode 100644 index 000000000..3e0e9361d --- /dev/null +++ b/dom/events/InputEvent.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_InputEvent_h_ +#define mozilla_dom_InputEvent_h_ + +#include "mozilla/dom/UIEvent.h" +#include "mozilla/dom/InputEventBinding.h" +#include "mozilla/EventForwards.h" + +namespace mozilla { +namespace dom { + +class InputEvent : public UIEvent +{ +public: + InputEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalEditorInputEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + // Forward to base class + NS_FORWARD_TO_UIEVENT + + + static already_AddRefed<InputEvent> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const InputEventInit& aParam, + ErrorResult& aRv); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return InputEventBinding::Wrap(aCx, this, aGivenProto); + } + + bool IsComposing(); + +protected: + ~InputEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::InputEvent> +NS_NewDOMInputEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalEditorInputEvent* aEvent); + +#endif // mozilla_dom_InputEvent_h_ diff --git a/dom/events/InternalMutationEvent.h b/dom/events/InternalMutationEvent.h new file mode 100644 index 000000000..d600ebe0e --- /dev/null +++ b/dom/events/InternalMutationEvent.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_MutationEvent_h__ +#define mozilla_MutationEvent_h__ + +#include "mozilla/BasicEvents.h" +#include "nsCOMPtr.h" +#include "nsIAtom.h" +#include "nsIDOMNode.h" + +namespace mozilla { + +class InternalMutationEvent : public WidgetEvent +{ +public: + virtual InternalMutationEvent* AsMutationEvent() override { return this; } + + InternalMutationEvent(bool aIsTrusted, EventMessage aMessage) + : WidgetEvent(aIsTrusted, aMessage, eMutationEventClass) + , mAttrChange(0) + { + mFlags.mCancelable = false; + } + + virtual WidgetEvent* Duplicate() const override + { + MOZ_ASSERT(mClass == eMutationEventClass, + "Duplicate() must be overridden by sub class"); + InternalMutationEvent* result = new InternalMutationEvent(false, mMessage); + result->AssignMutationEventData(*this, true); + result->mFlags = mFlags; + return result; + } + + nsCOMPtr<nsIDOMNode> mRelatedNode; + nsCOMPtr<nsIAtom> mAttrName; + nsCOMPtr<nsIAtom> mPrevAttrValue; + nsCOMPtr<nsIAtom> mNewAttrValue; + unsigned short mAttrChange; + + void AssignMutationEventData(const InternalMutationEvent& aEvent, + bool aCopyTargets) + { + AssignEventData(aEvent, aCopyTargets); + + mRelatedNode = aEvent.mRelatedNode; + mAttrName = aEvent.mAttrName; + mPrevAttrValue = aEvent.mPrevAttrValue; + mNewAttrValue = aEvent.mNewAttrValue; + mAttrChange = aEvent.mAttrChange; + } +}; + +// Bits are actually checked to optimize mutation event firing. +// That's why I don't number from 0x00. The first event should +// always be 0x01. +#define NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED 0x01 +#define NS_EVENT_BITS_MUTATION_NODEINSERTED 0x02 +#define NS_EVENT_BITS_MUTATION_NODEREMOVED 0x04 +#define NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT 0x08 +#define NS_EVENT_BITS_MUTATION_NODEINSERTEDINTODOCUMENT 0x10 +#define NS_EVENT_BITS_MUTATION_ATTRMODIFIED 0x20 +#define NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED 0x40 + +} // namespace mozilla + +#endif // mozilla_MutationEvent_h__ diff --git a/dom/events/JSEventHandler.cpp b/dom/events/JSEventHandler.cpp new file mode 100644 index 000000000..4b1b33313 --- /dev/null +++ b/dom/events/JSEventHandler.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsJSUtils.h" +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScriptContext.h" +#include "nsIScriptGlobalObject.h" +#include "nsIXPConnect.h" +#include "nsIMutableArray.h" +#include "nsVariant.h" +#include "nsIDOMBeforeUnloadEvent.h" +#include "nsGkAtoms.h" +#include "xpcpublic.h" +#include "nsJSEnvironment.h" +#include "nsDOMJSUtils.h" +#include "WorkerPrivate.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/JSEventHandler.h" +#include "mozilla/Likely.h" +#include "mozilla/dom/ErrorEvent.h" + +namespace mozilla { + +using namespace dom; + +JSEventHandler::JSEventHandler(nsISupports* aTarget, + nsIAtom* aType, + const TypedEventHandler& aTypedHandler) + : mEventName(aType) + , mTypedHandler(aTypedHandler) +{ + nsCOMPtr<nsISupports> base = do_QueryInterface(aTarget); + mTarget = base.get(); + // Note, we call HoldJSObjects to get CanSkip called before CC. + HoldJSObjects(this); +} + +JSEventHandler::~JSEventHandler() +{ + NS_ASSERTION(!mTarget, "Should have called Disconnect()!"); + DropJSObjects(this); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(JSEventHandler) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSEventHandler) + tmp->mTypedHandler.ForgetHandler(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(JSEventHandler) + if (MOZ_UNLIKELY(cb.WantDebugInfo()) && tmp->mEventName) { + nsAutoCString name; + name.AppendLiteral("JSEventHandler handlerName="); + name.Append( + NS_ConvertUTF16toUTF8(nsDependentAtomString(tmp->mEventName)).get()); + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get()); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(JSEventHandler, tmp->mRefCnt.get()) + } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mTypedHandler.Ptr()) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(JSEventHandler) + if (tmp->IsBlackForCC()) { + return true; + } + // If we have a target, it is the one which has tmp as onfoo handler. + if (tmp->mTarget) { + nsXPCOMCycleCollectionParticipant* cp = nullptr; + CallQueryInterface(tmp->mTarget, &cp); + nsISupports* canonical = nullptr; + tmp->mTarget->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast<void**>(&canonical)); + // Usually CanSkip ends up unmarking the event listeners of mTarget, + // so tmp may become black. + if (cp && canonical && cp->CanSkip(canonical, true)) { + return tmp->IsBlackForCC(); + } + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(JSEventHandler) + return tmp->IsBlackForCC(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(JSEventHandler) + return tmp->IsBlackForCC(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSEventHandler) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(JSEventHandler) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(JSEventHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JSEventHandler) + +bool +JSEventHandler::IsBlackForCC() +{ + // We can claim to be black if all the things we reference are + // effectively black already. + return !mTypedHandler.HasEventHandler() || + !mTypedHandler.Ptr()->HasGrayCallable(); +} + +nsresult +JSEventHandler::HandleEvent(nsIDOMEvent* aEvent) +{ + nsCOMPtr<EventTarget> target = do_QueryInterface(mTarget); + if (!target || !mTypedHandler.HasEventHandler() || + !GetTypedEventHandler().Ptr()->CallbackPreserveColor()) { + return NS_ERROR_FAILURE; + } + + Event* event = aEvent->InternalDOMEvent(); + bool isMainThread = event->IsMainThreadEvent(); + bool isChromeHandler = + isMainThread ? + nsContentUtils::ObjectPrincipal( + GetTypedEventHandler().Ptr()->CallbackPreserveColor()) == + nsContentUtils::GetSystemPrincipal() : + mozilla::dom::workers::IsCurrentThreadRunningChromeWorker(); + + if (mTypedHandler.Type() == TypedEventHandler::eOnError) { + MOZ_ASSERT_IF(mEventName, mEventName == nsGkAtoms::onerror); + + nsString errorMsg, file; + EventOrString msgOrEvent; + Optional<nsAString> fileName; + Optional<uint32_t> lineNumber; + Optional<uint32_t> columnNumber; + Optional<JS::Handle<JS::Value>> error; + + NS_ENSURE_TRUE(aEvent, NS_ERROR_UNEXPECTED); + ErrorEvent* scriptEvent = aEvent->InternalDOMEvent()->AsErrorEvent(); + if (scriptEvent) { + scriptEvent->GetMessage(errorMsg); + msgOrEvent.SetAsString().ShareOrDependUpon(errorMsg); + + scriptEvent->GetFilename(file); + fileName = &file; + + lineNumber.Construct(); + lineNumber.Value() = scriptEvent->Lineno(); + + columnNumber.Construct(); + columnNumber.Value() = scriptEvent->Colno(); + + error.Construct(RootingCx()); + scriptEvent->GetError(&error.Value()); + } else { + msgOrEvent.SetAsEvent() = aEvent->InternalDOMEvent(); + } + + RefPtr<OnErrorEventHandlerNonNull> handler = + mTypedHandler.OnErrorEventHandler(); + ErrorResult rv; + bool handled = handler->Call(mTarget, msgOrEvent, fileName, lineNumber, + columnNumber, error, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + if (handled) { + event->PreventDefaultInternal(isChromeHandler); + } + return NS_OK; + } + + if (mTypedHandler.Type() == TypedEventHandler::eOnBeforeUnload) { + MOZ_ASSERT(mEventName == nsGkAtoms::onbeforeunload); + + RefPtr<OnBeforeUnloadEventHandlerNonNull> handler = + mTypedHandler.OnBeforeUnloadEventHandler(); + ErrorResult rv; + nsString retval; + handler->Call(mTarget, *(aEvent->InternalDOMEvent()), retval, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + nsCOMPtr<nsIDOMBeforeUnloadEvent> beforeUnload = do_QueryInterface(aEvent); + NS_ENSURE_STATE(beforeUnload); + + if (!DOMStringIsNull(retval)) { + event->PreventDefaultInternal(isChromeHandler); + + nsAutoString text; + beforeUnload->GetReturnValue(text); + + // Set the text in the beforeUnload event as long as it wasn't + // already set (through event.returnValue, which takes + // precedence over a value returned from a JS function in IE) + if (text.IsEmpty()) { + beforeUnload->SetReturnValue(retval); + } + } + + return NS_OK; + } + + MOZ_ASSERT(mTypedHandler.Type() == TypedEventHandler::eNormal); + ErrorResult rv; + RefPtr<EventHandlerNonNull> handler = mTypedHandler.NormalEventHandler(); + JS::Rooted<JS::Value> retval(RootingCx()); + handler->Call(mTarget, *(aEvent->InternalDOMEvent()), &retval, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + // If the handler returned false and its sense is not reversed, + // or the handler returned true and its sense is reversed from + // the usual (false means cancel), then prevent default. + if (retval.isBoolean() && + retval.toBoolean() == (mEventName == nsGkAtoms::onerror || + mEventName == nsGkAtoms::onmouseover)) { + event->PreventDefaultInternal(isChromeHandler); + } + + return NS_OK; +} + +} // namespace mozilla + +using namespace mozilla; + +/* + * Factory functions + */ + +nsresult +NS_NewJSEventHandler(nsISupports* aTarget, + nsIAtom* aEventType, + const TypedEventHandler& aTypedHandler, + JSEventHandler** aReturn) +{ + NS_ENSURE_ARG(aEventType || !NS_IsMainThread()); + JSEventHandler* it = + new JSEventHandler(aTarget, aEventType, aTypedHandler); + NS_ADDREF(*aReturn = it); + + return NS_OK; +} diff --git a/dom/events/JSEventHandler.h b/dom/events/JSEventHandler.h new file mode 100644 index 000000000..be2ecf9a4 --- /dev/null +++ b/dom/events/JSEventHandler.h @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_JSEventHandler_h_ +#define mozilla_JSEventHandler_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/EventHandlerBinding.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIAtom.h" +#include "nsIDOMKeyEvent.h" +#include "nsIDOMEventListener.h" +#include "nsIScriptContext.h" + +namespace mozilla { + +class TypedEventHandler +{ +public: + enum HandlerType + { + eUnset = 0, + eNormal = 0x1, + eOnError = 0x2, + eOnBeforeUnload = 0x3, + eTypeBits = 0x3 + }; + + TypedEventHandler() + : mBits(0) + { + } + + explicit TypedEventHandler(dom::EventHandlerNonNull* aHandler) + : mBits(0) + { + Assign(aHandler, eNormal); + } + + explicit TypedEventHandler(dom::OnErrorEventHandlerNonNull* aHandler) + : mBits(0) + { + Assign(aHandler, eOnError); + } + + explicit TypedEventHandler(dom::OnBeforeUnloadEventHandlerNonNull* aHandler) + : mBits(0) + { + Assign(aHandler, eOnBeforeUnload); + } + + TypedEventHandler(const TypedEventHandler& aOther) + { + if (aOther.HasEventHandler()) { + // Have to make sure we take our own ref + Assign(aOther.Ptr(), aOther.Type()); + } else { + mBits = 0; + } + } + + ~TypedEventHandler() + { + ReleaseHandler(); + } + + HandlerType Type() const + { + return HandlerType(mBits & eTypeBits); + } + + bool HasEventHandler() const + { + return !!Ptr(); + } + + void SetHandler(const TypedEventHandler& aHandler) + { + if (aHandler.HasEventHandler()) { + ReleaseHandler(); + Assign(aHandler.Ptr(), aHandler.Type()); + } else { + ForgetHandler(); + } + } + + dom::EventHandlerNonNull* NormalEventHandler() const + { + MOZ_ASSERT(Type() == eNormal && Ptr()); + return reinterpret_cast<dom::EventHandlerNonNull*>(Ptr()); + } + + void SetHandler(dom::EventHandlerNonNull* aHandler) + { + ReleaseHandler(); + Assign(aHandler, eNormal); + } + + dom::OnBeforeUnloadEventHandlerNonNull* OnBeforeUnloadEventHandler() const + { + MOZ_ASSERT(Type() == eOnBeforeUnload); + return reinterpret_cast<dom::OnBeforeUnloadEventHandlerNonNull*>(Ptr()); + } + + void SetHandler(dom::OnBeforeUnloadEventHandlerNonNull* aHandler) + { + ReleaseHandler(); + Assign(aHandler, eOnBeforeUnload); + } + + dom::OnErrorEventHandlerNonNull* OnErrorEventHandler() const + { + MOZ_ASSERT(Type() == eOnError); + return reinterpret_cast<dom::OnErrorEventHandlerNonNull*>(Ptr()); + } + + void SetHandler(dom::OnErrorEventHandlerNonNull* aHandler) + { + ReleaseHandler(); + Assign(aHandler, eOnError); + } + + dom::CallbackFunction* Ptr() const + { + // Have to cast eTypeBits so we don't have to worry about + // promotion issues after the bitflip. + return reinterpret_cast<dom::CallbackFunction*>(mBits & + ~uintptr_t(eTypeBits)); + } + + void ForgetHandler() + { + ReleaseHandler(); + mBits = 0; + } + + bool operator==(const TypedEventHandler& aOther) const + { + return + Ptr() && aOther.Ptr() && + Ptr()->CallbackPreserveColor() == aOther.Ptr()->CallbackPreserveColor(); + } + +private: + void operator=(const TypedEventHandler&) = delete; + + void ReleaseHandler() + { + nsISupports* ptr = Ptr(); + NS_IF_RELEASE(ptr); + } + + void Assign(nsISupports* aHandler, HandlerType aType) + { + MOZ_ASSERT(aHandler, "Must have handler"); + NS_ADDREF(aHandler); + mBits = uintptr_t(aHandler) | uintptr_t(aType); + } + + uintptr_t mBits; +}; + +/** + * Implemented by script event listeners. Used to retrieve the script object + * corresponding to the event target and the handler itself. + * + * Note, mTarget is a raw pointer and the owner of the JSEventHandler object + * is expected to call Disconnect()! + */ + +#define NS_JSEVENTHANDLER_IID \ +{ 0x4f486881, 0x1956, 0x4079, \ + { 0x8c, 0xa0, 0xf3, 0xbd, 0x60, 0x5c, 0xc2, 0x79 } } + +class JSEventHandler : public nsIDOMEventListener +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_JSEVENTHANDLER_IID) + + JSEventHandler(nsISupports* aTarget, nsIAtom* aType, + const TypedEventHandler& aTypedHandler); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + + // nsIDOMEventListener interface + NS_DECL_NSIDOMEVENTLISTENER + + nsISupports* GetEventTarget() const + { + return mTarget; + } + + void Disconnect() + { + mTarget = nullptr; + } + + const TypedEventHandler& GetTypedEventHandler() const + { + return mTypedHandler; + } + + void ForgetHandler() + { + mTypedHandler.ForgetHandler(); + } + + nsIAtom* EventName() const + { + return mEventName; + } + + // Set a handler for this event listener. The handler must already + // be bound to the right target. + void SetHandler(const TypedEventHandler& aTypedHandler) + { + mTypedHandler.SetHandler(aTypedHandler); + } + void SetHandler(dom::EventHandlerNonNull* aHandler) + { + mTypedHandler.SetHandler(aHandler); + } + void SetHandler(dom::OnBeforeUnloadEventHandlerNonNull* aHandler) + { + mTypedHandler.SetHandler(aHandler); + } + void SetHandler(dom::OnErrorEventHandlerNonNull* aHandler) + { + mTypedHandler.SetHandler(aHandler); + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + return 0; + + // Measurement of the following members may be added later if DMD finds it + // is worthwhile: + // - mTarget + // + // The following members are not measured: + // - mTypedHandler: may be shared with others + // - mEventName: shared with others + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS(JSEventHandler) + + bool IsBlackForCC(); + +protected: + virtual ~JSEventHandler(); + + nsISupports* mTarget; + nsCOMPtr<nsIAtom> mEventName; + TypedEventHandler mTypedHandler; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(JSEventHandler, NS_JSEVENTHANDLER_IID) + +} // namespace mozilla + +/** + * Factory function. aHandler must already be bound to aTarget. + * aContext is allowed to be null if aHandler is already set up. + */ +nsresult NS_NewJSEventHandler(nsISupports* aTarget, + nsIAtom* aType, + const mozilla::TypedEventHandler& aTypedHandler, + mozilla::JSEventHandler** aReturn); + +#endif // mozilla_JSEventHandler_h_ + diff --git a/dom/events/KeyNameList.h b/dom/events/KeyNameList.h new file mode 100644 index 000000000..35fbe2d5f --- /dev/null +++ b/dom/events/KeyNameList.h @@ -0,0 +1,442 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This header file defines all DOM key name which are used for DOM + * KeyboardEvent.key. + * You must define NS_DEFINE_KEYNAME macro before including this. + * + * It must have two arguments, (aCPPName, aDOMKeyName) + * aCPPName is usable name for a part of C++ constants. + * aDOMKeyName is the actual value. + */ + +#define DEFINE_KEYNAME_INTERNAL(aCPPName, aDOMKeyName) \ + NS_DEFINE_KEYNAME(aCPPName, aDOMKeyName) + +#define DEFINE_KEYNAME_WITH_SAME_NAME(aName) \ + DEFINE_KEYNAME_INTERNAL(aName, #aName) + +/****************************************************************************** + * Special Key Values + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(Unidentified) + +/****************************************************************************** + * Our Internal Key Values (must have "Moz" prefix) + *****************************************************************************/ +DEFINE_KEYNAME_INTERNAL(PrintableKey, "MozPrintableKey") +DEFINE_KEYNAME_INTERNAL(SoftLeft, "MozSoftLeft") +DEFINE_KEYNAME_INTERNAL(SoftRight, "MozSoftRight") + +#ifdef MOZ_B2G +DEFINE_KEYNAME_INTERNAL(HomeScreen, "MozHomeScreen") +#endif // #ifdef MOZ_B2G + +/****************************************************************************** + * Modifier Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(Alt) +DEFINE_KEYNAME_WITH_SAME_NAME(AltGraph) +DEFINE_KEYNAME_WITH_SAME_NAME(CapsLock) +DEFINE_KEYNAME_WITH_SAME_NAME(Control) +DEFINE_KEYNAME_WITH_SAME_NAME(Fn) +DEFINE_KEYNAME_WITH_SAME_NAME(FnLock) +DEFINE_KEYNAME_WITH_SAME_NAME(Hyper) +DEFINE_KEYNAME_WITH_SAME_NAME(Meta) +DEFINE_KEYNAME_WITH_SAME_NAME(NumLock) +DEFINE_KEYNAME_WITH_SAME_NAME(OS) // Dropped from the latest draft, bug 1232918 +DEFINE_KEYNAME_WITH_SAME_NAME(ScrollLock) +DEFINE_KEYNAME_WITH_SAME_NAME(Shift) +DEFINE_KEYNAME_WITH_SAME_NAME(Super) +DEFINE_KEYNAME_WITH_SAME_NAME(Symbol) +DEFINE_KEYNAME_WITH_SAME_NAME(SymbolLock) + +/****************************************************************************** + * Whitespace Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(Enter) +DEFINE_KEYNAME_WITH_SAME_NAME(Tab) + +/****************************************************************************** + * Navigation Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowDown) +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowLeft) +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowRight) +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowUp) +DEFINE_KEYNAME_WITH_SAME_NAME(End) +DEFINE_KEYNAME_WITH_SAME_NAME(Home) +DEFINE_KEYNAME_WITH_SAME_NAME(PageDown) +DEFINE_KEYNAME_WITH_SAME_NAME(PageUp) + +/****************************************************************************** + * Editing Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(Backspace) +DEFINE_KEYNAME_WITH_SAME_NAME(Clear) +DEFINE_KEYNAME_WITH_SAME_NAME(Copy) +DEFINE_KEYNAME_WITH_SAME_NAME(CrSel) +DEFINE_KEYNAME_WITH_SAME_NAME(Cut) +DEFINE_KEYNAME_WITH_SAME_NAME(Delete) +DEFINE_KEYNAME_WITH_SAME_NAME(EraseEof) +DEFINE_KEYNAME_WITH_SAME_NAME(ExSel) +DEFINE_KEYNAME_WITH_SAME_NAME(Insert) +DEFINE_KEYNAME_WITH_SAME_NAME(Paste) +DEFINE_KEYNAME_WITH_SAME_NAME(Redo) +DEFINE_KEYNAME_WITH_SAME_NAME(Undo) + +/****************************************************************************** + * UI Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(Accept) +DEFINE_KEYNAME_WITH_SAME_NAME(Again) +DEFINE_KEYNAME_WITH_SAME_NAME(Attn) +DEFINE_KEYNAME_WITH_SAME_NAME(Cancel) +DEFINE_KEYNAME_WITH_SAME_NAME(ContextMenu) +DEFINE_KEYNAME_WITH_SAME_NAME(Escape) +DEFINE_KEYNAME_WITH_SAME_NAME(Execute) +DEFINE_KEYNAME_WITH_SAME_NAME(Find) +DEFINE_KEYNAME_WITH_SAME_NAME(Help) +DEFINE_KEYNAME_WITH_SAME_NAME(Pause) +DEFINE_KEYNAME_WITH_SAME_NAME(Play) +DEFINE_KEYNAME_WITH_SAME_NAME(Props) +DEFINE_KEYNAME_WITH_SAME_NAME(Select) +DEFINE_KEYNAME_WITH_SAME_NAME(ZoomIn) +DEFINE_KEYNAME_WITH_SAME_NAME(ZoomOut) + +/****************************************************************************** + * Device Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(BrightnessDown) +DEFINE_KEYNAME_WITH_SAME_NAME(BrightnessUp) +DEFINE_KEYNAME_WITH_SAME_NAME(Eject) +DEFINE_KEYNAME_WITH_SAME_NAME(LogOff) +DEFINE_KEYNAME_WITH_SAME_NAME(Power) +DEFINE_KEYNAME_WITH_SAME_NAME(PowerOff) +DEFINE_KEYNAME_WITH_SAME_NAME(PrintScreen) +DEFINE_KEYNAME_WITH_SAME_NAME(Hibernate) +DEFINE_KEYNAME_WITH_SAME_NAME(Standby) +DEFINE_KEYNAME_WITH_SAME_NAME(WakeUp) + +/****************************************************************************** + * IME and Composition Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(AllCandidates) +DEFINE_KEYNAME_WITH_SAME_NAME(Alphanumeric) +DEFINE_KEYNAME_WITH_SAME_NAME(CodeInput) +DEFINE_KEYNAME_WITH_SAME_NAME(Compose) +DEFINE_KEYNAME_WITH_SAME_NAME(Convert) +DEFINE_KEYNAME_WITH_SAME_NAME(Dead) +DEFINE_KEYNAME_WITH_SAME_NAME(FinalMode) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupFirst) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupLast) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupNext) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupPrevious) +DEFINE_KEYNAME_WITH_SAME_NAME(ModeChange) +DEFINE_KEYNAME_WITH_SAME_NAME(NextCandidate) +DEFINE_KEYNAME_WITH_SAME_NAME(NonConvert) +DEFINE_KEYNAME_WITH_SAME_NAME(PreviousCandidate) +DEFINE_KEYNAME_WITH_SAME_NAME(Process) +DEFINE_KEYNAME_WITH_SAME_NAME(SingleCandidate) + +/****************************************************************************** + * Keys specific to Korean keyboards + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(HangulMode) +DEFINE_KEYNAME_WITH_SAME_NAME(HanjaMode) +DEFINE_KEYNAME_WITH_SAME_NAME(JunjaMode) + +/****************************************************************************** + * Keys specific to Japanese keyboards + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(Eisu) +DEFINE_KEYNAME_WITH_SAME_NAME(Hankaku) +DEFINE_KEYNAME_WITH_SAME_NAME(Hiragana) +DEFINE_KEYNAME_WITH_SAME_NAME(HiraganaKatakana) +DEFINE_KEYNAME_WITH_SAME_NAME(KanaMode) +DEFINE_KEYNAME_WITH_SAME_NAME(KanjiMode) +DEFINE_KEYNAME_WITH_SAME_NAME(Katakana) +DEFINE_KEYNAME_WITH_SAME_NAME(Romaji) +DEFINE_KEYNAME_WITH_SAME_NAME(Zenkaku) +DEFINE_KEYNAME_WITH_SAME_NAME(ZenkakuHankaku) + +/****************************************************************************** + * General-Purpose Function Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(F1) +DEFINE_KEYNAME_WITH_SAME_NAME(F2) +DEFINE_KEYNAME_WITH_SAME_NAME(F3) +DEFINE_KEYNAME_WITH_SAME_NAME(F4) +DEFINE_KEYNAME_WITH_SAME_NAME(F5) +DEFINE_KEYNAME_WITH_SAME_NAME(F6) +DEFINE_KEYNAME_WITH_SAME_NAME(F7) +DEFINE_KEYNAME_WITH_SAME_NAME(F8) +DEFINE_KEYNAME_WITH_SAME_NAME(F9) +DEFINE_KEYNAME_WITH_SAME_NAME(F10) +DEFINE_KEYNAME_WITH_SAME_NAME(F11) +DEFINE_KEYNAME_WITH_SAME_NAME(F12) +DEFINE_KEYNAME_WITH_SAME_NAME(F13) +DEFINE_KEYNAME_WITH_SAME_NAME(F14) +DEFINE_KEYNAME_WITH_SAME_NAME(F15) +DEFINE_KEYNAME_WITH_SAME_NAME(F16) +DEFINE_KEYNAME_WITH_SAME_NAME(F17) +DEFINE_KEYNAME_WITH_SAME_NAME(F18) +DEFINE_KEYNAME_WITH_SAME_NAME(F19) +DEFINE_KEYNAME_WITH_SAME_NAME(F20) +DEFINE_KEYNAME_WITH_SAME_NAME(F21) +DEFINE_KEYNAME_WITH_SAME_NAME(F22) +DEFINE_KEYNAME_WITH_SAME_NAME(F23) +DEFINE_KEYNAME_WITH_SAME_NAME(F24) +DEFINE_KEYNAME_WITH_SAME_NAME(F25) +DEFINE_KEYNAME_WITH_SAME_NAME(F26) +DEFINE_KEYNAME_WITH_SAME_NAME(F27) +DEFINE_KEYNAME_WITH_SAME_NAME(F28) +DEFINE_KEYNAME_WITH_SAME_NAME(F29) +DEFINE_KEYNAME_WITH_SAME_NAME(F30) +DEFINE_KEYNAME_WITH_SAME_NAME(F31) +DEFINE_KEYNAME_WITH_SAME_NAME(F32) +DEFINE_KEYNAME_WITH_SAME_NAME(F33) +DEFINE_KEYNAME_WITH_SAME_NAME(F34) +DEFINE_KEYNAME_WITH_SAME_NAME(F35) +DEFINE_KEYNAME_WITH_SAME_NAME(Soft1) +DEFINE_KEYNAME_WITH_SAME_NAME(Soft2) +DEFINE_KEYNAME_WITH_SAME_NAME(Soft3) +DEFINE_KEYNAME_WITH_SAME_NAME(Soft4) + +/****************************************************************************** + * Multimedia Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(ChannelDown) +DEFINE_KEYNAME_WITH_SAME_NAME(ChannelUp) +DEFINE_KEYNAME_WITH_SAME_NAME(Close) +DEFINE_KEYNAME_WITH_SAME_NAME(MailForward) +DEFINE_KEYNAME_WITH_SAME_NAME(MailReply) +DEFINE_KEYNAME_WITH_SAME_NAME(MailSend) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaPause) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaPlay) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaPlayPause) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaRecord) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaRewind) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaStop) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaTrackNext) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaTrackPrevious) +DEFINE_KEYNAME_WITH_SAME_NAME(New) +DEFINE_KEYNAME_WITH_SAME_NAME(Open) +DEFINE_KEYNAME_WITH_SAME_NAME(Print) +DEFINE_KEYNAME_WITH_SAME_NAME(Save) +DEFINE_KEYNAME_WITH_SAME_NAME(SpellCheck) + +/****************************************************************************** + * Multimedia Numpad Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(Key11) +DEFINE_KEYNAME_WITH_SAME_NAME(Key12) + +/****************************************************************************** + * Audio Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(AudioBalanceLeft) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioBalanceRight) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioBassBoostDown) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioBassBoostToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioBassBoostUp) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioFaderFront) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioFaderRear) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioSurroundModeNext) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioTrebleDown) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioTrebleUp) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioVolumeDown) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioVolumeUp) +DEFINE_KEYNAME_WITH_SAME_NAME(AudioVolumeMute) + +DEFINE_KEYNAME_WITH_SAME_NAME(MicrophoneToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(MicrophoneVolumeDown) +DEFINE_KEYNAME_WITH_SAME_NAME(MicrophoneVolumeUp) +DEFINE_KEYNAME_WITH_SAME_NAME(MicrophoneVolumeMute) + +/****************************************************************************** + * Speech Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(SpeechCorrectionList) +DEFINE_KEYNAME_WITH_SAME_NAME(SpeechInputToggle) + +/****************************************************************************** + * Application Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchCalculator) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchCalendar) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchContacts) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMail) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMediaPlayer) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMusicPlayer) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMyComputer) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchPhone) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchScreenSaver) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchSpreadsheet) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWebBrowser) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWebCam) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWordProcessor) + +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication1) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication2) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication3) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication4) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication5) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication6) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication7) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication8) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication9) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication10) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication11) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication12) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication13) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication14) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication15) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication16) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication17) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication18) + +/****************************************************************************** + * Browser Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(BrowserBack) +DEFINE_KEYNAME_WITH_SAME_NAME(BrowserFavorites) +DEFINE_KEYNAME_WITH_SAME_NAME(BrowserForward) +DEFINE_KEYNAME_WITH_SAME_NAME(BrowserHome) +DEFINE_KEYNAME_WITH_SAME_NAME(BrowserRefresh) +DEFINE_KEYNAME_WITH_SAME_NAME(BrowserSearch) +DEFINE_KEYNAME_WITH_SAME_NAME(BrowserStop) + +/****************************************************************************** + * Mobile Phone Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(AppSwitch) +DEFINE_KEYNAME_WITH_SAME_NAME(Call) +DEFINE_KEYNAME_WITH_SAME_NAME(Camera) +DEFINE_KEYNAME_WITH_SAME_NAME(CameraFocus) +DEFINE_KEYNAME_WITH_SAME_NAME(EndCall) +DEFINE_KEYNAME_WITH_SAME_NAME(GoBack) +DEFINE_KEYNAME_WITH_SAME_NAME(GoHome) +DEFINE_KEYNAME_WITH_SAME_NAME(HeadsetHook) +DEFINE_KEYNAME_WITH_SAME_NAME(LastNumberRedial) +DEFINE_KEYNAME_WITH_SAME_NAME(Notification) +DEFINE_KEYNAME_WITH_SAME_NAME(MannerMode) +DEFINE_KEYNAME_WITH_SAME_NAME(VoiceDial) + +/****************************************************************************** + * TV Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(TV) +DEFINE_KEYNAME_WITH_SAME_NAME(TV3DMode) +DEFINE_KEYNAME_WITH_SAME_NAME(TVAntennaCable) +DEFINE_KEYNAME_WITH_SAME_NAME(TVAudioDescription) +DEFINE_KEYNAME_WITH_SAME_NAME(TVAudioDescriptionMixDown) +DEFINE_KEYNAME_WITH_SAME_NAME(TVAudioDescriptionMixUp) +DEFINE_KEYNAME_WITH_SAME_NAME(TVContentsMenu) +DEFINE_KEYNAME_WITH_SAME_NAME(TVDataService) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInput) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputComponent1) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputComponent2) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputComposite1) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputComposite2) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputHDMI1) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputHDMI2) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputHDMI3) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputHDMI4) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInputVGA1) +DEFINE_KEYNAME_WITH_SAME_NAME(TVMediaContext) +DEFINE_KEYNAME_WITH_SAME_NAME(TVNetwork) +DEFINE_KEYNAME_WITH_SAME_NAME(TVNumberEntry) +DEFINE_KEYNAME_WITH_SAME_NAME(TVPower) +DEFINE_KEYNAME_WITH_SAME_NAME(TVRadioService) +DEFINE_KEYNAME_WITH_SAME_NAME(TVSatellite) +DEFINE_KEYNAME_WITH_SAME_NAME(TVSatelliteBS) +DEFINE_KEYNAME_WITH_SAME_NAME(TVSatelliteCS) +DEFINE_KEYNAME_WITH_SAME_NAME(TVSatelliteToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(TVTerrestrialAnalog) +DEFINE_KEYNAME_WITH_SAME_NAME(TVTerrestrialDigital) +DEFINE_KEYNAME_WITH_SAME_NAME(TVTimer) + +/****************************************************************************** + * Media Controller Keys + *****************************************************************************/ +DEFINE_KEYNAME_WITH_SAME_NAME(AVRInput) +DEFINE_KEYNAME_WITH_SAME_NAME(AVRPower) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF0Red) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF1Green) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF2Yellow) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF3Blue) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF4Grey) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF5Brown) +DEFINE_KEYNAME_WITH_SAME_NAME(ClosedCaptionToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(Dimmer) +DEFINE_KEYNAME_WITH_SAME_NAME(DisplaySwap) +DEFINE_KEYNAME_WITH_SAME_NAME(DVR) +DEFINE_KEYNAME_WITH_SAME_NAME(Exit) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear0) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear1) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear2) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear3) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall0) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall1) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall2) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall3) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore0) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore1) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore2) +DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore3) +DEFINE_KEYNAME_WITH_SAME_NAME(Guide) +DEFINE_KEYNAME_WITH_SAME_NAME(GuideNextDay) +DEFINE_KEYNAME_WITH_SAME_NAME(GuidePreviousDay) +DEFINE_KEYNAME_WITH_SAME_NAME(Info) +DEFINE_KEYNAME_WITH_SAME_NAME(InstantReplay) +DEFINE_KEYNAME_WITH_SAME_NAME(Link) +DEFINE_KEYNAME_WITH_SAME_NAME(ListProgram) +DEFINE_KEYNAME_WITH_SAME_NAME(LiveContent) +DEFINE_KEYNAME_WITH_SAME_NAME(Lock) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaApps) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaAudioTrack) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaFastForward) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaLast) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaSkipBackward) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaSkipForward) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaStepBackward) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaStepForward) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaTopMenu) +DEFINE_KEYNAME_WITH_SAME_NAME(NavigateIn) +DEFINE_KEYNAME_WITH_SAME_NAME(NavigateNext) +DEFINE_KEYNAME_WITH_SAME_NAME(NavigateOut) +DEFINE_KEYNAME_WITH_SAME_NAME(NavigatePrevious) +DEFINE_KEYNAME_WITH_SAME_NAME(NextFavoriteChannel) +DEFINE_KEYNAME_WITH_SAME_NAME(NextUserProfile) +DEFINE_KEYNAME_WITH_SAME_NAME(OnDemand) +DEFINE_KEYNAME_WITH_SAME_NAME(Pairing) +DEFINE_KEYNAME_WITH_SAME_NAME(PinPDown) +DEFINE_KEYNAME_WITH_SAME_NAME(PinPMove) +DEFINE_KEYNAME_WITH_SAME_NAME(PinPToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(PinPUp) +DEFINE_KEYNAME_WITH_SAME_NAME(PlaySpeedDown) +DEFINE_KEYNAME_WITH_SAME_NAME(PlaySpeedReset) +DEFINE_KEYNAME_WITH_SAME_NAME(PlaySpeedUp) +DEFINE_KEYNAME_WITH_SAME_NAME(RandomToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(RcLowBattery) +DEFINE_KEYNAME_WITH_SAME_NAME(RecordSpeedNext) +DEFINE_KEYNAME_WITH_SAME_NAME(RfBypass) +DEFINE_KEYNAME_WITH_SAME_NAME(ScanChannelsToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(ScreenModeNext) +DEFINE_KEYNAME_WITH_SAME_NAME(Settings) +DEFINE_KEYNAME_WITH_SAME_NAME(SplitScreenToggle) +DEFINE_KEYNAME_WITH_SAME_NAME(STBInput) +DEFINE_KEYNAME_WITH_SAME_NAME(STBPower) +DEFINE_KEYNAME_WITH_SAME_NAME(Subtitle) +DEFINE_KEYNAME_WITH_SAME_NAME(Teletext) +DEFINE_KEYNAME_WITH_SAME_NAME(VideoModeNext) +DEFINE_KEYNAME_WITH_SAME_NAME(Wink) +DEFINE_KEYNAME_WITH_SAME_NAME(ZoomToggle) + +#undef DEFINE_KEYNAME_WITH_SAME_NAME +#undef DEFINE_KEYNAME_INTERNAL diff --git a/dom/events/KeyboardEvent.cpp b/dom/events/KeyboardEvent.cpp new file mode 100644 index 000000000..f0831d97b --- /dev/null +++ b/dom/events/KeyboardEvent.cpp @@ -0,0 +1,365 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/KeyboardEvent.h" +#include "mozilla/TextEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +KeyboardEvent::KeyboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetKeyboardEvent* aEvent) + : UIEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetKeyboardEvent(false, eVoidEvent, nullptr)) + , mInitializedByCtor(false) + , mInitializedWhichValue(0) +{ + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + mEvent->AsKeyboardEvent()->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + } +} + +NS_IMPL_ADDREF_INHERITED(KeyboardEvent, UIEvent) +NS_IMPL_RELEASE_INHERITED(KeyboardEvent, UIEvent) + +NS_INTERFACE_MAP_BEGIN(KeyboardEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMKeyEvent) +NS_INTERFACE_MAP_END_INHERITING(UIEvent) + +bool +KeyboardEvent::AltKey() +{ + return mEvent->AsKeyboardEvent()->IsAlt(); +} + +NS_IMETHODIMP +KeyboardEvent::GetAltKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = AltKey(); + return NS_OK; +} + +bool +KeyboardEvent::CtrlKey() +{ + return mEvent->AsKeyboardEvent()->IsControl(); +} + +NS_IMETHODIMP +KeyboardEvent::GetCtrlKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = CtrlKey(); + return NS_OK; +} + +bool +KeyboardEvent::ShiftKey() +{ + return mEvent->AsKeyboardEvent()->IsShift(); +} + +NS_IMETHODIMP +KeyboardEvent::GetShiftKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = ShiftKey(); + return NS_OK; +} + +bool +KeyboardEvent::MetaKey() +{ + return mEvent->AsKeyboardEvent()->IsMeta(); +} + +NS_IMETHODIMP +KeyboardEvent::GetMetaKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = MetaKey(); + return NS_OK; +} + +bool +KeyboardEvent::Repeat() +{ + return mEvent->AsKeyboardEvent()->mIsRepeat; +} + +NS_IMETHODIMP +KeyboardEvent::GetRepeat(bool* aIsRepeat) +{ + NS_ENSURE_ARG_POINTER(aIsRepeat); + *aIsRepeat = Repeat(); + return NS_OK; +} + +bool +KeyboardEvent::IsComposing() +{ + return mEvent->AsKeyboardEvent()->mIsComposing; +} + +NS_IMETHODIMP +KeyboardEvent::GetModifierState(const nsAString& aKey, + bool* aState) +{ + NS_ENSURE_ARG_POINTER(aState); + + *aState = GetModifierState(aKey); + return NS_OK; +} + +NS_IMETHODIMP +KeyboardEvent::GetKey(nsAString& aKeyName) +{ + mEvent->AsKeyboardEvent()->GetDOMKeyName(aKeyName); + return NS_OK; +} + +void +KeyboardEvent::GetCode(nsAString& aCodeName) +{ + mEvent->AsKeyboardEvent()->GetDOMCodeName(aCodeName); +} + +void KeyboardEvent::GetInitDict(KeyboardEventInit& aParam) +{ + GetKey(aParam.mKey); + GetCode(aParam.mCode); + aParam.mLocation = Location(); + aParam.mRepeat = Repeat(); + aParam.mIsComposing = IsComposing(); + + // legacy attributes + aParam.mKeyCode = KeyCode(); + aParam.mCharCode = CharCode(); + aParam.mWhich = Which(); + + // modifiers from EventModifierInit + aParam.mCtrlKey = CtrlKey(); + aParam.mShiftKey = ShiftKey(); + aParam.mAltKey = AltKey(); + aParam.mMetaKey = MetaKey(); + + WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent(); + aParam.mModifierAltGraph = internalEvent->IsAltGraph(); + aParam.mModifierCapsLock = internalEvent->IsCapsLocked(); + aParam.mModifierFn = internalEvent->IsFn(); + aParam.mModifierFnLock = internalEvent->IsFnLocked(); + aParam.mModifierNumLock = internalEvent->IsNumLocked(); + aParam.mModifierOS = internalEvent->IsOS(); + aParam.mModifierScrollLock = internalEvent->IsScrollLocked(); + aParam.mModifierSymbol = internalEvent->IsSymbol(); + aParam.mModifierSymbolLock = internalEvent->IsSymbolLocked(); + + // EventInit + aParam.mBubbles = internalEvent->mFlags.mBubbles; + aParam.mCancelable = internalEvent->mFlags.mCancelable; +} + +NS_IMETHODIMP +KeyboardEvent::GetCharCode(uint32_t* aCharCode) +{ + NS_ENSURE_ARG_POINTER(aCharCode); + *aCharCode = CharCode(); + return NS_OK; +} + +uint32_t +KeyboardEvent::CharCode() +{ + // If this event is initialized with ctor, we shouldn't check event type. + if (mInitializedByCtor) { + return mEvent->AsKeyboardEvent()->mCharCode; + } + + switch (mEvent->mMessage) { + case eBeforeKeyDown: + case eKeyDown: + case eKeyDownOnPlugin: + case eAfterKeyDown: + case eBeforeKeyUp: + case eKeyUp: + case eKeyUpOnPlugin: + case eAfterKeyUp: + return 0; + case eKeyPress: + case eAccessKeyNotFound: + return mEvent->AsKeyboardEvent()->mCharCode; + default: + break; + } + return 0; +} + +NS_IMETHODIMP +KeyboardEvent::GetKeyCode(uint32_t* aKeyCode) +{ + NS_ENSURE_ARG_POINTER(aKeyCode); + *aKeyCode = KeyCode(); + return NS_OK; +} + +uint32_t +KeyboardEvent::KeyCode() +{ + // If this event is initialized with ctor, we shouldn't check event type. + if (mInitializedByCtor) { + return mEvent->AsKeyboardEvent()->mKeyCode; + } + + if (mEvent->HasKeyEventMessage()) { + return mEvent->AsKeyboardEvent()->mKeyCode; + } + return 0; +} + +uint32_t +KeyboardEvent::Which() +{ + // If this event is initialized with ctor, which can have independent value. + if (mInitializedByCtor) { + return mInitializedWhichValue; + } + + switch (mEvent->mMessage) { + case eBeforeKeyDown: + case eKeyDown: + case eKeyDownOnPlugin: + case eAfterKeyDown: + case eBeforeKeyUp: + case eKeyUp: + case eKeyUpOnPlugin: + case eAfterKeyUp: + return KeyCode(); + case eKeyPress: + //Special case for 4xp bug 62878. Try to make value of which + //more closely mirror the values that 4.x gave for RETURN and BACKSPACE + { + uint32_t keyCode = mEvent->AsKeyboardEvent()->mKeyCode; + if (keyCode == NS_VK_RETURN || keyCode == NS_VK_BACK) { + return keyCode; + } + return CharCode(); + } + default: + break; + } + + return 0; +} + +NS_IMETHODIMP +KeyboardEvent::GetLocation(uint32_t* aLocation) +{ + NS_ENSURE_ARG_POINTER(aLocation); + + *aLocation = Location(); + return NS_OK; +} + +uint32_t +KeyboardEvent::Location() +{ + return mEvent->AsKeyboardEvent()->mLocation; +} + +// static +already_AddRefed<KeyboardEvent> +KeyboardEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const KeyboardEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<KeyboardEvent> newEvent = + new KeyboardEvent(target, nullptr, nullptr); + newEvent->InitWithKeyboardEventInit(target, aType, aParam, aRv); + + return newEvent.forget(); +} + +void +KeyboardEvent::InitWithKeyboardEventInit(EventTarget* aOwner, + const nsAString& aType, + const KeyboardEventInit& aParam, + ErrorResult& aRv) +{ + bool trusted = Init(aOwner); + InitKeyEvent(aType, aParam.mBubbles, aParam.mCancelable, + aParam.mView, false, false, false, false, + aParam.mKeyCode, aParam.mCharCode); + InitModifiers(aParam); + SetTrusted(trusted); + mDetail = aParam.mDetail; + mInitializedByCtor = true; + mInitializedWhichValue = aParam.mWhich; + + WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent(); + internalEvent->mLocation = aParam.mLocation; + internalEvent->mIsRepeat = aParam.mRepeat; + internalEvent->mIsComposing = aParam.mIsComposing; + internalEvent->mKeyNameIndex = + WidgetKeyboardEvent::GetKeyNameIndex(aParam.mKey); + if (internalEvent->mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { + internalEvent->mKeyValue = aParam.mKey; + } + internalEvent->mCodeNameIndex = + WidgetKeyboardEvent::GetCodeNameIndex(aParam.mCode); + if (internalEvent->mCodeNameIndex == CODE_NAME_INDEX_USE_STRING) { + internalEvent->mCodeValue = aParam.mCode; + } +} + +NS_IMETHODIMP +KeyboardEvent::InitKeyEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + mozIDOMWindow* aView, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + uint32_t aKeyCode, + uint32_t aCharCode) +{ + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0); + + WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent(); + keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey); + keyEvent->mKeyCode = aKeyCode; + keyEvent->mCharCode = aCharCode; + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<KeyboardEvent> +NS_NewDOMKeyboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetKeyboardEvent* aEvent) +{ + RefPtr<KeyboardEvent> it = new KeyboardEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/KeyboardEvent.h b/dom/events/KeyboardEvent.h new file mode 100644 index 000000000..ee860c401 --- /dev/null +++ b/dom/events/KeyboardEvent.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_KeyboardEvent_h_ +#define mozilla_dom_KeyboardEvent_h_ + +#include "mozilla/dom/UIEvent.h" +#include "mozilla/dom/KeyboardEventBinding.h" +#include "mozilla/EventForwards.h" +#include "nsIDOMKeyEvent.h" + +namespace mozilla { +namespace dom { + +class KeyboardEvent : public UIEvent, + public nsIDOMKeyEvent +{ +public: + KeyboardEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetKeyboardEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + // nsIDOMKeyEvent Interface + NS_DECL_NSIDOMKEYEVENT + + // Forward to base class + NS_FORWARD_TO_UIEVENT + + static already_AddRefed<KeyboardEvent> Constructor( + const GlobalObject& aGlobal, + const nsAString& aType, + const KeyboardEventInit& aParam, + ErrorResult& aRv); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return KeyboardEventBinding::Wrap(aCx, this, aGivenProto); + } + + bool AltKey(); + bool CtrlKey(); + bool ShiftKey(); + bool MetaKey(); + + bool GetModifierState(const nsAString& aKey) + { + return GetModifierStateInternal(aKey); + } + + bool Repeat(); + bool IsComposing(); + uint32_t CharCode(); + uint32_t KeyCode(); + virtual uint32_t Which() override; + uint32_t Location(); + + void GetCode(nsAString& aCode); + void GetInitDict(KeyboardEventInit& aParam); + + void InitKeyEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, + nsGlobalWindow* aView, bool aCtrlKey, bool aAltKey, + bool aShiftKey, bool aMetaKey, + uint32_t aKeyCode, uint32_t aCharCode) + { + auto* view = aView ? aView->AsInner() : nullptr; + InitKeyEvent(aType, aCanBubble, aCancelable, view, aCtrlKey, aAltKey, + aShiftKey, aMetaKey, aKeyCode, aCharCode); + } + +protected: + ~KeyboardEvent() {} + + void InitWithKeyboardEventInit(EventTarget* aOwner, + const nsAString& aType, + const KeyboardEventInit& aParam, + ErrorResult& aRv); + +private: + // True, if the instance is created with Constructor(). + bool mInitializedByCtor; + + // If the instance is created with Constructor(), which may have independent + // value. mInitializedWhichValue stores it. I.e., this is invalid when + // mInitializedByCtor is false. + uint32_t mInitializedWhichValue; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::KeyboardEvent> +NS_NewDOMKeyboardEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetKeyboardEvent* aEvent); + +#endif // mozilla_dom_KeyboardEvent_h_ diff --git a/dom/events/MessageEvent.cpp b/dom/events/MessageEvent.cpp new file mode 100644 index 000000000..565cd0cd7 --- /dev/null +++ b/dom/events/MessageEvent.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/MessageEvent.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/MessagePortBinding.h" + +#include "mozilla/HoldDropJSObjects.h" +#include "jsapi.h" +#include "nsGlobalWindow.h" // So we can assign an nsGlobalWindow* to mWindowSource + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(MessageEvent) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessageEvent, Event) + tmp->mData.setUndefined(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowSource) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPortSource) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPorts) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessageEvent, Event) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPortSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPorts) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MessageEvent, Event) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MessageEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMPL_ADDREF_INHERITED(MessageEvent, Event) +NS_IMPL_RELEASE_INHERITED(MessageEvent, Event) + +MessageEvent::MessageEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) + : Event(aOwner, aPresContext, aEvent) + , mData(JS::UndefinedValue()) +{ +} + +MessageEvent::~MessageEvent() +{ + mData.setUndefined(); + DropJSObjects(this); +} + +JSObject* +MessageEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return mozilla::dom::MessageEventBinding::Wrap(aCx, this, aGivenProto); +} + +void +MessageEvent::GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aData, + ErrorResult& aRv) +{ + aData.set(mData); + if (!JS_WrapValue(aCx, aData)) { + aRv.Throw(NS_ERROR_FAILURE); + } +} + +void +MessageEvent::GetOrigin(nsAString& aOrigin) const +{ + aOrigin = mOrigin; +} + +void +MessageEvent::GetLastEventId(nsAString& aLastEventId) const +{ + aLastEventId = mLastEventId; +} + +void +MessageEvent::GetSource(Nullable<OwningWindowProxyOrMessagePort>& aValue) const +{ + if (mWindowSource) { + aValue.SetValue().SetAsWindowProxy() = mWindowSource->GetOuterWindow(); + } else if (mPortSource) { + aValue.SetValue().SetAsMessagePort() = mPortSource; + } +} + +/* static */ already_AddRefed<MessageEvent> +MessageEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const MessageEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(t, aType, aParam, aRv); +} + +/* static */ already_AddRefed<MessageEvent> +MessageEvent::Constructor(EventTarget* aEventTarget, + const nsAString& aType, + const MessageEventInit& aParam, + ErrorResult& aRv) +{ + RefPtr<MessageEvent> event = new MessageEvent(aEventTarget, nullptr, nullptr); + + event->InitEvent(aType, aParam.mBubbles, aParam.mCancelable); + bool trusted = event->Init(aEventTarget); + event->SetTrusted(trusted); + + event->mData = aParam.mData; + + mozilla::HoldJSObjects(event.get()); + + event->mOrigin = aParam.mOrigin; + event->mLastEventId = aParam.mLastEventId; + + if (!aParam.mSource.IsNull()) { + if (aParam.mSource.Value().IsWindow()) { + event->mWindowSource = aParam.mSource.Value().GetAsWindow()->AsInner(); + } else { + event->mPortSource = aParam.mSource.Value().GetAsMessagePort(); + } + + MOZ_ASSERT(event->mWindowSource || event->mPortSource); + } + + event->mPorts.AppendElements(aParam.mPorts); + + return event.forget(); +} + +void +MessageEvent::InitMessageEvent(JSContext* aCx, const nsAString& aType, + bool aCanBubble, bool aCancelable, + JS::Handle<JS::Value> aData, + const nsAString& aOrigin, + const nsAString& aLastEventId, + const Nullable<WindowProxyOrMessagePort>& aSource, + const Sequence<OwningNonNull<MessagePort>>& aPorts) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + Event::InitEvent(aType, aCanBubble, aCancelable); + mData = aData; + mozilla::HoldJSObjects(this); + mOrigin = aOrigin; + mLastEventId = aLastEventId; + + mWindowSource = nullptr; + mPortSource = nullptr; + + if (!aSource.IsNull()) { + if (aSource.Value().IsWindowProxy()) { + auto* windowProxy = aSource.Value().GetAsWindowProxy(); + mWindowSource = windowProxy ? windowProxy->GetCurrentInnerWindow() : nullptr; + } else { + mPortSource = &aSource.Value().GetAsMessagePort(); + } + } + + mPorts.Clear(); + mPorts.AppendElements(aPorts); + MessageEventBinding::ClearCachedPortsValue(this); +} + +void +MessageEvent::GetPorts(nsTArray<RefPtr<MessagePort>>& aPorts) +{ + aPorts = mPorts; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/MessageEvent.h b/dom/events/MessageEvent.h new file mode 100644 index 000000000..4419b7927 --- /dev/null +++ b/dom/events/MessageEvent.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_MessageEvent_h_ +#define mozilla_dom_MessageEvent_h_ + +#include "mozilla/dom/Event.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsCycleCollectionParticipant.h" + +namespace mozilla { +namespace dom { + +struct MessageEventInit; +class MessagePort; +class OwningWindowProxyOrMessagePort; +class WindowProxyOrMessagePort; + +/** + * Implements the MessageEvent event, used for cross-document messaging and + * server-sent events. + * + * See http://www.whatwg.org/specs/web-apps/current-work/#messageevent for + * further details. + */ +class MessageEvent final : public Event +{ +public: + MessageEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MessageEvent, Event) + + // Forward to base class + NS_FORWARD_TO_EVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aData, + ErrorResult& aRv); + void GetOrigin(nsAString&) const; + void GetLastEventId(nsAString&) const; + void GetSource(Nullable<OwningWindowProxyOrMessagePort>& aValue) const; + + void GetPorts(nsTArray<RefPtr<MessagePort>>& aPorts); + + static already_AddRefed<MessageEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const MessageEventInit& aEventInit, + ErrorResult& aRv); + + static already_AddRefed<MessageEvent> + Constructor(EventTarget* aEventTarget, + const nsAString& aType, + const MessageEventInit& aEventInit, + ErrorResult& aRv); + + void InitMessageEvent(JSContext* aCx, const nsAString& aType, bool aCanBubble, + bool aCancelable, JS::Handle<JS::Value> aData, + const nsAString& aOrigin, const nsAString& aLastEventId, + const Nullable<WindowProxyOrMessagePort>& aSource, + const Sequence<OwningNonNull<MessagePort>>& aPorts); + +protected: + ~MessageEvent(); + +private: + JS::Heap<JS::Value> mData; + nsString mOrigin; + nsString mLastEventId; + RefPtr<nsPIDOMWindowInner> mWindowSource; + RefPtr<MessagePort> mPortSource; + + nsTArray<RefPtr<MessagePort>> mPorts; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MessageEvent_h_ diff --git a/dom/events/MouseEvent.cpp b/dom/events/MouseEvent.cpp new file mode 100644 index 000000000..5e540ac5b --- /dev/null +++ b/dom/events/MouseEvent.cpp @@ -0,0 +1,542 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/MouseEvents.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +MouseEvent::MouseEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetMouseEventBase* aEvent) + : UIEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetMouseEvent(false, eVoidEvent, nullptr, + WidgetMouseEvent::eReal)) +{ + // There's no way to make this class' ctor allocate an WidgetMouseScrollEvent. + // It's not that important, though, since a scroll event is not a real + // DOM event. + + WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); + mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + } + + if (mouseEvent) { + MOZ_ASSERT(mouseEvent->mReason != WidgetMouseEvent::eSynthesized, + "Don't dispatch DOM events from synthesized mouse events"); + mDetail = mouseEvent->mClickCount; + } +} + +NS_IMPL_ADDREF_INHERITED(MouseEvent, UIEvent) +NS_IMPL_RELEASE_INHERITED(MouseEvent, UIEvent) + +NS_INTERFACE_MAP_BEGIN(MouseEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMMouseEvent) +NS_INTERFACE_MAP_END_INHERITING(UIEvent) + +void +MouseEvent::InitMouseEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + uint16_t aButton, + EventTarget* aRelatedTarget) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); + + switch(mEvent->mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case ePointerEventClass: + case eSimpleGestureEventClass: { + WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase(); + mouseEventBase->relatedTarget = aRelatedTarget; + mouseEventBase->button = aButton; + mouseEventBase->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey); + mClientPoint.x = aClientX; + mClientPoint.y = aClientY; + mouseEventBase->mRefPoint.x = aScreenX; + mouseEventBase->mRefPoint.y = aScreenY; + + WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); + if (mouseEvent) { + mouseEvent->mClickCount = aDetail; + } + break; + } + default: + break; + } +} + +NS_IMETHODIMP +MouseEvent::InitMouseEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + mozIDOMWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + uint16_t aButton, + nsIDOMEventTarget* aRelatedTarget) +{ + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, + nsGlobalWindow::Cast(aView), aDetail, + aScreenX, aScreenY, + aClientX, aClientY, + aCtrlKey, aAltKey, aShiftKey, + aMetaKey, aButton, + static_cast<EventTarget *>(aRelatedTarget)); + + return NS_OK; +} + +void +MouseEvent::InitMouseEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + int16_t aButton, + EventTarget* aRelatedTarget, + const nsAString& aModifiersList) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + Modifiers modifiers = ComputeModifierState(aModifiersList); + + InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, + aScreenX, aScreenY, aClientX, aClientY, + (modifiers & MODIFIER_CONTROL) != 0, + (modifiers & MODIFIER_ALT) != 0, + (modifiers & MODIFIER_SHIFT) != 0, + (modifiers & MODIFIER_META) != 0, + aButton, aRelatedTarget); + + switch(mEvent->mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case ePointerEventClass: + case eSimpleGestureEventClass: + mEvent->AsInputEvent()->mModifiers = modifiers; + return; + default: + MOZ_CRASH("There is no space to store the modifiers"); + } +} + +void +MouseEvent::InitializeExtraMouseEventDictionaryMembers(const MouseEventInit& aParam) +{ + InitModifiers(aParam); + mEvent->AsMouseEventBase()->buttons = aParam.mButtons; + mMovementPoint.x = aParam.mMovementX; + mMovementPoint.y = aParam.mMovementY; +} + +already_AddRefed<MouseEvent> +MouseEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const MouseEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<MouseEvent> e = new MouseEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitMouseEvent(aType, aParam.mBubbles, aParam.mCancelable, + aParam.mView, aParam.mDetail, aParam.mScreenX, + aParam.mScreenY, aParam.mClientX, aParam.mClientY, + aParam.mCtrlKey, aParam.mAltKey, aParam.mShiftKey, + aParam.mMetaKey, aParam.mButton, aParam.mRelatedTarget); + e->InitializeExtraMouseEventDictionaryMembers(aParam); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +void +MouseEvent::InitNSMouseEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + uint16_t aButton, + EventTarget* aRelatedTarget, + float aPressure, + uint16_t aInputSource) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, + aView, aDetail, aScreenX, aScreenY, + aClientX, aClientY, + aCtrlKey, aAltKey, aShiftKey, + aMetaKey, aButton, aRelatedTarget); + + WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase(); + mouseEventBase->pressure = aPressure; + mouseEventBase->inputSource = aInputSource; +} + +NS_IMETHODIMP +MouseEvent::GetButton(int16_t* aButton) +{ + NS_ENSURE_ARG_POINTER(aButton); + *aButton = Button(); + return NS_OK; +} + +int16_t +MouseEvent::Button() +{ + switch(mEvent->mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case ePointerEventClass: + case eSimpleGestureEventClass: + return mEvent->AsMouseEventBase()->button; + default: + NS_WARNING("Tried to get mouse button for non-mouse event!"); + return WidgetMouseEvent::eLeftButton; + } +} + +NS_IMETHODIMP +MouseEvent::GetButtons(uint16_t* aButtons) +{ + NS_ENSURE_ARG_POINTER(aButtons); + *aButtons = Buttons(); + return NS_OK; +} + +uint16_t +MouseEvent::Buttons() +{ + switch(mEvent->mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case ePointerEventClass: + case eSimpleGestureEventClass: + return mEvent->AsMouseEventBase()->buttons; + default: + MOZ_CRASH("Tried to get mouse buttons for non-mouse event!"); + } +} + +NS_IMETHODIMP +MouseEvent::GetRelatedTarget(nsIDOMEventTarget** aRelatedTarget) +{ + NS_ENSURE_ARG_POINTER(aRelatedTarget); + *aRelatedTarget = GetRelatedTarget().take(); + return NS_OK; +} + +already_AddRefed<EventTarget> +MouseEvent::GetRelatedTarget() +{ + nsCOMPtr<EventTarget> relatedTarget; + switch(mEvent->mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case ePointerEventClass: + case eSimpleGestureEventClass: + relatedTarget = + do_QueryInterface(mEvent->AsMouseEventBase()->relatedTarget); + break; + default: + break; + } + + if (relatedTarget) { + nsCOMPtr<nsIContent> content = do_QueryInterface(relatedTarget); + nsCOMPtr<nsIContent> currentTarget = + do_QueryInterface(mEvent->mCurrentTarget); + + nsIContent* shadowRelatedTarget = GetShadowRelatedTarget(currentTarget, content); + if (shadowRelatedTarget) { + relatedTarget = shadowRelatedTarget; + } + + if (content && content->ChromeOnlyAccess() && + !nsContentUtils::CanAccessNativeAnon()) { + relatedTarget = do_QueryInterface(content->FindFirstNonChromeOnlyAccessContent()); + } + + if (relatedTarget) { + relatedTarget = relatedTarget->GetTargetForDOMEvent(); + } + return relatedTarget.forget(); + } + return nullptr; +} + +void +MouseEvent::GetRegion(nsAString& aRegion) +{ + SetDOMStringToNull(aRegion); + WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase(); + if (mouseEventBase) { + aRegion = mouseEventBase->region; + } +} + +NS_IMETHODIMP +MouseEvent::GetMozMovementX(int32_t* aMovementX) +{ + NS_ENSURE_ARG_POINTER(aMovementX); + *aMovementX = MovementX(); + + return NS_OK; +} + +NS_IMETHODIMP +MouseEvent::GetMozMovementY(int32_t* aMovementY) +{ + NS_ENSURE_ARG_POINTER(aMovementY); + *aMovementY = MovementY(); + + return NS_OK; +} + +NS_IMETHODIMP +MouseEvent::GetScreenX(int32_t* aScreenX) +{ + NS_ENSURE_ARG_POINTER(aScreenX); + *aScreenX = ScreenX(); + return NS_OK; +} + +int32_t +MouseEvent::ScreenX() +{ + return Event::GetScreenCoords(mPresContext, mEvent, mEvent->mRefPoint).x; +} + +NS_IMETHODIMP +MouseEvent::GetScreenY(int32_t* aScreenY) +{ + NS_ENSURE_ARG_POINTER(aScreenY); + *aScreenY = ScreenY(); + return NS_OK; +} + +int32_t +MouseEvent::ScreenY() +{ + return Event::GetScreenCoords(mPresContext, mEvent, mEvent->mRefPoint).y; +} + + +NS_IMETHODIMP +MouseEvent::GetClientX(int32_t* aClientX) +{ + NS_ENSURE_ARG_POINTER(aClientX); + *aClientX = ClientX(); + return NS_OK; +} + +int32_t +MouseEvent::ClientX() +{ + return Event::GetClientCoords(mPresContext, mEvent, mEvent->mRefPoint, + mClientPoint).x; +} + +NS_IMETHODIMP +MouseEvent::GetClientY(int32_t* aClientY) +{ + NS_ENSURE_ARG_POINTER(aClientY); + *aClientY = ClientY(); + return NS_OK; +} + +int32_t +MouseEvent::ClientY() +{ + return Event::GetClientCoords(mPresContext, mEvent, mEvent->mRefPoint, + mClientPoint).y; +} + +int32_t +MouseEvent::OffsetX() +{ + return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->mRefPoint, + mClientPoint).x; +} + +int32_t +MouseEvent::OffsetY() +{ + return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->mRefPoint, + mClientPoint).y; +} + +bool +MouseEvent::AltKey() +{ + return mEvent->AsInputEvent()->IsAlt(); +} + +NS_IMETHODIMP +MouseEvent::GetAltKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = AltKey(); + return NS_OK; +} + +bool +MouseEvent::CtrlKey() +{ + return mEvent->AsInputEvent()->IsControl(); +} + +NS_IMETHODIMP +MouseEvent::GetCtrlKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = CtrlKey(); + return NS_OK; +} + +bool +MouseEvent::ShiftKey() +{ + return mEvent->AsInputEvent()->IsShift(); +} + +NS_IMETHODIMP +MouseEvent::GetShiftKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = ShiftKey(); + return NS_OK; +} + +bool +MouseEvent::MetaKey() +{ + return mEvent->AsInputEvent()->IsMeta(); +} + +NS_IMETHODIMP +MouseEvent::GetMetaKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = MetaKey(); + return NS_OK; +} + +NS_IMETHODIMP +MouseEvent::GetModifierState(const nsAString& aKey, + bool* aState) +{ + NS_ENSURE_ARG_POINTER(aState); + + *aState = GetModifierState(aKey); + return NS_OK; +} + +float +MouseEvent::MozPressure() const +{ + return mEvent->AsMouseEventBase()->pressure; +} + +NS_IMETHODIMP +MouseEvent::GetMozPressure(float* aPressure) +{ + NS_ENSURE_ARG_POINTER(aPressure); + *aPressure = MozPressure(); + return NS_OK; +} + +bool +MouseEvent::HitCluster() const +{ + return mEvent->AsMouseEventBase()->hitCluster; +} + +uint16_t +MouseEvent::MozInputSource() const +{ + return mEvent->AsMouseEventBase()->inputSource; +} + +NS_IMETHODIMP +MouseEvent::GetMozInputSource(uint16_t* aInputSource) +{ + NS_ENSURE_ARG_POINTER(aInputSource); + *aInputSource = MozInputSource(); + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<MouseEvent> +NS_NewDOMMouseEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetMouseEvent* aEvent) +{ + RefPtr<MouseEvent> it = new MouseEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/MouseEvent.h b/dom/events/MouseEvent.h new file mode 100644 index 000000000..45539e8f9 --- /dev/null +++ b/dom/events/MouseEvent.h @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_MouseEvent_h_ +#define mozilla_dom_MouseEvent_h_ + +#include "mozilla/dom/UIEvent.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/EventForwards.h" +#include "nsIDOMMouseEvent.h" + +namespace mozilla { +namespace dom { + +class MouseEvent : public UIEvent, + public nsIDOMMouseEvent +{ +public: + MouseEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetMouseEventBase* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + // nsIDOMMouseEvent Interface + NS_DECL_NSIDOMMOUSEEVENT + + // Forward to base class + NS_FORWARD_TO_UIEVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return MouseEventBinding::Wrap(aCx, this, aGivenProto); + } + + // Web IDL binding methods + virtual uint32_t Which() override + { + return Button() + 1; + } + + int32_t ScreenX(); + int32_t ScreenY(); + int32_t ClientX(); + int32_t ClientY(); + int32_t OffsetX(); + int32_t OffsetY(); + bool CtrlKey(); + bool ShiftKey(); + bool AltKey(); + bool MetaKey(); + int16_t Button(); + uint16_t Buttons(); + already_AddRefed<EventTarget> GetRelatedTarget(); + void GetRegion(nsAString& aRegion); + void InitMouseEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, + nsGlobalWindow* aView, int32_t aDetail, int32_t aScreenX, + int32_t aScreenY, int32_t aClientX, int32_t aClientY, + bool aCtrlKey, bool aAltKey, bool aShiftKey, + bool aMetaKey, uint16_t aButton, + EventTarget* aRelatedTarget); + + void InitializeExtraMouseEventDictionaryMembers(const MouseEventInit& aParam); + + bool GetModifierState(const nsAString& aKeyArg) + { + return GetModifierStateInternal(aKeyArg); + } + static already_AddRefed<MouseEvent> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const MouseEventInit& aParam, + ErrorResult& aRv); + int32_t MovementX() + { + return GetMovementPoint().x; + } + int32_t MovementY() + { + return GetMovementPoint().y; + } + float MozPressure() const; + bool HitCluster() const; + uint16_t MozInputSource() const; + void InitNSMouseEvent(const nsAString& aType, + bool aCanBubble, bool aCancelable, + nsGlobalWindow* aView, int32_t aDetail, + int32_t aScreenX, int32_t aScreenY, + int32_t aClientX, int32_t aClientY, + bool aCtrlKey, bool aAltKey, bool aShiftKey, + bool aMetaKey, uint16_t aButton, + EventTarget* aRelatedTarget, + float aPressure, uint16_t aInputSource); + +protected: + ~MouseEvent() {} + + void InitMouseEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + int16_t aButton, + EventTarget* aRelatedTarget, + const nsAString& aModifiersList); +}; + +} // namespace dom +} // namespace mozilla + +#define NS_FORWARD_TO_MOUSEEVENT \ + NS_FORWARD_NSIDOMMOUSEEVENT(MouseEvent::) \ + NS_FORWARD_TO_UIEVENT + +already_AddRefed<mozilla::dom::MouseEvent> +NS_NewDOMMouseEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetMouseEvent* aEvent); + +#endif // mozilla_dom_MouseEvent_h_ diff --git a/dom/events/MouseScrollEvent.cpp b/dom/events/MouseScrollEvent.cpp new file mode 100644 index 000000000..4007c0b51 --- /dev/null +++ b/dom/events/MouseScrollEvent.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/MouseScrollEvent.h" +#include "mozilla/MouseEvents.h" +#include "prtime.h" +#include "nsIDOMMouseScrollEvent.h" + +namespace mozilla { +namespace dom { + +MouseScrollEvent::MouseScrollEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetMouseScrollEvent* aEvent) + : MouseEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetMouseScrollEvent(false, eVoidEvent, nullptr)) +{ + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); + static_cast<WidgetMouseEventBase*>(mEvent)->inputSource = + nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + } + + mDetail = mEvent->AsMouseScrollEvent()->mDelta; +} + +NS_IMPL_ADDREF_INHERITED(MouseScrollEvent, MouseEvent) +NS_IMPL_RELEASE_INHERITED(MouseScrollEvent, MouseEvent) + +NS_INTERFACE_MAP_BEGIN(MouseScrollEvent) +NS_INTERFACE_MAP_END_INHERITING(MouseEvent) + +void +MouseScrollEvent::InitMouseScrollEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + uint16_t aButton, + EventTarget* aRelatedTarget, + int32_t aAxis) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, + aScreenX, aScreenY, aClientX, aClientY, + aCtrlKey, aAltKey, aShiftKey, aMetaKey, aButton, + aRelatedTarget); + mEvent->AsMouseScrollEvent()->mIsHorizontal = + (aAxis == nsIDOMMouseScrollEvent::HORIZONTAL_AXIS); +} + +int32_t +MouseScrollEvent::Axis() +{ + return mEvent->AsMouseScrollEvent()->mIsHorizontal ? + static_cast<int32_t>(nsIDOMMouseScrollEvent::HORIZONTAL_AXIS) : + static_cast<int32_t>(nsIDOMMouseScrollEvent::VERTICAL_AXIS); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace dom; + +already_AddRefed<MouseScrollEvent> +NS_NewDOMMouseScrollEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetMouseScrollEvent* aEvent) +{ + RefPtr<MouseScrollEvent> it = + new MouseScrollEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/MouseScrollEvent.h b/dom/events/MouseScrollEvent.h new file mode 100644 index 000000000..5cfa03911 --- /dev/null +++ b/dom/events/MouseScrollEvent.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_MouseScrollEvent_h_ +#define mozilla_dom_MouseScrollEvent_h_ + +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/MouseScrollEventBinding.h" + +namespace mozilla { +namespace dom { + +class MouseScrollEvent : public MouseEvent +{ +public: + MouseScrollEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetMouseScrollEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + // Forward to base class + NS_FORWARD_TO_MOUSEEVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return MouseScrollEventBinding::Wrap(aCx, this, aGivenProto); + } + + int32_t Axis(); + + void InitMouseScrollEvent(const nsAString& aType, bool aCanBubble, + bool aCancelable, nsGlobalWindow* aView, + int32_t aDetail, int32_t aScreenX, int32_t aScreenY, + int32_t aClientX, int32_t aClientY, + bool aCtrlKey, bool aAltKey, bool aShiftKey, + bool aMetaKey, uint16_t aButton, + EventTarget* aRelatedTarget, + int32_t aAxis); + +protected: + ~MouseScrollEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::MouseScrollEvent> +NS_NewDOMMouseScrollEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetMouseScrollEvent* aEvent); + +#endif // mozilla_dom_MouseScrollEvent_h_ diff --git a/dom/events/MutationEvent.cpp b/dom/events/MutationEvent.cpp new file mode 100644 index 000000000..0ba495fbf --- /dev/null +++ b/dom/events/MutationEvent.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mozilla/dom/MutationEvent.h" +#include "mozilla/InternalMutationEvent.h" + +class nsPresContext; + +namespace mozilla { +namespace dom { + +MutationEvent::MutationEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalMutationEvent* aEvent) + : Event(aOwner, aPresContext, + aEvent ? aEvent : new InternalMutationEvent(false, eVoidEvent)) +{ + mEventIsInternal = (aEvent == nullptr); +} + +NS_INTERFACE_MAP_BEGIN(MutationEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMMutationEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMPL_ADDREF_INHERITED(MutationEvent, Event) +NS_IMPL_RELEASE_INHERITED(MutationEvent, Event) + +already_AddRefed<nsINode> +MutationEvent::GetRelatedNode() +{ + nsCOMPtr<nsINode> n = + do_QueryInterface(mEvent->AsMutationEvent()->mRelatedNode); + return n.forget(); +} + +NS_IMETHODIMP +MutationEvent::GetRelatedNode(nsIDOMNode** aRelatedNode) +{ + nsCOMPtr<nsINode> relatedNode = GetRelatedNode(); + nsCOMPtr<nsIDOMNode> relatedDOMNode = relatedNode ? relatedNode->AsDOMNode() : nullptr; + relatedDOMNode.forget(aRelatedNode); + return NS_OK; +} + +NS_IMETHODIMP +MutationEvent::GetPrevValue(nsAString& aPrevValue) +{ + InternalMutationEvent* mutation = mEvent->AsMutationEvent(); + if (mutation->mPrevAttrValue) + mutation->mPrevAttrValue->ToString(aPrevValue); + return NS_OK; +} + +NS_IMETHODIMP +MutationEvent::GetNewValue(nsAString& aNewValue) +{ + InternalMutationEvent* mutation = mEvent->AsMutationEvent(); + if (mutation->mNewAttrValue) + mutation->mNewAttrValue->ToString(aNewValue); + return NS_OK; +} + +NS_IMETHODIMP +MutationEvent::GetAttrName(nsAString& aAttrName) +{ + InternalMutationEvent* mutation = mEvent->AsMutationEvent(); + if (mutation->mAttrName) + mutation->mAttrName->ToString(aAttrName); + return NS_OK; +} + +uint16_t +MutationEvent::AttrChange() +{ + return mEvent->AsMutationEvent()->mAttrChange; +} + +NS_IMETHODIMP +MutationEvent::GetAttrChange(uint16_t* aAttrChange) +{ + *aAttrChange = AttrChange(); + return NS_OK; +} + +NS_IMETHODIMP +MutationEvent::InitMutationEvent(const nsAString& aTypeArg, + bool aCanBubbleArg, + bool aCancelableArg, + nsIDOMNode* aRelatedNodeArg, + const nsAString& aPrevValueArg, + const nsAString& aNewValueArg, + const nsAString& aAttrNameArg, + uint16_t aAttrChangeArg) +{ + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + + Event::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg); + + InternalMutationEvent* mutation = mEvent->AsMutationEvent(); + mutation->mRelatedNode = aRelatedNodeArg; + if (!aPrevValueArg.IsEmpty()) + mutation->mPrevAttrValue = NS_Atomize(aPrevValueArg); + if (!aNewValueArg.IsEmpty()) + mutation->mNewAttrValue = NS_Atomize(aNewValueArg); + if (!aAttrNameArg.IsEmpty()) { + mutation->mAttrName = NS_Atomize(aAttrNameArg); + } + mutation->mAttrChange = aAttrChangeArg; + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<MutationEvent> +NS_NewDOMMutationEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalMutationEvent* aEvent) +{ + RefPtr<MutationEvent> it = new MutationEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/MutationEvent.h b/dom/events/MutationEvent.h new file mode 100644 index 000000000..93e969708 --- /dev/null +++ b/dom/events/MutationEvent.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_MutationEvent_h_ +#define mozilla_dom_MutationEvent_h_ + +#include "mozilla/EventForwards.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "nsIDOMMutationEvent.h" +#include "nsINode.h" + +namespace mozilla { +namespace dom { + +class MutationEvent : public Event, + public nsIDOMMutationEvent +{ +public: + MutationEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalMutationEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIDOMMUTATIONEVENT + + // Forward to base class + NS_FORWARD_TO_EVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return MutationEventBinding::Wrap(aCx, this, aGivenProto); + } + + // xpidl implementation + // GetPrevValue(nsAString& aPrevValue); + // GetNewValue(nsAString& aNewValue); + // GetAttrName(nsAString& aAttrName); + + already_AddRefed<nsINode> GetRelatedNode(); + + uint16_t AttrChange(); + + void InitMutationEvent(const nsAString& aType, + bool& aCanBubble, bool& aCancelable, + nsINode* aRelatedNode, + const nsAString& aPrevValue, + const nsAString& aNewValue, + const nsAString& aAttrName, + uint16_t& aAttrChange, ErrorResult& aRv) + { + aRv = InitMutationEvent(aType, aCanBubble, aCancelable, + aRelatedNode ? aRelatedNode->AsDOMNode() : nullptr, + aPrevValue, aNewValue, aAttrName, aAttrChange); + } + +protected: + ~MutationEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::MutationEvent> +NS_NewDOMMutationEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalMutationEvent* aEvent); + +#endif // mozilla_dom_MutationEvent_h_ diff --git a/dom/events/NotifyPaintEvent.cpp b/dom/events/NotifyPaintEvent.cpp new file mode 100644 index 000000000..bc3537db9 --- /dev/null +++ b/dom/events/NotifyPaintEvent.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/NotifyPaintEvent.h" +#include "mozilla/dom/PaintRequest.h" +#include "mozilla/GfxMessageUtils.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace dom { + +NotifyPaintEvent::NotifyPaintEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + EventMessage aEventMessage, + nsInvalidateRequestList* aInvalidateRequests, + uint64_t aTransactionId) + : Event(aOwner, aPresContext, aEvent) +{ + if (mEvent) { + mEvent->mMessage = aEventMessage; + } + if (aInvalidateRequests) { + mInvalidateRequests.AppendElements(Move(aInvalidateRequests->mRequests)); + } + + mTransactionId = aTransactionId; +} + +NS_INTERFACE_MAP_BEGIN(NotifyPaintEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMNotifyPaintEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMPL_ADDREF_INHERITED(NotifyPaintEvent, Event) +NS_IMPL_RELEASE_INHERITED(NotifyPaintEvent, Event) + +nsRegion +NotifyPaintEvent::GetRegion() +{ + nsRegion r; + if (!nsContentUtils::IsCallerChrome()) { + return r; + } + for (uint32_t i = 0; i < mInvalidateRequests.Length(); ++i) { + r.Or(r, mInvalidateRequests[i].mRect); + r.SimplifyOutward(10); + } + return r; +} + +NS_IMETHODIMP +NotifyPaintEvent::GetBoundingClientRect(nsIDOMClientRect** aResult) +{ + *aResult = BoundingClientRect().take(); + return NS_OK; +} + +already_AddRefed<DOMRect> +NotifyPaintEvent::BoundingClientRect() +{ + RefPtr<DOMRect> rect = new DOMRect(ToSupports(this)); + + if (mPresContext) { + rect->SetLayoutRect(GetRegion().GetBounds()); + } + + return rect.forget(); +} + +NS_IMETHODIMP +NotifyPaintEvent::GetClientRects(nsIDOMClientRectList** aResult) +{ + *aResult = ClientRects().take(); + return NS_OK; +} + +already_AddRefed<DOMRectList> +NotifyPaintEvent::ClientRects() +{ + nsISupports* parent = ToSupports(this); + RefPtr<DOMRectList> rectList = new DOMRectList(parent); + + nsRegion r = GetRegion(); + for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) { + RefPtr<DOMRect> rect = new DOMRect(parent); + rect->SetLayoutRect(iter.Get()); + rectList->Append(rect); + } + + return rectList.forget(); +} + +NS_IMETHODIMP +NotifyPaintEvent::GetPaintRequests(nsISupports** aResult) +{ + RefPtr<PaintRequestList> requests = PaintRequests(); + requests.forget(aResult); + return NS_OK; +} + +already_AddRefed<PaintRequestList> +NotifyPaintEvent::PaintRequests() +{ + Event* parent = this; + RefPtr<PaintRequestList> requests = new PaintRequestList(parent); + + if (nsContentUtils::IsCallerChrome()) { + for (uint32_t i = 0; i < mInvalidateRequests.Length(); ++i) { + RefPtr<PaintRequest> r = new PaintRequest(parent); + r->SetRequest(mInvalidateRequests[i]); + requests->Append(r); + } + } + + return requests.forget(); +} + +NS_IMETHODIMP_(void) +NotifyPaintEvent::Serialize(IPC::Message* aMsg, + bool aSerializeInterfaceType) +{ + if (aSerializeInterfaceType) { + IPC::WriteParam(aMsg, NS_LITERAL_STRING("notifypaintevent")); + } + + Event::Serialize(aMsg, false); + + uint32_t length = mInvalidateRequests.Length(); + IPC::WriteParam(aMsg, length); + for (uint32_t i = 0; i < length; ++i) { + IPC::WriteParam(aMsg, mInvalidateRequests[i].mRect); + IPC::WriteParam(aMsg, mInvalidateRequests[i].mFlags); + } +} + +NS_IMETHODIMP_(bool) +NotifyPaintEvent::Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) +{ + NS_ENSURE_TRUE(Event::Deserialize(aMsg, aIter), false); + + uint32_t length = 0; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &length), false); + mInvalidateRequests.SetCapacity(length); + for (uint32_t i = 0; i < length; ++i) { + nsInvalidateRequestList::Request req; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &req.mRect), false); + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &req.mFlags), false); + mInvalidateRequests.AppendElement(req); + } + + return true; +} + +NS_IMETHODIMP +NotifyPaintEvent::GetTransactionId(uint64_t* aTransactionId) +{ + *aTransactionId = mTransactionId; + return NS_OK; +} + +uint64_t +NotifyPaintEvent::TransactionId() +{ + return mTransactionId; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<NotifyPaintEvent> +NS_NewDOMNotifyPaintEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + EventMessage aEventMessage, + nsInvalidateRequestList* aInvalidateRequests, + uint64_t aTransactionId) +{ + RefPtr<NotifyPaintEvent> it = + new NotifyPaintEvent(aOwner, aPresContext, aEvent, aEventMessage, + aInvalidateRequests, aTransactionId); + return it.forget(); +} diff --git a/dom/events/NotifyPaintEvent.h b/dom/events/NotifyPaintEvent.h new file mode 100644 index 000000000..e73a453d7 --- /dev/null +++ b/dom/events/NotifyPaintEvent.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_NotifyPaintEvent_h_ +#define mozilla_dom_NotifyPaintEvent_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/NotifyPaintEventBinding.h" +#include "nsIDOMNotifyPaintEvent.h" +#include "nsPresContext.h" + +namespace mozilla { +namespace dom { + +class DOMRect; +class DOMRectList; +class PaintRequestList; + +class NotifyPaintEvent : public Event, + public nsIDOMNotifyPaintEvent +{ + +public: + NotifyPaintEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + EventMessage aEventMessage, + nsInvalidateRequestList* aInvalidateRequests, + uint64_t aTransactionId); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIDOMNOTIFYPAINTEVENT + + // Forward to base class + NS_FORWARD_TO_EVENT_NO_SERIALIZATION_NO_DUPLICATION + NS_IMETHOD DuplicatePrivateData() override + { + return Event::DuplicatePrivateData(); + } + NS_IMETHOD_(void) Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType) override; + NS_IMETHOD_(bool) Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) override; + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return NotifyPaintEventBinding::Wrap(aCx, this, aGivenProto); + } + + already_AddRefed<DOMRectList> ClientRects(); + + already_AddRefed<DOMRect> BoundingClientRect(); + + already_AddRefed<PaintRequestList> PaintRequests(); + + uint64_t TransactionId(); + +protected: + ~NotifyPaintEvent() {} + +private: + nsRegion GetRegion(); + + nsTArray<nsInvalidateRequestList::Request> mInvalidateRequests; + uint64_t mTransactionId; +}; + +} // namespace dom +} // namespace mozilla + +// This empties aInvalidateRequests. +already_AddRefed<mozilla::dom::NotifyPaintEvent> +NS_NewDOMNotifyPaintEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetEvent* aEvent, + mozilla::EventMessage aEventMessage = + mozilla::eVoidEvent, + nsInvalidateRequestList* aInvalidateRequests = + nullptr, + uint64_t aTransactionId = 0); + +#endif // mozilla_dom_NotifyPaintEvent_h_ diff --git a/dom/events/PaintRequest.cpp b/dom/events/PaintRequest.cpp new file mode 100644 index 000000000..bd17c4754 --- /dev/null +++ b/dom/events/PaintRequest.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/PaintRequest.h" + +#include "mozilla/dom/PaintRequestBinding.h" +#include "mozilla/dom/PaintRequestListBinding.h" +#include "mozilla/dom/DOMRect.h" + +namespace mozilla { +namespace dom { + +/****************************************************************************** + * mozilla::dom::PaintRequest + *****************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PaintRequest, mParent) + +NS_INTERFACE_TABLE_HEAD(PaintRequest) + NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY + NS_INTERFACE_TABLE(PaintRequest, nsIDOMPaintRequest) + NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(PaintRequest) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(PaintRequest) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PaintRequest) + +/* virtual */ JSObject* +PaintRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return PaintRequestBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DOMRect> +PaintRequest::ClientRect() +{ + RefPtr<DOMRect> clientRect = new DOMRect(this); + clientRect->SetLayoutRect(mRequest.mRect); + return clientRect.forget(); +} + +NS_IMETHODIMP +PaintRequest::GetClientRect(nsIDOMClientRect** aResult) +{ + RefPtr<DOMRect> clientRect = ClientRect(); + clientRect.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +PaintRequest::GetXPCOMReason(nsAString& aResult) +{ + GetReason(aResult); + return NS_OK; +} + +/****************************************************************************** + * mozilla::dom::PaintRequestList + *****************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PaintRequestList, mParent) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaintRequestList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(PaintRequestList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PaintRequestList) + +JSObject* +PaintRequestList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return PaintRequestListBinding::Wrap(aCx, this, aGivenProto); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/PaintRequest.h b/dom/events/PaintRequest.h new file mode 100644 index 000000000..77887c0ee --- /dev/null +++ b/dom/events/PaintRequest.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_PaintRequest_h_ +#define mozilla_dom_PaintRequest_h_ + +#include "nsIDOMPaintRequest.h" +#include "nsPresContext.h" +#include "nsIDOMEvent.h" +#include "mozilla/Attributes.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +class DOMRect; + +class PaintRequest final : public nsIDOMPaintRequest + , public nsWrapperCache +{ +public: + explicit PaintRequest(nsIDOMEvent* aParent) + : mParent(aParent) + { + mRequest.mFlags = 0; + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PaintRequest) + NS_DECL_NSIDOMPAINTREQUEST + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsIDOMEvent* GetParentObject() const + { + return mParent; + } + + already_AddRefed<DOMRect> ClientRect(); + void GetReason(nsAString& aResult) const + { + aResult.AssignLiteral("repaint"); + } + + void SetRequest(const nsInvalidateRequestList::Request& aRequest) + { mRequest = aRequest; } + +private: + ~PaintRequest() {} + + nsCOMPtr<nsIDOMEvent> mParent; + nsInvalidateRequestList::Request mRequest; +}; + +class PaintRequestList final : public nsISupports, + public nsWrapperCache +{ +public: + explicit PaintRequestList(nsIDOMEvent *aParent) : mParent(aParent) + { + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PaintRequestList) + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + nsISupports* GetParentObject() + { + return mParent; + } + + void Append(PaintRequest* aElement) + { + mArray.AppendElement(aElement); + } + + uint32_t Length() + { + return mArray.Length(); + } + + PaintRequest* Item(uint32_t aIndex) + { + return mArray.SafeElementAt(aIndex); + } + PaintRequest* IndexedGetter(uint32_t aIndex, bool& aFound) + { + aFound = aIndex < mArray.Length(); + if (!aFound) { + return nullptr; + } + return mArray.ElementAt(aIndex); + } + +private: + ~PaintRequestList() {} + + nsTArray< RefPtr<PaintRequest> > mArray; + nsCOMPtr<nsIDOMEvent> mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PaintRequest_h_ diff --git a/dom/events/PhysicalKeyCodeNameList.h b/dom/events/PhysicalKeyCodeNameList.h new file mode 100644 index 000000000..d83af423d --- /dev/null +++ b/dom/events/PhysicalKeyCodeNameList.h @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This header file defines all DOM code name which are used for DOM + * KeyboardEvent.code. + * You must define NS_DEFINE_PHYSICAL_KEY_CODE_NAME macro before including this. + * + * It must have two arguments, (aCPPName, aDOMCodeName) + * aCPPName is usable name for a part of C++ constants. + * aDOMCodeName is the actual value. + */ + +#define NS_DEFINE_PHYSICAL_KEY_CODE_NAME_INTERNAL(aCPPName, aDOMCodeName) \ + NS_DEFINE_PHYSICAL_KEY_CODE_NAME(aCPPName, aDOMCodeName) + +#define DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(aName) \ + NS_DEFINE_PHYSICAL_KEY_CODE_NAME_INTERNAL(aName, #aName) + +// Unknown key +NS_DEFINE_PHYSICAL_KEY_CODE_NAME_INTERNAL(UNKNOWN, "") + +// Writing system keys +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Backquote) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Backslash) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Backspace) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BracketLeft) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BracketRight) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Comma) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit0) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit1) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit2) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit3) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit4) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit5) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit6) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit7) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit8) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Digit9) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Equal) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(IntlBackslash) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(IntlHash) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(IntlRo) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(IntlYen) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyA) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyB) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyC) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyD) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyE) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyF) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyG) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyH) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyI) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyJ) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyK) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyL) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyM) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyN) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyO) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyP) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyQ) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyR) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyS) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyT) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyU) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyV) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyW) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyX) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyY) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KeyZ) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Minus) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Period) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Quote) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Semicolon) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Slash) + +// Functional keys +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(AltLeft) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(AltRight) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(CapsLock) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ContextMenu) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ControlLeft) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ControlRight) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Enter) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(OSLeft) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(OSRight) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ShiftLeft) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ShiftRight) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Space) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Tab) + +// IME keys +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Convert) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(KanaMode) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Lang1) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Lang2) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Lang3) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Lang4) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Lang5) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NonConvert) + +// Control pad section +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Delete) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(End) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Help) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Home) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Insert) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(PageDown) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(PageUp) + +// Arrow pad section +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ArrowDown) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ArrowLeft) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ArrowRight) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ArrowUp) + +// Numpad section +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumLock) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad0) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad1) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad2) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad3) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad4) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad5) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad6) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad7) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad8) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Numpad9) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadAdd) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadBackspace) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadClear) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadClearEntry) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadComma) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadDecimal) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadDivide) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadEnter) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadEqual) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadMemoryAdd) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadMemoryClear) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadMemoryRecall) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadMemoryStore) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadMemorySubtract) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadMultiply) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadParenLeft) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadParenRight) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(NumpadSubtract) + +// Function section +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Escape) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F1) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F2) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F3) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F4) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F5) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F6) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F7) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F8) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F9) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F10) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F11) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F12) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F13) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F14) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F15) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F16) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F17) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F18) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F19) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F20) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F21) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F22) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F23) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(F24) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Fn) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(FnLock) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(PrintScreen) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(ScrollLock) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Pause) + +// Media keys +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BrowserBack) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BrowserFavorites) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BrowserForward) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BrowserHome) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BrowserRefresh) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BrowserSearch) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(BrowserStop) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Eject) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(LaunchApp1) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(LaunchApp2) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(LaunchMail) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(MediaPlayPause) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(MediaSelect) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(MediaStop) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(MediaTrackNext) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(MediaTrackPrevious) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Power) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Sleep) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(VolumeDown) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(VolumeMute) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(VolumeUp) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(WakeUp) + +// Legacy Keys and Non-Standard Keys + +// Legacy modifier keys +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Hyper) +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Super) +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Turbo) + +// Legacy process control keys +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Abort) +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Resume) +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Suspend) + +// Legacy editing keys +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Again) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Copy) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Cut) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Find) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Open) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Paste) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Props) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Select) +DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Undo) + +// International keyboards +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Hiragana) +// DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME(Katakana) + +#undef DEFINE_PHYSICAL_KEY_CODE_NAME_WITH_SAME_NAME +#undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME_INTERNAL diff --git a/dom/events/PointerEvent.cpp b/dom/events/PointerEvent.cpp new file mode 100644 index 000000000..f53739863 --- /dev/null +++ b/dom/events/PointerEvent.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Portions Copyright 2013 Microsoft Open Technologies, Inc. */ + +#include "mozilla/dom/PointerEvent.h" +#include "mozilla/MouseEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +PointerEvent::PointerEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetPointerEvent* aEvent) + : MouseEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetPointerEvent(false, eVoidEvent, nullptr)) +{ + NS_ASSERTION(mEvent->mClass == ePointerEventClass, + "event type mismatch ePointerEventClass"); + + WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); + mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + } + // 5.2 Pointer Event types, for all pointer events, |detail| attribute SHOULD + // be 0. + mDetail = 0; +} + +static uint16_t +ConvertStringToPointerType(const nsAString& aPointerTypeArg) +{ + if (aPointerTypeArg.EqualsLiteral("mouse")) { + return nsIDOMMouseEvent::MOZ_SOURCE_MOUSE; + } + if (aPointerTypeArg.EqualsLiteral("pen")) { + return nsIDOMMouseEvent::MOZ_SOURCE_PEN; + } + if (aPointerTypeArg.EqualsLiteral("touch")) { + return nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + } + + return nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; +} + +void +ConvertPointerTypeToString(uint16_t aPointerTypeSrc, nsAString& aPointerTypeDest) +{ + switch (aPointerTypeSrc) { + case nsIDOMMouseEvent::MOZ_SOURCE_MOUSE: + aPointerTypeDest.AssignLiteral("mouse"); + break; + case nsIDOMMouseEvent::MOZ_SOURCE_PEN: + aPointerTypeDest.AssignLiteral("pen"); + break; + case nsIDOMMouseEvent::MOZ_SOURCE_TOUCH: + aPointerTypeDest.AssignLiteral("touch"); + break; + default: + aPointerTypeDest.Truncate(); + break; + } +} + +// static +already_AddRefed<PointerEvent> +PointerEvent::Constructor(EventTarget* aOwner, + const nsAString& aType, + const PointerEventInit& aParam) +{ + RefPtr<PointerEvent> e = new PointerEvent(aOwner, nullptr, nullptr); + bool trusted = e->Init(aOwner); + + e->InitMouseEvent(aType, aParam.mBubbles, aParam.mCancelable, + aParam.mView, aParam.mDetail, aParam.mScreenX, + aParam.mScreenY, aParam.mClientX, aParam.mClientY, + false, false, false, false, aParam.mButton, + aParam.mRelatedTarget); + e->InitializeExtraMouseEventDictionaryMembers(aParam); + + WidgetPointerEvent* widgetEvent = e->mEvent->AsPointerEvent(); + widgetEvent->pointerId = aParam.mPointerId; + widgetEvent->mWidth = aParam.mWidth; + widgetEvent->mHeight = aParam.mHeight; + widgetEvent->pressure = aParam.mPressure; + widgetEvent->tiltX = aParam.mTiltX; + widgetEvent->tiltY = aParam.mTiltY; + widgetEvent->inputSource = ConvertStringToPointerType(aParam.mPointerType); + widgetEvent->mIsPrimary = aParam.mIsPrimary; + widgetEvent->buttons = aParam.mButtons; + + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +// static +already_AddRefed<PointerEvent> +PointerEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const PointerEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, aType, aParam); +} + +void +PointerEvent::GetPointerType(nsAString& aPointerType) +{ + ConvertPointerTypeToString(mEvent->AsPointerEvent()->inputSource, aPointerType); +} + +int32_t +PointerEvent::PointerId() +{ + return mEvent->AsPointerEvent()->pointerId; +} + +int32_t +PointerEvent::Width() +{ + return mEvent->AsPointerEvent()->mWidth; +} + +int32_t +PointerEvent::Height() +{ + return mEvent->AsPointerEvent()->mHeight; +} + +float +PointerEvent::Pressure() +{ + return mEvent->AsPointerEvent()->pressure; +} + +int32_t +PointerEvent::TiltX() +{ + return mEvent->AsPointerEvent()->tiltX; +} + +int32_t +PointerEvent::TiltY() +{ + return mEvent->AsPointerEvent()->tiltY; +} + +bool +PointerEvent::IsPrimary() +{ + return mEvent->AsPointerEvent()->mIsPrimary; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<PointerEvent> +NS_NewDOMPointerEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetPointerEvent *aEvent) +{ + RefPtr<PointerEvent> it = new PointerEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/PointerEvent.h b/dom/events/PointerEvent.h new file mode 100644 index 000000000..62c5a0c36 --- /dev/null +++ b/dom/events/PointerEvent.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Portions Copyright 2013 Microsoft Open Technologies, Inc. */ + +#ifndef mozilla_dom_PointerEvent_h_ +#define mozilla_dom_PointerEvent_h_ + +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/PointerEventBinding.h" + +class nsPresContext; + +namespace mozilla { +namespace dom { + +class PointerEvent : public MouseEvent +{ +public: + PointerEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetPointerEvent* aEvent); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return PointerEventBinding::Wrap(aCx, this, aGivenProto); + } + + static already_AddRefed<PointerEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const PointerEventInit& aParam, + ErrorResult& aRv); + + static already_AddRefed<PointerEvent> + Constructor(EventTarget* aOwner, + const nsAString& aType, + const PointerEventInit& aParam); + + int32_t PointerId(); + int32_t Width(); + int32_t Height(); + float Pressure(); + int32_t TiltX(); + int32_t TiltY(); + bool IsPrimary(); + void GetPointerType(nsAString& aPointerType); +}; + +void ConvertPointerTypeToString(uint16_t aPointerTypeSrc, nsAString& aPointerTypeDest); + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::PointerEvent> +NS_NewDOMPointerEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetPointerEvent* aEvent); + +#endif // mozilla_dom_PointerEvent_h_ diff --git a/dom/events/ScrollAreaEvent.cpp b/dom/events/ScrollAreaEvent.cpp new file mode 100644 index 000000000..39f9a3c4a --- /dev/null +++ b/dom/events/ScrollAreaEvent.cpp @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/ScrollAreaEvent.h" +#include "mozilla/ContentEvents.h" + +namespace mozilla { +namespace dom { + +ScrollAreaEvent::ScrollAreaEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalScrollAreaEvent* aEvent) + : UIEvent(aOwner, aPresContext, aEvent) + , mClientArea(new DOMRect(nullptr)) +{ + mClientArea->SetLayoutRect(aEvent ? aEvent->mArea : nsRect()); +} + +NS_IMPL_ADDREF_INHERITED(ScrollAreaEvent, UIEvent) +NS_IMPL_RELEASE_INHERITED(ScrollAreaEvent, UIEvent) + +NS_INTERFACE_MAP_BEGIN(ScrollAreaEvent) +NS_INTERFACE_MAP_END_INHERITING(UIEvent) + +void +ScrollAreaEvent::InitScrollAreaEvent(const nsAString& aEventType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + float aX, + float aY, + float aWidth, + float aHeight) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + UIEvent::InitUIEvent(aEventType, aCanBubble, aCancelable, aView, aDetail); + mClientArea->SetRect(aX, aY, aWidth, aHeight); +} + +NS_IMETHODIMP_(void) +ScrollAreaEvent::Serialize(IPC::Message* aMsg, + bool aSerializeInterfaceType) +{ + if (aSerializeInterfaceType) { + IPC::WriteParam(aMsg, NS_LITERAL_STRING("scrollareaevent")); + } + + Event::Serialize(aMsg, false); + + IPC::WriteParam(aMsg, X()); + IPC::WriteParam(aMsg, Y()); + IPC::WriteParam(aMsg, Width()); + IPC::WriteParam(aMsg, Height()); +} + +NS_IMETHODIMP_(bool) +ScrollAreaEvent::Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) +{ + NS_ENSURE_TRUE(Event::Deserialize(aMsg, aIter), false); + + float x, y, width, height; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &x), false); + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &y), false); + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &width), false); + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &height), false); + mClientArea->SetRect(x, y, width, height); + + return true; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<ScrollAreaEvent> +NS_NewDOMScrollAreaEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalScrollAreaEvent* aEvent) +{ + RefPtr<ScrollAreaEvent> ev = + new ScrollAreaEvent(aOwner, aPresContext, aEvent); + return ev.forget(); +} diff --git a/dom/events/ScrollAreaEvent.h b/dom/events/ScrollAreaEvent.h new file mode 100644 index 000000000..d91070351 --- /dev/null +++ b/dom/events/ScrollAreaEvent.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ScrollAreaEvent_h_ +#define mozilla_dom_ScrollAreaEvent_h_ + +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/ScrollAreaEventBinding.h" +#include "mozilla/dom/UIEvent.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" + +namespace mozilla { +namespace dom { + +class ScrollAreaEvent : public UIEvent +{ +public: + ScrollAreaEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalScrollAreaEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + NS_FORWARD_NSIDOMUIEVENT(UIEvent::) + + NS_FORWARD_TO_EVENT_NO_SERIALIZATION_NO_DUPLICATION + NS_IMETHOD DuplicatePrivateData() override + { + return Event::DuplicatePrivateData(); + } + NS_IMETHOD_(void) Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType) override; + NS_IMETHOD_(bool) Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) override; + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return ScrollAreaEventBinding::Wrap(aCx, this, aGivenProto); + } + + float X() const + { + return mClientArea->Left(); + } + + float Y() const + { + return mClientArea->Top(); + } + + float Width() const + { + return mClientArea->Width(); + } + + float Height() const + { + return mClientArea->Height(); + } + + void InitScrollAreaEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + float aX, float aY, + float aWidth, float aHeight); + +protected: + ~ScrollAreaEvent() {} + + RefPtr<DOMRect> mClientArea; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::ScrollAreaEvent> +NS_NewDOMScrollAreaEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalScrollAreaEvent* aEvent); + +#endif // mozilla_dom_ScrollAreaEvent_h_ diff --git a/dom/events/SimpleGestureEvent.cpp b/dom/events/SimpleGestureEvent.cpp new file mode 100644 index 000000000..597eb411e --- /dev/null +++ b/dom/events/SimpleGestureEvent.cpp @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SimpleGestureEvent.h" +#include "mozilla/TouchEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +SimpleGestureEvent::SimpleGestureEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetSimpleGestureEvent* aEvent) + : MouseEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetSimpleGestureEvent(false, eVoidEvent, + nullptr)) +{ + NS_ASSERTION(mEvent->mClass == eSimpleGestureEventClass, + "event type mismatch"); + + if (aEvent) { + mEventIsInternal = false; + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); + static_cast<WidgetMouseEventBase*>(mEvent)->inputSource = + nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + } +} + +NS_IMPL_ADDREF_INHERITED(SimpleGestureEvent, MouseEvent) +NS_IMPL_RELEASE_INHERITED(SimpleGestureEvent, MouseEvent) + +NS_INTERFACE_MAP_BEGIN(SimpleGestureEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMSimpleGestureEvent) +NS_INTERFACE_MAP_END_INHERITING(MouseEvent) + +uint32_t +SimpleGestureEvent::AllowedDirections() +{ + return mEvent->AsSimpleGestureEvent()->mAllowedDirections; +} + +NS_IMETHODIMP +SimpleGestureEvent::GetAllowedDirections(uint32_t* aAllowedDirections) +{ + NS_ENSURE_ARG_POINTER(aAllowedDirections); + *aAllowedDirections = AllowedDirections(); + return NS_OK; +} + +NS_IMETHODIMP +SimpleGestureEvent::SetAllowedDirections(uint32_t aAllowedDirections) +{ + mEvent->AsSimpleGestureEvent()->mAllowedDirections = aAllowedDirections; + return NS_OK; +} + +uint32_t +SimpleGestureEvent::Direction() +{ + return mEvent->AsSimpleGestureEvent()->mDirection; +} + +NS_IMETHODIMP +SimpleGestureEvent::GetDirection(uint32_t* aDirection) +{ + NS_ENSURE_ARG_POINTER(aDirection); + *aDirection = Direction(); + return NS_OK; +} + +double +SimpleGestureEvent::Delta() +{ + return mEvent->AsSimpleGestureEvent()->mDelta; +} + +NS_IMETHODIMP +SimpleGestureEvent::GetDelta(double* aDelta) +{ + NS_ENSURE_ARG_POINTER(aDelta); + *aDelta = Delta(); + return NS_OK; +} + +uint32_t +SimpleGestureEvent::ClickCount() +{ + return mEvent->AsSimpleGestureEvent()->mClickCount; +} + +NS_IMETHODIMP +SimpleGestureEvent::GetClickCount(uint32_t* aClickCount) +{ + NS_ENSURE_ARG_POINTER(aClickCount); + *aClickCount = ClickCount(); + return NS_OK; +} + +void +SimpleGestureEvent::InitSimpleGestureEvent(const nsAString& aTypeArg, + bool aCanBubbleArg, + bool aCancelableArg, + nsGlobalWindow* aViewArg, + int32_t aDetailArg, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + bool aCtrlKeyArg, + bool aAltKeyArg, + bool aShiftKeyArg, + bool aMetaKeyArg, + uint16_t aButton, + EventTarget* aRelatedTarget, + uint32_t aAllowedDirectionsArg, + uint32_t aDirectionArg, + double aDeltaArg, + uint32_t aClickCountArg) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + MouseEvent::InitMouseEvent(aTypeArg, aCanBubbleArg, aCancelableArg, + aViewArg, aDetailArg, + aScreenX, aScreenY, aClientX, aClientY, + aCtrlKeyArg, aAltKeyArg, aShiftKeyArg, + aMetaKeyArg, aButton, aRelatedTarget); + + WidgetSimpleGestureEvent* simpleGestureEvent = mEvent->AsSimpleGestureEvent(); + simpleGestureEvent->mAllowedDirections = aAllowedDirectionsArg; + simpleGestureEvent->mDirection = aDirectionArg; + simpleGestureEvent->mDelta = aDeltaArg; + simpleGestureEvent->mClickCount = aClickCountArg; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<SimpleGestureEvent> +NS_NewDOMSimpleGestureEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetSimpleGestureEvent* aEvent) +{ + RefPtr<SimpleGestureEvent> it = + new SimpleGestureEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/SimpleGestureEvent.h b/dom/events/SimpleGestureEvent.h new file mode 100644 index 000000000..b394753f6 --- /dev/null +++ b/dom/events/SimpleGestureEvent.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SimpleGestureEvent_h_ +#define mozilla_dom_SimpleGestureEvent_h_ + +#include "nsIDOMSimpleGestureEvent.h" +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/SimpleGestureEventBinding.h" +#include "mozilla/EventForwards.h" + +class nsPresContext; + +namespace mozilla { +namespace dom { + +class SimpleGestureEvent : public MouseEvent, + public nsIDOMSimpleGestureEvent +{ +public: + SimpleGestureEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetSimpleGestureEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIDOMSIMPLEGESTUREEVENT + + // Forward to base class + NS_FORWARD_TO_MOUSEEVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return SimpleGestureEventBinding::Wrap(aCx, this, aGivenProto); + } + + uint32_t AllowedDirections(); + uint32_t Direction(); + double Delta(); + uint32_t ClickCount(); + + void InitSimpleGestureEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + uint16_t aButton, + EventTarget* aRelatedTarget, + uint32_t aAllowedDirections, + uint32_t aDirection, + double aDelta, + uint32_t aClickCount); + +protected: + ~SimpleGestureEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::SimpleGestureEvent> +NS_NewDOMSimpleGestureEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetSimpleGestureEvent* aEvent); + +#endif // mozilla_dom_SimpleGestureEvent_h_ diff --git a/dom/events/SpeechRecognitionError.cpp b/dom/events/SpeechRecognitionError.cpp new file mode 100644 index 000000000..2d0c357e6 --- /dev/null +++ b/dom/events/SpeechRecognitionError.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SpeechRecognitionError.h" + +namespace mozilla { +namespace dom { + +SpeechRecognitionError::SpeechRecognitionError( + mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) + : Event(aOwner, aPresContext, aEvent) + , mError() +{ +} + +SpeechRecognitionError::~SpeechRecognitionError() {} + +already_AddRefed<SpeechRecognitionError> +SpeechRecognitionError::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const SpeechRecognitionErrorInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<SpeechRecognitionError> e = new SpeechRecognitionError(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitSpeechRecognitionError(aType, aParam.mBubbles, aParam.mCancelable, aParam.mError, aParam.mMessage); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +void +SpeechRecognitionError::InitSpeechRecognitionError(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + SpeechRecognitionErrorCode aError, + const nsAString& aMessage) +{ + Event::InitEvent(aType, aCanBubble, aCancelable); + mError = aError; + mMessage = aMessage; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/SpeechRecognitionError.h b/dom/events/SpeechRecognitionError.h new file mode 100644 index 000000000..ef43feffd --- /dev/null +++ b/dom/events/SpeechRecognitionError.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SpeechRecognitionError_h__ +#define SpeechRecognitionError_h__ + +#include "mozilla/dom/Event.h" +#include "mozilla/dom/SpeechRecognitionErrorBinding.h" + +namespace mozilla { +namespace dom { + +class SpeechRecognitionError : public Event +{ +public: + SpeechRecognitionError(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent); + virtual ~SpeechRecognitionError(); + + static already_AddRefed<SpeechRecognitionError> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const SpeechRecognitionErrorInit& aParam, + ErrorResult& aRv); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return mozilla::dom::SpeechRecognitionErrorBinding::Wrap(aCx, this, aGivenProto); + } + + void + GetMessage(nsAString& aString) + { + aString = mMessage; + } + + SpeechRecognitionErrorCode + Error() + { + return mError; + } + + void + InitSpeechRecognitionError(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + SpeechRecognitionErrorCode aError, + const nsAString& aMessage); + +protected: + SpeechRecognitionErrorCode mError; + nsString mMessage; +}; + +} // namespace dom +} // namespace mozilla + +#endif // SpeechRecognitionError_h__ diff --git a/dom/events/StorageEvent.cpp b/dom/events/StorageEvent.cpp new file mode 100644 index 000000000..64332503e --- /dev/null +++ b/dom/events/StorageEvent.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/StorageEvent.h" +#include "mozilla/dom/DOMStorage.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(StorageEvent) + +NS_IMPL_ADDREF_INHERITED(StorageEvent, Event) +NS_IMPL_RELEASE_INHERITED(StorageEvent, Event) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(StorageEvent, Event) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageArea) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(StorageEvent, Event) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(StorageEvent, Event) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mStorageArea) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(StorageEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +StorageEvent::StorageEvent(EventTarget* aOwner) + : Event(aOwner, nullptr, nullptr) +{ +} + +StorageEvent::~StorageEvent() +{ +} + +StorageEvent* +StorageEvent::AsStorageEvent() +{ + return this; +} + +JSObject* +StorageEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return StorageEventBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<StorageEvent> +StorageEvent::Constructor(EventTarget* aOwner, + const nsAString& aType, + const StorageEventInit& aEventInitDict) +{ + RefPtr<StorageEvent> e = new StorageEvent(aOwner); + + bool trusted = e->Init(aOwner); + e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable); + e->mKey = aEventInitDict.mKey; + e->mOldValue = aEventInitDict.mOldValue; + e->mNewValue = aEventInitDict.mNewValue; + e->mUrl = aEventInitDict.mUrl; + e->mStorageArea = aEventInitDict.mStorageArea; + e->SetTrusted(trusted); + e->SetComposed(aEventInitDict.mComposed); + return e.forget(); +} + +already_AddRefed<StorageEvent> +StorageEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const StorageEventInit& aEventInitDict, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, aType, aEventInitDict); +} + +void +StorageEvent::InitStorageEvent(const nsAString& aType, bool aCanBubble, + bool aCancelable, const nsAString& aKey, + const nsAString& aOldValue, + const nsAString& aNewValue, + const nsAString& aURL, + DOMStorage* aStorageArea) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + InitEvent(aType, aCanBubble, aCancelable); + mKey = aKey; + mOldValue = aOldValue; + mNewValue = aNewValue; + mUrl = aURL; + mStorageArea = aStorageArea; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<StorageEvent> +NS_NewDOMStorageEvent(EventTarget* aOwner) +{ + RefPtr<StorageEvent> e = new StorageEvent(aOwner); + + e->SetTrusted(e->Init(aOwner)); + return e.forget(); +} + diff --git a/dom/events/StorageEvent.h b/dom/events/StorageEvent.h new file mode 100644 index 000000000..39a107b93 --- /dev/null +++ b/dom/events/StorageEvent.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_StorageEvent_h +#define mozilla_dom_StorageEvent_h + +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/StorageEventBinding.h" + +// Helper for EventDispatcher. +already_AddRefed<mozilla::dom::StorageEvent> +NS_NewDOMStorageEvent(mozilla::dom::EventTarget* aOwner); + +namespace mozilla { +namespace dom { + +class DOMStorage; + +class StorageEvent : public Event +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(StorageEvent, Event) + + explicit StorageEvent(EventTarget* aOwner); + +protected: + virtual ~StorageEvent(); + + nsString mKey; + nsString mOldValue; + nsString mNewValue; + nsString mUrl; + RefPtr<DOMStorage> mStorageArea; + +public: + virtual StorageEvent* AsStorageEvent(); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<StorageEvent> + Constructor(EventTarget* aOwner, const nsAString& aType, + const StorageEventInit& aEventInitDict); + + static already_AddRefed<StorageEvent> + Constructor(const GlobalObject& aGlobal, const nsAString& aType, + const StorageEventInit& aEventInitDict, ErrorResult& aRv); + + void InitStorageEvent(const nsAString& aType, bool aCanBubble, + bool aCancelable, const nsAString& aKey, + const nsAString& aOldValue, + const nsAString& aNewValue, + const nsAString& aURL, + DOMStorage* aStorageArea); + + void GetKey(nsString& aRetVal) const + { + aRetVal = mKey; + } + + void GetOldValue(nsString& aRetVal) const + { + aRetVal = mOldValue; + } + + void GetNewValue(nsString& aRetVal) const + { + aRetVal = mNewValue; + } + + void GetUrl(nsString& aRetVal) const + { + aRetVal = mUrl; + } + + DOMStorage* GetStorageArea() const + { + return mStorageArea; + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_StorageEvent_h diff --git a/dom/events/TextClause.cpp b/dom/events/TextClause.cpp new file mode 100644 index 000000000..2ebb95dd9 --- /dev/null +++ b/dom/events/TextClause.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TextClause.h" +#include "mozilla/dom/TextClauseBinding.h" +#include "mozilla/TextEvents.h" + +namespace mozilla { +namespace dom { + +// Only needed for refcounted objects. +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(TextClause) +NS_IMPL_CYCLE_COLLECTING_ADDREF(TextClause) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TextClause) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextClause) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TextClause::TextClause(nsPIDOMWindowInner* aOwner, const TextRange& aRange, + const TextRange* aTargetRange) + : mOwner(aOwner) + , mIsTargetClause(false) +{ + MOZ_ASSERT(aOwner); + mStartOffset = aRange.mStartOffset; + mEndOffset = aRange.mEndOffset; + if (aRange.IsClause()) { + mIsCaret = false; + if (aTargetRange && aTargetRange->mStartOffset == mStartOffset) { + mIsTargetClause = true; + } + } else { + mIsCaret = true; + } +} + +JSObject* +TextClause::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return TextClauseBinding::Wrap(aCx, this, aGivenProto); +} + +TextClause::~TextClause() {} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/TextClause.h b/dom/events/TextClause.h new file mode 100644 index 000000000..7f85333e8 --- /dev/null +++ b/dom/events/TextClause.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_TextClause_h +#define mozilla_dom_TextClause_h + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +class TextClause final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TextClause) + + nsPIDOMWindowInner* GetParentObject() const { return mOwner; } + + TextClause(nsPIDOMWindowInner* aWindow, const TextRange& aRange, + const TextRange* targetRange); + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + inline uint32_t StartOffset() const { return mStartOffset; } + + inline uint32_t EndOffset() const { return mEndOffset; } + + inline bool IsCaret() const { return mIsCaret; } + + inline bool IsTargetClause() const { return mIsTargetClause; } + +private: + ~TextClause(); + nsCOMPtr<nsPIDOMWindowInner> mOwner; + + // Following members, please take look at widget/TextRange.h. + uint32_t mStartOffset; + uint32_t mEndOffset; + bool mIsCaret; + bool mIsTargetClause; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TextClause_h diff --git a/dom/events/TextComposition.cpp b/dom/events/TextComposition.cpp new file mode 100644 index 000000000..a53711c1f --- /dev/null +++ b/dom/events/TextComposition.cpp @@ -0,0 +1,867 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContentEventHandler.h" +#include "IMEContentObserver.h" +#include "IMEStateManager.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsIEditor.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEvents.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/TabParent.h" + +#ifdef XP_MACOSX +// Some defiens will be conflict with OSX SDK +#define TextRange _TextRange +#define TextRangeArray _TextRangeArray +#define Comment _Comment +#endif + +#include "nsPluginInstanceOwner.h" + +#ifdef XP_MACOSX +#undef TextRange +#undef TextRangeArray +#undef Comment +#endif + +using namespace mozilla::widget; + +namespace mozilla { + +#define IDEOGRAPHIC_SPACE (NS_LITERAL_STRING(u"\x3000")) + +/****************************************************************************** + * TextComposition + ******************************************************************************/ + +bool TextComposition::sHandlingSelectionEvent = false; + +TextComposition::TextComposition(nsPresContext* aPresContext, + nsINode* aNode, + TabParent* aTabParent, + WidgetCompositionEvent* aCompositionEvent) + : mPresContext(aPresContext) + , mNode(aNode) + , mTabParent(aTabParent) + , mNativeContext(aCompositionEvent->mNativeIMEContext) + , mCompositionStartOffset(0) + , mTargetClauseOffsetInComposition(0) + , mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests) + , mIsComposing(false) + , mIsEditorHandlingEvent(false) + , mIsRequestingCommit(false) + , mIsRequestingCancel(false) + , mRequestedToCommitOrCancel(false) + , mWasNativeCompositionEndEventDiscarded(false) + , mAllowControlCharacters( + Preferences::GetBool("dom.compositionevent.allow_control_characters", + false)) + , mWasCompositionStringEmpty(true) +{ + MOZ_ASSERT(aCompositionEvent->mNativeIMEContext.IsValid()); +} + +void +TextComposition::Destroy() +{ + mPresContext = nullptr; + mNode = nullptr; + mTabParent = nullptr; + // TODO: If the editor is still alive and this is held by it, we should tell + // this being destroyed for cleaning up the stuff. +} + +bool +TextComposition::IsValidStateForComposition(nsIWidget* aWidget) const +{ + return !Destroyed() && aWidget && !aWidget->Destroyed() && + mPresContext->GetPresShell() && + !mPresContext->GetPresShell()->IsDestroying(); +} + +bool +TextComposition::MaybeDispatchCompositionUpdate( + const WidgetCompositionEvent* aCompositionEvent) +{ + MOZ_RELEASE_ASSERT(!mTabParent); + + if (!IsValidStateForComposition(aCompositionEvent->mWidget)) { + return false; + } + + if (mLastData == aCompositionEvent->mData) { + return true; + } + CloneAndDispatchAs(aCompositionEvent, eCompositionUpdate); + return IsValidStateForComposition(aCompositionEvent->mWidget); +} + +BaseEventFlags +TextComposition::CloneAndDispatchAs( + const WidgetCompositionEvent* aCompositionEvent, + EventMessage aMessage, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack) +{ + MOZ_RELEASE_ASSERT(!mTabParent); + + MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->mWidget), + "Should be called only when it's safe to dispatch an event"); + + WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(), + aMessage, aCompositionEvent->mWidget); + compositionEvent.mTime = aCompositionEvent->mTime; + compositionEvent.mTimeStamp = aCompositionEvent->mTimeStamp; + compositionEvent.mData = aCompositionEvent->mData; + compositionEvent.mNativeIMEContext = aCompositionEvent->mNativeIMEContext; + compositionEvent.mOriginalMessage = aCompositionEvent->mMessage; + compositionEvent.mFlags.mIsSynthesizedForTests = + aCompositionEvent->mFlags.mIsSynthesizedForTests; + + nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault; + nsEventStatus* status = aStatus ? aStatus : &dummyStatus; + if (aMessage == eCompositionUpdate) { + mLastData = compositionEvent.mData; + mLastRanges = aCompositionEvent->mRanges; + } + + DispatchEvent(&compositionEvent, status, aCallBack, aCompositionEvent); + return compositionEvent.mFlags; +} + +void +TextComposition::DispatchEvent(WidgetCompositionEvent* aDispatchEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack, + const WidgetCompositionEvent *aOriginalEvent) +{ + nsPluginInstanceOwner::GeneratePluginEvent(aOriginalEvent, + aDispatchEvent); + + EventDispatcher::Dispatch(mNode, mPresContext, + aDispatchEvent, nullptr, aStatus, aCallBack); + + OnCompositionEventDispatched(aDispatchEvent); +} + +void +TextComposition::OnCompositionEventDiscarded( + WidgetCompositionEvent* aCompositionEvent) +{ + // Note that this method is never called for synthesized events for emulating + // commit or cancel composition. + + MOZ_ASSERT(aCompositionEvent->IsTrusted(), + "Shouldn't be called with untrusted event"); + + if (mTabParent) { + // The composition event should be discarded in the child process too. + Unused << mTabParent->SendCompositionEvent(*aCompositionEvent); + } + + // XXX If composition events are discarded, should we dispatch them with + // runnable event? However, even if we do so, it might make native IME + // confused due to async modification. Especially when native IME is + // TSF. + if (!aCompositionEvent->CausesDOMCompositionEndEvent()) { + return; + } + + mWasNativeCompositionEndEventDiscarded = true; +} + +static inline bool +IsControlChar(uint32_t aCharCode) +{ + return aCharCode < ' ' || aCharCode == 0x7F; +} + +static size_t +FindFirstControlCharacter(const nsAString& aStr) +{ + const char16_t* sourceBegin = aStr.BeginReading(); + const char16_t* sourceEnd = aStr.EndReading(); + + for (const char16_t* source = sourceBegin; source < sourceEnd; ++source) { + if (*source != '\t' && IsControlChar(*source)) { + return source - sourceBegin; + } + } + + return -1; +} + +static void +RemoveControlCharactersFrom(nsAString& aStr, TextRangeArray* aRanges) +{ + size_t firstControlCharOffset = FindFirstControlCharacter(aStr); + if (firstControlCharOffset == (size_t)-1) { + return; + } + + nsAutoString copy(aStr); + const char16_t* sourceBegin = copy.BeginReading(); + const char16_t* sourceEnd = copy.EndReading(); + + char16_t* dest = aStr.BeginWriting(); + if (NS_WARN_IF(!dest)) { + return; + } + + char16_t* curDest = dest + firstControlCharOffset; + size_t i = firstControlCharOffset; + for (const char16_t* source = sourceBegin + firstControlCharOffset; + source < sourceEnd; ++source) { + if (*source == '\t' || !IsControlChar(*source)) { + *curDest = *source; + ++curDest; + ++i; + } else if (aRanges) { + aRanges->RemoveCharacter(i); + } + } + + aStr.SetLength(curDest - dest); +} + +void +TextComposition::DispatchCompositionEvent( + WidgetCompositionEvent* aCompositionEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack, + bool aIsSynthesized) +{ + mWasCompositionStringEmpty = mString.IsEmpty(); + + // If the content is a container of TabParent, composition should be in the + // remote process. + if (mTabParent) { + Unused << mTabParent->SendCompositionEvent(*aCompositionEvent); + aCompositionEvent->StopPropagation(); + if (aCompositionEvent->CausesDOMTextEvent()) { + mLastData = aCompositionEvent->mData; + mLastRanges = aCompositionEvent->mRanges; + // Although, the composition event hasn't been actually handled yet, + // emulate an editor to be handling the composition event. + EditorWillHandleCompositionChangeEvent(aCompositionEvent); + EditorDidHandleCompositionChangeEvent(); + } + return; + } + + if (!mAllowControlCharacters) { + RemoveControlCharactersFrom(aCompositionEvent->mData, + aCompositionEvent->mRanges); + } + if (aCompositionEvent->mMessage == eCompositionCommitAsIs) { + NS_ASSERTION(!aCompositionEvent->mRanges, + "mRanges of eCompositionCommitAsIs should be null"); + aCompositionEvent->mRanges = nullptr; + NS_ASSERTION(aCompositionEvent->mData.IsEmpty(), + "mData of eCompositionCommitAsIs should be empty string"); + bool removePlaceholderCharacter = + Preferences::GetBool("intl.ime.remove_placeholder_character_at_commit", + false); + if (removePlaceholderCharacter && mLastData == IDEOGRAPHIC_SPACE) { + // If the last data is an ideographic space (FullWidth space), it might be + // a placeholder character of some Chinese IME. So, committing with + // this data might not be expected by users. Let's use empty string. + aCompositionEvent->mData.Truncate(); + } else { + aCompositionEvent->mData = mLastData; + } + } else if (aCompositionEvent->mMessage == eCompositionCommit) { + NS_ASSERTION(!aCompositionEvent->mRanges, + "mRanges of eCompositionCommit should be null"); + aCompositionEvent->mRanges = nullptr; + } + + if (!IsValidStateForComposition(aCompositionEvent->mWidget)) { + *aStatus = nsEventStatus_eConsumeNoDefault; + return; + } + + // If this instance has requested to commit or cancel composition but + // is not synthesizing commit event, that means that the IME commits or + // cancels the composition asynchronously. Typically, iBus behaves so. + // Then, synthesized events which were dispatched immediately after + // the request has already committed our editor's composition string and + // told it to web apps. Therefore, we should ignore the delayed events. + if (mRequestedToCommitOrCancel && !aIsSynthesized) { + *aStatus = nsEventStatus_eConsumeNoDefault; + return; + } + + // IME may commit composition with empty string for a commit request or + // with non-empty string for a cancel request. We should prevent such + // unexpected result. E.g., web apps may be confused if they implement + // autocomplete which attempts to commit composition forcibly when the user + // selects one of suggestions but composition string is cleared by IME. + // Note that most Chinese IMEs don't expose actual composition string to us. + // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition + // string. Therefore, we should hack it only when: + // 1. committing string is empty string at requesting commit but the last + // data isn't IDEOGRAPHIC SPACE. + // 2. non-empty string is committed at requesting cancel. + if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) { + nsString* committingData = nullptr; + switch (aCompositionEvent->mMessage) { + case eCompositionEnd: + case eCompositionChange: + case eCompositionCommitAsIs: + case eCompositionCommit: + committingData = &aCompositionEvent->mData; + break; + default: + NS_WARNING("Unexpected event comes during committing or " + "canceling composition"); + break; + } + if (committingData) { + if (mIsRequestingCommit && committingData->IsEmpty() && + mLastData != IDEOGRAPHIC_SPACE) { + committingData->Assign(mLastData); + } else if (mIsRequestingCancel && !committingData->IsEmpty()) { + committingData->Truncate(); + } + } + } + + bool dispatchEvent = true; + bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent(); + + // When mIsComposing is false but the committing string is different from + // the last data (E.g., previous eCompositionChange event made the + // composition string empty or didn't have clause information), we don't + // need to dispatch redundant DOM text event. + if (dispatchDOMTextEvent && + aCompositionEvent->mMessage != eCompositionChange && + !mIsComposing && mLastData == aCompositionEvent->mData) { + dispatchEvent = dispatchDOMTextEvent = false; + } + + // widget may dispatch redundant eCompositionChange event + // which modifies neither composition string, clauses nor caret + // position. In such case, we shouldn't dispatch DOM events. + if (dispatchDOMTextEvent && + aCompositionEvent->mMessage == eCompositionChange && + mLastData == aCompositionEvent->mData && + mRanges && aCompositionEvent->mRanges && + mRanges->Equals(*aCompositionEvent->mRanges)) { + dispatchEvent = dispatchDOMTextEvent = false; + } + + if (dispatchDOMTextEvent) { + if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) { + return; + } + } + + if (dispatchEvent) { + // If the composition event should cause a DOM text event, we should + // overwrite the event message as eCompositionChange because due to + // the limitation of mapping between event messages and DOM event types, + // we cannot map multiple event messages to a DOM event type. + if (dispatchDOMTextEvent && + aCompositionEvent->mMessage != eCompositionChange) { + aCompositionEvent->mFlags = + CloneAndDispatchAs(aCompositionEvent, eCompositionChange, + aStatus, aCallBack); + } else { + DispatchEvent(aCompositionEvent, aStatus, aCallBack); + } + } else { + *aStatus = nsEventStatus_eConsumeNoDefault; + } + + if (!IsValidStateForComposition(aCompositionEvent->mWidget)) { + return; + } + + // Emulate editor behavior of compositionchange event (DOM text event) handler + // if no editor handles composition events. + if (dispatchDOMTextEvent && !HasEditor()) { + EditorWillHandleCompositionChangeEvent(aCompositionEvent); + EditorDidHandleCompositionChangeEvent(); + } + + if (aCompositionEvent->CausesDOMCompositionEndEvent()) { + // Dispatch a compositionend event if it's necessary. + if (aCompositionEvent->mMessage != eCompositionEnd) { + CloneAndDispatchAs(aCompositionEvent, eCompositionEnd); + } + MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?"); + MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?"); + } + + MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent); +} + +// static +void +TextComposition::HandleSelectionEvent(nsPresContext* aPresContext, + TabParent* aTabParent, + WidgetSelectionEvent* aSelectionEvent) +{ + // If the content is a container of TabParent, composition should be in the + // remote process. + if (aTabParent) { + Unused << aTabParent->SendSelectionEvent(*aSelectionEvent); + aSelectionEvent->StopPropagation(); + return; + } + + ContentEventHandler handler(aPresContext); + AutoRestore<bool> saveHandlingSelectionEvent(sHandlingSelectionEvent); + sHandlingSelectionEvent = true; + // XXX During setting selection, a selection listener may change selection + // again. In such case, sHandlingSelectionEvent doesn't indicate if + // the selection change is caused by a selection event. However, it + // must be non-realistic scenario. + handler.OnSelectionEvent(aSelectionEvent); +} + +uint32_t +TextComposition::GetSelectionStartOffset() +{ + nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget(); + WidgetQueryContentEvent selectedTextEvent(true, eQuerySelectedText, widget); + // Due to a bug of widget, mRanges may not be nullptr even though composition + // string is empty. So, we need to check it here for avoiding to return + // odd start offset. + if (!mLastData.IsEmpty() && mRanges && mRanges->HasClauses()) { + selectedTextEvent.InitForQuerySelectedText( + ToSelectionType(mRanges->GetFirstClause()->mRangeType)); + } else { + NS_WARNING_ASSERTION( + !mLastData.IsEmpty() || !mRanges || !mRanges->HasClauses(), + "Shouldn't have empty clause info when composition string is empty"); + selectedTextEvent.InitForQuerySelectedText(SelectionType::eNormal); + } + + // The editor which has this composition is observed by active + // IMEContentObserver, we can use the cache of it. + RefPtr<IMEContentObserver> contentObserver = + IMEStateManager::GetActiveContentObserver(); + bool doQuerySelection = true; + if (contentObserver) { + if (contentObserver->IsManaging(this)) { + doQuerySelection = false; + contentObserver->HandleQueryContentEvent(&selectedTextEvent); + } + // If another editor already has focus, we cannot retrieve selection + // in the editor which has this composition... + else if (NS_WARN_IF(contentObserver->GetPresContext() == mPresContext)) { + return 0; // XXX Is this okay? + } + } + + // Otherwise, using slow path (i.e., compute every time with + // ContentEventHandler) + if (doQuerySelection) { + ContentEventHandler handler(mPresContext); + handler.HandleQueryContentEvent(&selectedTextEvent); + } + + if (NS_WARN_IF(!selectedTextEvent.mSucceeded)) { + return 0; // XXX Is this okay? + } + return selectedTextEvent.mReply.mOffset; +} + +void +TextComposition::OnCompositionEventDispatched( + const WidgetCompositionEvent* aCompositionEvent) +{ + MOZ_RELEASE_ASSERT(!mTabParent); + + if (!IsValidStateForComposition(aCompositionEvent->mWidget)) { + return; + } + + // Every composition event may cause changing composition start offset, + // especially when there is no composition string. Therefore, we need to + // update mCompositionStartOffset with the latest offset. + + MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart || + mWasCompositionStringEmpty, + "mWasCompositionStringEmpty should be true if the dispatched " + "event is eCompositionStart"); + + if (mWasCompositionStringEmpty && + !aCompositionEvent->CausesDOMCompositionEndEvent()) { + // If there was no composition string, current selection start may be the + // offset for inserting composition string. + // Update composition start offset with current selection start. + mCompositionStartOffset = GetSelectionStartOffset(); + mTargetClauseOffsetInComposition = 0; + } + + if (aCompositionEvent->CausesDOMTextEvent()) { + mTargetClauseOffsetInComposition = aCompositionEvent->TargetClauseOffset(); + } +} + +void +TextComposition::OnStartOffsetUpdatedInChild(uint32_t aStartOffset) +{ + mCompositionStartOffset = aStartOffset; +} + +void +TextComposition::MaybeNotifyIMEOfCompositionEventHandled( + const WidgetCompositionEvent* aCompositionEvent) +{ + if (aCompositionEvent->mMessage != eCompositionStart && + !aCompositionEvent->CausesDOMTextEvent()) { + return; + } + + RefPtr<IMEContentObserver> contentObserver = + IMEStateManager::GetActiveContentObserver(); + // When IMEContentObserver is managing the editor which has this composition, + // composition event handled notification should be sent after the observer + // notifies all pending notifications. Therefore, we should use it. + // XXX If IMEContentObserver suddenly loses focus after here and notifying + // widget of pending notifications, we won't notify widget of composition + // event handled. Although, this is a bug but it should be okay since + // destroying IMEContentObserver notifies IME of blur. So, native IME + // handler can treat it as this notification too. + if (contentObserver && contentObserver->IsManaging(this)) { + contentObserver->MaybeNotifyCompositionEventHandled(); + return; + } + // Otherwise, e.g., this composition is in non-active window, we should + // notify widget directly. + NotifyIME(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED); +} + +void +TextComposition::DispatchCompositionEventRunnable(EventMessage aEventMessage, + const nsAString& aData, + bool aIsSynthesizingCommit) +{ + nsContentUtils::AddScriptRunner( + new CompositionEventDispatcher(this, mNode, aEventMessage, aData, + aIsSynthesizingCommit)); +} + +nsresult +TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard) +{ + // If this composition is already requested to be committed or canceled, + // we don't need to request it again because even if the first request + // failed, new request won't success, probably. And we shouldn't synthesize + // events for committing or canceling composition twice or more times. + if (mRequestedToCommitOrCancel) { + return NS_OK; + } + + RefPtr<TextComposition> kungFuDeathGrip(this); + const nsAutoString lastData(mLastData); + + { + AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel); + AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit); + if (aDiscard) { + mIsRequestingCancel = true; + mIsRequestingCommit = false; + } else { + mIsRequestingCancel = false; + mIsRequestingCommit = true; + } + // FYI: CompositionEvents caused by a call of NotifyIME() may be + // discarded by PresShell if it's not safe to dispatch the event. + nsresult rv = + aWidget->NotifyIME(IMENotification(aDiscard ? + REQUEST_TO_CANCEL_COMPOSITION : + REQUEST_TO_COMMIT_COMPOSITION)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mRequestedToCommitOrCancel = true; + + // If the request is performed synchronously, this must be already destroyed. + if (Destroyed()) { + return NS_OK; + } + + // Otherwise, synthesize the commit in content. + nsAutoString data(aDiscard ? EmptyString() : lastData); + if (data == mLastData) { + DispatchCompositionEventRunnable(eCompositionCommitAsIs, EmptyString(), + true); + } else { + DispatchCompositionEventRunnable(eCompositionCommit, data, true); + } + return NS_OK; +} + +nsresult +TextComposition::NotifyIME(IMEMessage aMessage) +{ + NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE); + return IMEStateManager::NotifyIME(aMessage, mPresContext); +} + +void +TextComposition::EditorWillHandleCompositionChangeEvent( + const WidgetCompositionEvent* aCompositionChangeEvent) +{ + mIsComposing = aCompositionChangeEvent->IsComposing(); + mRanges = aCompositionChangeEvent->mRanges; + mIsEditorHandlingEvent = true; + + MOZ_ASSERT(mLastData == aCompositionChangeEvent->mData, + "The text of a compositionchange event must be same as previous data " + "attribute value of the latest compositionupdate event"); +} + +void +TextComposition::OnEditorDestroyed() +{ + MOZ_RELEASE_ASSERT(!mTabParent); + + MOZ_ASSERT(!mIsEditorHandlingEvent, + "The editor should have stopped listening events"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + // XXX If this could happen, how do we notify IME of destroying the editor? + return; + } + + // Try to cancel the composition. + RequestToCommit(widget, true); +} + +void +TextComposition::EditorDidHandleCompositionChangeEvent() +{ + mString = mLastData; + mIsEditorHandlingEvent = false; +} + +void +TextComposition::StartHandlingComposition(nsIEditor* aEditor) +{ + MOZ_RELEASE_ASSERT(!mTabParent); + + MOZ_ASSERT(!HasEditor(), "There is a handling editor already"); + mEditorWeak = do_GetWeakReference(aEditor); +} + +void +TextComposition::EndHandlingComposition(nsIEditor* aEditor) +{ + MOZ_RELEASE_ASSERT(!mTabParent); + +#ifdef DEBUG + nsCOMPtr<nsIEditor> editor = GetEditor(); + MOZ_ASSERT(editor == aEditor, "Another editor handled the composition?"); +#endif // #ifdef DEBUG + mEditorWeak = nullptr; +} + +already_AddRefed<nsIEditor> +TextComposition::GetEditor() const +{ + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditorWeak); + return editor.forget(); +} + +bool +TextComposition::HasEditor() const +{ + nsCOMPtr<nsIEditor> editor = GetEditor(); + return !!editor; +} + +/****************************************************************************** + * TextComposition::CompositionEventDispatcher + ******************************************************************************/ + +TextComposition::CompositionEventDispatcher::CompositionEventDispatcher( + TextComposition* aComposition, + nsINode* aEventTarget, + EventMessage aEventMessage, + const nsAString& aData, + bool aIsSynthesizedEvent) + : mTextComposition(aComposition) + , mEventTarget(aEventTarget) + , mData(aData) + , mEventMessage(aEventMessage) + , mIsSynthesizedEvent(aIsSynthesizedEvent) +{ +} + +NS_IMETHODIMP +TextComposition::CompositionEventDispatcher::Run() +{ + // The widget can be different from the widget which has dispatched + // composition events because GetWidget() returns a widget which is proper + // for calling NotifyIME(). However, this must no be problem since both + // widget should share native IME context. Therefore, even if an event + // handler uses the widget for requesting IME to commit or cancel, it works. + nsCOMPtr<nsIWidget> widget(mTextComposition->GetWidget()); + if (!mTextComposition->IsValidStateForComposition(widget)) { + return NS_OK; // cannot dispatch any events anymore + } + + RefPtr<nsPresContext> presContext = mTextComposition->mPresContext; + nsEventStatus status = nsEventStatus_eIgnore; + switch (mEventMessage) { + case eCompositionStart: { + WidgetCompositionEvent compStart(true, eCompositionStart, widget); + compStart.mNativeIMEContext = mTextComposition->mNativeContext; + WidgetQueryContentEvent selectedText(true, eQuerySelectedText, widget); + ContentEventHandler handler(presContext); + handler.OnQuerySelectedText(&selectedText); + NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text"); + compStart.mData = selectedText.mReply.mString; + compStart.mFlags.mIsSynthesizedForTests = + mTextComposition->IsSynthesizedForTests(); + IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext, + &compStart, &status, nullptr, + mIsSynthesizedEvent); + break; + } + case eCompositionChange: + case eCompositionCommitAsIs: + case eCompositionCommit: { + WidgetCompositionEvent compEvent(true, mEventMessage, widget); + compEvent.mNativeIMEContext = mTextComposition->mNativeContext; + if (mEventMessage != eCompositionCommitAsIs) { + compEvent.mData = mData; + } + compEvent.mFlags.mIsSynthesizedForTests = + mTextComposition->IsSynthesizedForTests(); + IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext, + &compEvent, &status, nullptr, + mIsSynthesizedEvent); + break; + } + default: + MOZ_CRASH("Unsupported event"); + } + return NS_OK; +} + +/****************************************************************************** + * TextCompositionArray + ******************************************************************************/ + +TextCompositionArray::index_type +TextCompositionArray::IndexOf(const NativeIMEContext& aNativeIMEContext) +{ + if (!aNativeIMEContext.IsValid()) { + return NoIndex; + } + for (index_type i = Length(); i > 0; --i) { + if (ElementAt(i - 1)->GetNativeIMEContext() == aNativeIMEContext) { + return i - 1; + } + } + return NoIndex; +} + +TextCompositionArray::index_type +TextCompositionArray::IndexOf(nsIWidget* aWidget) +{ + return IndexOf(aWidget->GetNativeIMEContext()); +} + +TextCompositionArray::index_type +TextCompositionArray::IndexOf(nsPresContext* aPresContext) +{ + for (index_type i = Length(); i > 0; --i) { + if (ElementAt(i - 1)->GetPresContext() == aPresContext) { + return i - 1; + } + } + return NoIndex; +} + +TextCompositionArray::index_type +TextCompositionArray::IndexOf(nsPresContext* aPresContext, + nsINode* aNode) +{ + index_type index = IndexOf(aPresContext); + if (index == NoIndex) { + return NoIndex; + } + nsINode* node = ElementAt(index)->GetEventTargetNode(); + return node == aNode ? index : NoIndex; +} + +TextComposition* +TextCompositionArray::GetCompositionFor(nsIWidget* aWidget) +{ + index_type i = IndexOf(aWidget); + if (i == NoIndex) { + return nullptr; + } + return ElementAt(i); +} + +TextComposition* +TextCompositionArray::GetCompositionFor( + const WidgetCompositionEvent* aCompositionEvent) +{ + index_type i = IndexOf(aCompositionEvent->mNativeIMEContext); + if (i == NoIndex) { + return nullptr; + } + return ElementAt(i); +} + +TextComposition* +TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext) +{ + index_type i = IndexOf(aPresContext); + if (i == NoIndex) { + return nullptr; + } + return ElementAt(i); +} + +TextComposition* +TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext, + nsINode* aNode) +{ + index_type i = IndexOf(aPresContext, aNode); + if (i == NoIndex) { + return nullptr; + } + return ElementAt(i); +} + +TextComposition* +TextCompositionArray::GetCompositionInContent(nsPresContext* aPresContext, + nsIContent* aContent) +{ + // There should be only one composition per content object. + for (index_type i = Length(); i > 0; --i) { + nsINode* node = ElementAt(i - 1)->GetEventTargetNode(); + if (node && nsContentUtils::ContentIsDescendantOf(node, aContent)) { + return ElementAt(i - 1); + } + } + return nullptr; +} + +} // namespace mozilla diff --git a/dom/events/TextComposition.h b/dom/events/TextComposition.h new file mode 100644 index 000000000..a4161f82f --- /dev/null +++ b/dom/events/TextComposition.h @@ -0,0 +1,490 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TextComposition_h +#define mozilla_TextComposition_h + +#include "nsCOMPtr.h" +#include "nsINode.h" +#include "nsIWeakReference.h" +#include "nsIWidget.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsPresContext.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextRange.h" +#include "mozilla/dom/TabParent.h" + +class nsIEditor; + +namespace mozilla { + +class EventDispatchingCallback; +class IMEStateManager; + +/** + * TextComposition represents a text composition. This class stores the + * composition event target and its presContext. At dispatching the event via + * this class, the instances use the stored event target. + */ + +class TextComposition final +{ + friend class IMEStateManager; + + NS_INLINE_DECL_REFCOUNTING(TextComposition) + +public: + typedef dom::TabParent TabParent; + + static bool IsHandlingSelectionEvent() { return sHandlingSelectionEvent; } + + TextComposition(nsPresContext* aPresContext, + nsINode* aNode, + TabParent* aTabParent, + WidgetCompositionEvent* aCompositionEvent); + + bool Destroyed() const { return !mPresContext; } + nsPresContext* GetPresContext() const { return mPresContext; } + nsINode* GetEventTargetNode() const { return mNode; } + // The latest CompositionEvent.data value except compositionstart event. + // This value is modified at dispatching compositionupdate. + const nsString& LastData() const { return mLastData; } + // The composition string which is already handled by the focused editor. + // I.e., this value must be same as the composition string on the focused + // editor. This value is modified at a call of + // EditorDidHandleCompositionChangeEvent(). + // Note that mString and mLastData are different between dispatcing + // compositionupdate and compositionchange event handled by focused editor. + const nsString& String() const { return mString; } + // The latest clauses range of the composition string. + // During compositionupdate event, GetRanges() returns old ranges. + // So if getting on compositionupdate, Use GetLastRange instead of GetRange(). + TextRangeArray* GetLastRanges() const { return mLastRanges; } + // Returns the clauses and/or caret range of the composition string. + // This is modified at a call of EditorWillHandleCompositionChangeEvent(). + // This may return null if there is no clauses and caret. + // XXX We should return |const TextRangeArray*| here, but it causes compile + // error due to inaccessible Release() method. + TextRangeArray* GetRanges() const { return mRanges; } + // Returns the widget which is proper to call NotifyIME(). + nsIWidget* GetWidget() const + { + return mPresContext ? mPresContext->GetRootWidget() : nullptr; + } + // Returns true if the composition is started with synthesized event which + // came from nsDOMWindowUtils. + bool IsSynthesizedForTests() const { return mIsSynthesizedForTests; } + + const widget::NativeIMEContext& GetNativeIMEContext() const + { + return mNativeContext; + } + + /** + * This is called when IMEStateManager stops managing the instance. + */ + void Destroy(); + + /** + * Request to commit (or cancel) the composition to IME. This method should + * be called only by IMEStateManager::NotifyIME(). + */ + nsresult RequestToCommit(nsIWidget* aWidget, bool aDiscard); + + /** + * Send a notification to IME. It depends on the IME or platform spec what + * will occur (or not occur). + */ + nsresult NotifyIME(widget::IMEMessage aMessage); + + /** + * the offset of first composition string + */ + uint32_t NativeOffsetOfStartComposition() const + { + return mCompositionStartOffset; + } + + /** + * the offset of first selected clause or start of composition + */ + uint32_t NativeOffsetOfTargetClause() const + { + return mCompositionStartOffset + mTargetClauseOffsetInComposition; + } + + /** + * Returns true if there is non-empty composition string and it's not fixed. + * Otherwise, false. + */ + bool IsComposing() const { return mIsComposing; } + + /** + * Returns true while editor is handling an event which is modifying the + * composition string. + */ + bool IsEditorHandlingEvent() const + { + return mIsEditorHandlingEvent; + } + + /** + * StartHandlingComposition() and EndHandlingComposition() are called by + * editor when it holds a TextComposition instance and release it. + */ + void StartHandlingComposition(nsIEditor* aEditor); + void EndHandlingComposition(nsIEditor* aEditor); + + /** + * OnEditorDestroyed() is called when the editor is destroyed but there is + * active composition. + */ + void OnEditorDestroyed(); + + /** + * CompositionChangeEventHandlingMarker class should be created at starting + * to handle text event in focused editor. This calls + * EditorWillHandleCompositionChangeEvent() and + * EditorDidHandleCompositionChangeEvent() automatically. + */ + class MOZ_STACK_CLASS CompositionChangeEventHandlingMarker + { + public: + CompositionChangeEventHandlingMarker( + TextComposition* aComposition, + const WidgetCompositionEvent* aCompositionChangeEvent) + : mComposition(aComposition) + { + mComposition->EditorWillHandleCompositionChangeEvent( + aCompositionChangeEvent); + } + + ~CompositionChangeEventHandlingMarker() + { + mComposition->EditorDidHandleCompositionChangeEvent(); + } + + private: + RefPtr<TextComposition> mComposition; + CompositionChangeEventHandlingMarker(); + CompositionChangeEventHandlingMarker( + const CompositionChangeEventHandlingMarker& aOther); + }; + +private: + // Private destructor, to discourage deletion outside of Release(): + ~TextComposition() + { + // WARNING: mPresContext may be destroying, so, be careful if you touch it. + } + + // sHandlingSelectionEvent is true while TextComposition sends a selection + // event to ContentEventHandler. + static bool sHandlingSelectionEvent; + + // This class holds nsPresContext weak. This instance shouldn't block + // destroying it. When the presContext is being destroyed, it's notified to + // IMEStateManager::OnDestroyPresContext(), and then, it destroy + // this instance. + nsPresContext* mPresContext; + nsCOMPtr<nsINode> mNode; + RefPtr<TabParent> mTabParent; + + // This is the clause and caret range information which is managed by + // the focused editor. This may be null if there is no clauses or caret. + RefPtr<TextRangeArray> mRanges; + // Same as mRange, but mRange will have old data during compositionupdate. + // So this will be valied during compositionupdate. + RefPtr<TextRangeArray> mLastRanges; + + // mNativeContext stores a opaque pointer. This works as the "ID" for this + // composition. Don't access the instance, it may not be available. + widget::NativeIMEContext mNativeContext; + + // mEditorWeak is a weak reference to the focused editor handling composition. + nsWeakPtr mEditorWeak; + + // mLastData stores the data attribute of the latest composition event (except + // the compositionstart event). + nsString mLastData; + + // mString stores the composition text which has been handled by the focused + // editor. + nsString mString; + + // Offset of the composition string from start of the editor + uint32_t mCompositionStartOffset; + // Offset of the selected clause of the composition string from + // mCompositionStartOffset + uint32_t mTargetClauseOffsetInComposition; + + // See the comment for IsSynthesizedForTests(). + bool mIsSynthesizedForTests; + + // See the comment for IsComposing(). + bool mIsComposing; + + // mIsEditorHandlingEvent is true while editor is modifying the composition + // string. + bool mIsEditorHandlingEvent; + + // mIsRequestingCommit or mIsRequestingCancel is true *only* while we're + // requesting commit or canceling the composition. In other words, while + // one of these values is true, we're handling the request. + bool mIsRequestingCommit; + bool mIsRequestingCancel; + + // mRequestedToCommitOrCancel is true *after* we requested IME to commit or + // cancel the composition. In other words, we already requested of IME that + // it commits or cancels current composition. + // NOTE: Before this is set true, both mIsRequestingCommit and + // mIsRequestingCancel are set false. + bool mRequestedToCommitOrCancel; + + // mWasNativeCompositionEndEventDiscarded is true if this composition was + // requested commit or cancel itself but native compositionend event is + // discarded by PresShell due to not safe to dispatch events. + bool mWasNativeCompositionEndEventDiscarded; + + // Allow control characters appear in composition string. + // When this is false, control characters except + // CHARACTER TABULATION (horizontal tab) are removed from + // both composition string and data attribute of compositionupdate + // and compositionend events. + bool mAllowControlCharacters; + + // mWasCompositionStringEmpty is true if the composition string was empty + // when DispatchCompositionEvent() is called. + bool mWasCompositionStringEmpty; + + // Hide the default constructor and copy constructor. + TextComposition() + : mPresContext(nullptr) + , mNativeContext(nullptr) + , mCompositionStartOffset(0) + , mTargetClauseOffsetInComposition(0) + , mIsSynthesizedForTests(false) + , mIsComposing(false) + , mIsEditorHandlingEvent(false) + , mIsRequestingCommit(false) + , mIsRequestingCancel(false) + , mRequestedToCommitOrCancel(false) + , mWasNativeCompositionEndEventDiscarded(false) + , mAllowControlCharacters(false) + , mWasCompositionStringEmpty(true) + {} + TextComposition(const TextComposition& aOther); + + /** + * GetEditor() returns nsIEditor pointer of mEditorWeak. + */ + already_AddRefed<nsIEditor> GetEditor() const; + + /** + * HasEditor() returns true if mEditorWeak holds nsIEditor instance which is + * alive. Otherwise, false. + */ + bool HasEditor() const; + + /** + * EditorWillHandleCompositionChangeEvent() must be called before the focused + * editor handles the compositionchange event. + */ + void EditorWillHandleCompositionChangeEvent( + const WidgetCompositionEvent* aCompositionChangeEvent); + + /** + * EditorDidHandleCompositionChangeEvent() must be called after the focused + * editor handles a compositionchange event. + */ + void EditorDidHandleCompositionChangeEvent(); + + /** + * IsValidStateForComposition() returns true if it's safe to dispatch an event + * to the DOM tree. Otherwise, false. + * WARNING: This doesn't check script blocker state. It should be checked + * before dispatching the first event. + */ + bool IsValidStateForComposition(nsIWidget* aWidget) const; + + /** + * DispatchCompositionEvent() dispatches the aCompositionEvent to the mContent + * synchronously. The caller must ensure that it's safe to dispatch the event. + */ + void DispatchCompositionEvent(WidgetCompositionEvent* aCompositionEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack, + bool aIsSynthesized); + + /** + * Simply calling EventDispatcher::Dispatch() with plugin event. + * If dispatching event has no orginal clone, aOriginalEvent can be null. + */ + void DispatchEvent(WidgetCompositionEvent* aDispatchEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallback, + const WidgetCompositionEvent *aOriginalEvent = nullptr); + + /** + * HandleSelectionEvent() sends the selection event to ContentEventHandler + * or dispatches it to the focused child process. + */ + void HandleSelectionEvent(WidgetSelectionEvent* aSelectionEvent) + { + HandleSelectionEvent(mPresContext, mTabParent, aSelectionEvent); + } + static void HandleSelectionEvent(nsPresContext* aPresContext, + TabParent* aTabParent, + WidgetSelectionEvent* aSelectionEvent); + + /** + * MaybeDispatchCompositionUpdate() may dispatch a compositionupdate event + * if aCompositionEvent changes composition string. + * @return Returns false if dispatching the compositionupdate event caused + * destroying this composition. + */ + bool MaybeDispatchCompositionUpdate( + const WidgetCompositionEvent* aCompositionEvent); + + /** + * CloneAndDispatchAs() dispatches a composition event which is + * duplicateed from aCompositionEvent and set the aMessage. + * + * @return Returns BaseEventFlags which is the result of dispatched event. + */ + BaseEventFlags CloneAndDispatchAs( + const WidgetCompositionEvent* aCompositionEvent, + EventMessage aMessage, + nsEventStatus* aStatus = nullptr, + EventDispatchingCallback* aCallBack = nullptr); + + /** + * If IME has already dispatched compositionend event but it was discarded + * by PresShell due to not safe to dispatch, this returns true. + */ + bool WasNativeCompositionEndEventDiscarded() const + { + return mWasNativeCompositionEndEventDiscarded; + } + + /** + * OnCompositionEventDiscarded() is called when PresShell discards + * compositionupdate, compositionend or compositionchange event due to not + * safe to dispatch event. + */ + void OnCompositionEventDiscarded(WidgetCompositionEvent* aCompositionEvent); + + /** + * OnCompositionEventDispatched() is called after a composition event is + * dispatched. + */ + void OnCompositionEventDispatched( + const WidgetCompositionEvent* aDispatchEvent); + + /** + * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition + * event handled. This should be called after dispatching a composition + * event which came from widget. + */ + void MaybeNotifyIMEOfCompositionEventHandled( + const WidgetCompositionEvent* aCompositionEvent); + + /** + * GetSelectionStartOffset() returns normal selection start offset in the + * editor which has this composition. + * If it failed or lost focus, this would return 0. + */ + uint32_t GetSelectionStartOffset(); + + /** + * OnStartOffsetUpdatedInChild() is called when composition start offset + * is updated in the child process. I.e., this is called and never called + * if the composition is in this process. + * @param aStartOffset New composition start offset with native + * linebreaks. + */ + void OnStartOffsetUpdatedInChild(uint32_t aStartOffset); + + /** + * CompositionEventDispatcher dispatches the specified composition (or text) + * event. + */ + class CompositionEventDispatcher : public Runnable + { + public: + CompositionEventDispatcher(TextComposition* aTextComposition, + nsINode* aEventTarget, + EventMessage aEventMessage, + const nsAString& aData, + bool aIsSynthesizedEvent = false); + NS_IMETHOD Run() override; + + private: + RefPtr<TextComposition> mTextComposition; + nsCOMPtr<nsINode> mEventTarget; + nsString mData; + EventMessage mEventMessage; + bool mIsSynthesizedEvent; + + CompositionEventDispatcher() : mIsSynthesizedEvent(false) {}; + }; + + /** + * DispatchCompositionEventRunnable() dispatches a composition event to the + * content. Be aware, if you use this method, nsPresShellEventCB isn't used. + * That means that nsIFrame::HandleEvent() is never called. + * WARNING: The instance which is managed by IMEStateManager may be + * destroyed by this method call. + * + * @param aEventMessage Must be one of composition events. + * @param aData Used for mData value. + * @param aIsSynthesizingCommit true if this is called for synthesizing + * commit or cancel composition. Otherwise, + * false. + */ + void DispatchCompositionEventRunnable(EventMessage aEventMessage, + const nsAString& aData, + bool aIsSynthesizingCommit = false); +}; + +/** + * TextCompositionArray manages the instances of TextComposition class. + * Managing with array is enough because only one composition is typically + * there. Even if user switches native IME context, it's very rare that + * second or more composition is started. + * It's assumed that this is used by IMEStateManager for storing all active + * compositions in the process. If the instance is it, each TextComposition + * in the array can be destroyed by calling some methods of itself. + */ + +class TextCompositionArray final : + public AutoTArray<RefPtr<TextComposition>, 2> +{ +public: + // Looking for per native IME context. + index_type IndexOf(const widget::NativeIMEContext& aNativeIMEContext); + index_type IndexOf(nsIWidget* aWidget); + + TextComposition* GetCompositionFor(nsIWidget* aWidget); + TextComposition* GetCompositionFor( + const WidgetCompositionEvent* aCompositionEvent); + + // Looking for per nsPresContext + index_type IndexOf(nsPresContext* aPresContext); + index_type IndexOf(nsPresContext* aPresContext, nsINode* aNode); + + TextComposition* GetCompositionFor(nsPresContext* aPresContext); + TextComposition* GetCompositionFor(nsPresContext* aPresContext, + nsINode* aNode); + TextComposition* GetCompositionInContent(nsPresContext* aPresContext, + nsIContent* aContent); +}; + +} // namespace mozilla + +#endif // #ifndef mozilla_TextComposition_h diff --git a/dom/events/Touch.cpp b/dom/events/Touch.cpp new file mode 100644 index 000000000..a538fa695 --- /dev/null +++ b/dom/events/Touch.cpp @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/Touch.h" + +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/TouchEvent.h" +#include "nsGlobalWindow.h" +#include "nsContentUtils.h" +#include "nsIContent.h" + +namespace mozilla { +namespace dom { + +// static +already_AddRefed<Touch> +Touch::Constructor(const GlobalObject& aGlobal, + const TouchInit& aParam, + ErrorResult& aRv) +{ + // Annoyingly many parameters, make sure the ordering is the same as in the + // Touch constructor. + RefPtr<Touch> touch = new Touch(aParam.mTarget, + aParam.mIdentifier, + aParam.mPageX, + aParam.mPageY, + aParam.mScreenX, + aParam.mScreenY, + aParam.mClientX, + aParam.mClientY, + aParam.mRadiusX, + aParam.mRadiusY, + aParam.mRotationAngle, + aParam.mForce); + return touch.forget(); +} + +Touch::Touch(EventTarget* aTarget, + int32_t aIdentifier, + int32_t aPageX, + int32_t aPageY, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + int32_t aRadiusX, + int32_t aRadiusY, + float aRotationAngle, + float aForce) +{ + mTarget = aTarget; + mIdentifier = aIdentifier; + mPagePoint = CSSIntPoint(aPageX, aPageY); + mScreenPoint = CSSIntPoint(aScreenX, aScreenY); + mClientPoint = CSSIntPoint(aClientX, aClientY); + mRefPoint = LayoutDeviceIntPoint(0, 0); + mPointsInitialized = true; + mRadius.x = aRadiusX; + mRadius.y = aRadiusY; + mRotationAngle = aRotationAngle; + mForce = aForce; + + mChanged = false; + mMessage = 0; + nsJSContext::LikelyShortLivingObjectCreated(); +} + +Touch::Touch(int32_t aIdentifier, + LayoutDeviceIntPoint aPoint, + LayoutDeviceIntPoint aRadius, + float aRotationAngle, + float aForce) +{ + mIdentifier = aIdentifier; + mPagePoint = CSSIntPoint(0, 0); + mScreenPoint = CSSIntPoint(0, 0); + mClientPoint = CSSIntPoint(0, 0); + mRefPoint = aPoint; + mPointsInitialized = false; + mRadius = aRadius; + mRotationAngle = aRotationAngle; + mForce = aForce; + + mChanged = false; + mMessage = 0; + nsJSContext::LikelyShortLivingObjectCreated(); +} + +Touch::Touch(const Touch& aOther) + : mTarget(aOther.mTarget) + , mRefPoint(aOther.mRefPoint) + , mChanged(aOther.mChanged) + , mMessage(aOther.mMessage) + , mIdentifier(aOther.mIdentifier) + , mPagePoint(aOther.mPagePoint) + , mClientPoint(aOther.mClientPoint) + , mScreenPoint(aOther.mScreenPoint) + , mRadius(aOther.mRadius) + , mRotationAngle(aOther.mRotationAngle) + , mForce(aOther.mForce) + , mPointsInitialized(aOther.mPointsInitialized) +{ + nsJSContext::LikelyShortLivingObjectCreated(); +} + +Touch::~Touch() +{ +} + +// static +bool +Touch::PrefEnabled(JSContext* aCx, JSObject* aGlobal) +{ + return TouchEvent::PrefEnabled(aCx, aGlobal); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Touch, mTarget) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Touch) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Touch) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Touch) + +EventTarget* +Touch::GetTarget() const +{ + nsCOMPtr<nsIContent> content = do_QueryInterface(mTarget); + if (content && content->ChromeOnlyAccess() && + !nsContentUtils::LegacyIsCallerNativeCode() && + !nsContentUtils::CanAccessNativeAnon()) { + return content->FindFirstNonChromeOnlyAccessContent(); + } + + return mTarget; +} + +void +Touch::InitializePoints(nsPresContext* aPresContext, WidgetEvent* aEvent) +{ + if (mPointsInitialized) { + return; + } + mClientPoint = Event::GetClientCoords( + aPresContext, aEvent, mRefPoint, mClientPoint); + mPagePoint = Event::GetPageCoords( + aPresContext, aEvent, mRefPoint, mClientPoint); + mScreenPoint = Event::GetScreenCoords(aPresContext, aEvent, mRefPoint); + mPointsInitialized = true; +} + +void +Touch::SetTarget(EventTarget* aTarget) +{ + mTarget = aTarget; +} + +bool +Touch::Equals(Touch* aTouch) +{ + return mRefPoint == aTouch->mRefPoint && + mForce == aTouch->Force() && + mRotationAngle == aTouch->RotationAngle() && + mRadius.x == aTouch->RadiusX() && + mRadius.y == aTouch->RadiusY(); +} + +JSObject* +Touch::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return TouchBinding::Wrap(aCx, this, aGivenProto); +} + +// Parent ourselves to the global of the target. This achieves the desirable +// effects of parenting to the target, but avoids making the touch inaccessible +// when the target happens to be NAC and therefore reflected into the XBL scope. +nsIGlobalObject* +Touch::GetParentObject() +{ + if (!mTarget) { + return nullptr; + } + return mTarget->GetOwnerGlobal(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/events/Touch.h b/dom/events/Touch.h new file mode 100644 index 000000000..f98f7f951 --- /dev/null +++ b/dom/events/Touch.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_Touch_h_ +#define mozilla_dom_Touch_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/dom/TouchBinding.h" +#include "nsWrapperCache.h" +#include "Units.h" + +class nsPresContext; + +namespace mozilla { +namespace dom { + +class EventTarget; + +class Touch final : public nsISupports + , public nsWrapperCache + , public WidgetPointerHelper +{ +public: + static bool PrefEnabled(JSContext* aCx, JSObject* aGlobal); + + static already_AddRefed<Touch> Constructor(const GlobalObject& aGlobal, + const TouchInit& aParam, + ErrorResult& aRv); + + Touch(EventTarget* aTarget, + int32_t aIdentifier, + int32_t aPageX, + int32_t aPageY, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + int32_t aRadiusX, + int32_t aRadiusY, + float aRotationAngle, + float aForce); + Touch(int32_t aIdentifier, + LayoutDeviceIntPoint aPoint, + LayoutDeviceIntPoint aRadius, + float aRotationAngle, + float aForce); + Touch(const Touch& aOther); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Touch) + + void InitializePoints(nsPresContext* aPresContext, WidgetEvent* aEvent); + + void SetTarget(EventTarget* aTarget); + + bool Equals(Touch* aTouch); + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsIGlobalObject* GetParentObject(); + + // WebIDL + int32_t Identifier() const { return mIdentifier; } + EventTarget* GetTarget() const; + int32_t ScreenX() const { return mScreenPoint.x; } + int32_t ScreenY() const { return mScreenPoint.y; } + int32_t ClientX() const { return mClientPoint.x; } + int32_t ClientY() const { return mClientPoint.y; } + int32_t PageX() const { return mPagePoint.x; } + int32_t PageY() const { return mPagePoint.y; } + int32_t RadiusX() const { return mRadius.x; } + int32_t RadiusY() const { return mRadius.y; } + float RotationAngle() const { return mRotationAngle; } + float Force() const { return mForce; } + + nsCOMPtr<EventTarget> mTarget; + LayoutDeviceIntPoint mRefPoint; + bool mChanged; + uint32_t mMessage; + int32_t mIdentifier; + CSSIntPoint mPagePoint; + CSSIntPoint mClientPoint; + CSSIntPoint mScreenPoint; + LayoutDeviceIntPoint mRadius; + float mRotationAngle; + float mForce; +protected: + ~Touch(); + + bool mPointsInitialized; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Touch_h_ diff --git a/dom/events/TouchEvent.cpp b/dom/events/TouchEvent.cpp new file mode 100644 index 000000000..cc9684eb3 --- /dev/null +++ b/dom/events/TouchEvent.cpp @@ -0,0 +1,303 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/TouchEvent.h" +#include "mozilla/dom/Touch.h" +#include "mozilla/dom/TouchListBinding.h" +#include "mozilla/Preferences.h" +#include "mozilla/TouchEvents.h" +#include "nsContentUtils.h" +#include "nsIDocShell.h" +#include "mozilla/WidgetUtils.h" + +namespace mozilla { + +namespace dom { + +/****************************************************************************** + * TouchList + *****************************************************************************/ + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TouchList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TouchList, mParent, mPoints) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TouchList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TouchList) + +JSObject* +TouchList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return TouchListBinding::Wrap(aCx, this, aGivenProto); +} + +// static +bool +TouchList::PrefEnabled(JSContext* aCx, JSObject* aGlobal) +{ + return TouchEvent::PrefEnabled(aCx, aGlobal); +} + +/****************************************************************************** + * TouchEvent + *****************************************************************************/ + +TouchEvent::TouchEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetTouchEvent* aEvent) + : UIEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetTouchEvent(false, eVoidEvent, nullptr)) +{ + if (aEvent) { + mEventIsInternal = false; + + for (uint32_t i = 0; i < aEvent->mTouches.Length(); ++i) { + Touch* touch = aEvent->mTouches[i]; + touch->InitializePoints(mPresContext, aEvent); + } + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(TouchEvent, UIEvent, + mTouches, + mTargetTouches, + mChangedTouches) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TouchEvent) +NS_INTERFACE_MAP_END_INHERITING(UIEvent) + +NS_IMPL_ADDREF_INHERITED(TouchEvent, UIEvent) +NS_IMPL_RELEASE_INHERITED(TouchEvent, UIEvent) + +void +TouchEvent::InitTouchEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + TouchList* aTouches, + TouchList* aTargetTouches, + TouchList* aChangedTouches) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); + mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey, + aShiftKey, aMetaKey); + mTouches = aTouches; + mTargetTouches = aTargetTouches; + mChangedTouches = aChangedTouches; +} + +TouchList* +TouchEvent::Touches() +{ + if (!mTouches) { + WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent(); + if (mEvent->mMessage == eTouchEnd || mEvent->mMessage == eTouchCancel) { + // for touchend events, remove any changed touches from mTouches + WidgetTouchEvent::AutoTouchArray unchangedTouches; + const WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; + for (uint32_t i = 0; i < touches.Length(); ++i) { + if (!touches[i]->mChanged) { + unchangedTouches.AppendElement(touches[i]); + } + } + mTouches = new TouchList(ToSupports(this), unchangedTouches); + } else { + mTouches = new TouchList(ToSupports(this), touchEvent->mTouches); + } + } + return mTouches; +} + +TouchList* +TouchEvent::TargetTouches() +{ + if (!mTargetTouches) { + WidgetTouchEvent::AutoTouchArray targetTouches; + WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent(); + const WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; + for (uint32_t i = 0; i < touches.Length(); ++i) { + // for touchend/cancel events, don't append to the target list if this is a + // touch that is ending + if ((mEvent->mMessage != eTouchEnd && mEvent->mMessage != eTouchCancel) || + !touches[i]->mChanged) { + if (touches[i]->mTarget == mEvent->mOriginalTarget) { + targetTouches.AppendElement(touches[i]); + } + } + } + mTargetTouches = new TouchList(ToSupports(this), targetTouches); + } + return mTargetTouches; +} + +TouchList* +TouchEvent::ChangedTouches() +{ + if (!mChangedTouches) { + WidgetTouchEvent::AutoTouchArray changedTouches; + WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent(); + const WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; + for (uint32_t i = 0; i < touches.Length(); ++i) { + if (touches[i]->mChanged) { + changedTouches.AppendElement(touches[i]); + } + } + mChangedTouches = new TouchList(ToSupports(this), changedTouches); + } + return mChangedTouches; +} + +// static +bool +TouchEvent::PrefEnabled(JSContext* aCx, JSObject* aGlobal) +{ + nsIDocShell* docShell = nullptr; + if (aGlobal) { + nsGlobalWindow* win = xpc::WindowOrNull(aGlobal); + if (win) { + docShell = win->GetDocShell(); + } + } + return PrefEnabled(docShell); +} + +// static +bool +TouchEvent::PrefEnabled(nsIDocShell* aDocShell) +{ + static bool sPrefCached = false; + static int32_t sPrefCacheValue = 0; + + uint32_t touchEventsOverride = nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE; + if (aDocShell) { + aDocShell->GetTouchEventsOverride(&touchEventsOverride); + } + + if (!sPrefCached) { + sPrefCached = true; + Preferences::AddIntVarCache(&sPrefCacheValue, "dom.w3c_touch_events.enabled"); + } + + bool enabled = false; + if (touchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_ENABLED) { + enabled = true; + } else if (touchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_DISABLED) { + enabled = false; + } else { + if (sPrefCacheValue == 2) { +#if defined(MOZ_B2G) || defined(MOZ_WIDGET_ANDROID) + // Touch support is always enabled on B2G and android. + enabled = true; +#elif defined(XP_WIN) || MOZ_WIDGET_GTK == 3 + static bool sDidCheckTouchDeviceSupport = false; + static bool sIsTouchDeviceSupportPresent = false; + // On Windows and GTK3 we auto-detect based on device support. + if (!sDidCheckTouchDeviceSupport) { + sDidCheckTouchDeviceSupport = true; + sIsTouchDeviceSupportPresent = WidgetUtils::IsTouchDeviceSupportPresent(); + } + enabled = sIsTouchDeviceSupportPresent; +#else + enabled = false; +#endif + } else { + enabled = !!sPrefCacheValue; + } + } + + if (enabled) { + nsContentUtils::InitializeTouchEventTable(); + } + return enabled; +} + +// static +already_AddRefed<Event> +TouchEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const TouchEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<TouchEvent> e = new TouchEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + RefPtr<TouchList> touches = e->CopyTouches(aParam.mTouches); + RefPtr<TouchList> targetTouches = e->CopyTouches(aParam.mTargetTouches); + RefPtr<TouchList> changedTouches = e->CopyTouches(aParam.mChangedTouches); + e->InitTouchEvent(aType, aParam.mBubbles, aParam.mCancelable, aParam.mView, + aParam.mDetail, aParam.mCtrlKey, aParam.mAltKey, + aParam.mShiftKey, aParam.mMetaKey, touches, targetTouches, + changedTouches); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + + +already_AddRefed<TouchList> +TouchEvent::CopyTouches(const Sequence<OwningNonNull<Touch>>& aTouches) +{ + RefPtr<TouchList> list = new TouchList(GetParentObject()); + size_t len = aTouches.Length(); + for (size_t i = 0; i < len; ++i) { + list->Append(aTouches[i]); + } + return list.forget(); +} + +bool +TouchEvent::AltKey() +{ + return mEvent->AsTouchEvent()->IsAlt(); +} + +bool +TouchEvent::MetaKey() +{ + return mEvent->AsTouchEvent()->IsMeta(); +} + +bool +TouchEvent::CtrlKey() +{ + return mEvent->AsTouchEvent()->IsControl(); +} + +bool +TouchEvent::ShiftKey() +{ + return mEvent->AsTouchEvent()->IsShift(); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<TouchEvent> +NS_NewDOMTouchEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetTouchEvent* aEvent) +{ + RefPtr<TouchEvent> it = new TouchEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/TouchEvent.h b/dom/events/TouchEvent.h new file mode 100644 index 000000000..c92e9a490 --- /dev/null +++ b/dom/events/TouchEvent.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_dom_TouchEvent_h_ +#define mozilla_dom_TouchEvent_h_ + +#include "mozilla/dom/Touch.h" +#include "mozilla/dom/TouchEventBinding.h" +#include "mozilla/dom/UIEvent.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TouchEvents.h" +#include "nsJSEnvironment.h" +#include "nsWrapperCache.h" + +class nsAString; + +namespace mozilla { +namespace dom { + +class TouchList final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TouchList) + + explicit TouchList(nsISupports* aParent) + : mParent(aParent) + { + nsJSContext::LikelyShortLivingObjectCreated(); + } + TouchList(nsISupports* aParent, + const WidgetTouchEvent::TouchArray& aTouches) + : mParent(aParent) + , mPoints(aTouches) + { + nsJSContext::LikelyShortLivingObjectCreated(); + } + + void Append(Touch* aPoint) + { + mPoints.AppendElement(aPoint); + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsISupports* GetParentObject() const + { + return mParent; + } + + static bool PrefEnabled(JSContext* aCx, JSObject* aGlobal); + + uint32_t Length() const + { + return mPoints.Length(); + } + Touch* Item(uint32_t aIndex) const + { + return mPoints.SafeElementAt(aIndex); + } + Touch* IndexedGetter(uint32_t aIndex, bool& aFound) const + { + aFound = aIndex < mPoints.Length(); + if (!aFound) { + return nullptr; + } + return mPoints[aIndex]; + } + +protected: + ~TouchList() {} + + nsCOMPtr<nsISupports> mParent; + WidgetTouchEvent::TouchArray mPoints; +}; + +class TouchEvent : public UIEvent +{ +public: + TouchEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetTouchEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TouchEvent, UIEvent) + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return TouchEventBinding::Wrap(aCx, this, aGivenProto); + } + + already_AddRefed<TouchList> + CopyTouches(const Sequence<OwningNonNull<Touch>>& aTouches); + + TouchList* Touches(); + TouchList* TargetTouches(); + TouchList* ChangedTouches(); + + bool AltKey(); + bool MetaKey(); + bool CtrlKey(); + bool ShiftKey(); + + void InitTouchEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + TouchList* aTouches, + TouchList* aTargetTouches, + TouchList* aChangedTouches); + + static bool PrefEnabled(JSContext* aCx, JSObject* aGlobal); + static bool PrefEnabled(nsIDocShell* aDocShell); + + static already_AddRefed<Event> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const TouchEventInit& aParam, + ErrorResult& aRv); + +protected: + ~TouchEvent() {} + + RefPtr<TouchList> mTouches; + RefPtr<TouchList> mTargetTouches; + RefPtr<TouchList> mChangedTouches; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::TouchEvent> +NS_NewDOMTouchEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetTouchEvent* aEvent); + +#endif // mozilla_dom_TouchEvent_h_ diff --git a/dom/events/TransitionEvent.cpp b/dom/events/TransitionEvent.cpp new file mode 100644 index 000000000..2490b31f1 --- /dev/null +++ b/dom/events/TransitionEvent.cpp @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TransitionEvent.h" +#include "mozilla/ContentEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +TransitionEvent::TransitionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalTransitionEvent* aEvent) + : Event(aOwner, aPresContext, + aEvent ? aEvent : new InternalTransitionEvent(false, eVoidEvent)) +{ + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +NS_INTERFACE_MAP_BEGIN(TransitionEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMTransitionEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +NS_IMPL_ADDREF_INHERITED(TransitionEvent, Event) +NS_IMPL_RELEASE_INHERITED(TransitionEvent, Event) + +// static +already_AddRefed<TransitionEvent> +TransitionEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const TransitionEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<TransitionEvent> e = new TransitionEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + + e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable); + + InternalTransitionEvent* internalEvent = e->mEvent->AsTransitionEvent(); + internalEvent->mPropertyName = aParam.mPropertyName; + internalEvent->mElapsedTime = aParam.mElapsedTime; + internalEvent->mPseudoElement = aParam.mPseudoElement; + + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +NS_IMETHODIMP +TransitionEvent::GetPropertyName(nsAString& aPropertyName) +{ + aPropertyName = mEvent->AsTransitionEvent()->mPropertyName; + return NS_OK; +} + +NS_IMETHODIMP +TransitionEvent::GetElapsedTime(float* aElapsedTime) +{ + *aElapsedTime = ElapsedTime(); + return NS_OK; +} + +float +TransitionEvent::ElapsedTime() +{ + return mEvent->AsTransitionEvent()->mElapsedTime; +} + +NS_IMETHODIMP +TransitionEvent::GetPseudoElement(nsAString& aPseudoElement) +{ + aPseudoElement = mEvent->AsTransitionEvent()->mPseudoElement; + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<TransitionEvent> +NS_NewDOMTransitionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalTransitionEvent* aEvent) +{ + RefPtr<TransitionEvent> it = + new TransitionEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/TransitionEvent.h b/dom/events/TransitionEvent.h new file mode 100644 index 000000000..345181a01 --- /dev/null +++ b/dom/events/TransitionEvent.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_dom_TransitionEvent_h_ +#define mozilla_dom_TransitionEvent_h_ + +#include "mozilla/EventForwards.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/TransitionEventBinding.h" +#include "nsIDOMTransitionEvent.h" + +class nsAString; + +namespace mozilla { +namespace dom { + +class TransitionEvent : public Event, + public nsIDOMTransitionEvent +{ +public: + TransitionEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + InternalTransitionEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_TO_EVENT + NS_DECL_NSIDOMTRANSITIONEVENT + + static already_AddRefed<TransitionEvent> + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const TransitionEventInit& aParam, + ErrorResult& aRv); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return TransitionEventBinding::Wrap(aCx, this, aGivenProto); + } + + // xpidl implementation + // GetPropertyName(nsAString& aPropertyName) + // GetPseudoElement(nsAString& aPreudoElement) + + float ElapsedTime(); + +protected: + ~TransitionEvent() {} +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::TransitionEvent> +NS_NewDOMTransitionEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::InternalTransitionEvent* aEvent); + +#endif // mozilla_dom_TransitionEvent_h_ diff --git a/dom/events/UIEvent.cpp b/dom/events/UIEvent.cpp new file mode 100644 index 000000000..561bf4a26 --- /dev/null +++ b/dom/events/UIEvent.cpp @@ -0,0 +1,512 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/dom/UIEvent.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/TextEvents.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDocShell.h" +#include "nsIDOMWindow.h" +#include "nsIDOMNode.h" +#include "nsIFrame.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +UIEvent::UIEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetGUIEvent* aEvent) + : Event(aOwner, aPresContext, + aEvent ? aEvent : new InternalUIEvent(false, eVoidEvent, nullptr)) + , mClientPoint(0, 0) + , mLayerPoint(0, 0) + , mPagePoint(0, 0) + , mMovementPoint(0, 0) + , mIsPointerLocked(EventStateManager::sIsPointerLocked) + , mLastClientPoint(EventStateManager::sLastClientPoint) +{ + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } + + // Fill mDetail and mView according to the mEvent (widget-generated + // event) we've got + switch(mEvent->mClass) { + case eUIEventClass: + { + mDetail = mEvent->AsUIEvent()->mDetail; + break; + } + + case eScrollPortEventClass: + { + InternalScrollPortEvent* scrollEvent = mEvent->AsScrollPortEvent(); + mDetail = static_cast<int32_t>(scrollEvent->mOrient); + break; + } + + default: + mDetail = 0; + break; + } + + mView = nullptr; + if (mPresContext) + { + nsIDocShell* docShell = mPresContext->GetDocShell(); + if (docShell) + { + mView = docShell->GetWindow(); + } + } +} + +// static +already_AddRefed<UIEvent> +UIEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const UIEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<UIEvent> e = new UIEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitUIEvent(aType, aParam.mBubbles, aParam.mCancelable, aParam.mView, + aParam.mDetail); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(UIEvent, Event, + mView) + +NS_IMPL_ADDREF_INHERITED(UIEvent, Event) +NS_IMPL_RELEASE_INHERITED(UIEvent, Event) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(UIEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMUIEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +static nsIntPoint +DevPixelsToCSSPixels(const LayoutDeviceIntPoint& aPoint, + nsPresContext* aContext) +{ + return nsIntPoint(aContext->DevPixelsToIntCSSPixels(aPoint.x), + aContext->DevPixelsToIntCSSPixels(aPoint.y)); +} + +nsIntPoint +UIEvent::GetMovementPoint() +{ + if (mPrivateDataDuplicated || mEventIsInternal) { + return mMovementPoint; + } + + if (!mEvent || + (mEvent->mClass != eMouseEventClass && + mEvent->mClass != eMouseScrollEventClass && + mEvent->mClass != eWheelEventClass && + mEvent->mClass != eDragEventClass && + mEvent->mClass != ePointerEventClass && + mEvent->mClass != eSimpleGestureEventClass) || + !mEvent->AsGUIEvent()->mWidget) { + return nsIntPoint(0, 0); + } + + // Calculate the delta between the last screen point and the current one. + nsIntPoint current = DevPixelsToCSSPixels(mEvent->mRefPoint, mPresContext); + nsIntPoint last = DevPixelsToCSSPixels(mEvent->mLastRefPoint, mPresContext); + return current - last; +} + +NS_IMETHODIMP +UIEvent::GetView(mozIDOMWindowProxy** aView) +{ + *aView = mView; + NS_IF_ADDREF(*aView); + return NS_OK; +} + +NS_IMETHODIMP +UIEvent::GetDetail(int32_t* aDetail) +{ + *aDetail = mDetail; + return NS_OK; +} + +void +UIEvent::InitUIEvent(const nsAString& typeArg, + bool canBubbleArg, + bool cancelableArg, + nsGlobalWindow* viewArg, + int32_t detailArg) +{ + auto* view = viewArg ? viewArg->AsInner() : nullptr; + InitUIEvent(typeArg, canBubbleArg, cancelableArg, view, detailArg); +} + +NS_IMETHODIMP +UIEvent::InitUIEvent(const nsAString& typeArg, + bool canBubbleArg, + bool cancelableArg, + mozIDOMWindow* viewArg, + int32_t detailArg) +{ + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + + Event::InitEvent(typeArg, canBubbleArg, cancelableArg); + + mDetail = detailArg; + mView = viewArg ? nsPIDOMWindowInner::From(viewArg)->GetOuterWindow() : + nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +UIEvent::GetPageX(int32_t* aPageX) +{ + NS_ENSURE_ARG_POINTER(aPageX); + *aPageX = PageX(); + return NS_OK; +} + +int32_t +UIEvent::PageX() const +{ + if (mPrivateDataDuplicated) { + return mPagePoint.x; + } + + return Event::GetPageCoords(mPresContext, mEvent, mEvent->mRefPoint, + mClientPoint).x; +} + +NS_IMETHODIMP +UIEvent::GetPageY(int32_t* aPageY) +{ + NS_ENSURE_ARG_POINTER(aPageY); + *aPageY = PageY(); + return NS_OK; +} + +int32_t +UIEvent::PageY() const +{ + if (mPrivateDataDuplicated) { + return mPagePoint.y; + } + + return Event::GetPageCoords(mPresContext, mEvent, mEvent->mRefPoint, + mClientPoint).y; +} + +NS_IMETHODIMP +UIEvent::GetWhich(uint32_t* aWhich) +{ + NS_ENSURE_ARG_POINTER(aWhich); + *aWhich = Which(); + return NS_OK; +} + +already_AddRefed<nsINode> +UIEvent::GetRangeParent() +{ + nsIFrame* targetFrame = nullptr; + + if (mPresContext) { + targetFrame = mPresContext->EventStateManager()->GetEventTarget(); + } + + if (targetFrame) { + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mEvent, + targetFrame); + nsCOMPtr<nsIContent> parent = targetFrame->GetContentOffsetsFromPoint(pt).content; + if (parent) { + if (parent->ChromeOnlyAccess() && + !nsContentUtils::CanAccessNativeAnon()) { + return nullptr; + } + return parent.forget(); + } + } + + return nullptr; +} + +NS_IMETHODIMP +UIEvent::GetRangeParent(nsIDOMNode** aRangeParent) +{ + NS_ENSURE_ARG_POINTER(aRangeParent); + *aRangeParent = nullptr; + nsCOMPtr<nsINode> n = GetRangeParent(); + if (n) { + CallQueryInterface(n, aRangeParent); + } + return NS_OK; +} + +NS_IMETHODIMP +UIEvent::GetRangeOffset(int32_t* aRangeOffset) +{ + NS_ENSURE_ARG_POINTER(aRangeOffset); + *aRangeOffset = RangeOffset(); + return NS_OK; +} + +int32_t +UIEvent::RangeOffset() const +{ + if (!mPresContext) { + return 0; + } + + nsIFrame* targetFrame = mPresContext->EventStateManager()->GetEventTarget(); + if (!targetFrame) { + return 0; + } + + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mEvent, + targetFrame); + return targetFrame->GetContentOffsetsFromPoint(pt).offset; +} + +nsIntPoint +UIEvent::GetLayerPoint() const +{ + if (!mEvent || + (mEvent->mClass != eMouseEventClass && + mEvent->mClass != eMouseScrollEventClass && + mEvent->mClass != eWheelEventClass && + mEvent->mClass != ePointerEventClass && + mEvent->mClass != eTouchEventClass && + mEvent->mClass != eDragEventClass && + mEvent->mClass != eSimpleGestureEventClass) || + !mPresContext || + mEventIsInternal) { + return mLayerPoint; + } + // XXX I'm not really sure this is correct; it's my best shot, though + nsIFrame* targetFrame = mPresContext->EventStateManager()->GetEventTarget(); + if (!targetFrame) + return mLayerPoint; + nsIFrame* layer = nsLayoutUtils::GetClosestLayer(targetFrame); + nsPoint pt(nsLayoutUtils::GetEventCoordinatesRelativeTo(mEvent, layer)); + return nsIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x), + nsPresContext::AppUnitsToIntCSSPixels(pt.y)); +} + +NS_IMETHODIMP +UIEvent::GetLayerX(int32_t* aLayerX) +{ + NS_ENSURE_ARG_POINTER(aLayerX); + *aLayerX = GetLayerPoint().x; + return NS_OK; +} + +NS_IMETHODIMP +UIEvent::GetLayerY(int32_t* aLayerY) +{ + NS_ENSURE_ARG_POINTER(aLayerY); + *aLayerY = GetLayerPoint().y; + return NS_OK; +} + +NS_IMETHODIMP +UIEvent::GetIsChar(bool* aIsChar) +{ + *aIsChar = IsChar(); + return NS_OK; +} + +bool +UIEvent::IsChar() const +{ + WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent(); + return keyEvent ? keyEvent->mIsChar : false; +} + +mozilla::dom::Event* +UIEvent::AsEvent(void) +{ + return this; +} + +NS_IMETHODIMP +UIEvent::DuplicatePrivateData() +{ + mClientPoint = + Event::GetClientCoords(mPresContext, mEvent, mEvent->mRefPoint, + mClientPoint); + mMovementPoint = GetMovementPoint(); + mLayerPoint = GetLayerPoint(); + mPagePoint = + Event::GetPageCoords(mPresContext, mEvent, mEvent->mRefPoint, mClientPoint); + // GetScreenPoint converts mEvent->mRefPoint to right coordinates. + CSSIntPoint screenPoint = + Event::GetScreenCoords(mPresContext, mEvent, mEvent->mRefPoint); + nsresult rv = Event::DuplicatePrivateData(); + if (NS_SUCCEEDED(rv)) { + CSSToLayoutDeviceScale scale = mPresContext ? mPresContext->CSSToDevPixelScale() + : CSSToLayoutDeviceScale(1); + mEvent->mRefPoint = RoundedToInt(screenPoint * scale); + } + return rv; +} + +NS_IMETHODIMP_(void) +UIEvent::Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType) +{ + if (aSerializeInterfaceType) { + IPC::WriteParam(aMsg, NS_LITERAL_STRING("uievent")); + } + + Event::Serialize(aMsg, false); + + int32_t detail = 0; + GetDetail(&detail); + IPC::WriteParam(aMsg, detail); +} + +NS_IMETHODIMP_(bool) +UIEvent::Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) +{ + NS_ENSURE_TRUE(Event::Deserialize(aMsg, aIter), false); + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &mDetail), false); + return true; +} + +// XXX Following struct and array are used only in +// UIEvent::ComputeModifierState(), but if we define them in it, +// we fail to build on Mac at calling mozilla::ArrayLength(). +struct ModifierPair +{ + Modifier modifier; + const char* name; +}; +static const ModifierPair kPairs[] = { + { MODIFIER_ALT, NS_DOM_KEYNAME_ALT }, + { MODIFIER_ALTGRAPH, NS_DOM_KEYNAME_ALTGRAPH }, + { MODIFIER_CAPSLOCK, NS_DOM_KEYNAME_CAPSLOCK }, + { MODIFIER_CONTROL, NS_DOM_KEYNAME_CONTROL }, + { MODIFIER_FN, NS_DOM_KEYNAME_FN }, + { MODIFIER_FNLOCK, NS_DOM_KEYNAME_FNLOCK }, + { MODIFIER_META, NS_DOM_KEYNAME_META }, + { MODIFIER_NUMLOCK, NS_DOM_KEYNAME_NUMLOCK }, + { MODIFIER_SCROLLLOCK, NS_DOM_KEYNAME_SCROLLLOCK }, + { MODIFIER_SHIFT, NS_DOM_KEYNAME_SHIFT }, + { MODIFIER_SYMBOL, NS_DOM_KEYNAME_SYMBOL }, + { MODIFIER_SYMBOLLOCK, NS_DOM_KEYNAME_SYMBOLLOCK }, + { MODIFIER_OS, NS_DOM_KEYNAME_OS } +}; + +// static +Modifiers +UIEvent::ComputeModifierState(const nsAString& aModifiersList) +{ + if (aModifiersList.IsEmpty()) { + return 0; + } + + // Be careful about the performance. If aModifiersList is too long, + // parsing it needs too long time. + // XXX Should we abort if aModifiersList is too long? + + Modifiers modifiers = 0; + + nsAString::const_iterator listStart, listEnd; + aModifiersList.BeginReading(listStart); + aModifiersList.EndReading(listEnd); + + for (uint32_t i = 0; i < ArrayLength(kPairs); i++) { + nsAString::const_iterator start(listStart), end(listEnd); + if (!FindInReadable(NS_ConvertASCIItoUTF16(kPairs[i].name), start, end)) { + continue; + } + + if ((start != listStart && !NS_IsAsciiWhitespace(*(--start))) || + (end != listEnd && !NS_IsAsciiWhitespace(*(end)))) { + continue; + } + modifiers |= kPairs[i].modifier; + } + + return modifiers; +} + +bool +UIEvent::GetModifierStateInternal(const nsAString& aKey) +{ + WidgetInputEvent* inputEvent = mEvent->AsInputEvent(); + MOZ_ASSERT(inputEvent, "mEvent must be WidgetInputEvent or derived class"); + return ((inputEvent->mModifiers & WidgetInputEvent::GetModifier(aKey)) != 0); +} + +void +UIEvent::InitModifiers(const EventModifierInit& aParam) +{ + if (NS_WARN_IF(!mEvent)) { + return; + } + WidgetInputEvent* inputEvent = mEvent->AsInputEvent(); + MOZ_ASSERT(inputEvent, + "This method shouldn't be called if it doesn't have modifiers"); + if (NS_WARN_IF(!inputEvent)) { + return; + } + + inputEvent->mModifiers = MODIFIER_NONE; + +#define SET_MODIFIER(aName, aValue) \ + if (aParam.m##aName) { \ + inputEvent->mModifiers |= aValue; \ + } \ + + SET_MODIFIER(CtrlKey, MODIFIER_CONTROL) + SET_MODIFIER(ShiftKey, MODIFIER_SHIFT) + SET_MODIFIER(AltKey, MODIFIER_ALT) + SET_MODIFIER(MetaKey, MODIFIER_META) + SET_MODIFIER(ModifierAltGraph, MODIFIER_ALTGRAPH) + SET_MODIFIER(ModifierCapsLock, MODIFIER_CAPSLOCK) + SET_MODIFIER(ModifierFn, MODIFIER_FN) + SET_MODIFIER(ModifierFnLock, MODIFIER_FNLOCK) + SET_MODIFIER(ModifierNumLock, MODIFIER_NUMLOCK) + SET_MODIFIER(ModifierOS, MODIFIER_OS) + SET_MODIFIER(ModifierScrollLock, MODIFIER_SCROLLLOCK) + SET_MODIFIER(ModifierSymbol, MODIFIER_SYMBOL) + SET_MODIFIER(ModifierSymbolLock, MODIFIER_SYMBOLLOCK) + +#undef SET_MODIFIER +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<UIEvent> +NS_NewDOMUIEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetGUIEvent* aEvent) +{ + RefPtr<UIEvent> it = new UIEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/UIEvent.h b/dom/events/UIEvent.h new file mode 100644 index 000000000..3ec960109 --- /dev/null +++ b/dom/events/UIEvent.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_UIEvent_h_ +#define mozilla_dom_UIEvent_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/UIEventBinding.h" +#include "nsDeviceContext.h" +#include "nsIDOMUIEvent.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" + +class nsINode; + +namespace mozilla { +namespace dom { + +class UIEvent : public Event, + public nsIDOMUIEvent +{ +public: + UIEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetGUIEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(UIEvent, Event) + + // nsIDOMUIEvent Interface + NS_DECL_NSIDOMUIEVENT + + // Forward to Event + NS_FORWARD_TO_EVENT_NO_SERIALIZATION_NO_DUPLICATION + NS_IMETHOD DuplicatePrivateData() override; + NS_IMETHOD_(void) Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType) override; + NS_IMETHOD_(bool) Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) override; + + + static already_AddRefed<UIEvent> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const UIEventInit& aParam, + ErrorResult& aRv); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return UIEventBinding::Wrap(aCx, this, aGivenProto); + } + + void InitUIEvent(const nsAString& typeArg, + bool canBubbleArg, + bool cancelableArg, + nsGlobalWindow* viewArg, + int32_t detailArg); + + nsPIDOMWindowOuter* GetView() const + { + return mView; + } + + int32_t Detail() const + { + return mDetail; + } + + int32_t LayerX() const + { + return GetLayerPoint().x; + } + + int32_t LayerY() const + { + return GetLayerPoint().y; + } + + int32_t PageX() const; + int32_t PageY() const; + + virtual uint32_t Which() + { + MOZ_ASSERT(mEvent->mClass != eKeyboardEventClass, + "Key events should override Which()"); + MOZ_ASSERT(mEvent->mClass != eMouseEventClass, + "Mouse events should override Which()"); + return 0; + } + + already_AddRefed<nsINode> GetRangeParent(); + + int32_t RangeOffset() const; + + bool IsChar() const; + +protected: + ~UIEvent() {} + + // Internal helper functions + nsIntPoint GetMovementPoint(); + nsIntPoint GetLayerPoint() const; + + nsCOMPtr<nsPIDOMWindowOuter> mView; + int32_t mDetail; + CSSIntPoint mClientPoint; + // Screenpoint is mEvent->mRefPoint. + nsIntPoint mLayerPoint; + CSSIntPoint mPagePoint; + nsIntPoint mMovementPoint; + bool mIsPointerLocked; + CSSIntPoint mLastClientPoint; + + static Modifiers ComputeModifierState(const nsAString& aModifiersList); + bool GetModifierStateInternal(const nsAString& aKey); + void InitModifiers(const EventModifierInit& aParam); +}; + +} // namespace dom +} // namespace mozilla + +#define NS_FORWARD_TO_UIEVENT \ + NS_FORWARD_NSIDOMUIEVENT(UIEvent::) \ + NS_FORWARD_TO_EVENT_NO_SERIALIZATION_NO_DUPLICATION \ + NS_IMETHOD DuplicatePrivateData() override \ + { \ + return UIEvent::DuplicatePrivateData(); \ + } \ + NS_IMETHOD_(void) Serialize(IPC::Message* aMsg, \ + bool aSerializeInterfaceType) \ + override \ + { \ + UIEvent::Serialize(aMsg, aSerializeInterfaceType); \ + } \ + NS_IMETHOD_(bool) Deserialize(const IPC::Message* aMsg, \ + PickleIterator* aIter) override \ + { \ + return UIEvent::Deserialize(aMsg, aIter); \ + } + +already_AddRefed<mozilla::dom::UIEvent> +NS_NewDOMUIEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent); + +#endif // mozilla_dom_UIEvent_h_ diff --git a/dom/events/VirtualKeyCodeList.h b/dom/events/VirtualKeyCodeList.h new file mode 100644 index 000000000..395d10cc8 --- /dev/null +++ b/dom/events/VirtualKeyCodeList.h @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +// IWYU pragma: private, include "mozilla/KeyTextEvents.h" + +/** + * This header file defines all DOM keys which are defined in nsIDOMKeyEvent. + * You must define NS_DEFINE_VK macro before including this. + * + * It must have two arguments, (aDOMKeyName, aDOMKeyCode) + * aDOMKeyName is a key name in DOM. + * aDOMKeyCode is one of nsIDOMKeyEvent::DOM_VK_*. + * + * Optionally, you can define NS_DISALLOW_SAME_KEYCODE. + * + * If NS_DISALLOW_SAME_KEYCODE is defined, same keyCode won't listed up. + * This is useful when you create switch-case statement. + */ + +#define DEFINE_VK_INTERNAL(aKeyName) \ + NS_DEFINE_VK(VK##aKeyName, nsIDOMKeyEvent::DOM_VK##aKeyName) + +// Some keycode may have different name in nsIDOMKeyEvent from its key name. +#define DEFINE_VK_INTERNAL2(aKeyName, aKeyCodeName) \ + NS_DEFINE_VK(VK##aKeyName, nsIDOMKeyEvent::DOM_VK##aKeyCodeName) + +DEFINE_VK_INTERNAL(_CANCEL) +DEFINE_VK_INTERNAL(_HELP) +DEFINE_VK_INTERNAL2(_BACK, _BACK_SPACE) +DEFINE_VK_INTERNAL(_TAB) +DEFINE_VK_INTERNAL(_CLEAR) +DEFINE_VK_INTERNAL(_RETURN) +DEFINE_VK_INTERNAL(_SHIFT) +DEFINE_VK_INTERNAL(_CONTROL) +DEFINE_VK_INTERNAL(_ALT) +DEFINE_VK_INTERNAL(_PAUSE) +DEFINE_VK_INTERNAL(_CAPS_LOCK) +#ifdef NS_DISALLOW_SAME_KEYCODE +DEFINE_VK_INTERNAL2(_KANA_OR_HANGUL, _KANA) +#else // #ifdef NS_DISALLOW_SAME_KEYCODE +DEFINE_VK_INTERNAL(_KANA) +DEFINE_VK_INTERNAL(_HANGUL) +#endif +DEFINE_VK_INTERNAL(_EISU) +DEFINE_VK_INTERNAL(_JUNJA) +DEFINE_VK_INTERNAL(_FINAL) +#ifdef NS_DISALLOW_SAME_KEYCODE +DEFINE_VK_INTERNAL2(_HANJA_OR_KANJI, _HANJA) +#else // #ifdef NS_DISALLOW_SAME_KEYCODE +DEFINE_VK_INTERNAL(_HANJA) +DEFINE_VK_INTERNAL(_KANJI) +#endif +DEFINE_VK_INTERNAL(_ESCAPE) +DEFINE_VK_INTERNAL(_CONVERT) +DEFINE_VK_INTERNAL(_NONCONVERT) +DEFINE_VK_INTERNAL(_ACCEPT) +DEFINE_VK_INTERNAL(_MODECHANGE) +DEFINE_VK_INTERNAL(_SPACE) +DEFINE_VK_INTERNAL(_PAGE_UP) +DEFINE_VK_INTERNAL(_PAGE_DOWN) +DEFINE_VK_INTERNAL(_END) +DEFINE_VK_INTERNAL(_HOME) +DEFINE_VK_INTERNAL(_LEFT) +DEFINE_VK_INTERNAL(_UP) +DEFINE_VK_INTERNAL(_RIGHT) +DEFINE_VK_INTERNAL(_DOWN) +DEFINE_VK_INTERNAL(_SELECT) +DEFINE_VK_INTERNAL(_PRINT) +DEFINE_VK_INTERNAL(_EXECUTE) +DEFINE_VK_INTERNAL(_PRINTSCREEN) +DEFINE_VK_INTERNAL(_INSERT) +DEFINE_VK_INTERNAL(_DELETE) + +DEFINE_VK_INTERNAL(_0) +DEFINE_VK_INTERNAL(_1) +DEFINE_VK_INTERNAL(_2) +DEFINE_VK_INTERNAL(_3) +DEFINE_VK_INTERNAL(_4) +DEFINE_VK_INTERNAL(_5) +DEFINE_VK_INTERNAL(_6) +DEFINE_VK_INTERNAL(_7) +DEFINE_VK_INTERNAL(_8) +DEFINE_VK_INTERNAL(_9) + +DEFINE_VK_INTERNAL(_COLON) +DEFINE_VK_INTERNAL(_SEMICOLON) +DEFINE_VK_INTERNAL(_LESS_THAN) +DEFINE_VK_INTERNAL(_EQUALS) +DEFINE_VK_INTERNAL(_GREATER_THAN) +DEFINE_VK_INTERNAL(_QUESTION_MARK) +DEFINE_VK_INTERNAL(_AT) + +DEFINE_VK_INTERNAL(_A) +DEFINE_VK_INTERNAL(_B) +DEFINE_VK_INTERNAL(_C) +DEFINE_VK_INTERNAL(_D) +DEFINE_VK_INTERNAL(_E) +DEFINE_VK_INTERNAL(_F) +DEFINE_VK_INTERNAL(_G) +DEFINE_VK_INTERNAL(_H) +DEFINE_VK_INTERNAL(_I) +DEFINE_VK_INTERNAL(_J) +DEFINE_VK_INTERNAL(_K) +DEFINE_VK_INTERNAL(_L) +DEFINE_VK_INTERNAL(_M) +DEFINE_VK_INTERNAL(_N) +DEFINE_VK_INTERNAL(_O) +DEFINE_VK_INTERNAL(_P) +DEFINE_VK_INTERNAL(_Q) +DEFINE_VK_INTERNAL(_R) +DEFINE_VK_INTERNAL(_S) +DEFINE_VK_INTERNAL(_T) +DEFINE_VK_INTERNAL(_U) +DEFINE_VK_INTERNAL(_V) +DEFINE_VK_INTERNAL(_W) +DEFINE_VK_INTERNAL(_X) +DEFINE_VK_INTERNAL(_Y) +DEFINE_VK_INTERNAL(_Z) + +DEFINE_VK_INTERNAL(_WIN) +DEFINE_VK_INTERNAL(_CONTEXT_MENU) +DEFINE_VK_INTERNAL(_SLEEP) + +DEFINE_VK_INTERNAL(_NUMPAD0) +DEFINE_VK_INTERNAL(_NUMPAD1) +DEFINE_VK_INTERNAL(_NUMPAD2) +DEFINE_VK_INTERNAL(_NUMPAD3) +DEFINE_VK_INTERNAL(_NUMPAD4) +DEFINE_VK_INTERNAL(_NUMPAD5) +DEFINE_VK_INTERNAL(_NUMPAD6) +DEFINE_VK_INTERNAL(_NUMPAD7) +DEFINE_VK_INTERNAL(_NUMPAD8) +DEFINE_VK_INTERNAL(_NUMPAD9) +DEFINE_VK_INTERNAL(_MULTIPLY) +DEFINE_VK_INTERNAL(_ADD) +DEFINE_VK_INTERNAL(_SEPARATOR) +DEFINE_VK_INTERNAL(_SUBTRACT) +DEFINE_VK_INTERNAL(_DECIMAL) +DEFINE_VK_INTERNAL(_DIVIDE) + +DEFINE_VK_INTERNAL(_F1) +DEFINE_VK_INTERNAL(_F2) +DEFINE_VK_INTERNAL(_F3) +DEFINE_VK_INTERNAL(_F4) +DEFINE_VK_INTERNAL(_F5) +DEFINE_VK_INTERNAL(_F6) +DEFINE_VK_INTERNAL(_F7) +DEFINE_VK_INTERNAL(_F8) +DEFINE_VK_INTERNAL(_F9) +DEFINE_VK_INTERNAL(_F10) +DEFINE_VK_INTERNAL(_F11) +DEFINE_VK_INTERNAL(_F12) +DEFINE_VK_INTERNAL(_F13) +DEFINE_VK_INTERNAL(_F14) +DEFINE_VK_INTERNAL(_F15) +DEFINE_VK_INTERNAL(_F16) +DEFINE_VK_INTERNAL(_F17) +DEFINE_VK_INTERNAL(_F18) +DEFINE_VK_INTERNAL(_F19) +DEFINE_VK_INTERNAL(_F20) +DEFINE_VK_INTERNAL(_F21) +DEFINE_VK_INTERNAL(_F22) +DEFINE_VK_INTERNAL(_F23) +DEFINE_VK_INTERNAL(_F24) + +DEFINE_VK_INTERNAL(_NUM_LOCK) +DEFINE_VK_INTERNAL(_SCROLL_LOCK) + +DEFINE_VK_INTERNAL(_WIN_OEM_FJ_JISHO) +DEFINE_VK_INTERNAL(_WIN_OEM_FJ_MASSHOU) +DEFINE_VK_INTERNAL(_WIN_OEM_FJ_TOUROKU) +DEFINE_VK_INTERNAL(_WIN_OEM_FJ_LOYA) +DEFINE_VK_INTERNAL(_WIN_OEM_FJ_ROYA) + +DEFINE_VK_INTERNAL(_CIRCUMFLEX) +DEFINE_VK_INTERNAL(_EXCLAMATION) +DEFINE_VK_INTERNAL(_DOUBLE_QUOTE) +DEFINE_VK_INTERNAL(_HASH) +DEFINE_VK_INTERNAL(_DOLLAR) +DEFINE_VK_INTERNAL(_PERCENT) +DEFINE_VK_INTERNAL(_AMPERSAND) +DEFINE_VK_INTERNAL(_UNDERSCORE) +DEFINE_VK_INTERNAL(_OPEN_PAREN) +DEFINE_VK_INTERNAL(_CLOSE_PAREN) +DEFINE_VK_INTERNAL(_ASTERISK) +DEFINE_VK_INTERNAL(_PLUS) +DEFINE_VK_INTERNAL(_PIPE) +DEFINE_VK_INTERNAL(_HYPHEN_MINUS) + +DEFINE_VK_INTERNAL(_OPEN_CURLY_BRACKET) +DEFINE_VK_INTERNAL(_CLOSE_CURLY_BRACKET) + +DEFINE_VK_INTERNAL(_TILDE) + +DEFINE_VK_INTERNAL(_VOLUME_MUTE) +DEFINE_VK_INTERNAL(_VOLUME_DOWN) +DEFINE_VK_INTERNAL(_VOLUME_UP) + +DEFINE_VK_INTERNAL(_COMMA) +DEFINE_VK_INTERNAL(_PERIOD) +DEFINE_VK_INTERNAL(_SLASH) +DEFINE_VK_INTERNAL(_BACK_QUOTE) +DEFINE_VK_INTERNAL(_OPEN_BRACKET) +DEFINE_VK_INTERNAL(_BACK_SLASH) +DEFINE_VK_INTERNAL(_CLOSE_BRACKET) +DEFINE_VK_INTERNAL(_QUOTE) + +DEFINE_VK_INTERNAL(_META) +DEFINE_VK_INTERNAL(_ALTGR) + +DEFINE_VK_INTERNAL(_WIN_ICO_HELP) +DEFINE_VK_INTERNAL(_WIN_ICO_00) +DEFINE_VK_INTERNAL(_WIN_ICO_CLEAR) +DEFINE_VK_INTERNAL(_WIN_OEM_RESET) +DEFINE_VK_INTERNAL(_WIN_OEM_JUMP) +DEFINE_VK_INTERNAL(_WIN_OEM_PA1) +DEFINE_VK_INTERNAL(_WIN_OEM_PA2) +DEFINE_VK_INTERNAL(_WIN_OEM_PA3) +DEFINE_VK_INTERNAL(_WIN_OEM_WSCTRL) +DEFINE_VK_INTERNAL(_WIN_OEM_CUSEL) +DEFINE_VK_INTERNAL(_WIN_OEM_ATTN) +DEFINE_VK_INTERNAL(_WIN_OEM_FINISH) +DEFINE_VK_INTERNAL(_WIN_OEM_COPY) +DEFINE_VK_INTERNAL(_WIN_OEM_AUTO) +DEFINE_VK_INTERNAL(_WIN_OEM_ENLW) +DEFINE_VK_INTERNAL(_WIN_OEM_BACKTAB) + +DEFINE_VK_INTERNAL(_ATTN) +DEFINE_VK_INTERNAL(_CRSEL) +DEFINE_VK_INTERNAL(_EXSEL) +DEFINE_VK_INTERNAL(_EREOF) +DEFINE_VK_INTERNAL(_PLAY) +DEFINE_VK_INTERNAL(_ZOOM) +DEFINE_VK_INTERNAL(_PA1) +DEFINE_VK_INTERNAL(_WIN_OEM_CLEAR) + +#undef DEFINE_VK_INTERNAL +#undef DEFINE_VK_INTERNAL2 diff --git a/dom/events/WheelEvent.cpp b/dom/events/WheelEvent.cpp new file mode 100644 index 000000000..de6c9445a --- /dev/null +++ b/dom/events/WheelEvent.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/WheelEvent.h" +#include "mozilla/MouseEvents.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +WheelEvent::WheelEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetWheelEvent* aWheelEvent) + : MouseEvent(aOwner, aPresContext, + aWheelEvent ? aWheelEvent : + new WidgetWheelEvent(false, eVoidEvent, nullptr)) + , mAppUnitsPerDevPixel(0) +{ + if (aWheelEvent) { + mEventIsInternal = false; + // If the delta mode is pixel, the WidgetWheelEvent's delta values are in + // device pixels. However, JS contents need the delta values in CSS pixels. + // We should store the value of mAppUnitsPerDevPixel here because + // it might be changed by changing zoom or something. + if (aWheelEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) { + mAppUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); + } + } else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); + mEvent->AsWheelEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + } +} + +NS_IMPL_ADDREF_INHERITED(WheelEvent, MouseEvent) +NS_IMPL_RELEASE_INHERITED(WheelEvent, MouseEvent) + +NS_INTERFACE_MAP_BEGIN(WheelEvent) +NS_INTERFACE_MAP_END_INHERITING(MouseEvent) + +void +WheelEvent::InitWheelEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + int32_t aScreenX, + int32_t aScreenY, + int32_t aClientX, + int32_t aClientY, + uint16_t aButton, + EventTarget* aRelatedTarget, + const nsAString& aModifiersList, + double aDeltaX, + double aDeltaY, + double aDeltaZ, + uint32_t aDeltaMode) +{ + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, + aScreenX, aScreenY, aClientX, aClientY, aButton, + aRelatedTarget, aModifiersList); + + WidgetWheelEvent* wheelEvent = mEvent->AsWheelEvent(); + wheelEvent->mDeltaX = aDeltaX; + wheelEvent->mDeltaY = aDeltaY; + wheelEvent->mDeltaZ = aDeltaZ; + wheelEvent->mDeltaMode = aDeltaMode; +} + +double +WheelEvent::DeltaX() +{ + if (!mAppUnitsPerDevPixel) { + return mEvent->AsWheelEvent()->mDeltaX; + } + return mEvent->AsWheelEvent()->mDeltaX * + mAppUnitsPerDevPixel / nsPresContext::AppUnitsPerCSSPixel(); +} + +double +WheelEvent::DeltaY() +{ + if (!mAppUnitsPerDevPixel) { + return mEvent->AsWheelEvent()->mDeltaY; + } + return mEvent->AsWheelEvent()->mDeltaY * + mAppUnitsPerDevPixel / nsPresContext::AppUnitsPerCSSPixel(); +} + +double +WheelEvent::DeltaZ() +{ + if (!mAppUnitsPerDevPixel) { + return mEvent->AsWheelEvent()->mDeltaZ; + } + return mEvent->AsWheelEvent()->mDeltaZ * + mAppUnitsPerDevPixel / nsPresContext::AppUnitsPerCSSPixel(); +} + +uint32_t +WheelEvent::DeltaMode() +{ + return mEvent->AsWheelEvent()->mDeltaMode; +} + +already_AddRefed<WheelEvent> +WheelEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const WheelEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<WheelEvent> e = new WheelEvent(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitWheelEvent(aType, aParam.mBubbles, aParam.mCancelable, + aParam.mView, aParam.mDetail, + aParam.mScreenX, aParam.mScreenY, + aParam.mClientX, aParam.mClientY, + aParam.mButton, aParam.mRelatedTarget, + EmptyString(), aParam.mDeltaX, + aParam.mDeltaY, aParam.mDeltaZ, aParam.mDeltaMode); + e->InitializeExtraMouseEventDictionaryMembers(aParam); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<WheelEvent> +NS_NewDOMWheelEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetWheelEvent* aEvent) +{ + RefPtr<WheelEvent> it = new WheelEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/WheelEvent.h b/dom/events/WheelEvent.h new file mode 100644 index 000000000..fd54606fd --- /dev/null +++ b/dom/events/WheelEvent.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WheelEvent_h_ +#define mozilla_dom_WheelEvent_h_ + +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/WheelEventBinding.h" +#include "mozilla/EventForwards.h" + +namespace mozilla { +namespace dom { + +class WheelEvent : public MouseEvent +{ +public: + WheelEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetWheelEvent* aWheelEvent); + + NS_DECL_ISUPPORTS_INHERITED + + // Forward to base class + NS_FORWARD_TO_MOUSEEVENT + + static + already_AddRefed<WheelEvent> Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const WheelEventInit& aParam, + ErrorResult& aRv); + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return WheelEventBinding::Wrap(aCx, this, aGivenProto); + } + + // NOTE: DeltaX(), DeltaY() and DeltaZ() return CSS pixels when deltaMode is + // DOM_DELTA_PIXEL. (The internal event's delta values are device pixels + // if it's dispatched by widget) + double DeltaX(); + double DeltaY(); + double DeltaZ(); + uint32_t DeltaMode(); + + void + InitWheelEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, + nsGlobalWindow* aView, int32_t aDetail, + int32_t aScreenX, int32_t aScreenY, + int32_t aClientX, int32_t aClientY, uint16_t aButton, + EventTarget* aRelatedTarget, const nsAString& aModifiersList, + double aDeltaX, double aDeltaY, double aDeltaZ, + uint32_t aDeltaMode); + +protected: + ~WheelEvent() {} + +private: + int32_t mAppUnitsPerDevPixel; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::WheelEvent> +NS_NewDOMWheelEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetWheelEvent* aEvent); + +#endif // mozilla_dom_WheelEvent_h_ diff --git a/dom/events/WheelHandlingHelper.cpp b/dom/events/WheelHandlingHelper.cpp new file mode 100644 index 000000000..7665ee922 --- /dev/null +++ b/dom/events/WheelHandlingHelper.cpp @@ -0,0 +1,551 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WheelHandlingHelper.h" + +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" +#include "nsIScrollableFrame.h" +#include "nsITimer.h" +#include "nsPluginFrame.h" +#include "nsPresContext.h" +#include "prtime.h" +#include "Units.h" +#include "AsyncScrollBase.h" + +namespace mozilla { + +/******************************************************************/ +/* mozilla::DeltaValues */ +/******************************************************************/ + +DeltaValues::DeltaValues(WidgetWheelEvent* aEvent) + : deltaX(aEvent->mDeltaX) + , deltaY(aEvent->mDeltaY) +{ +} + +/******************************************************************/ +/* mozilla::WheelHandlingUtils */ +/******************************************************************/ + +/* static */ bool +WheelHandlingUtils::CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax, + double aDirection) +{ + return aDirection > 0.0 ? aValue < static_cast<double>(aMax) : + static_cast<double>(aMin) < aValue; +} + +/* static */ bool +WheelHandlingUtils::CanScrollOn(nsIFrame* aFrame, + double aDirectionX, double aDirectionY) +{ + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame); + if (scrollableFrame) { + return CanScrollOn(scrollableFrame, aDirectionX, aDirectionY); + } + nsPluginFrame* pluginFrame = do_QueryFrame(aFrame); + return pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction(); +} + +/* static */ bool +WheelHandlingUtils::CanScrollOn(nsIScrollableFrame* aScrollFrame, + double aDirectionX, double aDirectionY) +{ + MOZ_ASSERT(aScrollFrame); + NS_ASSERTION(aDirectionX || aDirectionY, + "One of the delta values must be non-zero at least"); + + nsPoint scrollPt = aScrollFrame->GetScrollPosition(); + nsRect scrollRange = aScrollFrame->GetScrollRange(); + uint32_t directions = aScrollFrame->GetPerceivedScrollingDirections(); + + return (aDirectionX && (directions & nsIScrollableFrame::HORIZONTAL) && + CanScrollInRange(scrollRange.x, scrollPt.x, + scrollRange.XMost(), aDirectionX)) || + (aDirectionY && (directions & nsIScrollableFrame::VERTICAL) && + CanScrollInRange(scrollRange.y, scrollPt.y, + scrollRange.YMost(), aDirectionY)); +} + +/******************************************************************/ +/* mozilla::WheelTransaction */ +/******************************************************************/ + +nsWeakFrame WheelTransaction::sTargetFrame(nullptr); +uint32_t WheelTransaction::sTime = 0; +uint32_t WheelTransaction::sMouseMoved = 0; +nsITimer* WheelTransaction::sTimer = nullptr; +int32_t WheelTransaction::sScrollSeriesCounter = 0; +bool WheelTransaction::sOwnScrollbars = false; + +/* static */ bool +WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold) +{ + uint32_t now = PR_IntervalToMilliseconds(PR_IntervalNow()); + return (now - aBaseTime > aThreshold); +} + +/* static */ void +WheelTransaction::OwnScrollbars(bool aOwn) +{ + sOwnScrollbars = aOwn; +} + +/* static */ void +WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent) +{ + NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!"); + MOZ_ASSERT(aEvent->mMessage == eWheel, + "Transaction must be started with a wheel event"); + ScrollbarsForWheel::OwnWheelTransaction(false); + sTargetFrame = aTargetFrame; + sScrollSeriesCounter = 0; + if (!UpdateTransaction(aEvent)) { + NS_ERROR("BeginTransaction is called even cannot scroll the frame"); + EndTransaction(); + } +} + +/* static */ bool +WheelTransaction::UpdateTransaction(WidgetWheelEvent* aEvent) +{ + nsIFrame* scrollToFrame = GetTargetFrame(); + nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame(); + if (scrollableFrame) { + scrollToFrame = do_QueryFrame(scrollableFrame); + } + + if (!WheelHandlingUtils::CanScrollOn(scrollToFrame, + aEvent->mDeltaX, aEvent->mDeltaY)) { + OnFailToScrollTarget(); + // We should not modify the transaction state when the view will not be + // scrolled actually. + return false; + } + + SetTimeout(); + + if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeoutMs)) { + sScrollSeriesCounter = 0; + } + sScrollSeriesCounter++; + + // We should use current time instead of WidgetEvent.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 true; +} + +/* static */ void +WheelTransaction::MayEndTransaction() +{ + if (!sOwnScrollbars && ScrollbarsForWheel::IsActive()) { + ScrollbarsForWheel::OwnWheelTransaction(true); + } else { + EndTransaction(); + } +} + +/* static */ void +WheelTransaction::EndTransaction() +{ + if (sTimer) { + sTimer->Cancel(); + } + sTargetFrame = nullptr; + sScrollSeriesCounter = 0; + if (sOwnScrollbars) { + sOwnScrollbars = false; + ScrollbarsForWheel::OwnWheelTransaction(false); + ScrollbarsForWheel::Inactivate(); + } +} + +/* static */ bool +WheelTransaction::WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent, + nsWeakFrame& aTargetWeakFrame) +{ + nsIFrame* lastTargetFrame = GetTargetFrame(); + if (!lastTargetFrame) { + BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent); + } else if (lastTargetFrame != aTargetWeakFrame.GetFrame()) { + EndTransaction(); + BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent); + } else { + UpdateTransaction(aWheelEvent); + } + + // When the wheel event will not be handled with any frames, + // UpdateTransaction() fires MozMouseScrollFailed event which is for + // automated testing. In the event handler, the target frame might be + // destroyed. Then, the caller shouldn't try to handle the default action. + if (!aTargetWeakFrame.IsAlive()) { + EndTransaction(); + return false; + } + + return true; +} + +/* static */ void +WheelTransaction::OnEvent(WidgetEvent* aEvent) +{ + if (!sTargetFrame) { + return; + } + + if (OutOfTime(sTime, GetTimeoutTime())) { + // 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(nullptr, nullptr); + return; + } + + switch (aEvent->mMessage) { + case eWheel: + if (sMouseMoved != 0 && + OutOfTime(sMouseMoved, GetIgnoreMoveDelayTime())) { + // Terminate the current mousewheel transaction if the mouse moved more + // than ignoremovedelay milliseconds ago + EndTransaction(); + } + return; + case eMouseMove: + case eDragOver: { + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->IsReal()) { + // If the cursor is moving to be outside the frame, + // terminate the scrollwheel transaction. + nsIntPoint pt = GetScreenPoint(mouseEvent); + nsIntRect r = sTargetFrame->GetScreenRect(); + if (!r.Contains(pt)) { + EndTransaction(); + return; + } + + // If the cursor is moving inside the frame, and it is less than + // ignoremovedelay milliseconds since the last scroll operation, ignore + // the mouse move; otherwise, record the current mouse move time to be + // checked later + if (!sMouseMoved && OutOfTime(sTime, GetIgnoreMoveDelayTime())) { + sMouseMoved = PR_IntervalToMilliseconds(PR_IntervalNow()); + } + } + return; + } + case eKeyPress: + case eKeyUp: + case eKeyDown: + case eMouseUp: + case eMouseDown: + case eMouseDoubleClick: + case eMouseClick: + case eContextMenu: + case eDrop: + EndTransaction(); + return; + default: + break; + } +} + +/* static */ void +WheelTransaction::Shutdown() +{ + NS_IF_RELEASE(sTimer); +} + +/* static */ void +WheelTransaction::OnFailToScrollTarget() +{ + NS_PRECONDITION(sTargetFrame, "We don't have mouse scrolling transaction"); + + if (Preferences::GetBool("test.mousescroll", false)) { + // This event is used for automated tests, see bug 442774. + nsContentUtils::DispatchTrustedEvent( + sTargetFrame->GetContent()->OwnerDoc(), + sTargetFrame->GetContent(), + NS_LITERAL_STRING("MozMouseScrollFailed"), + true, true); + } + // The target frame might be destroyed in the event handler, at that time, + // we need to finish the current transaction + if (!sTargetFrame) { + EndTransaction(); + } +} + +/* static */ void +WheelTransaction::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. + MayEndTransaction(); + + if (Preferences::GetBool("test.mousescroll", false)) { + // This event is used for automated tests, see bug 442774. + nsContentUtils::DispatchTrustedEvent( + frame->GetContent()->OwnerDoc(), + frame->GetContent(), + NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"), + true, true); + } +} + +/* static */ void +WheelTransaction::SetTimeout() +{ + if (!sTimer) { + nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (!timer) { + return; + } + timer.swap(sTimer); + } + sTimer->Cancel(); + DebugOnly<nsresult> rv = + sTimer->InitWithFuncCallback(OnTimeout, nullptr, GetTimeoutTime(), + nsITimer::TYPE_ONE_SHOT); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "nsITimer::InitWithFuncCallback failed"); +} + +/* static */ nsIntPoint +WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent) +{ + NS_ASSERTION(aEvent, "aEvent is null"); + NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null"); + return (aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset()) + .ToUnknownPoint(); +} + +/* static */ uint32_t +WheelTransaction::GetTimeoutTime() +{ + return Preferences::GetUint("mousewheel.transaction.timeout", 1500); +} + +/* static */ uint32_t +WheelTransaction::GetIgnoreMoveDelayTime() +{ + return Preferences::GetUint("mousewheel.transaction.ignoremovedelay", 100); +} + +/* static */ DeltaValues +WheelTransaction::AccelerateWheelDelta(WidgetWheelEvent* aEvent, + bool aAllowScrollSpeedOverride) +{ + DeltaValues result(aEvent); + + // Don't accelerate the delta values if the event isn't line scrolling. + if (aEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_LINE) { + return result; + } + + if (aAllowScrollSpeedOverride) { + result = OverrideSystemScrollSpeed(aEvent); + } + + // Accelerate by the sScrollSeriesCounter + int32_t start = GetAccelerationStart(); + if (start >= 0 && sScrollSeriesCounter >= start) { + int32_t factor = GetAccelerationFactor(); + if (factor > 0) { + result.deltaX = ComputeAcceleratedWheelDelta(result.deltaX, factor); + result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor); + } + } + + return result; +} + +/* static */ double +WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor) +{ + return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter, aFactor); +} + +/* static */ int32_t +WheelTransaction::GetAccelerationStart() +{ + return Preferences::GetInt("mousewheel.acceleration.start", -1); +} + +/* static */ int32_t +WheelTransaction::GetAccelerationFactor() +{ + return Preferences::GetInt("mousewheel.acceleration.factor", -1); +} + +/* static */ DeltaValues +WheelTransaction::OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent) +{ + MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction"); + MOZ_ASSERT(aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE); + + // If the event doesn't scroll to both X and Y, we don't need to do anything + // here. + if (!aEvent->mDeltaX && !aEvent->mDeltaY) { + return DeltaValues(aEvent); + } + + return DeltaValues(aEvent->OverriddenDeltaX(), + aEvent->OverriddenDeltaY()); +} + +/******************************************************************/ +/* mozilla::ScrollbarsForWheel */ +/******************************************************************/ + +const DeltaValues ScrollbarsForWheel::directions[kNumberOfTargets] = { + DeltaValues(-1, 0), DeltaValues(+1, 0), DeltaValues(0, -1), DeltaValues(0, +1) +}; + +nsWeakFrame ScrollbarsForWheel::sActiveOwner = nullptr; +nsWeakFrame ScrollbarsForWheel::sActivatedScrollTargets[kNumberOfTargets] = { + nullptr, nullptr, nullptr, nullptr +}; + +bool ScrollbarsForWheel::sHadWheelStart = false; +bool ScrollbarsForWheel::sOwnWheelTransaction = false; + +/* static */ void +ScrollbarsForWheel::PrepareToScrollText(EventStateManager* aESM, + nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent) +{ + if (aEvent->mMessage == eWheelOperationStart) { + WheelTransaction::OwnScrollbars(false); + if (!IsActive()) { + TemporarilyActivateAllPossibleScrollTargets(aESM, aTargetFrame, aEvent); + sHadWheelStart = true; + } + } else { + DeactivateAllTemporarilyActivatedScrollTargets(); + } +} + +/* static */ void +ScrollbarsForWheel::SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget) +{ + if (!sHadWheelStart) { + return; + } + nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(aScrollTarget); + if (!scrollbarMediator) { + return; + } + sHadWheelStart = false; + sActiveOwner = do_QueryFrame(aScrollTarget); + scrollbarMediator->ScrollbarActivityStarted(); +} + +/* static */ void +ScrollbarsForWheel::MayInactivate() +{ + if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) { + WheelTransaction::OwnScrollbars(true); + } else { + Inactivate(); + } +} + +/* static */ void +ScrollbarsForWheel::Inactivate() +{ + nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sActiveOwner); + if (scrollbarMediator) { + scrollbarMediator->ScrollbarActivityStopped(); + } + sActiveOwner = nullptr; + DeactivateAllTemporarilyActivatedScrollTargets(); + if (sOwnWheelTransaction) { + sOwnWheelTransaction = false; + WheelTransaction::OwnScrollbars(false); + WheelTransaction::EndTransaction(); + } +} + +/* static */ bool +ScrollbarsForWheel::IsActive() +{ + if (sActiveOwner) { + return true; + } + for (size_t i = 0; i < kNumberOfTargets; ++i) { + if (sActivatedScrollTargets[i]) { + return true; + } + } + return false; +} + +/* static */ void +ScrollbarsForWheel::OwnWheelTransaction(bool aOwn) +{ + sOwnWheelTransaction = aOwn; +} + +/* static */ void +ScrollbarsForWheel::TemporarilyActivateAllPossibleScrollTargets( + EventStateManager* aESM, + nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent) +{ + for (size_t i = 0; i < kNumberOfTargets; i++) { + const DeltaValues *dir = &directions[i]; + nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i]; + MOZ_ASSERT(!*scrollTarget, "scroll target still temporarily activated!"); + nsIScrollableFrame* target = do_QueryFrame( + aESM->ComputeScrollTarget(aTargetFrame, dir->deltaX, dir->deltaY, aEvent, + EventStateManager::COMPUTE_DEFAULT_ACTION_TARGET)); + nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(target); + if (scrollbarMediator) { + nsIFrame* targetFrame = do_QueryFrame(target); + *scrollTarget = targetFrame; + scrollbarMediator->ScrollbarActivityStarted(); + } + } +} + +/* static */ void +ScrollbarsForWheel::DeactivateAllTemporarilyActivatedScrollTargets() +{ + for (size_t i = 0; i < kNumberOfTargets; i++) { + nsWeakFrame* scrollTarget = &sActivatedScrollTargets[i]; + if (*scrollTarget) { + nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(*scrollTarget); + if (scrollbarMediator) { + scrollbarMediator->ScrollbarActivityStopped(); + } + *scrollTarget = nullptr; + } + } +} + +} // namespace mozilla diff --git a/dom/events/WheelHandlingHelper.h b/dom/events/WheelHandlingHelper.h new file mode 100644 index 000000000..b88aa23b2 --- /dev/null +++ b/dom/events/WheelHandlingHelper.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WheelHandlingHelper_h_ +#define mozilla_WheelHandlingHelper_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "nsCoord.h" +#include "nsIFrame.h" +#include "nsPoint.h" + +class nsIScrollableFrame; +class nsITimer; + +namespace mozilla { + +class EventStateManager; + +/** + * DeltaValues stores two delta values which are along X and Y axis. This is + * useful for arguments and results of some methods. + */ + +struct DeltaValues +{ + DeltaValues() + : deltaX(0.0) + , deltaY(0.0) + { + } + + DeltaValues(double aDeltaX, double aDeltaY) + : deltaX(aDeltaX) + , deltaY(aDeltaY) + { + } + + explicit DeltaValues(WidgetWheelEvent* aEvent); + + double deltaX; + double deltaY; +}; + +/** + * WheelHandlingUtils provides some static methods which are useful at handling + * wheel events. + */ + +class WheelHandlingUtils +{ +public: + /** + * Returns true if aFrame is a scrollable frame and it can be scrolled to + * either aDirectionX or aDirectionY along each axis. Or if aFrame is a + * plugin frame (in this case, aDirectionX and aDirectionY are ignored). + * Otherwise, false. + */ + static bool CanScrollOn(nsIFrame* aFrame, + double aDirectionX, double aDirectionY); + /** + * Returns true if the scrollable frame can be scrolled to either aDirectionX + * or aDirectionY along each axis. Otherwise, false. + */ + static bool CanScrollOn(nsIScrollableFrame* aScrollFrame, + double aDirectionX, double aDirectionY); + +private: + static bool CanScrollInRange(nscoord aMin, nscoord aValue, nscoord aMax, + double aDirection); +}; + +/** + * ScrollbarsForWheel manages scrollbars state during wheel operation. + * E.g., on some platforms, scrollbars should show only while user attempts to + * scroll. At that time, scrollbars which may be possible to scroll by + * operation of wheel at the point should show temporarily. + */ + +class ScrollbarsForWheel +{ +public: + static void PrepareToScrollText(EventStateManager* aESM, + nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent); + static void SetActiveScrollTarget(nsIScrollableFrame* aScrollTarget); + // Hide all scrollbars (both mActiveOwner's and mActivatedScrollTargets') + static void MayInactivate(); + static void Inactivate(); + static bool IsActive(); + static void OwnWheelTransaction(bool aOwn); + +protected: + static const size_t kNumberOfTargets = 4; + static const DeltaValues directions[kNumberOfTargets]; + static nsWeakFrame sActiveOwner; + static nsWeakFrame sActivatedScrollTargets[kNumberOfTargets]; + static bool sHadWheelStart; + static bool sOwnWheelTransaction; + + + /** + * These two methods are called upon eWheelOperationStart/eWheelOperationEnd + * events to show/hide the right scrollbars. + */ + static void TemporarilyActivateAllPossibleScrollTargets( + EventStateManager* aESM, + nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent); + static void DeactivateAllTemporarilyActivatedScrollTargets(); +}; + +/** + * WheelTransaction manages a series of wheel events as a transaction. + * While in a transaction, every wheel event should scroll the same scrollable + * element even if a different scrollable element is under the mouse cursor. + * + * Additionally, this class also manages wheel scroll speed acceleration. + */ + +class WheelTransaction +{ +public: + static nsIFrame* GetTargetFrame() { return sTargetFrame; } + static void EndTransaction(); + /** + * WillHandleDefaultAction() is called before handling aWheelEvent on + * aTargetFrame. + * + * @return false if the caller cannot continue to handle the default + * action. Otherwise, true. + */ + static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent, + nsWeakFrame& aTargetWeakFrame); + static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent, + nsIFrame* aTargetFrame) + { + nsWeakFrame targetWeakFrame(aTargetFrame); + return WillHandleDefaultAction(aWheelEvent, targetWeakFrame); + } + static void OnEvent(WidgetEvent* aEvent); + static void Shutdown(); + static uint32_t GetTimeoutTime(); + + static void OwnScrollbars(bool aOwn); + + static DeltaValues AccelerateWheelDelta(WidgetWheelEvent* aEvent, + bool aAllowScrollSpeedOverride); + +protected: + static void BeginTransaction(nsIFrame* aTargetFrame, + WidgetWheelEvent* aEvent); + // Be careful, UpdateTransaction may fire a DOM event, therefore, the target + // frame might be destroyed in the event handler. + static bool UpdateTransaction(WidgetWheelEvent* aEvent); + static void MayEndTransaction(); + + static nsIntPoint GetScreenPoint(WidgetGUIEvent* aEvent); + static void OnFailToScrollTarget(); + static void OnTimeout(nsITimer* aTimer, void* aClosure); + static void SetTimeout(); + static uint32_t GetIgnoreMoveDelayTime(); + static int32_t GetAccelerationStart(); + static int32_t GetAccelerationFactor(); + static DeltaValues OverrideSystemScrollSpeed(WidgetWheelEvent* aEvent); + static double ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor); + static bool OutOfTime(uint32_t aBaseTime, uint32_t aThreshold); + + static nsWeakFrame sTargetFrame; + static uint32_t sTime; // in milliseconds + static uint32_t sMouseMoved; // in milliseconds + static nsITimer* sTimer; + static int32_t sScrollSeriesCounter; + static bool sOwnScrollbars; +}; + +} // namespace mozilla + +#endif // mozilla_WheelHandlingHelper_h_ diff --git a/dom/events/XULCommandEvent.cpp b/dom/events/XULCommandEvent.cpp new file mode 100644 index 000000000..770f37688 --- /dev/null +++ b/dom/events/XULCommandEvent.cpp @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/XULCommandEvent.h" +#include "prtime.h" + +namespace mozilla { +namespace dom { + +XULCommandEvent::XULCommandEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetInputEvent* aEvent) + : UIEvent(aOwner, aPresContext, + aEvent ? aEvent : + new WidgetInputEvent(false, eVoidEvent, nullptr)) +{ + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + mEvent->mTime = PR_Now(); + } +} + +NS_IMPL_ADDREF_INHERITED(XULCommandEvent, UIEvent) +NS_IMPL_RELEASE_INHERITED(XULCommandEvent, UIEvent) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(XULCommandEvent, UIEvent, + mSourceEvent) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULCommandEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMXULCommandEvent) +NS_INTERFACE_MAP_END_INHERITING(UIEvent) + +bool +XULCommandEvent::AltKey() +{ + return mEvent->AsInputEvent()->IsAlt(); +} + +NS_IMETHODIMP +XULCommandEvent::GetAltKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = AltKey(); + return NS_OK; +} + +bool +XULCommandEvent::CtrlKey() +{ + return mEvent->AsInputEvent()->IsControl(); +} + +NS_IMETHODIMP +XULCommandEvent::GetCtrlKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = CtrlKey(); + return NS_OK; +} + +bool +XULCommandEvent::ShiftKey() +{ + return mEvent->AsInputEvent()->IsShift(); +} + +NS_IMETHODIMP +XULCommandEvent::GetShiftKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = ShiftKey(); + return NS_OK; +} + +bool +XULCommandEvent::MetaKey() +{ + return mEvent->AsInputEvent()->IsMeta(); +} + +NS_IMETHODIMP +XULCommandEvent::GetMetaKey(bool* aIsDown) +{ + NS_ENSURE_ARG_POINTER(aIsDown); + *aIsDown = MetaKey(); + return NS_OK; +} + +NS_IMETHODIMP +XULCommandEvent::GetSourceEvent(nsIDOMEvent** aSourceEvent) +{ + NS_ENSURE_ARG_POINTER(aSourceEvent); + nsCOMPtr<nsIDOMEvent> event = GetSourceEvent(); + event.forget(aSourceEvent); + return NS_OK; +} + +NS_IMETHODIMP +XULCommandEvent::InitCommandEvent(const nsAString& aType, + bool aCanBubble, + bool aCancelable, + mozIDOMWindow* aView, + int32_t aDetail, + bool aCtrlKey, + bool aAltKey, + bool aShiftKey, + bool aMetaKey, + nsIDOMEvent* aSourceEvent) +{ + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + + auto* view = nsGlobalWindow::Cast(nsPIDOMWindowInner::From(aView)); + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, view, aDetail); + + mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey, + aShiftKey, aMetaKey); + mSourceEvent = aSourceEvent; + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<XULCommandEvent> +NS_NewDOMXULCommandEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetInputEvent* aEvent) +{ + RefPtr<XULCommandEvent> it = + new XULCommandEvent(aOwner, aPresContext, aEvent); + return it.forget(); +} diff --git a/dom/events/XULCommandEvent.h b/dom/events/XULCommandEvent.h new file mode 100644 index 000000000..ea5e85a75 --- /dev/null +++ b/dom/events/XULCommandEvent.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This class implements a XUL "command" event. See nsIDOMXULCommandEvent.idl + +#ifndef mozilla_dom_XULCommandEvent_h_ +#define mozilla_dom_XULCommandEvent_h_ + +#include "mozilla/dom/UIEvent.h" +#include "mozilla/dom/XULCommandEventBinding.h" +#include "nsIDOMXULCommandEvent.h" + +namespace mozilla { +namespace dom { + +class XULCommandEvent : public UIEvent, + public nsIDOMXULCommandEvent +{ +public: + XULCommandEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetInputEvent* aEvent); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULCommandEvent, UIEvent) + NS_DECL_NSIDOMXULCOMMANDEVENT + + // Forward our inherited virtual methods to the base class + NS_FORWARD_TO_UIEVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return XULCommandEventBinding::Wrap(aCx, this, aGivenProto); + } + + bool AltKey(); + bool CtrlKey(); + bool ShiftKey(); + bool MetaKey(); + + already_AddRefed<Event> GetSourceEvent() + { + RefPtr<Event> e = + mSourceEvent ? mSourceEvent->InternalDOMEvent() : nullptr; + return e.forget(); + } + + void InitCommandEvent(const nsAString& aType, + bool aCanBubble, bool aCancelable, + nsGlobalWindow* aView, + int32_t aDetail, + bool aCtrlKey, bool aAltKey, + bool aShiftKey, bool aMetaKey, + Event* aSourceEvent) + { + InitCommandEvent(aType, aCanBubble, aCancelable, aView->AsInner(), + aDetail, aCtrlKey, aAltKey, aShiftKey, aMetaKey, + aSourceEvent); + } + +protected: + ~XULCommandEvent() {} + + nsCOMPtr<nsIDOMEvent> mSourceEvent; +}; + +} // namespace dom +} // namespace mozilla + +already_AddRefed<mozilla::dom::XULCommandEvent> +NS_NewDOMXULCommandEvent(mozilla::dom::EventTarget* aOwner, + nsPresContext* aPresContext, + mozilla::WidgetInputEvent* aEvent); + +#endif // mozilla_dom_XULCommandEvent_h_ diff --git a/dom/events/crashtests/1033343.html b/dom/events/crashtests/1033343.html new file mode 100644 index 000000000..99160cebf --- /dev/null +++ b/dom/events/crashtests/1033343.html @@ -0,0 +1,5 @@ +<script> + +document.createEvent('CustomEvent').detail; + +</script> diff --git a/dom/events/crashtests/1035654-1.html b/dom/events/crashtests/1035654-1.html new file mode 100644 index 000000000..6e3e3c027 --- /dev/null +++ b/dom/events/crashtests/1035654-1.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + +<head> +<script> + +function boom() +{ + var video = document.createElement('video'); + var track = video.addTextTrack('chapters'); + window.meep = new TrackEvent("t", { "track": track }); + document.documentElement.style.expando = null; +} + +</script> +</head> + +<body onload="boom();"> +</body> + +</html> diff --git a/dom/events/crashtests/1035654-2.html b/dom/events/crashtests/1035654-2.html new file mode 100644 index 000000000..19f5a1cc8 --- /dev/null +++ b/dom/events/crashtests/1035654-2.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + +<head> +<script> + +function boom() +{ + var video = document.createElement('video'); + var track = video.addTextTrack('chapters'); + track.expando = new TrackEvent("t", { "track": track }) +} + +</script> +</head> + +<body onload="boom();"> +</body> + +</html> diff --git a/dom/events/crashtests/104310-1.html b/dom/events/crashtests/104310-1.html new file mode 100644 index 000000000..f9544c2f8 --- /dev/null +++ b/dom/events/crashtests/104310-1.html @@ -0,0 +1,22 @@ +<HTML>
+
+<HEAD>
+</HEAD>
+
+<BODY onload=document.input.name.focus()>
+
+
+<form method="post" action="index.cfm" name="input">
+<CENTER><TABLE BORDER=0>
+<TD><INPUT TYPE="text" NAME="name" onClick="this.focus()" onFocus="this.select()" onSelect="this.select()"></TD>
+
+</TABLE></CENTER>
+<HR>
+
+</FORM>
+
+</BODY>
+
+</HTML>
+
+
diff --git a/dom/events/crashtests/1072137-1.html b/dom/events/crashtests/1072137-1.html new file mode 100644 index 000000000..ae26188ab --- /dev/null +++ b/dom/events/crashtests/1072137-1.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<script> + +function boom() +{ + var textareaRoot = document.createElement("textarea"); + document.removeChild(document.documentElement); + document.appendChild(textareaRoot); + var input = document.createElement("input"); + textareaRoot.appendChild(input); + input.select(); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/events/crashtests/1143972-1.html b/dom/events/crashtests/1143972-1.html new file mode 100644 index 000000000..992bf9478 --- /dev/null +++ b/dom/events/crashtests/1143972-1.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<script> +var x; +function doTest() { + f.contentDocument.body.onclick = function (event) { + x = event.offsetX; + } + f.contentDocument.body.dispatchEvent(f.contentWindow.ev); +} +</script> +<iframe id="f" style="display:none" onload="doTest()" + src="data:text/html,<script>var ev = new MouseEvent('click', {clientX:0, clientY:0})</script>"></iframe> diff --git a/dom/events/crashtests/116206-1.html b/dom/events/crashtests/116206-1.html new file mode 100644 index 000000000..b04c15097 --- /dev/null +++ b/dom/events/crashtests/116206-1.html @@ -0,0 +1,23 @@ +<html> + <head> + <script language="JavaScript"> + function InitialFocus(){ + document.frmSelectUser.radResidence[0].focus(); + } + </script> + </head> + <body onfocus="InitialFocus();" > + <form name="frmSelectUser"> + <table> + <tbody> + <tr> + <td><input name="radResidence" type="radio" value="KOR"></td> + </tr> + <tr> + <td><input name="radResidence" type="radio" value="JPN"></td> + </tr> + </tbody> + </table> + </form> + </body> +</html> diff --git a/dom/events/crashtests/1190036-1.html b/dom/events/crashtests/1190036-1.html new file mode 100644 index 000000000..00a6a25db --- /dev/null +++ b/dom/events/crashtests/1190036-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> + +function boom() { + var ev = new ClipboardEvent("p"); + ev.clipboardData.getFilesAndDirectories(); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/events/crashtests/135345-1.html b/dom/events/crashtests/135345-1.html new file mode 100644 index 000000000..f9bfe0275 --- /dev/null +++ b/dom/events/crashtests/135345-1.html @@ -0,0 +1,14 @@ +<html> +<body> + <form name="frmlogin"> + <input type="text" name="username" + onfocus="frmlogin.username.select();" + onblur="frmlogin.password.focus();"> + <input type="password" name="password" + onfocus="frmlogin.password.select();"> + </form> + <script language="JavaScript"> + document.frmlogin.username.focus(); + </script> +</body> +</html> diff --git a/dom/events/crashtests/422009-1.xhtml b/dom/events/crashtests/422009-1.xhtml new file mode 100644 index 000000000..1bf37906d --- /dev/null +++ b/dom/events/crashtests/422009-1.xhtml @@ -0,0 +1,31 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + +<bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="g"> + <implementation> + <constructor> + throw 3; + </constructor> + </implementation> + </binding> +</bindings> + +<script type="text/javascript"> + +function boom() +{ + var HTML_NS = "http://www.w3.org/1999/xhtml"; + var span = document.createElementNS(HTML_NS, 'span'); + span.style.MozBinding = "url(#g)"; + document.removeChild(document.documentElement) + var w = document.createElementNS(HTML_NS, 'body'); + w.setAttribute('onload', "/"); + document.appendChild(span); +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/dom/events/crashtests/457776-1.html b/dom/events/crashtests/457776-1.html new file mode 100644 index 000000000..2c3910815 --- /dev/null +++ b/dom/events/crashtests/457776-1.html @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<script> +new XMLHttpRequest().upload.onabort = function(){}; +</script> +</head> +<body> +</body> +</html> diff --git a/dom/events/crashtests/496308-1.html b/dom/events/crashtests/496308-1.html new file mode 100644 index 000000000..29d9a646c --- /dev/null +++ b/dom/events/crashtests/496308-1.html @@ -0,0 +1,3 @@ +<script> + document.createEvent("popupblockedevents").requestingWindow; +</script> diff --git a/dom/events/crashtests/682637-1.html b/dom/events/crashtests/682637-1.html new file mode 100644 index 000000000..5afc12377 --- /dev/null +++ b/dom/events/crashtests/682637-1.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> + +<head> +<script> + +function boom() +{ + var frame = document.getElementById("f"); + var frameWin = frame.contentWindow; + frame.parentNode.removeChild(frame); + frameWin.onmouseover = function(){}; +} + +</script> +</head> + +<body onload="boom();"> +<iframe id="f" src="data:text/html,1"></iframe> +</body> + +</html> diff --git a/dom/events/crashtests/938341.html b/dom/events/crashtests/938341.html new file mode 100644 index 000000000..3190b4a6b --- /dev/null +++ b/dom/events/crashtests/938341.html @@ -0,0 +1,7 @@ +<html hidden> +<script> +function a(){document.getElementById('id1').click();} +window.onload = a; +</script> +<applet <ol onclick='' contenteditable='true' onended='' oncanplay=''> +<article id='id1'></article>
\ No newline at end of file diff --git a/dom/events/crashtests/crashtests.list b/dom/events/crashtests/crashtests.list new file mode 100644 index 000000000..fb11e4ec5 --- /dev/null +++ b/dom/events/crashtests/crashtests.list @@ -0,0 +1,18 @@ +load 104310-1.html +load 116206-1.html +load 135345-1.html +load 422009-1.xhtml +load 457776-1.html +load 496308-1.html +load 682637-1.html +load 938341.html +load 1033343.html +load 1035654-1.html +load 1035654-2.html +needs-focus load 1072137-1.html +load 1143972-1.html +load 1190036-1.html +load eventctor-nulldictionary.html +load eventctor-nullstorage.html +load recursive-DOMNodeInserted.html +load recursive-onload.html diff --git a/dom/events/crashtests/eventctor-nulldictionary.html b/dom/events/crashtests/eventctor-nulldictionary.html new file mode 100644 index 000000000..f81399413 --- /dev/null +++ b/dom/events/crashtests/eventctor-nulldictionary.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> +new MouseEvent("click", null); +</script> diff --git a/dom/events/crashtests/eventctor-nullstorage.html b/dom/events/crashtests/eventctor-nullstorage.html new file mode 100644 index 000000000..beae2f3df --- /dev/null +++ b/dom/events/crashtests/eventctor-nullstorage.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> +new StorageEvent("").storageArea; +</script> diff --git a/dom/events/crashtests/recursive-DOMNodeInserted.html b/dom/events/crashtests/recursive-DOMNodeInserted.html new file mode 100644 index 000000000..c3e6f41d5 --- /dev/null +++ b/dom/events/crashtests/recursive-DOMNodeInserted.html @@ -0,0 +1,17 @@ +<HTML>
+<HEAD>
+<TITLE></TITLE>
+
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#800080" onload="test()">
+<INPUT id="txt1" type="text" value="">
+
+<SCRIPT>
+document.addEventListener("DOMNodeInserted",test,0);
+count = 0;
+function test(){
+ n = document.createTextNode("test");
+ document.body.appendChild(n);
+}
+</SCRIPT>
+</BODY>
diff --git a/dom/events/crashtests/recursive-onload.html b/dom/events/crashtests/recursive-onload.html new file mode 100644 index 000000000..e8f15610b --- /dev/null +++ b/dom/events/crashtests/recursive-onload.html @@ -0,0 +1 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<head>
<title>Goodjet!</title>
<script language="JavaScript">
function onload()
{
// nothing here
}
</script>
</head>
<body onload ="onload();">
Body text
</body>
</html>
\ No newline at end of file diff --git a/dom/events/moz.build b/dom/events/moz.build new file mode 100644 index 000000000..ee4946038 --- /dev/null +++ b/dom/events/moz.build @@ -0,0 +1,162 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += [ + 'test/mochitest.ini', + 'test/pointerevents/mochitest.ini', +] + +MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] + +XPIDL_SOURCES += [ + 'nsIEventListenerService.idl', +] + +XPIDL_MODULE = 'content_events' + +EXPORTS.mozilla += [ + 'AsyncEventDispatcher.h', + 'DOMEventTargetHelper.h', + 'EventDispatcher.h', + 'EventListenerManager.h', + 'EventNameList.h', + 'EventStateManager.h', + 'EventStates.h', + 'IMEStateManager.h', + 'InternalMutationEvent.h', + 'JSEventHandler.h', + 'KeyNameList.h', + 'PhysicalKeyCodeNameList.h', + 'TextComposition.h', + 'VirtualKeyCodeList.h', +] + +EXPORTS.mozilla.dom += [ + 'AnimationEvent.h', + 'BeforeAfterKeyboardEvent.h', + 'BeforeUnloadEvent.h', + 'ClipboardEvent.h', + 'CommandEvent.h', + 'CompositionEvent.h', + 'CustomEvent.h', + 'DataContainerEvent.h', + 'DataTransfer.h', + 'DataTransferItem.h', + 'DataTransferItemList.h', + 'DeviceMotionEvent.h', + 'DragEvent.h', + 'Event.h', + 'EventTarget.h', + 'FocusEvent.h', + 'ImageCaptureError.h', + 'InputEvent.h', + 'KeyboardEvent.h', + 'MessageEvent.h', + 'MouseEvent.h', + 'MouseScrollEvent.h', + 'MutationEvent.h', + 'NotifyPaintEvent.h', + 'PaintRequest.h', + 'PointerEvent.h', + 'ScrollAreaEvent.h', + 'SimpleGestureEvent.h', + 'StorageEvent.h', + 'TextClause.h', + 'Touch.h', + 'TouchEvent.h', + 'TransitionEvent.h', + 'UIEvent.h', + 'WheelEvent.h', + 'XULCommandEvent.h', +] + +if CONFIG['MOZ_WEBSPEECH']: + EXPORTS.mozilla.dom += ['SpeechRecognitionError.h'] + +UNIFIED_SOURCES += [ + 'AnimationEvent.cpp', + 'AsyncEventDispatcher.cpp', + 'BeforeAfterKeyboardEvent.cpp', + 'BeforeUnloadEvent.cpp', + 'ClipboardEvent.cpp', + 'CommandEvent.cpp', + 'CompositionEvent.cpp', + 'ContentEventHandler.cpp', + 'CustomEvent.cpp', + 'DataContainerEvent.cpp', + 'DataTransfer.cpp', + 'DataTransferItem.cpp', + 'DataTransferItemList.cpp', + 'DeviceMotionEvent.cpp', + 'DOMEventTargetHelper.cpp', + 'DragEvent.cpp', + 'Event.cpp', + 'EventDispatcher.cpp', + 'EventListenerManager.cpp', + 'EventListenerService.cpp', + 'EventTarget.cpp', + 'FocusEvent.cpp', + 'ImageCaptureError.cpp', + 'IMEContentObserver.cpp', + 'IMEStateManager.cpp', + 'InputEvent.cpp', + 'JSEventHandler.cpp', + 'KeyboardEvent.cpp', + 'MessageEvent.cpp', + 'MouseEvent.cpp', + 'MouseScrollEvent.cpp', + 'MutationEvent.cpp', + 'NotifyPaintEvent.cpp', + 'PaintRequest.cpp', + 'PointerEvent.cpp', + 'ScrollAreaEvent.cpp', + 'SimpleGestureEvent.cpp', + 'StorageEvent.cpp', + 'TextClause.cpp', + 'TextComposition.cpp', + 'Touch.cpp', + 'TouchEvent.cpp', + 'TransitionEvent.cpp', + 'UIEvent.cpp', + 'WheelEvent.cpp', + 'WheelHandlingHelper.cpp', + 'XULCommandEvent.cpp', +] + +# nsEventStateManager.cpp should be built separately because of Mac OS X headers. +SOURCES += [ + 'EventStateManager.cpp', +] + +if CONFIG['MOZ_WEBSPEECH']: + UNIFIED_SOURCES += ['SpeechRecognitionError.cpp'] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/docshell/base', + '/dom/base', + '/dom/html', + '/dom/settings', + '/dom/storage', + '/dom/svg', + '/dom/workers', + '/dom/xml', + '/dom/xul', + '/js/xpconnect/wrappers', + '/layout/generic', + '/layout/xul', + '/layout/xul/tree/', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + LOCAL_INCLUDES += [ + '/dom/wifi', + ] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/events/nsIEventListenerService.idl b/dom/events/nsIEventListenerService.idl new file mode 100644 index 000000000..d59574449 --- /dev/null +++ b/dom/events/nsIEventListenerService.idl @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMEventListener; +interface nsIDOMEventTarget; +interface nsIArray; + +/** + * Contains an event target along with an array of nsIAtom in form "oneventname" + * representing changed event listener names. + */ +[scriptable, uuid(07222b02-da12-4cf4-b2f7-761da007a8d8)] +interface nsIEventListenerChange : nsISupports +{ + readonly attribute nsIDOMEventTarget target; + readonly attribute nsIArray changedListenerNames; +}; + +[scriptable, function, uuid(aa7c95f6-d3b5-44b3-9597-1d9f19b9c5f2)] +interface nsIListenerChangeListener : nsISupports +{ + void listenersChanged(in nsIArray aEventListenerChanges); +}; + +/** + * An instance of this interface describes how an event listener + * was added to an event target. + */ +[scriptable, uuid(11ba5fd7-8db2-4b1a-9f67-342cfa11afad)] +interface nsIEventListenerInfo : nsISupports +{ + /** + * The type of the event for which the listener was added. + * Null if the listener is for all the events. + */ + readonly attribute AString type; + readonly attribute boolean capturing; + readonly attribute boolean allowsUntrusted; + readonly attribute boolean inSystemEventGroup; + + /** + * The underlying JS object of the event listener, if this listener + * has one. Null otherwise. + */ + [implicit_jscontext] + readonly attribute jsval listenerObject; + + /** + * Tries to serialize event listener to a string. + * Returns null if serialization isn't possible + * (for example with C++ listeners). + */ + AString toSource(); +}; + +[scriptable, uuid(77aab5f7-213d-4db4-9f22-e46dfb774f15)] +interface nsIEventListenerService : nsISupports +{ + /** + * Returns an array of nsIEventListenerInfo objects. + * If aEventTarget doesn't have any listeners, this returns null. + */ + void getListenerInfoFor(in nsIDOMEventTarget aEventTarget, + [optional] out unsigned long aCount, + [retval, array, size_is(aCount)] out + nsIEventListenerInfo aOutArray); + + /** + * Returns an array of event targets. + * aEventTarget will be at index 0. + * The objects are the ones that would be used as DOMEvent.currentTarget while + * dispatching an event to aEventTarget + * @note Some events, especially 'load', may actually have a shorter + * event target chain than what this methods returns. + */ + void getEventTargetChainFor(in nsIDOMEventTarget aEventTarget, + in boolean composed, + [optional] out unsigned long aCount, + [retval, array, size_is(aCount)] out + nsIDOMEventTarget aOutArray); + + /** + * Returns true if a event target has any listener for the given type. + */ + boolean hasListenersFor(in nsIDOMEventTarget aEventTarget, + in DOMString aType); + + /** + * Add a system-group eventlistener to a event target. + */ + void addSystemEventListener(in nsIDOMEventTarget target, + in DOMString type, + in nsIDOMEventListener listener, + in boolean useCapture); + + /** + * Remove a system-group eventlistener from a event target. + */ + void removeSystemEventListener(in nsIDOMEventTarget target, + in DOMString type, + in nsIDOMEventListener listener, + in boolean useCapture); + + void addListenerForAllEvents(in nsIDOMEventTarget target, + in nsIDOMEventListener listener, + [optional] in boolean aUseCapture, + [optional] in boolean aWantsUntrusted, + [optional] in boolean aSystemEventGroup); + + void removeListenerForAllEvents(in nsIDOMEventTarget target, + in nsIDOMEventListener listener, + [optional] in boolean aUseCapture, + [optional] in boolean aSystemEventGroup); + + void addListenerChangeListener(in nsIListenerChangeListener aListener); + void removeListenerChangeListener(in nsIListenerChangeListener aListener); +}; + diff --git a/dom/events/test/bug1017086_inner.html b/dom/events/test/bug1017086_inner.html new file mode 100644 index 000000000..7ef0f6d86 --- /dev/null +++ b/dom/events/test/bug1017086_inner.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1017086 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1017086</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + /** Test for Bug 1017086 **/ + var testelem = undefined; + var pointer_events = ["onpointerover", "onpointerenter", + "onpointermove", + "onpointerdown", "onpointerup", + "onpointerout", "onpointerleave", + "onpointercancel"]; + function check(expected_value, event_name, container, container_name) { + var text = event_name + " in " + container_name + " should be " + expected_value; + parent.is(event_name in container, expected_value, text); + } + function runTest() { + testelem = document.getElementById("test"); + is(!!testelem, true, "Document should have element with id 'test'"); + parent.turnOnOffPointerEvents( function() { + parent.part_of_checks(pointer_events, check, window, document, testelem); + }); + } + </script> +</head> +<body onload="runTest();"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1017086">Mozilla Bug 1017086</a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/dom/events/test/bug1096146_embedded.html b/dom/events/test/bug1096146_embedded.html new file mode 100644 index 000000000..b1c1dd406 --- /dev/null +++ b/dom/events/test/bug1096146_embedded.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Embedded iframe</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload=""> + <p id="display"></p> + <h1>Top</h1> + <input id="input" style="display: block;"> + <pre id="test"> + <div id="content" style="height: 2000px;"></div> + <h1>Bottom</h1> +</body> +</html> diff --git a/dom/events/test/bug226361_iframe.xhtml b/dom/events/test/bug226361_iframe.xhtml new file mode 100644 index 000000000..df38a8bcb --- /dev/null +++ b/dom/events/test/bug226361_iframe.xhtml @@ -0,0 +1,47 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=226361 +--> +<head> + <title>Test for Bug 226361</title> +</head> +<body id="body"> +<p id="display"> + +<a id="a1" tabindex="3" href="http://home.mozilla.org">This is the 1st + +link but the 3rd tabindex</a><br /> + +<br /> + + <a id="a2" tabindex="4" href="http://home.mozilla.org">This is the 2nd + +link but the 4th tabindex</a><br /> + +<br /> + + <a id="a3" tabindex="1" href="http://home.mozilla.org">This is the 3rd + +link but the 1st tabindex</a><br /> + +<br /> + + <a id="a4" tabindex="5" href="http://home.mozilla.org">This is the 4th + +link but the 5th tabindex</a><br /> + +<br /> + + <a id="a5" tabindex="2" href="http://home.mozilla.org">This is the 5th + +link but the 2nd tabindex</a> + +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> + +</pre> +</body> +</html> diff --git a/dom/events/test/bug299673.js b/dom/events/test/bug299673.js new file mode 100644 index 000000000..f426ffda3 --- /dev/null +++ b/dom/events/test/bug299673.js @@ -0,0 +1,105 @@ +var popup; + +function OpenWindow() +{ +log({},">>> OpenWindow"); + popup = window.open("","Test"); + + var output = "<html>"; + + output+="<body>"; + output+="<form>" + output+="<input id='popupText1' type='text' onfocus='opener.log(event)' onblur='opener.log(event)'>"; + output+="</form>" + output+="</body>"; + output+="</html>"; + + popup.document.open(); + popup.document.write(output); + popup.document.close(); + + popup.document.onclick=function (event) { log(event,"popup-doc") }; + popup.document.onfocus=function (event) { log(event,"popup-doc") }; + popup.document.onblur=function (event) { log(event,"popup-doc") }; + popup.document.onchange=function (event) { log(event,"popup-doc") }; + + var e = popup.document.getElementById('popupText1'); + popup.focus(); + e.focus(); + is(popup.document.activeElement, e, "input element in popup should be focused"); +log({},"<<< OpenWindow"); +} + +var result; + +function log(event,message) { + if (event&&event.eventPhase==3) return; + e = event.currentTarget||event.target||event.srcElement; + var id = e?(e.id?e.id:e.name?e.name:e.value?e.value:''):''; + if (id) id = '(' + id + ')'; + result += + (e?(e.tagName?e.tagName:''):' ')+id+': '+ + (event.type?event.type:'')+' '+ + (message?message:'') + '\n'; +} + +document.onclick=function (event) { log(event,"top-doc") }; +document.onfocus=function (event) { log(event,"top-doc") }; +document.onblur=function (event) { log(event,"top-doc") }; +document.onchange=function (event) { log(event,"top-doc") }; + +function doTest1_rest2(expectedEventLog,focusAfterCloseId) { + try { + is(document.activeElement, document.getElementById(focusAfterCloseId), "wrong element is focused after popup was closed"); + is(result, expectedEventLog, "unexpected events"); + SimpleTest.finish(); + } catch(e) { + if (popup) + popup.close(); + throw e; + } +} +function doTest1_rest1(expectedEventLog,focusAfterCloseId) { + try { + synthesizeKey("V", {}, popup); + synthesizeKey("A", {}, popup); + synthesizeKey("L", {}, popup); + is(popup.document.getElementById('popupText1').value, "VAL", "input element in popup did not accept input"); + + var p = popup; + popup = null; + p.close(); + + SimpleTest.waitForFocus(function () { doTest1_rest2(expectedEventLog,focusAfterCloseId); }, window); + } catch(e) { + if (popup) + popup.close(); + throw e; + } +} + +function doTest1(expectedEventLog,focusAfterCloseId) { + try { + var select1 = document.getElementById('Select1'); + select1.focus(); + is(document.activeElement, select1, "select element should be focused"); + synthesizeKey("VK_DOWN",{}); + synthesizeKey("VK_TAB", {}); + SimpleTest.waitForFocus(function () { doTest1_rest1(expectedEventLog,focusAfterCloseId); }, popup); + + } catch(e) { + if (popup) + popup.close(); + throw e; + } +} + +function setPrefAndDoTest(expectedEventLog,focusAfterCloseId,prefValue) { + var select1 = document.getElementById('Select1'); + select1.blur(); + result = ""; + log({},"Test with browser.link.open_newwindow = "+prefValue); + SpecialPowers.pushPrefEnv({"set": [['browser.link.open_newwindow', prefValue]]}, function() { + doTest1(expectedEventLog,focusAfterCloseId); + }); +} diff --git a/dom/events/test/bug322588-popup.html b/dom/events/test/bug322588-popup.html new file mode 100644 index 000000000..767eb9db9 --- /dev/null +++ b/dom/events/test/bug322588-popup.html @@ -0,0 +1 @@ +<html><head></head><body onblur="window.close()"><a id="target">a id=target</a></body></html> diff --git a/dom/events/test/bug415498-doc1.html b/dom/events/test/bug415498-doc1.html new file mode 100644 index 000000000..91c04832a --- /dev/null +++ b/dom/events/test/bug415498-doc1.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> +<script type="text/javascript"> +function init() { + // This will throw a HierarchyRequestError exception + var doc = document.implementation.createDocument(null, 'DOC', null); + doc.documentElement.appendChild(doc); +} +window.addEventListener("load", init, false); +</script> +</head> +<body> + Testcase for bug 415498. This page should show an exception in Error Console on load +</body> diff --git a/dom/events/test/bug415498-doc2.html b/dom/events/test/bug415498-doc2.html new file mode 100644 index 000000000..e556a4e4c --- /dev/null +++ b/dom/events/test/bug415498-doc2.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> +<script type="text/javascript"> +function init() { + // This will throw a HierarchyRequestError exception + var doc = document.implementation.createDocument(null, 'DOC', null); + doc.documentElement.appendChild(doc); +} +onload = init; +</script> +</head> +<body> + Testcase for bug 415498. This page should show an exception in Error Console on load +</body> diff --git a/dom/events/test/bug418986-3.js b/dom/events/test/bug418986-3.js new file mode 100644 index 000000000..6bd2a6e69 --- /dev/null +++ b/dom/events/test/bug418986-3.js @@ -0,0 +1,69 @@ +SimpleTest.waitForExplicitFinish(); + +// The main testing function. +var test = function (isContent) { + // Each definition is [eventType, prefSetting] + // Where we are setting the "privacy.resistFingerprinting" pref. + let eventDefs = [["mousedown", true], + ["mouseup", true], + ["mousedown", false], + ["mouseup", false]]; + + let testCounter = 0; + + // Declare ahead of time. + let setup; + + // This function is called when the event handler fires. + let handleEvent = function (event, prefVal) { + let resisting = prefVal && isContent; + if (resisting) { + is(event.screenX, event.clientX, "event.screenX and event.clientX should be the same"); + is(event.screenY, event.clientY, "event.screenY and event.clientY should be the same"); + } else { + // We can't be sure about X coordinates not being equal, but we can test Y. + isnot(event.screenY, event.clientY, "event.screenY !== event.clientY"); + } + ++testCounter; + if (testCounter < eventDefs.length) { + nextTest(); + } else { + SimpleTest.finish(); + } + }; + + // In this function, we set up the nth div and event handler, + // and then synthesize a mouse event in the div, to test + // whether the resulting events resist fingerprinting by + // suppressing absolute screen coordinates. + nextTest = function () { + let [eventType, prefVal] = eventDefs[testCounter]; + SpecialPowers.pushPrefEnv({set:[["privacy.resistFingerprinting", prefVal]]}, + function () { + // The following code creates a new div for each event in eventDefs, + // attaches a listener to listen for the event, and then generates + // a fake event at the center of the div. + let div = document.createElement("div"); + div.style.width = "10px"; + div.style.height = "10px"; + div.style.backgroundColor = "red"; + // Name the div after the event we're listening for. + div.id = eventType; + document.getElementById("body").appendChild(div); + // Seems we can't add an event listener in chrome unless we run + // it in a later task. + window.setTimeout(function() { + div.addEventListener(eventType, event => handleEvent(event, prefVal), false); + // For some reason, the following synthesizeMouseAtCenter call only seems to run if we + // wrap it in a window.setTimeout(..., 0). + window.setTimeout(function () { + synthesizeMouseAtCenter(div, {type : eventType}); + }, 0); + }, 0); + }); + }; + + // Now run by starting with the 0th event. + nextTest(); + +}; diff --git a/dom/events/test/bug426082.html b/dom/events/test/bug426082.html new file mode 100644 index 000000000..8eaaa7391 --- /dev/null +++ b/dom/events/test/bug426082.html @@ -0,0 +1,165 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=426082 +--> +<head> + <title>Test for Bug 426082</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + canvas { + display: none; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=426082">Mozilla Bug 426082</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<p><input type="button" value="Button" id="button"></p> +<p><label for="button" id="label">Label</label></p> +<p id="outside">Something under the label</p> +<pre id="test"> +<script type="application/javascript;version=1.8"> + +/** Test for Bug 426082 **/ + +var normalButtonCanvas, pressedButtonCanvas, normalFocusedButtonCanvas, + pressedFocusedButtonCanvas, currentSnapshot, button, label, outside; + +function runTests() { + button = $("button"); + label = $("label"); + outside = $("outside"); + SimpleTest.executeSoon(executeTests); +} + +SimpleTest.waitForFocus(runTests); + +function isRectContainedInRectFromRegion(rect, region) { + return Array.some(region, function (r) { + return rect.left >= r.left && + rect.top >= r.top && + rect.right <= r.right && + rect.bottom <= r.bottom; + }); +} + +function paintListener(e) { + if (isRectContainedInRectFromRegion(buttonRect(), SpecialPowers.wrap(e).clientRects)) { + gNeedsPaint = false; + currentSnapshot = takeSnapshot(); + } +} + +var gNeedsPaint = false; +function executeTests() { + var testYielder = tests(); + function execNext() { + try { + if (!gNeedsPaint) { + testYielder.next(); + button.getBoundingClientRect(); // Flush. + gNeedsPaint = true; + } + SimpleTest.executeSoon(execNext); + } catch (e) {} + } + execNext(); +} + +function tests() { + window.addEventListener("MozAfterPaint", paintListener, false); + normalButtonCanvas = takeSnapshot(); + // Press the button. + sendMouseEvent("mousemove", button); + sendMouseEvent("mousedown", button); + yield undefined; + pressedFocusedButtonCanvas = takeSnapshot(); + compareSnapshots_(normalButtonCanvas, pressedFocusedButtonCanvas, false, "Pressed focused buttons should look different from normal buttons."); + // Release. + sendMouseEvent("mouseup", button); + yield undefined; + // make sure the button is focused as this doesn't happen on click on Mac + button.focus(); + normalFocusedButtonCanvas = takeSnapshot(); + compareSnapshots_(normalFocusedButtonCanvas, pressedFocusedButtonCanvas, false, "Pressed focused buttons should look different from normal focused buttons."); + // Unfocus the button. + sendMouseEvent("mousedown", outside); + sendMouseEvent("mouseup", outside); + yield undefined; + + // Press the label. + sendMouseEvent("mousemove", label); + sendMouseEvent("mousedown", label); + yield undefined; + compareSnapshots_(normalButtonCanvas, currentSnapshot, false, "Pressing the label should have pressed the button."); + pressedButtonCanvas = takeSnapshot(); + // Move the mouse down from the label. + sendMouseEvent("mousemove", outside); + yield undefined; + compareSnapshots_(normalButtonCanvas, currentSnapshot, true, "Moving the mouse down from the label should have unpressed the button."); + // ... and up again. + sendMouseEvent("mousemove", label); + yield undefined; + compareSnapshots_(pressedButtonCanvas, currentSnapshot, true, "Moving the mouse back on top of the label should have pressed the button."); + // Release. + sendMouseEvent("mouseup", label); + yield undefined; + var focusOnMouse = (navigator.platform.indexOf("Mac") != 0); + compareSnapshots_(focusOnMouse ? normalFocusedButtonCanvas : normalButtonCanvas, + currentSnapshot, true, "Releasing the mouse over the label should have unpressed" + + (focusOnMouse ? " (and focused)" : "") + " the button."); + // Press the label and remove it. + sendMouseEvent("mousemove", label); + sendMouseEvent("mousedown", label); + yield undefined; + label.parentNode.removeChild(label); + yield undefined; + compareSnapshots_(normalButtonCanvas, currentSnapshot, true, "Removing the label should have unpressed the button."); + sendMouseEvent("mouseup", label); + window.removeEventListener("MozAfterPaint", paintListener, false); + window.opener.finishTests(); + } + +function sendMouseEvent(t, elem) { + var r = elem.getBoundingClientRect(); + synthesizeMouse(elem, r.width / 2, r.height / 2, {type: t}); +} + +function compareSnapshots_(c1, c2, shouldBeIdentical, msg) { + var [correct, c1url, c2url] = compareSnapshots(c1, c2, shouldBeIdentical); + if (correct) { + if (shouldBeIdentical) { + window.opener.ok(true, msg + " - expected " + c1url); + } else { + window.opener.ok(true, msg + " - got " + c1url + " and " + c2url); + } + } else { + if (shouldBeIdentical) { + window.opener.ok(false, msg + " - expected " + c1url + " but got " + c2url); + } else { + window.opener.ok(false, msg + " - expected something other than " + c1url); + } + } +} + +function takeSnapshot() { + var r = buttonRect(); + adjustedRect = { left: r.left - 2, top: r.top - 2, + width: r.width + 4, height: r.height + 4 }; + return SpecialPowers.snapshotRect(window, adjustedRect); +} + +function buttonRect() { + return button.getBoundingClientRect(); +} +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/bug591249_iframe.xul b/dom/events/test/bug591249_iframe.xul new file mode 100644 index 000000000..7c7d7642b --- /dev/null +++ b/dom/events/test/bug591249_iframe.xul @@ -0,0 +1,33 @@ +<?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"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=591249 +--> +<window title="Mozilla Bug 591249" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <html:style type="text/css"> + #drop-target { + width: 50px; + height: 50px; + border: 4px dotted black; + } + #drop-target { + background-color: red; + } + #drop-target:-moz-drag-over { + background-color: yellow; + } + </html:style> + + <html:body> + <html:h1 id="iframetext">Iframe for Bug 591249</html:h1> + + <html:div id="drop-target" + ondrop="return false;" + ondragenter="return false;" + ondragover="return false;"> + </html:div> + </html:body> +</window> diff --git a/dom/events/test/bug602962.xul b/dom/events/test/bug602962.xul new file mode 100644 index 000000000..0d54b7ad5 --- /dev/null +++ b/dom/events/test/bug602962.xul @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window onload="window.opener.doTest()" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <scrollbox id="page-scrollbox" style="border: 1px solid red; background-color: black;overflow: auto" flex="1"> + <box id="page-box" style="border: 1px solid green;"/> + </scrollbox> +</window> diff --git a/dom/events/test/bug656379-1.html b/dom/events/test/bug656379-1.html new file mode 100644 index 000000000..d7a04e3bf --- /dev/null +++ b/dom/events/test/bug656379-1.html @@ -0,0 +1,185 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=656379 +--> +<head> + <title>Test for Bug 656379</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + canvas { + display: none; + } + input[type=button] { + -moz-appearance: none; + padding: 0; + border: none; + color: black; + background: white; + } + input[type=button]::-moz-focus-inner { border: none; } + + /* Make sure that normal, focused, hover+active, focused+hover+active + buttons all have different styles so that the test keeps moving along. */ + input[type=button]:hover:active { + background: red; + } + input[type=button]:focus { + background: green; + } + input[type=button]:focus:hover:active { + background: purple; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=656379">Mozilla Bug 656379</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.8"> + + +var normalButtonCanvas, pressedButtonCanvas, normalFocusedButtonCanvas, + pressedFocusedButtonCanvas, currentSnapshot, button, label, outside; + +function runTests() { + button = $("button"); + label = $("label"); + outside = $("outside"); + SimpleTest.executeSoon(executeTests); +} + +SimpleTest.waitForFocus(runTests); + +function isRectContainedInRectFromRegion(rect, region) { + return Array.some(region, function (r) { + return rect.left >= r.left && + rect.top >= r.top && + rect.right <= r.right && + rect.bottom <= r.bottom; + }); +} + +function paintListener(e) { + if (isRectContainedInRectFromRegion(buttonRect(), SpecialPowers.wrap(e).clientRects)) { + gNeedsPaint = false; + currentSnapshot = takeSnapshot(); + } +} + +var gNeedsPaint = false; +function executeTests() { + var testYielder = tests(); + function execNext() { + try { + if (!gNeedsPaint) { + testYielder.next(); + button.getBoundingClientRect(); // Flush. + gNeedsPaint = true; + } + SimpleTest.executeSoon(execNext); + } catch (e) {} + } + execNext(); +} + +function tests() { + window.addEventListener("MozAfterPaint", paintListener, false); + normalButtonCanvas = takeSnapshot(); + // Press the button. + sendMouseEvent("mousemove", button); + sendMouseEvent("mousedown", button); + yield undefined; + pressedFocusedButtonCanvas = takeSnapshot(); + compareSnapshots_(normalButtonCanvas, pressedFocusedButtonCanvas, false, "Pressed focused buttons should look different from normal buttons."); + // Release. + sendMouseEvent("mouseup", button); + yield undefined; + // make sure the button is focused as this doesn't happen on click on Mac + button.focus(); + normalFocusedButtonCanvas = takeSnapshot(); + compareSnapshots_(normalFocusedButtonCanvas, pressedFocusedButtonCanvas, false, "Pressed focused buttons should look different from normal focused buttons."); + // Unfocus the button. + sendMouseEvent("mousedown", outside); + sendMouseEvent("mouseup", outside); + yield undefined; + + // Press the label. + sendMouseEvent("mousemove", label); + sendMouseEvent("mousedown", label); + yield undefined; + compareSnapshots_(normalButtonCanvas, currentSnapshot, false, "Pressing the label should have pressed the button."); + pressedButtonCanvas = takeSnapshot(); + // Move the mouse down from the label. + sendMouseEvent("mousemove", outside); + yield undefined; + compareSnapshots_(normalButtonCanvas, currentSnapshot, true, "Moving the mouse down from the label should have unpressed the button."); + // ... and up again. + sendMouseEvent("mousemove", label); + yield undefined; + compareSnapshots_(pressedButtonCanvas, currentSnapshot, true, "Moving the mouse back on top of the label should have pressed the button."); + // Release. + sendMouseEvent("mouseup", label); + yield undefined; + var focusOnMouse = (navigator.platform.indexOf("Mac") != 0); + compareSnapshots_(focusOnMouse ? normalFocusedButtonCanvas : normalButtonCanvas, + currentSnapshot, true, "Releasing the mouse over the label should have unpressed" + + (focusOnMouse ? " (and focused)" : "") + " the button."); + // Press the label and remove it. + sendMouseEvent("mousemove", label); + sendMouseEvent("mousedown", label); + yield undefined; + label.parentNode.removeChild(label); + yield undefined; + compareSnapshots_(normalButtonCanvas, currentSnapshot, true, "Removing the label should have unpressed the button."); + sendMouseEvent("mouseup", label); + window.removeEventListener("MozAfterPaint", paintListener, false); + window.opener.finishTests(); + } + +function sendMouseEvent(t, elem) { + var r = elem.getBoundingClientRect(); + synthesizeMouse(elem, r.width / 2, r.height / 2, {type: t}); +} + +function compareSnapshots_(c1, c2, shouldBeIdentical, msg) { + var [correct, c1url, c2url] = compareSnapshots(c1, c2, shouldBeIdentical); + if (correct) { + if (shouldBeIdentical) { + window.opener.ok(true, msg + " - expected " + c1url); + } else { + window.opener.ok(true, msg + " - got " + c1url + " and " + c2url); + } + } else { + if (shouldBeIdentical) { + window.opener.ok(false, msg + " - expected " + c1url + " but got " + c2url); + } else { + window.opener.ok(false, msg + " - expected something other than " + c1url); + } + } +} + +function takeSnapshot(canvas) { + var r = buttonRect(); + adjustedRect = { left: r.left - 2, top: r.top - 2, + width: r.width + 4, height: r.height + 4 }; + return SpecialPowers.snapshotRect(window, adjustedRect); +} + +function buttonRect() { + return button.getBoundingClientRect(); +} +</script> +</pre> +<p><input type="button" value="Button" id="button"></p> +<p><label for="button" id="label">Label</label></p> +<p id="outside">Something under the label</p> + +</body> +</html> diff --git a/dom/events/test/bug989198_embedded.html b/dom/events/test/bug989198_embedded.html new file mode 100644 index 000000000..cee7683cf --- /dev/null +++ b/dom/events/test/bug989198_embedded.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Embedded iframe</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="getFocus();"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <input id="input" style="display: block;"> + <pre id="test"> + <script type="application/javascript"> + function getFocus() { + input = document.getElementById("input"); + input.focus(); + } + </script> +</body> +</html> diff --git a/dom/events/test/bug989198_helper.js b/dom/events/test/bug989198_helper.js new file mode 100644 index 000000000..2d74831e9 --- /dev/null +++ b/dom/events/test/bug989198_helper.js @@ -0,0 +1,192 @@ +/* + * Helper functions for testing BeforeAfterKeyboardEvent. + */ + +const kUnknownEvent = 0x000; +const kKeyDownEvent = 0x001; +const kKeyUpEvent = 0x002; +const kBeforeEvent = 0x010; +const kAfterEvent = 0x020; +const kParent = 0x100; +const kChild = 0x200; + +var gCurrentTest; + +function frameScript() +{ + function handler(e) { + var results = sendSyncMessage("forwardevent", { type: e.type }); + if (results[0]) { + e.preventDefault(); + } + } + addEventListener('keydown', handler); + addEventListener('keyup', handler); + addEventListener('mozbrowserbeforekeydown', handler); + addEventListener('mozbrowserbeforekeyup', handler); + addEventListener('mozbrowserafterkeydown', handler); + addEventListener('mozbrowserafterkeyup', handler); +} + +function prepareTest(useRemote) +{ + if (useRemote) { + setupHandlers(window, embedderHandler); + } else { + setupHandlers(window, embedderHandlerWithCheck); + } + + var iframe = document.createElement("iframe"); + iframe.id = "embedded"; + iframe.src = "bug989198_embedded.html"; + iframe.setAttribute("remote", useRemote ? "true" : "false"); + SpecialPowers.wrap(iframe).mozbrowser = true; + + iframe.addEventListener("mozbrowserloadend", function onloadend() { + iframe.removeEventListener("mozbrowserloadend", onloadend); + iframe.focus(); + var mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + mm.addMessageListener("forwardevent", function(msg) { + return embeddedHandler(msg.json); + }); + mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false); + runTests(); + return; + }); + + document.body.appendChild(iframe); +} + +function setupHandlers(element, handler) +{ + element.addEventListener('keydown', handler); + element.addEventListener('keyup', handler); + element.addEventListener('mozbrowserbeforekeydown', handler); + element.addEventListener('mozbrowserbeforekeyup', handler); + element.addEventListener('mozbrowserafterkeydown', handler); + element.addEventListener('mozbrowserafterkeyup', handler); +} + +function teardownHandlers(element, handler) +{ + element.removeEventListener('keydown', handler); + element.removeEventListener('keyup', handler); + element.removeEventListener('mozbrowserbeforekeydown', handler); + element.removeEventListener('mozbrowserbeforekeyup', handler); + element.removeEventListener('mozbrowserafterkeydown', handler); + element.removeEventListener('mozbrowserafterkeyup', handler); +} + +function convertNameToCode(name) +{ + switch (name) { + case "mozbrowserbeforekeydown": + return kBeforeEvent | kKeyDownEvent; + case "mozbrowserafterkeydown": + return kAfterEvent | kKeyDownEvent; + case "mozbrowserbeforekeyup": + return kBeforeEvent | kKeyUpEvent; + case "mozbrowserafterkeyup": + return kAfterEvent | kKeyUpEvent; + case "keydown": + return kKeyDownEvent; + case "keyup": + return kKeyUpEvent; + default: + return kUnknownEvent; + } +} + +function classifyEvents(test) +{ + // Categorize resultEvents into KEYDOWN group and KEYUP group. + for (var i = 0; i < gCurrentTest.resultEvents.length ; i++) { + var code = test.resultEvents[i]; + if ((code & 0xF) == 0x1) { // KEYDOWN + test.classifiedEvents[0].push(code); + } else if ((code & 0xF) == 0x2) { // KEYUP + test.classifiedEvents[1].push(code); + } else { + ok(false, "Invalid code for events"); + } + } +} + +function verifyResults(test) +{ + for (var i = 0; i < gCurrentTest.expectedEvents.length; i++) { + is(test.classifiedEvents[i].length, + test.expectedEvents[i].length, + test.description + ": Wrong number of events"); + + for (var j = 0; j < gCurrentTest.classifiedEvents[i].length; j++) { + var item = test.classifiedEvents[i][j]; + is(item, test.expectedEvents[i][j], + test.description + ": Wrong order of events"); + } + } +} + +function embeddedHandler(e) +{ + return handler(e, kChild); +} + +function embedderHandler(e, callback) +{ + handler(e, kParent, callback); +} + +function handler(e, highBit, callback) +{ + var code = convertNameToCode(e.type); + var newCode = highBit | code; + gCurrentTest.resultEvents.push(newCode); + + if (callback) { + callback(code); + } + + if (highBit == kChild) { + // return and let frameScript to handle + return newCode == gCurrentTest.doPreventDefaultAt; + } + + if (newCode == gCurrentTest.doPreventDefaultAt) { + e.preventDefault(); + } +} + +function embedderHandlerWithCheck(e) +{ + // Verify value of attribute embeddedCancelled + embedderHandler(e, function checkEmbeddedCancelled(code){ + switch (code) { + case kBeforeEvent | kKeyDownEvent: + case kBeforeEvent | kKeyUpEvent: + is(e.embeddedCancelled, null, + gCurrentTest.description + ": embeddedCancelled should be null"); + break; + case kAfterEvent | kKeyDownEvent: + if ((gCurrentTest.doPreventDefaultAt & 0xFF) == kKeyDownEvent) { + is(e.embeddedCancelled, true, + gCurrentTest.description + ": embeddedCancelled should be true"); + } else { + is(e.embeddedCancelled, false, + gCurrentTest.description + ": embeddedCancelled should be false"); + } + break; + case kAfterEvent | kKeyUpEvent: + if ((gCurrentTest.doPreventDefaultAt & 0xFF) == kKeyUpEvent) { + is(e.embeddedCancelled, true, + gCurrentTest.description + ": embeddedCancelled should be true"); + } else { + is(e.embeddedCancelled, false, + gCurrentTest.description + ": embeddedCancelled should be false"); + } + break; + default: + break; + } + }); +} diff --git a/dom/events/test/chrome.ini b/dom/events/test/chrome.ini new file mode 100644 index 000000000..111100a75 --- /dev/null +++ b/dom/events/test/chrome.ini @@ -0,0 +1,28 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + bug415498-doc1.html + bug415498-doc2.html + bug418986-3.js + bug591249_iframe.xul + bug602962.xul + file_bug679494.html + window_bug617528.xul + test_bug336682.js + +[test_bug336682_2.xul] +[test_bug368835.html] +[test_bug415498.xul] +[test_bug418986-3.xul] +[test_bug524674.xul] +[test_bug586961.xul] +[test_bug591249.xul] +[test_bug602962.xul] +[test_bug617528.xul] +[test_bug679494.xul] +[test_bug930374-chrome.html] +[test_bug1128787-1.html] +[test_bug1128787-2.html] +[test_bug1128787-3.html] +[test_eventctors.xul] +[test_DataTransferItemList.html] diff --git a/dom/events/test/empty.js b/dom/events/test/empty.js new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/dom/events/test/empty.js diff --git a/dom/events/test/error_event_worker.js b/dom/events/test/error_event_worker.js new file mode 100644 index 000000000..9f6e0e4bd --- /dev/null +++ b/dom/events/test/error_event_worker.js @@ -0,0 +1,15 @@ + addEventListener("error", function(e) { + var obj = {}; + for (var prop of ["message", "filename", "lineno"]) { + obj[prop] = e[prop] + } + obj.type = "event"; + postMessage(obj); +}); +onerror = function(message, filename, lineno) { + var obj = { message: message, filename: filename, lineno: lineno, + type: "callback" } + postMessage(obj); + return false; +} +throw new Error("workerhello"); diff --git a/dom/events/test/file_bug679494.html b/dom/events/test/file_bug679494.html new file mode 100644 index 000000000..a2e47916c --- /dev/null +++ b/dom/events/test/file_bug679494.html @@ -0,0 +1,8 @@ +<html> +<head> + <title>Test for Bug 679494</title> +</head> +<body> + There and back again. +</body> +</html> diff --git a/dom/events/test/marionette/head.js b/dom/events/test/marionette/head.js new file mode 100644 index 000000000..c2357f898 --- /dev/null +++ b/dom/events/test/marionette/head.js @@ -0,0 +1,142 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers; + +var Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; + +var _pendingEmulatorCmdCount = 0; + +/** + * Send emulator command with safe guard. + * + * We should only call |finish()| after all emulator command transactions + * end, so here comes with the pending counter. Resolve when the emulator + * gives positive response, and reject otherwise. + * + * Fulfill params: + * result -- an array of emulator response lines. + * Reject params: + * result -- an array of emulator response lines. + * + * @return A deferred promise. + */ +function runEmulatorCmdSafe(aCommand) { + let deferred = Promise.defer(); + + ++_pendingEmulatorCmdCount; + runEmulatorCmd(aCommand, function(aResult) { + --_pendingEmulatorCmdCount; + + ok(true, "Emulator response: " + JSON.stringify(aResult)); + if (Array.isArray(aResult) && + aResult[aResult.length - 1] === "OK") { + deferred.resolve(aResult); + } else { + deferred.reject(aResult); + } + }); + + return deferred.promise; +} + +/** + * Get emulator sensor values of a named sensor. + * + * Fulfill params: + * result -- an array of emulator sensor values. + * Reject params: (none) + * + * @param aSensorName + * A string name of the sensor. Availables are: "acceleration" + * "magnetic-field", "orientation", "temperature", "proximity". + * + * @return A deferred promise. + */ +function getEmulatorSensorValues(aSensorName) { + return runEmulatorCmdSafe("sensor get " + aSensorName) + .then(function(aResult) { + // aResult = ["orientation = 0:0:0", "OK"] + return aResult[0].split(" ")[2].split(":").map(function(aElement) { + return parseInt(aElement, 10); + }); + }); +} + +/** + * Convenient alias function for getting orientation sensor values. + */ +function getEmulatorOrientationValues() { + return getEmulatorSensorValues("orientation"); +} + +/** + * Set emulator orientation sensor values. + * + * Fulfill params: (none) + * Reject params: (none) + * + * @param aAzimuth + * @param aPitch + * @param aRoll + * + * @return A deferred promise. + */ +function setEmulatorOrientationValues(aAzimuth, aPitch, aRoll) { + let cmd = "sensor set orientation " + aAzimuth + ":" + aPitch + ":" + aRoll; + return runEmulatorCmdSafe(cmd); +} + +/** + * Wait for a named window event. + * + * Resolve if that named event occurs. Never reject. + * + * Forfill params: the DOMEvent passed. + * + * @param aEventName + * A string event name. + * + * @return A deferred promise. + */ +function waitForWindowEvent(aEventName) { + let deferred = Promise.defer(); + + window.addEventListener(aEventName, function onevent(aEvent) { + window.removeEventListener(aEventName, onevent); + + ok(true, "Window event '" + aEventName + "' got."); + deferred.resolve(aEvent); + }); + + return deferred.promise; +} + +/** + * Wait for pending emulator transactions and call |finish()|. + */ +function cleanUp() { + // Use ok here so that we have at least one test run. + ok(true, ":: CLEANING UP ::"); + + waitFor(finish, function() { + return _pendingEmulatorCmdCount === 0; + }); +} + +/** + * Basic test routine helper. + * + * This helper does nothing but clean-ups. + * + * @param aTestCaseMain + * A function that takes no parameter. + */ +function startTestBase(aTestCaseMain) { + Promise.resolve() + .then(aTestCaseMain) + .then(cleanUp, function() { + ok(false, 'promise rejects during test.'); + cleanUp(); + }); +} diff --git a/dom/events/test/marionette/manifest.ini b/dom/events/test/marionette/manifest.ini new file mode 100644 index 000000000..b0ec98d99 --- /dev/null +++ b/dom/events/test/marionette/manifest.ini @@ -0,0 +1,4 @@ +[DEFAULT] +run-if = buildapp == 'b2g' + +[test_sensor_orientation.js] diff --git a/dom/events/test/marionette/test_sensor_orientation.js b/dom/events/test/marionette/test_sensor_orientation.js new file mode 100644 index 000000000..70df0ccd3 --- /dev/null +++ b/dom/events/test/marionette/test_sensor_orientation.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 120000; +MARIONETTE_HEAD_JS = 'head.js'; + +function doTest(aAzimuth, aPitch, aRoll) { + log("Testing [azimuth, pitch, roll] = " + Array.slice(arguments)); + + return setEmulatorOrientationValues(aAzimuth, aPitch, aRoll) + .then(() => waitForWindowEvent("deviceorientation")) + .then(function(aEvent) { + is(aEvent.alpha, aAzimuth, "azimuth"); + is(aEvent.beta, aPitch, "pitch"); + is(aEvent.gamma, aRoll, "roll"); + }); +} + +function testAllPermutations() { + const angles = [-180, -90, 0, 90, 180]; + let promise = Promise.resolve(); + for (let i = 0; i < angles.length; i++) { + for (let j = 0; j < angles.length; j++) { + for (let k = 0; k < angles.length; k++) { + promise = + promise.then(doTest.bind(null, angles[i], angles[j], angles[k])); + } + } + } + return promise; +} + +startTestBase(function() { + let origValues; + + return Promise.resolve() + + // Retrieve original status. + .then(() => getEmulatorOrientationValues()) + .then(function(aValues) { + origValues = aValues; + is(typeof origValues, "object", "typeof origValues"); + is(origValues.length, 3, "origValues.length"); + }) + + // Test original status + .then(() => doTest.apply(null, origValues)) + + .then(testAllPermutations) + + // Restore original status. + .then(() => setEmulatorOrientationValues.apply(null, origValues)); +}); diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini new file mode 100644 index 000000000..e100e60a1 --- /dev/null +++ b/dom/events/test/mochitest.ini @@ -0,0 +1,186 @@ +[DEFAULT] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +support-files = + bug226361_iframe.xhtml + bug299673.js + bug322588-popup.html + bug426082.html + bug656379-1.html + bug418986-3.js + error_event_worker.js + empty.js + window_bug493251.html + window_bug659071.html + window_wheel_default_action.html + !/gfx/layers/apz/test/mochitest/apz_test_utils.js + +[test_accel_virtual_modifier.html] +[test_addEventListenerExtraArg.html] +[test_all_synthetic_events.html] +[test_bug226361.xhtml] +[test_bug238987.html] +[test_bug288392.html] +[test_bug299673-1.html] +[test_bug1037990.html] +[test_bug299673-2.html] +[test_bug322588.html] +[test_bug328885.html] +[test_bug336682_1.html] +support-files = test_bug336682.js +[test_bug367781.html] +[test_bug368835.html] +[test_bug379120.html] +[test_bug391568.xhtml] +[test_bug402089.html] +[test_bug405632.html] +[test_bug409604.html] +skip-if = toolkit == 'android' #TIMED_OUT +[test_bug412567.html] +[test_bug418986-3.html] +[test_bug422132.html] +[test_bug426082.html] +[test_bug427537.html] +[test_bug428988.html] +[test_bug432698.html] +[test_bug443985.html] +[test_bug447736.html] +[test_bug448602.html] +[test_bug450876.html] +[test_bug456273.html] +[test_bug457672.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug489671.html] +[test_bug493251.html] +[test_bug502818.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug508479.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM # drag event fails +[test_bug517851.html] +[test_bug534833.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug545268.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug547996-1.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug547996-2.xhtml] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug556493.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug563329.html] +skip-if = true # Disabled due to timeouts. +[test_bug574663.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug591815.html] +[test_bug593959.html] +[test_bug603008.html] +skip-if = toolkit == 'android' +[test_bug605242.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug607464.html] +skip-if = toolkit == 'android' || (e10s && os == 'win') || (e10s && os == "mac") #CRASH_DUMP, RANDOM, bug 1252273 +[test_bug613634.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug615597.html] +[test_bug624127.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug635465.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug641477.html] +[test_bug648573.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug650493.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug656379-1.html] +skip-if = toolkit == 'android' +[test_bug656379-2.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug656954.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug659071.html] +[test_bug659350.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug662678.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug667612.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug667919-1.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug684208.html] +[test_bug689564.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug698929.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_bug704423.html] +[test_bug741666.html] +[test_bug742376.html] +[test_bug812744.html] +[test_bug822898.html] +[test_bug855741.html] +[test_bug864040.html] +[test_bug924087.html] +[test_bug930374-content.html] +[test_bug944011.html] +[test_bug944847.html] +[test_bug946632.html] +skip-if = (e10s && os == "mac") # bug 1252273 +[test_bug967796.html] +skip-if = e10s # bug 1251659 +[test_bug985988.html] +[test_bug998809.html] +[test_bug1003432.html] +support-files = test_bug1003432.js +[test_bug1013412.html] +[test_bug1017086_disable.html] +support-files = bug1017086_inner.html +[test_bug1017086_enable.html] +support-files = bug1017086_inner.html +[test_bug1079236.html] +[test_bug1096146.html] +support-files = + bug1096146_embedded.html +[test_bug1145910.html] +[test_bug1150308.html] +[test_bug1248459.html] +[test_bug1264380.html] +run-if = (e10s && os != "win") # Bug 1270043, crash at windows platforms; Bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode. +[test_clickevent_on_input.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_continuous_wheel_events.html] +[test_dblclick_explicit_original_target.html] +[test_dom_activate_event.html] +[test_dom_before_after_keyboard_event.html] +support-files = + bug989198_embedded.html + bug989198_helper.js +[test_dom_before_after_keyboard_event_remote.html] +support-files = + bug989198_embedded.html + bug989198_helper.js +skip-if = e10s +[test_dom_keyboard_event.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_dom_mouse_event.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_dom_storage_event.html] +[test_dom_wheel_event.html] +[test_draggableprop.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_dragstart.html] +[test_error_events.html] +skip-if = toolkit == 'android' #TIMED_OUT +[test_eventctors.html] +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_eventhandler_scoping.html] +[test_eventTimeStamp.html] +[test_focus_disabled.html] +[test_legacy_event.html] +[test_messageEvent.html] +[test_messageEvent_init.html] +[test_moz_mouse_pixel_scroll_event.html] +[test_offsetxy.html] +[test_onerror_handler_args.html] +[test_passive_listeners.html] +[test_paste_image.html] +[test_wheel_default_action.html] +[test_bug687787.html] +[test_bug1298970.html] diff --git a/dom/events/test/pointerevents/bug1293174_implicit_pointer_capture_for_touch_1.html b/dom/events/test/pointerevents/bug1293174_implicit_pointer_capture_for_touch_1.html new file mode 100644 index 000000000..cd5c2b36e --- /dev/null +++ b/dom/events/test/pointerevents/bug1293174_implicit_pointer_capture_for_touch_1.html @@ -0,0 +1,64 @@ +<!doctype html> +<html> + <head> + <title>Pointer Events properties tests</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("implicit pointer capture for touch"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + let target0 = window.document.getElementById("target0"); + let target1 = window.document.getElementById("target1"); + + on_event(target0, "pointerdown", function (event) { + pointerdown_event = event; + detected_pointertypes[event.pointerType] = true; + assert_true(true, "target0 receives pointerdown"); + }); + + on_event(target0, "pointermove", function (event) { + assert_true(true, "target0 receives pointermove"); + assert_true(target0.hasPointerCapture(event.pointerId), "target0.hasPointerCapture should be true"); + }); + + on_event(target0, "gotpointercapture", function (event) { + assert_true(true, "target0 should receive gotpointercapture"); + }); + + on_event(target0, "lostpointercapture", function (event) { + assert_true(true, "target0 should receive lostpointercapture"); + }); + + on_event(target1, "pointermove", function (event) { + assert_true(false, "target1 should not receive pointermove"); + }); + + on_event(target0, "pointerup", function (event) { + assert_true(true, "target0 receives pointerup"); + test_pointerEvent.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Events tests</h1> + <div id="target0" style="width: 200px; height: 200px; background: green" touch-action:none></div> + <div id="target1" style="width: 200px; height: 200px; background: green" touch-action:none></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/bug1293174_implicit_pointer_capture_for_touch_2.html b/dom/events/test/pointerevents/bug1293174_implicit_pointer_capture_for_touch_2.html new file mode 100644 index 000000000..e84faacf6 --- /dev/null +++ b/dom/events/test/pointerevents/bug1293174_implicit_pointer_capture_for_touch_2.html @@ -0,0 +1,65 @@ +<!doctype html> +<html> + <head> + <title>Pointer Events properties tests</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("implicit pointer capture for touch"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + let target0 = window.document.getElementById("target0"); + let target1 = window.document.getElementById("target1"); + + on_event(target0, "pointerdown", function (event) { + pointerdown_event = event; + detected_pointertypes[event.pointerType] = true; + assert_true(true, "target0 receives pointerdown"); + }); + + on_event(target0, "pointermove", function (event) { + assert_true(true, "target0 receives pointermove"); + assert_false(target0.hasPointerCapture(event.pointerId), "target0.hasPointerCapture should be false"); + }); + + on_event(target0, "gotpointercapture", function (event) { + assert_unreached("target0 should not receive gotpointercapture"); + }); + + on_event(target0, "lostpointercapture", function (event) { + assert_unreached("target0 should not receive lostpointercapture"); + }); + + on_event(target1, "pointermove", function (event) { + assert_true(true, "target1 receives pointermove"); + assert_false(target1.hasPointerCapture(), "target1.hasPointerCapture should be false"); + }); + + on_event(target0, "pointerup", function (event) { + assert_true(true, "target0 receives pointerup"); + test_pointerEvent.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Events tests</h1> + <div id="target0" style="width: 200px; height: 200px; background: green" touch-action:none></div> + <div id="target1" style="width: 200px; height: 200px; background: green" touch-action:none></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/mochitest.ini b/dom/events/test/pointerevents/mochitest.ini new file mode 100644 index 000000000..58eae12fe --- /dev/null +++ b/dom/events/test/pointerevents/mochitest.ini @@ -0,0 +1,152 @@ +[DEFAULT] +skip-if = os == 'android' # Bug 1312791 +support-files = + mochitest_support_external.js + mochitest_support_internal.js + pointerevent_styles.css + pointerevent_support.js + +[test_pointerevent_attributes_mouse-manual.html] + support-files = pointerevent_attributes_mouse-manual.html +[test_pointerevent_capture_mouse-manual.html] + support-files = pointerevent_capture_mouse-manual.html +[test_pointerevent_capture_suppressing_mouse-manual.html] + support-files = pointerevent_capture_suppressing_mouse-manual.html +[test_pointerevent_change-touch-action-onpointerdown_touch-manual.html] + support-files = pointerevent_change-touch-action-onpointerdown_touch-manual.html + disabled = disabled +[test_pointerevent_constructor.html] + support-files = pointerevent_constructor.html +[test_pointerevent_element_haspointercapture-manual.html] + support-files = pointerevent_element_haspointercapture-manual.html +[test_pointerevent_element_haspointercapture_release_pending_capture-manual.html] + support-files = pointerevent_element_haspointercapture_release_pending_capture-manual.html +[test_pointerevent_gotpointercapture_before_first_pointerevent-manual.html] + support-files = pointerevent_gotpointercapture_before_first_pointerevent-manual.html +[test_pointerevent_lostpointercapture_for_disconnected_node-manual.html] + support-files = pointerevent_lostpointercapture_for_disconnected_node-manual.html +[test_pointerevent_lostpointercapture_is_first-manual.html] + support-files = pointerevent_lostpointercapture_is_first-manual.html +[test_pointerevent_multiple_primary_pointers_boundary_events-manual.html] + support-files = pointerevent_multiple_primary_pointers_boundary_events-manual.html + disabled = should be investigated +[test_pointerevent_pointercancel_touch-manual.html] + support-files = pointerevent_pointercancel_touch-manual.html +[test_pointerevent_pointerdown-manual.html] + support-files = pointerevent_pointerdown-manual.html +[test_pointerevent_pointerenter_does_not_bubble-manual.html] + support-files = pointerevent_pointerenter_does_not_bubble-manual.html +[test_pointerevent_pointerenter_nohover-manual.html] + support-files = pointerevent_pointerenter_nohover-manual.html +[test_pointerevent_pointerId_scope-manual.html] + support-files = + test_pointerevent_pointerId_scope-manual.html + ./resources/pointerevent_pointerId_scope-iframe.html + disabled = should be investigated +[test_pointerevent_pointerenter-manual.html] + support-files = pointerevent_pointerenter-manual.html +[test_pointerevent_pointerleave_after_pointercancel_touch-manual.html] + support-files = pointerevent_pointerleave_after_pointercancel_touch-manual.html +[test_pointerevent_pointerleave_after_pointerup_nohover-manual.html] + support-files = pointerevent_pointerleave_after_pointerup_nohover-manual.html +[test_pointerevent_pointerleave_descendant_over-manual.html] + support-files = pointerevent_pointerleave_descendant_over-manual.html +[test_pointerevent_pointerleave_descendants-manual.html] + support-files = pointerevent_pointerleave_descendants-manual.html +[test_pointerevent_pointerleave_does_not_bubble-manual.html] + support-files = pointerevent_pointerleave_does_not_bubble-manual.html +[test_pointerevent_pointerleave_mouse-manual.html] + support-files = pointerevent_pointerleave_mouse-manual.html +[test_pointerevent_pointerleave_pen-manual.html] + support-files = pointerevent_pointerleave_pen-manual.html +[test_pointerevent_pointerleave_touch-manual.html] + support-files = pointerevent_pointerleave_touch-manual.html +[test_pointerevent_pointermove-manual.html] + support-files = pointerevent_pointermove-manual.html +[test_pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html] + support-files = pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html +[test_pointerevent_pointermove-on-chorded-mouse-button.html] + support-files = pointerevent_pointermove-on-chorded-mouse-button.html +[test_pointerevent_pointermove_pointertype-manual.html] + support-files = pointerevent_pointermove_pointertype-manual.html +[test_pointerevent_pointerout-manual.html] + support-files = pointerevent_pointerout-manual.html +[test_pointerevent_pointerout_after_pointercancel_touch-manual.html] + support-files = pointerevent_pointerout_after_pointercancel_touch-manual.html +[test_pointerevent_pointerout_after_pointerup_nohover-manual.html] + support-files = pointerevent_pointerout_after_pointerup_nohover-manual.html +[test_pointerevent_pointerout_pen-manual.html] + support-files = pointerevent_pointerout_pen-manual.html +[test_pointerevent_pointerout_received_once-manual.html] + support-files = pointerevent_pointerout_received_once-manual.html +[test_pointerevent_pointerover-manual.html] + support-files = pointerevent_pointerover-manual.html +[test_pointerevent_pointertype_mouse-manual.html] + support-files = pointerevent_pointertype_mouse-manual.html +[test_pointerevent_pointertype_pen-manual.html] + support-files = pointerevent_pointertype_pen-manual.html +[test_pointerevent_pointertype_touch-manual.html] + support-files = pointerevent_pointertype_touch-manual.html +[test_pointerevent_pointerup-manual.html] + support-files = pointerevent_pointerup-manual.html +[test_pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html] + support-files = pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html +[test_pointerevent_pointerup_pointertype-manual.html] + support-files = pointerevent_pointerup_pointertype-manual.html +[test_pointerevent_releasepointercapture_events_to_original_target-manual.html] + support-files = pointerevent_releasepointercapture_events_to_original_target-manual.html +[test_pointerevent_releasepointercapture_invalid_pointerid-manual.html] + support-files = pointerevent_releasepointercapture_invalid_pointerid-manual.html +[test_pointerevent_releasepointercapture_onpointercancel_touch-manual.html] + support-files = pointerevent_releasepointercapture_onpointercancel_touch-manual.html +[test_pointerevent_releasepointercapture_onpointerup_mouse-manual.html] + support-files = pointerevent_releasepointercapture_onpointerup_mouse-manual.html +[test_pointerevent_releasepointercapture_release_right_after_capture-manual.html] + support-files = pointerevent_releasepointercapture_release_right_after_capture-manual.html +[test_pointerevent_setpointercapture_disconnected-manual.html] + support-files = pointerevent_setpointercapture_disconnected-manual.html +[test_pointerevent_setpointercapture_inactive_button_mouse-manual.html] + support-files = pointerevent_setpointercapture_inactive_button_mouse-manual.html + disabled = should be investigated +[test_pointerevent_setpointercapture_invalid_pointerid-manual.html] + support-files = pointerevent_setpointercapture_invalid_pointerid-manual.html +[test_pointerevent_setpointercapture_override_pending_capture_element-manual.html] + support-files = pointerevent_setpointercapture_override_pending_capture_element-manual.html +[test_pointerevent_setpointercapture_relatedtarget-manual.html] + support-files = pointerevent_setpointercapture_relatedtarget-manual.html +[test_pointerevent_setpointercapture_to_same_element_twice-manual.html] + support-files = pointerevent_setpointercapture_to_same_element_twice-manual.html +[test_pointerevent_suppress_compat_events_on_click.html] + support-files = pointerevent_suppress_compat_events_on_click.html + disabled = should be investigated +[test_pointerevent_suppress_compat_events_on_drag_mouse.html] + support-files = pointerevent_suppress_compat_events_on_drag_mouse.html + disabled = should be investigated +[test_touch_action.html] + support-files = + ../../../../gfx/layers/apz/test/mochitest/apz_test_utils.js + ../../../../gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js + touch_action_helpers.js + pointerevent_touch-action-auto-css_touch-manual.html + pointerevent_touch-action-button-test_touch-manual.html + pointerevent_touch-action-inherit_child-auto-child-none_touch-manual.html + pointerevent_touch-action-inherit_child-none_touch-manual.html + pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual.html + pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual.html + pointerevent_touch-action-inherit_highest-parent-none_touch-manual.html + pointerevent_touch-action-inherit_parent-none_touch-manual.html + pointerevent_touch-action-none-css_touch-manual.html + pointerevent_touch-action-pan-x-css_touch-manual.html + pointerevent_touch-action-pan-x-pan-y-pan-y_touch-manual.html + pointerevent_touch-action-pan-x-pan-y_touch-manual.html + pointerevent_touch-action-pan-y-css_touch-manual.html + pointerevent_touch-action-span-test_touch-manual.html + pointerevent_touch-action-svg-test_touch-manual.html + pointerevent_touch-action-table-test_touch-manual.html +[test_bug1285128.html] +[test_bug1293174_implicit_pointer_capture_for_touch_1.html] + support-files = bug1293174_implicit_pointer_capture_for_touch_1.html +[test_bug1293174_implicit_pointer_capture_for_touch_2.html] + support-files = bug1293174_implicit_pointer_capture_for_touch_2.html +[test_empty_file.html] + disabled = disabled # Bug 1150091 - Issue with support-files diff --git a/dom/events/test/pointerevents/mochitest_support_external.js b/dom/events/test/pointerevents/mochitest_support_external.js new file mode 100644 index 000000000..01c340728 --- /dev/null +++ b/dom/events/test/pointerevents/mochitest_support_external.js @@ -0,0 +1,123 @@ +// This file supports translating W3C tests +// to tests on auto MochiTest system with minimum changes. +// Author: Maksim Lebedev <alessarik@gmail.com> + +// Function allows to prepare our tests after load document +addEventListener("load", function(event) { + console.log("OnLoad external document"); + prepareTest(); +}, false); + +// Function allows to initialize prerequisites before testing +function prepareTest() { + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestCompleteLog(); + turnOnPointerEvents(startTest); +} + +function setImplicitPointerCapture(capture, callback) { + console.log("SET dom.w3c_pointer_events.implicit_capture as " + capture); + SpecialPowers.pushPrefEnv({ + "set": [ + ["dom.w3c_pointer_events.implicit_capture", capture] + ] + }, callback); +} + +function turnOnPointerEvents(callback) { + console.log("SET dom.w3c_pointer_events.enabled as TRUE"); + console.log("SET layout.css.touch_action.enabled as TRUE"); + SpecialPowers.pushPrefEnv({ + "set": [ + ["dom.w3c_pointer_events.enabled", true], + ["layout.css.touch_action.enabled", true] + ] + }, callback); +} + +// Helper function to send MouseEvent with different parameters +function sendMouseEvent(int_win, elemId, mouseEventType, params) { + var elem = int_win.document.getElementById(elemId); + if(!!elem) { + var rect = elem.getBoundingClientRect(); + var eventObj = {type: mouseEventType}; + if(params && "button" in params) + eventObj.button = params.button; + if(params && "inputSource" in params) + eventObj.inputSource = params.inputSource; + if(params && "buttons" in params) + eventObj.buttons = params.buttons; + + // Default to the center of the target element but we can still send to a + // position outside of the target element. + var offsetX = params && "offsetX" in params ? params.offsetX : rect.width / 2; + var offsetY = params && "offsetY" in params ? params.offsetY : rect.height / 2; + + console.log(elemId, eventObj); + synthesizeMouse(elem, offsetX, offsetY, eventObj, int_win); + + } else { + is(!!elem, true, "Document should have element with id: " + elemId); + } +} + +// Helper function to send TouchEvent with different parameters +function sendTouchEvent(int_win, elemId, touchEventType, params) { + var elem = int_win.document.getElementById(elemId); + if(!!elem) { + var rect = elem.getBoundingClientRect(); + var eventObj = {type: touchEventType}; + + // Default to the center of the target element but we can still send to a + // position outside of the target element. + var offsetX = params && "offsetX" in params ? params.offsetX : rect.width / 2; + var offsetY = params && "offsetY" in params ? params.offsetY : rect.height / 2; + + console.log(elemId, eventObj); + synthesizeTouch(elem, offsetX, offsetY, eventObj, int_win); + } else { + is(!!elem, true, "Document should have element with id: " + elemId); + } +} + +// Helper function to run Point Event test in a new tab. +function runTestInNewWindow(aFile) { + var w = window.open('', "_blank"); + w.is = function(a, b, msg) { return is(a, b, aFile + " | " + msg); }; + w.ok = function(cond, name, diag) { return ok(cond, aFile + " | " + name, diag); }; + w.location = location.href.substring(0, location.href.lastIndexOf('/') + 1) + aFile; + + w.testContext = { + result_callback: (aTestObj) => { + if(aTestObj["status"] != aTestObj["PASS"]) { + console.log(aTestObj["status"] + " = " + aTestObj["PASS"] + ". " + aTestObj["name"]); + } + is(aTestObj["status"], aTestObj["PASS"], aTestObj["name"]); + }, + + completion_callback: () => { + if (!!w.testContext.executionPromise) { + // We need to wait tests done and execute finished then we can close the window + w.testContext.executionPromise.then(() => { + w.close(); + SimpleTest.finish(); + }); + } else { + // execute may synchronous trigger tests done. In that case executionPromise + // is not yet assigned + w.close(); + SimpleTest.finish(); + } + }, + + execute: (aWindow) => { + turnOnPointerEvents(() => { + w.testContext.executionPromise = new Promise((aResolve, aReject) => { + executeTest(aWindow); + aResolve(); + }); + }); + } + }; + return w; +} diff --git a/dom/events/test/pointerevents/mochitest_support_internal.js b/dom/events/test/pointerevents/mochitest_support_internal.js new file mode 100644 index 000000000..f8161362c --- /dev/null +++ b/dom/events/test/pointerevents/mochitest_support_internal.js @@ -0,0 +1,31 @@ +// This file supports translating W3C tests +// to tests on auto MochiTest system with minimum changes. +// Author: Maksim Lebedev <alessarik@gmail.com> + +// Function allows to prepare our tests after load document +addEventListener("load", function(event) { + console.log("OnLoad internal document"); + addListeners(document.getElementById("target0")); + addListeners(document.getElementById("target1")); + preExecute(); +}, false); + +// Function allows to initialize prerequisites before testing +// and adds some callbacks to support mochitest system. +function preExecute() { + add_result_callback(testContext.result_callback); + add_completion_callback(testContext.completion_callback); + testContext.execute(window); +} + +function addListeners(elem) { + if(!elem) + return; + var All_Events = ["pointerdown","pointerup","pointercancel","pointermove","pointerover","pointerout", + "pointerenter","pointerleave","gotpointercapture","lostpointercapture"]; + All_Events.forEach(function(name) { + elem.addEventListener(name, function(event) { + console.log('('+event.type+')-('+event.pointerType+')'); + }, false); + }); +} diff --git a/dom/events/test/pointerevents/pointerevent_attributes_mouse-manual.html b/dom/events/test/pointerevents/pointerevent_attributes_mouse-manual.html new file mode 100644 index 000000000..a54e79911 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_attributes_mouse-manual.html @@ -0,0 +1,113 @@ +<!doctype html> +<html> + <head> + <title>Pointer Events properties tests</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script> + var detected_pointertypes = {}; + var detected_eventTypes = {}; + var test_pointerEvent = async_test("pointerevent attributes"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var square1 = document.getElementById("square1"); + var rectSquare1 = square1.getBoundingClientRect(); + var pointerover_event; + + var eventList = ['pointerenter', 'pointerover', 'pointermove', 'pointerdown', 'pointerup', 'pointerout', 'pointerleave']; + eventList.forEach(function(eventName) { + on_event(square1, eventName, function (event) { + if (detected_eventTypes[event.type]) + return; + detected_pointertypes[event.pointerType] = true; + test(function () { + assert_equals(event.pointerType, 'mouse', 'pointerType should be mouse'); + }, event.type + ".pointerType attribute is correct."); + + // Test button and buttons + if (event.type == 'pointerdown') { + test(function() { + assert_true(event.button == 0, "If left mouse button is pressed button attribute is 0") + }, event.type + "'s button attribute is 0 when left mouse button is pressed."); + test(function() { + assert_true(event.buttons == 1, "If left mouse button is pressed buttons attribute is 1") + }, event.type + "'s buttons attribute is 1 when left mouse button is pressed."); + } else if (event.type == 'pointerup') { + test(function() { + assert_true(event.button == 0, "If left mouse button is just released button attribute is 0") + }, event.type + "'s button attribute is 0 when left mouse button is just released."); + test(function() { + assert_true(event.buttons == 0, "If left mouse button is just released buttons attribute is 0") + }, event.type + "'s buttons attribute is 0 when left mouse button is just released."); + } else { + test(function() { + assert_true(event.button == -1, "If mouse buttons are released button attribute is -1") + }, event.type + "'s button is -1 when mouse buttons are released."); + test(function() { + assert_true(event.buttons == 0, "If mouse buttons are released buttons attribute is 0") + }, event.type + "'s buttons is 0 when mouse buttons are released."); + } + + // Test clientX and clientY + if (event.type != 'pointerout' && event.type != 'pointerleave' ) { + test(function () { + assert_true(event.clientX >= rectSquare1.left && event.clientX < rectSquare1.right, "ClientX should be in the boundaries of the black box"); + }, event.type + ".clientX attribute is correct."); + test(function () { + assert_true(event.clientY >= rectSquare1.top && event.clientY < rectSquare1.bottom, "ClientY should be in the boundaries of the black box"); + }, event.type + ".clientY attribute is correct."); + } else { + test(function () { + assert_true(event.clientX < rectSquare1.left || event.clientX > rectSquare1.right - 1 || event.clientY < rectSquare1.top || event.clientY > rectSquare1.bottom - 1, "ClientX/Y should be out of the boundaries of the black box"); + }, event.type + "'s ClientX and ClientY attributes are correct."); + } + + // Test isPrimary + test(function () { + assert_equals(event.isPrimary, true, "isPrimary should be true"); + }, event.type + ".isPrimary attribute is correct."); + + // Test width and height + test(function () { + assert_equals(event.width, 1, "width of mouse should be 1"); + }, event.type + ".width attribute is correct."); + test(function () { + assert_equals(event.height, 1, "height of mouse should be 1"); + }, event.type + ".height attribute is correct."); + + check_PointerEvent(event); + detected_eventTypes[event.type] = true; + if (Object.keys(detected_eventTypes).length == eventList.length) + test_pointerEvent.done(); + }); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Events pointerdown tests</h1> + <!-- + <h4> + Test Description: This test checks the properties of mouse pointer events. Move your mouse over the black square and click on it. Then move it off the black square. + </h4> + --> + Test passes if the proper behavior of the events is observed. + <div id="square1" class="square"></div> + <div class="spacer"></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html> + diff --git a/dom/events/test/pointerevents/pointerevent_capture_mouse-manual.html b/dom/events/test/pointerevents/pointerevent_capture_mouse-manual.html new file mode 100644 index 000000000..63e8af777 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_capture_mouse-manual.html @@ -0,0 +1,135 @@ +<!doctype html> +<html> + <head> + <title>Set/Release capture</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body> + <!-- + <h1>Pointer Events capture test</h1> + <h4> + Test Description: This test checks if setCapture/releaseCapture functions works properly. Complete the following actions: + <ol> + <li> Move your mouse over the black rectangle. pointermove event should be logged in the black rectangle</li> + <li> Move your mouse over the purple rectangle. pointerover event should be logged in the purple rectangle</li> + <li> Press and hold left mouse button over "Set Capture" button. "gotpointercapture" should be logged in the black rectangle</li> + <li> Move your mouse anywhere. pointermove should be logged in the black rectangle</li> + <li> Move your mouse over the purple rectangle. Nothig should happen</li> + <li> Move your mouse over the black rectangle. pointermove should be logged in the black rectangle</li> + <li> Release left mouse button. "lostpointercapture" should be logged in the black rectangle</li> + </ol> + </h4> + Test passes if the proper behaviour of the events is observed. + --> + <div id="target0"></div> + <br> + <div id="target1"></div> + <br> + <input type="button" id="btnCapture" value="Set Capture"> + <script type='text/javascript'> + var isPointerCapture = false; + var pointermoveNoCaptureGot0 = false; + var pointermoveCaptureGot0 = false; + var pointermoveNoCaptureGot1 = false; + var ownEventForTheCapturedTargetGot = false; + var count=0; + + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var target0 = document.getElementById('target0'); + var target1 = document.getElementById('target1'); + var captureButton = document.getElementById('btnCapture'); + + var test_gotpointercapture = async_test("gotpointercapture event received"); + var test_lostpointercapture = async_test("lostpointercapture event received"); + + window.onload = function() { + on_event(captureButton, 'pointerdown', function(e) { + if(isPointerCapture == false) { + isPointerCapture = true; + sPointerCapture(e); + } + }); + + on_event(target0, 'gotpointercapture', function(e) { + test_gotpointercapture.done(); + log("gotpointercapture", target0); + }); + + on_event(target0, 'lostpointercapture', function(e) { + test_lostpointercapture.done(); + isPointerCapture = false; + log("lostpointercapture", target0); + }); + + run(); + } + + function run() { + var test_pointermove0 = async_test("pointerover event for black rectangle received") + var test_pointermove1 = async_test("pointerover event for purple rectangle received") + + on_event(target0, "pointermove", function (event) { + detected_pointertypes[ event.pointerType ] = true; + if(!pointermoveNoCaptureGot0) { + test_pointermove0.done(); + log("pointermove", document.getElementById('target0')); + pointermoveNoCaptureGot0 = true; + } + if(isPointerCapture) { + if(!pointermoveCaptureGot0) { + test(function() { + assert_true(event.relatedTarget==null, "relatedTarget is null when the capture is set") + }, "relatedTarget is null when the capture is set. relatedTarget is " + event.relatedTarget); + test(function() { + assert_true((event.clientX < target0.getBoundingClientRect().left)|| + (event.clientX > target0.getBoundingClientRect().right)|| + (event.clientY < target0.getBoundingClientRect().top)|| + (event.clientY > target0.getBoundingClientRect().bottom), + "pointermove received for captured element while out of it") + }, "pointermove received for captured element while out of it"); + log("pointermove", document.getElementById('target0')); + pointermoveCaptureGot0 = true; + } + if((event.clientX > target0.getBoundingClientRect().left)&& + (event.clientX < target0.getBoundingClientRect().right)&& + (event.clientY > target0.getBoundingClientRect().top)&& + (event.clientY < target0.getBoundingClientRect().bottom)&& + !ownEventForTheCapturedTargetGot) { + test(function() { + assert_true(true, "pointermove received for captured element while inside of it"); + }, "pointermove received for captured element while inside of it"); + log("pointermove", document.getElementById('target0')); + ownEventForTheCapturedTargetGot = true; + } + } + }); + + on_event(target1, "pointermove", function (event) { + if(isPointerCapture == true) { + test(function() { + assert_unreached("pointermove shouldn't trigger for this target when capture is enabled"); + }, "pointermove shouldn't trigger for the purple rectangle while the black rectangle has capture"); + } + if(!pointermoveNoCaptureGot1) { + test_pointermove1.done(); + log("pointermove", document.getElementById('target1')); + pointermoveNoCaptureGot1 = true; + } + }); + } + </script> + <h1>Pointer Events Capture Test</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_capture_suppressing_mouse-manual.html b/dom/events/test/pointerevents/pointerevent_capture_suppressing_mouse-manual.html new file mode 100644 index 000000000..6b891b846 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_capture_suppressing_mouse-manual.html @@ -0,0 +1,186 @@ +<!doctype html> +<html> + <head> + <title>Set/Release capture</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body> + <!-- + <h1>Pointer Events capture test</h1> + <h4> + Test Description: This test checks if setCapture/releaseCapture functions works properly. Complete the following actions: + <ol> + <li> Put your mouse over the black rectangle. pointerover and pointerenter should be logged inside of it.</li> + <li> Move your mouse out of the black rectangle. pointerout and pointerleave should be logged inside of it</li> + <li> Put your mouse over the purple rectangle. pointerover and pointerenter should be logged inside of it.</li> + <li> Move your mouse out of the purple rectangle. pointerout and pointerleave should be logged inside of it</li> + <li> Press and hold left mouse button over "Set Capture" button. "gotpointercapture" should be logged in the black rectangle</li> + <li> Put your mouse over the purple rectangle and then move it out. Nothing should happen</li> + <li> Put your mouse over the black rectangle. pointerover and pointerenter should be logged inside of it.</li> + <li> Move your mouse out of the black rectangle. pointerout and pointerleave should be logged inside of it</li> + <li> Release left mouse button. "lostpointercapture" should be logged in the black rectangle</li> + </ol> + </h4> + Test passes if the proper behaviour of the events is observed. + --> + <div id="target0"></div> + <br> + <div id="target1"></div> + <br> + <input type="button" id="btnCapture" value="Set Capture"> + <script type='text/javascript'> + var isPointerCapture = false; + var isRelatedTargetValueTested = false; + var isTargetAuthenticityTested = false; + var count = 0; + + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var target0 = document.getElementById('target0'); + var target1 = document.getElementById('target1'); + var captureButton = document.getElementById('btnCapture'); + + var test_gotpointercapture = async_test("gotpointercapture event received"); + var test_lostpointercapture = async_test("lostpointercapture event received"); + + var test_pointerover_no_capture = async_test("pointerover event without capture received"); + var test_pointerover_capture = async_test("pointerover event with capture received"); + + var test_pointerout_no_capture = async_test("pointerout event without capture received"); + var test_pointerout_capture = async_test("pointerout event with capture received"); + + var test_pointerenter_no_capture = async_test("pointerenter event without capture received"); + var test_pointerenter_capture = async_test("pointerenter event with capture received"); + + var test_pointerleave_no_capture = async_test("pointerleave event without capture received"); + var test_pointerleave_capture = async_test("pointerleave event with capture received"); + + window.onload = function() { + on_event(captureButton, 'pointerdown', function(e) { + if(isPointerCapture == false) { + sPointerCapture(e); + isPointerCapture = true; + } + }); + + on_event(target0, 'gotpointercapture', function(e) { + test_gotpointercapture.done(); + log("gotpointercapture", target0); + }); + + on_event(target0, 'lostpointercapture', function(e) { + isPointerCapture = false; + test_lostpointercapture.done(); + log("lostpointercapture", target0); + }); + + run(); + } + + function run() { + on_event(target0, "pointerover", function (event) { + detected_pointertypes[ event.pointerType ] = true; + log("pointerover", target0); + if(isPointerCapture) { + test_pointerover_capture.done(); + if (!isRelatedTargetValueTested) { + test(function() { + assert_true(event.relatedTarget==null, "relatedTarget is null when the capture is set") + }, "relatedTarget is null when the capture is set. relatedTarget is " + event.relatedTarget); + isRelatedTargetValueTested = true; + } + var hitTest = document.elementFromPoint(event.clientX, event.clientY); + if(event.target !== hitTest && !isTargetAuthenticityTested) { + test(function () { + assert_unreached("pointerover for this target shouldn't trigger events on capture target"); + }, "pointerover should only trigger over the black rectangle"); + isTargetAuthenticityTested = true; + } + } + else { + test_pointerover_no_capture.done(); + } + }); + + on_event(target0, "pointerout", function (event) { + log("pointerout", target0); + if(isPointerCapture) { + test_pointerout_capture.done(); + } + else { + test_pointerout_no_capture.done(); + } + }); + + on_event(target0, "pointerenter", function (event) { + log("pointerenter", target0); + if(isPointerCapture) { + test_pointerenter_capture.done(); + } + else { + test_pointerenter_no_capture.done(); + } + }); + + on_event(target0, "pointerleave", function (event) { + log("pointerleave", target0); + if(isPointerCapture) { + test_pointerleave_capture.done(); + } + else { + test_pointerleave_no_capture.done(); + } + }); + + // fail if capture is set but event is received for the non-captured target + on_event(target1, "pointerover", function (event) { + log("pointerover", target1); + if(isPointerCapture == true) { + test(function() { + assert_unreached("pointerover shouldn't trigger for this target when capture is enabled"); + }, "pointerover shouldn't trigger for the purple rectangle while the black rectangle has capture"); + } + }); + + on_event(target1, "pointerout", function (event) { + log("pointerout", target1); + if(isPointerCapture == true) { + test(function() { + assert_unreached("pointerout shouldn't trigger for this target when capture is enabled"); + }, "pointerout shouldn't trigger for the purple rectangle while the black rectangle has capture"); + } + }); + + on_event(target1, "pointerenter", function (event) { + log("pointerenter", target1); + if(isPointerCapture == true) { + test(function() { + assert_unreached("pointerenter shouldn't trigger for this target when capture is enabled"); + }, "pointerenter shouldn't trigger for the purple rectangle while the black rectangle has capture"); + } + }); + + on_event(target1, "pointerleave", function (event) { + log("pointerleave", target1); + if(isPointerCapture == true) { + test(function() { + assert_unreached("pointerleave shouldn't trigger for this target when capture is enabled"); + }, "pointerleave shouldn't trigger for the purple rectangle while the black rectangle has capture"); + } + }); + } + </script> + <h1>Pointer Events Capture Test</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_change-touch-action-onpointerdown_touch-manual.html b/dom/events/test/pointerevents/pointerevent_change-touch-action-onpointerdown_touch-manual.html new file mode 100644 index 000000000..b5f522f8b --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_change-touch-action-onpointerdown_touch-manual.html @@ -0,0 +1,138 @@ +<!doctype html> +<html> + <head> + <title>Change touch-action on pointerdown</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + <style> + #target0 { + background: black; + width: 700px; + height: 430px; + color: white; + overflow-y: auto; + overflow-x: auto; + white-space: nowrap; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <!-- + <h4>Test Description: Press and hold your touch. Try to scroll text in any direction. + Then release your touch and try to scroll again. Expected: no panning. + </h4> + <p>Note: this test is for touch-devices only</p> + --> + <div id="target0" style="touch-action: auto;"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <script type='text/javascript'> + var detected_pointertypes = {}; + + var styleIsChanged = false; + var scrollIsReceived = false; + var firstTouchCompleted = false; + var countToPass = 50; + var xScr0, yScr0, xScr1, yScr1; + + setup({ explicit_done: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, 'scroll', function(event) { + if(!scrollIsReceived && firstTouchCompleted) { + test(function() { + failOnScroll(); + }, "scroll was received while shouldn't"); + scrollIsReceived = true; + } + done(); + }); + + on_event(target0, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + if(!styleIsChanged) { + var before = document.getElementById('target0').style.touchAction; + + document.getElementById('target0').style.touchAction = 'none'; + + var after = document.getElementById('target0').style.touchAction; + + test(function() { + assert_true(before != after, "touch-action was changed"); + }, "touch-action was changed"); + + styleIsChanged = true; + } + }); + + on_event(target0, 'pointerup', function(event) { + firstTouchCompleted = true; + }); + } + </script> + <h1>touch-action: auto to none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_constructor.html b/dom/events/test/pointerevents/pointerevent_constructor.html new file mode 100644 index 000000000..9ef04cccb --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_constructor.html @@ -0,0 +1,101 @@ +<!doctype html> +<html> + <head> + <title>PointerEvent: Constructor test</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>PointerEvent: Dispatch custom event</h1> + <h4>Test Description: This test checks if PointerEvent constructor works properly using synthetic pointerover and pointerout events. For valid results, this test must be run without generating real (trusted) pointerover or pointerout events on the black rectangle below.</h4> + <div id="target0"></div> + <script> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + async_test(function() { + var target0 = document.getElementById("target0"); + // set values for non-default constructor + var testBubbles = true; + var testCancelable = true; + var testPointerId = 42; + var testPointerType = 'pen'; + var testClientX = 300; + var testClientY = 500; + var testWidth = 3; + var testHeight = 5; + var testTiltX = -45; + var testTiltY = 30; + var testPressure = 0.4; + var testIsPrimary = true; + + on_event(target0, "pointerover", this.step_func(function(event) { + detected_pointertypes[ event.pointerType ] = true; + generate_tests(assert_equals, [ + ["custom bubbles", event.bubbles, testBubbles], + ["custom cancelable", event.cancelable, testCancelable], + ["custom pointerId", event.pointerId, testPointerId], + ["custom pointerType", event.pointerType, testPointerType], + ["custom width", event.width, testWidth], + ["custom height", event.height, testHeight], + ["custom clientX", event.clientX, testClientX], + ["custom clientY", event.clientY, testClientY], + ["custom tiltX", event.tiltX, testTiltX], + ["custom tiltY", event.tiltY, testTiltY], + ["custom isPrimary", event.isPrimary, testIsPrimary] + ]); + test(function() { + assert_approx_equals(event.pressure, testPressure, 0.00000001, "custom pressure: "); + }, "custom pressure: "); + })); + + on_event(target0, "pointerout", this.step_func(function(event) { + generate_tests(assert_equals, [ + ["default pointerId", event.pointerId, 0], + ["default pointerType", event.pointerType, ""], + ["default width", event.width, 1], + ["default height", event.height, 1], + ["default tiltX", event.tiltX, 0], + ["default tiltY", event.tiltY, 0], + ["default pressure", event.pressure, 0], + ["default isPrimary", event.isPrimary, false] + ]); + })); + + on_event(window, "load", this.step_func_done(function() { + assert_not_equals(window.PointerEvent, undefined); + + var pointerEventCustom = new PointerEvent("pointerover", + {bubbles: testBubbles, + cancelable: testCancelable, + pointerId: testPointerId, + pointerType: testPointerType, + width: testWidth, + height: testHeight, + clientX: testClientX, + clientY: testClientY, + tiltX: testTiltX, + tiltY: testTiltY, + pressure: testPressure, + isPrimary: testIsPrimary + }); + // A PointerEvent created with a PointerEvent constructor must have all its attributes set to the corresponding values provided to the constructor. + // For attributes where values are not provided to the constructor, the corresponding default values must be used. + // TA: 12.1 + target0.dispatchEvent(pointerEventCustom); + var pointerEventDefault = new PointerEvent("pointerout"); + target0.dispatchEvent(pointerEventDefault); + }, "PointerEvent constructor")); + }) + </script> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_element_haspointercapture-manual.html b/dom/events/test/pointerevents/pointerevent_element_haspointercapture-manual.html new file mode 100644 index 000000000..1826467fe --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_element_haspointercapture-manual.html @@ -0,0 +1,123 @@ +<!doctype html> +<html> + <head> + <title>Element.hasPointerCapture test</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + var test_pointerEvent = async_test("hasPointerCapture"); + var listening_events = [ + "pointerover", + "pointerenter", + "pointerout", + "pointerleave", + "pointermove", + "gotpointercapture" + ]; + var set_capture_to_target0 = false; + + function run() { + var target0 = document.getElementById("target0"); + var target1 = document.getElementById("target1"); + + on_event(target0, "pointerdown", function (e) { + detected_pointertypes[e.pointerType] = true; + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), false, + "before target0.setPointerCapture, target0.hasPointerCapture should be false"); + }); + target1.setPointerCapture(e.pointerId); + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), false, + "after target1.setPointerCapture, target0.hasPointerCapture should be false"); + assert_equals(target1.hasPointerCapture(e.pointerId), true, + "after target1.setPointerCapture, target1.hasPointerCapture should be true"); + }); + target0.setPointerCapture(e.pointerId); + set_capture_to_target0 = true; + // hasPointerCapture will return true immediately after a call to setPointerCapture + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), true, + "after target0.setPointerCapture, target0.hasPointerCapture should be true"); + }); + // hasPointerCapture will return false immediately after a call to releasePointerCapture + target0.releasePointerCapture(e.pointerId); + set_capture_to_target0 = false; + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), false, + "after target0.releasePointerCapture, target0.hasPointerCapture should be false"); + assert_equals(target1.hasPointerCapture(e.pointerId), false, + "after target0.releasePointerCapture, target1.hasPointerCapture should be false"); + }); + target0.setPointerCapture(e.pointerId); + set_capture_to_target0 = true; + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), true, + "after target0.setPointerCapture, target0.hasPointerCapture should be true"); + }); + }); + + for (var i = 0; i < listening_events.length; i++) { + on_event(target0, listening_events[i], function (e) { + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), set_capture_to_target0, + "Received " + e.type + " target0.hasPointerCapture should be " + set_capture_to_target0); + }); + }); + } + + on_event(target0, "pointerup", function (e) { + // Immediately after firing the pointerup or pointercancel events, a user agent must run the steps + // as if the releasePointerCapture() method has been called + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), true, + "pointerup target0.hasPointerCapture should be true"); + }); + set_capture_to_target0 = false; + }); + + on_event(target0, "lostpointercapture", function (e) { + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), false, + "pointerup target0.hasPointerCapture should be false"); + }); + }); + + on_event(target1, "pointerup", function (e) { + test_pointerEvent.step(function () { + assert_equals(target1.hasPointerCapture(e.pointerId), false, + "pointerup target1.hasPointerCapture should be false"); + }); + test_pointerEvent.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Element.hasPointerCapture test</h1> + <!-- + <h4> + Test Description: This test checks if Element.hasPointerCapture returns value correctly + <ol> + <li> Press black rectangle and do not release + <li> Move your pointer to purple rectangle + <li> Release the pointer + <li> Click purple rectangle + </ol> + </h4> + <p> + --> + <div id="target0" touch-action:none></div> + <div id="target1" touch-action:none></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_element_haspointercapture_release_pending_capture-manual.html b/dom/events/test/pointerevents/pointerevent_element_haspointercapture_release_pending_capture-manual.html new file mode 100644 index 000000000..a0f25c131 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_element_haspointercapture_release_pending_capture-manual.html @@ -0,0 +1,76 @@ +<!doctype html> +<html> + <head> + <title>Element.hasPointerCapture test after the pending pointer capture element releases pointer capture</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + var test_pointerEvent = async_test("hasPointerCapture test after the pending pointer capture element releases pointer capture"); + + function run() { + var target0 = document.getElementById("target0"); + var target1 = document.getElementById("target1"); + + on_event(target0, "pointerdown", function (e) { + detected_pointertypes[e.pointerType] = true; + target0.setPointerCapture(e.pointerId); + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), true, "After target0.setPointerCapture, target0.hasPointerCapture should return true"); + }); + }); + + on_event(target0, "gotpointercapture", function (e) { + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), true, "After target0 received gotpointercapture, target0.hasPointerCapture should return true"); + }); + target1.setPointerCapture(e.pointerId); + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), false, "After target1.setPointerCapture, target0.hasPointerCapture should return false"); + assert_equals(target1.hasPointerCapture(e.pointerId), true, "After target1.setPointerCapture, target1.hasPointerCapture should return true"); + }); + target1.releasePointerCapture(e.pointerId); + test_pointerEvent.step(function () { + assert_equals(target0.hasPointerCapture(e.pointerId), false, "After target1.releasePointerCapture, target0.hasPointerCapture should be false"); + assert_equals(target1.hasPointerCapture(e.pointerId), false, "After target1.releasePointerCapture, target1.hasPointerCapture should be false"); + }); + }); + + on_event(target1, "gotpointercapture", function (e) { + test_pointerEvent.step(function () { + assert_true(false, "target1 should never receive gotpointercapture in this test"); + }); + }); + + on_event(target0, "lostpointercapture", function (e) { + test_pointerEvent.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Element.hasPointerCapture test after the pending pointer capture element releases pointer capture</h1> + <!-- + <h4> + Test Description: This test checks if Element.hasPointerCapture returns value correctly after the pending pointer capture element releases pointer capture + <ol> + <li> Press black rectangle and do not release + <li> Move your pointer to purple rectangle + <li> Release the pointer + </ol> + </h4> + --> + <p> + <div id="target0" touch-action:none></div> + <div id="target1" touch-action:none></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_gotpointercapture_before_first_pointerevent-manual.html b/dom/events/test/pointerevents/pointerevent_gotpointercapture_before_first_pointerevent-manual.html new file mode 100644 index 000000000..0589ff2b3 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_gotpointercapture_before_first_pointerevent-manual.html @@ -0,0 +1,100 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: gotpiontercapture is fired first and asynchronously.</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/" /> + <meta name="assert" content="After setting capture, the gotpointercapture dispatched to the capturing element before any other event is fired." /> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("gotpointercapture event"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var target0; + var listener; + var pointerdown_event; + var detected_pointerEvents = new Array(); + var eventRcvd = false; + var isWaiting = false; + + + function run() { + target0 = document.getElementById("target0"); + target0.style["touchAction"] = "none"; + listener = document.getElementById("listener"); + + // listen to all events + for (var i = 0; i < All_Pointer_Events.length; i++) { + on_event(listener, All_Pointer_Events[i], function (event) { + if (event.type == "gotpointercapture") { + check_PointerEvent(event); + + // TA: 10.2 + assert_true(isWaiting, "gotpointercapture must be fired asynchronously"); + isWaiting = false; + + // if any events have been received with same pointerId before gotpointercapture, then fail + var eventsRcvd_str = ""; + if (eventRcvd) { + eventsRcvd_str = "Events received before gotpointercapture: "; + for (var i = 0; i < detected_pointerEvents.length; i++) { + eventsRcvd_str += detected_pointerEvents[i] + ", "; + } + } + test_pointerEvent.step(function () { + assert_false(eventRcvd, "no other events should be received before gotpointercapture." + eventsRcvd_str); + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerID is same for pointerdown and gotpointercapture"); + }); + } + else { + if (pointerdown_event.pointerId === event.pointerId) { + assert_false(isWaiting, event.type + " must be fired after gotpointercapture"); + detected_pointerEvents.push(event.type); + eventRcvd = true; + test_pointerEvent.done(); // complete test + } + } + }); + } + + // set pointer capture + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[event.pointerType] = true; + pointerdown_event = event; + listener.setPointerCapture(event.pointerId); + isWaiting = true; + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch gotpointercapture event</h1> + <!-- + <h4>Test Description: + After pointer capture is set for a pointer, and prior to dispatching the first event for the pointer, the gotpointercapture + event must be dispatched to the element that is receiving the pointer capture. The gotpointercapture event must be dispatched asynchronously. + </h4> + <br /> + --> + <div id="target0"> + Use the mouse, touch or pen to tap/click this box. + </div> + <div id="listener">Do not hover over or touch this element. </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_lostpointercapture_for_disconnected_node-manual.html b/dom/events/test/pointerevents/pointerevent_lostpointercapture_for_disconnected_node-manual.html new file mode 100644 index 000000000..d103804dc --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_lostpointercapture_for_disconnected_node-manual.html @@ -0,0 +1,81 @@ +<!doctype html> +<html> + <head> + <title>Lostpointercapture fires on document when target is removed</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body> + <!-- + <h1>Pointer Events - lostpointercapture when capturing element is removed</h1> + <h4> + Test Description: + This test checks if lostpointercapture is fired at the document when the capturing node is removed from the document. + Complete the following actions: + <ol> + <li>Press and hold left mouse button over "Set Capture" button. "gotpointercapture" should be logged inside of the black rectangle. + <li>"lostpointercapture" should be logged inside of the black rectangle after a short delay. + </ol> + </h4> + --> + <div id="target0"></div> + <div id="target1" style="background:black; color:white"></div> + <br> + <input type="button" id="btnCapture" value="Set Capture"> + <script type='text/javascript'> + var isDisconnected = false; + var count = 0; + + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var target0 = document.getElementById('target0'); + var target1 = document.getElementById('target1'); + var captureButton = document.getElementById('btnCapture'); + + var test_lostpointercapture = async_test("lostpointercapture event received"); + + window.onload = function() { + on_event(captureButton, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + sPointerCapture(event); + }); + + on_event(target0, 'gotpointercapture', function(e) { + log("gotpointercapture", target1); + setTimeout(function() { + isDisconnected = true; + target0.parentNode.removeChild(target0); + }, 250); + }); + + on_event(target0, 'lostpointercapture', function(e) { + log("lostpointercapture on element", target1); + test(function() { + // TA: 11.3 + assert_unreached("lostpointercapture must be fired on the document, not the capturing element"); + }, "lostpointercapture must not be dispatched on the disconnected node"); + }); + + on_event(document, 'lostpointercapture', function(e) { + log("lostpointercapture on document", target1); + test(function() { + // TA: 11.3 + assert_true(isDisconnected, "lostpointercapture must be fired on the document"); + + }, "lostpointercapture is dispatched on the document"); + test_lostpointercapture.done(); + }); + } + </script> + <h1>Pointer Events Capture Test</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_lostpointercapture_is_first-manual.html b/dom/events/test/pointerevents/pointerevent_lostpointercapture_is_first-manual.html new file mode 100644 index 000000000..16a000cbe --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_lostpointercapture_is_first-manual.html @@ -0,0 +1,118 @@ +<!doctype html> +<html> + <head> + <title>Lostpointercapture triggers first and asynchronously</title> + <meta name="assert" content="TA5.2.10: A user agent must fire a pointer event named lostpointercapture after pointer capture is released for a pointer. This event must be fired prior to any subsequent events for the pointer after capture was released. This event is fired at the element from which pointer capture was removed;"> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>Pointer Events capture test - lostpointercapture order</h1> + <!-- + <h4> + Test Description: + This test checks if lostpointercapture is handled asynchronously and prior to all subsequent events. + Complete the following actions: + <ol> + <li>Press and hold left mouse button over "Set Capture" button. "gotpointercapture" should be logged inside of the black rectangle + <li>"lostpointercapture" should be logged inside of the black rectangle after pointerup + </ol> + </h4> + Test passes if lostpointercapture is dispatched after releasing the mouse button and before any additional pointer events. + --> + <div id="target0" style="background:black; color:white"></div> + <br> + <input type="button" id="btnCapture" value="Set Capture"> + <script type='text/javascript'> + var detected_pointertypes = {}; + var detected_pointerEvents = new Array(); + var pointerdown_event = null; + + var test_pointerEvent = async_test("lostpointercapture is dispatched prior to subsequent events"); // set up test harness + + var isPointerCapture = false; + var count=0; + + var testStarted = false; + var eventRcvd = false; + var isAsync = false; + + add_completion_callback(showPointerTypes); + + var target0 = document.getElementById('target0'); + var captureButton = document.getElementById('btnCapture'); + + function run() { + on_event(captureButton, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + pointerdown_event = event; + if(isPointerCapture == false) { + isPointerCapture = true; + captureButton.value = 'Release Capture'; + sPointerCapture(event); + } + }); + + // TA5.1.3.1: Process Pending Pointer Capture + // Whenever a user agent is to fire a Pointer Event that is not gotpointercapture or lostpointercapture, + // it must first run the steps of processing pending pointer capture + // + // TA5.2.12: The lostpointercapture event + // After pointer capture is released for a pointer, and prior to any subsequent events for the pointer, + // the lostpointercapture event must be dispatched to the element from which pointer capture was removed. + // listen to all events + for (var i = 0; i < All_Pointer_Events.length; i++) { + on_event(target0, All_Pointer_Events[i], function (event) { + // if the event was gotpointercapture, just log it and return + if (event.type == "gotpointercapture") { + testStarted = true; + rPointerCapture(event); + isAsync = true; + log("gotpointercapture", target0); + return; + } + else if (event.type == "lostpointercapture") { + log("lostpointercapture", target0); + captureButton.value = 'Set Capture'; + isPointerCapture = false; + + // TA: 11.2 + test_pointerEvent.step(function () { + assert_true(isAsync, "lostpointercapture must be fired asynchronously"); + }); + + // if any events except pointerup have been received with same pointerId before lostpointercapture, then fail + var eventsRcvd_str = ""; + if (eventRcvd) { + eventsRcvd_str = "Events received before lostpointercapture: "; + for (var i = 0; i < detected_pointerEvents.length; i++) { + eventsRcvd_str += detected_pointerEvents[i] + ", "; + } + } + test_pointerEvent.step(function () { + assert_false(eventRcvd, "no other events should be received before lostpointercapture." + eventsRcvd_str); + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerID is same for pointerdown and lostpointercapture"); + }); + test_pointerEvent.done(); // complete test + } + else { + if (testStarted && pointerdown_event != null && pointerdown_event.pointerId === event.pointerId && event.type != "pointerup") { + detected_pointerEvents.push(event.type); + eventRcvd = true; + } + } + }); + } + } + </script> + <h1>Pointer Events Capture Test</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_multiple_primary_pointers_boundary_events-manual.html b/dom/events/test/pointerevents/pointerevent_multiple_primary_pointers_boundary_events-manual.html new file mode 100644 index 000000000..296d9d49f --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_multiple_primary_pointers_boundary_events-manual.html @@ -0,0 +1,148 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Boundary compatibility events for multiple primary pointers</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Google" href="http://www.google.com "/> + <meta name="assert" content="When more than one primary pointers are active, each will have an independent sequence of pointer boundary events but the compatibilty mouse boundary events have their own sequence."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var test_pointerEvent = async_test("Multi-pointer boundary compat events"); + add_completion_callback(end_of_test); + + var detected_pointertypes = {}; + var event_log = []; + + // These two ids help us detect two different pointing devices. + var first_entry_pointer_id = -1; + var second_entry_pointer_id = -1; + + // Current node for each pointer id + var current_node_for_id = {}; + + function end_of_test() { + showLoggedEvents(); + showPointerTypes(); + } + + function end_of_interaction() { + test(function () { + assert_equals(event_log.join(", "), + "mouseover@target0, mouseenter@target0, mouseout@target0, mouseleave@target0, " + + "mouseover@target1, mouseenter@target1, mouseout@target1, mouseleave@target1, " + + "mouseover@target0, mouseenter@target0, mouseout@target0, mouseleave@target0" + ); + }, "Event log"); + + test_pointerEvent.done(); // complete test + } + + function log_event(label) { + event_log.push(label); + } + + function run() { + on_event(document.getElementById("done"), "click", end_of_interaction); + + var target_list = ["target0", "target1"]; + var pointer_event_list = ["pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown"]; + var mouse_event_list = ["mouseenter", "mouseleave", "mouseover", "mouseout"]; + + target_list.forEach(function(targetId) { + var target = document.getElementById(targetId); + + pointer_event_list.forEach(function(eventName) { + on_event(target, eventName, function (event) { + var label = event.type + "@" + targetId; + + detected_pointertypes[event.pointerType] = true; + + if (!event.isPrimary) { + test(function () { + assert_unreached("Non-primary pointer " + label); + }, "Non-primary pointer " + label); + } + + if (event.type === "pointerenter") { + var pointer_id = event.pointerId; + if (current_node_for_id[pointer_id] !== undefined) { + test(function () { + assert_unreached("Double entry " + label); + }, "Double entry " + label); + } + current_node_for_id[pointer_id] = event.target; + + // Test that two different pointing devices are used + if (first_entry_pointer_id === -1) { + first_entry_pointer_id = pointer_id; + } else if (second_entry_pointer_id === -1) { + second_entry_pointer_id = pointer_id; + test(function () { + assert_true(first_entry_pointer_id !== second_entry_pointer_id); + }, "Different pointing devices"); + } + } else if (event.type === "pointerleave") { + var pointer_id = event.pointerId; + if (current_node_for_id[pointer_id] !== event.target) { + test(function () { + assert_unreached("Double exit " + label); + }, "Double exit " + label); + } + current_node_for_id[pointer_id] = undefined; + } + }); + }); + + mouse_event_list.forEach(function(eventName) { + on_event(target, eventName, function (event) { + log_event(event.type + "@" + targetId); + }); + }); + }); + } + </script> + <style> + #target0, #target1 { + margin: 20px; + } + + #done { + margin: 20px; + border: 2px solid black; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Event: Boundary compatibility events for multiple primary pointers</h1> + <!-- + <h4> + When more than one primary pointers are active, each will have an independent sequence of pointer boundary events but the compatibilty mouse boundary events have their own sequence. + </h4> + Instruction: + <ol> + <li>Move the mouse directly into Target0 (without going through Target1), and then leave the mouse there unmoved.</li> + <li>Tap directly on Target1 with a finger or a stylus, and then lift the finger/stylus off the screen/digitizer without crossing Target1 boundary.</li> + <li>Move the mouse into Target0 (if not there already) and move inside it.</li> + <li>Click Done (without passing over Target1).</li> + </ol> + --> + <div id="done"> + Done + </div> + <div id="target0"> + Target0 + </div> + <div id="target1"> + Target1 + </div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>The following events were logged: <span id="event-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointerId_scope-manual.html b/dom/events/test/pointerevents/pointerevent_pointerId_scope-manual.html new file mode 100644 index 000000000..98bbb879d --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerId_scope-manual.html @@ -0,0 +1,85 @@ +<!doctype html> +<html> + <!-- +Test cases for Pointer Events v1 spec +This document references Test Assertions (abbrev TA below) written by Cathy Chan +http://www.w3.org/wiki/PointerEvents/TestAssertions +--> + <head> + <title>Pointer Events pointerdown tests</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerId of an active pointer is the same across iframes"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + var detected_pointertypes = {}; + + function run() { + var target0 = document.getElementById("target0"); + var pointerover_pointerId = null; + var pointerover_pointerType = null; + + var eventList = ['pointerenter', 'pointerover', 'pointermove', 'pointerout', 'pointerleave']; + var receivedEvents = {}; + var receivedEventsInnerFrame = {}; + + + function checkPointerId(event, inner) { + detected_pointertypes[event.pointerType] = true; + var eventName = (inner ? "inner frame " : "" ) + event.type; + test_pointerEvent.step(function() { + assert_equals(event.pointerId, pointerover_pointerId, "PointerId of " + eventName + " is not correct"); + assert_equals(event.pointerType, pointerover_pointerType, "PointerType of " + eventName + " is not correct"); + }, eventName + ".pointerId were the same as first pointerover"); + } + + on_event(window, "message", function(event) { + var pe_event = JSON.parse(event.data); + receivedEventsInnerFrame[pe_event.type] = 1; + checkPointerId(pe_event, true); + if (Object.keys(receivedEvents).length == eventList.length && Object.keys(receivedEventsInnerFrame).length == eventList.length) + test_pointerEvent.done(); + }); + + eventList.forEach(function(eventName) { + on_event(target0, eventName, function (event) { + if (pointerover_pointerId === null && event.type == 'pointerover') { + pointerover_pointerId = event.pointerId; + pointerover_pointerType = event.pointerType; + } else { + checkPointerId(event, false); + } + receivedEvents[event.type] = 1; + }); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Events pointerdown tests</h1> + Complete the following actions: + <!-- + <ol> + <li>Start with your pointing device outside of black box, then move it into black box. If using touch just press in black box and don't release. + <li>Move your pointing device into purple box (without leaving the digitizer range if you are using hover supported pen or without releasing touch if using touch). Then move it out of the purple box. + </ol> + --> + <div id="target0" class="touchActionNone"> + </div> + <iframe src="resources/pointerevent_pointerId_scope-iframe.html" id="innerframe"></iframe> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointercancel_touch-manual.html b/dom/events/test/pointerevents/pointerevent_pointercancel_touch-manual.html new file mode 100644 index 000000000..00f4495dc --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointercancel_touch-manual.html @@ -0,0 +1,80 @@ +<!doctype html> +<html> + <head> + <title>PointerCancel - touch</title> + <meta name="viewport" content="width=device-width"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body class="scrollable" onload="run()"> + <h1>pointercancel test</h1> + <!-- + <h3>Warning: this test works properly only for devices that have touchscreen</h3> + <h4> + Test Description: This test checks if pointercancel event triggers. + <p>Start touch over the black rectangle and then move your finger to scroll the page.</p> + </h4> + <p> + --> + <div id="target0" style="background: black"></div> + <script> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointercancel event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var pointerdown_event = null; + var pointercancel_event = null; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerdown", function (event) { + pointerdown_event = event; + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, "pointercancel", function (event) { + pointercancel_event = event; + test_pointerEvent.step(function () { + assert_not_equals(pointerdown_event, null, "pointerdown was received: "); + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerId should be the same for pointerdown and pointercancel"); + assert_equals(event.pointerType, pointerdown_event.pointerType, "pointerType should be the same for pointerdown and pointercancel"); + assert_equals(event.isPrimary, pointerdown_event.isPrimary, "isPrimary should be the same for pointerdown and pointercancel"); + check_PointerEvent(event); + }); + }); + + on_event(target0, "pointerout", function (event) { + test_pointerEvent.step(function () { + assert_not_equals(pointercancel_event, null, "pointercancel was received before pointerout: "); + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerId should be the same for pointerout and pointercancel"); + assert_equals(event.pointerType, pointerdown_event.pointerType, "pointerType should be the same for pointerout and pointercancel"); + assert_equals(event.isPrimary, pointerdown_event.isPrimary, "isPrimary should be the same for pointerout and pointercancel"); + }); + }); + + on_event(target0, "pointerleave", function (event) { + test_pointerEvent.step(function () { + assert_not_equals(pointercancel_event, null, "pointercancel was received before pointerleave: "); + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerId should be the same for pointerleave and pointercancel"); + assert_equals(event.pointerType, pointerdown_event.pointerType, "pointerType should be the same for pointerleave and pointercancel"); + assert_equals(event.isPrimary, pointerdown_event.isPrimary, "isPrimary should be the same for pointerleave and pointercancel"); + }); + test_pointerEvent.done(); + }); + } + </script> + <h1>Pointer Events pointercancel Tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointerdown-manual.html b/dom/events/test/pointerevents/pointerevent_pointerdown-manual.html new file mode 100644 index 000000000..eb80e68a3 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerdown-manual.html @@ -0,0 +1,60 @@ +<!doctype html> +<html> + <!-- +Test cases for Pointer Events v1 spec +This document references Test Assertions (abbrev TA below) written by Cathy Chan +http://www.w3.org/wiki/PointerEvents/TestAssertions +--> + <head> + <title>Pointer Events pointerdown tests</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerdown event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + + function run() { + var target0 = document.getElementById("target0"); + var pointerover_event; + + on_event(target0, "pointerover", function (event) { + detected_pointertypes[event.pointerType] = true; + pointerover_event = event; + check_PointerEvent(event); + }); + + on_event(target0, "pointerdown", function (event) { + check_PointerEvent(event); + + test_pointerEvent.step(function () { + assert_equals(event.pointerType, pointerover_event.pointerType, "pointerType is same for pointerover and pointerdown"); + assert_equals(event.isPrimary, pointerover_event.isPrimary, "isPrimary is same for pointerover and pointerdown"); + }); + + test_pointerEvent.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Events pointerdown tests</h1> + <div id="target0"> + Start with your pointing device outside of this box, then click here. + </div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerenter-manual.html b/dom/events/test/pointerevents/pointerevent_pointerenter-manual.html new file mode 100644 index 000000000..d03741769 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerenter-manual.html @@ -0,0 +1,53 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Dispatch pointerenter. </title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="When a pointing device is moved into the hit test boundaries of an element or one of its descendants, the pointerenter event must be dispatched."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerenter event"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerenter", function (event) { + detected_pointertypes[event.pointerType] = true; + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.type, "pointerenter", "pointer event received: " + event.type); + }); + test_pointerEvent.done(); // complete test + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointerenter</h1> + <h4> + Test Description: + When a pointing device is moved into the hit test boundaries of an element or one of its descendants, the pointerenter event must be dispatched. + </h4> + <div id="target0"> + Use the mouse or pen to move over this box. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerenter_does_not_bubble-manual.html b/dom/events/test/pointerevents/pointerevent_pointerenter_does_not_bubble-manual.html new file mode 100644 index 000000000..38289201e --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerenter_does_not_bubble-manual.html @@ -0,0 +1,91 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: The pointerenter event does not bubble </title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="The pointerenter event must not bubble up to parent elements."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerEnter event does not bubble"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var pointerenter_event = null; + + function run() { + var target0 = document.getElementById("target0"); + var parent0 = document.getElementById("parent0"); + + on_event(target0, "pointerenter", function (event) { + pointerenter_event = event; + + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.type, "pointerenter", "pointer event received: " + event.type); + assert_false(event.bubbles, "pointerenter event.bubbles should be false: " + event.bubbles); + }); + }); + on_event(target0, "pointerout", function (event) { + test_pointerEvent.step(function () { + assert_not_equals(pointerenter_event, null, "pointerout event was never received: "); + }); + test_pointerEvent.done(); // complete test + }); + + // parent + on_event(parent0, "pointerenter", function (event) { + detected_pointertypes[event.pointerType] = true; + test_pointerEvent.step(function () { + assert_equals(event.target.id, "parent0", "Recieved " + event.type + " in parent for " + event.target.id); + }); + }); + } + + </script> + <style> + #target0 { + background: purple; + border: 1px solid orange; + width:50px; + height:50px; + } + #parent0 { + background: black; + border: 1px solid orange; + width:100px; + height:100px; + } + </style> + </head> + <body onload="run()"> + <h1> Pointer Event: pointerenter does not bubble</h1> + <!-- + <h4> + Test Description: + The pointerenter event must not bubble up to parent elements. + </h4> + <div id="instructions"> + Use the mouse or pen to hover over then out of the purple box nested in the black box. Or with touch, tap on the purple box. + </div> + --> + <div id="parent0"> + <div id="target0"></div> + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerenter_nohover-manual.html b/dom/events/test/pointerevents/pointerevent_pointerenter_nohover-manual.html new file mode 100644 index 000000000..9b8d38ca5 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerenter_nohover-manual.html @@ -0,0 +1,77 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Dispatch pointerenter. (nohover)</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="When a pointing device that does not support hover is moved into the hit test boundaries of an element +or one of its descendants as a result of a pointerdown event, the pointerenter event must be dispatched. "/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerenter event"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var test_pointerEnter; + var f_pointerenter_rcvd = false; + var pointerenter_event; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerdown", function (event) { + if(event.pointerType == 'touch') { + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.type, "pointerdown", "pointer event received: " + event.type); + assert_true(f_pointerenter_rcvd, "pointerenter event should have been received before pointerdown"); + assert_equals(event.pointerType, pointerenter_event.pointerType, "pointerType is same for pointerenter and pointerdown"); + assert_equals(event.isPrimary, pointerenter_event.isPrimary, "isPrimary is same for pointerenter and pointerdown"); + }); + test_pointerEvent.done(); // complete test + } + }); + + on_event(target0, "pointerenter", function (event) { + detected_pointertypes[event.pointerType] = true; + if(event.pointerType == 'touch') { + pointerenter_event = event; + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.type, "pointerenter", "pointer event received: " + event.type); + }); + f_pointerenter_rcvd = true; + } + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointerenter (nohover)</h1> + <!-- + <h4> + Test Description: + When a pointing device that does not support hover is moved into the hit test boundaries of an element or one of its + descendants as a result of a pointerdown event, the pointerenter event must be dispatched. + </h4> + <br /> + --> + <div id="target0"> + Tap here. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_after_pointercancel_touch-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_after_pointercancel_touch-manual.html new file mode 100644 index 000000000..b04698d3d --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_after_pointercancel_touch-manual.html @@ -0,0 +1,67 @@ +<!doctype html> +<html> + <head> + <title>pointerleave after pointercancel</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body class="scrollable" onload="run()"> + <h2>pointerleave after pointercancel</h2> + <h4>Test Description: This test checks if pointerleave event triggers after pointercancel. Start touch on the black rectangle and move your touch to scroll in any direction. </h4> + <p>Note: this test is for touch devices only</p> + <div id="target0"></div> + <script> + var test_pointerleave = async_test("pointerleave event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var eventTested = false; + var pointercancel_event = null; + var detected_pointertypes = {}; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointercancel", function (event) { + detected_pointertypes[event.pointerType] = true; + pointercancel_event = event; + }); + + // After firing the pointercancel event the pointerleave event must be dispatched. + // TA: 4.3.1 + on_event(target0, "pointerleave", function (event) { + if(event.pointerType == 'touch') { + if(pointercancel_event != null) { + if(eventTested == false) { + test_pointerleave.step(function() { + assert_equals(event.pointerType, pointercancel_event.pointerType, "pointerType is same for pointercancel and pointerleave"); + assert_equals(event.isPrimary, pointercancel_event.isPrimary, "isPrimary is same for pointercancel and pointerleave"); + }); + eventTested = true; + test_pointerleave.done(); + } + } + else { + test_pointerleave.step(function() { + assert_unreached("pointerleave received before pointercancel"); + }, "pointerleave received before pointercancel"); + } + } + }); + } + + </script> + <h1>Pointer Events pointerleave tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_after_pointerup_nohover-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_after_pointerup_nohover-manual.html new file mode 100644 index 000000000..b0c74f23b --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_after_pointerup_nohover-manual.html @@ -0,0 +1,68 @@ +<!doctype html> +<html> + <head> + <title>pointerleave after pointerup</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h2>pointerleave after pointerup</h2> + <h4>Test Description: This test checks if pointerleave event triggers for devices that don't support hover. Tap the black rectangle. </h4> + <p>Note: this test is only for devices that do not support hover.</p> + <div id="target0"></div> + <script> + var test_pointerleave = async_test("pointerleave event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var eventTested = false; + var isPointerupReceived = false; + var pointerup_event = null; + var detected_pointertypes = {}; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerup", function (event) { + detected_pointertypes[event.pointerType] = true; + pointerup_event = event; + }); + + // For input devices that do not support hover, a pointerleave event must follow the pointerup event. + // TA: 3.6 + on_event(target0, "pointerleave", function (event) { + if(event.pointerType == 'touch') { + if(pointerup_event != null) { + if(eventTested == false) { + eventTested = true; + test_pointerleave.step(function() { + assert_equals(event.pointerType, pointerup_event.pointerType, "pointerType is same for pointerup and pointerleave"); + assert_equals(event.isPrimary, pointerup_event.isPrimary, "isPrimary is same for pointerup and pointerleave"); + }); + test_pointerleave.done(); + } + } + else { + test_pointerleave.step(function() { + assert_unreached("pointerleave received before pointerup"); + }, "pointerleave received before pointerup"); + } + } + }); + } + + </script> + <h1>Pointer Events pointerleave tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_descendant_over-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_descendant_over-manual.html new file mode 100644 index 000000000..61bf12224 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_descendant_over-manual.html @@ -0,0 +1,64 @@ +<!doctype html> +<html> + <head> + <title>pointerleave + descendant</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>pointerleave</h1> + <!-- + <h4> + Test Description: This test checks if pointerleave event works properly. + <ol> + <li>Put your mouse over the black rectangle + <li>Then move it into the purple rectangle + <li>Click on the purple rectangle to complete the test + </ol> + Note: when you entered the black rectangle once don't leave it before the end of the test to get proper results. + </h4> + <p> + --> + <div id="target0" style="background:black"> + <div id="target1" style="background:purple"></div> + </div> + <script> + var eventTested = false; + var pointerleaveReceived = false; + var detected_pointertypes = {}; + + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + // The pointerleave event must not be dispatched when the pointer enters a child element without leaving the hit test boundaries of the parent. (as distinct from pointerout) + // TA: 9.2 + on_event(target1, "pointerdown", function(event) { + detected_pointertypes[event.pointerType] = true; + + test(function() { + assert_true(!pointerleaveReceived, "pointerleave shouldn't be received on descendant's pointerover") + }, "pointerleave shouldn't be received on descendant's pointerover"); + }); + + on_event(target0, "pointerleave", function (event) { + if (eventTested == false) { + pointerleaveReceived = true; + eventTested = true; + } + }); + } + </script> + <h1>Pointer Events pointerleave tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_descendants-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_descendants-manual.html new file mode 100644 index 000000000..40542f2a2 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_descendants-manual.html @@ -0,0 +1,55 @@ +<!doctype html> +<html> + <head> + <title>Pointerleave + descendant</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>pointerleave</h1> + <!-- + <h4> + Test Description: This test checks if pointerleave event works properly. + <p>Put your mouse over the black rectangle and then move it out through purple rectangle boundaries.</p> + </h4> + <p> + --> + <div id="target0" style="background:black; height: 47px;"> + <div style="background:purple; height: 35px; width: 90%; position: absolute"></div> + </div> + <script> + var eventTested = false; + var detected_pointertypes = {}; + + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var test_pointerleave = async_test("pointerleave event received"); + + on_event(target0, "pointerover", function(event) { + detected_pointertypes[ event.pointerType ] = true; + }); + + // When a pointing device is moved off of the hit test boundaries of an element and all of its descendants, the pointerleave event must be dispatched. + // TA: 9.1 + on_event(target0, "pointerleave", function (event) { + if (eventTested == false) { + test_pointerleave.done(); + eventTested = true; + } + }); + } + </script> + <h1>Pointer Events pointerleave tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_does_not_bubble-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_does_not_bubble-manual.html new file mode 100644 index 000000000..662a714a9 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_does_not_bubble-manual.html @@ -0,0 +1,80 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: The pointerleave event does not bubble </title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="The pointerleave event must not bubble up to parent elements."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerLeave event does not bubble"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var parent0 = document.getElementById("parent0"); + + on_event(target0, "pointerleave", function (event) { + detected_pointertypes[event.pointerType] = true; + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.type, "pointerleave", "pointer event received: " + event.type); + assert_false(event.bubbles, "pointerleave event.bubbles should be false: " + event.bubbles); + }); + }); + + on_event(parent0, "pointerleave", function (event) { + test_pointerEvent.step(function () { + assert_equals(event.target.id, "parent0", "Recieved " + event.type + " in parent for " + event.target.id); + }); + test_pointerEvent.done(); // complete test + }); + } + </script> + <style> + #target0 { + background: purple; + border: 1px solid orange; + width:50px; + height:50px; + } + #parent0 { + background: black; + border: 1px solid orange; + width:100px; + height:100px; + } + </style> + </head> + <body onload="run()"> + <h1> Pointer Event: pointerleave does not bubble</h1> + <!-- + <h4> + Test Description: + The pointerleave event must not bubble up to parent elements. + </h4> + <div id="instructions"> + Use the mouse or pen to hover over then out of the purple box nested in the black box. Or with touch, tap on the purple box. + </div> + --> + <div id="parent0"> + <div id="target0"></div> + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_mouse-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_mouse-manual.html new file mode 100644 index 000000000..042f7a8cd --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_mouse-manual.html @@ -0,0 +1,56 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Dispatch pointerleave (mouse). </title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="When a pointing device that has continuous position (such as a mouse) leaves the hit test boundaries of an element, the pointerleave event must be dispatched."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerleave event"); // set up test harness; + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerleave", function (event) { + detected_pointertypes[event.pointerType] = true; + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.pointerType, "mouse", "Test should be run using a mouse as input."); + assert_equals(event.type, "pointerleave", "The " + event.type + " event was received."); + }); + test_pointerEvent.done(); // complete test + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointerleave (mouse)</h1> + <!-- + <h4> + Test Description: + When a pointing device that has continuous position (such as a mouse) leaves the hit test boundaries of an element, the pointerleave event must be dispatched. + </h4> + <br /> + --> + <div id="target0"> + Use a mouse to move over then out of this element + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_pen-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_pen-manual.html new file mode 100644 index 000000000..b5ff230e8 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_pen-manual.html @@ -0,0 +1,59 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Dispatch pointerleave (pen). </title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="When a pointing device that supports hover (pen stylus) leaves the range of the digitizer while over an element, the pointerleave event must be dispatched."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerleave event"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerleave", function (event) { + detected_pointertypes[event.pointerType] = true; + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.pointerType, "pen", "Test should be run using a pen as input"); + assert_equals(event.type, "pointerleave", "The " + event.type + " event was received"); + assert_true((event.clientX > target0.getBoundingClientRect().left)&& + (event.clientX < target0.getBoundingClientRect().right)&& + (event.clientY > target0.getBoundingClientRect().top)&& + (event.clientY < target0.getBoundingClientRect().bottom), + "pointerleave should be received inside of target bounds"); + }); + test_pointerEvent.done(); // complete test + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointerleave (pen)</h1> + <h4> + Test Description: + When a pointing device that supports hover (pen stylus) leaves the range of the digitizer while over an element, the pointerleave event must be dispatched. + </h4> + <br /> + <div id="target0"> + Use a pen to hover over then lift up away from this element. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerleave_touch-manual.html b/dom/events/test/pointerevents/pointerevent_pointerleave_touch-manual.html new file mode 100644 index 000000000..1dc35c709 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerleave_touch-manual.html @@ -0,0 +1,54 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Dispatch pointerleave (touch). </title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="When a pointing device that does not support hover (such as a finger) leaves the hit test boundaries as a result of a pointerup event, the pointerleave event must be dispatched. "/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerleave event"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerleave", function (event) { + detected_pointertypes[event.pointerType] = true; + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.pointerType, "touch", "Test should be run using touch input"); + assert_equals(event.type, "pointerleave", "The " + event.type + " event received"); + }); + test_pointerEvent.done(); // complete test + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointerleave (touch)</h1> + <h4> + Test Description: + When a pointing device that does not support hover (such as a finger) leaves the hit test boundaries as a result of a pointerup event, the pointerleave event must be dispatched. + </h4> + <br /> + <div id="target0"> + Use touch to tap on this box. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointermove-manual.html b/dom/events/test/pointerevents/pointerevent_pointermove-manual.html new file mode 100644 index 000000000..2d55cfead --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointermove-manual.html @@ -0,0 +1,45 @@ +<!doctype html> +<html> + <head> + <title>Pointermove</title> + <meta name="viewport" content="width=device-width"> + <meta name="assert" content="When a pointer changes coordinates, the pointermove event must be dispatched."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h2>PointerMove</h2> + <h4>Test Description: This test checks if pointermove event triggers. Move your mouse over the black rectangle or slide it if you are using touchscreen.</h4> + <div id="target0" style="background:black"></div> + <script> + var eventTested = false; + var detected_pointertypes = {}; + var test_pointermove = async_test("pointermove event received"); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + // When a pointer changes coordinates, the pointermove event must be dispatched. + // TA: 5.3 + on_event(target0, "pointermove", function (event) { + detected_pointertypes[event.pointerType] = true; + if (eventTested == false) { + test_pointermove.done(); + eventTested = true; + } + }); + } + </script> + <h1>Pointer Events pointermove Tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointermove-on-chorded-mouse-button.html b/dom/events/test/pointerevents/pointerevent_pointermove-on-chorded-mouse-button.html new file mode 100644 index 000000000..7db3dcddd --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointermove-on-chorded-mouse-button.html @@ -0,0 +1,77 @@ +<!doctype html> +<html> + <head> + <title>Pointermove on button state changes</title> + <meta name="viewport" content="width=device-width"> + <meta name="assert" content="When a pointer changes button state and does not produce a different event, the pointermove event must be dispatched."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h2>PointerMove</h2> + <h4>Test Description: This test checks if pointermove event are triggered by button state changes + <ol> + <li>Put your mouse over the black rectangle</li> + <li>Press a button and hold it</li> + <li>Press a second button</li> + <li>Release the second button</li> + <li>Release the first button to complete the test</li> + </ol> + </h4> + <div id="target0" style="background:black"></div> + <script> + var eventTested = false; + var detected_pointertypes = {}; + var test_pointermove = async_test("pointermove events received for button state changes"); + add_completion_callback(showPointerTypes); + + var step = 0; + var firstButton = 0; + + function run() { + var target0 = document.getElementById("target0"); + + // When a pointer changes button state and the circumstances produce no other pointer event, the pointermove event must be dispatched. + // 5.2.6 + + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[event.pointerType] = true; + test_pointermove.step(function() {assert_true(step === 0, "There must not be more than one pointer down event.");}); + if (step == 0) { + step = 1; + firstButton = event.buttons; + } + }); + on_event(target0, "pointermove", function (event) { + detected_pointertypes[event.pointerType] = true; + + if (step == 1 && event.button != -1) { // second button pressed + test_pointermove.step(function() {assert_true(event.buttons !== firstButton, "The pointermove event must be triggered by pressing a second button.");}); + test_pointermove.step(function() {assert_true((event.buttons & firstButton) != 0, "The first button must still be reported pressed.");}); + step = 2; + } else if (step == 2 && event.button != -1) { // second button released + test_pointermove.step(function() {assert_true(event.buttons === firstButton, "The pointermove event must be triggered by releasing the second button.");}); + step = 3; + } + }); + on_event(target0, "pointerup", function (event) { + detected_pointertypes[event.pointerType] = true; + test_pointermove.step(function() {assert_true(step === 3, "The pointerup event must be triggered after pressing and releasing the second button.");}); + test_pointermove.step(function() {assert_true(event.buttons === 0, "The pointerup event must be triggered by releasing the last pressed button.");}); + test_pointermove.done(); + eventTested = true; + }); + } + </script> + <h1>Pointer Events pointermove on button state changes Tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html b/dom/events/test/pointerevents/pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html new file mode 100644 index 000000000..04c4c93ed --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html @@ -0,0 +1,73 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: pointermove has same isPrimary as last pointerdown with the same pointerId</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="The isPrimary attribute of a pointermove event must have the same value as the isPrimary attribute of the last pointerdown event with the same pointerId attribute."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointermove has same isPrimary as last pointerdown"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var pointermove_event = null; + var pointerdown_event = null; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointermove", function (event) { + if (pointerdown_event != null) { + test_pointerEvent.step(function () { + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointermove.pointerId should be the same as pointerdown.pointerId."); + assert_equals(event.isPrimary, pointerdown_event.isPrimary, "pointermove.isPrimary should be the same as pointerdown.isPrimary."); + }); + pointermove_event = event; + } + }); + + on_event(target0, "pointerdown", function (event) { + pointerdown_event = event; + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, "pointerup", function (event) { + test_pointerEvent.step(function () { + assert_not_equals(pointermove_event, null, "pointermove event was never received: "); + }); + test_pointerEvent.done(); // complete test + }); + } + </script> + </head> + <body onload="run()"> + <!-- + <h1>Pointer Event: pointermove has the same isPrimary as last pointerdown with the same pointerId</h1> + <h4>Test Description: + The isPrimary attribute of a pointermove event must have the same value as the isPrimary attribute of the last pointerdown event with the same pointerId attribute. + </h4> + <div id="instructions"> + Press and hold a mouse button, touch contact or pen contact on this element. Move around inside the element while maintaining contact/button down. Only use one device per test run. + <p>Lift off of the element to complete the test.</p> + </div> + --> + <div id="target0" style="touch-action: none;"> + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointermove_pointertype-manual.html b/dom/events/test/pointerevents/pointerevent_pointermove_pointertype-manual.html new file mode 100644 index 000000000..5db76ac1e --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointermove_pointertype-manual.html @@ -0,0 +1,67 @@ +<!doctype html> +<html> + <head> + <title>pointerType conservation</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <!-- + <h1>pointerType conservation</h1> + <h4>Test Description: This test checks if pointerType attribute defined properly.</h4> + <div id="instructions"> + Press and move a mouse button, touch contact or pen contact on the black rectangle. Only use one device per test run. + </div> + <p>Note: This test may be run with different pointer devices, however only one device should be used per test run. + <p> + --> + <div id="target0"></div> + <script> + var eventTested = false; + var pointerTypeGot = false; + var pointerdown_event; + var detected_pointertypes = {}; + + setup({ explicit_done: true }); + + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerover", function(event) { + detected_pointertypes[ event.pointerType ] = true; + }); + + // The pointerType attribute of a pointermove event must have the same value as the pointerType attribute of the last pointerdown event with the same pointerId attribute. + // TA: 5.1 + on_event(target0, "pointerdown", function (event) { + pointerdown_event = event; + pointerTypeGot = true; + }); + + on_event(target0, "pointermove", function (event) { + if(pointerTypeGot == true) { + if(!eventTested) { + test(function() { + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointer IDs are equal: "); + assert_equals(event.pointerType, pointerdown_event.pointerType, "pointerType of pointermove event matches pointerdown event: "); + }, "pointerType is dispatched properly"); + } + done(); + } + }); + } + </script> + <h1>Pointer Events pointerType conservation tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerout-manual.html b/dom/events/test/pointerevents/pointerevent_pointerout-manual.html new file mode 100644 index 000000000..eb8a820ea --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerout-manual.html @@ -0,0 +1,47 @@ +<!doctype html> +<html> + <head> + <title>pointerout</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h2>pointerout</h2> + <h4>Test Description: This test checks if pointerout event triggers. Put your mouse over the black rectangle and then move it out of the rectangle boundaries. If you are using touchscreen tap the black rectangle. </h4> + <div id="target0" style="background: black"></div> + <script> + var test_pointerEvent = async_test("pointerout event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var eventTested = false; + var detected_pointertypes = {}; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerout", function (event) { + detected_pointertypes[event.pointerType] = true; + if (eventTested == false) { + eventTested = true; + check_PointerEvent(event); + test_pointerEvent.done(); + } + }); + } + </script> + <h1>Pointer Events pointerout tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerout_after_pointercancel_touch-manual.html b/dom/events/test/pointerevents/pointerevent_pointerout_after_pointercancel_touch-manual.html new file mode 100644 index 000000000..63cdcb057 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerout_after_pointercancel_touch-manual.html @@ -0,0 +1,68 @@ +<!doctype html> +<html> + <head> + <title>pointerout</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body class="scrollable" onload="run()"> + <h2>pointerout</h2> + <h4>Test Description: This test checks if pointerout event triggers after pointercancel. Start touch on the black rectangle and move your touch to scroll in any direction. </h4> + <p>Note: this test is for touch devices only</p> + <div id="target0"></div> + <script> + var test_pointerout = async_test("pointerout event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var eventTested = false; + var pointercancel_event = null; + var detected_pointertypes = {}; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointercancel", function (event) { + detected_pointertypes[event.pointerType] = true; + pointercancel_event = event; + }); + + // After firing the pointercancel event the pointerout event must be dispatched. + // TA: 4.3 + on_event(target0, "pointerout", function (event) { + if(event.pointerType == 'touch') { + if(pointercancel_event != null) { + if (eventTested == false) { + test_pointerout.step(function() { + assert_equals(event.pointerType, pointercancel_event.pointerType, "pointerType is same for pointercancel and pointerout"); + assert_equals(event.isPrimary, pointercancel_event.isPrimary, "isPrimary is same for pointercancel and pointerout"); + }); + eventTested = true; + test_pointerout.done(); + } + } + else { + test_pointerout.step(function() { + assert_true(false, + "pointercancel received before pointerout"); + }, "pointercancel received before pointerout"); + } + } + }); + } + + </script> + <h1>Pointer Events pointerout tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointerout_after_pointerup_nohover-manual.html b/dom/events/test/pointerevents/pointerevent_pointerout_after_pointerup_nohover-manual.html new file mode 100644 index 000000000..025023d33 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerout_after_pointerup_nohover-manual.html @@ -0,0 +1,68 @@ +<!doctype html> +<html> + <head> + <title>pointerout</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h2>pointerout</h2> + <h4>Test Description: This test checks if pointerout event triggers for devices that don't support hover. Tap the black rectangle. </h4> + <p>Note: this test is only for devices that do not support hover.</p> + <div id="target0"></div> + <script> + var test_pointerout = async_test("pointerout event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var eventTested = false; + var pointerup_event = null; + var detected_pointertypes = {}; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerup", function (event) { + detected_pointertypes[event.pointerType] = true; + pointerup_event = event; + }); + + // For input devices that do not support hover, a pointerout event must follow the pointerup event. + // TA: 3.6 + on_event(target0, "pointerout", function (event) { + if(event.pointerType == 'touch') { + if(pointerup_event != null) { + if(eventTested == false) { + test_pointerout.step(function() { + assert_equals(event.pointerType, pointerup_event.pointerType, "pointerType is same for pointerup and pointerout"); + assert_equals(event.isPrimary, pointerup_event.isPrimary, "isPrimary is same for pointerup and pointerout"); + }); + eventTested = true; + test_pointerout.done(); + } + } + else { + test_pointerout.step(function() { + assert_true(false, + "pointerup received before pointerout"); + }, "pointerup received before pointerout"); + } + } + }); + } + + </script> + <h1>Pointer Events pointerout tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_pointerout_pen-manual.html b/dom/events/test/pointerevents/pointerevent_pointerout_pen-manual.html new file mode 100644 index 000000000..ba58b5503 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerout_pen-manual.html @@ -0,0 +1,58 @@ +<!doctype html> +<html> + <head> + <title>pointerout</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h2>pointerout</h2> + <h4>Test Description: This test checks if pointerout event triggers for pen. Place your pen over the black rectangle and then pull the pen out of the digitizer's detectable range. </h4> + <p>Note: this test is for devices that support hover - for pen only</p> + <div id="target0"></div> + <script> + var test_pointerout = async_test("pointerout event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var eventTested = false; + var isPointerupReceived = false; + var detected_pointertypes = {}; + + function run() { + var target0 = document.getElementById("target0"); + + // When a pen stylus leaves the hover range detectable by the digitizer the pointerout event must be dispatched. + // TA: 7.2 + on_event(target0, "pointerout", function (event) { + detected_pointertypes[event.pointerType] = true; + if(event.pointerType == 'pen') { + if (eventTested == false) { + eventTested = true; + test_pointerout.done(); + } + } + else { + test_pointerout.step(function() { + assert_true(false, + "you have to use pen for this test"); + }, "you have to use pen for this test"); + } + }); + } + + </script> + <h1>Pointer Events pointerout tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerout_received_once-manual.html b/dom/events/test/pointerevents/pointerevent_pointerout_received_once-manual.html new file mode 100644 index 000000000..44191fd47 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerout_received_once-manual.html @@ -0,0 +1,59 @@ +<!doctype html> +<html> + <head> + <title>pointerout received just once</title> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <meta name="viewport" content="width=device-width"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>pointerout received just once</h1> + <h4> + Test Description: This test checks if pointerout event dispatched properly. + <ol> + <li>Put your mouse over the black rectangle. + <li>Move your mouse out of the black rectangle + </ol> + </h4> + <p> + <div id="target0" style="background:black"></div> + <script> + var pointeroutCounter = 0; + var detected_pointertypes = {}; + + setup({ explicit_done: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + // When a mouse passes through dispatches one event + // TA: 7.4 + on_event(target0, "pointerover", function (event) { + pointeroutCounter = 0; + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, "pointerout", function (event) { + pointeroutCounter++; + + setTimeout(function() { + test(function() { + assert_true(pointeroutCounter == 1, "pointerout received just once") + }, "pointerout received just once"); + done(); + }, 5000); + }); + } + </script> + <h1>Pointer Events pointerout received once test</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerover-manual.html b/dom/events/test/pointerevents/pointerevent_pointerover-manual.html new file mode 100644 index 000000000..450ed1a03 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerover-manual.html @@ -0,0 +1,53 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Dispatch pointerover. </title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="When a pointing device is moved into the hit test boundaries of an element, the pointerover event must be dispatched. "/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerover is dispatched"); // set up test harness; + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerover", function (event) { + detected_pointertypes[event.pointerType] = true; + test_pointerEvent.step(function () { + check_PointerEvent(event); + assert_equals(event.type, "pointerover", "Pointer Event received"); + }); + test_pointerEvent.done(); // complete test + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointerover.</h1> + <h4>Test Description: + When a pointing device is moved into the hit test boundaries of an element, the pointerover event must be dispatched. + </h4> + <br /> + <div id="target0"> + Use mouse, touch or pen to hover or contact this element.. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointertype_mouse-manual.html b/dom/events/test/pointerevents/pointerevent_pointertype_mouse-manual.html new file mode 100644 index 000000000..50bd056e3 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointertype_mouse-manual.html @@ -0,0 +1,66 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: If a pointer event is initiated by a mouse device, then the pointerType must be "mouse"</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="If a pointer event is initiated by a mouse device, then the pointerType must be 'mouse'."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointer event have pointerType as mouse"); // set up test harness + var eventTested = false; + + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function eventHandler(event) { + detected_pointertypes[event.pointerType] = true; + if(!eventTested && event.type == "pointerover") { + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.pointerType, "mouse", "Verify event.pointerType is 'mouse'."); + }); + eventTested = true; + } + if (event.type == "pointerup") { + test_pointerEvent.done(); // complete test + } + } + + function run() { + var target0 = document.getElementById("target0"); + + // listen for all events. + for (var i = 0; i < All_Pointer_Events.length; i++) { + on_event(target0, All_Pointer_Events[i], eventHandler); + } + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointer events with pointerType equal to "mouse"</h1> + <!-- + <h4>Test Description: + If a pointer event is initiated by a mouse device, then the pointerType must be 'mouse'. + </h4> + <br /> + --> + <div id="target0"> + Using the mouse, click this element. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointertype_pen-manual.html b/dom/events/test/pointerevents/pointerevent_pointertype_pen-manual.html new file mode 100644 index 000000000..cd018c50e --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointertype_pen-manual.html @@ -0,0 +1,64 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: If a pointer event is initiated by a pen device, then the pointerType must be "pen"</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="If a pointer event is initiated by a pen device, then the pointerType must be 'pen'."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointer event has pointerType as pen"); // set up test harness + var eventTested = false; + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function eventHandler(event) { + detected_pointertypes[event.pointerType] = true; + if(!eventTested) { + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.pointerType, "pen", "Verify event.pointerType is 'pen'."); + }); + eventTested = true; + } + if (event.type == "pointerup") { + test_pointerEvent.done(); // complete test + } + } + + function run() { + var target0 = document.getElementById("target0"); + // listen for all events. + for (var i = 0; i < All_Pointer_Events.length; i++) { + on_event(target0, All_Pointer_Events[i], eventHandler); + } + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointer events with pointerType equal to "pen"</h1> + <!-- + <h4>Test Description: + If a pointer event is initiated by a pen device, then the pointerType must be 'pen'. + </h4> + <br /> + --> + <div id="target0"> + Using pen, tap here. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointertype_touch-manual.html b/dom/events/test/pointerevents/pointerevent_pointertype_touch-manual.html new file mode 100644 index 000000000..d8034573f --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointertype_touch-manual.html @@ -0,0 +1,65 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: If a pointer event is initiated by a touch device, then the pointerType must be "touch"</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="If a pointer event is initiated by a touch device, then the pointerType must be 'touch'."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointer event has pointerType as touch"); // set up test harness + var eventTested = false; + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function eventHandler(event) { + detected_pointertypes[event.pointerType] = true; + if(!eventTested) { + check_PointerEvent(event); + test_pointerEvent.step(function () { + assert_equals(event.pointerType, "touch", "Verify event.pointerType is 'touch'."); + }); + eventTested = true; + } + if (event.type == "pointerup") { + test_pointerEvent.done(); // complete test + } + } + + function run() { + var target0 = document.getElementById("target0"); + + // listen for all events. + for (var i = 0; i < All_Pointer_Events.length; i++) { + on_event(target0, All_Pointer_Events[i], eventHandler); + } + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Dispatch pointer events with pointerType equal to "touch"</h1> + <!-- + <h4>Test Description: + If a pointer event is initiated by a touch device, then the pointerType must be 'touch'. + </h4> + <br /> + --> + <div id="target0"> + Using touch, tap here. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerup-manual.html b/dom/events/test/pointerevents/pointerevent_pointerup-manual.html new file mode 100644 index 000000000..893a5bd2e --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerup-manual.html @@ -0,0 +1,45 @@ +<!doctype html> +<html> + <head> + <title>pointerup</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>pointerup</h1> + <h4>Test Description: This test checks if pointerup event triggers. Press mouse left button and release it over the black rectangle or tap it if you are using a touchscreen.</h4> + <div id="target0" style="background:black"></div> + <script> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerup event received"); + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerup", function (event) { + detected_pointertypes[event.pointerType] = true; + test_pointerEvent.step(function () { + check_PointerEvent(event); + assert_equals(event.type, "pointerup", "Pointer Event received."); + }); + test_pointerEvent.done(); + }); + } + </script> + <h1>Pointer Events pointerup tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html b/dom/events/test/pointerevents/pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html new file mode 100644 index 000000000..a339b5ffd --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html @@ -0,0 +1,66 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: pointerup has same isPrimary as last pointerdown with the same pointerId</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="The isPrimary attribute of a pointerup event must have the same value as the isPrimary attribute of the last pointerdown event with the same pointerId attribute."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointerup has same isPrimary as last pointerdown"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var pointerup_event = null; + var pointerdown_event = null; + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerup", function (event) { + if (pointerdown_event != null) { + test_pointerEvent.step(function () { + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerup.pointerId should be the same as pointerdown.pointerId."); + assert_equals(event.isPrimary, pointerdown_event.isPrimary, "pointerup.isPrimary should be the same as pointerdown.isPrimary."); + }); + pointerup_event = event; + test_pointerEvent.done(); // complete test + } + }); + + on_event(target0, "pointerdown", function (event) { + pointerdown_event = event; + detected_pointertypes[event.pointerType] = true; + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: pointerup has the same isPrimary as last pointerdown with the same pointerId</h1> + <!-- + <h4>Test Description: + The isPrimary attribute of a pointerup event must have the same value as the isPrimary attribute of the last pointerdown event with the same pointerId attribute. + </h4> + <div id="instructions"> + Press and release a mouse button, touch contact or pen contact on this element. Only use one device per test run. + </div> + --> + <div id="target0" style="touch-action: none;"> + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_pointerup_pointertype-manual.html b/dom/events/test/pointerevents/pointerevent_pointerup_pointertype-manual.html new file mode 100644 index 000000000..95dc56451 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_pointerup_pointertype-manual.html @@ -0,0 +1,67 @@ +<!doctype html> +<html> + <head> + <title>pointerType conservation</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>pointerType conservation</h1> + <!-- + <h4>Test Description: This test checks if pointerType attribute defined properly.</h4> + <div id="instructions"> + Press and release a mouse button, touch contact or pen contact on the black rectangle. Only use one device per test run. + </div> + <p>Note: This test may be run with different pointer devices, however only one device should be used per test run. + <p> + --> + <div id="target0"></div> + <script> + var eventTested = false; + var pointerTypeGot = false; + var pointerdown_event; + var detected_pointertypes = {}; + + setup({ explicit_done: true }); + + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerover", function(event) { + detected_pointertypes[ event.pointerType ] = true; + }); + + // The pointerType attribute of a pointerup event must have the same value as the pointerType attribute of the last pointerdown event with the same pointerId attribute. + // TA: 3.1 + on_event(target0, "pointerdown", function (event) { + pointerdown_event = event; + pointerTypeGot = true; + }); + + on_event(target0, "pointerup", function (event) { + if(pointerTypeGot == true) { + if(!eventTested) { + test(function() { + assert_equals(event.pointerId, pointerdown_event.pointerId, "pointer IDs are equal: "); + assert_equals(event.pointerType, pointerdown_event.pointerType, "pointerType of pointerup event matches pointerdown event: "); + }, "pointerType is dispatched properly"); + } + done(); + } + }); + } + </script> + <h1>Pointer Events pointerType conservation tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html b/dom/events/test/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html new file mode 100644 index 000000000..f4d5573ed --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html @@ -0,0 +1,119 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: releasePointerCapture() - subsequent events follow normal hitting testing mechanisms</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <meta name="assert" content="After invoking the releasePointerCapture method on an element, subsequent events for the specified pointer must follow normal hit testing mechanisms for determining the event target"/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("lostpointercapture: subsequent events to target."); // set up test harness + var suppressedEventsFail = false; + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var captured_event; + var f_gotPointerCapture = false; + var f_lostPointerCapture = false; + + function listenerEventHandler(event) { + detected_pointertypes[event.pointerType] = true; + if (event.type == "gotpointercapture") { + f_gotPointerCapture = true; + check_PointerEvent(event); + } + else if (event.type == "lostpointercapture") { + f_lostPointerCapture = true; + f_gotPointerCapture = false; + check_PointerEvent(event); + } + else if(event.type == "pointerover" || event.type == "pointerenter" || event.type == "pointerout" || event.type == "pointerleave") { + if(!suppressedEventsFail) { + test(function() { + assert_true(false, "Suppressed events were received"); + }, "Suppressed events were received"); + suppressedEventsFail = true; + } + } + else if (event.pointerId == captured_event.pointerId) { + if (f_gotPointerCapture && event.type == "pointermove") { + // on first event received for capture, release capture + listener.releasePointerCapture(event.pointerId); + } + else { + // if any other events are received after releaseCapture, then the test fails + test_pointerEvent.step(function () { + assert_true(false, event.target.id + "-" + event.type + " should be handled by target element handler"); + }); + } + } + } + + function targetEventHandler(event) { + if (f_gotPointerCapture) { + if(event.type != "pointerout" && event.type != "pointerleave") { + test_pointerEvent.step(function () { + assert_true(false, "The Target element should not have received any events while capture is active. Event recieved:" + event.type + ". "); + }); + } + } + + if (event.type == "pointerdown") { + // pointerdown event received will be used to capture events. + listener.setPointerCapture(event.pointerId); + captured_event = event; + } + + if (f_lostPointerCapture) { + test_pointerEvent.step(function () { + assert_equals(event.pointerId, captured_event.pointerId, "pointerID is same for event captured and after release"); + }); + if (event.type == "pointerup") { + test_pointerEvent.done(); // complete test + } + } + } + + function run() { + var listener = document.getElementById("listener"); + var target0 = document.getElementById("target0"); + target0.style["touchAction"] = "none"; + + // target0 and listener - handle all events + for (var i = 0; i < All_Pointer_Events.length; i++) { + on_event(target0, All_Pointer_Events[i], targetEventHandler); + on_event(listener, All_Pointer_Events[i], listenerEventHandler); + } + } + </script> + </head> + <body onload="run()"> + <div id="listener"></div> + <h1>Pointer Event: releasePointerCapture() - subsequent events follow normal hitting testing mechanisms</h1> + <!-- + <h4> + Test Description: + After invoking the releasePointerCapture method on an element, subsequent events for the specified + pointer must follow normal hit testing mechanisms for determining the event target + </h4> + <br /> + --> + <div id="target0"> + Use mouse, touch or pen to contact here and move around. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_releasepointercapture_invalid_pointerid-manual.html b/dom/events/test/pointerevents/pointerevent_releasepointercapture_invalid_pointerid-manual.html new file mode 100644 index 000000000..e92f2d38a --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_releasepointercapture_invalid_pointerid-manual.html @@ -0,0 +1,79 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: releasePointerCapture DOMException - InvalidPointerId</title> + <meta name="assert" content="releasePointerCapture DOMException - InvalidPointerId"/> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/"/> + <link rel="help" href="http://www.w3.org/wiki/PointerEvents/TestAssertions"> + <meta name="assert" content="When the releasePointerCapture method is invoked, if the provided pointerId value does not match any of the active pointers, a DOMException with the name InvalidPointerId must be thrown."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("releasePointerCapture: DOMException InvalidPointerId"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var invalid_pointerId = 314159265358973923; + + function run() { + var target0 = document.getElementById("target0"); + target0.style["touchAction"] = "none"; + var listener = document.getElementById("listener"); + + // try to release pointer capture with an invalid id + on_event(listener, "pointermove", function (event) { + detected_pointertypes[event.pointerType] = true; + + try { + listener.releasePointerCapture(invalid_pointerId); + + test_pointerEvent.step(function () { + assert_true(false, "DOMException not thrown. Expected: InvalidPointerId should have been thrown"); + }); + } catch (e) { + test_pointerEvent.step(function () { + assert_true(e.name == "InvalidPointerId", "DOMException should be InvalidPointerId"); + }); + } + test_pointerEvent.done(); // complete test + }); + + // set pointer capture + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[event.pointerType] = true; + listener.setPointerCapture(event.pointerId); + }); + } + </script> + </head> + <body onload="run()"> + <div id="listener"></div> + <h1> Pointer Event: releasePointerCapture() DOMException - InvalidPointerId</h1> + <!-- + <h4> + Test Description: + Upon invocation of the releasePointerCapture method, if the provided pointerId value does not match any of the + active pointers, a DOMException with the name InvalidPointerId must be thrown. + </h4> + <br /> + --> + <div id="target0"> + Use the mouse, touch or pen to move over or contact this box. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_releasepointercapture_onpointercancel_touch-manual.html b/dom/events/test/pointerevents/pointerevent_releasepointercapture_onpointercancel_touch-manual.html new file mode 100644 index 000000000..9c6c680ed --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_releasepointercapture_onpointercancel_touch-manual.html @@ -0,0 +1,74 @@ +<!doctype html> +<html> + <head> + <title>Release capture on pointercancel</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body class="scrollable"> + <h1>Pointer Events Capture Test - release capture on pointercancel</h1> + <!-- + <h4> + Test Description: This test checks if setCapture/releaseCapture functions works properly. Complete the following actions: + <ol> + <li> Touch black rectangle and do not release your touch + <li> Move your touch to scroll the page. "lostpointercapture" should be logged inside of the black rectangle immediately after "pointercancel" + </ol> + </h4> + Test passes if the proper behavior of the events is observed. + --> + <div id="target0" style="background:black; color:white"></div> + + <script type='text/javascript'> + var pointercancelGot = false; + var count=0; + var detected_pointertypes = {}; + var test_pointerEvent = async_test("pointer capture is released on pointercancel"); + + var target0 = document.getElementById('target0'); + + add_completion_callback(showPointerTypes); + + window.onload = function() { + on_event(target0, 'pointerdown', function(e) { + detected_pointertypes[e.pointerType] = true; + test_pointerEvent.step(function () { + assert_equals(e.pointerType, "touch", "Test should be run using a touch as input"); + }); + isPointerCapture = true; + sPointerCapture(e); + pointercancelGot = false; + }); + + on_event(target0, 'gotpointercapture', function(e) { + log("gotpointercapture", document.getElementById('target0')); + }); + + // If the setPointerCapture method has been invoked on the pointer specified by pointerId, and the releasePointerCapture method has not been invoked, a lostpointercapture event must be dispatched to the element on which the setPointerCapture method was invoked. Furthermore, subsequent events for the specified pointer must follow normal hit testing mechanisms for determining the event target. + // TA: 4.4 + on_event(target0, 'lostpointercapture', function(e) { + log("lostpointercapture", document.getElementById('target0')); + test_pointerEvent.step(function () { + assert_true(pointercancelGot, "pointercancel was received before lostpointercapture"); + }); + test_pointerEvent.done(); + }); + + on_event(target0, 'pointercancel', function(e) { + log("pointercancel", target0); + pointercancelGot = true; + }); + } + </script> + <h1>Pointer Events Capture Test</h1> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_releasepointercapture_onpointerup_mouse-manual.html b/dom/events/test/pointerevents/pointerevent_releasepointercapture_onpointerup_mouse-manual.html new file mode 100644 index 000000000..7ba33d9be --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_releasepointercapture_onpointerup_mouse-manual.html @@ -0,0 +1,82 @@ +<!doctype html> +<html> + <head> + <title>Release capture on pointerup</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body> + <h1>Pointer Events Capture Test - release capture on pointerup</h1> + <!-- + <h4> + Test Description: This test checks if setCapture/releaseCapture functions works properly. Complete the following actions: + <ol> + <li> Press and hold left mouse button over "Set Capture" button + <li> Release left mouse button anywhere over the document. "lostpointercapture" should be logged inside of the black rectangle immediately after "pointerup" + </ol> + </h4> + Test passes if the proper behavior of the events is observed. + --> + <div id="target0" style="background:black; color:white"></div> + <br> + <input type="button" id="btnCapture" value="Set Capture"> + <script type='text/javascript'> + var isPointerCapture = false; + var pointerupGot = false; + var count=0; + + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var target0 = document.getElementById('target0'); + var captureButton = document.getElementById('btnCapture'); + + setup({ explicit_done: true }); + + window.onload = function() { + on_event(captureButton, 'pointerdown', function(e) { + detected_pointertypes[e.pointerType] = true; + if(isPointerCapture == false) { + isPointerCapture = true; + sPointerCapture(e); + pointerupGot = false; + } + }); + + on_event(target0, 'gotpointercapture', function(e) { + log("gotpointercapture", document.getElementById('target0')); + }); + + // If the setPointerCapture method has been invoked on the pointer specified by pointerId, + // and the releasePointerCapture method has not been invoked,a lostpointercapture event must be + // dispatched to the element on which the setPointerCapture method was invoked. Furthermore, + // subsequent events for the specified pointer must follow normal hit testing mechanisms for + // determining the event target. + // TA: 3.7 + on_event(target0, 'lostpointercapture', function(e) { + test(function() { + assert_true(pointerupGot, "pointerup was received before lostpointercapture") + }, "pointerup was received before lostpointercapture"); + log("lostpointercapture", document.getElementById('target0')); + isPointerCapture = false; + done(); + }); + + on_event(target0, 'pointerup', function(e) { + log("pointerup", target0); + pointerupGot = true; + }); + } + </script> + <h1>Pointer Events Capture Test</h1> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_releasepointercapture_release_right_after_capture-manual.html b/dom/events/test/pointerevents/pointerevent_releasepointercapture_release_right_after_capture-manual.html new file mode 100644 index 000000000..d5aabddfa --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_releasepointercapture_release_right_after_capture-manual.html @@ -0,0 +1,64 @@ +<!doctype html> +<html> + <head> + <title>Release pointer capture right after setpointercapture</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + var test_setPointerCapture = async_test("Release pointer capture right after setpointercapture"); + + function run() { + var target0 = document.getElementById("target0"); + var target1 = document.getElementById("target1"); + + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[event.pointerType] = true; + target0.setPointerCapture(event.pointerId); + target0.releasePointerCapture(event.pointerId); + assert_equals(target0.hasPointerCapture(e.pointerId), false, "After target0.releasePointerCapture, target0.hasPointerCapture should be false"); + }); + + on_event(target0, "gotpointercapture", function (event) { + test_setPointerCapture.step(function () { + assert_true(false, "target0 shouldn't receive gotpointercapture"); + }); + }); + + on_event(target0, "lostpointercapture", function (event) { + test_setPointerCapture.step(function () { + assert_true(false, "target0 shouldn't receive lostpointercapture"); + }); + }); + + on_event(target0, "pointerup", function (event) { + test_setPointerCapture.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Release pointer capture right after setpointercapture</h1> + <!-- + <h4>Test Description: + When calling releasePointer right after setPointerCapture method is invoked, the pending pointer capture should be cleared and no element should receive gotpointercapture and lostpointercapture events + <ol> + <li>Press and hold left mouse button over black box + <li>Move mouse and release mouse button + </ol> + </h4> + --> + <br> + <div id="target0" touch-action:none></div> + <div id="target1" touch-action:none></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_setpointercapture_disconnected-manual.html b/dom/events/test/pointerevents/pointerevent_setpointercapture_disconnected-manual.html new file mode 100644 index 000000000..00875693e --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_setpointercapture_disconnected-manual.html @@ -0,0 +1,58 @@ +<!doctype html> +<html> + <head> + <title>setPointerCapture() throws on disconnected node</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + var test_setPointerCapture = async_test("setPointerCapture: DOMException InvalidStateError"); + + function run() { + var target0 = document.getElementById("target0"); + var target1 = document.getElementById("target1"); + + target1.parentNode.removeChild(target1); + + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[ event.pointerType ] = true; + try { + target1.setPointerCapture(event.pointerId); + + test_setPointerCapture.step(function() { + assert_unreached("DOMException: InvalidStateError should have been thrown."); + }); + } catch (e) { + // TA: 13.4 + test_setPointerCapture.step(function() { + assert_equals(e.name, "InvalidStateError", "DOMException should be InvalidStateError"); + }); + } + test_setPointerCapture.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: DOMException InvalidStateError</h1> + <!-- + <h4>Test Description: + When the setPointerCapture method is invoked, if the target node does not participate in its ownerDocument's tree, a DOMException with the name InvalidStateError must be thrown. + </h4> + <br> + --> + <div id="target0"> + Use the mouse, touch or pen to contact this box. + </div> + <div id="target1"></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_setpointercapture_inactive_button_mouse-manual.html b/dom/events/test/pointerevents/pointerevent_setpointercapture_inactive_button_mouse-manual.html new file mode 100644 index 000000000..72504bc3b --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_setpointercapture_inactive_button_mouse-manual.html @@ -0,0 +1,61 @@ +<!doctype html> +<html> + <head> + <title>setPointerCapture + inactive button state</title> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <meta name="viewport" content="width=device-width"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body onload="run()"> + <h1>setPointerCapture</h1> + <!-- + <h4> + Test Description: This test checks if setPointerCapture works properly. + <ol> + <li>Put your mouse over the black rectangle + <li>Move you mouse out to complete the test + </ol> + </h4> + <p> + --> + <div id="target0" style="background:black; color:white;"></div> + <script> + var detected_pointertypes = {}; + + var captureGot = false; + + setup({ explicit_done: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + on_event(target0, "pointerover", function (event) { + detected_pointertypes[event.pointerType] = true; + target0.setPointerCapture(event.pointerId); + }); + + // When the setPointerCapture method is invoked, if the specified pointer is not in active button state, then the method must have no effect on subsequent pointer events. + // TA: 13.2 + on_event(target0, "pointerout", function (event) { + test(function() { + assert_false(captureGot, "pointer capture is not set while button state is inactive") + }, "pointer capture is not set while button state is inactive"); + done(); + }); + + on_event(target0, 'gotpointercapture', function(e) { + captureGot = true; + }); + } + </script> + <h1>Pointer Events setPointerCapture Tests</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_setpointercapture_invalid_pointerid-manual.html b/dom/events/test/pointerevents/pointerevent_setpointercapture_invalid_pointerid-manual.html new file mode 100644 index 000000000..711afc615 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_setpointercapture_invalid_pointerid-manual.html @@ -0,0 +1,68 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: gotPiontercapture is fired first.</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/" /> + <link rel="help" href="http://www.w3.org/wiki/PointerEvents/TestAssertions"> + <meta name="assert" content="When the setPointerCapture method is invoked, if the provided pointerId value does not match any of the active pointers, a DOMException with the name InvalidPointerId must be thrown." /> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <!-- /resources/testharness.js --> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <!-- Additional helper script for common checks across event types --> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + var test_pointerEvent = async_test("setPointerCapture: DOMException InvalidPointerId"); // set up test harness + // showPointerTypes is defined in pointerevent_support.js + // Requirements: the callback function will reference the test_pointerEvent object and + // will fail unless the async_test is created with the var name "test_pointerEvent". + add_completion_callback(showPointerTypes); + + var INVALID_POINTERID = -39548; + + function run() { + var target0 = document.getElementById("target0"); + target0.style["touchAction"] = "none"; + var listener = document.getElementById("complete-notice"); + + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[event.pointerType] = true; + + try { + listener.setPointerCapture(INVALID_POINTERID); + + test_pointerEvent.step(function () { + assert_true(false, "DOMException: InvalidPointerId should have been thrown."); + }); + } catch (e) { + test_pointerEvent.step(function () { + assert_equals(e.name, "InvalidPointerId", "DOMException should be InvalidPointerId"); + }); + } + test_pointerEvent.done(); // complete test + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: DOMException InvalidPointerId</h1> + <!-- + <h4>Test Description: + When the setPointerCapture method is invoked, if the provided pointerId value does not match any of the active pointers, a DOMException with the name InvalidPointerId must be thrown. + </h4> + <br /> + --> + <div id="target0"> + Use the mouse, touch or pen to contact this box. + </div> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_setpointercapture_override_pending_capture_element-manual.html b/dom/events/test/pointerevents/pointerevent_setpointercapture_override_pending_capture_element-manual.html new file mode 100644 index 000000000..e4bf4ddea --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_setpointercapture_override_pending_capture_element-manual.html @@ -0,0 +1,66 @@ +<!doctype html> +<html> + <head> + <title>Test overriding the pending pointer capture element</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + var test_setPointerCapture = async_test("setPointerCapture: override the pending pointer capture element"); + + function run() { + var target0 = document.getElementById("target0"); + var target1 = document.getElementById("target1"); + + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[event.pointerType] = true; + target0.setPointerCapture(event.pointerId); + test_setPointerCapture.step(function () { + assert_equals(target0.hasPointerCapture(event.pointerId), true, "Set capture to target0, target0.hasPointerCapture should be true"); + }); + target1.setPointerCapture(event.pointerId); + test_setPointerCapture.step(function () { + assert_equals(target0.hasPointerCapture(event.pointerId), false, "Set capture to target1, target0.hasPointerCapture should be false"); + assert_equals(target1.hasPointerCapture(event.pointerId), true, "Set capture to target1, target1.hasPointerCapture should be true"); + }); + }); + + on_event(target0, "gotpointercapture", function (event) { + assert_true(false, "target0 shouldn't receive gotpointercapture"); + }); + + on_event(target1, "gotpointercapture", function (event) { + assert_true(true, "target1 should receive gotpointercapture"); + }); + + on_event(target1, "pointerup", function (event) { + test_setPointerCapture.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: Test overriding the pending pointer capture element</h1> + <!-- + <h4>Test Description: + After an element setPointerCapture, if another element also setPointerCapture and override it, the old pending pointer capture element shouldn't receive any gotpointercapture or lostpointercapture event + <ol> + <li>Press and hold left mouse button over black box + <li>Move mouse and release mouse button + </ol> + </h4> + --> + <br> + <div id="target0" touch-action:none></div> + <div id="target1" touch-action:none></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_setpointercapture_relatedtarget-manual.html b/dom/events/test/pointerevents/pointerevent_setpointercapture_relatedtarget-manual.html new file mode 100644 index 000000000..ede53af5d --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_setpointercapture_relatedtarget-manual.html @@ -0,0 +1,103 @@ +<!doctype html> +<html> + <head> + <title>Set/Release capture + relatedTarget</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + </head> + <body> + <!-- + <h1>Pointer Events Capture Test - capture and relatedTarget</h1> + <h4> + Test Description: This test checks if setCapture/releaseCapture functions works properly. Complete the following actions: + <ol> + <li> Put your mouse over the lower rectangle. pointerover should be received for the purple rectangle + <li> Press and hold left mouse button over "Set Capture" button + <li> Put your mouse over the upper rectangle. pointerover should be received for the black rectangle + <li> Release left mouse button to complete the test. + </ol> + </h4> + Test passes if the proper behavior of the events is observed. + --> + <div id="target0" style="background:black; color:white"></div> + <br> + <div id="target1" style="background:purple; color:white"></div> + <br> + <input type="button" id="btnCapture" value="Set Capture"> + <script type='text/javascript'> + var isPointerCapture = false; + var isPointeroverGot = false; + var count=0; + + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var target0 = document.getElementById('target0'); + var target1 = document.getElementById('target1'); + var captureButton = document.getElementById('btnCapture'); + + setup({ explicit_done: true }); + + window.onload = function() { + on_event(captureButton, 'pointerdown', function(e) { + if(isPointerCapture == false) { + isPointerCapture = true; + sPointerCapture(e); + } + else { + isPointerCapture = false; + rPointerCapture(e); + } + }); + + on_event(target0, 'gotpointercapture', function(e) { + log("gotpointercapture", document.getElementById('target0')); + }); + + on_event(target0, 'lostpointercapture', function(e) { + log("lostpointercapture", document.getElementById('target0')); + isPointerCapture = false; + }); + + run(); + } + + function run() { + // After invoking the setPointerCapture method on an element, subsequent pointer events for the specified pointer must be targeted at that element. + // Additionally, the relatedTarget property of all such pointer events must be set to null. + // TA: 13.3 + on_event(target0, "pointerover", function (event) { + log("pointerover", document.getElementById('target0')); + if(isPointerCapture && isPointeroverGot) { + test(function() { + assert_true(event.relatedTarget==null, "relatedTarget is null when the capture is set") + }, "relatedTarget is null when the capture is set. relatedTarget is " + event.relatedTarget); + done(); + } + }); + + on_event(target1, "pointerover", function (event) { + detected_pointertypes[ event.pointerType ] = true; + if(!isPointeroverGot) { + test(function() { + assert_true(isPointerCapture==false, "pointerover shouldn't trigger for this target when capture is enabled"); + }, "pointerover shouldn't trigger for the purple rectangle while the black rectangle has capture"); + isPointeroverGot = true; + log("pointerover", document.getElementById('target1')); + } + }); + } + </script> + <h1>Pointer Events Capture Test</h1> + <div id="complete-notice"> + <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>Refresh the page to run the tests again with a different pointer type.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_setpointercapture_to_same_element_twice-manual.html b/dom/events/test/pointerevents/pointerevent_setpointercapture_to_same_element_twice-manual.html new file mode 100644 index 000000000..248c54818 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_setpointercapture_to_same_element_twice-manual.html @@ -0,0 +1,65 @@ +<!doctype html> +<html> + <head> + <title>setPointerCapture() to the element which already captured the pointer</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script src="pointerevent_support.js"></script> + <script src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + var test_setPointerCapture = async_test("setPointerCapture: set to the element which already captured the pointer"); + var got_pointer_capture = false; + + function run() { + var target0 = document.getElementById("target0"); + var target1 = document.getElementById("target1"); + + on_event(target0, "pointerdown", function (event) { + detected_pointertypes[event.pointerType] = true; + target0.setPointerCapture(event.pointerId); + }); + + on_event(target0, "gotpointercapture", function (event) { + test_setPointerCapture.step(function () { + assert_equals(got_pointer_capture, false, "Target0 should receive gotpointercapture at the first time it captured the pointer"); + assert_equals(target0.hasPointerCapture(event.pointerId), true, "Target 0 received gotpointercapture, target0.hasPointerCapture should be true"); + }); + got_pointer_capture = true; + + target0.setPointerCapture(event.pointerId); + test_setPointerCapture.step(function () { + assert_equals(target0.hasPointerCapture(event.pointerId), true, "Set capture to target0, target0.hasPointerCapture should be true"); + assert_equals(target1.hasPointerCapture(event.pointerId), false, "Set capture to target0, target1.hasPointerCapture should be false"); + }); + }); + + on_event(target0, "pointerup", function (event) { + test_setPointerCapture.done(); + }); + } + </script> + </head> + <body onload="run()"> + <h1>Pointer Event: setPointerCapture to the element which already captured the pointer</h1> + <!-- + <h4>Test Description: + When the setPointerCapture method is invoked, if the target element had already captured the pointer, it should not trigger any gotpointercapture or lostpointercapture event + <ol> + <li>Press and hold left mouse button over black box + <li>Move mouse and release mouse button + </ol> + </h4> + --> + <br> + <div id="target0" touch-action:none></div> + <div id="target1" touch-action:none></div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_styles.css b/dom/events/test/pointerevents/pointerevent_styles.css new file mode 100644 index 000000000..d2acf940d --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_styles.css @@ -0,0 +1,93 @@ +.spacer { +height: 100px; +} + +#square1 { +background: black; +top: 150px; +left: 100px; +} + +.square { +height: 20px; +width: 20px; +position: absolute; +padding: 0px; +} + +#target0 { +background: black; +color: white; +white-space: nowrap; +overflow-y: auto; +overflow-x: auto; +} + +#target1 { +background: purple; +color: white; +white-space: nowrap; +overflow-y: auto; +overflow-x: auto; +} + +.touchActionNone { +touch-action: none; +} + +#innerframe { +width: 90%; +margin: 10px; +margin-left: 10%; +height: 200px; +} + +.scroller { +width: 700px; +height: 430px; +margin: 20px; +overflow: auto; +background: black; +} + +.scroller > div { +height: 1000px; +width: 1000px; +color: white; +} + +.scroller > div div { +height: 100%; +width: 100%; +color: white; +} + +div { +margin: 0em; +padding: 2em; +} + +#complete-notice { +background: #afa; +border: 1px solid #0a0; +display: none; +} + +#pointertype-log { +font-weight: bold; +} + +#event-log { +font-weight: bold; +} + +#listener { +background: orange; +border: 1px solid orange; +position: absolute; +top: -100px; +} + +body.scrollable { +min-height: 5000px; +} diff --git a/dom/events/test/pointerevents/pointerevent_support.js b/dom/events/test/pointerevents/pointerevent_support.js new file mode 100644 index 000000000..4067d53dd --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_support.js @@ -0,0 +1,185 @@ +var All_Pointer_Events = [ + "pointerdown", + "pointerup", + "pointercancel", + "pointermove", + "pointerover", + "pointerout", + "pointerenter", + "pointerleave", + "gotpointercapture", + "lostpointercapture"]; + +// Check for conformance to PointerEvent interface +// TA: 1.1, 1.2, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11, 1.12, 1.13 +function check_PointerEvent(event) { + var pointerTestName = event.pointerType + ' ' + event.type; + test(function () { + assert_true(event instanceof PointerEvent, "event is a PointerEvent event"); + }, pointerTestName + " event is a PointerEvent event"); + + + // Check attributes for conformance to WebIDL: + // * attribute exists + // * has proper type + // * if the attribute is "readonly", it cannot be changed + // TA: 1.1, 1.2 + var idl_type_check = { + "long": function (v) { return typeof v === "number" && Math.round(v) === v; }, + "float": function (v) { return typeof v === "number"; }, + "string": function (v) { return typeof v === "string"; }, + "boolean": function (v) { return typeof v === "boolean" } + }; + [ + ["readonly", "long", "pointerId"], + ["readonly", "float", "width"], + ["readonly", "float", "height"], + ["readonly", "float", "pressure"], + ["readonly", "long", "tiltX"], + ["readonly", "long", "tiltY"], + ["readonly", "string", "pointerType"], + ["readonly", "boolean", "isPrimary"], + ["readonly", "long", "detail", 0] + ].forEach(function (attr) { + var readonly = attr[0]; + var type = attr[1]; + var name = attr[2]; + var value = attr[3]; + + // existence check + test(function () { + assert_true(name in event, name + " attribute in " + event.type + " event"); + }, pointerTestName + "." + name + " attribute exists"); + + // readonly check + if (readonly === "readonly") { + test(function () { + assert_readonly(event.type, name, event.type + "." + name + " cannot be changed"); + }, pointerTestName + "." + name + " is readonly"); + } + + // type check + test(function () { + assert_true(idl_type_check[type](event[name]), name + " attribute of type " + type); + }, pointerTestName + "." + name + " IDL type " + type + " (JS type was " + typeof event[name] + ")"); + + // value check if defined + if (value != undefined) { + test(function () { + assert_equals(event[name], value, name + " attribute value"); + }, pointerTestName + "." + name + " value is " + value + "."); + } + }); + + + // Check the pressure value + // TA: 1.6, 1.7, 1.8 + test(function () { + // TA: 1.6 + assert_greater_than_equal(event.pressure, 0, "pressure is greater than or equal to 0"); + assert_less_than_equal(event.pressure, 1, "pressure is less than or equal to 1"); + + + // TA: 1.7, 1.8 + if (event.pointerType === "mouse") { + if (event.buttons === 0) { + assert_equals(event.pressure, 0, "pressure is 0 for mouse with no buttons pressed"); + } else { + assert_equals(event.pressure, 0.5, "pressure is 0.5 for mouse with a button pressed"); + } + } + }, pointerTestName + ".pressure value is valid"); + + + // Check mouse-specific properties + if (event.pointerType === "mouse") { + // TA: 1.9, 1.10, 1.13 + test(function () { + assert_equals(event.tiltX, 0, event.type + ".tiltX is 0 for mouse"); + assert_equals(event.tiltY, 0, event.type + ".tiltY is 0 for mouse"); + assert_true(event.isPrimary, event.type + ".isPrimary is true for mouse"); + }, pointerTestName + " properties for pointerType = mouse"); + // Check properties for pointers other than mouse + } +} + +function showPointerTypes() { + var complete_notice = document.getElementById("complete-notice"); + var pointertype_log = document.getElementById("pointertype-log"); + var pointertypes = Object.keys(detected_pointertypes); + pointertype_log.innerHTML = pointertypes.length ? + pointertypes.join(",") : "(none)"; + complete_notice.style.display = "block"; +} + +function showLoggedEvents() { + var event_log_elem = document.getElementById("event-log"); + event_log_elem.innerHTML = event_log.length ? event_log.join(", ") : "(none)"; + + var complete_notice = document.getElementById("complete-notice"); + complete_notice.style.display = "block"; +} + +function log(msg, el) { + if (++count > 10){ + count = 0; + el.innerHTML = ' '; + } + el.innerHTML = msg + '; ' + el.innerHTML; +} + + function failOnScroll() { + assert_true(false, + "scroll received while shouldn't"); +} + +function updateDescriptionNextStep() { + document.getElementById('desc').innerHTML = "Test Description: Try to scroll text RIGHT."; +} + +function updateDescriptionComplete() { + document.getElementById('desc').innerHTML = "Test Description: Test complete"; +} + +function updateDescriptionSecondStepTouchActionElement(target, scrollReturnInterval) { + window.setTimeout(function() { + objectScroller(target, 'up', 0);} + , scrollReturnInterval); + document.getElementById('desc').innerHTML = "Test Description: Try to scroll element RIGHT moving your outside of the red border"; +} + +function updateDescriptionThirdStepTouchActionElement(target, scrollReturnInterval) { + window.setTimeout(function() { + objectScroller(target, 'left', 0);} + , scrollReturnInterval); + document.getElementById('desc').innerHTML = "Test Description: Try to scroll element DOWN then RIGHT starting your touch inside of the element. Then tap complete button"; +} + +function updateDescriptionFourthStepTouchActionElement(target, scrollReturnInterval) { + document.getElementById('desc').innerHTML = "Test Description: Try to scroll element RIGHT starting your touch inside of the element"; +} + +function objectScroller(target, direction, value) { + if (direction == 'up') { + target.scrollTop = 0; + } else if (direction == 'left') { + target.scrollLeft = 0; + } +} + +function sPointerCapture(e) { + try { + target0.setPointerCapture(e.pointerId); + } + catch(e) { + } +} + +function rPointerCapture(e) { + try { + captureButton.value = 'Set Capture'; + target0.releasePointerCapture(e.pointerId); + } + catch(e) { + } +} diff --git a/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_click.html b/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_click.html new file mode 100644 index 000000000..89508ee92 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_click.html @@ -0,0 +1,104 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Suppress compatibility mouse events on click</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Google" href="http://www.google.com "/> + <meta name="assert" content="When a pointerdown is canceled, a click/tap shouldn't fire any compatibility mouse events."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var test_pointerEvent = async_test("Suppress compat mouse events on click"); + add_completion_callback(end_of_test); + + var detected_pointertypes = {}; + var event_log = []; + + function end_of_test() { + showLoggedEvents(); + showPointerTypes(); + } + + function end_of_interaction() { + test(function () { + assert_equals(event_log.join(", "), + "mousedown@target1, mouseup@target1"); + }, "Event log"); + + test_pointerEvent.done(); // complete test + } + + function run() { + on_event(document.getElementById("done"), "click", end_of_interaction); + + var target_list = ["target0", "target1"]; + var pointer_event_list = ["pointerdown"]; + var mouse_event_list = ["mousedown", "mouseup"]; + + target_list.forEach(function(targetId) { + var target = document.getElementById(targetId); + + pointer_event_list.forEach(function(eventName) { + on_event(target, eventName, function (event) { + detected_pointertypes[event.pointerType] = true; + var label = event.type + "@" + targetId; + + test(function () { + assert_true(event.isPrimary); + }, "primary pointer " + label); + + if (label === "pointerdown@target0") + event.preventDefault(); + }); + }); + + mouse_event_list.forEach(function(eventName) { + on_event(target, eventName, function (event) { + event_log.push(event.type + "@" + targetId); + }); + }); + }); + } + </script> + <style> + #target0, #target1 { + margin: 20px; + } + + #done { + margin: 20px; + border: 2px solid black; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Event: Suppress compatibility mouse events on click</h1> + <h4> + When a pointerdown is canceled, a click/tap shouldn't fire any compatibility mouse events. + </h4> + <!-- + <ol> + <li> Click or tap on Target0.</li> + <li> Click or tap on Target1.</li> + <li> Click Done.</li> + </ol> + --> + <div id="target0"> + Target0 + </div> + <div id="target1"> + Target1 + </div> + <div id="done"> + Done + </div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>The following events were logged: <span id="event-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_drag_mouse.html b/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_drag_mouse.html new file mode 100644 index 000000000..337d6ac5f --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_drag_mouse.html @@ -0,0 +1,117 @@ +<!doctype html> +<html> + <head> + <title>Pointer Event: Suppress compatibility mouse events on drag</title> + <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> + <link rel="author" title="Google" href="http://www.google.com "/> + <meta name="assert" content="When a pointerdown is canceled, a mouse drag shouldn't fire any compatibility mouse events."/> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <!--script src="/resources/testharnessreport.js"></script--> + <script type="text/javascript" src="pointerevent_support.js"></script> + <script type="text/javascript" src="mochitest_support_internal.js"></script> + <script type="text/javascript"> + var test_pointerEvent = async_test("Suppress compat mouse events on drag"); + add_completion_callback(end_of_test); + + var detected_pointertypes = {}; + var event_log = []; + + function end_of_test() { + showLoggedEvents(); + showPointerTypes(); + } + + var include_next_mousemove = false; + + // Limits logging/testing of mousemove. + function drop_event(event_type) { + return (event_type == "mousemove" && !include_next_mousemove); + } + + function end_of_interaction() { + test(function () { + assert_equals(event_log.join(", "), + "mousedown@target1, mousemove@target1, mouseup@target1"); + }, "Event log"); + + test_pointerEvent.done(); // complete test + } + + function run() { + on_event(document.getElementById("done"), "click", end_of_interaction); + + var target_list = ["target0", "target1"]; + var pointer_event_list = ["pointerdown"]; + var mouse_event_list = ["mousedown", "mouseup", "mousemove"]; + + target_list.forEach(function(targetId) { + var target = document.getElementById(targetId); + + pointer_event_list.forEach(function(eventName) { + on_event(target, eventName, function (event) { + detected_pointertypes[event.pointerType] = true; + var label = event.type + "@" + targetId; + + test(function () { + assert_true(event.isPrimary); + }, "primary pointer " + label); + + if (label === "pointerdown@target0") + event.preventDefault(); + }); + }); + + mouse_event_list.forEach(function(eventName) { + on_event(target, eventName, function (event) { + if (drop_event(event.type)) + return; + + event_log.push(event.type + "@" + targetId); + + include_next_mousemove = (event.type == "mousedown"); + }); + }); + }); + } + </script> + <style> + #target0, #target1 { + margin: 20px; + touch-action: none; + } + + #done { + margin: 20px; + border: 2px solid black; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Event: Suppress compatibility mouse events on drag</h1> + <!-- + <h4> + When a pointerdown is canceled, a mouse drag shouldn't fire any compatibility mouse events. + </h4> + <ol> + <li> Drag mouse within Target0 & release.</li> + <li> Drag mouse within Target1 & release.</li> + <li> Click Done.</li> + </ol> + --> + <div id="target0"> + Target0 + </div> + <div id="target1"> + Target1 + </div> + <div id="done"> + Done + </div> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + <p>The following events were logged: <span id="event-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html> diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-auto-css_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-auto-css_touch-manual.html new file mode 100644 index 000000000..f5e9d12c3 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-auto-css_touch-manual.html @@ -0,0 +1,129 @@ +<!doctype html> +<html> + <head> + <title>touch-action: auto</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 430px; + touch-action: auto; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll text DOWN. Wait for description update. Expected: pan enabled</h4> + <p>Note: this test is for touch-devices only</p> + <div id="target0"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <script type='text/javascript'> + var detected_pointertypes = {}; + + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + var test_touchaction = async_test("touch-action attribute test"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(target0, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(event.pointerType, "touch", "wrong pointer type was detected: "); + }); + }); + + on_event(target0, 'scroll', function(event) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction.step(function () { + yScrollIsReceived = true; + assert_true(true, "y-scroll received."); + }); + updateDescriptionNextStep(); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction.done(); + updateDescriptionComplete(); + } + }); + } + </script> + <h1>touch-action: auto</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-button-test_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-button-test_touch-manual.html new file mode 100644 index 000000000..8268f2ff7 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-button-test_touch-manual.html @@ -0,0 +1,109 @@ +<!doctype html> +<html> + <head> + <title>Button touch-action test</title> + <meta name="assert" content="TA15.11 -The touch-action CSS property applies to button elements."> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + height: 150px; + width: 200px; + overflow-y: auto; + background: black; + padding: 100px; + position: relative; + } + button { + touch-action: none; + width: 350px; + height: 350px; + border: 2px solid red; + } + </style> + </head> + <body onload="run()"> + <h2>Pointer Events touch-action attribute support</h2> + <h4 id="desc">Test Description: Try to scroll black element DOWN moving your touch outside of the red border. Wait for description update.</h4> + <p>Note: this test is for touch only</p> + <div id="target0"> + <button id="testButton">Test Button</button> + </div> + <br> + <input type="button" id="btnComplete" value="Complete test"> + + <script type='text/javascript'> + var detected_pointertypes = {}; + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + var scrollReturnInterval = 1000; + var isFirstPart = true; + setup({ explicit_timeout: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + //TA 15.11 + var test_touchaction_div = async_test("touch-action attribute test out of element"); + var test_touchaction_button = async_test("touch-action attribute test in element"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(btnComplete, 'click', function(event) { + test_touchaction_button.step(function() { + assert_equals(target0.scrollLeft, 0, "button scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "button scroll y offset should be 0 in the end of the test"); + assert_true(xScrollIsReceived && yScrollIsReceived, "target0 x and y scroll offsets should be greater than 0 after first two interactions (outside red border) respectively"); + }); + test_touchaction_button.done(); + updateDescriptionComplete(); + }); + + on_event(btnComplete, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, 'scroll', function(event) { + if(isFirstPart) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction_div.step(function () { + yScrollIsReceived = true; + }); + updateDescriptionSecondStepTouchActionElement(target0, scrollReturnInterval); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction_div.done(); + updateDescriptionThirdStepTouchActionElement(target0, scrollReturnInterval); + setTimeout(function() { + isFirstPart = false; + }, 2 * scrollReturnInterval); // avoid immediate triggering while scroll is still being performed + } + } + else { + test_touchaction_button.step(failOnScroll, "scroll received while shouldn't"); + } + }); + } + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-illegal.html b/dom/events/test/pointerevents/pointerevent_touch-action-illegal.html new file mode 100644 index 000000000..5fe617984 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-illegal.html @@ -0,0 +1,67 @@ +<!doctype html> +<html> + <head> + <title>touch-action: illegal</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 50px; + touch-action: pan-x none; + } + #target1 { + width: 700px; + height: 50px; + background: black; + margin-top: 5px; + touch-action: pan-y none; + } + #target2 { + width: 700px; + height: 50px; + background: black; + margin-top: 5px; + touch-action: auto none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Test will automatically check behaviour of following combinations: 'pan-x none', 'pan-y none', 'auto none'</h4> + <div id="target0"></div> + <div id="target1"></div> + <div id="target2"></div> + <script type='text/javascript'> + var detected_pointertypes = {}; + + setup({ explicit_done: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById('target0'); + var target1 = document.getElementById('target1'); + var target2 = document.getElementById('target2'); + + test(function() { + assert_true(getComputedStyle(target0).touchAction == 'auto', "'pan-x none' is corrected properly"); + }, "'pan-x none' is corrected properly"); + test(function() { + assert_true(getComputedStyle(target1).touchAction == 'auto', "'pan-y none' is corrected properly"); + }, "'pan-y none' is corrected properly"); + test(function() { + assert_true(getComputedStyle(target2).touchAction == 'auto', "'auto none' is corrected properly"); + }, "'auto none' is corrected properly"); + done(); + } + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch-manual.html new file mode 100644 index 000000000..364c9c11f --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch-manual.html @@ -0,0 +1,117 @@ +<!doctype html> +<html> + <head> + <title>touch-action: parent > child: auto > child: none</title> + <meta name="assert" content="TA15.5 - when a user touches an element, the effect of that touch is determined by the value of the touch-action property and the default touch behaviors on the element and its ancestors. Scrollable-Parent, Child: `auto`, Grand-Child: `none`"> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + .scroller > div { + touch-action: auto; + } + .scroller > div div { + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: no panning.</h4> + <p>Note: this test is for touch-devices only</p> + <div class="scroller" id="target0"> + <div> + <div> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + </div> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var test_touchaction = async_test("touch-action attribute test"); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if touch-action attribute works properly for embedded divs + // Scrollable-Parent, Child: `auto`, Grand-Child: `none` + // TA: 15.5 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + + on_event(target0, 'scroll', function(event) { + test_touchaction.step(failOnScroll, "scroll received while touch-action is none"); + }); + } + </script> + <h1>behaviour: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-none_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-none_touch-manual.html new file mode 100644 index 000000000..786819b85 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-none_touch-manual.html @@ -0,0 +1,112 @@ +<!doctype html> +<html> + <head> + <title>touch-action: child: none</title> + <meta name="assert" content="TA15.9 - when a user touches an element, the effect of that touch is determined by the value of the touch-action property and the default touch behaviors on the element and its ancestors. Scrollable-Parent, Child: `none`"> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + .scroller > div { + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: no panning</h4> + <p>Note: this test is for touch-devices only</p> + <div class="scroller" id="target0"> + <div> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var test_touchaction = async_test("touch-action attribute test"); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if touch-action attribute works properly for embedded divs + // + // TA: 15.9 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + + on_event(target0, 'scroll', function(event) { + test_touchaction.step(failOnScroll, "scroll received while touch-action is none"); + }); + } + </script> + <h1>behaviour: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual.html new file mode 100644 index 000000000..09a97e3cb --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual.html @@ -0,0 +1,112 @@ +<!doctype html> +<html> + <head> + <title>touch-action: parent > child: pan-x > child: pan-x</title> + <meta name="assert" content="TA15.6 - when a user touches an element, the effect of that touch is determined by the value of the touch-action property and the default touch behaviors on the element and its ancestors. Scrollable-Parent, Child: `pan-x`, Grand-Child: `pan-x`"> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + .scroller > div { + touch-action: pan-x; + } + .scroller > div div { + touch-action: pan-x; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: only pans in x direction.</h4> + <p>Note: this test is for touch-devices only</p> + <div class="scroller" id="target0"> + <div> + <div> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + </div> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + var test_touchaction = async_test("touch-action attribute test"); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if touch-action attribute works properly for embedded divs + // + // TA: 15.6 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_not_equals(target0.scrollLeft, 0, "scroll x offset should not be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + } + </script> + <h1>behaviour: pan-x</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual.html new file mode 100644 index 000000000..527e55374 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual.html @@ -0,0 +1,117 @@ +<!doctype html> +<html> + <head> + <title>touch-action: parent > child: pan-x > child: pan-y</title> + <meta name="assert" content="TA15.13 - Touch action inherits child 'pan-x' -> child 'pan-y' test"> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + .scroller > div { + touch-action: pan-x; + } + .scroller > div div { + touch-action: pan-y; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: no panning/zooming/etc.</h4> + <p>Note: this test is for touch-devices only</p> + <div class="scroller" id="target0"> + <div> + <div> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + </div> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var test_touchaction = async_test("touch-action attribute test"); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if touch-action attribute works properly for embedded divs + // Scrollable-Parent, Child: `pan-x`, Grand-Child: `pan-y` + // TA: 15.13 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + + on_event(target0, 'scroll', function(event) { + test_touchaction.step(failOnScroll, "scroll received while touch-action is none"); + }); + } + </script> + <h1>behaviour: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-inherit_highest-parent-none_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_highest-parent-none_touch-manual.html new file mode 100644 index 000000000..b13013c43 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_highest-parent-none_touch-manual.html @@ -0,0 +1,133 @@ +<!doctype html> +<html> + <head> + <title>touch-action: parent: none + two embedded children</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #divParent { + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll text DOWN. Wait for description update. Expected: pan enabled</h4> + <p>Note: this test is for touch-devices only</p> + <div id="divParent"> + <div class="scroller" id="target0"> + <div> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + </div> + </div> + <script type='text/javascript'> + var detected_pointertypes = {}; + + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + + add_completion_callback(showPointerTypes); + add_completion_callback(enableScrolling); + + function run() { + var target0 = document.getElementById("target0"); + + var test_touchaction = async_test("touch-action attribute test"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(target0, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + }); + + // Check if touch-action attribute works properly for embedded divs + // + // TA: 15. + on_event(target0, 'scroll', function(event) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + yScrollIsReceived = true; + updateDescriptionNextStep(); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction.done(); + updateDescriptionComplete(); + } + }); + } + + function enableScrolling() { + document.getElementById('divParent').setAttribute('style', 'touch-action: auto'); + } + </script> + <h1>behaviour: auto</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-inherit_parent-none_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_parent-none_touch-manual.html new file mode 100644 index 000000000..163ef9b8e --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-inherit_parent-none_touch-manual.html @@ -0,0 +1,112 @@ +<!doctype html> +<html> + <head> + <title>touch-action: inherit from parent: none</title> + <meta name="assert" content="TA15.8 - when a user touches an element, the effect of that touch is determined by the value of the touch-action property and the default touch behaviors on the element and its ancestors. Scrollable-Parent: `none` Child: `auto`"> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + .scroller { + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: no panning</h4> + <p>Note: this test is for touch-devices only</p> + <div class="scroller" id="target0"> + <div> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var test_touchaction = async_test("touch-action attribute test"); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if touch-action attribute works properly for embedded divs + // + // TA: 15.8 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + + on_event(target0, 'scroll', function(event) { + test_touchaction.step(failOnScroll, "scroll received while touch-action is none"); + }); + } + </script> + <h1>behaviour: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-keyboard-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-keyboard-manual.html new file mode 100644 index 000000000..3fef3f646 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-keyboard-manual.html @@ -0,0 +1,124 @@ +<!doctype html> +<html> + <head> + <title>touch-action: keyboard</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 430px; + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Press DOWN ARROW key. Wait for description update. Expected: pan enabled</h4> + <p>Note: this test is for keyboard only</p> + <div id="target0"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <script type='text/javascript'> + + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + + function run() { + var target0 = document.getElementById("target0"); + + var test_touchaction = async_test("touch-action attribute test"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + target0.focus(); + + on_event(target0, 'scroll', function(event) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction.step(function () { + yScrollIsReceived = true; + assert_true(true, "y-scroll received."); + }); + updateDescriptionNextStepKeyboard(); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction.done(); + updateDescriptionComplete(); + } + }); + } + + function updateDescriptionNextStepKeyboard() { + document.getElementById('desc').innerHTML = "Test Description: press RIGHT ARROW key."; + } + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-mouse-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-mouse-manual.html new file mode 100644 index 000000000..fcc858451 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-mouse-manual.html @@ -0,0 +1,130 @@ +<!doctype html> +<html> + <head> + <title>touch-action: mouse</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 430px; + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll text down using mouse (use mouse wheel or click on the scrollbar). Wait for description update.</h4> + <p>Note: this test is for mouse only</p> + <div id="target0"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <script type='text/javascript'> + var detected_pointertypes = {}; + + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + var test_touchaction = async_test("touch-action attribute test"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(target0, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, 'scroll', function(event) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction.step(function () { + yScrollIsReceived = true; + assert_true(true, "y-scroll received."); + }); + updateDescriptionNextStepMouse(); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction.done(); + updateDescriptionComplete(); + } + }); + } + + function updateDescriptionNextStepMouse() { + document.getElementById('desc').innerHTML = "Test Description: Try to scroll text right using mouse (use mouse wheel or click on the scrollbar)."; + } + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-none-css_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-none-css_touch-manual.html new file mode 100644 index 000000000..dec694f3e --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-none-css_touch-manual.html @@ -0,0 +1,111 @@ +<!doctype html> +<html> + <head> + <title>touch-action: none</title> + <meta name="assert" content="TA15.2 - With `touch-action: none` on a swiped or click/dragged element, `pointerdown+(optional pointermove)+pointerup` must be dispatched."> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 430px; + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: no panning/zooming/etc.</h4> + <p>Note: this test is for touch-devices only</p> + <div id="target0"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + + var test_touchaction = async_test("touch-action attribute test"); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if "touch-action: none" attribute works properly + //TA: 15.2 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + + on_event(target0, 'scroll', function(event) { + test_touchaction.step(failOnScroll, "scroll received while touch-action is none"); + }); + } + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-css_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-css_touch-manual.html new file mode 100644 index 000000000..e757baec6 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-css_touch-manual.html @@ -0,0 +1,106 @@ +<!doctype html> +<html> + <head> + <title>touch-action: pan-x</title> + <meta name="assert" content="TA15.3 - With `touch-action: pan-x` on a swiped or click/dragged element, only panning on the x-axis should be possible."> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 430px; + touch-action: pan-x; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: only pans in x direction.</h4> + <p>Note: this test is for touch-devices only</p> + <div id="target0"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + var test_touchaction = async_test("touch-action attribute test"); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if "touch-action: pan-x" attribute works properly + //TA: 15.3 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_not_equals(target0.scrollLeft, 0, "scroll x offset should not be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + } + </script> + <h1>touch-action: pan-x</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-pan-y-pan-y_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-pan-y-pan-y_touch-manual.html new file mode 100644 index 000000000..e89b8b742 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-pan-y-pan-y_touch-manual.html @@ -0,0 +1,111 @@ +<!doctype html> +<html> + <head> + <title>touch-action: parent > child: pan-x pan-y > child: pan-y</title> + <meta name="assert" content="TA15.17 - Touch action 'pan-x pan-y' 'pan-y' test"> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + .scroller > div { + touch-action: pan-x pan-y; + } + .scroller > div div { + touch-action: pan-y; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: only pans in y direction.</h4> + <p>Note: this test is for touch-devices only</p> + <div class="scroller" id="target0"> + <div> + <div> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + </div> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + add_completion_callback(showPointerTypes); + + var test_touchaction = async_test("touch-action attribute test"); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if touch-action attribute works properly for embedded divs + // + // TA: 15.17 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test"); + assert_not_equals(target0.scrollTop, 0, "scroll y offset should not be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + } + </script> + <h1>behaviour: pan-y</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-pan-y_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-pan-y_touch-manual.html new file mode 100644 index 000000000..0c900ff74 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-pan-x-pan-y_touch-manual.html @@ -0,0 +1,126 @@ +<!doctype html> +<html> + <head> + <title>touch-action: pan-x pan-y</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 430px; + touch-action: pan-x pan-y; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll text DOWN. Wait for description update. Expected: pan enabled</h4> + <p>Note: this test is for touch-devices only</p> + <div id="target0"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <script type='text/javascript'> + var detected_pointertypes = {}; + + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + + var test_touchaction = async_test("touch-action attribute test"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(target0, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, 'scroll', function(event) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction.step(function () { + yScrollIsReceived = true; + assert_true(true, "y-scroll received."); + }); + updateDescriptionNextStep(); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction.done(); + updateDescriptionComplete(); + } + }); + } + </script> + <h1>touch-action: pan-x pan-y</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-pan-y-css_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-pan-y-css_touch-manual.html new file mode 100644 index 000000000..4ad39ecc8 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-pan-y-css_touch-manual.html @@ -0,0 +1,106 @@ +<!doctype html> +<html> + <head> + <title>touch-action: pan-y</title> + <meta name="assert" content="TA15.4 - With `touch-action: pan-y` on a swiped or click/dragged element, only panning in the y-axis should be possible."> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 430px; + touch-action: pan-y; + } + </style> + </head> + <body onload="run()"> + <h1>Pointer Events touch-action attribute support</h1> + <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT. Tap Complete button under the rectangle when done. Expected: only pans in y direction.</h4> + <p>Note: this test is for touch-devices only</p> + <div id="target0"> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem + nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat. + Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit + lobortis nisl ut aliquip ex ea commodo consequat. + </p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + <p>Lorem ipsum dolor sit amet...</p> + </div> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + var test_touchaction = async_test("touch-action attribute test"); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + // Check if "touch-action: pan-y" attribute works properly + //TA: 15.4 + on_event(btnComplete, 'click', function(event) { + detected_pointertypes[event.pointerType] = true; + test_touchaction.step(function() { + assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test"); + assert_not_equals(target0.scrollTop, 0, "scroll y offset should not be 0 in the end of the test"); + }); + test_touchaction.done(); + updateDescriptionComplete(); + }); + } + </script> + <h1>touch-action: pan-y</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-span-test_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-span-test_touch-manual.html new file mode 100644 index 000000000..41635e0bf --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-span-test_touch-manual.html @@ -0,0 +1,113 @@ +<!doctype html> +<html> + <head> + <title>Span touch-action test</title> + <meta name="assert" content="TA15.18 - The touch-action CSS property applies to all elements except non-replaced inline elements." + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + height: 150px; + width: 200px; + overflow-y: auto; + background: black; + padding: 100px; + position: relative; + } + #testspan { + touch-action: none; + font-size: 72pt; + padding: 0px 0px 180px 0px; + border: 2px solid red; + } + </style> + </head> + <body onload="run()"> + <h2>Pointer Events touch-action attribute support</h2> + <h4 id="desc">Test Description: Try to scroll black element DOWN moving your touch outside of the red border. Wait for description update.</h4> + <p>Note: this test is for touch only</p> + <div id="target0"> + <span id="testspan"> + Test span + </span> + </div> + <input type="button" id="btnComplete" value="Complete test"> + + <script type='text/javascript'> + var detected_pointertypes = {}; + + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var failScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + var scrollReturnInterval = 500; + var isFirstPart = true; + setup({ explicit_timeout: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + //TA 15.18 + var test_touchaction_div = async_test("touch-action attribute test out of element"); + var test_touchaction_span = async_test("touch-action attribute test in element"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(btnComplete, 'click', function(event) { + test_touchaction_span.step(function() { + assert_not_equals(target0.scrollLeft, 0, "span scroll x offset should not be 0 in the end of the test"); + assert_not_equals(target0.scrollTop, 0, "span scroll y offset should not be 0 in the end of the test"); + assert_true(!isFirstPart, "target0 x and y scroll offsets should be greater than 0 after first two interactions (outside red border) respectively"); + }); + test_touchaction_span.done(); + updateDescriptionComplete(); + }); + + on_event(btnComplete, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, 'scroll', function(event) { + if(isFirstPart) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction_div.step(function () { + yScrollIsReceived = true; + }); + updateDescriptionSecondStepTouchActionElement(target0, scrollReturnInterval); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction_div.done(); + updateDescriptionThirdStepTouchActionElement(target0, scrollReturnInterval); + setTimeout(function() { + isFirstPart = false; + xScr0 = target0.scrollLeft; + xScr0 = target0.scrollLeft; + xScrollIsReceived = false; + yScrollIsReceived = false; + }, 2 * scrollReturnInterval); // avoid immediate triggering while scroll is still being performed + } + } + }); + } + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-svg-test_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-svg-test_touch-manual.html new file mode 100644 index 000000000..422a72e19 --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-svg-test_touch-manual.html @@ -0,0 +1,122 @@ +<!doctype html> +<html> + <head> + <title>SVG test</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + height: 350px; + width: 300px; + overflow-y: auto; + background: black; + padding: 100px; + position: relative; + } + </style> + </head> + <body onload="run()"> + <h2>Pointer Events touch-action attribute support</h2> + <h4 id="desc">Test Description: Try to scroll black element DOWN moving your touch outside of the red border. Wait for description update.</h4> + <p>Note: this test is for touch only</p> + <div id="target0"> + <svg id="testSvg" width="555" height="555" style="touch-action: none; border: 4px double red;"> + <circle cx="305" cy="305" r="250" stroke="green" stroke-width="4" fill="yellow" /> + Sorry, your browser does not support inline SVG. + </svg> + </div> + <br> + <input type="button" id="btnComplete" value="Complete test"> + <script type='text/javascript'> + var detected_pointertypes = {}; + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + var scrollReturnInterval = 1000; + var isFirstPart = true; + setup({ explicit_timeout: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + var test_touchaction_div = async_test("touch-action attribute test out of SVG"); + var test_touchaction_svg = async_test("touch-action attribute test in SVG"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(btnComplete, 'click', function(event) { + test_touchaction_svg.step(function() { + assert_equals(target0.scrollLeft, 0, "SVG scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "SVG scroll y offset should be 0 in the end of the test"); + }); + test_touchaction_svg.done(); + updateDescriptionComplete(); + }); + + on_event(btnComplete, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, 'scroll', function(event) { + if(isFirstPart) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction_div.step(function () { + yScrollIsReceived = true; + assert_true(true, "y-scroll received."); + }); + updateDescriptionSecondStepSVG(); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction_div.done(); + updateDescriptionThirdStepSVG(); + setTimeout(function() { + isFirstPart = false; + }, 2 * scrollReturnInterval); + } + } + }); + } + + function updateDescriptionSecondStepSVG() { + window.setTimeout(function() { + objectScroller(target0, 'up', 0);} + , scrollReturnInterval); + document.getElementById('desc').innerHTML = "Test Description: Try to scroll element RIGHT moving your touch outside of the red border"; + } + + function updateDescriptionThirdStepSVG() { + window.setTimeout(function() { + objectScroller(target0, 'left', 0);} + , scrollReturnInterval); + document.getElementById('desc').innerHTML = "Test Description: Try to scroll element DOWN then RIGHT starting your touch inside of the circle. Tap Complete button under the rectangle when done"; + } + + function objectScroller(target, direction, value) { + if (direction == 'up') { + target.scrollTop = 0; + } else if (direction == 'left') { + target.scrollLeft = 0; + } + } + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-table-test_touch-manual.html b/dom/events/test/pointerevents/pointerevent_touch-action-table-test_touch-manual.html new file mode 100644 index 000000000..fcc3a3e7c --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-table-test_touch-manual.html @@ -0,0 +1,141 @@ +<!doctype html> +<html> + <head> + <title>Table touch-action test</title> + <meta name="assert" content="TA15.19 The touch-action CSS property applies to all elements except table rows, row groups, table columns, and column groups."> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + height: 150px; + width: 200px; + overflow-y: auto; + background: black; + padding: 100px; + position: relative; + } + #testtable{ + color: white; + width: 350px; + padding: 0px 0px 200px 0px; + border: 2px solid green; + } + .testtd, .testth { + border: 2px solid green; + height: 80px; + } + #row1 { + touch-action: none; + } + #cell3 { + touch-action: none; + } + </style> + </head> + <body onload="run()"> + <h2>Pointer Events touch-action attribute support</h2> + <h4 id="desc">Test Description: Try to scroll element DOWN starting your touch over the 1st Row. Wait for description update.</h4> + <p>Note: this test is for touch only</p> + <div id="target0"> + <table id="testtable"> + <caption>The caption, first row element, and cell 3 have touch-action: none.</caption> + <tr id="row1"><th class="testth">Header 1 <td class="testtd">Cell 1 <td class="testtd">Cell 2</tr> + <tr id="row2"><th class="testth">Header 2 <td id="cell3" class="testtd">Cell 3 <td class="testtd">Cell 4</tr> + <tr id="row3"> <th class="testth">Header 3 <td class="testtd">Cell 5 <td class="testtd"> Cell 6</tr> + </table> + </div> + <br> + <input type="button" id="btnComplete" value="Complete test"> + + <script type='text/javascript'> + var detected_pointertypes = {}; + var xScrollIsReceived = false; + var yScrollIsReceived = false; + var xScr0, yScr0, xScr1, yScr1; + var scrollReturnInterval = 1000; + var isFirstPart = true; + setup({ explicit_timeout: true }); + add_completion_callback(showPointerTypes); + + function run() { + var target0 = document.getElementById("target0"); + var btnComplete = document.getElementById("btnComplete"); + + //TA 15.19 + var test_touchaction_cell = async_test("touch-action attribute test on the cell"); + var test_touchaction_row = async_test("touch-action attribute test on the row"); + + xScr0 = target0.scrollLeft; + yScr0 = target0.scrollTop; + + on_event(btnComplete, 'click', function(event) { + test_touchaction_cell.step(function() { + assert_equals(target0.scrollLeft, 0, "table scroll x offset should be 0 in the end of the test"); + assert_equals(target0.scrollTop, 0, "table scroll y offset should be 0 in the end of the test"); + assert_true(xScrollIsReceived && yScrollIsReceived, "target0 x and y scroll offsets should be greater than 0 after first two interactions (outside red border) respectively"); + }); + test_touchaction_cell.done(); + updateDescriptionComplete(); + }); + + on_event(btnComplete, 'pointerdown', function(event) { + detected_pointertypes[event.pointerType] = true; + }); + + on_event(target0, 'scroll', function(event) { + if(isFirstPart) { + xScr1 = target0.scrollLeft; + yScr1 = target0.scrollTop; + + if(xScr1 != xScr0) { + xScrollIsReceived = true; + } + + if(yScr1 != yScr0) { + test_touchaction_row.step(function () { + yScrollIsReceived = true; + }); + updateDescriptionSecondStepTable(target0, scrollReturnInterval); + } + + if(xScrollIsReceived && yScrollIsReceived) { + test_touchaction_row.done(); + updateDescriptionThirdStepTable(target0, scrollReturnInterval); + setTimeout(function() { + isFirstPart = false; + }, 2 * scrollReturnInterval); // avoid immediate triggering while scroll is still being performed + } + } + else { + test_touchaction_cell.step(failOnScroll, "scroll received while shouldn't"); + } + }); + } + + function updateDescriptionSecondStepTable(target, scrollReturnInterval, element) { + window.setTimeout(function() { + objectScroller(target, 'up', 0); + } + , scrollReturnInterval); + document.getElementById('desc').innerHTML = "Test Description: Try to scroll element RIGHT staring your touch over the Row 1"; + } + + function updateDescriptionThirdStepTable(target, scrollReturnInterval) { + window.setTimeout(function() { + objectScroller(target, 'left', 0); + } + , scrollReturnInterval); + document.getElementById('desc').innerHTML = "Test Description: Try to scroll element DOWN then RIGHT starting your touch inside of the Cell 3"; + } + + </script> + <h1>touch-action: none</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/pointerevent_touch-action-verification.html b/dom/events/test/pointerevents/pointerevent_touch-action-verification.html new file mode 100644 index 000000000..2e694229a --- /dev/null +++ b/dom/events/test/pointerevents/pointerevent_touch-action-verification.html @@ -0,0 +1,101 @@ +<!doctype html> +<html> + <head> + <title>touch-action: basic verification</title> + <meta name="assert" content="TA15.20 - The touch-action CSS property determines whether touch input MAY trigger default behavior supplied by the user agent. + auto: The user agent MAY determine any permitted touch behaviors, such as panning and zooming manipulations of the viewport, for touches that begin on the element. + none: Touches that begin on the element MUST NOT trigger default touch behaviors. + pan-x: The user agent MAY consider touches that begin on the element only for the purposes of horizontally scrolling the element's nearest ancestor with horizontally scrollable content. + pan-y: The user agent MAY consider touches that begin on the element only for the purposes of vertically scrolling the element's nearest ancestor with vertically scrollable content. + manipulation: The user agent MAY consider touches that begin on the element only for the purposes of scrolling and continuous zooming. Any additional behaviors supported by auto are out of scope for this specification."> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="pointerevent_support.js"></script> + <style> + #target0 { + width: 700px; + height: 20px; + touch-action: auto; + } + #target1 { + width: 700px; + height: 20px; + touch-action: none; + background: black; + margin-top: 5px; + touch-action: pan-x; + } + #target2 { + width: 700px; + height: 20px; + touch-action: none; + background: black; + margin-top: 5px; + touch-action: pan-y; + } + #target3 { + width: 700px; + height: 20px; + touch-action: none; + background: black; + margin-top: 5px; + touch-action: none; + } + #target4 { + width: 700px; + height: 20px; + touch-action: none; + background: black; + margin-top: 5px; + touch-action: manipulation; + } + </style> + </head> + <body onload="run()"> + <h2>Pointer Events touch-action attribute support</h2> + <h4 id="desc">Test Description: Test will automatically check behaviour of following values: 'auto', 'pan-x', 'pan-y', ' none', 'manipulation'</h4> + <div id="target0"></div> + <div id="target1"></div> + <div id="target2"></div> + <div id="target3"></div> + <div id="target4"></div> + <script type='text/javascript'> + var detected_pointertypes = {}; + + setup({ explicit_done: true }); + + function run() { + var target0 = document.getElementById('target0'); + var target1 = document.getElementById('target1'); + var target2 = document.getElementById('target2'); + var target3 = document.getElementById('target3'); + var target4 = document.getElementById('target4'); + + //TA 15.20 + test(function() { + assert_true(getComputedStyle(target0).touchAction == 'auto', "'auto' is set properly"); + }, "'auto' is set properly"); + test(function() { + assert_true(getComputedStyle(target1).touchAction == 'pan-x', "'pan-x' is corrected properly"); + }, "'pan-x' is corrected properly"); + test(function() { + assert_true(getComputedStyle(target2).touchAction == 'pan-y', "'pan-y' is set properly"); + }, "'pan-y' is set properly"); + test(function() { + assert_true(getComputedStyle(target3).touchAction == 'none', "'none' is set properly"); + }, "'none' is set properly"); + test(function() { + assert_true(getComputedStyle(target4).touchAction == 'manipulation', "'manipulation' is set properly"); + }, "'manipulation' is set properly"); + done(); + } + </script> + <h1>touch-action: basic verification</h1> + <div id="complete-notice"> + <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p> + </div> + <div id="log"></div> + </body> +</html>
\ No newline at end of file diff --git a/dom/events/test/pointerevents/readme.md b/dom/events/test/pointerevents/readme.md new file mode 100644 index 000000000..f0d4fc73e --- /dev/null +++ b/dom/events/test/pointerevents/readme.md @@ -0,0 +1,5 @@ +Directory for Pointer Events Tests + +All tests were got from official repository: + +https://github.com/w3c/web-platform-tests/tree/master/pointerevents diff --git a/dom/events/test/pointerevents/resources/pointerevent_pointerId_scope-iframe.html b/dom/events/test/pointerevents/resources/pointerevent_pointerId_scope-iframe.html new file mode 100644 index 000000000..ad1a57fb4 --- /dev/null +++ b/dom/events/test/pointerevents/resources/pointerevent_pointerId_scope-iframe.html @@ -0,0 +1,37 @@ +<!doctype html> +<html> + <!-- +Test cases for Pointer Events v1 spec +This document references Test Assertions (abbrev TA below) written by Cathy Chan +http://www.w3.org/wiki/PointerEvents/TestAssertions +--> + <head> + <title>Pointer Events pointerdown tests</title> + <meta name="viewport" content="width=device-width"> + <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css"> + <script> + function run() { + var target1 = document.getElementById("target1"); + var pointerover_event; + var ponterId = null; + + var eventList = ['pointerenter', 'pointerover', 'pointermove', 'pointerout', 'pointerleave']; + + eventList.forEach(function(eventName) { + target1.addEventListener(eventName, function (event) { + var pass_data = { + 'pointerId' : event.pointerId, + 'type' : event.type, + 'pointerType' : event.pointerType + }; + top.postMessage(JSON.stringify(pass_data), "*"); + }); + }); + } + </script> + </head> + <body onload="run()"> + <div id="target1" class="touchActionNone"> + </div> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_bug1285128.html b/dom/events/test/pointerevents/test_bug1285128.html new file mode 100644 index 000000000..f7f1eb698 --- /dev/null +++ b/dom/events/test/pointerevents/test_bug1285128.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1285128 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1285128</title> + <script type="text/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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1285128">Mozilla Bug 1285128</a> +<p id="display"></p> +<div id="target0" style="width: 200px; height: 200px; background: green"></div> +<script type="text/javascript"> + +/** Test for Bug 1285128 **/ +SimpleTest.waitForExplicitFinish(); + +function runTests() { + let target0 = window.document.getElementById("target0"); + let pointerEventsList = ["pointerover", "pointerenter", "pointerdown", + "pointerup", "pointerleave", "pointerout"]; + let receivedPointerEvents = false; + pointerEventsList.forEach((elem, index, arr) => { + target0.addEventListener(elem, (event) => { + ok(false, "receiving event " + event.type); + receivedPointerEvents = true; + }, false); + }); + + target0.addEventListener("mouseenter", () => { + ok(!receivedPointerEvents, "synthesized mousemove should not trigger any pointer events"); + SimpleTest.finish(); + }); + + synthesizeMouseAtCenter(target0, { type: "mousemove", + inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE, + isWidgetEventSynthesized: true }); +} + +SpecialPowers.pushPrefEnv({"set": [["dom.w3c_pointer_events.enabled", true]]}, runTests); + +</script> +</body> +</html> diff --git a/dom/events/test/pointerevents/test_bug1293174_implicit_pointer_capture_for_touch_1.html b/dom/events/test/pointerevents/test_bug1293174_implicit_pointer_capture_for_touch_1.html new file mode 100644 index 000000000..bf5fd5d5e --- /dev/null +++ b/dom/events/test/pointerevents/test_bug1293174_implicit_pointer_capture_for_touch_1.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1293174</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + setImplicitPointerCapture(true, loadSubFrame); + } + function loadSubFrame() { + runTestInNewWindow("bug1293174_implicit_pointer_capture_for_touch_1.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchmove"); + sendTouchEvent(int_win, "target1", "touchmove"); + sendTouchEvent(int_win, "target0", "touchmove"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> + diff --git a/dom/events/test/pointerevents/test_bug1293174_implicit_pointer_capture_for_touch_2.html b/dom/events/test/pointerevents/test_bug1293174_implicit_pointer_capture_for_touch_2.html new file mode 100644 index 000000000..f1a1cc314 --- /dev/null +++ b/dom/events/test/pointerevents/test_bug1293174_implicit_pointer_capture_for_touch_2.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1293174</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + setImplicitPointerCapture(false, loadSubFrame); + } + function loadSubFrame() { + runTestInNewWindow("bug1293174_implicit_pointer_capture_for_touch_2.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchmove"); + sendTouchEvent(int_win, "target1", "touchmove"); + sendTouchEvent(int_win, "target0", "touchmove"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> + diff --git a/dom/events/test/pointerevents/test_empty_file.html b/dom/events/test/pointerevents/test_empty_file.html new file mode 100644 index 000000000..56f2cd085 --- /dev/null +++ b/dom/events/test/pointerevents/test_empty_file.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<html> +<!-- +This file should exist until bug 1150091 will be fixed. +[manifestparser] Adding support-files under tests overwrites DEFAULT instead of appending. +--> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_attributes_mouse-manual.html b/dom/events/test/pointerevents/test_pointerevent_attributes_mouse-manual.html new file mode 100644 index 000000000..1cda30c35 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_attributes_mouse-manual.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_attributes_mouse-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "square1", "mousemove", {button:-1}); + sendMouseEvent(int_win, "square1", "mousedown", {button:0}); + sendMouseEvent(int_win, "square1", "mouseup", {button:0}); + sendMouseEvent(int_win, "square1", "mousemove", {button:-1}); + sendMouseEvent(int_win, "square1", "mousemove", {button:-1, + offsetX: -1, + offsetY: -1}); + } + </script> + </head> + <body> + </body> +</html> + diff --git a/dom/events/test/pointerevents/test_pointerevent_capture_mouse-manual.html b/dom/events/test/pointerevents/test_pointerevent_capture_mouse-manual.html new file mode 100644 index 000000000..d2e0c52bf --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_capture_mouse-manual.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_capture_mouse-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "btnCapture", "mousemove"); + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "btnCapture", "mousedown", {button:1}); + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "target1", "mouseup"); + sendMouseEvent(int_win, "target1", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_capture_suppressing_mouse-manual.html b/dom/events/test/pointerevents/test_pointerevent_capture_suppressing_mouse-manual.html new file mode 100644 index 000000000..dc3903592 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_capture_suppressing_mouse-manual.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_capture_suppressing_mouse-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "btnCapture", "mousedown", {button:1}); + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "target1", "mouseup"); + sendMouseEvent(int_win, "target1", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_change-touch-action-onpointerdown_touch-manual.html b/dom/events/test/pointerevents/test_pointerevent_change-touch-action-onpointerdown_touch-manual.html new file mode 100644 index 000000000..44d4f2a1b --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_change-touch-action-onpointerdown_touch-manual.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_change-touch-action-onpointerdown_touch-manual.html"); + } + function executeTest(int_win) { + const WM_VSCROLL = 0x0115; + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchmove"); + sendTouchEvent(int_win, "target0", "touchend"); + + // NOTE: This testcase is about that modifying touch-action during a + // pointerdown callback "should not" affect the gesture detection of the + // touch session started by the pointerdown. That is, a scroll should + // still fired by gesture detection, instead of launching by our own. + var utils = _getDOMWindowUtils(int_win); + var target0 = int_win.document.getElementById("target0"); + utils.sendNativeMouseScrollEvent(target0.getBoundingClientRect().left + 5, + target0.getBoundingClientRect().top + 5, + WM_VSCROLL, 10, 10, 0, 0, 0, target0); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_constructor.html b/dom/events/test/pointerevents/test_pointerevent_constructor.html new file mode 100644 index 000000000..785169fb5 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_constructor.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_constructor.html"); + } + function executeTest(int_win) { + // Function should be, but can be empty + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_element_haspointercapture-manual.html b/dom/events/test/pointerevents/test_pointerevent_element_haspointercapture-manual.html new file mode 100644 index 000000000..215e93b57 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_element_haspointercapture-manual.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_element_haspointercapture-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target1", "mousemove", {button:0}); + sendMouseEvent(int_win, "target1", "mouseup", {button:0}); + sendMouseEvent(int_win, "target1", "mousedown", {button:0}); + sendMouseEvent(int_win, "target1", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_element_haspointercapture_release_pending_capture-manual.html b/dom/events/test/pointerevents/test_pointerevent_element_haspointercapture_release_pending_capture-manual.html new file mode 100644 index 000000000..1ec769bc7 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_element_haspointercapture_release_pending_capture-manual.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_element_haspointercapture_release_pending_capture-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target1", "mousemove", {button:0}); + sendMouseEvent(int_win, "target1", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_gotpointercapture_before_first_pointerevent-manual.html b/dom/events/test/pointerevents/test_pointerevent_gotpointercapture_before_first_pointerevent-manual.html new file mode 100644 index 000000000..875e18429 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_gotpointercapture_before_first_pointerevent-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_gotpointercapture_before_first_pointerevent-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_lostpointercapture_for_disconnected_node-manual.html b/dom/events/test/pointerevents/test_pointerevent_lostpointercapture_for_disconnected_node-manual.html new file mode 100644 index 000000000..b381de108 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_lostpointercapture_for_disconnected_node-manual.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("Official test uses timeout"); + function startTest() { + runTestInNewWindow("pointerevent_lostpointercapture_for_disconnected_node-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "btnCapture", "mousedown"); + sendMouseEvent(int_win, "btnCapture", "mousemove"); + setTimeout(function() { + sendMouseEvent(int_win, "target1", "mousemove"); + }, 500); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_lostpointercapture_is_first-manual.html b/dom/events/test/pointerevents/test_pointerevent_lostpointercapture_is_first-manual.html new file mode 100644 index 000000000..43071b67f --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_lostpointercapture_is_first-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_lostpointercapture_is_first-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "btnCapture", "mousedown", {button:0}); + sendMouseEvent(int_win, "btnCapture", "mouseup", {button:0}); + sendMouseEvent(int_win, "btnCapture", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_multiple_primary_pointers_boundary_events-manual.html b/dom/events/test/pointerevents/test_pointerevent_multiple_primary_pointers_boundary_events-manual.html new file mode 100644 index 000000000..d3c2f8962 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_multiple_primary_pointers_boundary_events-manual.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_multiple_primary_pointers_boundary_events-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendTouchEvent(int_win, "target1", "touchstart"); + sendTouchEvent(int_win, "target1", "touchend"); + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "done", "mousedown", {button:0}); + sendMouseEvent(int_win, "done", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerId_scope-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerId_scope-manual.html new file mode 100644 index 000000000..d78246dca --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerId_scope-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerId_scope-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointercancel_touch-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointercancel_touch-manual.html new file mode 100644 index 000000000..de2839350 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointercancel_touch-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointercancel_touch-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchcancel"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerdown-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerdown-manual.html new file mode 100644 index 000000000..f6febcfd4 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerdown-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerdown-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "log", "mousemove"); + sendMouseEvent(int_win, "target0", "mousedown", {button:1}); + sendMouseEvent(int_win, "target0", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerenter-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerenter-manual.html new file mode 100644 index 000000000..9c2247198 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerenter-manual.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerenter-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerenter_does_not_bubble-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerenter_does_not_bubble-manual.html new file mode 100644 index 000000000..5e13dd212 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerenter_does_not_bubble-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerenter_does_not_bubble-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "log", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerenter_nohover-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerenter_nohover-manual.html new file mode 100644 index 000000000..b92db3d52 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerenter_nohover-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerenter_nohover-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "log", "mousemove"); + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_after_pointercancel_touch-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_after_pointercancel_touch-manual.html new file mode 100644 index 000000000..89419e29b --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_after_pointercancel_touch-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_after_pointercancel_touch-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchcancel"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_after_pointerup_nohover-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_after_pointerup_nohover-manual.html new file mode 100644 index 000000000..1cde764c4 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_after_pointerup_nohover-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_after_pointerup_nohover-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendant_over-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendant_over-manual.html new file mode 100644 index 000000000..49c769f40 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendant_over-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_descendant_over-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target1", "mousedown"); + sendMouseEvent(int_win, "target1", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendants-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendants-manual.html new file mode 100644 index 000000000..6893b4aca --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendants-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_descendants-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "log", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_does_not_bubble-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_does_not_bubble-manual.html new file mode 100644 index 000000000..e7b96d218 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_does_not_bubble-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_does_not_bubble-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_mouse-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_mouse-manual.html new file mode 100644 index 000000000..d15f2c72a --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_mouse-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_mouse-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "log", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_pen-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_pen-manual.html new file mode 100644 index 000000000..52f8d6618 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_pen-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_pen-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + sendMouseEvent(int_win, "target0", "mouseup", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + sendMouseEvent(int_win, "target0", "mousecancel", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerleave_touch-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerleave_touch-manual.html new file mode 100644 index 000000000..78ad0190a --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_touch-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerleave_touch-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointermove-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointermove-manual.html new file mode 100644 index 000000000..218da5124 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointermove-manual.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointermove-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointermove-on-chorded-mouse-button.html b/dom/events/test/pointerevents/test_pointerevent_pointermove-on-chorded-mouse-button.html new file mode 100644 index 000000000..6e90b4230 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointermove-on-chorded-mouse-button.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointermove-on-chorded-mouse-button.html"); + } + + function executeTest(int_win) { + var utils = _getDOMWindowUtils(int_win); + sendMouseEvent(int_win, "target0", "mousemove", {button:0, buttons:utils.MOUSE_BUTTONS_NO_BUTTON}); + sendMouseEvent(int_win, "target0", "mousedown", {button:0, buttons:utils.MOUSE_BUTTONS_LEFT_BUTTON}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0, buttons:utils.MOUSE_BUTTONS_LEFT_BUTTON}); + sendMouseEvent(int_win, "target0", "mousedown", {button:1, buttons:utils.MOUSE_BUTTONS_LEFT_BUTTON | + utils.MOUSE_BUTTONS_MIDDLE_BUTTON}); + sendMouseEvent(int_win, "target0", "mousemove", {button:1, buttons:utils.MOUSE_BUTTONS_LEFT_BUTTON | + utils.MOUSE_BUTTONS_MIDDLE_BUTTON}); + sendMouseEvent(int_win, "target0", "mouseup", {button:1, buttons:utils.MOUSE_BUTTONS_LEFT_BUTTON}); + sendMouseEvent(int_win, "target0", "mousemove", {button:1, buttons:utils.MOUSE_BUTTONS_LEFT_BUTTON}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0, buttons:utils.MOUSE_BUTTONS_NO_BUTTON}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html new file mode 100644 index 000000000..a0f3cee07 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown"); + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target0", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointermove_pointertype-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointermove_pointertype-manual.html new file mode 100644 index 000000000..7b2d13980 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointermove_pointertype-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointermove_pointertype-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown"); + sendMouseEvent(int_win, "target0", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerout-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerout-manual.html new file mode 100644 index 000000000..31c6dad5f --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerout-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerout-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerout_after_pointercancel_touch-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerout_after_pointercancel_touch-manual.html new file mode 100644 index 000000000..c861caf26 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerout_after_pointercancel_touch-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerout_after_pointercancel_touch-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchcancel"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerout_after_pointerup_nohover-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerout_after_pointerup_nohover-manual.html new file mode 100644 index 000000000..c3bfd29d5 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerout_after_pointerup_nohover-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerout_after_pointerup_nohover-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerout_pen-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerout_pen-manual.html new file mode 100644 index 000000000..11f48b104 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerout_pen-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerout_pen-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + sendMouseEvent(int_win, "target0", "mouseup", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + sendMouseEvent(int_win, "target0", "mousecancel", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerout_received_once-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerout_received_once-manual.html new file mode 100644 index 000000000..b895244bb --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerout_received_once-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerout_received_once-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerover-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerover-manual.html new file mode 100644 index 000000000..80ae99c48 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerover-manual.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerover-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointertype_mouse-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointertype_mouse-manual.html new file mode 100644 index 000000000..aec2e8486 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointertype_mouse-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointertype_mouse-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointertype_pen-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointertype_pen-manual.html new file mode 100644 index 000000000..1469de68c --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointertype_pen-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointertype_pen-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + sendMouseEvent(int_win, "target0", "mouseup", {inputSource:MouseEvent.MOZ_SOURCE_PEN}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointertype_touch-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointertype_touch-manual.html new file mode 100644 index 000000000..18a99f490 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointertype_touch-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointertype_touch-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerup-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerup-manual.html new file mode 100644 index 000000000..5417ad5cd --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerup-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerup-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown"); + sendMouseEvent(int_win, "target0", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html new file mode 100644 index 000000000..fc1fb6981 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_pointerup_pointertype-manual.html b/dom/events/test/pointerevents/test_pointerevent_pointerup_pointertype-manual.html new file mode 100644 index 000000000..b6f07dd50 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_pointerup_pointertype-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_pointerup_pointertype-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown"); + sendMouseEvent(int_win, "target0", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_events_to_original_target-manual.html b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_events_to_original_target-manual.html new file mode 100644 index 000000000..cbf91df74 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_events_to_original_target-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_releasepointercapture_events_to_original_target-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchmove"); + sendTouchEvent(int_win, "target0", "touchend"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_invalid_pointerid-manual.html b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_invalid_pointerid-manual.html new file mode 100644 index 000000000..55f347358 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_invalid_pointerid-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_releasepointercapture_invalid_pointerid-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchmove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointercancel_touch-manual.html b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointercancel_touch-manual.html new file mode 100644 index 000000000..6b63c307c --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointercancel_touch-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_releasepointercapture_onpointercancel_touch-manual.html"); + } + function executeTest(int_win) { + sendTouchEvent(int_win, "target0", "touchstart"); + sendTouchEvent(int_win, "target0", "touchcancel"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointerup_mouse-manual.html b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointerup_mouse-manual.html new file mode 100644 index 000000000..8d5bec3c3 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointerup_mouse-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_releasepointercapture_onpointerup_mouse-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "btnCapture", "mousedown"); + sendMouseEvent(int_win, "btnCapture", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_release_right_after_capture-manual.html b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_release_right_after_capture-manual.html new file mode 100644 index 000000000..3f55c1afe --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_release_right_after_capture-manual.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_releasepointercapture_release_right_after_capture-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_setpointercapture_disconnected-manual.html b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_disconnected-manual.html new file mode 100644 index 000000000..ffbfc6009 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_disconnected-manual.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_setpointercapture_disconnected-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown"); + sendMouseEvent(int_win, "target0", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_setpointercapture_inactive_button_mouse-manual.html b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_inactive_button_mouse-manual.html new file mode 100644 index 000000000..e587e6e68 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_inactive_button_mouse-manual.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_setpointercapture_inactive_button_mouse-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target1", "mousemove"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_setpointercapture_invalid_pointerid-manual.html b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_invalid_pointerid-manual.html new file mode 100644 index 000000000..61171196a --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_invalid_pointerid-manual.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_setpointercapture_invalid_pointerid-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_setpointercapture_override_pending_capture_element-manual.html b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_override_pending_capture_element-manual.html new file mode 100644 index 000000000..5a3adede6 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_override_pending_capture_element-manual.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_setpointercapture_override_pending_capture_element-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_setpointercapture_relatedtarget-manual.html b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_relatedtarget-manual.html new file mode 100644 index 000000000..0883d616b --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_relatedtarget-manual.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_setpointercapture_relatedtarget-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "btnCapture", "mousedown"); + sendMouseEvent(int_win, "target1", "mousemove"); + sendMouseEvent(int_win, "target1", "mouseup"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_setpointercapture_to_same_element_twice-manual.html b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_to_same_element_twice-manual.html new file mode 100644 index 000000000..e4af7dd56 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_to_same_element_twice-manual.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_setpointercapture_to_same_element_twice-manual.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousemove"); + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_click.html b/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_click.html new file mode 100644 index 000000000..c09135449 --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_click.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_suppress_compat_events_on_click.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + sendMouseEvent(int_win, "target1", "mousedown", {button:0}); + sendMouseEvent(int_win, "target1", "mouseup", {button:0}); + sendMouseEvent(int_win, "done", "mousedown", {button:0}); + sendMouseEvent(int_win, "done", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_drag_mouse.html b/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_drag_mouse.html new file mode 100644 index 000000000..d379134cd --- /dev/null +++ b/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_drag_mouse.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1000870 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1000870</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="mochitest_support_external.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + function startTest() { + runTestInNewWindow("pointerevent_suppress_compat_events_on_drag_mouse.html"); + } + function executeTest(int_win) { + sendMouseEvent(int_win, "target0", "mousedown", {button:0}); + sendMouseEvent(int_win, "target0", "mousemove", {button:0}); + sendMouseEvent(int_win, "target0", "mouseup", {button:0}); + sendMouseEvent(int_win, "target1", "mousedown", {button:0}); + sendMouseEvent(int_win, "target1", "mousemove", {button:0}); + sendMouseEvent(int_win, "target1", "mouseup", {button:0}); + sendMouseEvent(int_win, "done", "mousedown", {button:0}); + sendMouseEvent(int_win, "done", "mouseup", {button:0}); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/test_touch_action.html b/dom/events/test/pointerevents/test_touch_action.html new file mode 100644 index 000000000..31d115f82 --- /dev/null +++ b/dom/events/test/pointerevents/test_touch_action.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>W3C pointerevents/*touch-action*.html tests in Mochitest form</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="apz_test_utils.js"></script> + <script type="application/javascript" src="apz_test_native_event_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> +var apz_touch_action_prefs = [ + // Obviously we need touch-action support enabled for testing touch-action. + ["layout.css.touch_action.enabled", true], + // Dropping the touch slop to 0 makes the tests easier to write because + // we can just do a one-pixel drag to get over the pan threshold rather + // than having to hard-code some larger value. + ["apz.touch_start_tolerance", "0.0"], + // The touchstart from the drag can turn into a long-tap if the touch-move + // events get held up. Try to prevent that by making long-taps require + // a 10 second hold. Note that we also cannot enable chaos mode on this + // test for this reason, since chaos mode can cause the long-press timer + // to fire sooner than the pref dictates. + ["ui.click_hold_context_menus.delay", 10000], + // The subtests in this test do touch-drags to pan the page, but we don't + // want those pans to turn into fling animations, so we increase the + // fling-stop threshold velocity to absurdly high. + ["apz.fling_stopped_threshold", "10000"], + // The helper_div_pan's div gets a displayport on scroll, but if the + // test takes too long the displayport can expire before the new scroll + // position is synced back to the main thread. So we disable displayport + // expiry for these tests. + ["apz.displayport_expiry_ms", 0], +]; + +function apzScriptInjector(name) { + return function(childWin) { + childWin._ACTIVE_TEST_NAME = name; + injectScript('/tests/SimpleTest/paint_listener.js', childWin)() + .then(injectScript('apz_test_utils.js', childWin)) + .then(injectScript('apz_test_native_event_utils.js', childWin)) + .then(injectScript('touch_action_helpers.js', childWin)); + }; +} + +// Each of these test names is turned into an entry in the |subtests| array +// below. +var testnames = [ + 'pointerevent_touch-action-auto-css_touch-manual', + 'pointerevent_touch-action-button-test_touch-manual', + // this one runs as a web-platform-test since it's not a manual test + // 'pointerevent_touch-action-illegal', + 'pointerevent_touch-action-inherit_child-auto-child-none_touch-manual', + 'pointerevent_touch-action-inherit_child-none_touch-manual', + 'pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual', + 'pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual', + 'pointerevent_touch-action-inherit_highest-parent-none_touch-manual', + 'pointerevent_touch-action-inherit_parent-none_touch-manual', + // the keyboard-manual and mouse-manual tests require simulating keyboard/ + // mouse input, rather than touch, so we're not going to do that here. + //'pointerevent_touch-action-keyboard-manual', + //'pointerevent_touch-action-mouse-manual', + 'pointerevent_touch-action-none-css_touch-manual', + 'pointerevent_touch-action-pan-x-css_touch-manual', + 'pointerevent_touch-action-pan-x-pan-y-pan-y_touch-manual', + 'pointerevent_touch-action-pan-x-pan-y_touch-manual', + 'pointerevent_touch-action-pan-y-css_touch-manual', + 'pointerevent_touch-action-span-test_touch-manual', + 'pointerevent_touch-action-svg-test_touch-manual', + 'pointerevent_touch-action-table-test_touch-manual', + // this one runs as a web-platform-test since it's not a manual test + //'pointerevent_touch-action-verification', +]; + +// Each entry in |subtests| is loaded in a new window. When loaded, it runs +// the function returned by apzScriptInjector, which injects some helper JS +// files into the vanilla unmodified W3C testcase, and simulates the necessary +// user input to run the test. +var subtests = []; +for (var name of testnames) { + subtests.push({ + 'file': name + '.html', + 'prefs': apz_touch_action_prefs, + 'onload': apzScriptInjector(name), + }); +} + +if (isApzEnabled()) { + SimpleTest.waitForExplicitFinish(); + window.onload = function() { + runSubtestsSeriallyInFreshWindows(subtests) + .then(SimpleTest.finish); + }; +} + + </script> + </head> + <body> + </body> +</html> diff --git a/dom/events/test/pointerevents/touch_action_helpers.js b/dom/events/test/pointerevents/touch_action_helpers.js new file mode 100644 index 000000000..91f628827 --- /dev/null +++ b/dom/events/test/pointerevents/touch_action_helpers.js @@ -0,0 +1,206 @@ +// Some common helpers + +function touchActionSetup(testDriver) { + add_completion_callback(subtestDone); + document.body.addEventListener('touchend', testDriver, { passive: true }); +} + +function touchScrollRight(aSelector = '#target0', aX = 20, aY = 20) { + var target = document.querySelector(aSelector); + return ok(synthesizeNativeTouchDrag(target, aX + 40, aY, -40, 0), "Synthesized horizontal drag"); +} + +function touchScrollDown(aSelector = '#target0', aX = 20, aY = 20) { + var target = document.querySelector(aSelector); + return ok(synthesizeNativeTouchDrag(target, aX, aY + 40, 0, -40), "Synthesized vertical drag"); +} + +function tapComplete() { + var button = document.getElementById('btnComplete'); + return button.click(); +} + +// The main body functions to simulate the input events required for the named test + +function* pointerevent_touch_action_auto_css_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollDown(); +} + +function* pointerevent_touch_action_button_test_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollDown('#target0 > button'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0 > button'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_inherit_child_auto_child_none_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_inherit_child_none_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown('#target0 > div'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0 > div'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_inherit_child_pan_x_child_pan_x_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_inherit_child_pan_x_child_pan_y_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_inherit_highest_parent_none_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown('#target0 > div'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0 > div'); +} + +function* pointerevent_touch_action_inherit_parent_none_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_none_css_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_pan_x_css_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_pan_x_pan_y_pan_y_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0 > div div'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_pan_x_pan_y_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight(); +} + +function* pointerevent_touch_action_pan_y_css_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_span_test_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollDown('#testspan'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#testspan'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_svg_test_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown(); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollRight(); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollDown('#target0', 250, 250); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#target0', 250, 250); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +function* pointerevent_touch_action_table_test_touch_manual(testDriver) { + touchActionSetup(testDriver); + + yield touchScrollDown('#row1'); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollRight('#row1'); + yield waitForApzFlushedRepaints(testDriver); + yield setTimeout(testDriver, 2 * scrollReturnInterval); + yield touchScrollDown('#cell3'); + yield waitForApzFlushedRepaints(testDriver); + yield touchScrollRight('#cell3'); + yield waitForApzFlushedRepaints(testDriver); + yield tapComplete(); +} + +// This the stuff that runs the appropriate body function above + +var test = eval(_ACTIVE_TEST_NAME.replace(/-/g, '_')); +waitUntilApzStable().then(runContinuation(test)); diff --git a/dom/events/test/test_DataTransferItemList.html b/dom/events/test/test_DataTransferItemList.html new file mode 100644 index 000000000..a267a183e --- /dev/null +++ b/dom/events/test/test_DataTransferItemList.html @@ -0,0 +1,232 @@ +<html> +<head> + <title>Tests for the DatTransferItemList object</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body style="height: 300px; overflow: auto;"> +<p id="display"> </p> +<img id="image" draggable="true" src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82"> +<div id="over" "style="width: 100px; height: 100px; border: 2px black dashed;"> + drag over here +</div> + +<script> + function spin() { + // Defer to the event loop twice to wait for any events to be flushed out. + return new Promise(function(a) { + SimpleTest.executeSoon(function() { + SimpleTest.executeSoon(a) + }); + }); + } + + add_task(function* () { + yield spin(); + var draggable = document.getElementById('image'); + var over = document.getElementById('over'); + + var dragstartFired = 0; + draggable.addEventListener('dragstart', onDragStart); + function onDragStart(e) { + draggable.removeEventListener('dragstart', onDragStart); + + var dt = e.dataTransfer; + dragstartFired++; + + ok(true, "dragStart event fired"); + var dtList = e.dataTransfer.items; + ok(dtList instanceof DataTransferItemList, + "DataTransfer.items returns a DataTransferItemList"); + + for (var i = 0; i < dtList.length; i++) { + var item = dtList[i]; + ok(item instanceof DataTransferItem, + "operator[] returns DataTransferItem objects"); + if (item.kind == "file") { + var file = item.getAsFile(); + ok(file instanceof File, "getAsFile() returns File objects"); + } + } + + dtList.clear(); + is(dtList.length, 0, "after .clear() DataTransferItemList should be empty"); + + dtList.add("this is some text", "text/plain"); + dtList.add("<a href='www.mozilla.org'>this is a link</a>", "text/html"); + dtList.add("http://www.mozilla.org", "text/uri-list"); + dtList.add("this is custom-data", "custom-data"); + + + var file = new File(['<a id="a"><b id="b">hey!</b></a>'], "myfile.html", + {type: "text/html"}); + + dtList.add(file); + + checkTypes(["text/plain", "text/html", "text/uri-list", "custom-data", "text/html"], + dtList, "DataTransferItemList.add test"); + + var files = e.dataTransfer.files; + is(files.length, 1, "DataTransfer.files should contain the one file we added earlier"); + is(files[0], file, "It should be the same file as the file we originally created"); + is(file, e.dataTransfer.mozGetDataAt("text/html", 1), + "It should be stored in index 1 for mozGetDataAt"); + + var file2 = new File(['<a id="c"><b id="d">yo!</b></a>'], "myotherfile.html", + {type: "text/html"}); + dtList.add(file2); + + todo(files.length == 2, "This test has chrome privileges, so the FileList objects aren't updated live"); + files = e.dataTransfer.files; + is(files.length, 2, "The files property should have been updated in place"); + is(files[1], file2, "It should be the same file as the file we originally created"); + is(file2, e.dataTransfer.mozGetDataAt("text/html", 2), + "It should be stored in index 2 for mozGetDataAt"); + + var oldLength = dtList.length; + var randomString = "foo!"; + e.dataTransfer.mozSetDataAt("random/string", randomString, 3); + is(oldLength, dtList.length, + "Adding a non-file entry to a non-zero index should not add an item to the items list"); + + var file3 = new File(['<a id="e"><b id="f">heya!</b></a>'], "yetanotherfile.html", + {type: "text/html"}); + e.dataTransfer.mozSetDataAt("random/string", file3, 3); + is(oldLength + 1, dtList.length, + "Replacing the entry with a file should add it to the list!"); + is(dtList[oldLength].getAsFile(), file3, "It should be stored in the last index as a file"); + is(dtList[oldLength].type, "text/html", "It should have the correct type"); + is(dtList[oldLength].kind, "file", "It should have the correct kind"); + + todo(files.length == 3, "This test has chrome privileges, so the FileList objects aren't updated live"); + files = e.dataTransfer.files; + is(files[files.length - 1], file3, "It should also be in the files list"); + + oldLength = dtList.length; + var nonstring = {}; + e.dataTransfer.mozSetDataAt("jsobject", nonstring, 0); + is(oldLength + 1, dtList.length, + "Adding a non-string object using the mozAPIs to index 0 should add an item to the dataTransfer"); + is(dtList[oldLength].type, "jsobject", "It should have the correct type"); + is(dtList[oldLength].kind, "other", "It should have the correct kind"); + + // Clear the event's data and get it set up so we can read it later! + dtList.clear(); + + dtList.add(file); + dtList.add("this is some text", "text/plain"); + is(e.dataTransfer.mozGetDataAt("text/html", 1), file); + } + + var getAsStringCalled = 0; + var dragenterFired = 0; + over.addEventListener('dragenter', onDragEnter); + function onDragEnter(e) { + over.removeEventListener('dragenter', onDragEnter); + + var dt = e.dataTransfer; + dragenterFired++; + + readOnly(e); + } + + var dropFired = 0; + over.addEventListener('drop', onDrop); + function onDrop(e) { + over.removeEventListener('drop', onDrop); + + var dt = e.dataTransfer; + dropFired++; + e.preventDefault(); + + readOnly(e); + } + + + function readOnly(e) { + var dtList = e.dataTransfer.items; + var num = dtList.length; + + // .clear() should have no effect + dtList.clear(); + is(dtList.length, num, + ".clear() should have no effect on the object during a readOnly event"); + + // .remove(i) should throw InvalidStateError + for (var i = 0; i < dtList.length; i++) { + expectError(function() { dtList.remove(i); }, + "InvalidStateError", ".remove(" + i + ") during a readOnly event"); + } + + // .add() should return null and have no effect + var data = [["This is a plain string", "text/plain"], + ["This is <em>HTML!</em>", "text/html"], + ["http://www.mozilla.org/", "text/uri-list"], + ["this is some custom data", "custom-data"]]; + + for (var i = 0; i < data.length; i++) { + is(dtList.add(data[i][0], data[i][1]), null, + ".add() should return null during a readOnly event"); + + is(dtList.length, num, ".add() should have no effect during a readOnly event"); + } + + // .add() with a file should return null and have no effect + var file = new File(['<a id="a"><b id="b">hey!</b></a>'], "myfile.html", + {type: "text/html"}); + is(dtList.add(file), null, ".add() with a file should return null during a readOnly event"); + is(dtList.length, num, ".add() should have no effect during a readOnly event"); + + // We should be able to access the files + is(e.dataTransfer.files.length, 1, "Should be able to access files"); + ok(e.dataTransfer.files[0], "File should be the same file!"); + is(e.dataTransfer.items.length, 2, "Should be able to see there are 2 items"); + + is(e.dataTransfer.items[0].kind, "file", "First item should be a file"); + is(e.dataTransfer.items[1].kind, "string", "Second item should be a string"); + + is(e.dataTransfer.items[0].type, "text/html", "first item should be text/html"); + is(e.dataTransfer.items[1].type, "text/plain", "second item should be text/plain"); + + ok(e.dataTransfer.items[0].getAsFile(), "Should be able to get file"); + e.dataTransfer.items[1].getAsString(function(s) { + getAsStringCalled++; + is(s, "this is some text", "Should provide the correct string"); + }); + } + + synthesizeDrop(draggable, over, null, null); + + // Wait for the getAsString callbacks to complete + yield spin(); + is(getAsStringCalled, 2, "getAsString should be called twice"); + + // Sanity-check to make sure that the events were actually run + is(dragstartFired, 1, "dragstart fired"); + is(dragenterFired, 1, "dragenter fired"); + is(dropFired, 1, "drop fired"); + }); + + function expectError(fn, eid, testid) { + var error = ""; + try { + fn(); + } catch (ex) { + error = ex.name; + } + is(error, eid, testid + " causes exception " + eid); + } + + function checkTypes(aExpectedList, aDtList, aTestid) { + is(aDtList.length, aExpectedList.length, aTestid + " length test"); + for (var i = 0; i < aExpectedList.length; i++) { + is(aDtList[i].type, aExpectedList[i], aTestid + " type " + i); + } + } +</script> + +</body> +</html> diff --git a/dom/events/test/test_accel_virtual_modifier.html b/dom/events/test/test_accel_virtual_modifier.html new file mode 100644 index 000000000..5e320b61f --- /dev/null +++ b/dom/events/test/test_accel_virtual_modifier.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM "Accel" virtual modifier</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +var kAccel = "Accel"; +var kAccelKeyCode = SpecialPowers.getIntPref("ui.key.accelKey"); + +var mouseEvent = new MouseEvent("mousedown", {}); +is(mouseEvent.getModifierState(kAccel), false, + "MouseEvent.getModifierState(\"" + kAccel + "\") should be false"); +mouseEvent = new MouseEvent("wheel", { accelKey: true}); +is(mouseEvent.getModifierState(kAccel), false, + "MouseEvent.getModifierState(\"" + kAccel + "\") should be false due to not supporting accelKey attribute"); +mouseEvent = new MouseEvent("mousedown", { ctrlKey: true }); +is(mouseEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_CONTROL, + "MouseEvent.getModifierState(\"" + kAccel + "\") should be true if ctrlKey is an accel modifier"); +mouseEvent = new MouseEvent("mousedown", { altKey: true }); +is(mouseEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_ALT, + "MouseEvent.getModifierState(\"" + kAccel + "\") should be true if altKey is an accel modifier"); +mouseEvent = new MouseEvent("mousedown", { metaKey: true }); +is(mouseEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_META, + "MouseEvent.getModifierState(\"" + kAccel + "\") should be true if metaKey is an accel modifier"); +mouseEvent = new MouseEvent("mousedown", { ctrlKey: true, altKey: true, metaKey: true }); +is(mouseEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_CONTROL || + kAccelKeyCode == KeyboardEvent.DOM_VK_ALT || + kAccelKeyCode == KeyboardEvent.DOM_VK_META, + "MouseEvent.getModifierState(\"" + kAccel + "\") should be true if one of ctrlKey, altKey or metaKey is an accel modifier"); + +var wheelEvent = new WheelEvent("wheel", {}); +is(wheelEvent.getModifierState(kAccel), false, + "WheelEvent.getModifierState(\"" + kAccel + "\") should be false"); +wheelEvent = new WheelEvent("wheel", { accelKey: true}); +is(wheelEvent.getModifierState(kAccel), false, + "WheelEvent.getModifierState(\"" + kAccel + "\") should be false due to not supporting accelKey attribute"); +wheelEvent = new WheelEvent("wheel", { ctrlKey: true }); +is(wheelEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_CONTROL, + "WheelEvent.getModifierState(\"" + kAccel + "\") should be true if ctrlKey is an accel modifier"); +wheelEvent = new WheelEvent("wheel", { altKey: true }); +is(wheelEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_ALT, + "WheelEvent.getModifierState(\"" + kAccel + "\") should be true if altKey is an accel modifier"); +wheelEvent = new WheelEvent("wheel", { metaKey: true }); +is(wheelEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_META, + "WheelEvent.getModifierState(\"" + kAccel + "\") should be true if metaKey is an accel modifier"); +wheelEvent = new WheelEvent("wheel", { ctrlKey: true, altKey: true, metaKey: true }); +is(wheelEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_CONTROL || + kAccelKeyCode == KeyboardEvent.DOM_VK_ALT || + kAccelKeyCode == KeyboardEvent.DOM_VK_META, + "WheelEvent.getModifierState(\"" + kAccel + "\") should be true if one of ctrlKey, altKey or metaKey is an accel modifier"); + +var keyboardEvent = new KeyboardEvent("keydown", {}); +is(keyboardEvent.getModifierState(kAccel), false, + "KeyboardEvent.getModifierState(\"" + kAccel + "\") should be false"); +keyboardEvent = new KeyboardEvent("keydown", { accelKey: true}); +is(keyboardEvent.getModifierState(kAccel), false, + "KeyboardEvent.getModifierState(\"" + kAccel + "\") should be false due to not supporting accelKey attribute"); +keyboardEvent = new KeyboardEvent("keydown", { ctrlKey: true }); +is(keyboardEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_CONTROL, + "KeyboardEvent.getModifierState(\"" + kAccel + "\") should be true if ctrlKey is an accel modifier"); +keyboardEvent = new KeyboardEvent("keydown", { altKey: true }); +is(keyboardEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_ALT, + "KeyboardEvent.getModifierState(\"" + kAccel + "\") should be true if altKey is an accel modifier"); +keyboardEvent = new KeyboardEvent("keydown", { metaKey: true }); +is(keyboardEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_META, + "KeyboardEvent.getModifierState(\"" + kAccel + "\") should be true if metaKey is an accel modifier"); +keyboardEvent = new KeyboardEvent("keydown", { ctrlKey: true, altKey: true, metaKey: true }); +is(keyboardEvent.getModifierState(kAccel), kAccelKeyCode == KeyboardEvent.DOM_VK_CONTROL || + kAccelKeyCode == KeyboardEvent.DOM_VK_ALT || + kAccelKeyCode == KeyboardEvent.DOM_VK_META, + "KeyboardEvent.getModifierState(\"" + kAccel + "\") should be true if one of ctrlKey, altKey or metaKey is an accel modifier"); + +// "Accel" virtual modifier must be supported with getModifierState(). So, any legacy init*Event()'s +// modifiers list argument shouldn't accept "Accel". +ok(typeof(KeyboardEvent.initKeyboardEvent) != "function", + "If we would support KeyboardEvent.initKeyboardEvent, we should test its modifier list argument doesn't accept \"" + kAccel + "\""); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_addEventListenerExtraArg.html b/dom/events/test/test_addEventListenerExtraArg.html new file mode 100644 index 000000000..e343f3c6d --- /dev/null +++ b/dom/events/test/test_addEventListenerExtraArg.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=828554 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 828554</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 828554 **/ + SimpleTest.waitForExplicitFinish(); + window.addEventListener("message", function() { + ok(true, "We got called"); + SimpleTest.finish(); + }, false, undefined); + window.postMessage("Hey there", "*"); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=828554">Mozilla Bug 828554</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_all_synthetic_events.html b/dom/events/test/test_all_synthetic_events.html new file mode 100644 index 000000000..90dbe95ee --- /dev/null +++ b/dom/events/test/test_all_synthetic_events.html @@ -0,0 +1,515 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test all synthetic events</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"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** + * kEventConstructors is a helper and database of all events. + * The sort order of the definition is by A to Z (ignore the Event postfix). + * + * XXX: should we move this into EventUtils.js? + * + * create: function or null. If this is null, it's impossible to create untrusted event for it. + * Otherwise, create(aName, aProps) returns an instance of the event initialized with aProps. + * aName specifies the event's type name. See each create() code for the detail of aProps. + */ +const kEventConstructors = { + Event: { create: function (aName, aProps) { + return new Event(aName, aProps); + }, + }, + AnimationEvent: { create: function (aName, aProps) { + return new AnimationEvent(aName, aProps); + }, + }, + AnimationPlaybackEvent: { create: function (aName, aProps) { + return new AnimationPlaybackEvent(aName, aProps); + }, + }, + AudioProcessingEvent: { create: null, // Cannot create untrusted event from JS. + }, + BeforeAfterKeyboardEvent: { create: function (aName, aProps) { + return new BeforeAfterKeyboardEvent(aName, aProps); + }, + }, + BeforeUnloadEvent: { create: function (aName, aProps) { + var e = document.createEvent("beforeunloadevent"); + e.initEvent(aName, aProps.bubbles, aProps.cancelable); + return e; + }, + }, + BlobEvent: { create: function (aName, aProps) { + return new BlobEvent(aName, aProps); + }, + }, + CallEvent: { create: function (aName, aProps) { + return new CallEvent(aName, aProps); + }, + }, + CallGroupErrorEvent: { create: function (aName, aProps) { + return new CallGroupErrorEvent(aName, aProps); + }, + }, + CFStateChangeEvent: { create: function (aName, aProps) { + return new CFStateChangeEvent(aName, aProps); + }, + }, + CloseEvent: { create: function (aName, aProps) { + return new CloseEvent(aName, aProps); + }, + }, + ClipboardEvent: { create: function (aName, aProps) { + return new ClipboardEvent(aName, aProps); + }, + }, + CommandEvent: { create: function (aName, aProps) { + var e = document.createEvent("commandevent"); + e.initCommandEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.command); + return e; + }, + }, + CompositionEvent: { create: function (aName, aProps) { + var e = document.createEvent("compositionevent"); + e.initCompositionEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.view, aProps.data, aProps.locale); + return e; + }, + }, + CustomEvent: { create: function (aName, aProps) { + return new CustomEvent(aName, aProps); + }, + }, + DataErrorEvent: { create: function (aName, aProps) { + return new DataErrorEvent(aName, aProps); + }, + }, + DataContainerEvent: { create: function (aName, aProps) { + var e = document.createEvent("datacontainerevent"); + e.initEvent(aName, aProps.bubbles, aProps.cancelable); + return e; + }, + }, + DeviceLightEvent: { create: function (aName, aProps) { + return new DeviceLightEvent(aName, aProps); + }, + }, + DeviceMotionEvent: { create: function (aName, aProps) { + var e = document.createEvent("devicemotionevent"); + e.initDeviceMotionEvent(aName, aProps.bubbles, aProps.cancelable, aProps.acceleration, + aProps.accelerationIncludingGravity, aProps.rotationRate, + aProps.interval || 0.0); + return e; + }, + }, + DeviceOrientationEvent: { create: function (aName, aProps) { + return new DeviceOrientationEvent(aName, aProps); + }, + }, + DeviceProximityEvent: { create: function (aName, aProps) { + return new DeviceProximityEvent(aName, aProps); + }, + }, + DownloadEvent: { create: function (aName, aProps) { + return new DownloadEvent(aName, aProps); + }, + }, + DragEvent: { create: function (aName, aProps) { + var e = document.createEvent("dragevent"); + e.initDragEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.view, aProps.detail, + aProps.screenX, aProps.screenY, + aProps.clientX, aProps.clientY, + aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey, + aProps.button, aProps.relatedTarget, aProps.dataTransfer); + return e; + }, + }, + ErrorEvent: { create: function (aName, aProps) { + return new ErrorEvent(aName, aProps); + }, + }, + FlyWebFetchEvent: { create: null, // Cannot create untrusted event from JS. + }, + FlyWebWebSocketEvent: { create: null, // Cannot create untrusted event from JS. + }, + FocusEvent: { create: function (aName, aProps) { + return new FocusEvent(aName, aProps); + }, + }, + FontFaceSetLoadEvent: { create: function (aName, aProps) { + return new FontFaceSetLoadEvent(aName, aProps); + }, + }, + GamepadEvent: { create: function (aName, aProps) { + return new GamepadEvent(aName, aProps); + }, + }, + GamepadAxisMoveEvent: { create: function (aName, aProps) { + return new GamepadAxisMoveEvent(aName, aProps); + }, + }, + GamepadButtonEvent: { create: function (aName, aProps) { + return new GamepadButtonEvent(aName, aProps); + }, + }, + HashChangeEvent: { create: function (aName, aProps) { + return new HashChangeEvent(aName, aProps); + }, + }, + IDBVersionChangeEvent: { create: function (aName, aProps) { + return new IDBVersionChangeEvent(aName, aProps); + }, + }, + ImageCaptureErrorEvent: { create: function (aName, aProps) { + return new ImageCaptureErrorEvent(aName, aProps); + }, + }, + InputEvent: { create: function (aName, aProps) { + return new InputEvent(aName, aProps); + }, + }, + KeyEvent: { create: function (aName, aProps) { + return new KeyboardEvent(aName, aProps); + }, + }, + KeyboardEvent: { create: function (aName, aProps) { + return new KeyboardEvent(aName, aProps); + }, + }, + MediaEncryptedEvent: { create: function (aName, aProps) { + return new MediaEncryptedEvent(aName, aProps); + }, + }, + MediaKeyMessageEvent: { create: function (aName, aProps) { + return new MediaKeyMessageEvent(aName, { + messageType: "license-request", + message: new ArrayBuffer(0) + }); + }, + }, + MediaStreamEvent: { create: function (aName, aProps) { + return new MediaStreamEvent(aName, aProps); + }, + }, + MediaStreamTrackEvent: { + // Difficult to test required arguments. + }, + MessageEvent: { create: function (aName, aProps) { + var e = new MessageEvent("messageevent", { bubbles: aProps.bubbles, + cancelable: aProps.cancelable, data: aProps.data, origin: aProps.origin, + lastEventId: aProps.lastEventId, source: aProps.source }); + return e; + }, + }, + MouseEvent: { create: function (aName, aProps) { + return new MouseEvent(aName, aProps); + }, + }, + MouseScrollEvent: { create: function (aName, aProps) { + var e = document.createEvent("mousescrollevents"); + e.initMouseScrollEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.view, aProps.detail, + aProps.screenX, aProps.screenY, + aProps.clientX, aProps.clientY, + aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey, + aProps.button, aProps.relatedTarget, aProps.axis); + return e; + }, + }, + MozApplicationEvent: { create: function (aName, aProps) { + return new MozApplicationEvent(aName, aProps); + }, + }, + MozClirModeEvent: { create: function (aName, aProps) { + return new MozClirModeEvent(aName, aProps); + }, + }, + MozContactChangeEvent: { create: function (aName, aProps) { + return new MozContactChangeEvent(aName, aProps); + }, + }, + MozEmergencyCbModeEvent: { create: function (aName, aProps) { + return new MozEmergencyCbModeEvent(aName, aProps); + }, + }, + MozMessageDeletedEvent: { create: function (aName, aProps) { + return new MozMessageDeletedEvent(aName, aProps); + }, + }, + MozMmsEvent: { create: function (aName, aProps) { + return new MozMmsEvent(aName, aProps); + }, + }, + MozOtaStatusEvent: { create: function (aName, aProps) { + return new MozOtaStatusEvent(aName, aProps); + }, + }, + MozSettingsEvent: { create: function (aName, aProps) { + return new MozSettingsEvent(aName, aProps); + }, + }, + MozSettingsTransactionEvent: { create: function (aName, aProps) { + return new MozSettingsTransactionEvent(aName, aProps); + }, + }, + MozSmsEvent: { create: function (aName, aProps) { + return new MozSmsEvent(aName, aProps); + }, + }, + MozStkCommandEvent: { create: function (aName, aProps) { + return new MozStkCommandEvent(aName, aProps); + }, + }, + MozWifiConnectionInfoEvent: { create: function (aName, aProps) { + return new MozWifiConnectionInfoEvent(aName, aProps); + }, + }, + MozWifiStatusChangeEvent: { create: function (aName, aProps) { + return new MozWifiStatusChangeEvent(aName, aProps); + }, + }, + MozWifiStationInfoEvent: { create: function (aName, aProps) { + return new MozWifiStationInfoEvent(aName, aProps); + }, + }, + MutationEvent: { create: function (aName, aProps) { + var e = document.createEvent("mutationevent"); + e.initMutationEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.relatedNode, aProps.prevValue, aProps.newValue, + aProps.attrName, aProps.attrChange); + return e; + }, + }, + NotifyPaintEvent: { create: function (aName, aProps) { + var e = document.createEvent("notifypaintevent"); + e.initEvent(aName, aProps.bubbles, aProps.cancelable); + return e; + }, + }, + OfflineAudioCompletionEvent: { create: null, // Cannot create untrusted event from JS. + }, + PageTransitionEvent: { create: function (aName, aProps) { + return new PageTransitionEvent(aName, aProps); + }, + }, + PointerEvent: { create: function (aName, aProps) { + return new PointerEvent(aName, aProps); + }, + }, + PopStateEvent: { create: function (aName, aProps) { + return new PopStateEvent(aName, aProps); + }, + }, + PopupBlockedEvent: { create: function (aName, aProps) { + return new PopupBlockedEvent(aName, aProps); + }, + }, + ProgressEvent: { create: function (aName, aProps) { + return new ProgressEvent(aName, aProps); + }, + }, + RecordErrorEvent: { create: function (aName, aProps) { + return new RecordErrorEvent(aName, aProps); + }, + }, + RTCDataChannelEvent: { create: function (aName, aProps) { + return new RTCDataChannelEvent(aName, aProps); + }, + }, + RTCDTMFToneChangeEvent: { create: function (aName, aProps) { + return new RTCDTMFToneChangeEvent(aName, aProps); + }, + }, + RTCPeerConnectionIceEvent: { create: function (aName, aProps) { + return new RTCPeerConnectionIceEvent(aName, aProps); + }, + }, + RTCTrackEvent: { + // Difficult to test required arguments. + }, + ScrollAreaEvent: { create: function (aName, aProps) { + var e = document.createEvent("scrollareaevent"); + e.initScrollAreaEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.view, aProps.details, + aProps.x || 0.0, aProps.y || 0.0, + aProps.width || 0.0, aProps.height || 0.0); + return e; + }, + }, + ServiceWorkerMessageEvent: { create: function (aName, aProps) { + var e = new ServiceWorkerMessageEvent("serviceworkermessageevent", { bubbles: aProps.bubbles, + cancelable: aProps.cancelable, data: aProps.data, origin: aProps.origin, + lastEventId: aProps.lastEventId, source: aProps.source }); + return e; + }, + }, + SimpleGestureEvent: { create: function (aName, aProps) { + var e = document.createEvent("simplegestureevent"); + e.initSimpleGestureEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.view, aProps.detail, + aProps.screenX, aProps.screenY, + aProps.clientX, aProps.clientY, + aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey, + aProps.button, aProps.relatedTarget, + aProps.allowedDirections, aProps.direction, aProps.delta || 0.0, + aProps.clickCount); + return e; + }, + }, + SpeechRecognitionError: { create: function (aName, aProps) { + return new SpeechRecognitionError(aName, aProps); + }, + }, + SpeechRecognitionEvent: { create: function (aName, aProps) { + return new SpeechRecognitionEvent(aName, aProps); + }, + }, + SpeechSynthesisErrorEvent: { create: function (aName, aProps) { + aProps.error = "synthesis-unavailable"; + aProps.utterance = new SpeechSynthesisUtterance("Hello World"); + return new SpeechSynthesisErrorEvent(aName, aProps); + }, + }, + SpeechSynthesisEvent: { create: function (aName, aProps) { + aProps.utterance = new SpeechSynthesisUtterance("Hello World"); + return new SpeechSynthesisEvent(aName, aProps); + }, + }, + StorageEvent: { create: function (aName, aProps) { + return new StorageEvent(aName, aProps); + }, + }, + StyleRuleChangeEvent: { create: function (aName, aProps) { + return new StyleRuleChangeEvent(aName, aProps); + }, + chromeOnly: true, + }, + StyleSheetApplicableStateChangeEvent: { create: function (aName, aProps) { + return new StyleSheetApplicableStateChangeEvent(aName, aProps); + }, + chromeOnly: true, + }, + StyleSheetChangeEvent: { create: function (aName, aProps) { + return new StyleSheetChangeEvent(aName, aProps); + }, + chromeOnly: true, + }, + SVGZoomEvent: { create: function (aName, aProps) { + var e = document.createEvent("svgzoomevent"); + e.initUIEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.view, aProps.detail); + return e; + }, + }, + TCPSocketErrorEvent: { create: function(aName, aProps) { + return new TCPSocketErrorEvent(aName, aProps); + }, + }, + TCPSocketEvent: { create: function(aName, aProps) { + return new TCPSocketEvent(aName, aProps); + }, + }, + TCPServerSocketEvent: { create: function(aName, aProps) { + return new TCPServerSocketEvent(aName, aProps); + }, + }, + TimeEvent: { create: function (aName, aProps) { + var e = document.createEvent("timeevent"); + e.initTimeEvent(aName, aProps.view, aProps.detail); + return e; + }, + }, + TouchEvent: { create: function (aName, aProps) { + var e = document.createEvent("touchevent"); + e.initTouchEvent(aName, aProps.bubbles, aProps.cancelable, + aProps.view, aProps.detail, + aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey, + aProps.touches, aProps.targetTouches, aProps.changedTouches); + return e; + }, + }, + TrackEvent: { create: function (aName, aProps) { + return new TrackEvent(aName, aProps); + }, + }, + TransitionEvent: { create: function (aName, aProps) { + return new TransitionEvent(aName, aProps); + }, + }, + UIEvent: { create: function (aName, aProps) { + return new UIEvent(aName, aProps); + }, + }, + UserProximityEvent: { create: function (aName, aProps) { + return new UserProximityEvent(aName, aProps); + }, + }, + USSDReceivedEvent: { create: function (aName, aProps) { + return new USSDReceivedEvent(aName, aProps); + }, + }, + WheelEvent: { create: function (aName, aProps) { + return new WheelEvent(aName, aProps); + }, + }, + WebGLContextEvent: { create: function (aName, aProps) { + return new WebGLContextEvent(aName, aProps); + }, + }, +}; + +for (var name of Object.keys(kEventConstructors)) { + if (!kEventConstructors[name].chromeOnly) { + continue; + } + if (window[name]) { + ok(false, name + " should be chrome only."); + } + window[name] = SpecialPowers.unwrap(SpecialPowers.wrap(window)[name]); +} + +var props = Object.getOwnPropertyNames(window); +for (var i = 0; i < props.length; i++) { + // Assume that event object must be named as "FooBarEvent". + if (!props[i].match(/^([A-Z][a-zA-Z]+)?Event$/)) { + continue; + } + if (!kEventConstructors[props[i]]) { + ok(false, "Unknown event found: " + props[i]); + continue; + } + if (!kEventConstructors[props[i]].create) { + todo(false, "Cannot create untrusted event of " + props[i]); + continue; + } + ok(true, "Creating " + props[i] + "..."); + var event = kEventConstructors[props[i]].create("foo", {}); + if (!event) { + ok(false, "Failed to create untrusted event: " + props[i]); + continue; + } + if (typeof(event.getModifierState) == "function") { + const kModifiers = [ "Shift", "Control", "Alt", "AltGr", "Meta", "CapsLock", "ScrollLock", "NumLock", "OS", "Fn", "FnLock", "Symbol", "SymbolLock" ]; + for (var j = 0; j < kModifiers.length; j++) { + ok(true, "Calling " + props[i] + ".getModifierState(" + kModifiers[j] + ")..."); + var modifierState = event.getModifierState(kModifiers[j]); + ok(true, props[i] + ".getModifierState(" + kModifiers[j] + ") = " + modifierState); + } + } +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug1003432.html b/dom/events/test/test_bug1003432.html new file mode 100644 index 000000000..7ffd3f7c4 --- /dev/null +++ b/dom/events/test/test_bug1003432.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1003432 +--> +<head> + <title>Test for Bug 1003432</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1003432">Mozilla Bug 1003432</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 1003432 **/ +// Test CustomEvent on worker +SimpleTest.waitForExplicitFinish(); +var worker = new Worker("test_bug1003432.js"); +ok(worker, "Should have worker!"); + +var count = 0; +worker.onmessage = function(evt) { + is(evt.data.type, "foobar", "Should get 'foobar' event!"); + is(evt.data.detail, "test", "Detail should be 'test'."); + ok(evt.data.bubbles, "Event should bubble!"); + ok(evt.data.cancelable, "Event should be cancelable."); + + // wait for test results of constructor and initCustomEvent + if (++count == 2) { + worker.terminate(); + SimpleTest.finish(); + } +}; + +worker.postMessage(""); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug1003432.js b/dom/events/test/test_bug1003432.js new file mode 100644 index 000000000..1ace8fefd --- /dev/null +++ b/dom/events/test/test_bug1003432.js @@ -0,0 +1,23 @@ +addEventListener("foobar", + function(evt) { + postMessage( + { + type: evt.type, + bubbles: evt.bubbles, + cancelable: evt.cancelable, + detail: evt.detail + }); + }, true); + +addEventListener("message", + function(evt) { + // Test the constructor of CustomEvent + var e = new CustomEvent("foobar", + {bubbles:true, cancelable: true, detail:"test"}); + dispatchEvent(e); + + // Test initCustomEvent + e = new CustomEvent("foobar"); + e.initCustomEvent("foobar", true, true, "test"); + dispatchEvent(e); + }, true); diff --git a/dom/events/test/test_bug1013412.html b/dom/events/test/test_bug1013412.html new file mode 100644 index 000000000..b63eb615e --- /dev/null +++ b/dom/events/test/test_bug1013412.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1013412 +--> +<head> + <title>Test for Bug 1013412</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"/> + <style> + #content { + height: 800px; + overflow: scroll; + } + + #scroller { + height: 2000px; + background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px); + } + + #scrollbox { + margin-top: 200px; + width: 500px; + height: 500px; + border-radius: 250px; + box-shadow: inset 0 0 0 60px #555; + background: #777; + } + + #circle { + position: relative; + left: 240px; + top: 20px; + border: 10px solid white; + border-radius: 10px; + width: 0px; + height: 0px; + transform-origin: 10px 230px; + will-change: transform; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1013412">Mozilla Bug 1013412</a> +<p id="display"></p> +<div id="content"> + <p>Scrolling the page should be async, but scrolling over the dark circle should not scroll the page and instead rotate the white ball.</p> + <div id="scroller"> + <div id="scrollbox"> + <div id="circle"></div> + </div> + </div> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for Bug 1013412 **/ + +var rotation = 0; +var rotationAdjusted = false; + +var incrementForMode = function (mode) { + switch (mode) { + case WheelEvent.DOM_DELTA_PIXEL: return 1; + case WheelEvent.DOM_DELTA_LINE: return 15; + case WheelEvent.DOM_DELTA_PAGE: return 400; + } + return 0; +}; + +document.getElementById("scrollbox").addEventListener("wheel", function (e) { + rotation += e.deltaY * incrementForMode(e.deltaMode) * 0.2; + document.getElementById("circle").style.transform = "rotate(" + rotation + "deg)"; + rotationAdjusted = true; + e.preventDefault(); +}); + +var iteration = 0; +function runTest() { + var content = document.getElementById('content'); + if (iteration < 300) { // enough iterations that we would scroll to the bottom of 'content' + iteration++; + synthesizeWheel(content, 100, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + setTimeout(runTest, 0); + return; + } + var scrollbox = document.getElementById('scrollbox'); + is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe"); + is(rotationAdjusted, true, "The rotation should have been adjusted"); + SimpleTest.finish(); +} + +function startTest() { + // If we allow smooth scrolling the "smooth" scrolling may cause the page to + // glide past the scrollbox (which is supposed to stop the scrolling) and so + // we might end up at the bottom of the page. + SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]}, runTest); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(startTest, window); + +</script> +</pre> + +</body> +</html> diff --git a/dom/events/test/test_bug1017086_disable.html b/dom/events/test/test_bug1017086_disable.html new file mode 100644 index 000000000..f13d2aff3 --- /dev/null +++ b/dom/events/test/test_bug1017086_disable.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1017086 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1017086</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + /** Test for Bug 1017086 **/ + var pointer_events_enabled = false; + function prepareTest() { + SimpleTest.waitForExplicitFinish(); + turnOnOffPointerEvents(startTest); + } + function turnOnOffPointerEvents(callback) { + SpecialPowers.pushPrefEnv({ + "set": [ + ["dom.w3c_pointer_events.enabled", pointer_events_enabled] + ] + }, callback); + } + function startTest() { + var iframe = document.getElementById("testFrame"); + iframe.src = "bug1017086_inner.html"; + } + function part_of_checks(pointer_events, check, window, document, testelem) { + for(item in pointer_events) { check(false, pointer_events[item], window, "window"); } + for(item in pointer_events) { check(false, pointer_events[item], document, "document"); } + for(item in pointer_events) { check(false, pointer_events[item], testelem, "element"); } + SimpleTest.finish(); + } + </script> + </head> + <body onload="prepareTest()"> + <iframe id="testFrame" height="700" width="700"></iframe> + </body> +</html> diff --git a/dom/events/test/test_bug1017086_enable.html b/dom/events/test/test_bug1017086_enable.html new file mode 100644 index 000000000..7930757a6 --- /dev/null +++ b/dom/events/test/test_bug1017086_enable.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1017086 +--> + <head> + <meta charset="utf-8"> + <title>Test for Bug 1017086</title> + <meta name="author" content="Maksim Lebedev" /> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + /** Test for Bug 1017086 **/ + var pointer_events_enabled = true; + function prepareTest() { + SimpleTest.waitForExplicitFinish(); + turnOnOffPointerEvents(startTest); + } + function turnOnOffPointerEvents(callback) { + SpecialPowers.pushPrefEnv({ + "set": [ + ["dom.w3c_pointer_events.enabled", pointer_events_enabled] + ] + }, callback); + } + function startTest() { + var iframe = document.getElementById("testFrame"); + iframe.src = "bug1017086_inner.html"; + } + function part_of_checks(pointer_events, check, window, document, testelem) { + for(item in pointer_events) { check(true, pointer_events[item], window, "window"); } + /** TODO + for(item in pointer_events) { check(false, pointer_events[item], document, "document"); } + **/ + for(item in pointer_events) { check(true, pointer_events[item], testelem, "element"); } + SimpleTest.finish(); + } + </script> + </head> + <body onload="prepareTest()"> + <iframe id="testFrame" height="700" width="700"></iframe> + </body> +</html> diff --git a/dom/events/test/test_bug1037990.html b/dom/events/test/test_bug1037990.html new file mode 100644 index 000000000..025c8744b --- /dev/null +++ b/dom/events/test/test_bug1037990.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1037990 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1037990</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1037990">Mozilla Bug 1037990</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<script type="application/javascript"> + + /** Test for Bug 1037990 **/ + + var pre, node, detachedAccess, attachedAcess; + + node = document.createElement('a'); + node.href = 'http://example.org'; + node.accessKey = 'e'; + detachedAccess = node.accessKeyLabel; + info('[window.document] detached: ' + detachedAccess); + document.body.appendChild(node); + attachedAcess = node.accessKeyLabel; + info('[window.document] attached: ' + attachedAcess); + is(detachedAccess, attachedAcess, "Both values are same for the window.document"); + + var parser=new DOMParser(); + var xmlDoc=parser.parseFromString("<root></root>","text/xml"); + var nn = xmlDoc.createElementNS('http://www.w3.org/1999/xhtml','a'); + nn.setAttribute('accesskey','t') + detachedAccess = nn.accessKeyLabel; + info('[xmlDoc] detached: ' + detachedAccess); + var root = xmlDoc.getElementsByTagName('root')[0]; + root.appendChild(nn); + attachedAcess = nn.accessKeyLabel; + info('[xmlDoc] attached: ' + attachedAcess); + is(detachedAccess, attachedAcess, "Both values are same for the xmlDoc"); + + var myDoc = new Document(); + var newnode = myDoc.createElementNS('http://www.w3.org/1999/xhtml','a'); + newnode.href = 'http://example.org'; + newnode.accessKey = 'f'; + detachedAccess = newnode.accessKeyLabel; + info('[new document] detached: ' + detachedAccess); + myDoc.appendChild(newnode); + attachedAcess = newnode.accessKeyLabel; + info('[new document] attached: ' + attachedAcess); + is(detachedAccess, attachedAcess, "Both values are same for the new Document()"); + +</script> +</body> +</html> diff --git a/dom/events/test/test_bug1079236.html b/dom/events/test/test_bug1079236.html new file mode 100644 index 000000000..7957823e6 --- /dev/null +++ b/dom/events/test/test_bug1079236.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1079236 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1079236</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"/> + <script type="application/javascript"> + + /** Test for Bug 1079236 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); + +function runTests() { + var c = document.getElementById("content"); + var sr = c.createShadowRoot(); + sr.innerHTML = "<input type='file'" + ">"; + var file = sr.firstChild; + is(file.type, "file"); + file.offsetLeft; // Flush layout because dispatching mouse events. + document.body.onmousemove = function(e) { + is(e.target, c, "Event target should be the element in non-Shadow DOM"); + if (e.originalTarget == file) { + is(e.originalTarget, file, + "type='file' implementation doesn't seem to have native anonymous content"); + } else { + var wrapped = SpecialPowers.wrap(e.originalTarget); + isnot(wrapped, file, "Shouldn't have the same event.target and event.originalTarget"); + } + + ok(!("composedTarget" in e), "Events shouldn't have composedTarget in non-chrome context!"); + e = SpecialPowers.wrap(e); + var composedTarget = SpecialPowers.unwrap(e.composedTarget); + ok(composedTarget, file, "composedTarget should be the file object."); + + SimpleTest.finish(); + } + + var r = file.getBoundingClientRect(); + synthesizeMouse(file, r.width / 6, r.height / 2, { type: "mousemove"} ); + document.body.onmousemove = null; +} + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1079236">Mozilla Bug 1079236</a> +<p id="display"></p> +<div id="content"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug1096146.html b/dom/events/test/test_bug1096146.html new file mode 100644 index 000000000..85b7a35f0 --- /dev/null +++ b/dom/events/test/test_bug1096146.html @@ -0,0 +1,187 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1096146 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1096146</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"/> +</head> +<body onload="runTests();"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1096146">Mozilla Bug 1096146</a> +<div id="content" style="display: none;"> +</div> +<pre id="test"> +<script type="application/javascript"> + +const kKeydownEvent = 0x1; +const kScrollEvent = 0x2; + +var gCurrentTest = 0; +var gNumEvents = 0; +var kTests = [ + { + description: "no preventDefault at 'mozbrowserbeforekeydown'", + expectedEvents: kKeydownEvent | kScrollEvent, + resultEvents: 0x0, + doPreventDefault: false + }, + { + description: "do preventDefault at 'mozbrowserbeforekeydown'", + expectedEvents: 0x0, + resultEvents: 0x0, + doPreventDefault: true + } +] + +function frameScript() +{ + function handler(e) { + sendSyncMessage("forwardevent", { type: e.type }); + } + addEventListener('keydown', handler); + addEventListener('scroll', handler); +} + + +function waitAndVerifyResult(count) { + if (gNumEvents >= 3 || count > 10) { + is(kTests[gCurrentTest].resultEvents, + kTests[gCurrentTest].expectedEvents, + "verify result"); + runTests(); + } else { + SimpleTest.requestFlakyTimeout("We must delay to wait for scroll/keydown events."); + setTimeout(() => waitAndVerifyResult(count + 1), 100); + } +} + +function testDefaultAction() +{ + synthesizeKey('VK_END', {}, document.getElementById("embedded").contentWindow); + waitAndVerifyResult(0); +} + +function prepareTest() +{ + gNumEvents = 0; + + var handler; + if (kTests[gCurrentTest].doPreventDefault) { + handler = preventDefaultHandler; + } else { + handler = noPreventDefaultHandler; + } + window.addEventListener("mozbrowserbeforekeydown", handler); + + var iframe = document.createElement("iframe"); + iframe.id = "embedded"; + iframe.src = "bug1096146_embedded.html"; + iframe.setAttribute("remote", "false"); + SpecialPowers.wrap(iframe).mozbrowser = true; + + iframe.addEventListener("mozbrowserloadend", function onloadend() { + iframe.removeEventListener("mozbrowserloadend", onloadend); + iframe.focus(); + var mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + mm.addMessageListener("forwardevent", function(msg) { + var value = 0; + switch(msg.json.type) { + case "scroll": + ++gNumEvents; + value = kScrollEvent; + break; + case "keydown": + ++gNumEvents; + value = kKeydownEvent; + break; + default: + ok(false, "unexpected event"); + } + + kTests[gCurrentTest].resultEvents = + kTests[gCurrentTest].resultEvents | value; + }); + mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false); + runTests(); + return; + }); + + document.body.appendChild(iframe); +} + +function preventDefaultHandler(evt) +{ + ok(true, "receive " + evt.type + " and do preventDefault."); + ++gNumEvents; + evt.preventDefault(); +} + +function noPreventDefaultHandler(evt) +{ + ok(true, "receive " + evt.type + "."); + ++gNumEvents; +} + +function teardownHandler() +{ + var handler; + if (kTests[gCurrentTest].doPreventDefault) { + handler = preventDefaultHandler; + } else { + handler = noPreventDefaultHandler; + } + window.removeEventListener("mozbrowserbeforekeydown", handler); + document.body.removeChild(document.getElementById("embedded")); + + runTests(); +} + +var tests = [ + function addPermissions() { + SpecialPowers.pushPermissions( + [{ type: "before-after-keyboard-event", allow: true, context: document }, + { type: "browser", allow: true, context: document }], + runTests); + }, + function addPreferences() { + SpecialPowers.pushPrefEnv( + { "set": [["dom.beforeAfterKeyboardEvent.enabled", true], + ["dom.mozBrowserFramesEnabled", true], + ["dom.ipc.tabs.disabled", false], + ["network.disable.ipc.security", true]] }, + runTests); + }, + + prepareTest, + testDefaultAction, + teardownHandler, + + function() { + gCurrentTest++; + runTests(); + }, + prepareTest, + testDefaultAction, + teardownHandler +]; + +function runTests() +{ + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug1128787-1.html b/dom/events/test/test_bug1128787-1.html new file mode 100644 index 000000000..947bace38 --- /dev/null +++ b/dom/events/test/test_bug1128787-1.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1128787 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1128787</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1128787 **/ + SimpleTest.waitForExplicitFinish(); + + window.onload = function (aEvent) { + var blurEventFired = false; + var input = document.getElementsByTagName("input")[0]; + input.addEventListener("blur", function (aEvent) { + input.removeEventListener("blur", arguments.callee); + ok(true, "input element gets blur event correctly"); + + var utils = SpecialPowers.getDOMWindowUtils(window); + is(utils.IMEStatus, utils.IME_STATUS_ENABLED, "IME should be enabled"); + + SimpleTest.executeSoon(function () { + document.designMode = "off"; + + // XXX Should be fixed. + todo_is(utils.IMEStatus, utils.IME_STATUS_DISABLED, "IME should be disabled"); + + SimpleTest.finish(); + }); + }); + document.designMode = "on"; + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1128787">Mozilla Bug 1128787</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<input type="button"/> +<script> +var input = document.getElementsByTagName("input")[0]; +input.focus(); +</script> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug1128787-2.html b/dom/events/test/test_bug1128787-2.html new file mode 100644 index 000000000..3e2a6cada --- /dev/null +++ b/dom/events/test/test_bug1128787-2.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1128787 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1128787</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1128787 **/ + SimpleTest.waitForExplicitFinish(); + + window.onload = function (aEvent) { + var blurEventFired = false; + var input = document.getElementsByTagName("input")[0]; + input.addEventListener("blur", function (aEvent) { + input.removeEventListener("blur", arguments.callee); + ok(true, "input element gets blur event correctly"); + + var utils = SpecialPowers.getDOMWindowUtils(window); + is(utils.IMEStatus, utils.IME_STATUS_ENABLED, "IME should be enabled"); + + SimpleTest.executeSoon(function () { + document.designMode = "off"; + + // XXX Should be fixed. + todo_is(utils.IMEStatus, utils.IME_STATUS_DISABLED, "IME should be disabled"); + + SimpleTest.finish(); + }); + }); + document.designMode = "on"; + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1128787">Mozilla Bug 1128787</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<p contenteditable="true"></p> +<input type="button"/> +<script> +var input = document.getElementsByTagName("input")[0]; +input.focus(); +</script> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug1128787-3.html b/dom/events/test/test_bug1128787-3.html new file mode 100644 index 000000000..a7b86cdb6 --- /dev/null +++ b/dom/events/test/test_bug1128787-3.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1128787 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1128787</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1128787 **/ + SimpleTest.waitForExplicitFinish(); + + window.onload = function (aEvent) { + var blurEventFired = false; + var input = document.getElementsByTagName("input")[0]; + input.addEventListener("blur", function (aEvent) { + input.removeEventListener("blur", arguments.callee); + ok(true, "input element gets blur event correctly"); + + var utils = SpecialPowers.getDOMWindowUtils(window); + is(utils.IMEStatus, utils.IME_STATUS_ENABLED, "IME should be enabled"); + + is(document.designMode, "on", + "designMode should be \"on\" when blur event caused by enabling designMode is fired"); + document.designMode = "off"; + is(document.designMode, "off", + "designMode should become \"off\" even if it's reset by the blur event handler caused by enabling designMode"); + + todo_is(utils.IMEStatus, utils.IME_STATUS_DISABLED, "IME should be disabled"); + SimpleTest.finish(); + }); + document.designMode = "on"; + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1128787">Mozilla Bug 1128787</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<input type="button"/> +<script> +var input = document.getElementsByTagName("input")[0]; +input.focus(); +</script> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug1145910.html b/dom/events/test/test_bug1145910.html new file mode 100644 index 000000000..b87104eb5 --- /dev/null +++ b/dom/events/test/test_bug1145910.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1145910 +--> +<head> + <title>Test for Bug 1145910</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"/> +</head> +<body> +<style> +div:active { + color: rgb(0, 255, 0); +} +</style> +<div id="host">Foo</div> +<script type="application/javascript"> + +/** Test for Bug 1145910 **/ +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + var host = document.getElementById("host"); + var shadow = host.createShadowRoot(); + shadow.innerHTML = '<style>div:active { color: rgb(0, 255, 0); }</style><div id="inner">Bar</div>'; + var inner = shadow.getElementById("inner"); + + is(window.getComputedStyle(host).color, "rgb(0, 0, 0)", "The host should not be active"); + is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "The div inside the shadow root should not be active."); + + synthesizeMouseAtCenter(host, { type: "mousedown" }); + + is(window.getComputedStyle(inner).color, "rgb(0, 255, 0)", "Div inside shadow root should be active."); + is(window.getComputedStyle(host).color, "rgb(0, 255, 0)", "Host should be active when the inner div is made active."); + + synthesizeMouseAtCenter(host, { type: "mouseup" }); + + is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "Div inside shadow root should no longer be active."); + is(window.getComputedStyle(host).color, "rgb(0, 0, 0)", "Host should no longer be active."); + + SimpleTest.finish(); +}); + +</script> +</body> +</html> diff --git a/dom/events/test/test_bug1150308.html b/dom/events/test/test_bug1150308.html new file mode 100644 index 000000000..c90e1055c --- /dev/null +++ b/dom/events/test/test_bug1150308.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1150308 +--> +<head> + <title>Test for Bug 1150308</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"/> +</head> +<body> +<div id="host"><span id="distributeme">Foo</span></div> +<script type="application/javascript"> + +/** Test for Bug 1150308 **/ +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + var host = document.getElementById("host"); + var shadow = host.createShadowRoot(); + shadow.innerHTML = '<style>.bar:active { color: rgb(0, 255, 0); }</style><div class="bar" id="inner"><content></content></div>'; + var inner = shadow.getElementById("inner"); + var distributed = document.getElementById("distributeme"); + + is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "The div inside the shadow root should not be active."); + + synthesizeMouseAtCenter(distributed, { type: "mousedown" }); + + is(window.getComputedStyle(inner).color, "rgb(0, 255, 0)", "Div inside shadow root should be active."); + + synthesizeMouseAtCenter(distributed, { type: "mouseup" }); + + is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "Div inside shadow root should no longer be active."); + + SimpleTest.finish(); +}); + +</script> +</body> +</html> diff --git a/dom/events/test/test_bug1248459.html b/dom/events/test/test_bug1248459.html new file mode 100644 index 000000000..4eefa8e12 --- /dev/null +++ b/dom/events/test/test_bug1248459.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248459 +--> +<head> + <title>Test for Bug 1248459</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"/> +</head> +<body> +<input id="input" value="foo"> +<div id="div">bar</div> +<script type="application/javascript"> + +/** Test for Bug 1248459 **/ +/** + * The bug occurs when a piece of text outside of the editor's root element is + * somehow selected when the editor is focused. In the bug's case, it's the + * placeholder anonymous div that's selected. In this test's case, it's a + * document div that's selected. + */ +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + var div = document.getElementById("div"); + var input = document.getElementById("input"); + + input.appendChild(div); + input.focus(); + + var editor = SpecialPowers.wrap(input).editor; + var sel = editor.selection; + + sel.selectAllChildren(editor.rootElement); + var result = synthesizeQuerySelectedText(); + + ok(result.succeeded, "Query selected text should succeed"); + is(result.offset, 0, "Selected text should be at offset 0"); + is(result.text, "foo", "Selected text should match"); + + var range = document.createRange(); + range.selectNode(div); + + sel.removeAllRanges(); + sel.addRange(range); + + result = synthesizeQuerySelectedText(); + + ok(!result.succeeded, "Query out-of-bounds selection should fail"); + + SimpleTest.finish(); +}); + +</script> +</body> +</html> diff --git a/dom/events/test/test_bug1264380.html b/dom/events/test/test_bug1264380.html new file mode 100644 index 000000000..76b9fb46b --- /dev/null +++ b/dom/events/test/test_bug1264380.html @@ -0,0 +1,54 @@ +<html> +<head> + <title>Test the dragstart event on the anchor in side shadow DOM</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> +<script> + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({"set": [ + ["dom.webcomponents.enabled", true] +]}); + +function runTests() +{ + let dragService = SpecialPowers.Cc["@mozilla.org/widget/dragservice;1"]. + getService(SpecialPowers.Ci.nsIDragService); + + let shadow = document.querySelector('#outter').createShadowRoot(); + let target = document.createElement('a'); + let linkText = document.createTextNode("Drag me if you can!"); + target.appendChild(linkText); + target.href = "http://www.mozilla.org/"; + shadow.appendChild(target); + + let dataTransfer; + let trapDrag = function(event) { + ok(true, "Got dragstart event"); + dataTransfer = event.dataTransfer; + ok(dataTransfer, "DataTransfer object is available."); + is(dataTransfer.mozItemCount, 1, "initial link item count"); + is(dataTransfer.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list"); + is(dataTransfer.getData("text/plain"), "http://www.mozilla.org/", "link text/plain"); + } + + ok(!dragService.getCurrentSession(), "There shouldn't be a drag session!"); + window.addEventListener("dragstart", trapDrag, true); + synthesizeMouse(target, 2, 2, { type: "mousedown" }); + synthesizeMouse(target, 11, 11, { type: "mousemove" }); + synthesizeMouse(target, 20, 20, { type: "mousemove" }); + window.removeEventListener("dragstart", trapDrag, true); + ok(dragService.getCurrentSession(), "Drag session is available."); + dragService.endDragSession(false); + ok(!dragService.getCurrentSession(), "There shouldn't be a drag session anymore!"); + SimpleTest.finish(); +} + +</script> + +<body onload="window.setTimeout(runTests, 0);"> +<div id="outter"/> +</body> +</html> diff --git a/dom/events/test/test_bug1298970.html b/dom/events/test/test_bug1298970.html new file mode 100644 index 000000000..6b00aabff --- /dev/null +++ b/dom/events/test/test_bug1298970.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1298970 +--> +<head> + <title>Test for Bug 1298970</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1298970">Mozilla Bug 1298970</a> +<p id="display"></p> +<div id="inner"></div> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1298970 **/ +var target = document.getElementById("inner"); +var event = new Event("test", { bubbles: true, cancelable: true }); + +is(event.cancelBubble, false, "Event.cancelBubble should be false by default"); + +target.addEventListener("test", (e) => { + e.stopPropagation(); + is(e.cancelBubble, true, "Event.cancelBubble should be true after stopPropagation"); +}, true); + +target.dispatchEvent(event); + +</script> +</body> +</html> + diff --git a/dom/events/test/test_bug226361.xhtml b/dom/events/test/test_bug226361.xhtml new file mode 100644 index 000000000..5095bb193 --- /dev/null +++ b/dom/events/test/test_bug226361.xhtml @@ -0,0 +1,82 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=226361 +--> +<head> + <title>Test for Bug 226361</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body id="body1"> +<p id="display"> + + <a id="b1" tabindex="1" href="http://home.mozilla.org">start</a><br /> +<br /> + +<iframe id="iframe" tabindex="2" src="bug226361_iframe.xhtml"></iframe> + + <a id="b2" tabindex="3" href="http://home.mozilla.org">end</a> + +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ + +/** Test for Bug 226361 **/ + +// accessibility.tabfocus must be set to value 7 before running test also +// on a mac. +function setTabFocus() { + SpecialPowers.pushPrefEnv({ set: [[ "accessibility.tabfocus", 7 ]] }, doTest); +} + +// ================================= + +var doc = document; +function tab_to(id) { + var wu = SpecialPowers.DOMWindowUtils; + wu.sendKeyEvent('keypress', 9, 0, 0); + is(doc.activeElement.id, id, "element with id=" + id + " should have focus"); +} + +function tab_iframe() { + doc = document; + tab_to('iframe'); + + // inside iframe + doc = document.getElementById('iframe').contentDocument + tab_to('a3');tab_to('a5');tab_to('a1');tab_to('a2');tab_to('a4'); +} + + +function doTest() { + window.getSelection().removeAllRanges(); + document.getElementById('body1').focus(); + is(document.activeElement.id, document.body.id, "body element should be focused"); + + doc = document; + tab_to('b1'); + + tab_iframe(); + + doc=document + document.getElementById('iframe').focus() + tab_to('b2'); + // Change tabindex so the next TAB goes back to the IFRAME + document.getElementById('iframe').setAttribute('tabindex','4'); + + tab_iframe(); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(setTabFocus); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug238987.html b/dom/events/test/test_bug238987.html new file mode 100644 index 000000000..814b0596b --- /dev/null +++ b/dom/events/test/test_bug238987.html @@ -0,0 +1,282 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=238987 +--> +<head> + <title>Test for Bug 238987</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=238987">Mozilla Bug 238987</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + /** Test for Bug 238987 **/ + + var shouldStop = false; + var modifier = 0; + var expectedResult = "i1,i2,i3,i4,i5,i6,i7,i8,number,i9,i10,i11,i12"; + var forwardFocusArray = expectedResult.split(","); + var backwardFocusArray = expectedResult.split(","); + var forwardBlurArray = expectedResult.split(","); + var backwardBlurArray = expectedResult.split(","); + // Adding 3 for "begin", "end", "begin" and one for the <a> in the Mochitest template, + var expectedWindowFocusCount = forwardFocusArray.length + backwardFocusArray.length + 4; + // but the last blur event goes to i1, not "begin". + var expectedWindowBlurCount = forwardFocusArray.length + backwardFocusArray.length + 3; + + function handleFocus(e) { + if (e.target.id == "begin") { + // if the modifier is set, the test is coming back from the end. + if (modifier) { + shouldStop = true; + } + } else if (e.target.id == "end") { + modifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK; + } else if (modifier) { + var expected = backwardFocusArray.pop(); + ok(expected == e.target.id, + "(focus) Backward tabbing, expected [" + + expected + "], got [" + e.target.id + "]"); + } else { + var expected = forwardFocusArray.shift(); + is(e.target, document.activeElement, "Wrong activeElement!"); + ok(expected == e.target.id, + "(focus) Forward tabbing, expected [" + + expected + "], got [" + e.target.id + "]"); + } + } + + function handleWindowFocus(e) { + --expectedWindowFocusCount; + var s = "target " + e.target; + if ("id" in e.target) { + s = s + ", id=\"" + e.target.id + "\""; + } + ok(e.eventPhase == Components.interfaces.nsIDOMEvent.CAPTURING_PHASE, + "|window| should not have got a focus event, " + s); + } + + function handleBlur(e) { + if (e.target.id == "begin" || e.target.id == "end") { + return; + } else if (modifier) { + var expected = backwardBlurArray.pop(); + ok(expected == e.target.id, + "(blur) backward tabbing, expected [" + + expected + "], got [" + e.target.id + "]"); + } else { + var expected = forwardBlurArray.shift(); + ok(expected == e.target.id, + "(blur) forward tabbing, expected [" + + expected + "], got [" + e.target.id + "]"); + } + } + + function handleWindowBlur(e) { + --expectedWindowBlurCount; + var s = "target " + e.target; + if ("id" in e.target) { + s = s + ", id=\"" + e.target.id + "\""; + } + ok(e.eventPhase == Components.interfaces.nsIDOMEvent.CAPTURING_PHASE, + "|window| should not have got a blur event, " + s); + } + + function tab() { + var utils = SpecialPowers.DOMWindowUtils; + // Send tab key events. + var key = Components.interfaces.nsIDOMKeyEvent.DOM_VK_TAB; + utils.sendKeyEvent("keydown", key, 0, modifier); + utils.sendKeyEvent("keypress", key, 0, modifier); + utils.sendKeyEvent("keyup", key, 0, modifier); + if (shouldStop) { + // Did focus handling succeed + is(forwardFocusArray.length, 0, + "Not all forward tabbing focus tests were run, " + + forwardFocusArray.toString()); + is(backwardFocusArray.length, 0, + "Not all backward tabbing focus tests were run, " + + backwardFocusArray.toString()); + is(expectedWindowFocusCount, 0, + "|window| didn't get the right amount of focus events"); + + // and blur. + is(forwardBlurArray.length, 0, + "Not all forward tabbing blur tests were run, " + + forwardBlurArray.toString()); + is(backwardBlurArray.length, 0, + "Not all backward tabbing blur tests were run, " + + backwardBlurArray.toString()); + is(expectedWindowBlurCount, 0, + "|window| didn't get the right amount of blur events"); + + // Cleanup + window.removeEventListener("focus", handleWindowFocus, true); + window.removeEventListener("focus", handleWindowFocus, false); + window.removeEventListener("blur", handleWindowBlur, true); + window.removeEventListener("blur", handleWindowBlur, false); + var elements = document.getElementsByTagName("*"); + for (var i = 0; i < elements.length; ++i) { + if (elements[i].hasAttribute("id")) { + elements[i].removeEventListener("focus", handleFocus, false); + elements[i].removeEventListener("blur", handleBlur, false); + } + } + + SimpleTest.finish(); + } else { + setTimeout(tab, 0); + } + } + + function start() { + window.focus(); + window.addEventListener("focus", handleWindowFocus, true); + window.addEventListener("focus", handleWindowFocus, false); + window.addEventListener("blur", handleWindowBlur, true); + window.addEventListener("blur", handleWindowBlur, false); + var elements = document.getElementsByTagName("*"); + for (var i = 0; i < elements.length; ++i) { + if (elements[i].hasAttribute("id")) { + elements[i].addEventListener("focus", handleFocus, false); + elements[i].addEventListener("blur", handleBlur, false); + } + if (elements[i].getAttribute("tabindex") == "1") { + elements[i].setAttribute("tabindex", "-1"); + } + } + tab(); + } + + // accessibility.tabfocus must be set to value 7 before running test also + // on a mac. + function doTest() { + SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}, start); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(doTest); + +</script> +</pre> + <h4 tabindex="0" id="begin">Test:</h4> + <table> + <tbody> + <tr> + <td>type="text"</td><td><input type="text" id="i1" value=""></td> + </tr> + <tr> + <td>type="button"</td><td><input type="button" id="i2" value="type='button'"></td> + </tr> + <tr> + <td>type="checkbox"</td><td><input type="checkbox" id="i3" ></td> + </tr> + <tr> + <td>type="radio" checked</td><td><input type="radio" id="i4" name="radio" checked> + <input type="radio" id="i4b" name="radio"></td> + </tr> + <tr> + <td>type="radio"</td><td><input type="radio" id="i5" name="radio2"> + <input type="radio" id="i6" name="radio2"></td> + </tr> + <tr> + <td>type="password"</td><td><input type="password" id="i7"></td> + </tr> + <tr> + <td>type="file"</td><td><input type="file" id="i8"></td> + </tr> + <tr> + <td>type="number"</td><td><input type="number" id="number"></td> + </tr> + <tr> + <td>button</td><td><button id="i9">button</button></td> + </tr> + <tr> + <td>select</td><td><select id="i10"><option>select</option></select></td> + </tr> + <tr> + <td>a</td><td><a href="#radio" id="i11">a link</a></td> + </tr> + <tr> + <td>tabindex="0"</td><td><span tabindex="0" id="i12">span</span></td> + </tr> + + <tr> + <td><h3>Form elements with tabindex="-1"</h3></td> + </tr> + <tr> + <td>type="text"</td><td><input type="text" tabindex="-1" value=""></td> + </tr> + <tr> + <td>type="button"</td><td><input type="button" tabindex="-1" value="type='button'"></td> + </tr> + <tr> + <td>type="checkbox"</td><td><input type="checkbox" tabindex="-1"></td> + </tr> + <tr> + <td>type="radio" checked</td><td><input type="radio" tabindex="-1" name="radio3" checked> + <input type="radio" tabindex="-1" name="radio3"></td> + </tr> + <tr> + <td>type="radio"</td><td><input type="radio" tabindex="-1" name="radio4"> + <input type="radio" tabindex="-1" name="radio4"></td> + </tr> + <tr> + <td>type="password"</td><td><input type="password" tabindex="-1"></td> + </tr> + <tr> + <td>type="file"</td><td><input type="file" tabindex="-1"></td> + </tr> + <tr> + <td>button</td><td><button tabindex="-1">button</button></td> + </tr> + <tr> + <td>select</td><td><select tabindex="-1"><option>select</option></select></td> + </tr> + + <tr> + <td><h3>Form elements with .setAttribute("tabindex", "-1")</h3></td> + </tr> + <tr> + <td>type="text"</td><td><input type="text" tabindex="1" value=""></td> + </tr> + <tr> + <td>type="button"</td><td><input type="button" tabindex="1" value="type='button'"></td> + </tr> + <tr> + <td>type="checkbox"</td><td><input type="checkbox" tabindex="1"></td> + </tr> + <tr> + <td>type="radio" checked</td><td><input type="radio" tabindex="1" name="radio5" checked> + <input type="radio" tabindex="1" name="radio5"></td> + </tr> + <tr> + <td>type="radio"</td><td><input type="radio" tabindex="1" name="radio6"> + <input type="radio" tabindex="1" name="radio6"></td> + </tr> + <tr> + <td>type="password"</td><td><input type="password" tabindex="1"></td> + </tr> + <tr> + <td>type="file"</td><td><input type="file" tabindex="1"></td> + </tr> + <tr> + <td>button</td><td><button tabindex="1">button</button></td> + </tr> + <tr> + <td>select</td><td><select tabindex="1"><option>select</option></select></td> + </tr> + + </tbody> + </table> + <h4 tabindex="0" id="end">done.</h4> +</body> +</html> + diff --git a/dom/events/test/test_bug288392.html b/dom/events/test/test_bug288392.html new file mode 100644 index 000000000..bec2cca4c --- /dev/null +++ b/dom/events/test/test_bug288392.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=288392 +--> +<head> + <title>Test for Bug 288392</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=288392">Mozilla Bug 288392</a> +<p id="display"></p> +<div id="content" style="display: none"> +<div id="mutationTarget"> +</div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 288392 **/ +var subtreeModifiedCount; + +function subtreeModified(e) +{ + ++subtreeModifiedCount; +} + +function doTest() { + var targetNode = document.getElementById("mutationTarget"); + targetNode.addEventListener("DOMSubtreeModified", subtreeModified, false); + + subtreeModifiedCount = 0; + var temp = document.createElement("DIV"); + targetNode.appendChild(temp); + is(subtreeModifiedCount, 1, + "Appending a child node should have dispatched a DOMSubtreeModified event"); + + subtreeModifiedCount = 0; + temp.setAttribute("foo", "bar"); + is(subtreeModifiedCount, 1, + "Setting an attribute should have dispatched a DOMSubtreeModified event"); + + subtreeModifiedCount = 0; + targetNode.removeChild(temp); + is(subtreeModifiedCount, 1, + "Removing a child node should have dispatched a DOMSubtreeModified event"); + + // Testing events in a subtree, which is not in the document. + var subtree = document.createElement("div"); + var s = "<e1 attr1='value1'>Something1</e1><e2 attr2='value2'>Something2</e2>"; + subtree.innerHTML = s; + subtree.addEventListener("DOMSubtreeModified", subtreeModified, false); + + subtreeModifiedCount = 0; + subtree.firstChild.firstChild.data = "foo"; + is(subtreeModifiedCount, 1, + "Editing character data should have dispatched a DOMSubtreeModified event"); + + subtreeModifiedCount = 0; + subtree.firstChild.removeChild(subtree.firstChild.firstChild); + is(subtreeModifiedCount, 1, + "Removing a child node should have dispatched a DOMSubtreeModified event"); + + subtreeModifiedCount = 0; + subtree.firstChild.setAttribute("foo", "bar"); + is(subtreeModifiedCount, 1, + "Setting an attribute should have dispatched a DOMSubtreeModified event"); + + subtreeModifiedCount = 0; + subtree.textContent = "foobar"; + is(subtreeModifiedCount, 1, + "Setting .textContent should have dispatched a DOMSubtreeModified event"); + + subtreeModifiedCount = 0; + subtree.innerHTML = s; + is(subtreeModifiedCount, 1, + "Setting .innerHTML should have dispatched a DOMSubtreeModified event"); + + subtreeModifiedCount = 0; + subtree.removeEventListener("DOMSubtreeModified", subtreeModified, false); + subtree.appendChild(document.createTextNode("")); + subtree.addEventListener("DOMSubtreeModified", subtreeModified, false); + subtree.normalize(); + is(subtreeModifiedCount, 1, + "Calling normalize() should have dispatched a DOMSubtreeModified event"); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); +addLoadEvent(SimpleTest.finish); + +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug299673-1.html b/dom/events/test/test_bug299673-1.html new file mode 100644 index 000000000..f3c6fb276 --- /dev/null +++ b/dom/events/test/test_bug299673-1.html @@ -0,0 +1,61 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=299673 +--> +<head> + <title>Test #1 for Bug 299673</title> + <script type="text/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" /> +</head> +<body id="Body"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=299673">Mozilla Bug 299673</a> +<p id="display"> + + <SELECT id="Select1" onchange="log(event); OpenWindow()" onfocus="log(event); " onblur="log(event)"> + <OPTION selected>option1</OPTION> + <OPTION>option2</OPTION> + <OPTION>option3</OPTION> + </SELECT> + + <INPUT id="Text1" type="text" onfocus="log(event)" onblur="log(event)"> + <INPUT id="Text2" type="text" onfocus="log(event)" onblur="log(event)"> + +</p> +<div id="content" style="display: none"> + +</div> + +<pre id="test"> + +<script src="bug299673.js"></script> + +<script class="testbody" type="text/javascript"> + +/** Test #1 for Bug 299673 **/ +function doTest(expectedEventLog) { + var eventLogForNewWindow = '\ + : Test with browser.link.open_newwindow = 2\n\ +: focus top-doc\n\ +SELECT(Select1): focus \n\ +SELECT(Select1): change \n\ + : >>> OpenWindow\n\ +: blur top-doc\n\ +: focus popup-doc\n\ +INPUT(popupText1): focus \n\ + : <<< OpenWindow\n\ +SELECT(Select1): blur \n\ +INPUT(popupText1): blur \n\ +: blur popup-doc\n\ +: focus top-doc\n\ +' + + setPrefAndDoTest(eventLogForNewWindow,'Body',2); // 2 = open new window as window +} + +todo(false, "Please write a test for bug 299673 that actually works, see bug 553417"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug299673-2.html b/dom/events/test/test_bug299673-2.html new file mode 100644 index 000000000..f393d6609 --- /dev/null +++ b/dom/events/test/test_bug299673-2.html @@ -0,0 +1,60 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=299673 +--> +<head> + <title>Test #2 for Bug 299673</title> + <script type="text/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" /> +</head> +<body id="Body"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=299673">Mozilla Bug 299673</a> +<p id="display"> + + <SELECT id="Select1" onchange="log(event); OpenWindow()" onfocus="log(event); " onblur="log(event)"> + <OPTION selected>option1</OPTION> + <OPTION>option2</OPTION> + <OPTION>option3</OPTION> + </SELECT> + + <INPUT id="Text1" type="text" onfocus="log(event)" onblur="log(event)"> + <INPUT id="Text2" type="text" onfocus="log(event)" onblur="log(event)"> + +</p> +<div id="content" style="display: none"> + +</div> + +<pre id="test"> + +<script src="bug299673.js"></script> + +<script class="testbody" type="text/javascript"> + +/** Test #2 for Bug 299673 **/ +function doTest(expectedEventLog) { + var eventLogForNewTab = '\ + : Test with browser.link.open_newwindow = 3\n\ +: focus top-doc\n\ +SELECT(Select1): focus \n\ +SELECT(Select1): change \n\ + : >>> OpenWindow\n\ +: blur top-doc\n\ +: focus popup-doc\n\ +INPUT(popupText1): focus \n\ + : <<< OpenWindow\n\ +SELECT(Select1): blur \n\ +INPUT(popupText1): blur \n\ +: blur popup-doc\n\ +: focus top-doc\n\ +' + setPrefAndDoTest(eventLogForNewTab,'Body',3); // 3 = open new window as tab +} + +todo(false, "Please write a test for bug 299673 that actually works, see bug 553417"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug322588.html b/dom/events/test/test_bug322588.html new file mode 100644 index 000000000..1c931c006 --- /dev/null +++ b/dom/events/test/test_bug322588.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=322588 +--> +<head> + <title>Test for Bug 322588 - onBlur window close no longer works</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=322588">Mozilla Bug 322588 - onBlur window close no longer works</a> +<p id="display"> +<a id="link" href="javascript:pop350d('bug322588-popup.html#target')">Openwindow</a><br> +The opened window should not directly close when clicking on the Openwindow link +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 322588 **/ + +var result = ""; + +var w; +function pop350d(url) { + w = window.open(); + w.addEventListener("unload", function () { result += " unload";}, false); + w.addEventListener("load", function () { result += " load"; setTimeout(done, 1000);}, false); + w.addEventListener("blur", function () { result += " blur";}, false); + w.location = url; +} + +function doTest() { + try { + sendMouseEvent({type:'click'}, 'link'); + } catch(e) { + if (w) + w.close(); + throw e; + } +} + +function done() { + is(result," unload load","unexpected events"); // The first unload is for about:blank + if (w) + w.close(); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +addLoadEvent(doTest); + + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug328885.html b/dom/events/test/test_bug328885.html new file mode 100644 index 000000000..5af2d5611 --- /dev/null +++ b/dom/events/test/test_bug328885.html @@ -0,0 +1,134 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=328885 +--> +<head> + <title>Test for Bug 328885</title> + <script type="text/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" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=328885">Mozilla Bug 328885</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<input type="text" id="inputelement" + style="position: absolute; left: 0px; top: 0px;"> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 328885 **/ + + var inputelement = null; + var mutationCount = 0; + + function mutationListener(evt) { + ++mutationCount; + } + + function clickTest() { + inputelement.addEventListener("DOMSubtreeModified", mutationListener, false); + inputelement.addEventListener("DOMNodeInserted", mutationListener, false); + inputelement.addEventListener("DOMNodeRemoved", mutationListener, false); + inputelement.addEventListener("DOMNodeRemovedFromDocument", mutationListener, false); + inputelement.addEventListener("DOMNodeInsertedIntoDocument", mutationListener, false); + inputelement.addEventListener("DOMAttrModified", mutationListener, false); + inputelement.addEventListener("DOMCharacterDataModified", mutationListener, false); + + inputelement.addEventListener('click', + function(event) { + var evt = SpecialPowers.wrap(event); + ok(SpecialPowers.unwrap(evt.originalTarget) instanceof HTMLDivElement, + "(1) Wrong originalTarget!"); + is(SpecialPowers.unwrap(evt.originalTarget.parentNode), inputelement, + "(2) Wront parent node!"); + ok(mutationCount == 0, "(3) No mutations should have happened! [" + mutationCount + "]"); + evt.originalTarget.textContent = "foo"; + ok(mutationCount == 0, "(4) Mutation listener shouldn't have been called! [" + mutationCount + "]"); + evt.originalTarget.innerHTML = "foo2"; + ok(mutationCount == 0, "(5) Mutation listener shouldn't have been called! [" + mutationCount + "]"); + evt.originalTarget.lastChild.data = "bar"; + ok(mutationCount == 0, "(6) Mutation listener shouldn't have been called! [" + mutationCount + "]"); + + var r = SpecialPowers.wrap(document.createRange()); + r.selectNodeContents(evt.originalTarget); + r.deleteContents(); + ok(mutationCount == 0, "(7) Mutation listener shouldn't have been called! [" + mutationCount + "]"); + + evt.originalTarget.textContent = "foo"; + ok(mutationCount == 0, "(8) Mutation listener shouldn't have been called! [" + mutationCount + "]"); + r = SpecialPowers.wrap(document.createRange()); + r.selectNodeContents(evt.originalTarget); + r.extractContents(); + ok(mutationCount == 0, "(9) Mutation listener shouldn't have been called! [" + mutationCount + "]"); + + evt.originalTarget.setAttribute("foo", "bar"); + ok(mutationCount == 0, "(10) Mutation listener shouldn't have been called! ["+ mutationCount + "]"); + + // Same tests with non-native-anononymous element. + // mutationCount should be increased by 2 each time, since there is + // first a mutation specific event and then DOMSubtreeModified. + inputelement.textContent = "foo"; + ok(mutationCount == 2, "(11) Mutation listener should have been called! [" + mutationCount + "]"); + inputelement.lastChild.data = "bar"; + ok(mutationCount == 4, "(12) Mutation listener should have been called! [" + mutationCount + "]"); + + r = document.createRange(); + r.selectNodeContents(inputelement); + r.deleteContents(); + ok(mutationCount == 6, "(13) Mutation listener should have been called! [" + mutationCount + "]"); + + inputelement.textContent = "foo"; + ok(mutationCount == 8, "(14) Mutation listener should have been called! [" + mutationCount + "]"); + r = document.createRange(); + r.selectNodeContents(inputelement); + r.extractContents(); + ok(mutationCount == 10, "(15) Mutation listener should have been called! [" + mutationCount + "]"); + + inputelement.setAttribute("foo", "bar"); + ok(mutationCount == 12, "(16) Mutation listener should have been called! ["+ mutationCount + "]"); + + // Then try some mixed mutations. The mutation handler of non-native-a + inputelement.addEventListener("DOMAttrModified", + function (evt2) { + evt.originalTarget.setAttribute("foo", "bar" + mutationCount); + ok(evt.originalTarget.getAttribute("foo") == "bar" + mutationCount, + "(17) Couldn't update the attribute?!?"); + } + , false); + inputelement.setAttribute("foo", ""); + ok(mutationCount == 14, "(18) Mutation listener should have been called! ["+ mutationCount + "]"); + + inputelement.textContent = "foo"; + ok(mutationCount == 16, "(19) Mutation listener should have been called! ["+ mutationCount + "]"); + inputelement.addEventListener("DOMCharacterDataModified", + function (evt2) { + evt.originalTarget.textContent = "bar" + mutationCount; + }, false); + // This one deletes and inserts a new node, then DOMSubtreeModified. + inputelement.textContent = "bar"; + ok(mutationCount == 19, "(20) Mutation listener should have been called! ["+ mutationCount + "]"); + } + ,false); + synthesizeMouseAtCenter(inputelement, {}, window); + SimpleTest.finish(); + } + + function doTest() { + inputelement = document.getElementById('inputelement'); + inputelement.focus(); + setTimeout(clickTest, 100); + } + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + addLoadEvent(doTest); + +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug336682.js b/dom/events/test/test_bug336682.js new file mode 100644 index 000000000..7f4ecfdf7 --- /dev/null +++ b/dom/events/test/test_bug336682.js @@ -0,0 +1,94 @@ +/* + * Helper functions for online/offline events tests. + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ +var gState = 0; +/** + * After all the on/offline handlers run, + * gState is expected to be equal to MAX_STATE. + */ +var MAX_STATE; + +function trace(text) { + var t = text.replace(/&/g, "&" + "amp;").replace(/</g, "&" + "lt;") + "<br>"; + //document.getElementById("display").innerHTML += t; +} + +// window.ononline and window.onclick shouldn't work +// Right now, <body ononline=...> sets window.ononline (bug 380618) +// When these start passing, be sure to uncomment the code inside if(0) below. +todo(typeof window.ononline == "undefined", + "window.ononline should be undefined at this point"); +todo(typeof window.onoffline == "undefined", + "window.onoffline should be undefined at this point"); + +if (0) { + window.ononline = function() { + ok(false, "window.ononline shouldn't be called"); + } + window.onoffline = function() { + ok(false, "window.onclick shouldn't be called"); + } +} + +/** + * Returns a handler function for an online/offline event. The returned handler + * ensures the passed event object has expected properties and that the handler + * is called at the right moment (according to the gState variable). + * @param nameTemplate The string identifying the hanlder. '%1' in that + * string will be replaced with the event name. + * @param eventName 'online' or 'offline' + * @param expectedStates an array listing the possible values of gState at the + * moment the handler is called. The handler increases + * gState by one before checking if it's listed in + * expectedStates. + */ +function makeHandler(nameTemplate, eventName, expectedStates) { + return function(e) { + var name = nameTemplate.replace(/%1/, eventName); + ++gState; + trace(name + ": gState=" + gState); + ok(expectedStates.indexOf(gState) != -1, + "handlers called in the right order: " + name + " is called, " + + "gState=" + gState + ", expectedStates=" + expectedStates); + ok(e.constructor == Event, "event should be an Event"); + ok(e.type == eventName, "event type should be " + eventName); + ok(e.bubbles, "event should bubble"); + ok(!e.cancelable, "event should not be cancelable"); + ok(e.target == (document instanceof HTMLDocument + ? document.body : document.documentElement), + "the event target should be the body element"); + } +} + +function doTest() { + var iosvc = SpecialPowers.Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService2); + iosvc.manageOfflineStatus = false; + iosvc.offline = false; + ok(navigator.onLine, "navigator.onLine should be true, since we've just " + + "set nsIIOService.offline to false"); + + gState = 0; + + trace("setting iosvc.offline = true"); + iosvc.offline = true; + trace("done setting iosvc.offline = true"); + ok(!navigator.onLine, + "navigator.onLine should be false when iosvc.offline == true"); + ok(gState == window.MAX_STATE, + "offline event: all registered handlers should have been invoked, " + + "actual: " + gState); + + gState = 0; + trace("setting iosvc.offline = false"); + iosvc.offline = false; + trace("done setting iosvc.offline = false"); + ok(navigator.onLine, + "navigator.onLine should be true when iosvc.offline == false"); + ok(gState == window.MAX_STATE, + "online event: all registered handlers should have been invoked, " + + "actual: " + gState); +} diff --git a/dom/events/test/test_bug336682_1.html b/dom/events/test/test_bug336682_1.html new file mode 100644 index 000000000..da80c1649 --- /dev/null +++ b/dom/events/test/test_bug336682_1.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 336682: online/offline events tests. + +Any copyright is dedicated to the Public Domain. +http://creativecommons.org/licenses/publicdomain/ +--> +<head> + <title>Test for Bug 336682 (online/offline events)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body ononline="trace('<body ononline=...>'); + bodyOnonline(this, event)" + onoffline="trace('<body onoffline=...>'); bodyOnoffline(this, event)" + > +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=336682">Mozilla Bug 336682</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<script type="text/javascript" src="test_bug336682.js"></script> + +<script class="testbody" type="text/javascript"> + +function makeBodyHandler(eventName) { + return function (aThis, aEvent) { + var handler = makeHandler("<body on%1='...'>", eventName, [3,4]); + handler(aEvent); + } +} +addLoadEvent(function() { + /** @see test_bug336682.js */ + MAX_STATE = 4; + + for (var event of ["online", "offline"]) { + document.body.addEventListener( + event, + makeHandler("document.body.addEventListener('%1', ..., false)", + event, [1]), + false); + + document.addEventListener( + event, + makeHandler("document.addEventListener('%1', ..., false)", + event, [2]), + false); + + window["bodyOn" + event] = makeBodyHandler(event); + + window.addEventListener( + event, + makeHandler("window.addEventListener('%1', ..., false)", + event, [3,4]), + false); + } + + doTest(); + SimpleTest.finish(); +}); + +SimpleTest.waitForExplicitFinish(); +</script> +</body> +</html> diff --git a/dom/events/test/test_bug336682_2.xul b/dom/events/test/test_bug336682_2.xul new file mode 100644 index 000000000..303348c2e --- /dev/null +++ b/dom/events/test/test_bug336682_2.xul @@ -0,0 +1,71 @@ +<?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"?> +<!-- +Bug 336682: online/offline events tests. + +Any copyright is dedicated to the Public Domain. +http://creativecommons.org/licenses/publicdomain/ +--> +<window title="Mozilla Bug 336682" + onoffline="trace('lt;body onoffline=...'); windowOnoffline(this, event)" + ononline="trace('lt;body ononline=...'); windowOnonline(this, event)" + + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=336682"> +Mozilla Bug 336682 (online/offline events)</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +</body> + +<script type="text/javascript" src="test_bug336682.js"/> +<script class="testbody" type="text/javascript"> +<![CDATA[ +addLoadEvent(function() { + /** @see test_bug336682.js */ + MAX_STATE = 4; + + function makeWindowHandler(eventName) { + return function (aThis, aEvent) { + var handler = makeHandler("<body on%1='...'>", eventName, [3,4]); + handler(aEvent); + } + } + + for (var event of ["online", "offline"]) { + document.documentElement.addEventListener( + event, + makeHandler("document.body.addEventListener('%1', ..., false)", + event, [1]), + false); + + document.addEventListener( + event, + makeHandler("document.addEventListener('%1', ..., false)", + event, [2]), + false); + + window["windowOn" + event] = makeWindowHandler(event); + + window.addEventListener( + event, + makeHandler("window.addEventListener('%1', ..., false)", + event, [3,4]), + false); + } + + doTest(); + SimpleTest.finish(); +}); + +SimpleTest.waitForExplicitFinish(); +]]> +</script> + +</window> diff --git a/dom/events/test/test_bug367781.html b/dom/events/test/test_bug367781.html new file mode 100644 index 000000000..06fa130c8 --- /dev/null +++ b/dom/events/test/test_bug367781.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=367781 +--> +<head> + <title>Test for Bug 367781</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=367781">Mozilla Bug 367781</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug **/ +var eventCounter = 0; + +function handler(e) { + if (e.type == "DOMNodeInserted") { + ++eventCounter; + } +} + +function doTest() { + var i1 = document.getElementById('i1'); + var i2 = document.getElementById('i2'); + var pre = i1.contentDocument.getElementsByTagName("pre")[0]; + pre.addEventListener("DOMNodeInserted", handler, false); + pre.textContent = pre.textContent + pre.textContent; + ok(eventCounter == 1, "DOMNodeInserted should have been dispatched"); + + pre.parentNode.removeChild(pre); + i2.contentDocument.adoptNode(pre); + i2.contentDocument.body.appendChild(pre); + ok(eventCounter == 2, "DOMNodeInserted should have been dispatched in the new document"); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); +addLoadEvent(SimpleTest.finish); + +</script> +</pre> +<iframe id="i1" src="data:text/html,<html><body><pre>Foobar</pre></body></html>"></iframe> +<iframe id="i2" src="data:text/html,<html><body></body></html>"></iframe> +</body> +</html> + diff --git a/dom/events/test/test_bug368835.html b/dom/events/test/test_bug368835.html new file mode 100644 index 000000000..14581ee34 --- /dev/null +++ b/dom/events/test/test_bug368835.html @@ -0,0 +1,101 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=368835 +--> + <head> + <title>Test for Bug 368835</title> + + <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" /> + + <script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script> + </head> + + <body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=368835"> + Mozilla Bug 368835 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + <script class="testbody" type="text/javascript"> + function dataContainerEventHandler(aEvent) + { + var value = ""; + var isPassed = true; + try { + value = aEvent.getData("data1"); + isPassed = true; + } catch (e) { + isPassed = false; + } + + ok(isPassed, "getData shouldn't fail."); + ok(value == "data1", "Wrong value of data."); + + is(aEvent.getData("document"), document); + is(aEvent.getData("window"), window); + is(aEvent.getData("event"), aEvent); + is(aEvent.getData("null"), null); + is(aEvent.getData("1"), 1); + is(aEvent.getData("1.1"), 1.1); + is(aEvent.getData("true"), true); + + try { + aEvent.setData("data3", "data3"); + isPassed = false; + } catch (e) { + isPassed = true; + } + + ok(isPassed, "setData should fail during event dispatching."); + } + + function doTest() + { + var isPassed; + var event = null; + + try { + event = document.createEvent("datacontainerevents"); + isPassed = true; + } catch (e) { + isPassed = false; + } + + ok(isPassed, "Document should know about 'datacontainerevents' event class."); + ok(("setData" in event), "nsIDOMDataContainerEvent isn't available."); + + event.initEvent("dataContainerEvent", true, true); + + try { + event.setData("data1", "data1"); + event.setData("document", document); + event.setData("window", window); + event.setData("event", event); + event.setData("null", null); + event.setData("1", 1); + event.setData("1.1", 1.1); + event.setData("true", true); + isPassed = true; + } catch (e) { + isPassed = false; + } + + ok(isPassed, "setData shouldn't fail when event is initialized."); + + document.body.addEventListener("dataContainerEvent", + dataContainerEventHandler, true); + document.body.dispatchEvent(event); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(doTest); + </script> + </pre> + </body> +</html> + diff --git a/dom/events/test/test_bug379120.html b/dom/events/test/test_bug379120.html new file mode 100644 index 000000000..9a7925743 --- /dev/null +++ b/dom/events/test/test_bug379120.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=379120 +--> +<head> + <title>Test for Bug 379120</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379120">Mozilla Bug 379120</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 379120 **/ + + var originalString = "<test></test>"; + + // Parse the content into an XMLDocument + var parser = new DOMParser(); + var originalDoc = parser.parseFromString(originalString, "text/xml"); + + var stylesheetText = + "<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' " + + "version='1.0' xmlns='http://www.w3.org/1999/xhtml'> " + + + "<xsl:output method='xml' version='1.0' encoding='UTF-8' " + + "doctype-system='http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd' " + + "doctype-public='-//W3C//DTD XHTML 1.0 Transitional//EN' /> " + + + "<xsl:template match='/'>" + + "<div onload='var i = 1'/>" + + "<xsl:apply-templates />" + + "</xsl:template>" + + "</xsl:stylesheet>"; + var stylesheet = parser.parseFromString(stylesheetText, "text/xml"); + + var processor = new XSLTProcessor(); + + var targetDocument; + processor.importStylesheet (stylesheet); + var transformedDocument = processor.transformToDocument (originalDoc); + is(transformedDocument.documentElement.getAttribute("onload"), + "var i = 1"); + ok(transformedDocument.documentElement.onload === null, + "Shouldn't have onload handler", + "got " + repr(transformedDocument.documentElement.onload) + + ", expected null");; +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug391568.xhtml b/dom/events/test/test_bug391568.xhtml new file mode 100644 index 000000000..0a35072be --- /dev/null +++ b/dom/events/test/test_bug391568.xhtml @@ -0,0 +1,77 @@ +<?xml version="1.0"?> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xbl="http://www.mozilla.org/xbl"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=391568 +--> +<head> + <title>Test for Bug 391568</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script> + var constructorFired = 0; + </script> + <xbl:bindings> + <xbl:binding id="test"> + <xbl:content><span> + (anonumous content) + <span><xbl:children/></span> + (anonumous content)</span> + </xbl:content> + + <xbl:implementation> + <xbl:constructor> + var win = XPCNativeWrapper.unwrap(window); + ++win.constructorFired; + document.getAnonymousNodes(this)[0].addEventListener( + "DOMCharacterDataModified", + function(evt) { + ++win.characterdatamodified; + }, + true); + </xbl:constructor> + </xbl:implementation> + </xbl:binding> + </xbl:bindings> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391568">Mozilla Bug 391568</a> +<p id="display"></p> +<div id="content"> + <span style="-moz-binding: url(#test);"><span id="real1">(real content)</span></span> + <span style="-moz-binding: url(#test);"><span id="real2">(real content)</span></span> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +/** Test for Bug 391568 **/ + +var characterdatamodified = 0; + +document.getElementById('real1').addEventListener( + "DOMCharacterDataModified", + function(evt) {}, + true); + +function testListeners() { + if (constructorFired < 2) { + setTimeout(testListeners, 0); + return; + } + document.getElementById('real1').firstChild.data = "(real content 2)"; + ok(characterdatamodified == 1, + "There is a DOMCharacterDataModified listener in anonymous content which didn't get called (1)!"); + document.getElementById('real2').firstChild.data = "(real content 2)"; + ok(characterdatamodified == 2, + "There is a DOMCharacterDataModified listener in anonymous content which didn't get called (2)!"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(testListeners); +]]> +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug402089.html b/dom/events/test/test_bug402089.html new file mode 100644 index 000000000..58ba423dd --- /dev/null +++ b/dom/events/test/test_bug402089.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=402089 +--> +<head> + <title>Test for Bug 402089</title> + <script type="text/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" /> +</head> +<!-- setTimeout so that the test starts after paint suppression ends --> +<body onload="setTimeout(doTest,0);"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=402089">Mozilla Bug 402089</a> +<p id="display"></p> +<div id="content"> + <pre id="result1"></pre> + <pre id="result2"></pre> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 402089 **/ + +var cachedEvent = null; + +function testCachedEvent() { + testEvent('result2'); + ok((document.getElementById('result1').textContent == + document.getElementById('result2').textContent), + "Event coordinates should be the same after dispatching."); + SimpleTest.finish(); +} + +function testEvent(res) { + var s = cachedEvent.type + "\n"; + s += "clientX: " + cachedEvent.clientX + ", clientY: " + cachedEvent.clientY + "\n"; + s += "screenX: " + cachedEvent.screenX + ", screenY: " + cachedEvent.screenY + "\n"; + s += "layerX: " + cachedEvent.layerX + ", layerY: " + cachedEvent.layerY + "\n"; + s += "pageX: " + cachedEvent.pageX + ", pageY: " + cachedEvent.pageY + "\n"; + document.getElementById(res).textContent += s; +} + +function clickHandler(e) { + cachedEvent = e; + testEvent('result1'); + e.stopPropagation(); + e.preventDefault(); + window.removeEventListener("click", clickHandler, true); + setTimeout(testCachedEvent, 10); +} + +function doTest() { + window.addEventListener("click", clickHandler, true); + var utils = SpecialPowers.getDOMWindowUtils(window); + utils.sendMouseEvent("mousedown", 1, 1, 0, 1, 0); + utils.sendMouseEvent("mouseup", 1, 1, 0, 1, 0); + +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug405632.html b/dom/events/test/test_bug405632.html new file mode 100644 index 000000000..96465225e --- /dev/null +++ b/dom/events/test/test_bug405632.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=405632 +--> +<head> + <title>Test for Bug 405632</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=405632">Mozilla Bug 405632</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 405632 **/ + + var me = document.createEvent("mouseevent"); + me.initMouseEvent("foo", false, false, window, 0, 100, 100, 100, 100, + false, false, false, false, 0, null); + ok(me.clientX == me.pageX, + "mouseEvent.clientX should be the same as mouseEvent.pageX when event is initialized manually"); + ok(me.clientY == me.pageY, + "mouseEvent.clientY should be the same as mouseEvent.pageY when event is initialized manually"); + +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug409604.html b/dom/events/test/test_bug409604.html new file mode 100644 index 000000000..ec714ab64 --- /dev/null +++ b/dom/events/test/test_bug409604.html @@ -0,0 +1,382 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=409604 +--> +<head> + <title>Test for Bug 409604</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body id="body"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=409604">Mozilla Bug 409604</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + /** Test for Bug 409604 **/ + + var modifier = SpecialPowers.Ci.nsIDOMEvent.ALT_MASK | + SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK; + var expectedFocus = "a,c,d,e,f,g,h,i,j,k,l,m,n,p,x,y"; + // XXX the "map" test is causing trouble, see bug 433089 + var focusArray = expectedFocus.split(","); + var unfocusableElementId = "invalid"; + var unfocusableTags = [ + {tag: "abbr", content: "text", attribs: {title: "something"}}, + {tag: "acronym", content: "text", attribs: {title: "something"}}, + {tag: "address", content: "text"}, + {tag: "b", content: "text"}, + {tag: "bdo", content: "text"}, + {tag: "big", content: "text"}, + {tag: "blockquote", content: "text"}, + {tag: "caption", content: "text", parent: "table", where: "first"}, + {tag: "cite", content: "text"}, + {tag: "code", content: "text"}, + {tag: "dd", content: "text", parent: "dl"}, + {tag: "del", content: "text"}, + {tag: "dfn", content: "text", attribs: {title: "something"}}, + {tag: "div", content: "text"}, + {tag: "dl", content: "<dd>text</dd>", parent: "dl"}, + {tag: "dt", content: "text", parent: "dl"}, + {tag: "em", content: "text"}, + {tag: "fieldset", content: "text"}, + {tag: "form", content: "text", attribs: {action: "any.html"}}, + {tag: "h1", content: "text"}, + {tag: "h2", content: "text"}, + {tag: "h3", content: "text"}, + {tag: "h4", content: "text"}, + {tag: "h5", content: "text"}, + {tag: "h6", content: "text"}, + {tag: "hr"}, + {tag: "i", content: "text"}, + {tag: "img", attribs: {src: "any.png", alt: "image"}}, + {tag: "ins", content: "text"}, + {tag: "kbd", content: "text"}, + {tag: "li", content: "text", parent: "ol"}, + {tag: "li", content: "text", parent: "ul"}, + {tag: "noscript", content: "text"}, + {tag: "ol", content: "<li>text</li>"}, + {tag: "optgroup", content: "<option>text</option>", attribs: {label: "some label"}, parent: "select"}, + {tag: "option", content: "text", parent: "select"}, + {tag: "p", content: "text"}, + {tag: "pre", content: "text"}, + {tag: "q", content: "text"}, + {tag: "samp", content: "text"}, + {tag: "small", content: "text"}, + {tag: "span", content: "text"}, + {tag: "strong", content: "text"}, + {tag: "sub", content: "text"}, + {tag: "sup", content: "text"}, + {tag: "tt", content: "text"}, + {tag: "ul", content: "<li>text</li>"}, + {tag: "var", content: "text"} + ]; + var invalidElements = [ + "body", + "col", + "colgroup", +// XXX the "map" test is causing trouble, see bug 433089 +// "map", + "table", + "tbody", + "td", + "tfoot", + "th", + "thead", + "tr" + ]; + + function handleFocus(e) { + ok("accessKey" in e, "(focus) accesskey property not found on element"); + var expected = focusArray.shift(); + // "k" and "n" are a special cases because the element receiving the focus + // is not the element which has the accesskey. + if (expected == "k" || expected == "n") { + ok(e.value == "test for label", "(focus) unexpected element: " + e.value + + " expected: " + "test for label"); + // "l" is a special case because the element receiving the focus is not + // the element which has the accesskey. + } else if (expected == "l") { + ok(e.value == "test for legend", "(focus) unexpected element: " + e.value + + " expected: " + "test for legend"); + } else { + ok(expected == e.accessKey, "(focus) unexpected element: " + e.accessKey + + " expected: " + expected); + } + } + + function handleClick(e) { + ok("accessKey" in e, "(click) accesskey property not found on element"); + } + + function handleInvalid(e) { + ok("accessKey" in e, "(invalid) accesskey property not found on element"); + ok(false, "(invalid) accesskey should not have any effect on this element: " + + e.localName); + } + + function pressAccessKey(key) { + var utils = SpecialPowers.DOMWindowUtils; + utils.sendKeyEvent("keydown", key, key, modifier); + utils.sendKeyEvent("keypress", key, key, modifier); + utils.sendKeyEvent("keyup", key, key, modifier); + } + + function testFocusableElements() { + for (var code = "a".charCodeAt(0); code <= "y".charCodeAt(0); ++ code) { + // XXX the "map" test is causing trouble, see bug 433089 + if (code == "b".charCodeAt(0)) + continue; + pressAccessKey(code); + } + ok(focusArray.length == 0, "(focus) unhandled elements remaining: " + focusArray.join(",")); + } + + function createUnfocusableElement(elem, accesskey) { + ok("tag" in elem, "invalid object passed to createUnfocusableElement: " + elem.toString()); + var e = document.createElement(elem.tag); + if ("content" in elem) { + e.innerHTML = elem.content; + } + if ("attribs" in elem) { + for (var attr in elem.attribs) { + e.setAttribute(attr, elem.attribs[attr]); + } + } + e.setAttribute("accesskey", accesskey); + e.setAttribute("onclick", "handleClick(event.target); event.preventDefault();"); + e.setAttribute("onfocus", "handleInvalid(event.target);"); + var parent = null; + var elementToInsert = null; + if ("parent" in elem) { + parent = document.getElementById(elem.parent); + elementToInsert = e; + } else { + parent = document.getElementById("tbody"); + elementToInsert = document.createElement("tr"); + var td = document.createElement("td"); + td.textContent = elem.tag; + elementToInsert.appendChild(td); + td = document.createElement("td"); + td.appendChild(e); + elementToInsert.appendChild(td); + } + ok(parent != null, "parent element not specified for element: " + elem.tag); + ok(elementToInsert != null, "elementToInsert not specified for element: " + elem.tag); + elementToInsert.setAttribute("id", unfocusableElementId); + if ("where" in elem) { + if (elem.where == "first") { + parent.insertBefore(elementToInsert, parent.firstChild); + } else { + ok(false, "invalid where value specified for element: " + elem.tag); + } + } else { + parent.appendChild(elementToInsert); + } + } + + function destroyUnfocusableElement() { + var el = document.getElementById(unfocusableElementId); + ok(el != null, "unfocusable element not found"); + el.parentNode.removeChild(el); + ok(document.getElementById(unfocusableElementId) == null, "unfocusable element not properly removed"); + } + + function testUnfocusableElements() { + var i, e; + for (i = 0; i < unfocusableTags.length; ++ i) { + createUnfocusableElement(unfocusableTags[i], "z"); + pressAccessKey("z".charCodeAt(0)); + destroyUnfocusableElement(); + } + for (i = 0; i < invalidElements.length; ++ i) { + e = document.getElementById(invalidElements[i]); + ok(e != null, "element with ID " + invalidElements[i] + " not found"); + e.setAttribute("accesskey", "z"); + e.setAttribute("onclick", "handleClick(event.target); event.preventDefault();"); + e.setAttribute("onfocus", "handleInvalid(event.target);"); + pressAccessKey("z".charCodeAt(0)); + e.removeAttribute("accesskey"); + e.removeAttribute("onclick"); + e.removeAttribute("onfocus"); + } + } + + function start() { + testFocusableElements(); + testUnfocusableElements(); + SimpleTest.finish(); + } + + function doTest() { + SpecialPowers.pushPrefEnv({"set": [["ui.key.contentAccess", 5]]}, start); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(doTest); + +</script> +</pre> + <table id="table"> + <thead id="thead"> + <tr id="tr"><th id="th">Test header</th><th></th></tr> + </thead> + <tfoot id="tfoot"> + <tr><td id="td">Test footer</td><td></td></tr> + </tfoot> + <tbody id="tbody"> + <colgroup id="colgroup"> + <col id="col"></col> + <col></col> + </colgroup> + <tr> + <td>a</td><td><a href="#" onclick="handleClick(event.target); return false;" accesskey="a" onfocus="handleFocus(event.target);">test link"</a></td> + </tr> +<!-- the "map" test is causing trouble, see bug 433089 + <tr> + <td>area</td><td><img src="about:logo" width="300" height="236" usemap="#map"> + <map id="map" name="map"><area shape="rect" coords="0,0,82,126" href="#" + onclick="handleClick(event.target); return false;" accesskey="b"></map> + </td> + </tr> +--> + <tr> + <td>button</td><td><button onclick="handleClick(event.target);" accesskey="c" onfocus="handleFocus(event.target);">test button"</button></td> + </tr> + <tr> + <td>input type="text"</td><td><input type="text" value="" onclick="handleClick(event.target);" onfocus="handleFocus(event.target);" accesskey="d"></td> + </tr> + <tr> + <td>input type="button"</td><td><input type="button" value="type='button'" onclick="handleClick(event.target);" onfocus="handleFocus(event.target);" accesskey="e"></td> + </tr> + <tr> + <td>input type="checkbox"</td><td><input type="checkbox" onclick="handleClick(event.target);" onfocus="handleFocus(event.target)" accesskey="f"></td> + </tr> + <tr> + <td>input type="radio"</td><td><input type="radio" name="radio" onclick="handleClick(event.target);" onfocus="handleFocus(event.target);" accesskey="g"></td> + </tr> + <tr> + <td>input type="password"</td><td><input type="password" onclick="handleClick(event.target);" onfocus="handleFocus(event.target);" accesskey="h"></td> + </tr> + <tr> + <td>input type="submit"</td><td><input type="submit" value="type='submit'" onclick="handleClick(event.target); return false;" + onfocus="handleFocus(event.target);" accesskey="i"></td> + </tr> + <tr> + <td>input type="reset"</td><td><input type="submit" value="type='reset'" onclick="handleClick(event.target);" + onfocus="handleFocus(event.target);" accesskey="j"></td> + </tr> + <tr> + <td>label</td><td><label accesskey="k" onclick="handleClick(event.target);" onfocus="handleInvalid(event.target);">test label + <input type="text" value="test for label" onfocus="handleFocus(event.target);" onclick="handleClick(event.target);"></label></td> + </tr> + <tr> + <td>legend</td><td><fieldset><legend accesskey="l">test legend</legend> + <input type="text" value="test for legend" onfocus="handleFocus(event.target);" onclick="handleClick(event.target);" ></fieldset></td> + </tr> + <tr> + <td>textarea</td><td><textarea onfocus="handleFocus(event.target);" onclick="handleClick(event.target);" accesskey="m">test text</textarea></td> + </tr> + <tr> + <td>label (label invisible)</td><td><label for="txt1" accesskey="n" style="display:none" + onclick="handleClick(event.target);" onfocus="handleInvalid(event.target);">test label</label> + <input type="text" id="txt1" value="test for label" onclick="handleClick(event.target);" onfocus="handleFocus(event.target);"></td> + </tr> + <tr> + <td>label (control invisible)</td><td><label for="txt2" accesskey="o" + onclick="handleClick(event.target);" onfocus="handleInvalid(event.target);">test label</label> + <input type="text" id="txt2" value="test for label" onclick="handleClick(event.target);" + onfocus="handleInvalid(event.target);" style="display:none"></td> + </tr> + <tr> + <td>select</td> + <td> + <select onclick="handleClick(event.target);" onfocus="handleFocus(event.target)" accesskey="p"><option>option</option></select> + </td> + </tr> + <tr> + <td>object</td> + <td> + <object onclick="handleClick(event.target);" onfocus="handleInvalid(event.target)" accesskey="q">an object</object> + </td> + </tr> + <tr> + <td>a without href</td> + <td> + <a onclick="handleClick(event.target);" onfocus="handleInvalid(event.target)" accesskey="r">an object</object> + </td> + </tr> + <tr> + <td>disabled button</td> + <td> + <button disabled="" onclick="handleClick(event.target);" onfocus="handleInvalid(event.target)" accesskey="s">disabled</button> + </td> + </tr> + <tr> + <td>disabled input</td> + <td> + <input disabled="" onclick="handleClick(event.target);" onfocus="handleInvalid(event.target)" accesskey="t"></input> + </td> + </tr> + <tr> + <td>hidden input</td> + <td> + <input type="hidden" onclick="handleClick(event.target);" onfocus="handleInvalid(event.target)" accesskey="u">disabled</input> + </td> + </tr> + <tr> + <td>disabled select</td> + <td> + <select disabled onclick="handleClick(event.target);" onfocus="handleInvalid(event.target)" accesskey="v"> + <option>disabled</option> + </select> + </td> + </tr> + <tr> + <td>disabled textarea</td> + <td> + <textarea disabled onclick="handleClick(event.target);" onfocus="handleInvalid(event.target)" accesskey="w">disabled</textarea> + </td> + </tr> + <tr> + <td>scrollable div(focusable)</td> + <td> + <div onclick="handleClick(event.target);" onfocus="handleFocus(event.target)" accesskey="x" style="height: 50px; overflow: auto;"> + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy + + dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the + + lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy + + dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the + + lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy + + dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the + + lazy dog. The quick brown fox jumps over the lazy dog. + </div> + </td> + </tr> + <tr> + <td>contenteditable div(focusable)</td> + <td> + <div onclick="handleClick(event.target);" onfocus="handleFocus(event.target)" accesskey="y" contenteditable="true"> + Test text..... + </div> + </td> + </tr> + </tbody> + </table> + <dl id="dl"></dl> + <ul id="ul"></ul> + <ol id="ol"></ol> + <select id="select"></select> +</body> +</html> diff --git a/dom/events/test/test_bug412567.html b/dom/events/test/test_bug412567.html new file mode 100644 index 000000000..08352366b --- /dev/null +++ b/dom/events/test/test_bug412567.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=412567 +--> +<head> + <title>Test for Bug 412567</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="testRedispatching(event);"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=412567">Mozilla Bug 412567</a> +<p id="display"></p> +<div id="content" style="display: none" onload="redispatchinHandler(event)"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 412567 **/ + +var loadEvent = null; + +function redispatchinHandler(evt) { + is(evt.type, "load", "Wrong event type!"); + ok(!evt.isTrusted, "Event should not be trusted!"); + SimpleTest.finish(); +} + +function redispatch() { + ok(loadEvent.isTrusted, "Event should be trusted before redispatching!"); + document.getElementById('content').dispatchEvent(loadEvent); +} + +function testRedispatching(evt) { + is(evt.type, "load", "Wrong event type!"); + ok(evt.isTrusted, "Event should be trusted!"); + loadEvent = evt; + setTimeout(redispatch, 0); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug415498.xul b/dom/events/test/test_bug415498.xul new file mode 100644 index 000000000..009431cb1 --- /dev/null +++ b/dom/events/test/test_bug415498.xul @@ -0,0 +1,106 @@ +<?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"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=415498 +--> +<window title="Mozilla Bug 415498" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="init()"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/chrome-harness.js"></script> +<body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=415498">Mozilla Bug 415498</a> + + <p id="display"></p> + + <pre id="test"> + <script class="testbody" type="application/javascript"><![CDATA[ + + /** Test for Bug 415498 **/ + if (Cc === undefined) { + var Cc = Components.classes; + var Ci = Components.interfaces; + } + var Cr = Components.results; + + SimpleTest.waitForExplicitFinish(); + + var gTestsIterator; + var gConsole; + var gConsoleListener; + var gMessages = []; + + function init() { + gTestsIterator = testsIterator(); + + gConsole = Cc["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + + gConsoleListener = { + observe: function(aObject) { + gMessages.push(aObject); + } + }; + gConsole.registerListener(gConsoleListener); + + nextTest(); + } + + function nextTest() { + try { + gTestsIterator.next(); + } catch (err) { + ok(err instanceof StopIteration, + "Some other exception was thrown than what we expected!"); + + if (gConsole && gConsoleListener) { + gConsole.unregisterListener(gConsoleListener); + } + SimpleTest.finish(); + } + } + + function testsIterator() { + + var browser = $("browser"); + browser.addEventListener("load", function() { + setTimeout(nextTest, 0) + }, false); + + // 1) This document uses addEventListener to register a method throwing an exception + var chromeDir = getRootDirectory(window.location.href); + browser.loadURI(chromeDir + "bug415498-doc1.html"); + yield undefined; + + ok(verifyErrorReceived("HierarchyRequestError"), + "Error message not reported in event listener callback!"); + gMessages = []; + + // 2) This document sets window.onload to register a method throwing an exception + var chromeDir = getRootDirectory(window.location.href); + browser.loadURI(chromeDir + "bug415498-doc2.html"); + yield undefined; + + ok(verifyErrorReceived("HierarchyRequestError"), + "Error message not reported in window.onload!"); + } + + function verifyErrorReceived(errorString) { + for (var i = 0; i < gMessages.length; i++) { + if (gMessages[i].message.indexOf(errorString) != -1) + return true; + } + return false; + } + ]]></script> + </pre> +</body> + +<browser id="browser" type="content" flex="1" src="about:blank"/> + +</window> diff --git a/dom/events/test/test_bug418986-3.html b/dom/events/test/test_bug418986-3.html new file mode 100644 index 000000000..a92b1e0f5 --- /dev/null +++ b/dom/events/test/test_bug418986-3.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=418986 +--> +<head> + <meta charset="utf-8"> + <title>Test 3/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</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"/> +</head> +<body id="body"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a> +<p id="display"></p> +<pre id="test"></pre> +<script type="application/javascript;version=1.7" src="bug418986-3.js"></script> +<script type="application/javascript;version=1.7"> + // This test produces fake mouse events and checks that the screenX and screenY + // properties of the received event objects provide client window coordinates. + // Run the test once the window has loaded. + window.onload = () => test(true); +</script> +</body> +</html> diff --git a/dom/events/test/test_bug418986-3.xul b/dom/events/test/test_bug418986-3.xul new file mode 100644 index 000000000..574cda0ce --- /dev/null +++ b/dom/events/test/test_bug418986-3.xul @@ -0,0 +1,27 @@ +<?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"?> +<!-- +Bug 418986 +--> +<window title="Mozilla Bug 418986" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<body id="body" xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986"> +Mozilla Bug 418986</a> +</body> + +<script type="application/javascript;version=1.7" src="bug418986-3.js"></script> +<script type="application/javascript;version=1.7"><![CDATA[ + // This test produces fake mouse events and checks that the screenX and screenY + // properties of the received event objects provide client window coordinates. + // Run the test once the window has loaded. + test(false); +]]></script> + +</window> diff --git a/dom/events/test/test_bug422132.html b/dom/events/test/test_bug422132.html new file mode 100644 index 000000000..bceeb79e8 --- /dev/null +++ b/dom/events/test/test_bug422132.html @@ -0,0 +1,124 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=422132 +--> +<head> + <title>Test for Bug 422132</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=422132">Mozilla Bug 422132</a> +<p id="display"></p> +<div id="target" style="font-size: 0; width: 200px; height: 200px; overflow: auto;"> + <div style="width: 1000px; height: 1000px;"></div> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 422132 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + SpecialPowers.pushPrefEnv({ + "set":[["general.smoothScroll", false], + ["mousewheel.min_line_scroll_amount", 1], + ["mousewheel.system_scroll_override_on_root_content.enabled", false], + ["mousewheel.transaction.timeout", 100000]]}, runTests)}, window); + +function runTests() +{ + var target = document.getElementById("target"); + + var scrollLeft = target.scrollLeft; + var scrollTop = target.scrollTop; + + var tests = [ + { + prepare: function() { + scrollLeft = target.scrollLeft; + scrollTop = target.scrollTop; + }, + event: { + deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.5, + deltaY: 0.5, + lineOrPageDeltaX: 0, + lineOrPageDeltaY: 0 + }, + }, { + event: { + deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.5, + deltaY: 0.5, + lineOrPageDeltaX: 0, + lineOrPageDeltaY: 0 + }, + check: function() { + is(target.scrollLeft - scrollLeft, 1, + "not scrolled to right by 0.5px delta value with pending 0.5px delta"); + is(target.scrollTop - scrollTop, 1, + "not scrolled to bottom by 0.5px delta value with pending 0.5px delta"); + }, + }, { + prepare: function() { + scrollLeft = target.scrollLeft; + scrollTop = target.scrollTop; + }, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, + deltaY: 0.5, + lineOrPageDeltaX: 0, + lineOrPageDeltaY: 0 + }, + }, { + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, + deltaY: 0.5, + lineOrPageDeltaX: 1, + lineOrPageDeltaY: 1 + }, + check: function() { + is(target.scrollLeft - scrollLeft, 1, + "not scrolled to right by 0.5 line delta value with pending 0.5 line delta"); + is(target.scrollTop - scrollTop, 1, + "not scrolled to bottom by 0.5 line delta value with pending 0.5 line delta"); + } + } + ]; + + var nextTest = function() { + var test = tests.shift(); + if (test.prepare) { + test.prepare(); + } + + sendWheelAndPaint(target, 10, 10, test.event, function() { + if (test.check) { + test.check(); + } + if (tests.length == 0) { + SimpleTest.finish(); + return; + } + + setTimeout(nextTest, 0); + }); + } + + nextTest(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug426082.html b/dom/events/test/test_bug426082.html new file mode 100644 index 000000000..d07559413 --- /dev/null +++ b/dom/events/test/test_bug426082.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=426082 +--> +<head> + <title>Test for Bug 426082</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript;version=1.8"> + +/** Test for Bug 426082 **/ +SimpleTest.waitForExplicitFinish(); +var subwindow = window.open("./bug426082.html", "bug426082", "width=800,height=1000"); + +function finishTests() { + subwindow.close(); + SimpleTest.finish(); +} +</script> +</pre> + +</body> +</html> diff --git a/dom/events/test/test_bug427537.html b/dom/events/test/test_bug427537.html new file mode 100644 index 000000000..060581f2f --- /dev/null +++ b/dom/events/test/test_bug427537.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=427537 +--> +<head> + <title>Test for Bug 427537</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=427537">Mozilla Bug 427537</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 427537 **/ + +var e = document.createEvent("CustomEvent"); +ok(e, "Should have custom event!"); + +// Test initCustomEvent and also cycle collection handling by +// passing reference to the event as 'detail' parameter. +e.initCustomEvent("foobar", true, true, e); + +var didCallListener = false; +document.addEventListener("foobar", + function(evt) { + didCallListener = true; + is(evt.type, "foobar", "Should get 'foobar' event!"); + is(evt.detail, evt, ".detail should point to the event itself."); + ok(e.bubbles, "Event should bubble!"); + ok(e.cancelable, "Event should be cancelable."); + }, true); + +document.dispatchEvent(e); +ok(didCallListener, "Should have called listener!"); + +e = document.createEvent("CustomEvent"); +e.initEvent("foo", true, true); +is(e.detail, null, "Default detail should be null."); + +e = document.createEvent("CustomEvent"); +e.initCustomEvent("foobar", true, true, 1); +is(e.detail, 1, "Detail should be 1."); + +e = document.createEvent("CustomEvent"); +e.initCustomEvent("foobar", true, true, "test"); +is(e.detail, "test", "Detail should be 'test'."); + +e = document.createEvent("CustomEvent"); +e.initCustomEvent("foobar", true, true, true); +is(e.detail, true, "Detail should be true."); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug428988.html b/dom/events/test/test_bug428988.html new file mode 100644 index 000000000..97a0069fd --- /dev/null +++ b/dom/events/test/test_bug428988.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=428988 +--> +<head> + <title>Test for Bug 428988</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428988">Mozilla Bug 428988</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 428988 **/ + +function listenerForClick(evt) { + is(Math.round(evt.mozPressure*100), 56, "Wrong .mozPressure"); +} + +function doTest() { + var target = document.getElementById("testTarget"); + target.addEventListener("click", listenerForClick, true); + var me = document.createEvent("MouseEvent"); + me.initNSMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, 0.56, 0); + target.dispatchEvent(me); + target.removeEventListener("click", listenerForClick, true); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> +</pre> +<span id="testTarget" style="border: 1px solid black;">testTarget</span> +</body> +</html> diff --git a/dom/events/test/test_bug432698.html b/dom/events/test/test_bug432698.html new file mode 100644 index 000000000..335890b0a --- /dev/null +++ b/dom/events/test/test_bug432698.html @@ -0,0 +1,223 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=432698 +--> +<head> + <title>Test for Bug 432698</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=432698">Mozilla Bug 432698</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 432698 **/ +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); +var outer; +var middle; +var inner; +var outside; +var container; +var file; +var iframe; +var checkRelatedTarget = false; +var expectedRelatedEnter = null; +var expectedRelatedLeave = null; +var mouseentercount = 0; +var mouseleavecount = 0; +var mouseovercount = 0; +var mouseoutcount = 0; + +function sendMouseEvent(t, elem) { + var r = elem.getBoundingClientRect(); + synthesizeMouse(elem, r.width / 2, r.height / 2, {type: t}); +} + +var expectedMouseEnterTargets = []; +var expectedMouseLeaveTargets = []; + +function runTests() { + outer = document.getElementById("outertest"); + middle = document.getElementById("middletest"); + inner = document.getElementById("innertest"); + outside = document.getElementById("outside"); + container = document.getElementById("container"); + file = document.getElementById("file"); + iframe = document.getElementById("iframe"); + + // Make sure ESM thinks mouse is outside the test elements. + sendMouseEvent("mousemove", outside); + + mouseentercount = 0; + mouseleavecount = 0; + mouseovercount = 0; + mouseoutcount = 0; + checkRelatedTarget = true; + expectedRelatedEnter = outside; + expectedRelatedLeave = inner; + expectedMouseEnterTargets = ["outertest", "middletest", "innertest"]; + sendMouseEvent("mousemove", inner); + is(mouseentercount, 3, "Unexpected mouseenter event count!"); + is(mouseovercount, 1, "Unexpected mouseover event count!"); + is(mouseoutcount, 0, "Unexpected mouseout event count!"); + is(mouseleavecount, 0, "Unexpected mouseleave event count!"); + expectedRelatedEnter = inner; + expectedRelatedLeave = outside; + expectedMouseLeaveTargets = ["innertest", "middletest", "outertest"]; + sendMouseEvent("mousemove", outside); + is(mouseentercount, 3, "Unexpected mouseenter event count!"); + is(mouseovercount, 1, "Unexpected mouseover event count!"); + is(mouseoutcount, 1, "Unexpected mouseout event count!"); + is(mouseleavecount, 3, "Unexpected mouseleave event count!"); + + // Event handling over native anonymous content. + var r = file.getBoundingClientRect(); + expectedRelatedEnter = outside; + expectedRelatedLeave = file; + synthesizeMouse(file, r.width / 6, r.height / 2, {type: "mousemove"}); + is(mouseentercount, 4, "Unexpected mouseenter event count!"); + is(mouseovercount, 2, "Unexpected mouseover event count!"); + is(mouseoutcount, 1, "Unexpected mouseout event count!"); + is(mouseleavecount, 3, "Unexpected mouseleave event count!"); + + // Moving mouse over type="file" shouldn't cause mouseover/out/enter/leave events + synthesizeMouse(file, r.width - (r.width / 6), r.height / 2, {type: "mousemove"}); + is(mouseentercount, 4, "Unexpected mouseenter event count!"); + is(mouseovercount, 2, "Unexpected mouseover event count!"); + is(mouseoutcount, 1, "Unexpected mouseout event count!"); + is(mouseleavecount, 3, "Unexpected mouseleave event count!"); + + expectedRelatedEnter = file; + expectedRelatedLeave = outside; + sendMouseEvent("mousemove", outside); + is(mouseentercount, 4, "Unexpected mouseenter event count!"); + is(mouseovercount, 2, "Unexpected mouseover event count!"); + is(mouseoutcount, 2, "Unexpected mouseout event count!"); + is(mouseleavecount, 4, "Unexpected mouseleave event count!"); + + // Initialize iframe + iframe.contentDocument.documentElement.style.overflow = "hidden"; + iframe.contentDocument.body.style.margin = "0px"; + iframe.contentDocument.body.style.width = "100%"; + iframe.contentDocument.body.style.height = "100%"; + iframe.contentDocument.body.innerHTML = + "<div style='width: 100%; height: 50%; border: 1px solid black;'></div>" + + "<div style='width: 100%; height: 50%; border: 1px solid black;'></div>"; + iframe.contentDocument.body.offsetLeft; // flush + + iframe.contentDocument.body.firstChild.onmouseenter = menter; + iframe.contentDocument.body.firstChild.onmouseleave = mleave; + iframe.contentDocument.body.lastChild.onmouseenter = menter; + iframe.contentDocument.body.lastChild.onmouseleave = mleave; + r = iframe.getBoundingClientRect(); + expectedRelatedEnter = outside; + expectedRelatedLeave = iframe; + // Move mouse inside the iframe. + synthesizeMouse(iframe.contentDocument.body, r.width / 2, r.height / 4, {type: "mousemove"}, + iframe.contentWindow); + synthesizeMouse(iframe.contentDocument.body, r.width / 2, r.height - (r.height / 4), {type: "mousemove"}, + iframe.contentWindow); + is(mouseentercount, 7, "Unexpected mouseenter event count!"); + expectedRelatedEnter = iframe; + expectedRelatedLeave = outside; + sendMouseEvent("mousemove", outside); + is(mouseleavecount, 7, "Unexpected mouseleave event count!"); + + checkRelatedTarget = false; + + iframe.contentDocument.body.firstChild.onmouseenter = null; + iframe.contentDocument.body.firstChild.onmouseleave = null; + iframe.contentDocument.body.lastChild.onmouseenter = null; + iframe.contentDocument.body.lastChild.onmouseleave = null; + + container.onmouseenter = null; + container.onmouseleave = null; + container.onmouseout = null; + container.onmouseover = null; + + var children = container.getElementsByTagName('*'); + for (var i=0;i<children.length;i++) { + children[i].onmouseenter = null; + children[i].onmouseleave = null; + children[i].onmouseout = null; + children[i].onmouseover = null; + } + + SimpleTest.finish(); +} + +function menter(evt) { + ++mouseentercount; + evt.stopPropagation(); + if (expectedMouseEnterTargets.length) { + var t = expectedMouseEnterTargets.shift(); + is(evt.target.id, t, "Wrong event target!"); + } + is(evt.bubbles, false, evt.type + " should not bubble!"); + is(evt.cancelable, false, evt.type + " is not cancelable!"); + is(evt.target, evt.currentTarget, "Wrong event target!"); + ok(!evt.relatedTarget || evt.target.ownerDocument == evt.relatedTarget.ownerDocument, + "Leaking nodes to another document?"); + if (checkRelatedTarget && evt.target.ownerDocument == document) { + is(evt.relatedTarget, expectedRelatedEnter, "Wrong related target (mouseenter)"); + } +} + +function mleave(evt) { + ++mouseleavecount; + evt.stopPropagation(); + if (expectedMouseLeaveTargets.length) { + var t = expectedMouseLeaveTargets.shift(); + is(evt.target.id, t, "Wrong event target!"); + } + is(evt.bubbles, false, evt.type + " should not bubble!"); + is(evt.cancelable, false, evt.type + " is not cancelable!"); + is(evt.target, evt.currentTarget, "Wrong event target!"); + ok(!evt.relatedTarget || evt.target.ownerDocument == evt.relatedTarget.ownerDocument, + "Leaking nodes to another document?"); + if (checkRelatedTarget && evt.target.ownerDocument == document) { + is(evt.relatedTarget, expectedRelatedLeave, "Wrong related target (mouseleave)"); + } +} + +function mover(evt) { + ++mouseovercount; + evt.stopPropagation(); +} + +function mout(evt) { + ++mouseoutcount; + evt.stopPropagation(); +} + +</script> +</pre> +<div id="container" onmouseenter="menter(event)" onmouseleave="mleave(event)" + onmouseout="mout(event)" onmouseover="mover(event)"> + <div id="outside" onmouseout="event.stopPropagation()" onmouseover="event.stopPropagation()">foo</div> + <div id="outertest" onmouseenter="menter(event)" onmouseleave="mleave(event)" + onmouseout="mout(event)" onmouseover="mover(event)"> + <div id="middletest" onmouseenter="menter(event)" onmouseleave="mleave(event)" + onmouseout="mout(event)" onmouseover="mover(event)"> + <div id="innertest" onmouseenter="menter(event)" onmouseleave="mleave(event)" + onmouseout="mout(event)" onmouseover="mover(event)">foo</div> + </div> + </div> + <input type="file" id="file" + onmouseenter="menter(event)" onmouseleave="mleave(event)" + onmouseout="mout(event)" onmouseover="mover(event)"> + <br> + <iframe id="iframe" width="50px" height="50px" + onmouseenter="menter(event)" onmouseleave="mleave(event)" + onmouseout="mout(event)" onmouseover="mover(event)"></iframe> +</div> +</body> +</html> diff --git a/dom/events/test/test_bug443985.html b/dom/events/test/test_bug443985.html new file mode 100644 index 000000000..0ed4138be --- /dev/null +++ b/dom/events/test/test_bug443985.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=443985 +--> +<head> + <title>Test for Bug 443985</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=443985">Mozilla Bug 443985</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 443985 **/ + + +function listenerForNoScroll(evt) { + is(evt.clientX, evt.pageX, "Wrong .pageX"); + is(evt.clientY, evt.pageY, "Wrong .pageY"); + is(evt.screenX, 0, "Wrong .screenX"); + is(evt.screenY, 0, "Wrong .screenY"); + is(evt.clientX, 10, "Wrong .clientX"); + is(evt.clientY, 10, "Wrong .clientY"); +} + +function listenerForScroll(evt) { + isnot(evt.clientX, evt.pageX, "Wrong .pageX"); + isnot(evt.clientY, evt.pageY, "Wrong .pageY"); + ok(evt.pageX > 3000, "Wrong .pageX"); + ok(evt.pageY > 3000, "Wrong .pageY"); + is(evt.screenX, 0, "Wrong .screenX"); + is(evt.screenY, 0, "Wrong .screenY"); + is(evt.clientX, 10, "Wrong .clientX"); + is(evt.clientY, 10, "Wrong .clientY"); +} + +function doTest() { + window.scrollTo(0, 0); + var target = document.getElementById("testTarget"); + target.addEventListener("click", listenerForNoScroll, true); + var me = document.createEvent("MouseEvent"); + me.initMouseEvent("click", true, true, window, 0, 0, 0, 10, 10, + false, false, false, false, 0, null); + target.dispatchEvent(me); + target.removeEventListener("click", listenerForNoScroll, true); + + target.scrollIntoView(true); + target.addEventListener("click", listenerForScroll, true); + me = document.createEvent("MouseEvent"); + me.initMouseEvent("click", true, true, window, 0, 0, 0, 10, 10, + false, false, false, false, 0, null); + target.dispatchEvent(me); + target.addEventListener("click", listenerForNoScroll, true); + + document.getElementsByTagName("a")[0].scrollIntoView(true); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> +</pre> +<div style="min-height: 4000px; min-width: 4000px;"></div> +<div style="min-width: 4000px; text-align: right;"> + <span id="testTarget" style="border: 1px solid black;">testTarget</span> +</div> +</body> +</html> + diff --git a/dom/events/test/test_bug447736.html b/dom/events/test/test_bug447736.html new file mode 100644 index 000000000..fc696d5e9 --- /dev/null +++ b/dom/events/test/test_bug447736.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=447736 +--> +<head> + <title>Test for Bug 447736</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=447736">Mozilla Bug 447736</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<div id="secondTarget"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 447736 **/ + +var loadEvent = null; +window.addEventListener("load", + function (evt) { + is(evt.target, window.document, "Wrong target!"); + is(evt.originalTarget, window.document, "Wrong originalTarget!"); + ok(evt.isTrusted, "Event should be trusted!"); + loadEvent = evt; + setTimeout("st.dispatchEvent(loadEvent)", 0); + }, true); + +var st = document.getElementById("secondTarget"); +st.addEventListener("load", + function (evt) { + is(evt.target, st, "Wrong target! (2)"); + is(evt.originalTarget, st, "Wrong originalTarget! (2)"); + ok(!evt.isTrusted, "Event shouldn't be trusted anymore!"); + SimpleTest.finish(); + }, true); + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug448602.html b/dom/events/test/test_bug448602.html new file mode 100644 index 000000000..0d2d8b580 --- /dev/null +++ b/dom/events/test/test_bug448602.html @@ -0,0 +1,220 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=448602 +--> +<head> + <title>Test for Bug 448602</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448602">Mozilla Bug 448602</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 448602 **/ + +var els, root, l2, l3; + +function runTests() { + els = SpecialPowers.Cc["@mozilla.org/eventlistenerservice;1"] + .getService(SpecialPowers.Ci.nsIEventListenerService); + + // Event listener info tests + root = document.getElementById("testroot"); + var infos = els.getListenerInfoFor(root, {}); + is(infos.length, 0, "Element shouldn't have listeners (1)"); + + var listenerSource = 'alert(event);'; + root.setAttribute("onclick", listenerSource); + infos = els.getListenerInfoFor(root, {}); + is(infos.length, 1, "Element should have listeners (1)"); + is(infos[0].toSource(), 'function onclick(event) {\n' + listenerSource + '\n}', + "Unexpected serialization (1)"); + is(infos[0].type, "click", "Wrong type (1)"); + is(infos[0].capturing, false, "Wrong phase (1)"); + is(infos[0].allowsUntrusted, true, "Should allow untrusted events (1)"); + is(SpecialPowers.unwrap(infos[0].listenerObject), root.onclick, + "Should have the right listener object (1)"); + + root.removeAttribute("onclick"); + root.setAttribute("onclick", "...invalid script..."); + SimpleTest.expectUncaughtException(true); + infos = els.getListenerInfoFor(root, {}); + SimpleTest.expectUncaughtException(false); + is(infos.length, 1); + is(infos[0].listenerObject, null); + + root.removeAttribute("onclick"); + infos = els.getListenerInfoFor(root, {}); + is(infos.length, 0, "Element shouldn't have listeners (2)"); + + var l = function (e) { alert(e); }; + root.addEventListener("foo", l, true, true); + root.addEventListener("foo", l, false, false); + infos = els.getListenerInfoFor(root, {}); + is(infos.length, 2, "Element should have listeners (2)"); + is(infos[0].toSource(), "(function (e) { alert(e); })", + "Unexpected serialization (2)"); + is(infos[0].type, "foo", "Wrong type (2)"); + is(infos[0].capturing, true, "Wrong phase (2)"); + is(infos[0].allowsUntrusted, true, "Should allow untrusted events (2)"); + is(SpecialPowers.unwrap(infos[0].listenerObject), l, + "Should have the right listener object (2)"); + is(infos[1].toSource(), "(function (e) { alert(e); })", + "Unexpected serialization (3)"); + is(infos[1].type, "foo", "Wrong type (3)"); + is(infos[1].capturing, false, "Wrong phase (3)"); + is(infos[1].allowsUntrusted, false, "Shouldn't allow untrusted events (1)"); + is(SpecialPowers.unwrap(infos[1].listenerObject), l, + "Should have the right listener object (3)"); + + root.removeEventListener("foo", l, true); + root.removeEventListener("foo", l, false); + infos = els.getListenerInfoFor(root, {}); + is(infos.length, 0, "Element shouldn't have listeners (3)"); + + root.onclick = l; + infos = els.getListenerInfoFor(root, {}); + is(infos.length, 1, "Element should have listeners (3)"); + is(infos[0].toSource(), '(function (e) { alert(e); })', + "Unexpected serialization (4)"); + is(infos[0].type, "click", "Wrong type (4)"); + is(infos[0].capturing, false, "Wrong phase (4)"); + is(infos[0].allowsUntrusted, true, "Should allow untrusted events (3)"); + is(SpecialPowers.unwrap(infos[0].listenerObject), l, + "Should have the right listener object (4)"); + + // Event target chain tests + l2 = document.getElementById("testlevel2"); + l3 = document.getElementById("testlevel3"); + var textnode = l3.firstChild; + var chain = els.getEventTargetChainFor(textnode, true, {}); + ok(chain.length > 3, "Too short event target chain."); + ok(SpecialPowers.compare(chain[0], textnode), "Wrong chain item (1)"); + ok(SpecialPowers.compare(chain[1], l3), "Wrong chain item (2)"); + ok(SpecialPowers.compare(chain[2], l2), "Wrong chain item (3)"); + ok(SpecialPowers.compare(chain[3], root), "Wrong chain item (4)"); + + var hasDocumentInChain = false; + var hasWindowInChain = false; + for (var i = 0; i < chain.length; ++i) { + if (SpecialPowers.compare(chain[i], document)) { + hasDocumentInChain = true; + } else if (SpecialPowers.compare(chain[i], window)) { + hasWindowInChain = true; + } + } + + ok(hasDocumentInChain, "Should have document in event target chain!"); + ok(hasWindowInChain, "Should have window in event target chain!"); + + try { + els.getListenerInfoFor(null, {}); + ok(false, "Should have thrown an exception."); + } catch (ex) { + ok(true, "We should be still running."); + } + setTimeout(testAllListener, 0); +} + +function dispatchTrusted(t, o) { + SpecialPowers.dispatchEvent(window, t, new Event("testevent", o)); +} + +function testAllListener() { + els = SpecialPowers.wrap(els); + var results = []; + var expectedResults = + [ { target: "testlevel3", phase: 3, trusted: false }, + { target: "testlevel3", phase: 3, trusted: false }, + { target: "testlevel3", phase: 3, trusted: true }, + { target: "testlevel3", phase: 3, trusted: true }, + { target: "testlevel3", phase: 3, trusted: true } + ]; + + function allListener(e) { + results.push({ + target: e.target.id, + phase: e.eventPhase, + trusted: e.isTrusted + }); + e.stopPropagation(); + } + function allListenerTrustedOnly(e) { + results.push({ + target: e.target.id, + phase: e.eventPhase, + trusted: e.isTrusted + }); + e.stopPropagation(); + } + + els.addListenerForAllEvents(root, allListener, false, true); + var infos = els.getListenerInfoFor(root); + var nullTypes = 0; + for (var i = 0; i < infos.length; ++i) { + if (infos[i].type == null) { + ++nullTypes; + } + } + is(nullTypes, 1, "Should have one all-event-listener!"); + + els.addListenerForAllEvents(root, allListener, false, true, true); + els.addListenerForAllEvents(root, allListenerTrustedOnly, false, false, true); + l3.dispatchEvent(new Event("testevent", { bubbles: true, composed: true })); + dispatchTrusted(l3, { bubbles: true, composed: true }); + els.removeListenerForAllEvents(root, allListener, false); + els.removeListenerForAllEvents(root, allListener, false, true); + els.removeListenerForAllEvents(root, allListenerTrustedOnly, false, true); + // make sure removeListenerForAllEvents works. + l3.dispatchEvent(new Event("testevent", { bubbles: true, composed : true })); + dispatchTrusted(l3, { bubbles: true, composed: true }); + + // Test the order of event listeners. + var clickListenerCalled = false; + var allListenerCalled = false; + function clickListener() { + clickListenerCalled = true; + ok(allListenerCalled, "Should have called '*' listener before normal listener!"); + } + function allListener2() { + allListenerCalled = true; + notok(clickListenerCalled, "Shouldn't have called click listener before '*' listener!"); + } + root.onclick = null; // Remove the listener added in earlier tests. + root.addEventListener("click", clickListener); + els.addListenerForAllEvents(root, allListener2, false, true); + l3.dispatchEvent(new MouseEvent("click", { bubbles: true })); + root.removeEventListener("click", clickListener); + els.removeListenerForAllEvents(root, allListener2, false); + ok(allListenerCalled, "Should have called '*' listener"); + ok(clickListenerCalled, "Should have called click listener"); + + is(results.length, expectedResults.length, "count"); + for (var i = 0; i < expectedResults.length; ++i) { + for (var p in expectedResults[i]) { + is(results[i][p], expectedResults[i][p], p); + } + } + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(runTests); +</script> +</pre> +<div id="testroot"> + <div id="testlevel2"> + <div id="testlevel3"> + Test + </div> + </div> +</div> +</body> +</html> diff --git a/dom/events/test/test_bug450876.html b/dom/events/test/test_bug450876.html new file mode 100644 index 000000000..79efeda28 --- /dev/null +++ b/dom/events/test/test_bug450876.html @@ -0,0 +1,41 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=450876 +--> +<head> + <title>Test for Bug 450876 - Crash [@ nsEventStateManager::GetNextTabbableMapArea] with img usemap and tabindex</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450876">Mozilla Bug 450876</a> +<p id="display"><a href="#" id="a">link to focus from</a><img usemap="#a" tabindex="1"></p> +<div id="content" style="display: none"> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 450876 **/ + +function doTest() { + is(document.activeElement, document.body, "body element should be focused"); + document.getElementById('a').focus(); + is(document.activeElement, document.getElementById('a'), "link should have focus"); + is(document.hasFocus(), true, "document should be focused"); + SpecialPowers.DOMWindowUtils.sendKeyEvent('keypress', 9, 0, 0); + is(document.activeElement, document.getElementById('a'), "body element should be focused"); + is(document.hasFocus(), false, "document should not be focused"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug456273.html b/dom/events/test/test_bug456273.html new file mode 100644 index 000000000..ac7bd2bce --- /dev/null +++ b/dom/events/test/test_bug456273.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=456273 +--> +<head> + <title>Test for Bug 456273</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=456273">Mozilla Bug 456273</a> +<p id="display">PASS if Firefox does not crash.</p> +<div id="content" style="display: none"> + +</div> + +<div id="edit456273" contenteditable="true">text</div> + +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 456273 **/ + +function doTest() { + var ev = document.createEvent('KeyboardEvent'); + ev.initKeyEvent("keypress", true, true, null, true, false, + false, false, 0, "z".charCodeAt(0)); + SpecialPowers.dispatchEvent(window, document.getElementById('edit456273'), ev); + + ok(true, "PASS"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug457672.html b/dom/events/test/test_bug457672.html new file mode 100644 index 000000000..b3dc5bcb3 --- /dev/null +++ b/dom/events/test/test_bug457672.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=457672 +--> +<head> + <title>Test for Bug 457672</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=457672">Mozilla Bug 457672</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 457672 **/ + +var windowBlurCount = 0; + +function listener(evt) { + if (evt.type == "focus") { + is(windowBlurCount, 1, + "Window should have got blur event when opening a new tab!"); + document.getElementsByTagName("a")[0].focus(); + SimpleTest.finish(); + } else if (evt.type == "blur") { + ++windowBlurCount; + } + document.getElementById('log').textContent += evt.target + ":" + evt.type + "\n"; +} + +function startTest() { + SpecialPowers.pushPrefEnv({"set": [["browser.link.open_newwindow", 3]]}, function() { + document.getElementsByTagName("a")[0].focus(); + // Note, focus/blur don't bubble + window.addEventListener("focus", listener, false); + window.addEventListener("blur", listener, false); + var subwin = window.open("about:blank", "", ""); + subwin.addEventListener("focus", function(e) { subwin.close(); }, false); + }); +} + +addLoadEvent(startTest); +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +<pre id="log"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug489671.html b/dom/events/test/test_bug489671.html new file mode 100644 index 000000000..4def80cba --- /dev/null +++ b/dom/events/test/test_bug489671.html @@ -0,0 +1,55 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=489671 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 489671</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=489671" + >Mozilla Bug 489671</a> +<p id="display" onclick="queueNextTest(); throw 'Got click 1';"></p> +<script> +// override window.onerror so it won't see our exceptions +window.onerror = function() {} + +var testNum = 0; +function doTest() { + switch(testNum++) { + case 0: + var event = document.createEvent("MouseEvents"); + event.initMouseEvent("click", true, true, document.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + $("display").dispatchEvent(event); + break; + case 1: + var script = document.createElement("script"); + script.textContent = "queueNextTest(); throw 'Got click 2'"; + document.body.appendChild(script); + break; + case 2: + window.setTimeout("queueNextTest(); throw 'Got click 3'", 0); + break; + case 3: + SimpleTest.endMonitorConsole(); + return; + } +} +function queueNextTest() { SimpleTest.executeSoon(doTest); } + +SimpleTest.waitForExplicitFinish(); +SimpleTest.monitorConsole(SimpleTest.finish, [ + { errorMessage: "uncaught exception: Got click 1" }, + { errorMessage: "uncaught exception: Got click 2" }, + { errorMessage: "uncaught exception: Got click 3" } +]); + +doTest(); +</script> +</body> +</html> diff --git a/dom/events/test/test_bug493251.html b/dom/events/test/test_bug493251.html new file mode 100644 index 000000000..d23045dee --- /dev/null +++ b/dom/events/test/test_bug493251.html @@ -0,0 +1,194 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=493251 +--> +<head> + <title>Test for Bug 493251</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=493251">Mozilla Bug 493251</a> +<p id="display"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 493251 **/ + + var win; + + var mouseDown = 0; + var mouseUp = 0; + var mouseClick = 0; + + var keyDown = 0; + var keyPress = 0; + var keyUp = 0; + + function suppressEventHandling(aSuppress) { + var utils = SpecialPowers.getDOMWindowUtils(win); + ok(true, "suppressEventHandling: aSuppress=" + aSuppress); + utils.suppressEventHandling(aSuppress); + } + + function dispatchKeyEvent(type) { + var utils = SpecialPowers.getDOMWindowUtils(win); + ok(true, "Dipatching key event: type=" + type); + utils.sendKeyEvent(type, + SpecialPowers.Ci.nsIDOMKeyEvent.DOM_VK_A, + 0, 0); + } + + function dispatchMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers) { + var utils = SpecialPowers.getDOMWindowUtils(win); + ok(true, "Dipatching mouse event: aType=" + aType + ", aX=" + aX + ", aY" + + aY + ", aButton=" + aButton + ", aClickCount=" + aClickCount + + ", aModifiers=" + aModifiers); + utils.sendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers); + } + + function dumpEvent(aEvent) { + var detail = "target=" + aEvent.target + ", originalTarget=" + + aEvent.originalTarget + ", defaultPrevented=" + + aEvent.defaultPrevented + ", isTrusted=" + aEvent.isTrusted; + switch (aEvent.type) { + case "keydown": + case "keypress": + case "keyup": + detail += ", charCode=0x" + aEvent.charCode.toString(16) + + ", keyCode=0x" + aEvent.keyCode.toString(16) + + ", altKey=" + (aEvent.altKey ? "PRESSED" : "no") + + ", ctrlKey=" + (aEvent.ctrlKey ? "PRESSED" : "no") + + ", shiftKey=" + (aEvent.shiftKey ? "PRESSED" : "no") + + ", metaKey=" + (aEvent.metaKey ? "PRESSED" : "no"); + break; + case "mousedown": + case "mouseup": + case "click": + detail += ", screenX=" + aEvent.screenX + ", screenY=" + aEvent.screenY + + ", clientX=" + aEvent.clientX + ", clientY=" + aEvent.clientY + + ", altKey=" + (aEvent.altKey ? "PRESSED" : "no") + + ", ctrlKey=" + (aEvent.ctrlKey ? "PRESSED" : "no") + + ", shiftKey=" + (aEvent.shiftKey ? "PRESSED" : "no") + + ", metaKey=" + (aEvent.metaKey ? "PRESSED" : "no") + + ", button=" + aEvent.button + + ", relatedTarget=" + aEvent.relatedTarget; + break; + } + ok(true, aEvent.type + " event is handled: " + detail); + + var fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]. + getService(SpecialPowers.Ci.nsIFocusManager); + ok(true, "focused element is \"" + fm.focusedElement + + "\" and focused window is \"" + fm.focusedWindow + + "\" (the testing window is \"" + win + "\""); + } + + function doTest() { + win.document.getElementsByTagName("input")[0].focus(); + win.addEventListener("keydown", + function(e) { dumpEvent(e); ++keyDown; }, true); + win.addEventListener("keypress", + function(e) { dumpEvent(e); ++keyPress; }, true); + win.addEventListener("keyup", + function(e) { dumpEvent(e); ++keyUp; }, true); + win.addEventListener("mousedown", + function(e) { dumpEvent(e); ++mouseDown; }, true); + win.addEventListener("mouseup", + function(e) { dumpEvent(e); ++mouseUp; }, true); + win.addEventListener("click", + function(e) { dumpEvent(e); ++mouseClick; }, true); + + ok(true, "doTest #1..."); + dispatchKeyEvent("keydown"); + dispatchKeyEvent("keypress"); + dispatchKeyEvent("keyup"); + is(keyDown, 1, "Wrong number events (1)"); + is(keyPress, 1, "Wrong number events (2)"); + is(keyUp, 1, "Wrong number events (3)"); + + ok(true, "doTest #2..."); + suppressEventHandling(true); + dispatchKeyEvent("keydown"); + dispatchKeyEvent("keypress"); + dispatchKeyEvent("keyup"); + is(keyDown, 1, "Wrong number events (4)"); + is(keyPress, 1, "Wrong number events (5)"); + is(keyUp, 1, "Wrong number events (6)"); + suppressEventHandling(false); + is(keyDown, 1, "Wrong number events (7)"); + is(keyPress, 1, "Wrong number events (8)"); + is(keyUp, 1, "Wrong number events (9)"); + + setTimeout(continueTest1, 0); + } + + function continueTest1() { + ok(true, "continueTest1..."); + dispatchKeyEvent("keydown"); + suppressEventHandling(true); + dispatchKeyEvent("keypress"); + dispatchKeyEvent("keyup"); + is(keyDown, 2, "Wrong number events (10)"); + is(keyPress, 1, "Wrong number events (11)"); + is(keyUp, 1, "Wrong number events (12)"); + suppressEventHandling(false); + setTimeout(continueTest2, 0); + } + + function continueTest2() { + ok(true, "continueTest2 #1..."); + is(keyDown, 2, "Wrong number events (13)"); + is(keyPress, 2, "Wrong number events (14)"); + is(keyUp, 2, "Wrong number events (15)"); + + dispatchMouseEvent("mousedown", 5, 5, 0, 1, 0); + dispatchMouseEvent("mouseup", 5, 5, 0, 1, 0); + is(mouseDown, 1, "Wrong number events (16)"); + is(mouseUp, 1, "Wrong number events (17)"); + is(mouseClick, 1, "Wrong number events (18)"); + + ok(true, "continueTest2 #2..."); + suppressEventHandling(true); + dispatchMouseEvent("mousedown", 5, 5, 0, 1, 0); + dispatchMouseEvent("mouseup", 5, 5, 0, 1, 0); + suppressEventHandling(false); + is(mouseDown, 1, "Wrong number events (19)"); + is(mouseUp, 1, "Wrong number events (20)"); + is(mouseClick, 1, "Wrong number events (21)"); + + setTimeout(continueTest3, 0); + } + + function continueTest3() { + ok(true, "continueTest3..."); + dispatchMouseEvent("mousedown", 5, 5, 0, 1, 0); + suppressEventHandling(true); + dispatchMouseEvent("mouseup", 5, 5, 0, 1, 0); + suppressEventHandling(false); + setTimeout(continueTest4, 1000); + } + + function continueTest4() { + ok(true, "continueTest4..."); + is(mouseDown, 2, "Wrong number events (19)"); + is(mouseUp, 2, "Wrong number events (20)"); + is(mouseClick, 2, "Wrong number events (21)"); + win.close(); + SimpleTest.finish(); + } + + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + win = window.open("window_bug493251.html", "_blank" , "width=500,height=500"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug502818.html b/dom/events/test/test_bug502818.html new file mode 100644 index 000000000..699b312f3 --- /dev/null +++ b/dom/events/test/test_bug502818.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=502818 +--> +<head> + <title>Test for Bug 502818</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=502818">Mozilla Bug 502818</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 502818 **/ + + var count = 0; + + function listener(evt) { + ++count; + is(evt.foo, 'bar', "Event.prototype should be accessible from " + evt.type); + } + + Event.prototype.foo = 'bar'; + window.addEventListener('DOMMouseScroll', listener, false); + window.addEventListener('MozMousePixelScroll', listener, false); + window.addEventListener('foobar', listener, false); + + var e = document.createEvent("mousescrollevents"); + e.initMouseScrollEvent("DOMMouseScroll", true, true, window, + 0, 0, 0, 0, 0, false, false, false, false, + 0, null, 1); + window.dispatchEvent(e); + + e.initMouseScrollEvent("MozMousePixelScroll", true, true, window, + 0, 0, 0, 0, 0, false, false, false, false, + 0, null, 1); + window.dispatchEvent(e); + + e.initMouseScrollEvent("foobar", true, true, window, + 0, 0, 0, 0, 0, false, false, false, false, + 0, null, 1); + window.dispatchEvent(e); + + is(count, 3, "Wrong number tests run!"); +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug508479.html b/dom/events/test/test_bug508479.html new file mode 100644 index 000000000..9cf9e22b8 --- /dev/null +++ b/dom/events/test/test_bug508479.html @@ -0,0 +1,103 @@ +<html> +<head> + <title>Tests for the dragstart event</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + +<script> + +var gGotHandlingDrop = false; +var gGotNotHandlingDrop = false; + +SimpleTest.waitForExplicitFinish(); + +function fireEvent(target, event) { + SpecialPowers.DOMWindowUtils.dispatchDOMEventViaPresShell(target, event, true); +} + +function fireDrop(element, shouldAllowDrop, shouldAllowOnlyChromeDrop) { + var ds = SpecialPowers.Cc["@mozilla.org/widget/dragservice;1"]. + getService(SpecialPowers.Ci.nsIDragService); + + var dataTransfer; + var trapDrag = function(event) { + dataTransfer = event.dataTransfer; + dataTransfer.setData("text/plain", "Hello");; + dataTransfer.dropEffect = "move"; + event.preventDefault(); + event.stopPropagation(); + } + + // need to use real mouse action + window.addEventListener("dragstart", trapDrag, true); + synthesizeMouse(element, 2, 2, { type: "mousedown" }); + synthesizeMouse(element, 11, 11, { type: "mousemove" }); + synthesizeMouse(element, 20, 20, { type: "mousemove" }); + window.removeEventListener("dragstart", trapDrag, true); + synthesizeMouse(element, 20, 20, { type: "mouseup" }); + + ds.startDragSession(); + + var event = document.createEvent("DragEvent"); + event.initDragEvent("dragover", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); + fireEvent(element, event); + + is(ds.getCurrentSession().canDrop, shouldAllowDrop, "Unexpected .canDrop"); + is(ds.getCurrentSession().onlyChromeDrop, shouldAllowOnlyChromeDrop, + "Unexpected .onlyChromeDrop"); + + event = document.createEvent("DragEvent"); + event.initDragEvent("drop", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); + fireEvent(element, event); + + ds.endDragSession(false); + ok(!ds.getCurrentSession(), "There shouldn't be a drag session anymore!"); +} + +var chromeGotEvent = false; +function chromeListener(e) { + chromeGotEvent = true; +} + +function runTests() +{ + var targetHandling = document.getElementById("handling_target"); + fireDrop(targetHandling, true, false); + + is(gGotHandlingDrop, true, "Got drop on accepting element (1)"); + is(gGotNotHandlingDrop, false, "Didn't get drop on unaccepting element (1)"); + + // reset + gGotHandlingDrop = false; + gGotNotHandlingDrop = false; + + SpecialPowers.addChromeEventListener("drop", chromeListener, true, false); + var targetNotHandling = document.getElementById("nothandling_target"); + fireDrop(targetNotHandling, true, true); + SpecialPowers.removeChromeEventListener("drop", chromeListener, true); + ok(chromeGotEvent, "Chrome should have got drop event!"); + is(gGotHandlingDrop, false, "Didn't get drop on accepting element (2)"); + is(gGotNotHandlingDrop, false, "Didn't get drop on unaccepting element (2)"); + + SimpleTest.finish(); +} + +</script> + +<body onload="window.setTimeout(runTests, 0);"> + +<img style="width: 100px; height: 100px;" + src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82" + id="handling_target" + ondragenter="event.preventDefault()" + ondragover="event.preventDefault()" + ondrop="gGotHandlingDrop = true;"> + +<img style="width: 100px; height: 100px;" + src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82" + id="nothandling_target" + ondrop="gGotNotHandlingDrop = true;"> + +</body> +</html> diff --git a/dom/events/test/test_bug517851.html b/dom/events/test/test_bug517851.html new file mode 100644 index 000000000..895c867a1 --- /dev/null +++ b/dom/events/test/test_bug517851.html @@ -0,0 +1,112 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=517851 +--> +<head> + <title>Test for Bug 517851</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=517851">Mozilla Bug 517851</a> +<p id="display"></p> +<div id="content" style="display: none"> + <iframe id="subframe"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 517851 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { +window.handledCount = 0; +window.testReturnValue = false; +var target = document.createElement("div"); +var target2 = $("subframe").contentDocument.body; +target.setAttribute("onerror", "++window.handledCount; return window.testReturnValue;"); +target.setAttribute("onmouseover", "++window.handledCount; return window.testReturnValue;"); +target.setAttribute("onbeforeunload", "++window.handledCount; return window.testReturnValue;"); +target2.setAttribute("onbeforeunload", "++window.parent.handledCount; return window.parent.testReturnValue;"); +target.setAttribute("onmousemove", "++window.handledCount; return window.testReturnValue;"); + +var e = document.createEvent("Event"); +e.initEvent("error", true, true); +window.testReturnValue = false; +is(target.dispatchEvent(e), !window.testReturnValue, + "error event should have reverse return value handling!"); +is(handledCount, 1, "Wrong event count!"); +window.testReturnValue = true; +is(target.dispatchEvent(e), !window.testReturnValue, + "error event should have reverse return value handling (2)!"); +is(handledCount, 2, "Wrong event count!"); + +e = document.createEvent("MouseEvent"); +e.initEvent("mouseover", true, true); +window.testReturnValue = false; +is(target.dispatchEvent(e), !window.testReturnValue, + "mouseover event should have reverse return value handling!"); +is(handledCount, 3, "Wrong event count!"); +window.testReturnValue = true; +is(target.dispatchEvent(e), !window.testReturnValue, + "mouseover event should have reverse return value handling (2)!"); +is(handledCount, 4, "Wrong event count!"); + +e = document.createEvent("BeforeUnloadEvent"); +e.initEvent("beforeunload", true, true); +window.testReturnValue = true; +is(target.dispatchEvent(e), true, + "beforeunload event on random element should not be prevented!"); +is(handledCount, 4, "Wrong event count; handler should not have run!"); +is(target2.dispatchEvent(e), false, + "beforeunload event should be prevented!"); +is(handledCount, 5, "Wrong event count!"); +window.testReturnValue = false; +is(target.dispatchEvent(e), false, + "beforeunload event on random element should be prevented because the event was already cancelled!"); +is(handledCount, 5, "Wrong event count; handler should not have run! (2)"); + +e = document.createEvent("BeforeUnloadEvent"); +e.initEvent("beforeunload", true, true); +window.testReturnValue = false; +is(target.dispatchEvent(e), true, + "beforeunload event on random element should not be prevented (2)!"); +is(handledCount, 5, "Wrong event count; handler should not have run! (2)"); + +is(target2.dispatchEvent(e), false, + "beforeunload event should be prevented (2)!"); +is(handledCount, 6, "Wrong event count!"); + +// Create normal event for beforeunload. +e = document.createEvent("Event"); +e.initEvent("beforeunload", true, true); +window.testReturnValue = true; +is(target.dispatchEvent(e), true, + "beforeunload event shouldn't be prevented (3)!"); +is(handledCount, 6, "Wrong event count: handler should not have run(3)!"); +is(target2.dispatchEvent(e), true, + "beforeunload event shouldn't be prevented (3)!"); +is(handledCount, 7, "Wrong event count!"); + +e = document.createEvent("MouseEvent"); +e.initEvent("mousemove", true, true); +window.testReturnValue = true; +is(target.dispatchEvent(e), window.testReturnValue, + "mousemove event shouldn't have reverse return value handling!"); +is(handledCount, 8, "Wrong event count!"); +window.testReturnValue = false; +is(target.dispatchEvent(e), window.testReturnValue, + "mousemove event shouldn't have reverse return value handling (2)!"); +is(handledCount, 9, "Wrong event count!"); + +// Now unhook the beforeunload handler in the subframe, so we don't prompt to +// unload. +target2.onbeforeunload = null; + +SimpleTest.finish(); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug524674.xul b/dom/events/test/test_bug524674.xul new file mode 100644 index 000000000..463d9269b --- /dev/null +++ b/dom/events/test/test_bug524674.xul @@ -0,0 +1,147 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=524674 +--> +<window title="Mozilla Bug 524674" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=524674" + target="_blank">Mozilla Bug 524674</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 524674 **/ + + var els = Components.classes["@mozilla.org/eventlistenerservice;1"] + .getService(Components.interfaces.nsIEventListenerService); + + const Ci = Components.interfaces; + + function dummyListener() {} + + var runningTest = null; + var d = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); + var xhr = new XMLHttpRequest(); + + // Test also double removals and such. + var tests = [ + function() { + els.addListenerChangeListener(changeListener); + d.addEventListener("foo", dummyListener); + d.addEventListener("foo", dummyListener); + xhr.addEventListener("foo", dummyListener); + tests[0] = [{target: d, listeners: ["onfoo"]}, + {target: xhr, listeners: ["onfoo"]}]; + }, + function() { + d.addEventListener("bar", dummyListener); + d.addEventListener("baz", dummyListener); + xhr.addEventListener("bar", dummyListener); + xhr.addEventListener("baz", dummyListener); + tests[0] = [{target: d, listeners: ["onbaz", "onbar"]}, + {target: xhr, listeners: ["onbaz", "onbar"]}]; + }, + function() { + d.onclick = dummyListener; + d.onclick = dummyListener; + xhr.onload = dummyListener; + tests[0] = [{target: d, listeners: ["onclick"]}, + {target: xhr, listeners: ["onload"]}]; + }, + function() { + d.onclick = function() {}; + tests[0] = [{target: d, listeners: ["onclick"]}]; + }, + function() { + d.removeEventListener("foo", dummyListener); + d.removeEventListener("foo", dummyListener); + xhr.removeEventListener("foo", dummyListener); + tests[0] = [{target: d, listeners: ["onfoo"]}, + {target: xhr, listeners: ["onfoo"]}]; + }, + function() { + d.removeEventListener("bar", dummyListener); + d.removeEventListener("baz", dummyListener); + xhr.removeEventListener("bar", dummyListener); + xhr.removeEventListener("baz", dummyListener); + tests[0] = [{target: d, listeners: ["onbar", "onbaz"]}, + {target: xhr, listeners: ["onbar", "onbaz"]}]; + }, + function() { + d.onclick = null; + d.onclick = null; + xhr.onload = null; + tests[0] = [{target: d, listeners: ["onclick"]}, + {target: xhr, listeners: ["onload"]}]; + }, + function() { + els.removeListenerChangeListener(changeListener); + // Check that once we've removed the change listener, it isn't called anymore. + d.addEventListener("foo", dummyListener); + xhr.addEventListener("foo", dummyListener); + SimpleTest.executeSoon(function() { + SimpleTest.finish(); + }); + } + ]; + + SimpleTest.executeSoon(tests[0]); + + function changeListener(array) { + if (typeof tests[0] == "function") { + return; + } + var expectedEventChanges = tests[0]; + var eventChanges = array.enumerate(); + var i = 0; + while (eventChanges.hasMoreElements() && i < expectedEventChanges.length) { + var current; + try { + current = eventChanges.getNext().QueryInterface(Ci.nsIEventListenerChange); + var expected = expectedEventChanges[i]; + + if (current.target == expected.target) { + // expected.target.listeners should be a subset of + // current.changedListenerNames if all expected listener changes were + // sent. We may get random other event listener changes here too, not + // just the one from the test. + is(current.target, expected.target, current.target + " = " + expected.target); + + var eNames = current.changedListenerNames.enumerate(); + var listeners = []; + while (eNames.hasMoreElements()) { + var listenerName = eNames.getNext().QueryInterface(Ci.nsIAtom).toString(); + listeners.push(listenerName); + } + var matchAll = expected.listeners.every(function(val) + { return listeners.indexOf(val) >= 0; }); + if (!matchAll) + return; + ++i; + } + } catch(ex) { + continue; + } + } + if (expectedEventChanges.length != i) { + return; + } + + is(expectedEventChanges.length, i, "Should have got notification for all the changes."); + tests.shift(); + + ok(tests.length); + SimpleTest.executeSoon(tests[0]); + } + + SimpleTest.waitForExplicitFinish(); + ]]> + </script> +</window> diff --git a/dom/events/test/test_bug534833.html b/dom/events/test/test_bug534833.html new file mode 100644 index 000000000..9d9a2eb1d --- /dev/null +++ b/dom/events/test/test_bug534833.html @@ -0,0 +1,157 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=534833 +--> +<head> + <title>Test for Bug 534833</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534833">Mozilla Bug 534833</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 534833 **/ +SimpleTest.waitForExplicitFinish(); +addLoadEvent(runTests); + +var input1GotClick = 0; +var input2GotClick = 0; +var textarea1GotClick = 0; +var textarea2GotClick = 0; +var div1GotClick = 0; +var div2GotClick = 0; + +var tests = [ { element: "text", clickText: true }, + { element: "text2", clickText: false }, + { element: "area", clickText: true }, + { element: "area2", clickText: false }, + { element: "d", clickText: true }, + { element: "d", clickText: false }, + { element: "d2", clickText: true }, + { element: "d2", clickText: false } + ]; + +function nextTest_() { + if (!tests.length) { + finishTests(); + return; + } + + var test = tests.shift(); + var el = document.getElementById(test.element); + el.scrollIntoView(true); + if (test.clickText) { + synthesizeMouse(el, 5, 5, {type : "mousedown" }); + synthesizeMouse(el, 5, 5, {type : "mouseup" }); + } else { + synthesizeMouse(el, el.getBoundingClientRect().width - 5, 5, {type : "mousedown" }); + synthesizeMouse(el, el.getBoundingClientRect().width - 5, 5, {type : "mouseup" }); + } + nextTest(); +} + +function nextTest() { + var el = document.getElementById("initialfocus"); + + el.addEventListener("focus", function() { + el.removeEventListener("focus", arguments.callee, false); + setTimeout(nextTest_, 0); + }, false); + el.focus(); +} + +function runTests() { + var t = document.getElementById("text"); + var t2 = document.getElementById("text2"); + var a = document.getElementById("area"); + var a2 = document.getElementById("area2"); + var d = document.getElementById("d"); + var d2 = document.getElementById("d2"); + + // input 1 + t.onfocus = function(e) { + t.value = ""; + } + t.onclick = function(e) { + ++input1GotClick; + } + + // input 2 + t2.onfocus = function(e) { + t2.value = ""; + } + t2.onclick = function(e) { + ++input2GotClick; + } + + // textarea 1 + a.onfocus = function(e) { + a.value = ""; + } + a.onclick = function(e) { + ++textarea1GotClick; + } + + // textarea 2 + a2.onfocus = function(e) { + a2.value = ""; + } + a2.onclick = function(e) { + ++textarea2GotClick; + } + + // div 1 + var c = 0; + d.onmousedown = function(e) { + d.textContent = (++c) + " / click before or after |"; + } + d.onclick = function(e) { + ++div1GotClick; + } + + // div 2 + var c2 = 0; + d2.onmousedown = function(e) { + d2.firstChild.data = (++c2) + " / click before or after |"; + } + d2.onclick = function(e) { + ++div2GotClick; + } + nextTest(); +} + +function finishTests() { + is(input1GotClick, 1, "input element should have got a click!"); + is(input2GotClick, 1, "input element should have got a click! (2)"); + is(textarea1GotClick, 1, "textarea element should have got a click!"); + is(textarea2GotClick, 1, "textarea element should have got a click! (2)"); + is(div1GotClick, 2, "div element's content text was replaced, it should have got 2 click!"); + is(div2GotClick, 2, "div element's content text was modified, it should have got 2 clicks!"); + SimpleTest.finish(); +} + +</script> +</pre> +<input type="text" id="initialfocus"><br> +<input type="text" id="text" value="click before |" style="width: 95%;"><br> +<input type="text" id="text2" value="click after |" style="width: 95%;"> +<br> +<textarea id="area" rows="2" style="width: 95%;"> + click before + | +</textarea><br> +<textarea id="area2" rows="2" style="width: 95%;"> + click after | +</textarea> +<div id="d" style="border: 1px solid black;">click before or after |</div> +<div id="d2" style="border: 1px solid black;">click before or after |</div> +</body> +</html> diff --git a/dom/events/test/test_bug545268.html b/dom/events/test/test_bug545268.html new file mode 100644 index 000000000..c2ad64780 --- /dev/null +++ b/dom/events/test/test_bug545268.html @@ -0,0 +1,141 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=545268 +--> +<head> + <title>Test for Bug 545268</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=545268">Mozilla Bug 545268</a> +<p id="display"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 545268 + Like the test for bug 493251, but we test that suppressing events in + a parent window stops the events from reaching the child window. */ + + var win; + var subwin; + + var mouseDown = 0; + var mouseUp = 0; + var mouseClick = 0; + + var keyDown = 0; + var keyPress = 0; + var keyUp = 0; + + function dispatchKeyEvent(type) { + var utils = SpecialPowers.getDOMWindowUtils(subwin); + utils.sendKeyEvent(type, + SpecialPowers.Ci.nsIDOMKeyEvent.DOM_VK_A, + 0, 0); + } + + function doTest() { + var utils = SpecialPowers.getDOMWindowUtils(win); + var f = win.document.getElementById("f"); + subwin = f.contentWindow; + subwin.document.getElementsByTagName("input")[0].focus(); + subwin.addEventListener("keydown", function(e) { ++keyDown; }, true); + subwin.addEventListener("keypress", function(e) { ++keyPress; }, true); + subwin.addEventListener("keyup", function(e) { ++keyUp; }, true); + subwin.addEventListener("mousedown", function(e) { ++mouseDown; }, true); + subwin.addEventListener("mouseup", function(e) { ++mouseUp; }, true); + subwin.addEventListener("click", function(e) { ++mouseClick; }, true); + + dispatchKeyEvent("keydown"); + dispatchKeyEvent("keypress"); + dispatchKeyEvent("keyup"); + is(keyDown, 1, "Wrong number events (1)"); + is(keyPress, 1, "Wrong number events (2)"); + is(keyUp, 1, "Wrong number events (3)"); + + // Test that suppressing events on the parent window prevents key + // events in the subdocument window + utils.suppressEventHandling(true); + dispatchKeyEvent("keydown"); + dispatchKeyEvent("keypress"); + dispatchKeyEvent("keyup"); + is(keyDown, 1, "Wrong number events (4)"); + is(keyPress, 1, "Wrong number events (5)"); + is(keyUp, 1, "Wrong number events (6)"); + utils.suppressEventHandling(false); + is(keyDown, 1, "Wrong number events (7)"); + is(keyPress, 1, "Wrong number events (8)"); + is(keyUp, 1, "Wrong number events (9)"); + + setTimeout(continueTest1, 0); + } + + function continueTest1() { + var utils = SpecialPowers.getDOMWindowUtils(win); + dispatchKeyEvent("keydown"); + utils.suppressEventHandling(true); + dispatchKeyEvent("keypress"); + dispatchKeyEvent("keyup"); + is(keyDown, 2, "Wrong number events (10)"); + is(keyPress, 1, "Wrong number events (11)"); + is(keyUp, 1, "Wrong number events (12)"); + utils.suppressEventHandling(false); + setTimeout(continueTest2, 0); + } + + function continueTest2() { + var utils = SpecialPowers.getDOMWindowUtils(win); + is(keyDown, 2, "Wrong number events (13)"); + is(keyPress, 2, "Wrong number events (14)"); + is(keyUp, 2, "Wrong number events (15)"); + + utils.sendMouseEvent("mousedown", 5, 5, 0, 1, 0); + utils.sendMouseEvent("mouseup", 5, 5, 0, 1, 0); + is(mouseDown, 1, "Wrong number events (16)"); + is(mouseUp, 1, "Wrong number events (17)"); + is(mouseClick, 1, "Wrong number events (18)"); + + utils.suppressEventHandling(true); + utils.sendMouseEvent("mousedown", 5, 5, 0, 1, 0); + utils.sendMouseEvent("mouseup", 5, 5, 0, 1, 0); + utils.suppressEventHandling(false); + is(mouseDown, 1, "Wrong number events (19)"); + is(mouseUp, 1, "Wrong number events (20)"); + is(mouseClick, 1, "Wrong number events (21)"); + + setTimeout(continueTest3, 0); + } + + function continueTest3() { + var utils = SpecialPowers.getDOMWindowUtils(win); + utils.sendMouseEvent("mousedown", 5, 5, 0, 1, 0); + utils.suppressEventHandling(true); + utils.sendMouseEvent("mouseup", 5, 5, 0, 1, 0); + utils.suppressEventHandling(false); + setTimeout(continueTest4, 1000); + } + + function continueTest4() { + is(mouseDown, 2, "Wrong number events (19)"); + is(mouseUp, 2, "Wrong number events (20)"); + is(mouseClick, 2, "Wrong number events (21)"); + win.close(); + SimpleTest.finish(); + } + + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + win = window.open("data:text/html,<iframe id='f' style='position:absolute; border:none; width:100%; height:100%; left:0; top:0' src='data:text/html,<input>'>", "" , ""); + win.onload = doTest; + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug547996-1.html b/dom/events/test/test_bug547996-1.html new file mode 100644 index 000000000..11944ec5d --- /dev/null +++ b/dom/events/test/test_bug547996-1.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=547996 +--> +<head> + <title>Test for Bug 547996</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=547996">Mozilla Bug 547996</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 547996 **/ +/* mouseEvent.mozInputSource attribute */ + +function prepareListener(eventName, expectedValue) { + return function(event) { + is(event.mozInputSource, expectedValue, "Correct .mozInputSource value in " + eventName); + }; +} + +const INPUT_SOURCE_UNKNOWN = SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN; +const INPUT_SOURCE_KEYBOARD = SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_KEYBOARD; + +function doTest() { + var eventNames = [ + "mousedown", + "mouseup", + "click", + "dblclick", + "contextmenu", + "DOMMouseScroll", + "dragdrop", + "dragstart", + "dragend", + "dragenter", + "dragleave", + "dragover" + ]; + + var target = document.getElementById("testTarget"); + + for (var i in eventNames) { + for(var value = INPUT_SOURCE_UNKNOWN; value <= INPUT_SOURCE_KEYBOARD; value++) { + var eventName = eventNames[i]; + var listener = prepareListener(eventName, value); + + target.addEventListener(eventName, listener, false); + + var newEvent = document.createEvent("MouseEvent"); + newEvent.initNSMouseEvent(eventName, true, true, window, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, 0, value); + target.dispatchEvent(newEvent); + target.removeEventListener(eventName, listener, false); + } + + // Events created by script that do not initialize the mozInputSource + // value should have the value MOZ_SOURCE_UNKNOWN + var listener = prepareListener(eventName, INPUT_SOURCE_UNKNOWN); + target.addEventListener(eventName, listener, false); + + var newEvent = document.createEvent("MouseEvent"); + newEvent.initMouseEvent(eventName, true, true, window, 0, 0, 0, 0, 0, + false, false, false, false, 0, null); + target.dispatchEvent(newEvent); + target.removeEventListener(eventName, listener, false); + + } + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> +</pre> +<span id="testTarget" style="border: 1px solid black;">testTarget</span> +</body> +</html> diff --git a/dom/events/test/test_bug547996-2.xhtml b/dom/events/test/test_bug547996-2.xhtml new file mode 100644 index 000000000..52ee9a06e --- /dev/null +++ b/dom/events/test/test_bug547996-2.xhtml @@ -0,0 +1,125 @@ +<?xml version="1.0"?> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=547996 +--> +<head> + <title>Test for Bug 547996</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=547996">Mozilla Bug 547996</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"><![CDATA[ + +/** Test for Bug 547996 **/ +/* mouseEvent.mozInputSource attribute */ + +var expectedInputSource = null; + +function check(event) { + is(event.mozInputSource, expectedInputSource, ".mozInputSource"); +} + +function doTest() { + setup(); + + expectedInputSource = SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_KEYBOARD; + testKeyboard(); + + expectedInputSource = SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE; + testMouse(); + + expectedInputSource = SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN; + testScriptedClicks(); + + cleanup(); + SimpleTest.finish(); +} + +function testKeyboard() { + + $("inputTarget").focus(); + synthesizeKey("VK_SPACE", {}); + synthesizeKey("VK_RETURN", {}); + + $("buttonTarget").focus(); + synthesizeKey("VK_SPACE", {}); + synthesizeKey("VK_RETURN", {}); + + //XUL buttons do not generate click on ENTER or SPACE, + //they do only on accessKey + + $("anchorTarget").focus(); + synthesizeKey("VK_RETURN", {}); + + synthesizeKey("VK_TAB", {}); + synthesizeKey("VK_SPACE", {}); + synthesizeKey("VK_RIGHT", {}); + + $("checkboxTarget").focus(); + synthesizeKey("VK_SPACE", {}); + + var accessKeyDetails = (navigator.platform.indexOf("Mac") >= 0) ? + { ctrlKey : true } : { altKey : true, shiftKey: true }; + + synthesizeKey("o", accessKeyDetails); + synthesizeKey("t", accessKeyDetails); +} + +function testMouse() { + synthesizeMouse($("inputTarget"), 0, 0, {}); + synthesizeMouse($("buttonTarget"), 0, 0, {}); + synthesizeMouse($("xulButtonTarget"), 0, 0, {}); + synthesizeMouse($("anchorTarget"), 0, 0, {}); + synthesizeMouse($("radioTarget1"), 0, 0, {}); + synthesizeMouse($("radioTarget2"), 0, 0, {}); + synthesizeMouse($("checkboxTarget"), 0, 0, {}); +} + +function testScriptedClicks() { + $("inputTarget").click(); + $("buttonTarget").click(); + $("xulButtonTarget").click(); +} + +function setup() { + $("inputTarget").addEventListener("click", check, false); + $("buttonTarget").addEventListener("click", check, false); + $("anchorTarget").addEventListener("click", check, false); + $("xulButtonTarget").addEventListener("click", check, false); + $("radioTarget1").addEventListener("click", check, false); + $("radioTarget2").addEventListener("click", check, false); + $("checkboxTarget").addEventListener("click", check, false); + +} + +function cleanup() { + $("inputTarget").removeEventListener("click", check, false); + $("buttonTarget").removeEventListener("click", check, false); + $("xulButtonTarget").removeEventListener("click", check, false); + $("anchorTarget").removeEventListener("click", check, false); + $("radioTarget1").removeEventListener("click", check, false); + $("radioTarget2").removeEventListener("click", check, false); + $("checkboxTarget").removeEventListener("click", check, false); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(doTest, window); + +]]></script> +</pre> +<input type="checkbox" id="checkboxTarget">Checkbox target</input> +<input id="inputTarget" type="button" value="HTML Input" accesskey="o"/> +<button id="buttonTarget">HTML Button</button> +<xul:button id="xulButtonTarget" accesskey="t">XUL Button</xul:button> +<a href="#" id="anchorTarget">Anchor</a> +<input type="radio" id="radioTarget1" name="group">Radio Target 1</input> +<input type="radio" id="radioTarget2" name="group">Radio Target 2</input> +</body> +</html> diff --git a/dom/events/test/test_bug556493.html b/dom/events/test/test_bug556493.html new file mode 100644 index 000000000..3a86ada65 --- /dev/null +++ b/dom/events/test/test_bug556493.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=556493 +--> +<head> + <title>Test for Bug 556493</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> + div { + border: 1px solid; + } + </style> +</head> +<body onload="setTimeout(runTest, 0)"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=556493">Mozilla Bug 556493</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 556493 **/ + +SimpleTest.waitForExplicitFinish(); + +var downCount = 0; +var upCount = 0; +var clickCount = 0; +function runTest() { + var d0 = document.getElementById("d0"); + var d1 = document.getElementById("d1"); + var d2 = document.getElementById("d2"); + + d0.onmousedown = function(e) { ++downCount; }; + d0.onmouseup = function(e) { ++upCount; } + d0.onclick = function(e) { ++clickCount; } + + synthesizeMouse(d1, 3, 3, { type: "mousedown"}); + synthesizeMouse(d1, 3, 3, { type: "mouseup"}); + + is(downCount, 1, "Wrong mousedown event count!"); + is(upCount, 1, "Wrong mouseup event count!"); + is(clickCount, 1, "Wrong click event count!"); + + synthesizeMouse(d1, 3, 3, { type: "mousedown"}); + synthesizeMouse(d1, 30, 3, { type: "mouseup"}); + + is(downCount, 2, "Wrong mousedown event count!"); + is(upCount, 2, "Wrong mouseup event count!"); + is(clickCount, 2, "Wrong click event count!"); + + synthesizeMouse(d1, 3, 3, { type: "mousedown"}); + synthesizeMouse(d2, 3, 3, { type: "mouseup"}); + + is(downCount, 3, "Wrong mousedown event count!"); + is(upCount, 3, "Wrong mouseup event count!"); + is(clickCount, 2, "Wrong click event count!"); + + SimpleTest.finish(); +} + +</script> +</pre> +<div id="d0"> +Test divs -- +<div id="d1">t</div><div id="d2">t</div> +-- +</div> +</body> +</html> diff --git a/dom/events/test/test_bug563329.html b/dom/events/test/test_bug563329.html new file mode 100644 index 000000000..847c5b2a8 --- /dev/null +++ b/dom/events/test/test_bug563329.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=563329 +--> +<head> + <title>Test for Bug 563329</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=563329">Mozilla Bug 563329</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for Bug 563329 **/ +/* ui.click_hold_context_menus preference */ + +var target = null; +var tests = getTests(); +var currentTest = null; + +function getTests() { + let tests = [ + { "func": function() { setTimeout(doCheckContextMenu, 100)}, "message": "Context menu should has fired"}, + { "func": function() { setTimeout(doCheckDuration, 100)}, "message": "Context menu should has fired with delay"}, + { "func": function() { setTimeout(finishTest, 100)}, "message": "" } + ]; + + let i = 0; + while (i < tests.length) + yield tests[i++]; +} + +function doTest() { + target = document.getElementById("testTarget"); + + document.documentElement.addEventListener("contextmenu", function() { + SimpleTest.ok(true, currentTest.message); + synthesizeMouse(target, 0, 0, {type: "mouseup"}); + SimpleTest.executeSoon(function() { + currentTest = tests.next(); + currentTest.func(); + }); + }, false); + + SimpleTest.executeSoon(function() { + currentTest = tests.next(); + currentTest.func(); + }); +} + +function doCheckContextMenu() { + synthesizeMouse(target, 0, 0, {type: "mousedown"}); +} + +function doCheckDuration() { + var duration = 50; + + // Change click hold delay + SpecialPowers.pushPrefEnv({"set":[["ui.click_hold_context_menus.delay", duration]]}, function() { synthesizeMouse(target, 0, 0, {type: "mousedown"}); }); +} + +function finishTest() { + synthesizeKey("VK_ESCAPE", {}, window); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set":[["ui.click_hold_context_menus", true]]}, doTest); +}); +</script> +</pre> +<span id="testTarget" style="border: 1px solid black;">testTarget</span> +</body> +</html> diff --git a/dom/events/test/test_bug574663.html b/dom/events/test/test_bug574663.html new file mode 100644 index 000000000..23c572590 --- /dev/null +++ b/dom/events/test/test_bug574663.html @@ -0,0 +1,170 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=574663 +--> +<head> + <title>Test for Bug 574663</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=574663">Mozilla Bug 574663</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for Bug 574663 **/ + +// SimpleTest's paint_listener does not work on other windows, so we inline +// a smaller version here. +function waitForPaint(win, utils, callback) { + win.document.documentElement.getBoundingClientRect(); + if (!utils.isMozAfterPaintPending) { + callback(); + return; + } + + var onpaint = function() { + if (!utils.isMozAfterPaintPending) { + win.removeEventListener("MozAfterPaint", onpaint); + callback(); + return; + } + if (utils.isTestControllingRefreshes) { + utils.advanceTimeAndRefresh(0); + } + } + win.addEventListener("MozAfterPaint", onpaint); + if (utils.isTestControllingRefreshes) { + utils.advanceTimeAndRefresh(0); + } +} + +function forceScrollAndWait(scrollbox, callback) { + let win = scrollbox.ownerDocument.defaultView; + let utils = SpecialPowers.getDOMWindowUtils(win); + + utils.advanceTimeAndRefresh(1000); + + let postApzFlush = function() { + SpecialPowers.Services.obs.removeObserver(postApzFlush, "apz-repaints-flushed", false); + waitForPaint(win, utils, callback); + } + SpecialPowers.Services.obs.addObserver(postApzFlush, "apz-repaints-flushed", false); + if (!utils.flushApzRepaints()) { + postApzFlush(); + } +} + +function sendTouchpadScrollMotion(scrollbox, direction, ctrl, momentum, callback) { + var win = scrollbox.ownerDocument.defaultView; + let event = { + deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaY: direction * 3, + lineOrPageDeltaY: direction, + ctrlKey: ctrl, + isMomentum: momentum + }; + + let kExtraEvents = 5; + + var received = 0; + var onwheel = function() { + if (++received == 1 + kExtraEvents) { + // We have captured all the outstanding wheel events. Wait for the + // animation to add itself to the refresh driver. + scrollbox.removeEventListener("wheel", onwheel); + setTimeout(function() { + forceScrollAndWait(scrollbox, callback); + }, 0); + } + }; + scrollbox.addEventListener("wheel", onwheel); + + synthesizeWheel(scrollbox, 10, 10, event, win); + // then 5 additional pixel scrolls + event.lineOrPageDeltaY = 0; + for (let i = 1; i <= kExtraEvents; ++i) { + synthesizeWheel(scrollbox, 10, 10, event, win); + } +} + +function runTest() { + var win = open('data:text/html,<!DOCTYPE html>\n' + + '<div id="scrollbox" style="height: 100px; overflow: auto;">' + + ' <div style="height: 1000px;"></div>' + + '</div>', '_blank', 'width=300,height=300'); + SimpleTest.waitForFocus(function () { + var scrollbox = win.document.getElementById("scrollbox"); + let winUtils = SpecialPowers.getDOMWindowUtils(win); + let outstandingTests = [ + [false, false], + [false, true], + [true, false], + [true, true], + ]; + + // grab the refresh driver, since we want to make sure + // async scrolls happen in deterministic time + winUtils.advanceTimeAndRefresh(1000); + + function nextTest() { + let [ctrlKey, isMomentum] = outstandingTests.shift(); + let scrollTopBefore = scrollbox.scrollTop; + let zoomFactorBefore = winUtils.fullZoom; + + let check = function() { + if (!ctrlKey) { + let postfix = isMomentum ? ", even after releasing the touchpad" : ""; + // Normal scroll: scroll + is(winUtils.fullZoom, zoomFactorBefore, "Normal scrolling shouldn't change zoom" + postfix); + isnot(scrollbox.scrollTop, scrollTopBefore, "Normal scrolling should scroll" + postfix); + } else { + if (!isMomentum) { + isnot(winUtils.fullZoom, zoomFactorBefore, "Ctrl-scrolling should zoom while the user is touching the touchpad"); + is(scrollbox.scrollTop, scrollTopBefore, "Ctrl-scrolling shouldn't scroll while the user is touching the touchpad"); + } else { + is(winUtils.fullZoom, zoomFactorBefore, "Momentum scrolling shouldn't zoom, even when pressing Ctrl"); + isnot(scrollbox.scrollTop, scrollTopBefore, "Momentum scrolling should scroll, even when pressing Ctrl"); + } + } + + if (!outstandingTests.length) { + winUtils.restoreNormalRefresh(); + win.close(); + SimpleTest.finish(); + return; + } + + // Revert the effect for the next test. + sendTouchpadScrollMotion(scrollbox, -1, ctrlKey, isMomentum, function() { + setTimeout(nextTest, 0); + }); + } + + sendTouchpadScrollMotion(scrollbox, 1, ctrlKey, isMomentum, check); + } + nextTest(); + }, win); +} + +window.onload = function() { + SpecialPowers.pushPrefEnv({ + "set":[["general.smoothScroll", false], + ["mousewheel.acceleration.start", -1], + ["mousewheel.system_scroll_override_on_root_content.enabled", false], + ["mousewheel.with_control.action", 3]]}, runTest); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> + +</body> +</html> diff --git a/dom/events/test/test_bug586961.xul b/dom/events/test/test_bug586961.xul new file mode 100644 index 000000000..43ad5fb9b --- /dev/null +++ b/dom/events/test/test_bug586961.xul @@ -0,0 +1,46 @@ +<?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"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=586961 +--> +<window title="Mozilla Bug 586961" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +<body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=586961">Mozilla Bug 586961</a> + + <p id="display"></p> +<div id="content" style="display: none"> +</div> +</body> + +<box onclick="clicked(event)"> + <label id="controllabel" control="controlbutton" accesskey="k" value="Click here" /> + <button id="controlbutton" label="Button" /> +</box> + +<script class="testbody" type="application/javascript;version=1.7"><![CDATA[ + +/** Test for Bug 586961 **/ + +function clicked(event) { + is(event.target.id, "controlbutton", "Accesskey was directed to controlled element."); + SimpleTest.finish(); +} + +function test() { + var accessKeyDetails = (navigator.platform.indexOf("Mac") >= 0) ? + { altKey : true, ctrlKey : true } : + { altKey : true, shiftKey: true }; + synthesizeKey("k", accessKeyDetails); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(test, window); + +]]></script> + +</window> diff --git a/dom/events/test/test_bug591249.xul b/dom/events/test/test_bug591249.xul new file mode 100644 index 000000000..d57c02e66 --- /dev/null +++ b/dom/events/test/test_bug591249.xul @@ -0,0 +1,75 @@ +<?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"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=591249 +--> +<window title="Mozilla Bug 591249" onload="RunTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=591249">Mozilla Bug 591249</a> + <img id="image" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" + ondragstart="event.preventDefault();"/> + <iframe id="iframe" src="chrome://mochitests/content/chrome/dom/events/test/bug591249_iframe.xul" style="height: 300px; width: 100%;"></iframe> +</body> + +<script class="testbody" type="application/javascript;version=1.8"><![CDATA[ +/** Test for Bug 591249 **/ + +SimpleTest.waitForExplicitFinish(); + +function completeTest(aBox) { + ok(window.getComputedStyle(aBox).backgroundColor == "rgb(255, 0, 0)", "The -moz-drag-over style should be removed."); + SimpleTest.finish(); +} + +function fireEvent(target, event) { + var win = target.ownerDocument.defaultView; + var utils = + win.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.dispatchDOMEventViaPresShell(target, event, true); +} + +function RunTest() { + var image = document.getElementById("image"); + var iframe = document.getElementById("iframe"); + var iBox = iframe.contentDocument.getElementById("drop-target"); + var insideBoxX = iBox.offsetWidth + 10; + var insideBoxY = iBox.offsetHeight + 10; + + var dataTransfer; + var trapDrag = function(event) { + dataTransfer = event.dataTransfer; + dataTransfer.setData("text/plain", "Hello"); + dataTransfer.dropEffect = "move"; + event.preventDefault(); + event.stopPropagation(); + } + + // need to use real mouse action to get the dataTransfer + window.addEventListener("dragstart", trapDrag, true); + synthesizeMouse(image, 2, 2, { type: "mousedown" }); + synthesizeMouse(image, 11, 11, { type: "mousemove" }); + synthesizeMouse(image, 20, 20, { type: "mousemove" }); + window.removeEventListener("dragstart", trapDrag, true); + synthesizeMouse(image, 20, 20, { type: "mouseup" }); + + var event = document.createEvent("DragEvent"); + event.initDragEvent("dragover", true, true, iBox.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, iBox, dataTransfer); + fireEvent(iBox, event); + synthesizeMouse(image, 3, 3, { type: "mousedown" }); + synthesizeMouse(image, 23, 23, { type: "mousemove" }); + synthesizeMouse(iBox, insideBoxX, insideBoxY, { type: "mousemove" }); + ok(window.getComputedStyle(iBox).backgroundColor == "rgb(255, 255, 0)", "The -moz-drag-over style should be applied."); + synthesizeMouse(iBox, insideBoxX, insideBoxY, { type: "mouseup" }); + window.setTimeout(function () { completeTest(iBox); }, 40); +} +]]></script> + +</window> diff --git a/dom/events/test/test_bug591815.html b/dom/events/test/test_bug591815.html new file mode 100644 index 000000000..f7e5c5050 --- /dev/null +++ b/dom/events/test/test_bug591815.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=591815 +--> +<head> + <title>Test for Bug 591815</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"/> +</head> +<body onload="setTimeout(runTest, 0)"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=591815">Mozilla Bug 591815</a> +<p id="display"></p> +<div id="content"> + <div id="wrapper"> + <!-- 20x20 of red --> + <img id="image" ondragstart="fail();" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC"/> + </div> +</div> +<pre id="test"> + +<script type="application/javascript"> + +/** Test for Bug 591815 **/ + +SimpleTest.waitForExplicitFinish(); + +function fail() { + ok(false, "drag started but should not have"); +} + +function runTest() { + var image = document.getElementById("image"); + var wrapper = document.getElementById("wrapper"); + var preventDefault = function(event) { + event.preventDefault(); + }; + wrapper.addEventListener('mousedown', preventDefault, false); + + synthesizeMouse(image, 3, 3, { type: "mousedown"}); + synthesizeMouse(image, 53, 53, { type: "mousemove"}); + synthesizeMouse(image, 53, 53, { type: "mouseup"}); + + wrapper.removeEventListener('mousedown', preventDefault, false); + + var relocateElementAndPreventDefault = function(event) { + document.body.appendChild(wrapper); + event.preventDefault(); + } + wrapper.addEventListener('mousedown', relocateElementAndPreventDefault, false); + + synthesizeMouse(image, 3, 3, { type: "mousedown"}); + synthesizeMouse(image, 53, 53, { type: "mousemove"}); + synthesizeMouse(image, 53, 53, { type: "mouseup"}); + + wrapper.removeEventListener('mousedown', relocateElementAndPreventDefault, false); + + ok(true, "passed the test"); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug593959.html b/dom/events/test/test_bug593959.html new file mode 100644 index 000000000..933616744 --- /dev/null +++ b/dom/events/test/test_bug593959.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=593959 +--> +<head> + <title>Test for Bug 593959</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> + body:active { + background: red; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=593959">Mozilla Bug 593959</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 593959 **/ + + function doTest() { + var utils = SpecialPowers.getDOMWindowUtils(window); + var e = document.createEvent("MouseEvent"); + e.initEvent("mousedown", false, false, window, 0, 1, 1, 1, 1, + false, false, false, false, 0, null); + utils.dispatchDOMEventViaPresShell(document.body, e, true); + + is(document.querySelector("body:active"), document.body, "body should be active!") + + var ifrwindow = document.getElementById("ifr").contentWindow; + + var utils2 = SpecialPowers.getDOMWindowUtils(ifrwindow); + + var e2 = ifrwindow.document.createEvent("MouseEvent"); + e2.initEvent("mouseup", false, false, ifrwindow, 0, 1, 1, 1, 1, + false, false, false, false, 0, null); + utils2.dispatchDOMEventViaPresShell(ifrwindow.document.body, e2, true); + + isnot(document.querySelector("body:active"), document.body, "body shouldn't be active!") + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(doTest); + + + +</script> +</pre> +<iframe id="ifr"></iframe> +</body> +</html> diff --git a/dom/events/test/test_bug602962.xul b/dom/events/test/test_bug602962.xul new file mode 100644 index 000000000..cc500d132 --- /dev/null +++ b/dom/events/test/test_bug602962.xul @@ -0,0 +1,88 @@ +<?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"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602962 +--> +<window title="Mozilla Bug 602962" onload="openWindow()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602962">Mozilla Bug 602962</a> + <p id="display"></p> +<div id="content" style="display: none"> +</div> +</body> + +<script class="testbody" type="application/javascript;version=1.8"><![CDATA[ +/** Test for Bug 602962 **/ +var scrollbox, sbo, content; +var scrollX = 0, scrollY = 0; + +var oldWidth = 0, oldHeight = 0; +var win = null; + +function openWindow() { + win = window.open("chrome://mochitests/content/chrome/dom/events/test/bug602962.xul", "_blank", "width=600,height=600"); +} + +function doTest() { + scrollbox = win.document.getElementById("page-scrollbox"); + sbo = scrollbox.boxObject; + content = win.document.getElementById("page-box"); + content.style.width = 400 + "px"; + + win.addEventListener("resize", function() { + win.removeEventListener("resize", arguments.callee, false); + + setTimeout(function(){ + sbo.scrollBy(200, 0); + resize(); + },0); + }, false); + + oldWidth = win.outerWidth; + oldHeight = win.outerHeight; + win.resizeTo(200, 400); +} + +function resize() { + scrollX = sbo.positionX; + scrollY = sbo.positionY; + + win.addEventListener("resize", function() { + content.style.width = (oldWidth + 400) + "px"; + win.removeEventListener("resize", arguments.callee, true); + + setTimeout(function() { + finish(); + }, 0); + }, true); + + win.resizeTo(oldWidth, oldHeight); +} + +function finish() { + if (win.outerWidth != oldWidth || + win.outerHeight != oldHeight) { + // We should eventually get back to the original size. + setTimeout(finish, 0); + return; + } + sbo.scrollBy(scrollX, scrollY); + + is(sbo.positionX, 200, "Scroll X should have been restored to the value before the resize"); + is(sbo.positionY, 0, "Scroll Y should have been restored to the value before the resize"); + + is(win.outerWidth, oldWidth, "Width should be resized"); + is(win.outerHeight, oldHeight, "Height should be resized"); + win.close(); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +]]></script> + +</window> diff --git a/dom/events/test/test_bug603008.html b/dom/events/test/test_bug603008.html new file mode 100644 index 000000000..786f68412 --- /dev/null +++ b/dom/events/test/test_bug603008.html @@ -0,0 +1,556 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=508906 +--> +<head> + <title>Test for Bug 603008</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=508906">Mozilla Bug 603008</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.8"> + +/** Test for Bug 306008 - Touch* Events **/ + +let tests = [], testTarget, parent; + +let touch = { + id: 0, + point: {x: 0, y: 0}, + radius: {x: 0, y: 0}, + rotation: 0, + force: 0.5, + target: null +} + +function nextTest() { + if (tests.length) + SimpleTest.executeSoon(tests.shift()); +} + +function random() { + return Math.floor(Math.random() * 100); +} + +function checkEvent(aFakeEvent) { + return function(aEvent) { + is(aFakeEvent.ctrlKey, aEvent.ctrlKey, "Correct ctrlKey"); + is(aFakeEvent.altKey, aEvent.altKey, "Correct altKey"); + is(aFakeEvent.shiftKey, aEvent.shiftKey, "Correct shiftKey"); + is(aFakeEvent.metaKey, aEvent.metaKey, "Correct metaKey"); + checkTouches(aFakeEvent.touches, aEvent.touches); + checkTouches(aFakeEvent.targetTouches, aEvent.targetTouches); + checkTouches(aFakeEvent.changedTouches, aEvent.changedTouches); + } +} + +function checkTouches(aTouches1, aTouches2) { + is(aTouches1.length, aTouches2.length, "Correct touches length"); + for (var i = 0; i < aTouches1.length; i++) { + checkTouch(aTouches1[i], aTouches2[i]); + } +} + +function checkTouch(aFakeTouch, aTouch) { + is(aFakeTouch.identifier, aTouch.identifier, "Touch has correct identifier"); + is(aFakeTouch.target, aTouch.target, "Touch has correct target"); + is(aFakeTouch.page.x, aTouch.pageX, "Touch has correct pageX"); + is(aFakeTouch.page.y, aTouch.pageY, "Touch has correct pageY"); + is(aFakeTouch.page.x + Math.round(window.mozInnerScreenX), aTouch.screenX, "Touch has correct screenX"); + is(aFakeTouch.page.y + Math.round(window.mozInnerScreenY), aTouch.screenY, "Touch has correct screenY"); + is(aFakeTouch.page.x, aTouch.clientX, "Touch has correct clientX"); + is(aFakeTouch.page.y, aTouch.clientY, "Touch has correct clientY"); + is(aFakeTouch.radius.x, aTouch.radiusX, "Touch has correct radiusX"); + is(aFakeTouch.radius.y, aTouch.radiusY, "Touch has correct radiusY"); + is(aFakeTouch.rotationAngle, aTouch.rotationAngle, "Touch has correct rotationAngle"); + is(aFakeTouch.force, aTouch.force, "Touch has correct force"); +} + +function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) { + var ids = [], xs=[], ys=[], rxs = [], rys = [], + rotations = [], forces = []; + + for (var touchType of ["touches", "changedTouches", "targetTouches"]) { + for (var i = 0; i < aEvent[touchType].length; i++) { + if (ids.indexOf(aEvent[touchType][i].identifier) == -1) { + ids.push(aEvent[touchType][i].identifier); + xs.push(aEvent[touchType][i].page.x); + ys.push(aEvent[touchType][i].page.y); + rxs.push(aEvent[touchType][i].radius.x); + rys.push(aEvent[touchType][i].radius.y); + rotations.push(aEvent[touchType][i].rotationAngle); + forces.push(aEvent[touchType][i].force); + } + } + } + return windowUtils.sendTouchEvent(aType, + ids, xs, ys, rxs, rys, + rotations, forces, + ids.length, aModifiers, 0); +} + +function touchEvent(aOptions) { + if (!aOptions) { + aOptions = {}; + } + this.ctrlKey = aOptions.ctrlKey || false; + this.altKey = aOptions.altKey || false; + this.shiftKey = aOptions.shiftKey || false; + this.metaKey = aOptions.metaKey || false; + this.touches = aOptions.touches || []; + this.targetTouches = aOptions.targetTouches || []; + this.changedTouches = aOptions.changedTouches || []; +} + +function testtouch(aOptions) { + if (!aOptions) + aOptions = {}; + this.identifier = aOptions.identifier || 0; + this.target = aOptions.target || 0; + this.page = aOptions.page || {x: 0, y: 0}; + this.radius = aOptions.radius || {x: 0, y: 0}; + this.rotationAngle = aOptions.rotationAngle || 0; + this.force = aOptions.force || 1; +} + +function testSingleTouch(name) { + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target = document.getElementById("testTarget"); + let target2 = document.getElementById("testTarget2"); + let bcr = target.getBoundingClientRect(); + let bcr2 = target2.getBoundingClientRect(); + + let touch1 = new testtouch({ + page: {x: Math.round(bcr.left + bcr.width/2), + y: Math.round(bcr.top + bcr.height/2)}, + target: target + }); + let event = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + + // test touchstart event fires correctly + var checkFunction = checkEvent(event); + window.addEventListener("touchstart", checkFunction, false); + sendTouchEvent(cwu, "touchstart", event, 0); + window.removeEventListener("touchstart", checkFunction, false); + + // test touchmove event fires correctly + event.touches[0].page.x -= 1; + event.targetTouches[0].page.x -= 1; + event.changedTouches[0].page.x -= 1; + checkFunction = checkEvent(event); + window.addEventListener("touchmove", checkFunction, false); + sendTouchEvent(cwu, "touchmove", event, 0); + window.removeEventListener("touchmove", checkFunction, false); + + // test touchend event fires correctly + event.touches = []; + event.targetTouches = []; + checkFunction = checkEvent(event); + window.addEventListener("touchend", checkFunction, false); + sendTouchEvent(cwu, "touchend", event, 0); + window.removeEventListener("touchend", checkFunction, false); + + nextTest(); +} + +function testSingleTouch2(name) { + // firing a touchstart that includes only one touch will evict any touches in the queue with touchend messages + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target = document.getElementById("testTarget"); + let target2 = document.getElementById("testTarget2"); + let bcr = target.getBoundingClientRect(); + let bcr2 = target2.getBoundingClientRect(); + + let touch1 = new testtouch({ + identifier: 0, + page: {x: Math.round(bcr.left + bcr.width/2), + y: Math.round(bcr.top + bcr.height/2)}, + target: target + }); + let event1 = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + let touch2 = new testtouch({ + identifier: 1, + page: {x: Math.round(bcr2.left + bcr2.width/2), + y: Math.round(bcr2.top + bcr2.height/2)}, + target: target2 + }); + let event2 = new touchEvent({ + touches: [touch2], + targetTouches: [touch2], + changedTouches: [touch2] + }); + + // test touchstart event fires correctly + var checkFunction1 = checkEvent(event1); + window.addEventListener("touchstart", checkFunction1, false); + sendTouchEvent(cwu, "touchstart", event1, 0); + window.removeEventListener("touchstart", checkFunction1, false); + + event1.touches = []; + event1.targetTouches = []; + checkFunction1 = checkEvent(event1); + var checkFunction2 = checkEvent(event2); + + window.addEventListener("touchend", checkFunction1, false); + window.addEventListener("touchstart", checkFunction2, false); + sendTouchEvent(cwu, "touchstart", event2, 0); + window.removeEventListener("touchend", checkFunction1, false); + window.removeEventListener("touchstart", checkFunction2, false); + + sendTouchEvent(cwu, "touchstart", event1, 0); + + nextTest(); +} + + +function testMultiTouch(name) { + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target1 = document.getElementById("testTarget"); + let target2 = document.getElementById("testTarget2"); + let bcr = target1.getBoundingClientRect(); + let bcr2 = target2.getBoundingClientRect(); + + let touch1 = new testtouch({ + identifier: 0, + page: {x: Math.round(bcr.left + bcr.width/2), + y: Math.round(bcr.top + bcr.height/2)}, + target: target1 + }); + let touch2 = new testtouch({ + identifier: 1, + page: {x: Math.round(bcr2.left + bcr2.width/2), + y: Math.round(bcr2.top + bcr2.height/2)}, + target: target2 + }); + let event = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + + // test touchstart event fires correctly + var checkFunction = checkEvent(event); + window.addEventListener("touchstart", checkFunction, false); + sendTouchEvent(cwu, "touchstart", event, 0); + window.removeEventListener("touchstart", checkFunction, false); + + event.touches.push(touch2); + event.targetTouches = [touch2]; + event.changedTouches = [touch2]; + window.addEventListener("touchstart", checkFunction, false); + sendTouchEvent(cwu, "touchstart", event, 0); + window.removeEventListener("touchstart", checkFunction, false); + + // test moving one touch point + event.touches[0].page.x -= 1; + event.targetTouches = [event.touches[0]]; + event.changedTouches = [event.touches[0]]; + window.addEventListener("touchmove", checkFunction, false); + sendTouchEvent(cwu, "touchmove", event, 0); + window.removeEventListener("touchmove", checkFunction, false); + + // test moving both touch points -- two touchmove events should fire, one on each target + event.touches[0].page.x -= 1; + event.touches[1].page.x -= 1; + event.targetTouches = event.touches; + event.changedTouches = event.touches; + var touchMoveEvents = 0; + var checkFunction2 = function(aEvent) { + is(event.ctrlKey, aEvent.ctrlKey, "Correct ctrlKey"); + is(event.altKey, aEvent.altKey, "Correct altKey"); + is(event.shiftKey, aEvent.shiftKey, "Correct shiftKey"); + is(event.metaKey, aEvent.metaKey, "Correct metaKey"); + checkTouches(event.touches, aEvent.touches); + checkTouches(event.changedTouches, aEvent.changedTouches); + if (aEvent.targetTouches[0].target == target1) { + checkTouches([event.touches[0]], aEvent.targetTouches); + } else if (aEvent.targetTouches[0].target == target2) { + checkTouches([event.touches[1]], aEvent.targetTouches); + } else + ok(false, "Event target is incorrect: " + event.targetTouches[0].target.nodeName + "#" + event.targetTouches[0].target.id); + touchMoveEvents++; + }; + window.addEventListener("touchmove", checkFunction2, false); + sendTouchEvent(cwu, "touchmove", event, 0); + ok(touchMoveEvents, 2, "Correct number of touchmove events fired"); + window.removeEventListener("touchmove", checkFunction2, false); + + // test removing just one finger + var expected = new touchEvent({ + touches: [touch2], + targetTouches: [], + changedTouches: [touch1] + }); + checkFunction = checkEvent(expected); + + event.touches = []; + event.targetTouches = []; + event.changedTouches = [touch1]; + + // test removing the other finger + window.addEventListener("touchend", checkFunction, false); + sendTouchEvent(cwu, "touchend", event, 0); + window.removeEventListener("touchend", checkFunction, false); + + event.touches = []; + event.targetTouches = []; + event.changedTouches = [touch2]; + checkFunction = checkEvent(event); + window.addEventListener("touchend", checkFunction, false); + sendTouchEvent(cwu, "touchend", event, 0); + window.removeEventListener("touchend", checkFunction, false); + + nextTest(); +} + +function testTouchChanged() { + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target1 = document.getElementById("testTarget"); + let bcr = target1.getBoundingClientRect(); + + let touch1 = new testtouch({ + identifier: 0, + page: {x: Math.round(bcr.left + bcr.width/2), + y: Math.round(bcr.top + bcr.height/2)}, + target: target1 + }); + let event = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + + var checkFunction = checkEvent(event); + sendTouchEvent(cwu, "touchstart", event, 0); + + var moveEvents = 0; + function onMove(aEvent) { + moveEvents++; + } + + window.addEventListener("touchmove", onMove, false); + + // the first touchmove should always fire an event + sendTouchEvent(cwu, "touchmove", event, 0); + + // changing nothing should not fire a touchmove event + sendTouchEvent(cwu, "touchmove", event, 0); + + // test moving x + event.touches[0].page.x -= 1; + sendTouchEvent(cwu, "touchmove", event, 0); + + // test moving y + event.touches[0].page.y -= 1; + sendTouchEvent(cwu, "touchmove", event, 0); + + // test changing y radius + event.touches[0].radius.y += 1; + sendTouchEvent(cwu, "touchmove", event, 0); + + // test changing x radius + event.touches[0].radius.x += 1; + sendTouchEvent(cwu, "touchmove", event, 0); + + // test changing rotationAngle + event.touches[0].rotationAngle += 1; + sendTouchEvent(cwu, "touchmove", event, 0); + + // test changing force + event.touches[0].force += 1; + sendTouchEvent(cwu, "touchmove", event, 0); + + // changing nothing again + sendTouchEvent(cwu, "touchmove", event, 0); + + is(moveEvents, 7, "Six move events fired"); + + window.removeEventListener("touchmove", onMove, false); + sendTouchEvent(cwu, "touchend", event, 0); + nextTest(); +} + +function testPreventDefault() { + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target = document.getElementById("testTarget"); + let target2 = document.getElementById("testTarget2"); + let bcr = target.getBoundingClientRect(); + let bcr2 = target2.getBoundingClientRect(); + + let touch1 = new testtouch({ + page: {x: bcr.left + bcr.width/2, + y: bcr.top + bcr.height/2}, + target: target + }); + let event = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + + let preventFunction = function(aEvent) { + aEvent.preventDefault(); + } + + let tests = [ + [{ name: "touchstart", prevent: false }, + { name: "touchmove", prevent: false }, + { name: "touchmove", prevent: false }, + { name: "touchend", prevent: false }], + [{ name: "touchstart", prevent: true, doPrevent: true }, + { name: "touchmove", prevent: false }, + { name: "touchmove", prevent: false }, + { name: "touchend", prevent: false }], + [{ name: "touchstart", prevent: false }, + { name: "touchmove", prevent: true, doPrevent: true }, + { name: "touchmove", prevent: false }, + { name: "touchend", prevent: false }], + [{ name: "touchstart", prevent: false }, + { name: "touchmove", prevent: false }, + { name: "touchmove", prevent: false, doPrevent: true }, + { name: "touchend", prevent: false }], + [{ name: "touchstart", prevent: false }, + { name: "touchmove", prevent: false }, + { name: "touchmove", prevent: false }, + { name: "touchend", prevent: true, doPrevent: true }] + ]; + + var dotest = function(aTest) { + if (aTest.doPrevent) { + target.addEventListener(aTest.name, preventFunction, false); + } + + if (aTest.name == "touchmove") { + touch1.page.x++; + event.touches[0] = touch1; + } + + is(sendTouchEvent(cwu, aTest.name, event, 0), aTest.prevent, "Got correct status"); + + if (aTest.doPrevent) + target.removeEventListener(aTest.name, preventFunction, false); + } + + for (var i = 0; i < tests.length; i++) { + for (var j = 0; j < tests[i].length; j++) { + dotest(tests[i][j]); + } + } + + nextTest(); +} + +function testRemovingElement() { + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target = document.getElementById("testTarget"); + let bcr = document.getElementById("testTarget").getBoundingClientRect(); + + let touch1 = new testtouch({ + page: {x: bcr.left + bcr.width/2, + y: bcr.top + bcr.height/2}, + }); + let e = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + + var touchEvents = 0; + var removeTarget = function(aEvent) { + aEvent.target.parentNode.removeChild(aEvent.target); + }; + + var checkTarget = function(aEvent) { + is(aEvent.target, target, "Event has correct target"); + touchEvents++; + }; + + target.addEventListener("touchstart", removeTarget, false); + target.addEventListener("touchmove", checkTarget, false); + target.addEventListener("touchend", checkTarget, false); + + sendTouchEvent(cwu, "touchstart", e, 0); + + e.touches[0].page.x++; + sendTouchEvent(cwu, "touchmove", e, 0); + sendTouchEvent(cwu, "touchend", e, 0); + + target.removeEventListener("touchstart", removeTarget, false); + target.removeEventListener("touchmove", checkTarget, false); + target.removeEventListener("touchend", checkTarget, false); + + is(touchEvents, 2, "Check target was called twice"); + + nextTest(); +} + +function testNAC() { + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target = document.getElementById("testTarget3"); + let bcr = target.getBoundingClientRect(); + + let touch1 = new testtouch({ + page: {x: Math.round(bcr.left + bcr.width/2), + y: Math.round(bcr.top + bcr.height/2)}, + target: target + }); + let event = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + + // test touchstart event fires correctly + var checkFunction = checkEvent(event); + window.addEventListener("touchstart", checkFunction, false); + sendTouchEvent(cwu, "touchstart", event, 0); + window.removeEventListener("touchstart", checkFunction, false); + + sendTouchEvent(cwu, "touchend", event, 0); + + nextTest(); +} + +function doTest() { + tests.push(testSingleTouch); + tests.push(testSingleTouch2); + tests.push(testMultiTouch); + tests.push(testPreventDefault); + tests.push(testTouchChanged); + tests.push(testRemovingElement); + tests.push(testNAC); + + tests.push(function() { + SimpleTest.finish(); + }); + + nextTest(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> +</pre> +<div id="parent"> + <span id="testTarget" style="padding: 5px; border: 1px solid black;">testTarget</span> + <span id="testTarget2" style="padding: 5px; border: 1px solid blue;">testTarget</span> + <input type="text" id="testTarget3"> +</div> +</body> +</html> diff --git a/dom/events/test/test_bug605242.html b/dom/events/test/test_bug605242.html new file mode 100644 index 000000000..f8f9354d5 --- /dev/null +++ b/dom/events/test/test_bug605242.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=605242 +--> +<head> + <title>Test for Bug 605242</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="setTimeout('runTest()', 0)"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=605242">Mozilla Bug 605242</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 605242 **/ + +SimpleTest.waitForExplicitFinish(); + +var utils = SpecialPowers.getDOMWindowUtils(window); +function sendMouseDown(el) { + var rect = el.getBoundingClientRect(); + utils.sendMouseEvent('mousedown', rect.left + 5, rect.top + 5, 0, 1, 0); +} + +function sendMouseUp(el) { + var rect = el.getBoundingClientRect(); + utils.sendMouseEvent('mouseup', rect.left + 5, rect.top + 5, 0, 1, 0); +} + +function runTest() { + var b = document.getElementById("testbutton"); + sendMouseDown(b); + var l = document.querySelectorAll(":active"); + + var contains = false; + for (var i = 0; i < l.length; ++i) { + if (l[i] == b) { + contains = true; + } + } + + ok(contains, "Wrong active content! \n"); + sendMouseUp(b); + SimpleTest.finish(); +} + +</script> +</pre> +<button id="testbutton">A button</button> +<pre id="log"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug607464.html b/dom/events/test/test_bug607464.html new file mode 100644 index 000000000..ac8076bfd --- /dev/null +++ b/dom/events/test/test_bug607464.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=607464 +--> +<head> + <title>Test for Bug 607464</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=607464">Mozilla Bug 607464</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** + * Test for Bug 607464: + * Pixel scrolling shouldn't scroll smoothly, even if general.smoothScroll is on. + **/ + +function scrollDown150PxWithPixelScrolling(scrollbox) { + var win = scrollbox.ownerDocument.defaultView; + let event = { + deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaY: 30.0, + lineOrPageDeltaY: 1 + }; + // A pixel scroll with lineOrPageDeltaY. + synthesizeWheel(scrollbox, 10, 10, event, win); + // then 4 pixel scrolls without lineOrPageDeltaY. + event.lineOrPageDeltaY = 0; + for (let i = 0; i < 4; ++i) { + synthesizeWheel(scrollbox, 10, 10, event, win); + } + + // Note: the line scroll shouldn't have any effect because it has + // hasPixels = true set on it. We send it to emulate normal + // behavior. +} + +function runTest() { + var win = open('data:text/html,<!DOCTYPE html>\n' + + '<div id="scrollbox" style="height: 100px; overflow: auto;">' + + ' <div style="height: 1000px;"></div>' + + '</div>', '_blank', 'width=300,height=300'); + SimpleTest.waitForFocus(function () { + var scrollbox = win.document.getElementById("scrollbox"); + let scrollTopBefore = scrollbox.scrollTop; + + win.addEventListener("scroll", function(e) { + is(scrollbox.scrollTop % 30, 0, + "Pixel scrolling should happen instantly, not smoothly. The " + + "scroll position " + scrollbox.scrollTop + " in this sequence of wheel " + + "events should be a multiple of 30."); + if (scrollbox.scrollTop == 150) { + win.close(); + SimpleTest.finish(); + } + }, true); + + flushApzRepaints(function() { + scrollDown150PxWithPixelScrolling(scrollbox); + }, win); + }, win); +} + +window.onload = function() { + SpecialPowers.pushPrefEnv({ + "set":[["general.smoothScroll", true], + ["mousewheel.acceleration.start", -1], + ["mousewheel.system_scroll_override_on_root_content.enabled", false]]}, runTest); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.testInChaosMode(); + +</script> +</pre> + +</body> +</html> diff --git a/dom/events/test/test_bug613634.html b/dom/events/test/test_bug613634.html new file mode 100644 index 000000000..540df5c90 --- /dev/null +++ b/dom/events/test/test_bug613634.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=613634 +--> +<head> + <title>Test for Bug 613634</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=613634">Mozilla Bug 613634</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 613634 **/ + +var eventCount = 0; +function l(e) { + if (e.eventPhase != Event.CAPTURING_PHASE) { + ++eventCount; + } else { + ok(false, "Listener shouldn't be called!"); + } +} + +var d1 = document.createElement("div"); +var d2 = document.createElement("div"); + +d1.appendChild(d2); + +var x = new XMLHttpRequest(); + +try { +d1.addEventListener("foo", l); +document.addEventListener("foo", l); +window.addEventListener("foo", l); +x.addEventListener("foo", l); +} catch(ex) { + ok(false, "Shouldn't throw " + ex); +} + +var ev = document.createEvent("Event"); +ev.initEvent("foo", true, true); +d2.dispatchEvent(ev); +is(eventCount, 1, "Event listener should have been called!"); + +ev = document.createEvent("Event"); +ev.initEvent("foo", false, false); +d2.dispatchEvent(ev); +is(eventCount, 1, "Event listener shouldn't have been called!"); + +d1.removeEventListener("foo", l); +ev = document.createEvent("Event"); +ev.initEvent("foo", true, true); +d2.dispatchEvent(ev); +is(eventCount, 1, "Event listener shouldn't have been called!"); + + +ev = document.createEvent("Event"); +ev.initEvent("foo", true, true); +document.body.dispatchEvent(ev); +is(eventCount, 3, "Event listener should have been called on document and window!"); + +document.removeEventListener("foo", l); +window.removeEventListener("foo", l, false); +ev = document.createEvent("Event"); +ev.initEvent("foo", true, true); +document.body.dispatchEvent(ev); +is(eventCount, 3, "Event listener shouldn't have been called on document and window!"); + +ev = document.createEvent("Event"); +ev.initEvent("foo", true, true); +x.dispatchEvent(ev); +is(eventCount, 4, "Event listener should have been called on XMLHttpRequest!"); + +x.removeEventListener("foo", l); +ev = document.createEvent("Event"); +ev.initEvent("foo", true, true); +x.dispatchEvent(ev); +is(eventCount, 4, "Event listener shouldn't have been called on XMLHttpRequest!"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug615597.html b/dom/events/test/test_bug615597.html new file mode 100644 index 000000000..4f0eaeecf --- /dev/null +++ b/dom/events/test/test_bug615597.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=615597 +--> +<head> + <title>Test for Bug 615597</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=615597">Mozilla Bug 615597</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 615597 **/ + +window.addEventListener("deviceorientation", function(event) { + is(event.alpha, 1.5); + is(event.beta, 2.25); + is(event.gamma, 3.667); + is(event.absolute, true); +}, true); + +var event = DeviceOrientationEvent; +ok(!!event, "Should have seen DeviceOrientationEvent!"); + +event = document.createEvent("DeviceOrientationEvent"); +event.initDeviceOrientationEvent('deviceorientation', true, true, 1.5, 2.25, 3.667, true); +window.dispatchEvent(event); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug617528.xul b/dom/events/test/test_bug617528.xul new file mode 100644 index 000000000..83038b36b --- /dev/null +++ b/dom/events/test/test_bug617528.xul @@ -0,0 +1,95 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=617528 +--> +<window title="Mozilla Bug 617528" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=617528" + target="_blank">Mozilla Bug 617528</a> + </body> + + <script type="application/javascript"><![CDATA[ + var _window; + var browser; + + function start() { + _window = window.open("window_bug617528.xul", "_new", "chrome"); + _window.addEventListener("load", onLoad, false); + } + + function onLoad() { + _window.removeEventListener("load", onLoad, false); + + browser = _window.document.getElementById("browser"); + browser.addEventListener("pageshow", onPageShow, false); + + var uri='data:text/html,\ +<html>\ + <body>\ + <div oncontextmenu="event.preventDefault()">\ + <input id="node" type="text" value="Click here"></input>\ + </div>\ + </body>\ +</html>'; + browser.loadURI(uri); + } + + function onPageShow() { + browser.removeEventListener("pageshow", onPageShow, true); + SimpleTest.executeSoon(doTest); + } + + function onContextMenu1(event) { + is(event.defaultPrevented, true, + "expected event.defaultPrevented to be true (1)"); + is(event.target.localName, "input", + "expected event.target.localName to be 'input' (1)"); + is(event.originalTarget.localName, "div", + "expected event.originalTarget.localName to be 'div' (1)"); + } + + function onContextMenu2(event) { + is(event.defaultPrevented, false, + "expected event.defaultPrevented to be false (2)"); + is(event.target.localName, "input", + "expected event.target.localName to be 'input' (2)"); + is(event.originalTarget.localName, "div", + "expected event.originalTarget.localName to be 'div' (2)"); + } + + function doTest() { + var win = browser.contentWindow; + win.focus(); + var node = win.document.getElementById("node"); + var rect = node.getBoundingClientRect(); + var left = rect.left + rect.width / 2; + var top = rect.top + rect.height / 2; + + var wu = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + + browser.addEventListener("contextmenu", onContextMenu1, false); + wu.sendMouseEvent("contextmenu", left, top, 2, 1, 0); + browser.removeEventListener("contextmenu", onContextMenu1, false); + + browser.addEventListener("contextmenu", onContextMenu2, false); + var shiftMask = Components.interfaces.nsIDOMEvent.SHIFT_MASK; + wu.sendMouseEvent("contextmenu", left, top, 2, 1, shiftMask); + browser.removeEventListener("contextmenu", onContextMenu2, false); + + _window.close(); + SimpleTest.finish(); + } + + addLoadEvent(start); + SimpleTest.waitForExplicitFinish(); + ]]></script> +</window> diff --git a/dom/events/test/test_bug624127.html b/dom/events/test/test_bug624127.html new file mode 100644 index 000000000..269e1f62d --- /dev/null +++ b/dom/events/test/test_bug624127.html @@ -0,0 +1,35 @@ +<html> +<head> + <title>Test for Bug 624127</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"/> +</head> +<body onload="setTimeout('runTest()', 0)"> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function runTest() { + synthesizeMouse($("text"), 2, 2, { type: "mousedown" }); + synthesizeMouse(frames[0].document.body, 2, 2, { type: "mouseup" }, frames[0]); + synthesizeMouse($("text2"), 50, 8, { type: "mousemove" }); + + is(window.getSelection().toString(), "", "no selection made"); + SimpleTest.finish(); +} + +</script> +</pre> + +<p id="text">Normal text</p> +<iframe src="data:text/plain,text in iframe"></iframe> +<p id="text2">Normal text</p> + +</body> +</html> diff --git a/dom/events/test/test_bug635465.html b/dom/events/test/test_bug635465.html new file mode 100644 index 000000000..7bd0db2be --- /dev/null +++ b/dom/events/test/test_bug635465.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=635465 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 635465</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 type="text/css"> + #item { + position: relative; + } + .s-menu-section-submenu { + position: absolute; + display: none; + } + .open .s-menu-section-submenu { + display: block; + } +</style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=635465">Mozilla Bug 635465</a> +<div id="display"> + <div class="item" id="item" + onmouseover="showSubmenu(event)" onmouseout="hideSubmenu(event)"> + <a href="#" id="firsthover">Hover me</a> + <div class="s-menu-section-submenu" id="menu"> + <a href="#" id="secondhover">Now hover me</a> + </div> + </div> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.8"> + +/** Test for Bug 635465 **/ +function showSubmenu(event) { + var item = document.getElementById('item'); + + var width = item.offsetWidth; // IT WORKS IF YOU REMOVE THIS LINE + + item.className='open'; +} + +function hideSubmenu(event) { + document.getElementById('item').className=''; +} + +SimpleTest.waitForExplicitFinish(); + +function executeTests() { + // First flush out layout of firsthover + ok($("firsthover").getBoundingClientRect().height > 0, true, + "Should have a nonzero height before hover"); + + // Now trigger a mouseover on firsthover + synthesizeMouseAtCenter($("firsthover"), { type: "mousemove" }); + + ok($("secondhover").getBoundingClientRect().height > 0, true, + "Should have a nonzero height for submenu after hover"); + + // Now determine where secondhover is hanging out + var rect = $("secondhover").getBoundingClientRect(); + synthesizeMouseAtCenter($("secondhover"), { type: "mousemove" }); + + // And another mouseover one pixel to the right of where the center used to be + synthesizeMouseAtPoint(rect.left + rect.width/2 + 1, + rect.top + rect.height/2, + { type: "mousemove" }); + + ok($("secondhover").getBoundingClientRect().height > 0, true, + "Should have a nonzero height for submenu after second hover"); + + // And check computed display of the menu + is(getComputedStyle($("menu"), "").display, "block", "Should have block display"); + + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(executeTests); +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug641477.html b/dom/events/test/test_bug641477.html new file mode 100644 index 000000000..2fdc43a5c --- /dev/null +++ b/dom/events/test/test_bug641477.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=641477 +--> +<head> + <title>Test for Bug 641477</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641477">Mozilla Bug 641477</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 641477 **/ + +var didThrow = false; + +var e = document.createEvent("Event"); +try { + is(e.type, "", "Event type should be empty string before initialization"); + document.dispatchEvent(e); +} catch(ex) { + didThrow = (ex.name == "InvalidStateError" && ex.code == DOMException.INVALID_STATE_ERR); +} + +ok(didThrow, "Should have thrown InvalidStateError!"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug648573.html b/dom/events/test/test_bug648573.html new file mode 100644 index 000000000..0a4a9e7e3 --- /dev/null +++ b/dom/events/test/test_bug648573.html @@ -0,0 +1,109 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE html> +<html> + <!-- + https://bugzilla.mozilla.org/show_bug.cgi?id=648573 + --> + <head> + <title>Test for Bug 648573</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + <body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=648573">Mozilla Bug 648573</a> + <p id="display"></p> + <div id="content" style="display: none"> + + </div> + <pre id="test"> + <script type="application/javascript"> + + /** Test for Bug 648573 **/ + SimpleTest.waitForExplicitFinish(); + var utils = SpecialPowers.getDOMWindowUtils(window); + + ok("createTouch" in document, "Should have createTouch function"); + ok("createTouchList" in document, "Should have createTouchList function"); + ok(document.createEvent("touchevent"), "Should be able to create TouchEvent objects"); + + var t1 = document.createTouch(window, document, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + is(t1.target, document, "Wrong target"); + is(t1.identifier, 1, "Wrong identifier"); + is(t1.pageX, 2, "Wrong pageX"); + is(t1.pageY, 3, "Wrong pageY"); + is(t1.screenX, 4, "Wrong screenX"); + is(t1.screenY, 5, "Wrong screenY"); + is(t1.clientX, 6, "Wrong clientX"); + is(t1.clientY, 7, "Wrong clientY"); + is(t1.radiusX, 8, "Wrong radiusX"); + is(t1.radiusY, 9, "Wrong radiusY"); + is(t1.rotationAngle, 10, "Wrong rotationAngle"); + is(t1.force, 11, "Wrong force"); + + var t2 = document.createTouch(); + + var l1 = document.createTouchList(t1); + is(l1.length, 1, "Wrong length"); + is(l1.item(0), t1, "Wront item (1)"); + is(l1[0], t1, "Wront item (2)"); + + var l2 = document.createTouchList([t1, t2]); + is(l2.length, 2, "Wrong length"); + is(l2.item(0), t1, "Wront item (3)"); + is(l2.item(1), t2, "Wront item (4)"); + is(l2[0], t1, "Wront item (5)"); + is(l2[1], t2, "Wront item (6)"); + + var l3 = document.createTouchList(); + + var e = document.createEvent("touchevent"); + e.initTouchEvent("touchmove", true, true, window, 0, true, true, true, true, + l1, l2, l3); + is(e.touches, l1, "Wrong list (1)"); + is(e.targetTouches, l2, "Wrong list (2)"); + is(e.changedTouches, l3, "Wrong list (3)"); + ok(e.altKey, "Alt should be true"); + ok(e.metaKey, "Meta should be true"); + ok(e.ctrlKey, "Ctrl should be true"); + ok(e.shiftKey, "Shift should be true"); + + + var events = + ["touchstart", + "touchend", + "touchmove", + "touchcancel"]; + + function runEventTest(type) { + var e = document.createEvent("touchevent"); + e.initTouchEvent(type, true, true, window, 0, true, true, true, true, + l1, l2, l3); + var t = document.createElement("div"); + // Testing target.onFoo; + var didCall = false; + t["on" + type] = function (evt) { + is(evt, e, "Wrong event"); + evt.target.didCall = true; + } + t.dispatchEvent(e); + ok(t.didCall, "Should have called the listener(1)"); + + // Testing <element onFoo=""> + t = document.createElement("div"); + t.setAttribute("on" + type, "this.didCall = true;"); + t.dispatchEvent(e); + ok(t.didCall, "Should have called the listener(2)"); + } + + for (var i = 0; i < events.length; ++i) { + runEventTest(events[i]); + } + + SimpleTest.finish(); + </script> + </pre> + </body> +</html> diff --git a/dom/events/test/test_bug650493.html b/dom/events/test/test_bug650493.html new file mode 100644 index 000000000..60a32d56e --- /dev/null +++ b/dom/events/test/test_bug650493.html @@ -0,0 +1,215 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650493 +--> +<head> + <title>Test for Bug 650493</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650493">Mozilla Bug 650493</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function getNodes() { + var walker = document.createTreeWalker($('content'), NodeFilter.SHOW_ALL, null); + var nodes = []; + do { + nodes.push(walker.currentNode); + } while(walker.nextNode()); + + return nodes; +} + +function check() { + var current = getNodes(); + is(nodes.length, current.length, "length after " + testName); + nodes.forEach(function(val, index) { + ok(current.indexOf(val) > -1, "nodes[" + index + "] (" + val + ") shouldn't exist after " + testName); + }); +} + +var nodes = getNodes(); +var testName = "empty"; +var mutateCount = 0; + +check(); + +// Set up listeners +root = $('content'); +root.addEventListener("DOMNodeInserted", function(e) { + mutateCount++; + is(e.isTrusted, true, "untrusted mutation event"); + var w = document.createTreeWalker(e.target, NodeFilter.SHOW_ALL, null); + do { + is(nodes.indexOf(w.currentNode), -1, "already have inserted node (" + w.currentNode + ") when " + testName); + nodes.push(w.currentNode); + } while(w.nextNode()); +}, false); +root.addEventListener("DOMNodeRemoved", function(e) { + mutateCount++; + is(e.isTrusted, true, "untrusted mutation event"); + var w = document.createTreeWalker(e.target, NodeFilter.SHOW_ALL, null); + do { + var index = nodes.indexOf(w.currentNode); + ok(index != -1, "missing removed node (" + w.currentNode + ") when " + testName); + nodes.splice(index, 1); + } while(w.nextNode()); +}, false); + +testName = "text-only innerHTML"; +root.innerHTML = "hello world"; +check(); + +testName = "innerHTML with <b>"; +root.innerHTML = "<b>bold</b> world"; +check(); + +testName = "complex innerHTML"; +root.innerHTML = "<b>b<span>old</span></b> <strong>world"; +check(); + +testName = "replacing using .textContent"; +root.textContent = "i'm just a plain text minding my own business"; +check(); + +testName = "clearing using .textContent"; +root.textContent = ""; +check(); + +testName = "inserting using .textContent"; +root.textContent = "i'm new text!!"; +check(); + +testName = "inserting using .textContent"; +root.textContent = "i'm new text!!"; +check(); + +testName = "preparing to normalize"; +root.innerHTML = "<u><b>foo</b></u> "; +var u = root.firstChild; +is(u.nodeName, "U", "got the right node"); +var b = u.firstChild; +is(b.nodeName, "B", "got the right node"); +b.insertBefore(document.createTextNode(""), b.firstChild); +b.insertBefore(document.createTextNode(""), b.firstChild); +b.appendChild(document.createTextNode("")); +b.appendChild(document.createTextNode("hello")); +b.appendChild(document.createTextNode("world")); +u.appendChild(document.createTextNode("foo")); +u.appendChild(document.createTextNode("")); +u.appendChild(document.createTextNode("bar")); +check(); + +testName = "normalizing"; +root.normalize(); +check(); + +testName = "self replace firstChild"; +mutateCount = 0; +root.replaceChild(root.firstChild, root.firstChild); +check(); +is(mutateCount, 2, "should remove and reinsert " + testName); + +testName = "self replace second child"; +mutateCount = 0; +root.replaceChild(root.firstChild.nextSibling, root.firstChild.nextSibling); +check(); +is(mutateCount, 2, "should remove and reinsert " + testName); + +testName = "self replace lastChild"; +mutateCount = 0; +root.replaceChild(root.lastChild, root.lastChild); +check(); +is(mutateCount, 2, "should remove and reinsert " + testName); + +testName = "self insertBefore firstChild"; +mutateCount = 0; +root.insertBefore(root.firstChild, root.firstChild); +check(); +is(mutateCount, 2, "should remove and reinsert " + testName); + +testName = "self insertBefore second child"; +mutateCount = 0; +root.insertBefore(root.firstChild.nextSibling, root.firstChild.nextSibling); +check(); +is(mutateCount, 2, "should remove and reinsert " + testName); + +testName = "self insertBefore lastChild"; +mutateCount = 0; +root.insertBefore(root.lastChild, root.lastChild); +check(); +is(mutateCount, 2, "should remove and reinsert " + testName); + +testName = "appendChild last"; +mutateCount = 0; +root.appendChild(root.lastChild); +check(); +is(mutateCount, 2, "should remove and reinsert " + testName); + +testName = "prepare script/style"; +script = document.createElement("script"); +script.appendChild(document.createTextNode("void(0);")); +root.appendChild(script); +style = document.createElement("style"); +root.appendChild(style); +check(); + +testName = "set something in script"; +script.text = "something"; +check(); + +testName = "set something in style"; +style.innerHTML = "something { dislay: none; }"; +check(); + +testName = "moving style"; +root.insertBefore(style, root.firstChild); +check(); + +testName = "replacing script"; +root.replaceChild(b, script); +check(); + +testName = "doc-fragment insert in the middle"; +frag = document.createDocumentFragment(); +frag.addEventListener("DOMNodeRemoved", function(e) { + var index = children.indexOf(e.target); + ok(index != -1, "unknown child removed from fragment"); + children.splice(index, 1); +}, false); +var children = []; +children.push(document.createTextNode("foo")); +children.push(document.createTextNode("bar")); +children.push(document.createElement("span")); +children.push(document.createElement("b")); +children[2].appendChild(document.createElement("i")); +children.forEach(function(child) { frag.appendChild(child); }); +ok(root.firstChild, "need to have children in order to test inserting before end"); +root.replaceChild(frag, root.firstChild); +check(); +is(children.length, 0, "should have received DOMNodeRemoved for all frag children when inserting"); +is(frag.childNodes.length, 0, "fragment should be empty when inserting"); + +testName = "doc-fragment append at the end"; +children.push(document.createTextNode("foo")); +children.push(document.createTextNode("bar")); +children.push(document.createElement("span")); +children.push(document.createElement("b")); +children[2].appendChild(document.createElement("i")); +children.forEach(function(child) { frag.appendChild(child); }); +root.appendChild(frag); +check(); +is(children.length, 0, "should have received DOMNodeRemoved for all frag children when appending"); +is(frag.childNodes.length, 0, "fragment should be empty when appending"); + +</script> +</body> +</html> + diff --git a/dom/events/test/test_bug656379-1.html b/dom/events/test/test_bug656379-1.html new file mode 100644 index 000000000..bc92189e2 --- /dev/null +++ b/dom/events/test/test_bug656379-1.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=656379 +--> +<head> + <title>Test for Bug 656379</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript;version=1.8"> + +/** Test for Bug 656379 **/ +SimpleTest.waitForExplicitFinish(); +var subwindow = window.open("./bug656379-1.html", "bug656379", "width=800,height=1000"); + +function finishTests() { + subwindow.close(); + SimpleTest.finish(); +} +</script> +</pre> + +</body> +</html> diff --git a/dom/events/test/test_bug656379-2.html b/dom/events/test/test_bug656379-2.html new file mode 100644 index 000000000..d53abce41 --- /dev/null +++ b/dom/events/test/test_bug656379-2.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=656379 +--> +<head> + <title>Test for Bug 656379</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="button"]:hover { color: green; } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=656379">Mozilla Bug 656379</a> +<p id="display"> + <label for="button1" id="label1">Label 1</label> + <input type="button" id="button1" value="Button 1"> + <label> + <span id="label2">Label 2</span> + <input type="button" id="button2" value="Button 2"> + </label> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.8"> + +/** Test for Bug 656379 **/ +SimpleTest.waitForExplicitFinish(); +function tests() { + synthesizeMouseAtCenter($("label1"), { type: "mousemove" }); + yield undefined; + is($("button1").matches(":hover"), true, + "Button 1 should be hovered after mousemove over label1"); + is($("label1").matches(":hover"), true, + "Label 1 should be hovered after mousemove over label1"); + is($("button2").matches(":hover"), false, + "Button 2 should not be hovered after mousemove over label1"); + is($("label2").matches(":hover"), false, + "Label 2 should not be hovered after mousemove over label1"); + synthesizeMouseAtCenter($("button2"), { type: "mousemove" }); + yield undefined; + is($("button1").matches(":hover"), false, + "Button 1 should not be hovered after mousemove over button2"); + is($("label1").matches(":hover"), false, + "Label 1 should not be hovered after mousemove over button2"); + is($("button2").matches(":hover"), true, + "Button 2 should be hovered after mousemove over button2"); + is($("label2").matches(":hover"), false, + "Label 2 should not be hovered after mousemove over label2"); + synthesizeMouseAtCenter($("label2"), { type: "mousemove" }); + yield undefined; + is($("button1").matches(":hover"), false, + "Button 1 should not be hovered after mousemove over label2"); + is($("label1").matches(":hover"), false, + "Label 1 should not be hovered after mousemove over label2"); + is($("button2").matches(":hover"), true, + "Button 2 should be hovered after mousemove over label2"); + is($("label2").matches(":hover"), true, + "Label 2 should be hovered after mousemove over label2"); + SimpleTest.finish(); +} + +function executeTests() { + var testYielder = tests(); + function execNext() { + try { + testYielder.next(); + SimpleTest.executeSoon(execNext); + } catch(e) {} + } + execNext(); +} + +SimpleTest.waitForFocus(executeTests); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug656954.html b/dom/events/test/test_bug656954.html new file mode 100644 index 000000000..c4fd29e04 --- /dev/null +++ b/dom/events/test/test_bug656954.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=656954 +--> +<head> + <title>Test for Bug 656954</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=656954">Mozilla Bug 656954</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 656954 **/ + +var e = document.createEvent("Event"); +is(e.defaultPrevented, false, + "After creating event defaultPrevented should be false"); +e.initEvent("foo", true, true); +var el = document.createElement("div"); +el.addEventListener("foo", + function(evt) { + evt.preventDefault(); + }, false); +el.dispatchEvent(e); +is(e.defaultPrevented, true, "preventDefault() should have been called!"); + +e = document.createEvent("Event"); +e.initEvent("foo", true, false); +el.dispatchEvent(e); +is(e.defaultPrevented, false, "preventDefault() should not have any effect!"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug659071.html b/dom/events/test/test_bug659071.html new file mode 100644 index 000000000..9a40c525c --- /dev/null +++ b/dom/events/test/test_bug659071.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=659071 +--> +<head> + <title>Test for Bug 659071</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/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" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=659071">Mozilla Bug 659071</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 659071 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var subWin = window.open("window_bug659071.html", "_blank", + "width=500,height=500"); + +function finish() +{ + subWin.close(); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug659350.html b/dom/events/test/test_bug659350.html new file mode 100644 index 000000000..0d566c0b1 --- /dev/null +++ b/dom/events/test/test_bug659350.html @@ -0,0 +1,111 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=659350 +--> +<head> + <title>Test for Bug 659350</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=659350">Mozilla Bug 659350</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 659350 **/ +function testIn(eventName, obj, objName, expected) { + is(eventName in obj, expected, "'" + eventName + "' shuld be in " + objName); +} + +var div = document.createElement("div"); + +// Forwarded events +testIn("onscroll", window, "window", true); +testIn("onscroll", document.body, "body", true); +testIn("onscroll", div, "div", true); +// Window events +testIn("onpopstate", window, "window", true); +testIn("onpopstate", document.body, "body", true); +testIn("onpopstate", div, "div", false); +// Non-idl events +testIn("onopen", window, "window", false); +testIn("onopen", document.body, "body", false); +testIn("onopen", div, "div", false); + +function f() {} +function g() {} + +// Basic sanity of interaction between the IDL and content attributes +div.onload = f; +is(div.onload, f, "Should have 'f' as div's onload"); +div.setAttribute("onload", ""); +isnot(div.onload, f, "Should not longer have 'f' as div's onload"); +is(div.onload.toString(), "function onload(event) {\n\n}", + "Should have wrapped empty string in a function"); +div.setAttribute("onload", "foopy();"); +is(div.onload.toString(), "function onload(event) {\nfoopy();\n}", + "Should have wrapped call in a function"); +div.removeAttribute("onload"); +is(div.onload, null, "Should have null onload now"); + +// Test forwarding to window for both events that are window-specific and that +// exist on all elements +function testPropagationToWindow(eventName) { + is(window["on"+eventName], null, "Shouldn't have " + eventName + " stuff yet"); + document.body["on"+eventName] = f; + is(window["on"+eventName], f, + "Setting on"+eventName+" on body should propagate to window"); + document.createElement("body")["on"+eventName] = g; + is(window["on"+eventName], g, + "Setting on"+eventName+" on body not in document should propagate to window"); + document.createElement("frameset")["on"+eventName] = f; + is(window["on"+eventName], f, + "Setting on"+eventName+" on frameset not in document should propagate to window"); + + document.body.setAttribute("on"+eventName, eventName); + is(window["on"+eventName].toString(), + "function on"+eventName+"(event) {\n"+eventName+"\n}", + "Setting on"+eventName+"attribute on body should propagate to window"); + document.createElement("body").setAttribute("on"+eventName, eventName+"2"); + is(window["on"+eventName].toString(), + "function on"+eventName+"(event) {\n"+eventName+"2\n}", + "Setting on"+eventName+"attribute on body outside the document should propagate to window"); +} + +testPropagationToWindow("popstate"); +testPropagationToWindow("scroll"); + +// Test |this| and scoping +var called; +div.onscroll = function(event) { + is(this, div, "This should be div when invoking event listener"); + is(event, ev, "Event argument should be the event that was dispatched"); + called = true; +} +var ev = document.createEvent("Events"); +ev.initEvent("scroll", true, true); +called = false; +div.dispatchEvent(ev); +is(called, true, "Event listener set via on* property not called"); + +div.foopy = "Found me"; +document.foopy = "Didn't find me"; +document.foopy2 = "Found me"; +div.setAttribute("onscroll", + "is(this, div, 'This should be div when invoking via attribute');\ + is(foopy, 'Found me', 'div should be on the scope chain when invoking handler compiled from content attribute');\ + is(foopy2, 'Found me', 'document should be on the scope chain when invking handler compiled from content attribute');\ + is(event, ev, 'Event argument should be the event that was dispatched');\ + called = true;"); +called = false; +div.dispatchEvent(ev); +is(called, true, "Event listener set via on* attribute not called"); +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug662678.html b/dom/events/test/test_bug662678.html new file mode 100644 index 000000000..d543f9b96 --- /dev/null +++ b/dom/events/test/test_bug662678.html @@ -0,0 +1,153 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=662678 +--> +<head> + <title>Test for Bug 662678</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=662678">Mozilla Bug 662678</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 662678 **/ +SimpleTest.waitForExplicitFinish(); + +var checkMotion = function(event) { + window.removeEventListener("devicemotion", checkMotion, true); + + is(event.acceleration.x, 1.5, "acceleration.x"); + is(event.acceleration.y, 2.5, "acceleration.y"); + is(event.acceleration.z, 3.5, "acceleration.z"); + is(event.accelerationIncludingGravity.x, 4.5, "accelerationIncludingGravity.x"); + is(event.accelerationIncludingGravity.y, 5.5, "accelerationIncludingGravity.y"); + is(event.accelerationIncludingGravity.z, 6.5, "accelerationIncludingGravity.z"); + is(event.rotationRate.alpha, 7.5, "rotationRate.alpha"); + is(event.rotationRate.beta, 8.5, "rotationRate.beta"); + is(event.rotationRate.gamma, 9.5, "rotationRate.gamma"); + is(event.interval, 0.5, "interval"); + + var e = document.createEvent("DeviceMotionEvent"); + e.initDeviceMotionEvent('devicemotion', true, true, + null, null, null, null); + is(e.acceleration.x, null, "acceleration.x"); + is(e.acceleration.y, null, "acceleration.y"); + is(e.acceleration.z, null, "acceleration.z"); + is(e.accelerationIncludingGravity.x, null, "accelerationIncludingGravity.x"); + is(e.accelerationIncludingGravity.y, null, "accelerationIncludingGravity.y"); + is(e.accelerationIncludingGravity.z, null, "accelerationIncludingGravity.z"); + is(e.rotationRate.alpha, null, "rotationRate.alpha"); + is(e.rotationRate.beta, null, "rotationRate.beta"); + is(e.rotationRate.gamma, null, "rotationRate.gamma"); + is(e.interval, null, "interval"); + + e.initDeviceMotionEvent('devicemotion', true, true, + {}, {}, {}, 0); + is(e.acceleration.x, null, "acceleration.x"); + is(e.acceleration.y, null, "acceleration.y"); + is(e.acceleration.z, null, "acceleration.z"); + is(e.accelerationIncludingGravity.x, null, "accelerationIncludingGravity.x"); + is(e.accelerationIncludingGravity.y, null, "accelerationIncludingGravity.y"); + is(e.accelerationIncludingGravity.z, null, "accelerationIncludingGravity.z"); + is(e.rotationRate.alpha, null, "rotationRate.alpha"); + is(e.rotationRate.beta, null, "rotationRate.beta"); + is(e.rotationRate.gamma, null, "rotationRate.gamma"); + is(e.interval, 0, "interval"); + + window.addEventListener("devicemotion", checkMotionCtor, true); + + event = new DeviceMotionEvent('devicemotion', { + bubbles: true, cancelable: true, + acceleration: {x:1.5,y:2.5,z:3.5}, + accelerationIncludingGravity: {x:4.5,y:5.5,z:6.5}, + rotationRate: {alpha:7.5,beta:8.5,gamma:9.5}, + interval: 0.5 + }); + window.dispatchEvent(event); +}; + +var checkMotionCtor = function(event) { + window.removeEventListener("devicemotion", checkMotionCtor, true); + + is(event.acceleration.x, 1.5, "acceleration.x"); + is(event.acceleration.y, 2.5, "acceleration.y"); + is(event.acceleration.z, 3.5, "acceleration.z"); + is(event.accelerationIncludingGravity.x, 4.5, "accelerationIncludingGravity.x"); + is(event.accelerationIncludingGravity.y, 5.5, "accelerationIncludingGravity.y"); + is(event.accelerationIncludingGravity.z, 6.5, "accelerationIncludingGravity.z"); + is(event.rotationRate.alpha, 7.5, "rotationRate.alpha"); + is(event.rotationRate.beta, 8.5, "rotationRate.beta"); + is(event.rotationRate.gamma, 9.5, "rotationRate.gamma"); + is(event.interval, 0.5, "interval"); + + var e = new DeviceMotionEvent('devicemotion'); + is(e.acceleration.x, null, "acceleration.x"); + is(e.acceleration.y, null, "acceleration.y"); + is(e.acceleration.z, null, "acceleration.z"); + is(e.accelerationIncludingGravity.x, null, "accelerationIncludingGravity.x"); + is(e.accelerationIncludingGravity.y, null, "accelerationIncludingGravity.y"); + is(e.accelerationIncludingGravity.z, null, "accelerationIncludingGravity.z"); + is(e.rotationRate.alpha, null, "rotationRate.alpha"); + is(e.rotationRate.beta, null, "rotationRate.beta"); + is(e.rotationRate.gamma, null, "rotationRate.gamma"); + is(e.interval, null, "interval"); + + e = new DeviceMotionEvent('devicemotion', { + bubbles: true, cancelable: true, + acceleration: null, accelerationIncludingGravity: null, + rotationRate: null, interval: null + }); + is(e.acceleration.x, null, "acceleration.x"); + is(e.acceleration.y, null, "acceleration.y"); + is(e.acceleration.z, null, "acceleration.z"); + is(e.accelerationIncludingGravity.x, null, "accelerationIncludingGravity.x"); + is(e.accelerationIncludingGravity.y, null, "accelerationIncludingGravity.y"); + is(e.accelerationIncludingGravity.z, null, "accelerationIncludingGravity.z"); + is(e.rotationRate.alpha, null, "rotationRate.alpha"); + is(e.rotationRate.beta, null, "rotationRate.beta"); + is(e.rotationRate.gamma, null, "rotationRate.gamma"); + is(e.interval, null, "interval"); + + e = new DeviceMotionEvent('devicemotion', { + bubbles: true, cancelable: true, + acceleration: {}, accelerationIncludingGravity: {}, + rotationRate: {}, interval: 0 + }); + is(e.acceleration.x, null, "acceleration.x"); + is(e.acceleration.y, null, "acceleration.y"); + is(e.acceleration.z, null, "acceleration.z"); + is(e.accelerationIncludingGravity.x, null, "accelerationIncludingGravity.x"); + is(e.accelerationIncludingGravity.y, null, "accelerationIncludingGravity.y"); + is(e.accelerationIncludingGravity.z, null, "accelerationIncludingGravity.z"); + is(e.rotationRate.alpha, null, "rotationRate.alpha"); + is(e.rotationRate.beta, null, "rotationRate.beta"); + is(e.rotationRate.gamma, null, "rotationRate.gamma"); + is(e.interval, 0, "interval"); + + SimpleTest.finish(); +}; + +window.addEventListener("devicemotion", checkMotion, true); + +var event = DeviceMotionEvent; +ok(!!event, "Should have seen DeviceMotionEvent!"); + +event = document.createEvent("DeviceMotionEvent"); +event.initDeviceMotionEvent('devicemotion', true, true, + {x:1.5,y:2.5,z:3.5}, + {x:4.5,y:5.5,z:6.5}, + {alpha:7.5,beta:8.5,gamma:9.5}, + 0.5); +window.dispatchEvent(event); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug667612.html b/dom/events/test/test_bug667612.html new file mode 100644 index 000000000..b5d4fe88b --- /dev/null +++ b/dom/events/test/test_bug667612.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=667612 +--> +<head> + <title>Test for Bug 667612</title> + <script type="text/javascript" src="/MochiKit/packed.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=667612">Mozilla Bug 667612</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +xhr = new XMLHttpRequest; +w = new Worker("empty.js"); +window.addEventListener("load", null, false); +document.addEventListener("load", null, false); +document.body.addEventListener("load", null, false); +xhr.addEventListener("load", null, false); +w.addEventListener("load", null, false); +window.addEventListener("load", undefined, false); +document.addEventListener("load", undefined, false); +document.body.addEventListener("load", undefined, false); +xhr.addEventListener("load", undefined, false); +w.addEventListener("load", undefined, false); + +ok(true, "didn't throw"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug667919-1.html b/dom/events/test/test_bug667919-1.html new file mode 100644 index 000000000..56643ae01 --- /dev/null +++ b/dom/events/test/test_bug667919-1.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=615597 +--> +<head> + <title>Test for Bug 615597</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=615597">Mozilla Bug 615597</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 615597 **/ + +window.ondeviceorientation = function(event) { + is(event.alpha, 1.5); + is(event.beta, 2.25); + is(event.gamma, 3.667); + is(event.absolute, true); + SimpleTest.finish(); +}; + +var event = DeviceOrientationEvent; +ok(!!event, "Should have seen DeviceOrientationEvent!"); + +event = document.createEvent("DeviceOrientationEvent"); +event.initDeviceOrientationEvent('deviceorientation', true, true, 1.5, 2.25, 3.667, true); +window.dispatchEvent(event); +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug679494.xul b/dom/events/test/test_bug679494.xul new file mode 100644 index 000000000..0239fbb02 --- /dev/null +++ b/dom/events/test/test_bug679494.xul @@ -0,0 +1,36 @@ +<?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"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=679494 +--> +<window title="Mozilla Bug 679494" onload="doTest();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=679494">Mozilla Bug 679494</a> + <p id="display"></p> +<div id="content" style="display: none"> + <iframe id="contentframe" src="http://mochi.test:8888/tests/dom/events/test/file_bug679494.html"></iframe> +</div> +</body> + +<script class="testbody" type="application/javascript;version=1.8"><![CDATA[ + +/* Test for bug 679494 */ +function doTest() { + SimpleTest.waitForExplicitFinish(); + + var w = document.getElementById("contentframe").contentWindow; + w.addEventListener("message", function(e) { + is("test", e.data, "We got the data without a compartment mismatch assertion!"); + SimpleTest.finish(); + }, false); + w.postMessage("test", "*"); +} + +]]></script> + +</window> diff --git a/dom/events/test/test_bug684208.html b/dom/events/test/test_bug684208.html new file mode 100644 index 000000000..f8aa2f575 --- /dev/null +++ b/dom/events/test/test_bug684208.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=684208 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 684208</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 684208 **/ + + function checkDispatchReturnValue(targetOrUndefined) { + var target = targetOrUndefined ? targetOrUndefined : self; + function createEvent() { + if ("MouseEvent" in this) { + return new MouseEvent("click", {cancelable: true}); + } + return new Event("dummy", {cancelable: true}); + } + + function postSelfMessage(msg) { + try { + self.postMessage(msg); + } catch(ex) { + self.postMessage(msg, "*"); + } + } + + function passiveListener(e) { + e.target.removeEventListener(e.type, passiveListener); + } + var event = createEvent(); + target.addEventListener(event.type, passiveListener); + postSelfMessage(target.dispatchEvent(event) == true); + + function cancellingListener(e) { + e.target.removeEventListener(e.type, cancellingListener); + e.preventDefault(); + } + event = createEvent(); + target.addEventListener(event.type, cancellingListener); + postSelfMessage(target.dispatchEvent(event) == false); + } + + function test() { + var expectedEvents = 6; + function messageHandler(e) { + ok(e.data, "All the dispatchEvent calls should pass."); + --expectedEvents; + if (!expectedEvents) { + window.onmessage = null; + window.worker.onmessage = null; + SimpleTest.finish(); + } + } + window.onmessage = messageHandler; + checkDispatchReturnValue(); + checkDispatchReturnValue(document.getElementById("link")); + window.worker = + new Worker(URL.createObjectURL(new Blob(["(" + checkDispatchReturnValue.toString() + ")();"]))); + window.worker.onmessage = messageHandler; + } + + SimpleTest.waitForExplicitFinish(); + + </script> +</head> +<body onload="test();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=684208">Mozilla Bug 684208</a> +<p id="display"></p> +<div id="content" style="display: none"> +<a id="link" href="#foo">foo</a> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug687787.html b/dom/events/test/test_bug687787.html new file mode 100644 index 000000000..137e2a1eb --- /dev/null +++ b/dom/events/test/test_bug687787.html @@ -0,0 +1,617 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +--> +<head> + <title>Test for Bug 687787</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=687787">Mozilla Bug 687787</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var content = document.getElementById('content'); +var eventStack = []; + +function _callback(e){ + var event = {'type' : e.type, 'target' : e.target, 'relatedTarget' : e.relatedTarget } + eventStack.push(event); +} + +function clearEventStack(){ + eventStack = []; +} + +window.addEventListener("focus", _callback, true); +window.addEventListener("focusin", _callback, true); +window.addEventListener("focusout", _callback, true); +window.addEventListener("blur", _callback, true); + +function CompareEventToExpected(e, expected) { + if (expected == null || e == null) + return false; + if (e.type == expected.type && e.target == expected.target && e.relatedTarget == expected.relatedTarget) + return true; + return false; +} + +function TestEventOrderNormal() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'blur', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focusout', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : input2}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + + input2.focus(); + input3.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestEventOrderNormalFiresAtRightTime() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + + input1.onblur = function(e) + { + ok(document.activeElement == document.body, 'input1: not focused when blur fires') + } + + input1.addEventListener('focusout', function(e) + { + ok(document.activeElement == document.body, 'input1: not focused when focusout fires') + }); + + input2.onfocus = function(e) + { + ok(document.activeElement == input2, 'input2: focused when focus fires') + } + + input2.addEventListener('focusin', function(e) + { + ok(document.activeElement == input2, 'input2: focused when focusin fires') + }); + + content.appendChild(input1); + content.appendChild(input2); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : input1}, + ] + + input1.focus(); + clearEventStack(); + + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestFocusOutRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input1.addEventListener('focusout', function () { + input3.focus(); + }); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : null}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestFocusInRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input2.addEventListener('focusin', function () { + input3.focus(); + }); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'blur', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focusout', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : input2}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestBlurRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input1.onblur = function () { + input3.focus(); + } + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : null}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestFocusRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input2.onfocus = function () { + input3.focus(); + } + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'blur', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focusout', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : input2}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestEventOrderDifferentDocument() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var iframe1 = document.createElement('iframe'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + iframe1.setAttribute('id', 'iframe1'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + + content.appendChild(input1); + content.appendChild(iframe1); + iframe1.contentDocument.body.appendChild(input2); + content.style.display = 'block' + + iframe1.contentDocument.addEventListener("focus", _callback, true); + iframe1.contentDocument.addEventListener("focusin", _callback, true); + iframe1.contentDocument.addEventListener("focusout", _callback, true); + iframe1.contentDocument.addEventListener("blur", _callback, true); + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : null}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : null}, + {'type' : 'blur', + 'target' : document, + 'relatedTarget' : null}, + {'type' : 'blur', + 'target' : window, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : iframe1.contentDocument, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : null}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + + +function TestFocusOutMovesTarget() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var iframe1 = document.createElement('iframe'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + iframe1.setAttribute('id', 'iframe1'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + + input1.addEventListener('focusout', function () { + iframe1.contentDocument.body.appendChild(input2); + }); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(iframe1); + content.style.display = 'block' + + iframe1.contentDocument.addEventListener("focus", _callback, true); + iframe1.contentDocument.addEventListener("focusin", _callback, true); + iframe1.contentDocument.addEventListener("focusout", _callback, true); + iframe1.contentDocument.addEventListener("blur", _callback, true); + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : null}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput() { + + var input1 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input1.setAttribute('type', 'text'); + + content.appendChild(input1); + + expectedEventOrder = [ + {'type' : 'focus', + 'target' : document, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : window, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : input1, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input1, + 'relatedTarget' : null}, + ] + + window.blur(); + clearEventStack(); + input1.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +TestEventOrderNormal(); +TestEventOrderNormalFiresAtRightTime(); +TestFocusOutRedirectsFocus(); +TestFocusInRedirectsFocus(); +TestBlurRedirectsFocus(); +TestFocusRedirectsFocus(); +TestFocusOutMovesTarget(); +TestEventOrderDifferentDocument(); +TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput(); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug689564.html b/dom/events/test/test_bug689564.html new file mode 100644 index 000000000..e6b4f3764 --- /dev/null +++ b/dom/events/test/test_bug689564.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=689564 +--> +<head> + <title>Test for Bug 689564</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=689564">Mozilla Bug 689564</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 689564 **/ +var div = document.createElement("div"); +div.setAttribute("onclick", "div"); +is(window.onclick, null, "div should not forward onclick"); +is(div.onclick.toString(), "function onclick(event) {\ndiv\n}", + "div should have an onclick handler"); + +div.setAttribute("onscroll", "div"); +is(window.onscroll, null, "div should not forward onscroll"); +is(div.onscroll.toString(), "function onscroll(event) {\ndiv\n}", + "div should have an onscroll handler"); + +div.setAttribute("onpopstate", "div"); +is(window.onpopstate, null, "div should not forward onpopstate"); +is("onpopstate" in div, false, "div should not have onpopstate handler"); + +var body = document.createElement("body"); +body.setAttribute("onclick", "body"); +is(window.onclick, null, "body should not forward onclick"); +is(body.onclick.toString(), "function onclick(event) {\nbody\n}", + "body should have an onclick handler"); +body.setAttribute("onscroll", "body"); +is(window.onscroll.toString(), "function onscroll(event) {\nbody\n}", + "body should forward onscroll"); +body.setAttribute("onpopstate", "body"); +is(window.onpopstate.toString(), "function onpopstate(event) {\nbody\n}", + "body should forward onpopstate"); + +var frameset = document.createElement("frameset"); +frameset.setAttribute("onclick", "frameset"); +is(window.onclick, null, "frameset should not forward onclick"); +is(frameset.onclick.toString(), "function onclick(event) {\nframeset\n}", + "frameset should have an onclick handler"); +frameset.setAttribute("onscroll", "frameset"); +is(window.onscroll.toString(), "function onscroll(event) {\nframeset\n}", + "frameset should forward onscroll"); +frameset.setAttribute("onpopstate", "frameset"); +is(window.onpopstate.toString(), "function onpopstate(event) {\nframeset\n}", + "frameset should forward onpopstate"); + + + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug698929.html b/dom/events/test/test_bug698929.html new file mode 100644 index 000000000..03c2ff081 --- /dev/null +++ b/dom/events/test/test_bug698929.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=698929 +--> +<head> + <title>Test for Bug 698929</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=698929">Mozilla Bug 698929</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 698929 **/ + + var e = document.createEvent("Event"); + e.initEvent("foo", true, true); + var c = 0; + var b = 0; + document.addEventListener("foo", + function() { + ++c; + }); + document.body.addEventListener("foo", function(e) { + ++b; + e.stopImmediatePropagation(); + }); + document.body.addEventListener("foo", function(e) { + ++b; + }); + document.body.dispatchEvent(e); + document.documentElement.dispatchEvent(e); + is(c, 1, "Listener in the document should have been called once."); + is(b, 1, "Listener in the body should have been called once."); + + + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug704423.html b/dom/events/test/test_bug704423.html new file mode 100644 index 000000000..282745477 --- /dev/null +++ b/dom/events/test/test_bug704423.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=704423 +--> +<head> + <title>Test for Bug 704423</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704423">Mozilla Bug 704423</a> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 704423 **/ + +function doTest() +{ + function handler(aEvent) { + aEvent.preventDefault(); + ok(aEvent.defaultPrevented, + "mousemove event should be cancelable"); + } + window.addEventListener("mousemove", handler, true); + synthesizeMouseAtCenter(document.body, { type: "mousemove" }); + window.removeEventListener("mousemove", handler, true); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(doTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug741666.html b/dom/events/test/test_bug741666.html new file mode 100644 index 000000000..41d6c8321 --- /dev/null +++ b/dom/events/test/test_bug741666.html @@ -0,0 +1,176 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=741666 +--> +<head> + <title>Test for Bug 741666</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=741666">Mozilla Bug 741666</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.8"> + +/** Test for Bug 306008 - Touch events with a reference held should retain their touch lists **/ + +let tests = [], testTarget, parent; + +let touch = { + id: 0, + point: {x: 0, y: 0}, + radius: {x: 0, y: 0}, + rotation: 0, + force: 0.5, + target: null +} + +function nextTest() { + if (tests.length) + SimpleTest.executeSoon(tests.shift()); +} + +function checkEvent(aFakeEvent, aTouches) { + return function(aEvent, aTrusted) { + is(aFakeEvent.ctrlKey, aEvent.ctrlKey, "Correct ctrlKey"); + is(aFakeEvent.altKey, aEvent.altKey, "Correct altKey"); + is(aFakeEvent.shiftKey, aEvent.shiftKey, "Correct shiftKey"); + is(aFakeEvent.metaKey, aEvent.metaKey, "Correct metaKey"); + is(aEvent.isTrusted, aTrusted, "Event is trusted"); + checkTouches(aFakeEvent[aTouches], aEvent[aTouches]); + } +} + +function checkTouches(aTouches1, aTouches2) { + is(aTouches1.length, aTouches2.length, "Correct touches length"); + for (var i = 0; i < aTouches1.length; i++) { + checkTouch(aTouches1[i], aTouches2[i]); + } +} + +function checkTouch(aFakeTouch, aTouch) { + is(aFakeTouch.identifier, aTouch.identifier, "Touch has correct identifier"); + is(aFakeTouch.target, aTouch.target, "Touch has correct target"); + is(aFakeTouch.page.x, aTouch.pageX, "Touch has correct pageX"); + is(aFakeTouch.page.y, aTouch.pageY, "Touch has correct pageY"); + is(aFakeTouch.page.x + Math.round(window.mozInnerScreenX), aTouch.screenX, "Touch has correct screenX"); + is(aFakeTouch.page.y + Math.round(window.mozInnerScreenY), aTouch.screenY, "Touch has correct screenY"); + is(aFakeTouch.page.x, aTouch.clientX, "Touch has correct clientX"); + is(aFakeTouch.page.y, aTouch.clientY, "Touch has correct clientY"); + is(aFakeTouch.radius.x, aTouch.radiusX, "Touch has correct radiusX"); + is(aFakeTouch.radius.y, aTouch.radiusY, "Touch has correct radiusY"); + is(aFakeTouch.rotationAngle, aTouch.rotationAngle, "Touch has correct rotationAngle"); + is(aFakeTouch.force, aTouch.force, "Touch has correct force"); +} + +function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) { + var ids = [], xs=[], ys=[], rxs = [], rys = [], + rotations = [], forces = []; + + for (var touchType of ["touches", "changedTouches", "targetTouches"]) { + for (var i = 0; i < aEvent[touchType].length; i++) { + if (ids.indexOf(aEvent[touchType][i].identifier) == -1) { + ids.push(aEvent[touchType][i].identifier); + xs.push(aEvent[touchType][i].page.x); + ys.push(aEvent[touchType][i].page.y); + rxs.push(aEvent[touchType][i].radius.x); + rys.push(aEvent[touchType][i].radius.y); + rotations.push(aEvent[touchType][i].rotationAngle); + forces.push(aEvent[touchType][i].force); + } + } + } + return windowUtils.sendTouchEvent(aType, + ids, xs, ys, rxs, rys, + rotations, forces, + ids.length, aModifiers, 0); +} + +function touchEvent(aOptions) { + if (!aOptions) { + aOptions = {}; + } + this.ctrlKey = aOptions.ctrlKey || false; + this.altKey = aOptions.altKey || false; + this.shiftKey = aOptions.shiftKey || false; + this.metaKey = aOptions.metaKey || false; + this.touches = aOptions.touches || []; + this.targetTouches = aOptions.targetTouches || []; + this.changedTouches = aOptions.changedTouches || []; +} + +function testtouch(aOptions) { + if (!aOptions) + aOptions = {}; + this.identifier = aOptions.identifier || 0; + this.target = aOptions.target || 0; + this.page = aOptions.page || {x: 0, y: 0}; + this.radius = aOptions.radius || {x: 0, y: 0}; + this.rotationAngle = aOptions.rotationAngle || 0; + this.force = aOptions.force || 1; +} + +function testPreventDefault(name) { + let cwu = SpecialPowers.getDOMWindowUtils(window); + let target = document.getElementById("testTarget"); + let bcr = target.getBoundingClientRect(); + + let touch1 = new testtouch({ + page: {x: Math.round(bcr.left + bcr.width/2), + y: Math.round(bcr.top + bcr.height/2)}, + target: target + }); + + let event = new touchEvent({ + touches: [touch1], + targetTouches: [touch1], + changedTouches: [touch1] + }); + + // test touchstart event fires correctly + var checkTouches = checkEvent(event, "touches"); + var checkTargetTouches = checkEvent(event, "targetTouches"); + + /* This is the heart of the test. Verify that the touch event + looks correct both in and outside of a setTimeout */ + window.addEventListener("touchstart", function(firedEvent) { + checkTouches(firedEvent, true); + setTimeout(function() { + checkTouches(firedEvent, true); + checkTargetTouches(firedEvent, true); + + event.touches = []; + event.targetTouches = []; + sendTouchEvent(cwu, "touchend", event, 0); + + nextTest(); + }, 0); + }, false); + sendTouchEvent(cwu, "touchstart", event, 0); +} + +function doTest() { + tests.push(testPreventDefault); + + tests.push(function() { + SimpleTest.finish(); + }); + + nextTest(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> +</pre> +<div id="parent"> + <span id="testTarget" style="margin-left: 200px; padding: 5px; border: 1px solid black;">testTarget</span> +</div> +</body> +</html> diff --git a/dom/events/test/test_bug742376.html b/dom/events/test/test_bug742376.html new file mode 100644 index 000000000..7672ea014 --- /dev/null +++ b/dom/events/test/test_bug742376.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=402089 +--> +<head> + <title>Test for Bug 742376</title> + <script type="text/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" /> +</head> + +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=742376">Mozilla Bug 742376</a> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 742376 **/ + +function hasListeners() { + + var Cc = SpecialPowers.Cc; + var Ci = SpecialPowers.Ci; + var dss = Cc["@mozilla.org/devicesensors;1"].getService(Ci.nsIDeviceSensors); + + return dss.hasWindowListener(Ci.nsIDeviceSensorData.TYPE_ORIENTATION, window) || + dss.hasWindowListener(Ci.nsIDeviceSensorData.TYPE_ROTATION_VECTOR, window) || + dss.hasWindowListener(Ci.nsIDeviceSensorData.TYPE_GAME_ROTATION_VECTOR, window); +} + +is(hasListeners(), false, "Must not have listeners before tests start"); + +function dumbListener(event) {} +function dumbListener2(event) {} +function dumbListener3(event) {} + +window.addEventListener("deviceorientation", dumbListener, false); +window.addEventListener("random_event_name", function() {}, false); +window.addEventListener("deviceorientation", dumbListener2, false); + +is(hasListeners(), true, "Listeners should have been added"); + +window.setTimeout(function() { + + window.removeEventListener("deviceorientation", dumbListener, false); + is(hasListeners(), true, "Only some listeners should have been removed"); + window.setTimeout(function() { + + window.removeEventListener("deviceorientation", dumbListener2, false); + window.setTimeout(function() { + is(hasListeners(), false, "Listeners should have been removed"); + testEventHandler(); + }, 0); + }, 0); +}, 0); + +function testEventHandler() { + window.ondeviceorientation = function() {} + window.setTimeout(function() { + is(hasListeners(), true, "Handler should have been added"); + window.ondeviceorientation = null; + window.setTimeout(function() { + is(hasListeners(), false, "Handler should have been removed"); + SimpleTest.finish(); + }, 0); + }, 0) +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> + diff --git a/dom/events/test/test_bug812744.html b/dom/events/test/test_bug812744.html new file mode 100644 index 000000000..aac2435f7 --- /dev/null +++ b/dom/events/test/test_bug812744.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=812744 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 812744</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=812744">Mozilla Bug 812744</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="f"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 812744 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + var f = $("f"); + var el = f.contentDocument.documentElement; + f.onload = function() { + el.setAttribute("onmouseleave", "(void 0)"); + is(el.onmouseleave.toString(), "function onmouseleave(event) {\n(void 0)\n}", + "Should have a function here"); + SimpleTest.finish(); + }; + f.src = "http://www.example.com/" +}); +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug822898.html b/dom/events/test/test_bug822898.html new file mode 100644 index 000000000..2ac0a6572 --- /dev/null +++ b/dom/events/test/test_bug822898.html @@ -0,0 +1,350 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=822898 +--> +<head> + <title>Test for Bug 822898</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822898">Mozilla Bug 822898</a> +<p id="display"></p> +<div id="content" style="display: none"> + <iframe id="subFrame"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.8"> + +/** Test for Bug 822898 - Pointer* Events **/ + +let tests = [], testTarget, parent, iframeBody, gOnPointerPropHandled; + +function nextTest() { + if (tests.length) + SimpleTest.executeSoon(tests.shift()); +} + +function random() { + return Math.floor(Math.random() * 100); +} + +function createTestEventValue(name) { + + let detail = random(); + let screenX = random(); + let screenY = random(); + let clientX = random(); + let clientY = random(); + let ctrlKey = random() % 2 ? true : false; + let altKey = random() % 2 ? true : false; + let shiftKey = random() % 2 ? true : false; + let metaKey = random() % 2 ? true : false; + let button = random(); + let pointerId = random(); + + return function() { + let event = new PointerEvent("pointerdown", { + bubbles: true, cancelable: true, view: window, + detail: detail, screenX: screenX, screenY: screenY, clientX: clientX, clientY: clientY, + ctrlKey: ctrlKey, altKey: altKey, shiftKey: shiftKey, metaKey: metaKey, + button: button, relatedTarget: null, pointerId: pointerId + }); + + + function check(ev) { + is(ev.detail, detail, "Correct detail"); + is(ev.screenX, screenX, "Correct screenX"); + is(ev.screenY, screenY, "Correct screenY"); + is(ev.clientX, clientX, "Correct clientX"); + is(ev.clientY, clientY, "Correct clientY"); + is(ev.ctrlKey, ctrlKey, "Correct ctrlKey"); + is(ev.altKey, altKey, "Correct altKey"); + is(ev.shiftKey, shiftKey, "Correct shiftKey"); + is(ev.metaKey, metaKey, "Correct metaKey"); + is(ev.button, button, "Correct buttonArg"); + is(ev.pointerId, pointerId, "Correct pointerId"); + } + + for (let target of [document, window, testTarget, parent]) + target.addEventListener(name, check, false); + + testTarget.dispatchEvent(event); + + for (let target of [document, window, testTarget, parent]) + target.removeEventListener(name, check, false); + + + nextTest(); + } +} + +function getDefaultArgEvent(eventname) { + return new PointerEvent(eventname, { + bubbles: true, cancelable: true, view: window, + detail: 0, screenX: 0, screenY: 0, clientX: 0, clientY: 0, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, + button: 0, relatedTarget: null, pointerId: 0 + }); +} + +function testDefaultArg() { + let event = getDefaultArgEvent("pointerdown"); + + testTarget.addEventListener("pointerdown", function(ev) { + testTarget.removeEventListener("pointerdown", arguments.callee, false); + is(ev.pointerId, 0, "Correct default pointerId"); + }, false); + testTarget.dispatchEvent(event); + + nextTest(); +} + +function testStopPropagation() { + let event = getDefaultArgEvent("pointerdown"); + + let unreachableListener = function () { + ok(false, "Event should have been stopped"); + } + + // Capturing phase + let captured = false; + parent.addEventListener("pointerdown", function() { + parent.removeEventListener("pointerdown", arguments.callee, true); + captured = true; + }, true); // Capturing phase + + // Bubbling phase + parent.addEventListener("pointerdown", unreachableListener, false); + + testTarget.addEventListener("pointerdown", function(ev) { + testTarget.removeEventListener("pointerdown", arguments.callee, false); + is(captured, true, "Event should have been captured"); + ev.stopPropagation(); + }, false); + + testTarget.dispatchEvent(event); + + parent.removeEventListener("pointerdown", unreachableListener, false); + + nextTest(); +} + +function testPreventDefault() { + let event = getDefaultArgEvent("pointerdown"); + + parent.addEventListener("pointerdown", function(ev) { + parent.removeEventListener("pointerdown", arguments.callee, false); + is(ev.defaultPrevented, true, "preventDefault can be called"); + nextTest(); + }, false); + + testTarget.addEventListener("pointerdown", function(ev) { + testTarget.removeEventListener("pointerdown", arguments.callee, false); + ev.preventDefault(); + }, false); + + testTarget.dispatchEvent(event); +} + +function testBlockPreventDefault() { + let event = new PointerEvent("pointerdown", { + bubbles: true, cancelable: false, view: window, + detail: 0, screenX: 0, screenY: 0, clientX: 0, clientY: 0, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, + button: 0, relatedTarget: null, pointerId: 0, pointerType: "pen" + }); + + parent.addEventListener("pointerdown", function(ev) { + parent.removeEventListener("pointerdown", arguments.callee, false); + is(ev.defaultPrevented, false, "aCancelableArg works"); + nextTest(); + }, false); + + testTarget.addEventListener("pointerdown", function(ev) { + testTarget.removeEventListener("pointerdown", arguments.callee, false); + ev.preventDefault(); + }, false); + + testTarget.dispatchEvent(event); +} + +function testBlockBubbling() { + let unreachableListener = function () { + ok(false, "aCanBubble doesn't work"); + } + + let event = new PointerEvent("pointerdown", { + bubbles: false, cancelable: true, view: window, + detail: 0, screenX: 0, screenY: 0, clientX: 0, clientY: 0, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, + button: 0, relatedTarget: null, pointerId: 0 + }); + + parent.addEventListener("pointerdown", unreachableListener, false); + testTarget.dispatchEvent(event); + parent.removeEventListener("pointerdown", unreachableListener, false); + + nextTest(); +} + +function testOnPointerProperty() +{ + iframeBody.onpointerdown = function (e) { gOnPointerPropHandled["pointerdown"] = true; } + iframeBody.onpointerup = function (e) { gOnPointerPropHandled["pointerup"] = true; } + iframeBody.onpointermove = function (e) { gOnPointerPropHandled["pointermove"] = true; } + iframeBody.onpointerout = function (e) { gOnPointerPropHandled["pointerout"] = true; } + iframeBody.onpointerover = function (e) { gOnPointerPropHandled["pointerover"] = true; } + iframeBody.onpointerenter = function (e) { gOnPointerPropHandled["pointerenter"] = true; } + iframeBody.onpointerleave = function (e) { gOnPointerPropHandled["pointerleave"] = true; } + iframeBody.onpointercancel = function (e) { gOnPointerPropHandled["pointercancel"] = true; } + + iframeBody.dispatchEvent(getDefaultArgEvent("pointerdown")); + is(gOnPointerPropHandled['pointerdown'], true, "pointerdown property is performed"); + + iframeBody.dispatchEvent(getDefaultArgEvent("pointerup")); + is(gOnPointerPropHandled['pointerup'], true, "pointerup property is performed"); + + iframeBody.dispatchEvent(getDefaultArgEvent("pointermove")); + is(gOnPointerPropHandled['pointermove'], true, "pointermove property is performed"); + + iframeBody.dispatchEvent(getDefaultArgEvent("pointerout")); + is(gOnPointerPropHandled['pointerout'], true, "pointerout property is performed"); + + iframeBody.dispatchEvent(getDefaultArgEvent("pointerover")); + is(gOnPointerPropHandled['pointerover'], true, "pointerover property is performed"); + + iframeBody.dispatchEvent(getDefaultArgEvent("pointerenter")); + is(gOnPointerPropHandled['pointerenter'], true, "pointerenter property is performed"); + + iframeBody.dispatchEvent(getDefaultArgEvent("pointerleave")); + is(gOnPointerPropHandled['pointerleave'], true, "pointerleave property is performed"); + + iframeBody.dispatchEvent(getDefaultArgEvent("pointercancel")); + is(gOnPointerPropHandled['pointercancel'], true, "pointercancel property is performed"); + + nextTest(); +} + +function testPointerEventCTORS() +{ + // TODO: This should go to test_eventctors.html, when PointerEvents enabled by default + var receivedEvent; + iframeBody.addEventListener("hello", function(e) { receivedEvent = e; }, true); + + var e; + var ex = false; + + try { + e = new PointerEvent(); + } catch(exp) { + ex = true; + } + ok(ex, "PointerEvent: First parameter is required!"); + ex = false; + + e = new PointerEvent("hello"); + ok(e.type, "hello", "PointerEvent: Wrong event type!"); + ok(!e.isTrusted, "PointerEvent: Event shouldn't be trusted!"); + ok(!e.bubbles, "PointerEvent: Event shouldn't bubble!"); + ok(!e.cancelable, "PointerEvent: Event shouldn't be cancelable!"); + iframeBody.dispatchEvent(e); + is(receivedEvent, e, "PointerEvent: Wrong event!"); + + var PointerEventProps = + [ { screenX: 0 }, + { screenY: 0 }, + { clientX: 0 }, + { clientY: 0 }, + { ctrlKey: false }, + { shiftKey: false }, + { altKey: false }, + { metaKey: false }, + { button: 0 }, + { buttons: 0 }, + { relatedTarget: null }, + { pointerId: 0 }, + { pointerType: "" } + ]; + + var testPointerProps = + [ + { screenX: 1 }, + { screenY: 2 }, + { clientX: 3 }, + { clientY: 4 }, + { ctrlKey: true }, + { shiftKey: true }, + { altKey: true }, + { metaKey: true }, + { button: 5 }, + { buttons: 6 }, + { relatedTarget: window }, + { pointerId: 5 }, + { pointerType: "mouse" } + ]; + + var defaultPointerEventValues = {}; + for (var i = 0; i < PointerEventProps.length; ++i) { + for (prop in PointerEventProps[i]) { + ok(prop in e, "PointerEvent: PointerEvent doesn't have property " + prop + "!"); + defaultPointerEventValues[prop] = PointerEventProps[i][prop]; + } + } + + while (testPointerProps.length) { + var p = testPointerProps.shift(); + e = new PointerEvent("foo", p); + for (var def in defaultPointerEventValues) { + if (!(def in p)) { + is(e[def], defaultPointerEventValues[def], + "PointerEvent: Wrong default value for " + def + "!"); + } else { + is(e[def], p[def], "PointerEvent: Wrong event init value for " + def + "!"); + } + } + } + nextTest(); +} + +function runTests() { + testTarget = document.getElementById("testTarget"); + parent = testTarget.parentNode; + gOnPointerPropHandled = new Array; + iframeBody = document.getElementById("subFrame").contentWindow.document.body; + + tests.push(createTestEventValue("pointerdown")); + tests.push(createTestEventValue("pointermove")); + tests.push(createTestEventValue("pointerup")); + + tests.push(testDefaultArg); + tests.push(testStopPropagation); + + tests.push(testPreventDefault); + tests.push(testBlockPreventDefault); + + tests.push(testBlockBubbling); + tests.push(testOnPointerProperty()); + tests.push(testPointerEventCTORS()); + + tests.push(function() { + SimpleTest.finish(); + }); + + nextTest(); +} + +window.onload = function() { + SpecialPowers.pushPrefEnv({"set":[["dom.w3c_pointer_events.enabled", true]]}, runTests); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +<div id="parent"> + <span id="testTarget" style="border: 1px solid black;">testTarget</span> +</div> +</body> +</html> diff --git a/dom/events/test/test_bug855741.html b/dom/events/test/test_bug855741.html new file mode 100644 index 000000000..d7707e220 --- /dev/null +++ b/dom/events/test/test_bug855741.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=855741 +--> +<head> + <title>Test for Bug 855741</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<input type="text" id="testTarget" value="focus"> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 855741 **/ +function testFocusEvent(event) { + ok(('relatedTarget' in event), 'FocusEvent.relatedTarget exists'); + + if (event.construct_test == true) { + ok(event.relatedTarget == $("content"), 'FocusEvent.relatedTarget is ' + $("content").id); + } +} + +function testUIEvent(event) { + ok((event.detail == 0), + 'UIEvent.detail should be 0 in ' + event.target.value + ' event'); + + ok((event.defaultView == null), + 'UIEvent.defaultView should be null in ' + event.target.value + ' event'); +} + +function testEventType(event, type) { + ok((event.type == type), 'Event.type match: ' + type); +} + +function eventhandle(event) { + testFocusEvent(event); + testUIEvent(event); + testEventType(event, event.target.value); + + if (event.target.value == 'blur') { + event.target.value = 'focus'; + } else { + event.target.value = 'blur'; + } +} + +// +// event handler: +// +$("testTarget").addEventListener("focus", eventhandle, true); +$("testTarget").addEventListener("blur", eventhandle, true); + +// +// FocusEvent structure test +// +$("testTarget").focus(); +$("testTarget").blur(); + +// +// Focus/Blur constructor test +// +var focus_event = new FocusEvent("focus", + {bubbles: true, + cancelable: true, + relatedTarget: $("content")}); +focus_event.construct_test = true; + +var blur_event = new FocusEvent("blur", + {bubbles: true, + cancelable: true, + relatedTarget: $("content")}); +blur_event.construct_test = true; + +// create cycle referece for leak test +$("content").foo_focus = focus_event; +$("content").foo_blur = blur_event; + +$("testTarget").dispatchEvent(focus_event); +$("testTarget").dispatchEvent(blur_event); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug864040.html b/dom/events/test/test_bug864040.html new file mode 100644 index 000000000..774e1e3ca --- /dev/null +++ b/dom/events/test/test_bug864040.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=864040 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 864040</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=864040">Mozilla Bug 864040</a> +<div id="display"> + <textarea id="ta" rows="5" cols="20" style="-moz-appearance:none"></textarea> + <div id="ce" contentEditable="true" style="height: 5em;"></div> +</div> +<div id="content" style="display: none"> +</div> +<pre id="test"> + <script type="application/javascript"> + /** + * Test for Bug 864040 + * + * We use a selection event to set the selection to the end of an editor + * containing an ending newline. Then we test to see that the caret is + * actually drawn on the newline. + */ + + function testSelectEndOfText(elem) { + var tn = elem.tagName; + elem.focus(); + + // Enter test string into editor + var test_string = 'test\n'; + sendString(test_string); + + // Get the caret position after what we entered + var result = synthesizeQuerySelectedText(); + ok(result, tn + ': failed to query selection (1)'); + var refoffset = result.offset; + + // Take a snapshot of where the caret is (on the new line) + referenceSnapshot = snapshotWindow(window, true /* withCaret */); + ok(referenceSnapshot, tn + ': failed to take snapshot (1)'); + + // Set selection to the same spot through a selection event + ok(synthesizeSelectionSet(refoffset, 0, false), tn + ': failed to set selection'); + + // Make sure new selection is the same + result = synthesizeQuerySelectedText(); + ok(result, tn + ': failed to query selection (2)'); + is(result.offset, refoffset, tn + ': caret is not at the right position'); + + // Take a snapshot of where the new caret is (shoud still be on the new line) + testSnapshot = snapshotWindow(window, true /* withCaret */); + ok(testSnapshot, tn + ': failed to take snapshot (2)'); + + // Compare snapshot (should be the same) + result = compareSnapshots(referenceSnapshot, testSnapshot, true /* expected */) + ok(result, tn + ': failed to compare snapshots'); + // result = [correct, s1data, s2data] + ok(result[0], tn + ': caret is not on new line'); + if (!result[0]) { + dump('Ref: ' + result[1] + '\n'); + dump('Res: ' + result[2] + '\n'); + } + } + + function runTests() { + // we don't test regular <input> because this test is about multiline support + // test textarea + testSelectEndOfText(document.getElementById('ta')); + // test contentEditable + testSelectEndOfText(document.getElementById('ce')); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + SimpleTest.waitForFocus(runTests); + </script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug924087.html b/dom/events/test/test_bug924087.html new file mode 100644 index 000000000..59fc81f5d --- /dev/null +++ b/dom/events/test/test_bug924087.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=924087 +--> +<head> + <title>Test for Bug 924087</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"/> +</head> +<body> +<div contenteditable><a id="editable" href="#">editable link</a></div> +<a id="noneditable" href="#">non-editable link</a> +<input> +<textarea></textarea> +<pre id="test"> +<script type="application/javascript;version=1.8"> + +/** Test for Bug 924087 **/ +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + var editable = document.querySelector("#editable"); + var noneditable = document.querySelector("#noneditable"); + var input = document.querySelector("input"); + var textarea = document.querySelector("textarea"); + synthesizeMouseAtCenter(noneditable, {type:"mousedown"}); + is(document.querySelector(":active:link"), noneditable, "Normal links should become :active"); + synthesizeMouseAtCenter(noneditable, {type:"mouseup"}); + synthesizeMouseAtCenter(editable, {type:"mousedown"}); + is(document.querySelector(":active:link"), null, "Editable links should not become :active"); + synthesizeMouseAtCenter(editable, {type:"mouseup"}); + [input, textarea].forEach(textbox => { + synthesizeMouseAtCenter(textbox, {type:"mousedown"}); + is(document.querySelector(textbox.localName + ":active"), textbox, "The textbox should become :active"); + synthesizeMouseAtCenter(textbox, {type:"mouseup"}); + }); + SimpleTest.finish(); +}); + +</script> +</pre> + +</body> +</html> diff --git a/dom/events/test/test_bug930374-chrome.html b/dom/events/test/test_bug930374-chrome.html new file mode 100644 index 000000000..291a98ca1 --- /dev/null +++ b/dom/events/test/test_bug930374-chrome.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=930374 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 930374</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=930374">Mozilla Bug 930374</a> +<div id="display"> + <input id="input-text"> +</div> +<div id="content" style="display: none"> +</div> +<pre id="test"> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + var gKeyPress = null; + function onKeyPress(aEvent) + { + gKeyPress = aEvent; + is(aEvent.target, document.getElementById("input-text"), "input element should have focus"); + ok(!aEvent.defaultPrevented, "keypress event should be consumed before keypress event handler"); + } + + function runTests() + { + document.addEventListener("keypress", onKeyPress, false); + var input = document.getElementById("input-text"); + input.focus(); + + input.addEventListener("input", function (aEvent) { + input.removeEventListener("input", arguments.callee, false); + ok(gKeyPress, + "Test1: keypress event must be fired before an input event"); + ok(gKeyPress.defaultPrevented, + "Test1: keypress event's defaultPrevented should be true in chrome even if it's consumed by default action handler of editor"); + setTimeout(function () { + ok(gKeyPress.defaultPrevented, + "Test2: keypress event's defaultPrevented should be true after event dispatching finished"); + SimpleTest.finish(); + }, 0); + }, false); + + sendChar("a"); + } + + SimpleTest.waitForFocus(runTests); + </script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug930374-content.html b/dom/events/test/test_bug930374-content.html new file mode 100644 index 000000000..73654bf04 --- /dev/null +++ b/dom/events/test/test_bug930374-content.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=930374 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 930374</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=930374">Mozilla Bug 930374</a> +<div id="display"> + <input id="input-text"> +</div> +<div id="content" style="display: none"> +</div> +<pre id="test"> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + var gKeyPress = null; + function onKeyPress(aEvent) + { + gKeyPress = aEvent; + is(aEvent.target, document.getElementById("input-text"), "input element should have focus"); + ok(!aEvent.defaultPrevented, "keypress event should be consumed before keypress event handler"); + } + + function runTests() + { + document.addEventListener("keypress", onKeyPress, false); + var input = document.getElementById("input-text"); + input.focus(); + + input.addEventListener("input", function (aEvent) { + input.removeEventListener("input", arguments.callee, false); + ok(gKeyPress, + "Test1: keypress event must be fired before an input event"); + ok(!gKeyPress.defaultPrevented, + "Test1: keypress event's defaultPrevented should be false even though it's consumed by the default action handler of editor"); + gKeyPress.preventDefault(); + ok(gKeyPress.defaultPrevented, + "Test1: keypress event's defaultPrevented should become true because of a call of preventDefault()"); + }, false); + + sendChar("a"); + gKeyPress = null; + + input.addEventListener("input", function (aEvent) { + input.removeEventListener("input", arguments.callee, false); + ok(gKeyPress, + "Test2: keypress event must be fired before an input event"); + ok(!gKeyPress.defaultPrevented, + "Test2: keypress event's defaultPrevented should be false even though it's consumed by the default action handler of editor"); + setTimeout(function () { + ok(!gKeyPress.defaultPrevented, + "Test2: keypress event's defaultPrevented should not become true after event dispatching finished"); + SimpleTest.finish(); + }, 0); + }, false); + + sendChar("b"); + } + + SimpleTest.waitForFocus(runTests); + </script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug944011.html b/dom/events/test/test_bug944011.html new file mode 100644 index 000000000..eab74ee32 --- /dev/null +++ b/dom/events/test/test_bug944011.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=944011 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 944011</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 944011 comment 24 - Event handlers should fire even if the + target comes from a non-current inner. **/ + SimpleTest.waitForExplicitFinish(); + var gLoadCount = 0; + function loaded() { + ++gLoadCount; + switch(gLoadCount) { + case 1: + ok(true, "Got first load"); + oldBody = window[0].document.body; + oldBody.onclick = function() { + ok(true, "Got onclick"); + SimpleTest.finish(); + } + $('ifr').setAttribute('src', 'data:text/html,<html><body>Second frame</body></html>'); + break; + case 2: + ok(true, "Got second load"); + oldBody.dispatchEvent(new MouseEvent('click')); + break; + default: + ok(false, "Unexpected load"); + SimpleTest.finish(); + } + } + + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944011">Mozilla Bug 944011</a> +<p id="display"></p> +<div id="content" style="display: none"> + <iframe id="ifr" onload="loaded();" src="data:text/html,<html><body>foo</body></html>"></iframe> + <div name="testTarget"></div> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug944847.html b/dom/events/test/test_bug944847.html new file mode 100644 index 000000000..105c0d5c9 --- /dev/null +++ b/dom/events/test/test_bug944847.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=944847 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 944847</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 944847 **/ + + var e1 = document.createElement("div"); + is(e1.onclick, null); + e1.setAttribute("onclick", ""); + isnot(e1.onclick, null); + + var e2 = document.implementation.createHTMLDocument(null, null).createElement("div"); + is(e2.onclick, null); + e2.setAttribute("onclick", ""); + is(e2.onclick, null); + + var e3 = document.createElement("div"); + is(e3.onclick, null); + e3.setAttribute("onclick", ""); + e2.ownerDocument.adoptNode(e3); + is(e3.onclick, null); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944847">Mozilla Bug 944847</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug946632.html b/dom/events/test/test_bug946632.html new file mode 100644 index 000000000..3ef7b815f --- /dev/null +++ b/dom/events/test/test_bug946632.html @@ -0,0 +1,161 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=946632 +--> +<head> + <title>Test for bug 946632 - propagate mouse-wheel vertical scroll events to container</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + .scrollable { + overflow: scroll; + height: 200px; + width: 200px; + } + input { + font-size: 72px; + height: 20px; + width: 20px; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946632">Mozilla Bug 946632</a> +<p id="display"></p> +<div id="container" class="scrollable"> + <input value="value"> + x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br> + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + SpecialPowers.pushPrefEnv({ + "set":[["general.smoothScroll", false], + ["mousewheel.system_scroll_override_on_root_content.enabled", false]] + }, runTests) + }, window); + +var input = document.querySelector("input"); +var container = document.querySelector("#container"); + +function reset() +{ + container.scrollTop = 0; + container.scrollLeft = 0; + input.scrollTop = 0; + input.scrollLeft = 0; + container.style.display='none'; + container.getBoundingClientRect(); +} + +function prepare(check) +{ + container.style.display=''; + container.getBoundingClientRect(); + scrollHandler = function(event) { + window.removeEventListener("scroll", arguments.callee, true); + event.stopPropagation(); + check(event) + setTimeout(nextTest,0); + }; + window.addEventListener("scroll", scrollHandler, true); +} + +var tests = [ + { + check: function(event) { + is(event.target, container, "<input> vertical line scroll targets container"); + ok(container.scrollTop > 0, "<input> vertical line scroll container.scrollTop"); + is(container.scrollLeft, 0, "<input> vertical line scroll container.scrollLeft"); + is(input.scrollTop, 0, "<input> horizontal line scroll input.scrollTop"); + is(input.scrollLeft, 0, "<input> horizontal line scroll input.scrollLeft"); + }, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, + lineOrPageDeltaY: 1, + } + }, + { + check: function(event) { + is(event.target, input, "<input> horizontal line scroll targets <input>"); + is(input.scrollTop, 0, "<input> horizontal line scroll input.scrollTop"); + ok(input.scrollLeft > 0, "<input> horizontal line scroll input.scrollLeft"); + is(container.scrollTop, 0, "<input> horizontal line scroll container.scrollTop"); + is(container.scrollLeft, 0, "<input> horizontal line scroll container.scrollLeft"); + }, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, + lineOrPageDeltaX: 1 + } + }, + { + check: function(event) { + is(event.target, container, "<input> vertical page scroll targets container"); + ok(container.scrollTop > 0, "<input> vertical line scroll container.scrollTop"); + is(container.scrollLeft, 0, "<input> vertical line scroll container.scrollLeft"); + is(input.scrollTop, 0, "<input> vertical page scroll input.scrollTop"); + is(input.scrollLeft, 0, "<input> vertical page scroll input.scrollLeft"); + }, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, + lineOrPageDeltaY: 1 + } + }, + { + check: function(event) { + is(event.target, input, "<input> horizontal page scroll targets <input>"); + is(input.scrollTop, 0, "<input> horizontal page scroll input.scrollTop"); + ok(input.scrollLeft > 0, "<input> horizontal page scroll input.scrollLeft"); + is(container.scrollTop, 0, "<input> horizontal page scroll container.scrollTop"); + is(container.scrollLeft, 0, "<input> horizontal page scroll container.scrollLeft"); + }, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, + lineOrPageDeltaX: 1 + } + }, +]; + +var i = 0; +function nextTest() +{ + if (i == tests.length) { + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + SimpleTest.finish(); + return; + } + var test = tests[i]; + ++i; + reset(); + + window.waitForAllPaintsFlushed(function() { + prepare(test.check); + + sendWheelAndPaint(input, 5, 5, test.event, function() { + // Do nothing - we wait for the scroll event. + }); + }); +} + +function runTests() +{ + nextTest(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug967796.html b/dom/events/test/test_bug967796.html new file mode 100644 index 000000000..cf1996a45 --- /dev/null +++ b/dom/events/test/test_bug967796.html @@ -0,0 +1,235 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=967796 +--> +<head> + <title>Test for Bug 967796</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=967796">Mozilla Bug 967796</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 967796 **/ + +SpecialPowers.setBoolPref("dom.w3c_pointer_events.enabled", true); // Enable Pointer Events + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); +var outer; +var middle; +var inner; +var outside; +var container; +var file; +var iframe; +var checkRelatedTarget = false; +var expectedRelatedEnter = null; +var expectedRelatedLeave = null; +var pointerentercount = 0; +var pointerleavecount = 0; +var pointerovercount = 0; +var pointeroutcount = 0; + +function sendPointerEvent(t, elem) { + var r = elem.getBoundingClientRect(); + synthesizePointer(elem, r.width / 2, r.height / 2, {type: t}); +} + +var expectedPointerEnterTargets = []; +var expectedPointerLeaveTargets = []; + +function runTests() { + outer = document.getElementById("outertest"); + middle = document.getElementById("middletest"); + inner = document.getElementById("innertest"); + outside = document.getElementById("outside"); + container = document.getElementById("container"); + file = document.getElementById("file"); + iframe = document.getElementById("iframe"); + + // Make sure ESM thinks pointer is outside the test elements. + sendPointerEvent("pointermove", outside); + + pointerentercount = 0; + pointerleavecount = 0; + pointerovercount = 0; + pointeroutcount = 0; + checkRelatedTarget = true; + expectedRelatedEnter = outside; + expectedRelatedLeave = inner; + expectedPointerEnterTargets = ["outertest", "middletest", "innertest"]; + sendPointerEvent("pointermove", inner); + is(pointerentercount, 3, "Unexpected pointerenter event count!"); + is(pointerovercount, 1, "Unexpected pointerover event count!"); + is(pointeroutcount, 0, "Unexpected pointerout event count!"); + is(pointerleavecount, 0, "Unexpected pointerleave event count!"); + expectedRelatedEnter = inner; + expectedRelatedLeave = outside; + expectedPointerLeaveTargets = ["innertest", "middletest", "outertest"]; + sendPointerEvent("pointermove", outside); + is(pointerentercount, 3, "Unexpected pointerenter event count!"); + is(pointerovercount, 1, "Unexpected pointerover event count!"); + is(pointeroutcount, 1, "Unexpected pointerout event count!"); + is(pointerleavecount, 3, "Unexpected pointerleave event count!"); + + // Event handling over native anonymous content. + var r = file.getBoundingClientRect(); + expectedRelatedEnter = outside; + expectedRelatedLeave = file; + synthesizePointer(file, r.width / 6, r.height / 2, {type: "pointermove"}); + is(pointerentercount, 4, "Unexpected pointerenter event count!"); + is(pointerovercount, 2, "Unexpected pointerover event count!"); + is(pointeroutcount, 1, "Unexpected pointerout event count!"); + is(pointerleavecount, 3, "Unexpected pointerleave event count!"); + + // Moving pointer over type="file" shouldn't cause pointerover/out/enter/leave events + synthesizePointer(file, r.width - (r.width / 6), r.height / 2, {type: "pointermove"}); + is(pointerentercount, 4, "Unexpected pointerenter event count!"); + is(pointerovercount, 2, "Unexpected pointerover event count!"); + is(pointeroutcount, 1, "Unexpected pointerout event count!"); + is(pointerleavecount, 3, "Unexpected pointerleave event count!"); + + expectedRelatedEnter = file; + expectedRelatedLeave = outside; + sendPointerEvent("pointermove", outside); + is(pointerentercount, 4, "Unexpected pointerenter event count!"); + is(pointerovercount, 2, "Unexpected pointerover event count!"); + is(pointeroutcount, 2, "Unexpected pointerout event count!"); + is(pointerleavecount, 4, "Unexpected pointerleave event count!"); + + // Initialize iframe + iframe.contentDocument.documentElement.style.overflow = "hidden"; + iframe.contentDocument.body.style.margin = "0px"; + iframe.contentDocument.body.style.width = "100%"; + iframe.contentDocument.body.style.height = "100%"; + iframe.contentDocument.body.innerHTML = + "<div style='width: 100%; height: 50%; border: 1px solid black;'></div>" + + "<div style='width: 100%; height: 50%; border: 1px solid black;'></div>"; + iframe.contentDocument.body.offsetLeft; // flush + + iframe.contentDocument.body.firstChild.onpointerenter = penter; + iframe.contentDocument.body.firstChild.onpointerleave = pleave; + iframe.contentDocument.body.lastChild.onpointerenter = penter; + iframe.contentDocument.body.lastChild.onpointerleave = pleave; + r = iframe.getBoundingClientRect(); + expectedRelatedEnter = outside; + expectedRelatedLeave = iframe; + // Move pointer inside the iframe. + synthesizePointer(iframe.contentDocument.body, r.width / 2, r.height / 4, {type: "pointermove"}, + iframe.contentWindow); + synthesizePointer(iframe.contentDocument.body, r.width / 2, r.height - (r.height / 4), {type: "pointermove"}, + iframe.contentWindow); + is(pointerentercount, 7, "Unexpected pointerenter event count!"); + expectedRelatedEnter = iframe; + expectedRelatedLeave = outside; + sendPointerEvent("pointermove", outside); + is(pointerleavecount, 7, "Unexpected pointerleave event count!"); + + // pointerdown must produce pointerenter event + expectedRelatedEnter = outside; + expectedRelatedLeave = iframe; + // Move pointer inside the iframe. + synthesizePointer(iframe.contentDocument.body, r.width / 2, r.height / 4, {type: "pointerdown"}, + iframe.contentWindow); + synthesizePointer(iframe.contentDocument.body, r.width / 2, r.height - (r.height / 4), {type: "pointerdown"}, + iframe.contentWindow); + is(pointerentercount, 10, "Unexpected pointerenter event count!"); + + // pointerdown + pointermove must produce single pointerenter event + expectedRelatedEnter = outside; + expectedRelatedLeave = iframe; + synthesizePointer(iframe.contentDocument.body, r.width / 2, r.height / 4, {type: "pointerdown"}, + iframe.contentWindow); + synthesizePointer(iframe.contentDocument.body, r.width / 2 + 1, r.height / 4 + 1, {type: "pointermove"}, + iframe.contentWindow); + is(pointerentercount, 11, "Unexpected pointerenter event count!"); + + Array.from(document.querySelectorAll('*')) + .concat([iframe.contentDocument.body.firstChild, iframe.contentDocument.body.lastChild]) + .forEach((elt) => { + elt.onpointerenter = null; + elt.onpointerleave = null; + elt.onpointerenter = null; + elt.onpointerleave = null; + }); + SpecialPowers.clearUserPref("dom.w3c_pointer_events.enabled"); // Disable Pointer Events + + SimpleTest.finish(); +} + +function penter(evt) { + ++pointerentercount; + evt.stopPropagation(); + if (expectedPointerEnterTargets.length) { + var t = expectedPointerEnterTargets.shift(); + is(evt.target.id, t, "Wrong event target!"); + } + is(evt.bubbles, false, evt.type + " should not bubble!"); + is(evt.cancelable, false, evt.type + " is cancelable!"); + is(evt.target, evt.currentTarget, "Wrong event target!"); + ok(!evt.relatedTarget || evt.target.ownerDocument == evt.relatedTarget.ownerDocument, + "Leaking nodes to another document?"); + if (checkRelatedTarget && evt.target.ownerDocument == document) { + is(evt.relatedTarget, expectedRelatedEnter, "Wrong related target (pointerenter)"); + } +} + +function pleave(evt) { + ++pointerleavecount; + evt.stopPropagation(); + if (expectedPointerLeaveTargets.length) { + var t = expectedPointerLeaveTargets.shift(); + is(evt.target.id, t, "Wrong event target!"); + } + is(evt.bubbles, false, evt.type + " should not bubble!"); + is(evt.cancelable, false, evt.type + " is cancelable!"); + is(evt.target, evt.currentTarget, "Wrong event target!"); + ok(!evt.relatedTarget || evt.target.ownerDocument == evt.relatedTarget.ownerDocument, + "Leaking nodes to another document?"); + if (checkRelatedTarget && evt.target.ownerDocument == document) { + is(evt.relatedTarget, expectedRelatedLeave, "Wrong related target (pointerleave)"); + } +} + +function pover(evt) { + ++pointerovercount; + evt.stopPropagation(); +} + +function pout(evt) { + ++pointeroutcount; + evt.stopPropagation(); +} + +</script> +</pre> +<div id="container" onpointerenter="penter(event)" onpointerleave="pleave(event)" + onpointerout="pout(event)" onpointerover="pover(event)"> + <div id="outside" onpointerout="event.stopPropagation()" onpointerover="event.stopPropagation()">foo</div> + <div id="outertest" onpointerenter="penter(event)" onpointerleave="pleave(event)" + onpointerout="pout(event)" onpointerover="pover(event)"> + <div id="middletest" onpointerenter="penter(event)" onpointerleave="pleave(event)" + onpointerout="pout(event)" onpointerover="pover(event)"> + <div id="innertest" onpointerenter="penter(event)" onpointerleave="pleave(event)" + onpointerout="pout(event)" onpointerover="pover(event)">foo</div> + </div> + </div> + <input type="file" id="file" + onpointerenter="penter(event)" onpointerleave="pleave(event)" + onpointerout="pout(event)" onpointerover="pover(event)"> + <br> + <iframe id="iframe" width="50px" height="50px" + onpointerenter="penter(event)" onpointerleave="pleave(event)" + onpointerout="pout(event)" onpointerover="pover(event)"></iframe> +</div> +</body> +</html> diff --git a/dom/events/test/test_bug985988.html b/dom/events/test/test_bug985988.html new file mode 100644 index 000000000..1939e52a6 --- /dev/null +++ b/dom/events/test/test_bug985988.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=985988 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 985988</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 985988 **/ + + function handler() { + return false; + } + + function reversedHandler() { + return true; + } + + function test() { + var t = document.getElementById("testtarget"); + + t.onclick = handler; + var e = new MouseEvent("click", {cancelable: true}); + t.dispatchEvent(e); + ok(e.defaultPrevented, "Should have prevented default handling."); + + t.onclick = reversedHandler; + e = new MouseEvent("click", {cancelable: true}); + t.dispatchEvent(e); + ok(!e.defaultPrevented, "Shouldn't have prevented default handling."); + + // mouseover has reversed meaning for handler return value. + t.onmouseover = reversedHandler; + e = new MouseEvent("mouseover", {cancelable: true}); + t.dispatchEvent(e); + ok(e.defaultPrevented, "Should have prevented default handling."); + + t.onmouseover = handler; + e = new MouseEvent("mouseover", {cancelable: true}); + t.dispatchEvent(e); + ok(!e.defaultPrevented, "Shouldn't have prevented default handling."); + + // error has reversed meaning for handler return value. + t.onerror = reversedHandler; + e = new ErrorEvent("error", {cancelable: true}); + t.dispatchEvent(e); + ok(e.defaultPrevented, "Should have prevented default handling."); + + t.onerror = handler; + e = new MouseEvent("error", {cancelable: true}); + t.dispatchEvent(e); + ok(!e.defaultPrevented, "Shouldn't have prevented default handling."); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(test); + + </script> +</head> +<body onload="test()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=985988">Mozilla Bug 985988</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<a href="#" id="testtarget">test target</a> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_bug998809.html b/dom/events/test/test_bug998809.html new file mode 100644 index 000000000..08499d2c7 --- /dev/null +++ b/dom/events/test/test_bug998809.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=998809 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 998809</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 998809 **/ + var event1 = document.createEvent("Event"); + event1.initEvent("a", false, false); + event1.initEvent("b", false, false); + is(event1.type, "b"); + var event2 = document.createEvent("Event"); + event2.initEvent("a", false, false); + is(event2.type, "a"); + event2.initEvent("b", false, false); + is(event2.type, "b"); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=998809">Mozilla Bug 998809</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_clickevent_on_input.html b/dom/events/test/test_clickevent_on_input.html new file mode 100644 index 000000000..07fe6ade6 --- /dev/null +++ b/dom/events/test/test_clickevent_on_input.html @@ -0,0 +1,111 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test click event on input</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"/> +</head> +<body> +<p id="display"> +<input id="input" + style="position: absolute; top: 5px; left: 5px; border: solid 15px blue; width: 100px; height: 20px;" + onclick="gClickCount++;"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +var gClickCount = 0; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); + +var input = document.getElementById("input"); + +function runTests() +{ + for (var i = 0; i < 3; i++) { + doTest(i); + } + + // Re-test left clicking when the input element has some text. + gClickCount = 0; + input.value = "Long text Long text Long text Long text Long text Long text"; + doTest(0); + + input.style.display = "none"; + SimpleTest.finish(); +} + +function isEnabledMiddleClickPaste() +{ + try { + return SpecialPowers.getBoolPref("middlemouse.paste"); + } catch (e) { + return false; + } +} + +function isEnabledAccessibleCaret() +{ + try { + return SpecialPowers.getBoolPref("layout.accessiblecaret.enabled"); + } catch (e) { + return false; + } +} + +function doTest(aButton) +{ + // NOTE #1: Right click causes a context menu to popup, then, the click event + // isn't generated. + // NOTE #2: If middle click causes text to be pasted, then, the click event + // isn't generated. + // NOTE #3: If touch caret is enabled, touch caret would ovelap input element, + // then, the click event isn't generated. + if (aButton != 2 && + (aButton != 1 || !isEnabledMiddleClickPaste()) && + (aButton != 0 || !isEnabledAccessibleCaret())) { + gClickCount = 0; + // click on border of input + synthesizeMouse(input, 5, 5, { button: aButton }); + is(gClickCount, 1, + "click event doesn't fired on input element (button is " + + aButton + ")"); + + gClickCount = 0; + // down on border + synthesizeMouse(input, 5, 5, { type: "mousedown", button: aButton }); + // up on anonymous div of input + synthesizeMouse(input, 20, 20, { type: "mouseup", button: aButton }); + is(gClickCount, 1, + "click event doesn't fired on input element (button is " + + aButton + ")"); + + gClickCount = 0; + // down on anonymous div of input + synthesizeMouse(input, 20, 20, { type: "mousedown", button: aButton }); + // up on border + synthesizeMouse(input, 5, 5, { type: "mouseup", button: aButton }); + is(gClickCount, 1, + "click event doesn't fired on input element (button is " + + aButton + ")"); + } + + gClickCount = 0; + // down on outside of input + synthesizeMouse(input, -3, -3, { type: "mousedown", button: aButton }); + // up on border + synthesizeMouse(input, 5, 5, { type: "mouseup", button: aButton }); + is(gClickCount, 0, + "click event is fired on input element unexpectedly (button is " + + aButton + ")"); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_continuous_wheel_events.html b/dom/events/test/test_continuous_wheel_events.html new file mode 100644 index 000000000..fc8c69390 --- /dev/null +++ b/dom/events/test/test_continuous_wheel_events.html @@ -0,0 +1,3248 @@ +<!DOCTYPE HTML> +<html style="font-size: 32px;"> +<head> + <title>Test for D3E WheelEvent</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="scrollable" style="font-family:monospace; font-size: 18px; line-height: 1; overflow: auto; width: 200px; height: 200px;"> + <div id="scrolled" style="font-size: 64px; width: 5000px; height: 5000px;"> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + </div> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests, window); + +var gScrollableElement = document.getElementById("scrollable"); +var gScrolledElement = document.getElementById("scrolled"); + +var gLineHeight = 0; +var gHorizontalLine = 0; +var gPageHeight = 0; +var gPageWidth = 0; + +function sendWheelAndWait(aX, aY, aEvent) +{ + sendWheelAndPaint(gScrollableElement, aX, aY, aEvent, continueTest); +} + +function* prepareScrollUnits() +{ + var result = -1; + function handler(aEvent) + { + result = aEvent.detail; + aEvent.preventDefault(); + } + window.addEventListener("MozMousePixelScroll", handler, true); + + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gLineHeight = result; + ok(gLineHeight > 10 && gLineHeight < 25, "prepareScrollUnits: gLineHeight may be illegal value, got " + gLineHeight); + + result = -1; + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gHorizontalLine = result; + ok(gHorizontalLine > 5 && gHorizontalLine < 16, "prepareScrollUnits: gHorizontalLine may be illegal value, got " + gHorizontalLine); + + result = -1; + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gPageHeight = result; + // XXX Cannot we know the actual scroll port size? + ok(gPageHeight >= 150 && gPageHeight <= 200, + "prepareScrollUnits: gPageHeight is strange value, got " + gPageHeight); + + result = -1; + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gPageWidth = result; + ok(gPageWidth >= 150 && gPageWidth <= 200, + "prepareScrollUnits: gPageWidth is strange value, got " + gPageWidth); + + window.removeEventListener("MozMousePixelScroll", handler, true); +} + +// Tests continuous trusted wheel events. Trusted wheel events should cause +// legacy mouse scroll events when its lineOrPageDelta value is not zero or +// accumulated delta values of pixel scroll events of pixel only device +// become over the line height. +function* testContinuousTrustedEvents() +{ + const kSynthesizedWheelEventTests = [ + { description: "Simple horizontal wheel event by pixels (16.0 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 16 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by pixels (16.0 - 1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 16 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by pixels (16.0 - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 16 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Simple vertical wheel event by pixels (16.0 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 16.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 16.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 16 } } + }, + { description: "Simple vertical wheel event by pixels (16.0 - 1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 16.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 16.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 16 } } + }, + { description: "Simple vertical wheel event by pixels (16.0 - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 16.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 16.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 16 } } + }, + + { description: "Simple z-direction wheel event by pixels (16.0 - 1)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 0.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Simple horizontal wheel event by pixels (-16.0 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -16.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -16 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by pixels (-16.0 - -1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -16.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -16 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by pixels (-16.0 - -1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -16.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -16 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Simple vertical wheel event by pixels (-16.0 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -16.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -16.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -16 } } + }, + { description: "Simple vertical wheel event by pixels (-16.0 - -1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -16.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -16.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -16 } } + }, + { description: "Simple vertical wheel event by pixels (-16.0 - -1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -16.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -16.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -16 } } + }, + + { description: "Simple z-direction wheel event by pixels (-16.0 - -1)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: -16.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 0.0, deltaZ: -16.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + // 3 scroll events per line, and legacy line scroll will be fired first. + { description: "Horizontal wheel event by pixels (5.3 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Horizontal wheel event by pixels (5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Vertical wheel event by pixels (5.3 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "Vertical wheel event by pixels (5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "Vertical wheel event by pixels (5.3 - 0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + + { description: "Horizontal wheel event by pixels (-5.3 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Horizontal wheel event by pixels (-5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Horizontal wheel event by pixels (-5.3 - 0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Vertical wheel event by pixels (-5.3 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "Vertical wheel event by pixels (-5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "Vertical wheel event by pixels (-5.3 - 0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + + // 3 scroll events per line, and legacy line scroll will be fired last. + { description: "Horizontal wheel event by pixels (5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Horizontal wheel event by pixels (5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Horizontal wheel event by pixels (5.3 - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Vertical wheel event by pixels (5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "Vertical wheel event by pixels (5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "Vertical wheel event by pixels (5.3 - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + + { description: "Horizontal wheel event by pixels (-5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Horizontal wheel event by pixels (-5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Horizontal wheel event by pixels (-5.3 - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Vertical wheel event by pixels (-5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "Vertical wheel event by pixels (-5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "Vertical wheel event by pixels (-5.3 - -1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + + // Oblique scroll. + { description: "To bottom-right wheel event by pixels (5.3/5.2 - 1/1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 5.2, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 5.2, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "To bottom-right wheel event by pixels (5.3/5.2 - 0/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 5.2, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 5.2, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "To bottom-right wheel event by pixels (5.3/5.2 - 0/0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 5.2, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 5.2, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + + { description: "To bottom-left wheel event by pixels (-5.3/5.3 - -1/1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "To bottom-left wheel event by pixels (-5.3/5.3 - 0/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "To bottom-left wheel event by pixels (-5.3/5.3 - 0/0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + + { description: "To top-left wheel event by pixels (-5.2/-5.3 - -1/-1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.2, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.2, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "To top-left wheel event by pixels (-5.2/-5.3 - 0/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.2, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.2, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "To top-left wheel event by pixels (-5.2/-5.3 - 0/0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.2, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.2, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + + { description: "To top-right wheel event by pixels (5.3/-5.3 - 1/-1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "To top-right wheel event by pixels (5.3/-5.3 - 0/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + + // Pixel scroll only device's test. the lineOrPageDelta values should be computed + // by ESM. When changing the direction for each delta value, it should be + // reset at that time. + { description: "Pixel only device's horizontal wheel event by pixels (5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Pixel only device's horizontal wheel event by pixels (5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Pixel only device's horizontal wheel event by pixels (5.3 - 0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (5.3 - 0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (5.3 - 0) #4", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 1.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (5.3 - 1) #5", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 5 } } + }, + + { description: "Pixel only device's horizontal wheel event by pixels (-5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Pixel only device's horizontal wheel event by pixels (-5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Pixel only device's horizontal wheel event by pixels (-5.3 - 0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Pixel only device's Vertical wheel event by pixels (-5.3 - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (-5.3 - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (-5.3 - 0) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (-5.3 - 0) #4", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -1.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } } + }, + { description: "Pixel only device's Vertical wheel event by pixels (-5.3 - -1) #5", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -5.3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -5 } } + }, + + // ESM should reset an accumulated delta value only when the direction of it + // is changed but shouldn't reset the other delta. + { description: "Pixel only device's bottom-right wheel event by pixels (5.3/4.9 - 0/0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 4.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 4.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: 4 } } + }, + { description: "Pixel only device's bottom-right wheel event by pixels (5.3/4.9 - 0/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 4.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 4.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: 4 } } + }, + { description: "Pixel only device's bottom-left wheel event by pixels (-5.3/4.9 - 0/0) #4", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 4.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 4.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: 4 } } + }, + // the accumulated X should be 0 here, but Y shouldn't be reset. + { description: "Pixel only device's bottom-right wheel event by pixels (5.3/4.9 - 0/0) #5", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 5.3, deltaY: 1.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 5.3, deltaY: 1.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 5 }, + vertical: { expected: true, preventDefault: false, detail: 1 } } + }, + + { description: "Pixel only device's top-left wheel event by pixels (-5.3/-4.9 - 0/0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: -4.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: -4.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: -4 } } + }, + { description: "Pixel only device's top-left wheel event by pixels (-5.3/-4.9 - 0/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: -4.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: -4.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: -4 } } + }, + { description: "Pixel only device's bottom-left wheel event by pixels (-5.3/4.9 - 0/0) #4", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: 4.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: 4.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: 4 } } + }, + // the accumulated Y should be 0 here, but X shouldn't be reset. + { description: "Pixel only device's top-left wheel event by pixels (-5.3/-4.9 - 0/0) #5", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -5.3, deltaY: -4.9, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: true, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -5.3, deltaY: -4.9, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -5 }, + vertical: { expected: true, preventDefault: false, detail: -4 } } + }, + + // Simple line scroll tests. + { description: "Simple horizontal wheel event by lines (1.0 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gHorizontalLine }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by lines (1.0 - 1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gHorizontalLine }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Simple horizontal wheel event by lines (-1.0 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -gHorizontalLine }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by lines (-1.0 - -1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -gHorizontalLine }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Simple vertical wheel event by lines (-1.0 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -gLineHeight } } + }, + { description: "Simple vertical wheel event by lines (-1.0 - -1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -gLineHeight } } + }, + + { description: "Simple vertical wheel event by lines (1.0 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: gLineHeight } } + }, + { description: "Simple vertical wheel event by lines (1.0 - 1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: gLineHeight } } + }, + + // high resolution line scroll + { description: "High resolution horizontal wheel event by lines (0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by lines (0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by lines (0.333... - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "High resolution horizontal wheel event by lines (-0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -Math.floor(gHorizontalLine / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by lines (-0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -Math.floor(gHorizontalLine / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by lines (-0.333... - -1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -Math.floor(gHorizontalLine / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "High resolution vertical wheel event by lines (0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight / 3) } } + }, + { description: "High resolution vertical wheel event by lines (0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight / 3) } } + }, + { description: "High resolution vertical wheel event by lines (0.333... - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight / 3) } } + }, + + { description: "High resolution vertical wheel event by lines (-0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -Math.floor(gLineHeight / 3) } } + }, + { description: "High resolution vertical wheel event by lines (-0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -Math.floor(gLineHeight / 3) } } + }, + { description: "High resolution vertical wheel event by lines (-0.333... - -1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -Math.floor(gLineHeight / 3) } } + }, + + // Oblique line scroll + { description: "Oblique wheel event by lines (-1.0/2.0 - -1/2)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 2.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 2, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0, deltaY: 2.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: true, preventDefault: false, detail: 2 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -gHorizontalLine }, + vertical: { expected: true, preventDefault: false, detail: gLineHeight * 2 } } + }, + + { description: "Oblique wheel event by lines (1.0/-2.0 - 1/-2)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: -2.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: -2, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: -2.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: -2 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: false, detail: -gLineHeight * 2 } } + }, + + { description: "High resolution oblique wheel event by lines (0.5/0.333.../-0.8 - 0/0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine / 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight / 3) } } + }, + { description: "High resolution oblique wheel event by lines (0.5/0.333.../-0.8 - 1/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine / 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight / 3) } } + }, + { description: "High resolution oblique wheel event by lines (0.5/0.333.../-0.8 - 0/1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine / 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight / 3) } } + }, + + // Simple page scroll tests. + { description: "Simple horizontal wheel event by pages (1.0 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gPageWidth }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by pages (1.0 - 1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gPageWidth }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Simple horizontal wheel event by pages (-1.0 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -gPageWidth }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "Simple horizontal wheel event by pages (-1.0 - -1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -gPageWidth }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "Simple vertical wheel event by pages (-1.0 - -1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -gPageHeight } } + }, + { description: "Simple vertical wheel event by pages (-1.0 - -1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -gPageHeight } } + }, + + { description: "Simple vertical wheel event by pages (1.0 - 1) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: gPageHeight } } + }, + { description: "Simple vertical wheel event by pages (1.0 - 1) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: gPageHeight } } + }, + + // high resolution page scroll + { description: "High resolution horizontal wheel event by pages (0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by pages (0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by pages (0.333... - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "High resolution horizontal wheel event by pages (-0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -Math.floor(gPageWidth / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by pages (-0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -Math.floor(gPageWidth / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + { description: "High resolution horizontal wheel event by pages (-0.333... - -1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0 / 3, deltaY: 0.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -Math.floor(gPageWidth / 3) }, + vertical: { expected: false, preventDefault: false, detail: 0 } } + }, + + { description: "High resolution vertical wheel event by pages (0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight / 3) } } + }, + { description: "High resolution vertical wheel event by pages (0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight / 3) } } + }, + { description: "High resolution vertical wheel event by pages (0.333... - 1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: 1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight / 3) } } + }, + + { description: "High resolution vertical wheel event by pages (-0.333... - 0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -Math.floor(gPageHeight / 3) } } + }, + { description: "High resolution vertical wheel event by pages (-0.333... - 0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -Math.floor(gPageHeight / 3) } } + }, + { description: "High resolution vertical wheel event by pages (-0.333... - -1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.0, deltaY: -1.0 / 3, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -Math.floor(gPageHeight / 3) } } + }, + + // Oblique page scroll + { description: "Oblique wheel event by pages (-1.0/2.0 - -1/2)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 2.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 2, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -1.0, deltaY: 2.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: -gPageWidth }, + vertical: { expected: true, preventDefault: false, detail: gPageHeight * 2 } } + }, + + { description: "Oblique wheel event by pages (1.0/-2.0 - 1/-2)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: -2.0, deltaZ: 0.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: -2, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: -2.0, deltaZ: 0.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gPageWidth }, + vertical: { expected: true, preventDefault: false, detail: -gPageHeight * 2 } } + }, + + { description: "High resolution oblique wheel event by pages (0.5/0.333.../-0.8 - 0/0) #1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth / 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight / 3) } } + }, + { description: "High resolution oblique wheel event by pages (0.5/0.333.../-0.8 - 1/0) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth / 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight / 3) } } + }, + { description: "High resolution oblique wheel event by pages (0.5/0.333.../-0.8 - 0/1) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8, isMomentum: false, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.5, deltaY: 1.0 / 3, deltaZ: -0.8 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth / 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight / 3) } } + }, + + // preventDefault() shouldn't prevent other legacy events. + { description: "preventDefault() shouldn't prevent other legacy events (pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: true, detail: 1 }, + vertical: { expected: true, preventDefault: true, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: 16 }, + vertical: { expected: true, preventDefault: true, detail: 16 } }, + }, + { description: "preventDefault() shouldn't prevent other legacy events (line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: true, detail: 1 }, + vertical: { expected: true, preventDefault: true, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: true, detail: gLineHeight } }, + }, + { description: "preventDefault() shouldn't prevent other legacy events (page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: true, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: true, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gPageWidth }, + vertical: { expected: true, preventDefault: true, detail: gPageHeight } }, + }, + + // If wheel event is consumed by preventDefault(), legacy events are not necessary. + { description: "If wheel event is consumed by preventDefault(), legacy events are not necessary (pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: true, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + }, + { description: "If wheel event is consumed by preventDefault(), legacy events are not necessary (line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: true, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + }, + { description: "If wheel event is consumed by preventDefault(), legacy events are not necessary (page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: true, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + }, + + // modifier key state tests + { description: "modifier key tests (shift, pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: 16 }, + vertical: { expected: true, preventDefault: true, detail: 16 } }, + }, + { description: "modifier key tests (shift, line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: true, detail: gLineHeight } }, + }, + { description: "modifier key tests (shift, page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gPageWidth }, + vertical: { expected: true, preventDefault: true, detail: gPageHeight } }, + }, + + { description: "modifier key tests (ctrl, pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: 16 }, + vertical: { expected: true, preventDefault: true, detail: 16 } }, + }, + { description: "modifier key tests (ctrl, line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: true, detail: gLineHeight } }, + }, + { description: "modifier key tests (ctrl, page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gPageWidth }, + vertical: { expected: true, preventDefault: true, detail: gPageHeight } }, + }, + + { description: "modifier key tests (alt, pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: true, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: 16 }, + vertical: { expected: true, preventDefault: true, detail: 16 } }, + }, + { description: "modifier key tests (alt, line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: true, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: true, detail: gLineHeight } }, + }, + { description: "modifier key tests (alt, page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: true, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gPageWidth }, + vertical: { expected: true, preventDefault: true, detail: gPageHeight } }, + }, + + { description: "modifier key tests (meta, pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: true }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: 16 }, + vertical: { expected: true, preventDefault: true, detail: 16 } }, + }, + { description: "modifier key tests (meta, line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: true }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: true, detail: gLineHeight } }, + }, + { description: "modifier key tests (meta, page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: true, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: true, detail: gPageWidth }, + vertical: { expected: true, preventDefault: true, detail: gPageHeight } }, + }, + + // Momentum scroll should cause legacy events. + { description: "Momentum scroll should cause legacy events (pixel, not momentum)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 16 }, + vertical: { expected: true, preventDefault: false, detail: 16 } }, + }, + { description: "Momentum scroll should cause legacy events (pixel, momentum)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0, isMomentum: true, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 16.0, deltaY: 16.0, deltaZ: 16.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: 16 }, + vertical: { expected: true, preventDefault: false, detail: 16 } }, + }, + { description: "Momentum scroll should cause legacy events (line, not momentum)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: false, detail: gLineHeight } }, + }, + { description: "Momentum scroll should cause legacy events (line, momentum)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: true, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gHorizontalLine }, + vertical: { expected: true, preventDefault: false, detail: gLineHeight } }, + }, + { description: "Momentum scroll should cause legacy events (page, not momentum)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: false, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gPageWidth }, + vertical: { expected: true, preventDefault: false, detail: gPageHeight } }, + }, + { description: "Momentum scroll should cause legacy events (page, momentum)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, isMomentum: true, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: gPageWidth }, + vertical: { expected: true, preventDefault: false, detail: gPageHeight } }, + }, + + // Tests for accumulation delta when delta_multiplier_is customized. + { description: "lineOrPageDelta should be recomputed by ESM (pixel) #1", + prepare: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 200], + ["mousewheel.default.delta_multiplier_y", 300]]}, + continueTest); + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: gHorizontalLine / 4, deltaY: gLineHeight / 8, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: gHorizontalLine / 4 * 2, deltaY: gLineHeight / 8 * 3, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine / 4 * 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight / 8 * 3) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (pixel) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: gHorizontalLine / 4 + 1, deltaY: gLineHeight / 8 + 1, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: (gHorizontalLine / 4 + 1) * 2, deltaY: (gLineHeight / 8 + 1) * 3, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor((gHorizontalLine / 4 + 1) * 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor((gLineHeight / 8 + 1) * 3) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (pixel) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: gHorizontalLine / 4 + 1, deltaY: gLineHeight / 8 + 1, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: (gHorizontalLine / 4 + 1) * 2, deltaY: (gLineHeight / 8 + 1) * 3, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor((gHorizontalLine / 4 + 1) * 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor((gLineHeight / 8 + 1) * 3) } }, + finished: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100]]}, + continueTest); + }, + }, + + { description: "lineOrPageDelta should be recomputed by ESM (pixel, negative, shift) #1", + prepare: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.with_shift.delta_multiplier_x", 200], + ["mousewheel.with_shift.delta_multiplier_y", 300]]}, + continueTest); + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -gHorizontalLine / 4, deltaY: -gLineHeight / 8, deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -gHorizontalLine / 4 * 2, deltaY: -gLineHeight / 8 * 3, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(-gHorizontalLine / 4 * 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.ceil(-gLineHeight / 8 * 3) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (pixel, negative, shift) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -(gHorizontalLine / 4 + 1), deltaY: -(gLineHeight / 8 + 1), deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -(gHorizontalLine / 4 + 1) * 2, deltaY: -(gLineHeight / 8 + 1) * 3, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(-(gHorizontalLine / 4 + 1) * 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.ceil(-(gLineHeight / 8 + 1) * 3) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (pixel, negative, shift) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -(gHorizontalLine / 4 + 1), deltaY: -(gLineHeight / 8 + 1), deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -(gHorizontalLine / 4 + 1) * 2, deltaY: -(gLineHeight / 8 + 1) * 3, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: -1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(-(gHorizontalLine / 4 + 1) * 2) }, + vertical: { expected: true, preventDefault: false, detail: Math.ceil(-(gLineHeight / 8 + 1) * 3) } }, + finished: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.with_shift.delta_multiplier_x", 100], + ["mousewheel.with_shift.delta_multiplier_y", 100]]}, + continueTest); + }, + }, + + { description: "lineOrPageDelta should be recomputed by ESM (line) #1", + prepare: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 200], + ["mousewheel.default.delta_multiplier_y", 100]]}, + continueTest); + }, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.3, deltaY: 0.4, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.6, deltaY: 0.4, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine * 0.6) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight * 0.4) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (line) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.3, deltaY: 0.4, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.6, deltaY: 0.4, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: 1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine * 0.6) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight * 0.4) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (line) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.3, deltaY: 0.4, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.6, deltaY: 0.4, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gHorizontalLine * 0.6) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight * 0.4) } }, + finished: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100]]}, + continueTest); + }, + }, + + { description: "lineOrPageDelta should be recomputed by ESM (line, negative) #1", + prepare: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 200], + ["mousewheel.default.delta_multiplier_y", -100]]}, + continueTest); + }, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.3, deltaY: -0.4, deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -0.6, deltaY: 0.4, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(gHorizontalLine * -0.6) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight * 0.4) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (line, negative) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.3, deltaY: -0.4, deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -0.6, deltaY: 0.4, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: -1 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(gHorizontalLine * -0.6) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight * 0.4) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (line, negative) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.3, deltaY: -0.4, deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -0.6, deltaY: 0.4, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: 1 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(gHorizontalLine * -0.6) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gLineHeight * 0.4) } }, + finished: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100]]}, + continueTest); + }, + }, + + { description: "lineOrPageDelta should be recomputed by ESM (page) #1", + prepare: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 200]]}, + continueTest); + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.3, deltaY: 0.4, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.3, deltaY: 0.8, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth * 0.3) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight * 0.8) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (page) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.3, deltaY: 0.4, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.3, deltaY: 0.8, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth * 0.3) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight * 0.8) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (page) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.4, deltaY: 0.4, deltaZ: 0, + lineOrPageDeltaX: 3, lineOrPageDeltaY: 5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: 0.4, deltaY: 0.8, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_DOWN } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.floor(gPageWidth * 0.4) }, + vertical: { expected: true, preventDefault: false, detail: Math.floor(gPageHeight * 0.8) } }, + finished: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100]]}, + continueTest); + }, + }, + + { description: "lineOrPageDelta should be recomputed by ESM (page, negative) #1", + prepare: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 200]]}, + continueTest); + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.3, deltaY: -0.4, deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -0.3, deltaY: -0.8, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: false, preventDefault: false, detail: 0 } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(gPageWidth * -0.3) }, + vertical: { expected: true, preventDefault: false, detail: Math.ceil(gPageHeight * -0.8) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (page, negative) #2", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.3, deltaY: -0.4, deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -0.3, deltaY: -0.8, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: false, preventDefault: false, detail: 0 }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(gPageWidth * -0.3) }, + vertical: { expected: true, preventDefault: false, detail: Math.ceil(gPageHeight * -0.8) } }, + }, + { description: "lineOrPageDelta should be recomputed by ESM (page, negative) #3", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.4, deltaY: -0.4, deltaZ: 0, + lineOrPageDeltaX: -3, lineOrPageDeltaY: -5, isNoLineOrPageDelta: false, + isCustomizedByPrefs: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }, + wheel: { + expected: true, preventDefault: false, + deltaX: -0.4, deltaY: -0.8, deltaZ: 0 + }, + DOMMouseScroll: { + horizontal: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP }, + vertical: { expected: true, preventDefault: false, detail: UIEvent.SCROLL_PAGE_UP } }, + MozMousePixelScroll: { + horizontal: { expected: true, preventDefault: false, detail: Math.ceil(gPageWidth * -0.4) }, + vertical: { expected: true, preventDefault: false, detail: Math.ceil(gPageHeight * -0.8) } }, + finished: function () { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100]]}, + continueTest); + }, + }, + ]; + + var currentWheelEventTest; + var calledHandlers = { wheel: false, + DOMMouseScroll: { horizontal: false, vertical: false }, + MozMousePixelScroll: { horizontal: false, vertical: false } }; + + function wheelEventHandler(aEvent) + { + var description = "testContinuousTrustedEvents, "; + description += currentWheelEventTest.description + ": wheel event "; + + ok(!calledHandlers.wheel, + description + "was fired twice or more"); + calledHandlers.wheel = true; + + is(aEvent.target, gScrolledElement, + description + "target was invalid"); + is(aEvent.deltaMode, currentWheelEventTest.event.deltaMode, + description + "deltaMode was invalid"); + is(aEvent.deltaX, currentWheelEventTest.wheel.deltaX, + description + "deltaX was invalid"); + is(aEvent.deltaY, currentWheelEventTest.wheel.deltaY, + description + "deltaY was invalid"); + is(aEvent.deltaZ, currentWheelEventTest.wheel.deltaZ, + description + "deltaZ was invalid"); + is(aEvent.shiftKey, currentWheelEventTest.event.shiftKey, + description + "shiftKey was invalid"); + is(aEvent.ctrlKey, currentWheelEventTest.event.ctrlKey, + description + "ctrlKey was invalid"); + is(aEvent.altKey, currentWheelEventTest.event.altKey, + description + "shiftKey was invalid"); + is(aEvent.metaKey, currentWheelEventTest.event.metaKey, + description + "metaKey was invalid"); + + ok(!aEvent.defaultPrevented, + description + "defaultPrevented should be false"); + if (currentWheelEventTest.wheel.preventDefault) { + aEvent.preventDefault(); + ok(aEvent.defaultPrevented, + description + "defaultPrevented should be true"); + } + } + + function legacyEventHandler(aEvent) + { + var description = "testContinuousTrustedEvents, "; + description += currentWheelEventTest.description + ": " + aEvent.type + " event "; + + if (aEvent.axis != MouseScrollEvent.HORIZONTAL_AXIS && + aEvent.axis != MouseScrollEvent.VERTICAL_AXIS) { + ok(false, + description + "had invalid axis (" + aEvent.axis + ")"); + return; + } + + var isHorizontal = (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS); + + description += isHorizontal ? "(horizontal) " : "(vertical) "; + + var isScrollEvent = (aEvent.type == "DOMMouseScroll"); + var expectedEvent = + isScrollEvent ? currentWheelEventTest.DOMMouseScroll : + currentWheelEventTest.MozMousePixelScroll; + var expected = + isHorizontal ? expectedEvent.horizontal : expectedEvent.vertical; + + if (aEvent.type == "DOMMouseScroll") { + if (isHorizontal) { + ok(!calledHandlers.DOMMouseScroll.horizontal, + description + "was fired twice or more"); + calledHandlers.DOMMouseScroll.horizontal = true; + } else { + ok(!calledHandlers.DOMMouseScroll.vertical, + description + "was fired twice or more"); + calledHandlers.DOMMouseScroll.vertical = true; + } + } else { + if (isHorizontal) { + ok(!calledHandlers.MozMousePixelScroll.horizontal, + description + "was fired twice or more"); + calledHandlers.MozMousePixelScroll.horizontal = true; + } else { + ok(!calledHandlers.MozMousePixelScroll.vertical, + description + "was fired twice or more"); + calledHandlers.MozMousePixelScroll.vertical = true; + } + } + + is(aEvent.target, gScrolledElement, + description + "target was invalid"); + is(aEvent.detail, expected.detail, + description + "detail was invalid"); + + is(aEvent.shiftKey, currentWheelEventTest.event.shiftKey, + description + "shiftKey was invalid"); + is(aEvent.ctrlKey, currentWheelEventTest.event.ctrlKey, + description + "ctrlKey was invalid"); + is(aEvent.altKey, currentWheelEventTest.event.altKey, + description + "shiftKey was invalid"); + is(aEvent.metaKey, currentWheelEventTest.event.metaKey, + description + "metaKey was invalid"); + + var expectedDefaultPrevented = + isScrollEvent ? false : + isHorizontal ? currentWheelEventTest.DOMMouseScroll.horizontal.preventDefault : + currentWheelEventTest.DOMMouseScroll.vertical.preventDefault; + is(aEvent.defaultPrevented, expectedDefaultPrevented, + description + "defaultPrevented should be " + expectedDefaultPrevented); + + if (expected.preventDefault) { + aEvent.preventDefault(); + ok(aEvent.defaultPrevented, + description + "defaultPrevented should be true"); + } + } + + window.addEventListener("wheel", wheelEventHandler, true); + window.addEventListener("DOMMouseScroll", legacyEventHandler, true); + window.addEventListener("MozMousePixelScroll", legacyEventHandler, true); + + for (var i = 0; i < kSynthesizedWheelEventTests.length; i++) { + gScrollableElement.scrollTop = gScrollableElement.scrollBottom = 1000; + + currentWheelEventTest = kSynthesizedWheelEventTests[i]; + + if (currentWheelEventTest.prepare) { + yield currentWheelEventTest.prepare(); + } + + yield sendWheelAndWait(10, 10, currentWheelEventTest.event); + + if (currentWheelEventTest.finished) { + yield currentWheelEventTest.finished(); + } + + var description = "testContinuousTrustedEvents, " + + currentWheelEventTest.description + ": "; + is(calledHandlers.wheel, currentWheelEventTest.wheel.expected, + description + "wheel event was fired or not fired"); + is(calledHandlers.DOMMouseScroll.horizontal, + currentWheelEventTest.DOMMouseScroll.horizontal.expected, + description + "horizontal DOMMouseScroll event was fired or not fired"); + is(calledHandlers.DOMMouseScroll.vertical, + currentWheelEventTest.DOMMouseScroll.vertical.expected, + description + "vertical DOMMouseScroll event was fired or not fired"); + is(calledHandlers.MozMousePixelScroll.horizontal, + currentWheelEventTest.MozMousePixelScroll.horizontal.expected, + description + "horizontal MozMousePixelScroll event was fired or not fired"); + is(calledHandlers.MozMousePixelScroll.vertical, + currentWheelEventTest.MozMousePixelScroll.vertical.expected, + description + "vertical MozMousePixelScroll event was fired or not fired"); + + calledHandlers = { wheel: false, + DOMMouseScroll: { horizontal: false, vertical: false }, + MozMousePixelScroll: { horizontal: false, vertical: false } }; + } + + window.removeEventListener("wheel", wheelEventHandler, true); + window.removeEventListener("DOMMouseScroll", legacyEventHandler, true); + window.removeEventListener("MozMousePixelScroll", legacyEventHandler, true); +} + +var gTestContinuation = null; + +function continueTest() +{ + if (!gTestContinuation) { + gTestContinuation = testBody(); + } + var ret = gTestContinuation.next(); + if (ret.done) { + SimpleTest.finish(); + } +} + +function* testBody() +{ + yield* prepareScrollUnits(); + yield* testContinuousTrustedEvents(); +} + +function runTests() +{ + SpecialPowers.pushPrefEnv({"set": [ + ["mousewheel.transaction.timeout", 100000], + ["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100], + ["mousewheel.default.delta_multiplier_z", 100], + ["mousewheel.with_alt.delta_multiplier_x", 100], + ["mousewheel.with_alt.delta_multiplier_y", 100], + ["mousewheel.with_alt.delta_multiplier_z", 100], + ["mousewheel.with_control.delta_multiplier_x", 100], + ["mousewheel.with_control.delta_multiplier_y", 100], + ["mousewheel.with_control.delta_multiplier_z", 100], + ["mousewheel.with_meta.delta_multiplier_x", 100], + ["mousewheel.with_meta.delta_multiplier_y", 100], + ["mousewheel.with_meta.delta_multiplier_z", 100], + ["mousewheel.with_shift.delta_multiplier_x", 100], + ["mousewheel.with_shift.delta_multiplier_y", 100], + ["mousewheel.with_shift.delta_multiplier_z", 100], + ["mousewheel.with_win.delta_multiplier_x", 100], + ["mousewheel.with_win.delta_multiplier_y", 100], + ["mousewheel.with_win.delta_multiplier_z", 100] + ]}, continueTest); +} +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_dblclick_explicit_original_target.html b/dom/events/test/test_dblclick_explicit_original_target.html new file mode 100644 index 000000000..8aa5f5d4c --- /dev/null +++ b/dom/events/test/test_dblclick_explicit_original_target.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test explicit original target of dblclick event</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"/> +</head> +<body> +<p id="display">Test explicit original target of dblclick event</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); + +function runTests() +{ + synthesizeMouse(document.getElementById("display"), 5, 5, { clickCount: 2 }); +} + +window.ondblclick = function(event) { + is(event.explicitOriginalTarget.nodeType, Node.TEXT_NODE, "explicitOriginalTarget is a text node"); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_dom_activate_event.html b/dom/events/test/test_dom_activate_event.html new file mode 100644 index 000000000..22e32d988 --- /dev/null +++ b/dom/events/test/test_dom_activate_event.html @@ -0,0 +1,89 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DOMActivate event</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"/> +</head> +<body> +<p id="display"> +<a id="a" href="#dummy">link</a> +<button id="button">button</button> +<input id="checkbox" type="checkbox"> +<input id="radio" type="radio"> +<input id="submit" type="submit"> +<input id="ibutton" type="button"> +<input id="reset" type="reset"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); + +function runIsTrustedTestCausedByTrustedClick(aElement, aNextTest) +{ + const kDescription = "runIsTrustedTestCausedByTrustedClick(aElement.id=" + aElement.id + "): "; + var DOMActivateFired = false; + aElement.addEventListener("DOMActivate", function (aEvent) { + DOMActivateFired = true; + ok(aEvent.isTrusted, kDescription + "DOMActivate event should be trusted event"); + aElement.removeEventListener("DOMActivate", arguments.callee); + aNextTest(); + }); + aElement.addEventListener("click", function (aEvent) { + ok(aEvent.isTrusted, kDescription + "click event should be trusted event"); + aElement.removeEventListener("click", arguments.callee); + }); + synthesizeMouseAtCenter(aElement, {}); +} + +function runIsTrustedTestCausedByUntrustedClick(aElement, aNextTest) +{ + const kDescription = "runIsTrustedTestCausedByUntrustedClick(aElement.id=" + aElement.id + "): "; + var DOMActivateFired = false; + aElement.addEventListener("DOMActivate", function (aEvent) { + DOMActivateFired = true; + ok(aEvent.isTrusted, + kDescription + "DOMActivate event should be trusted event even if it's caused by untrusted event"); + aElement.removeEventListener("DOMActivate", arguments.callee); + aNextTest(); + }); + aElement.addEventListener("click", function (aEvent) { + ok(!aEvent.isTrusted, kDescription + "click event should be untrusted event"); + aElement.removeEventListener("click", arguments.callee); + }); + var click = new MouseEvent("click", { button: 0 }); + aElement.dispatchEvent(click); +} + +function runTests() +{ + // XXX Don't add indentation here. If you add indentation, the diff will be + // complicated when somebody adds new tests. + runIsTrustedTestCausedByTrustedClick(document.getElementById("a"), function () { + runIsTrustedTestCausedByTrustedClick(document.getElementById("button"), function () { + runIsTrustedTestCausedByTrustedClick(document.getElementById("checkbox"), function () { + runIsTrustedTestCausedByTrustedClick(document.getElementById("radio"), function () { + runIsTrustedTestCausedByTrustedClick(document.getElementById("submit"), function () { + runIsTrustedTestCausedByTrustedClick(document.getElementById("ibutton"), function () { + runIsTrustedTestCausedByTrustedClick(document.getElementById("reset"), function () { + runIsTrustedTestCausedByUntrustedClick(document.getElementById("a"), function () { + runIsTrustedTestCausedByUntrustedClick(document.getElementById("button"), function () { + runIsTrustedTestCausedByUntrustedClick(document.getElementById("checkbox"), function () { + runIsTrustedTestCausedByUntrustedClick(document.getElementById("radio"), function () { + runIsTrustedTestCausedByUntrustedClick(document.getElementById("submit"), function () { + runIsTrustedTestCausedByUntrustedClick(document.getElementById("ibutton"), function () { + runIsTrustedTestCausedByUntrustedClick(document.getElementById("reset"), function () { + SimpleTest.finish(); + });});});});});});});});});});});});});}); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_dom_before_after_keyboard_event.html b/dom/events/test/test_dom_before_after_keyboard_event.html new file mode 100644 index 000000000..533a7e73a --- /dev/null +++ b/dom/events/test/test_dom_before_after_keyboard_event.html @@ -0,0 +1,136 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 989198</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/NativeKeyCodes.js"></script> + <script type="text/javascript" src="bug989198_helper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="runTests();"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=989198">Mozilla Bug 989198</a> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function cleanupTest() +{ + teardownHandlers(window, embedderHandler); + runTests(); +} + +function testEventOrderAndAttr() +{ + const kTests = [ + { + description: "Testing the order of the events", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kUnknownEvent + }, + { + description: "Testing the order of the events, calling preventDefault() at \"mozbrowserbeforekeydown\" event", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kParent | kBeforeEvent | kKeyDownEvent + }, + { + description: "Testing the order of the events, calling preventDefault() at \"mozbrowserbeforekeyup\" event", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kParent | kBeforeEvent | kKeyUpEvent + }, + { + description: "Testing the order of the events, calling preventDefault() at \"keydown\" event", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kChild | kKeyDownEvent + }, + { + description: "Testing the order of the events, calling preventDefault() at \"keyup\" event", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kChild | kKeyUpEvent + } + ]; + + for (var k = 0; k < kTests.length; k++ ) { + gCurrentTest = kTests[k]; + synthesizeKey('a', {}, document.getElementById("embedded").contentWindow); + classifyEvents(kTests[k]); + verifyResults(kTests[k]); + } + + runTests(); +} + +var tests = [ + function addPermissions() { + SpecialPowers.pushPermissions( + [{ type: "before-after-keyboard-event", allow: true, context: document }, + { type: "browser", allow: true, context: document }], + runTests); + }, + function addPreferences() { + SpecialPowers.pushPrefEnv( + { "set": [["dom.beforeAfterKeyboardEvent.enabled", true], + ["dom.mozBrowserFramesEnabled", true], + ["network.disable.ipc.security", true], + ["dom.ipc.tabs.disabled", false]] }, + runTests); + }, + + // Tests for in-process iframe, i.e. <iframe mozbrowser>. + function () { + prepareTest(false); + }, + testEventOrderAndAttr, + cleanupTest, +]; + +function runTests() +{ + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +</script> +</body> +</html> diff --git a/dom/events/test/test_dom_before_after_keyboard_event_remote.html b/dom/events/test/test_dom_before_after_keyboard_event_remote.html new file mode 100644 index 000000000..b82b102a9 --- /dev/null +++ b/dom/events/test/test_dom_before_after_keyboard_event_remote.html @@ -0,0 +1,195 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 989198</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/NativeKeyCodes.js"></script> + <script type="text/javascript" src="bug989198_helper.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="runTests();"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=989198">Mozilla Bug 989198</a> +<p id="display"></p> +<div id="content"> +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var testsForEventOrder = [ + { + description: "Testing the order of the events (OOP)", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kUnknownEvent + }, + { + description: "Testing the order of the events (OOP), calling " + + "preventDefault() at \"mozbrowserbeforekeydown\" event", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kParent | kBeforeEvent | kKeyDownEvent + }, + { + description: "Testing the order of the events (OOP), calling " + + "preventDefault() at \"mozbrowserbeforekeyup\" event", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kParent | kBeforeEvent | kKeyUpEvent + }, + { + description: "Testing the order of the events (OOP), calling " + + "preventDefault() at \"keydown\" event in parent process", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kParent | kKeyDownEvent + }, + { + description: "Testing the order of the events (OOP), calling " + + "preventDefault() at \"keydown\" event in child process", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kChild | kKeyDownEvent + }, + { + description: "Testing the order of the events (OOP), calling " + + "preventDefault() at \"keyup\" event in parent process", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kParent | kKeyUpEvent + }, + { + description: "Testing the order of the events (OOP), calling " + + "preventDefault() at \"keyup\" event in child process", + expectedEvents: [ [ kParent | kBeforeEvent | kKeyDownEvent, + kParent | kKeyDownEvent, + kChild | kKeyDownEvent, + kParent | kAfterEvent | kKeyDownEvent ], + [ kParent | kBeforeEvent | kKeyUpEvent, + kParent | kKeyUpEvent, + kChild | kKeyUpEvent, + kParent | kAfterEvent | kKeyUpEvent ] ], + resultEvents: [], + classifiedEvents: [ [], [] ], + doPreventDefaultAt: kChild | kKeyUpEvent + } +]; + +function cleanupTest() +{ + teardownHandlers(window, embedderHandler); + runTests(); +} + +function testEventOrder() +{ + if (!testsForEventOrder.length) { + runTests(); + return; + } + gCurrentTest = testsForEventOrder.shift(); + + synthesizeKey('a', {}, document.getElementById("embedded").contentWindow); + // It take some time to propagate events to a remote iframe. + + waitAndVerifyResult(0); +} + +function waitAndVerifyResult(aCount) { + expectedEventLength = gCurrentTest.expectedEvents[0].length + + gCurrentTest.expectedEvents[1].length; + if (gCurrentTest.resultEvents.length >= expectedEventLength || aCount > 10) { + classifyEvents(gCurrentTest); + verifyResults(gCurrentTest); + testEventOrder(); + } + else { + setTimeout(() => waitAndVerifyResult(aCount + 1), + 100); + } +} + +var tests = [ + function addPermissions() { + SpecialPowers.pushPermissions( + [{ type: "before-after-keyboard-event", allow: true, context: document }, + { type: "browser", allow: true, context: document }], + runTests); + }, + function addPreferences() { + SpecialPowers.pushPrefEnv( + { "set": [["dom.beforeAfterKeyboardEvent.enabled", true], + ["dom.mozBrowserFramesEnabled", true], + ["network.disable.ipc.security", true], + ["dom.ipc.tabs.disabled", false]] }, + runTests); + }, + + // Tests for out-of-process iframe, i.el. <iframe mozbrowser remote> + function () { + prepareTest(true); + }, + testEventOrder, + cleanupTest +]; + +function runTests() +{ + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +</script> +</body> +</html> diff --git a/dom/events/test/test_dom_keyboard_event.html b/dom/events/test/test_dom_keyboard_event.html new file mode 100644 index 000000000..e85065904 --- /dev/null +++ b/dom/events/test/test_dom_keyboard_event.html @@ -0,0 +1,306 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM KeyboardEvent</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"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +var gCodeEnabled = SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled"); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests, window); + +function testInitializingUntrustedEvent() +{ + const kTests = [ + { createEventArg: "KeyboardEvent", + type: "keydown", bubbles: true, cancelable: true, view: null, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, + keyCode: 0x00, charCode: 0x00 }, + + { createEventArg: "keyboardevent", + type: "keyup", bubbles: false, cancelable: true, view: window, + ctrlKey: true, altKey: false, shiftKey: false, metaKey: false, + keyCode: 0x10, charCode: 0x00 }, + + { createEventArg: "Keyboardevent", + type: "keypess", bubbles: true, cancelable: false, view: null, + ctrlKey: false, altKey: true, shiftKey: false, metaKey: false, + keyCode: 0x11, charCode: 0x30 }, + + { createEventArg: "keyboardEvent", + type: "boo", bubbles: false, cancelable: false, view: window, + ctrlKey: false, altKey: false, shiftKey: true, metaKey: false, + keyCode: 0x30, charCode: 0x40 }, + + { createEventArg: "KeyBoardEvent", + type: "foo", bubbles: true, cancelable: true, view: null, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: true, + keyCode: 0x00, charCode: 0x50 }, + + { createEventArg: "keyboardevEnt", + type: "bar", bubbles: false, cancelable: true, view: window, + ctrlKey: true, altKey: true, shiftKey: false, metaKey: false, + keyCode: 0x00, charCode: 0x60 }, + + { createEventArg: "KeyboaRdevent", + type: "keydown", bubbles: true, cancelable: false, view: null, + ctrlKey: false, altKey: true, shiftKey: false, metaKey: true, + keyCode: 0x30, charCode: 0x00 }, + + { createEventArg: "KEYBOARDEVENT", + type: "keyup", bubbles: false, cancelable: false, view: window, + ctrlKey: true, altKey: false, shiftKey: true, metaKey: false, + keyCode: 0x10, charCode: 0x80 }, + + { createEventArg: "KeyboardEvent", + type: "keypress", bubbles: false, cancelable: false, view: window, + ctrlKey: true, altKey: false, shiftKey: true, metaKey: true, + keyCode: 0x10, charCode: 0x80 }, + + { createEventArg: "KeyboardEvent", + type: "foo", bubbles: false, cancelable: false, view: window, + ctrlKey: true, altKey: true, shiftKey: true, metaKey: true, + keyCode: 0x10, charCode: 0x80 }, + ]; + + const kOtherModifierName = [ + "CapsLock", "NumLock", "ScrollLock", "Symbol", "SymbolLock", "Fn", "FnLock", "OS", "AltGraph" + ]; + + const kInvalidModifierName = [ + "shift", "control", "alt", "meta", "capslock", "numlock", "scrolllock", + "symbollock", "fn", "os", "altgraph", "Invalid", "Shift Control", + "Win", "Scroll" + ]; + + for (var i = 0; i < kTests.length; i++) { + var description = "testInitializingUntrustedEvent, Index: " + i + ", "; + const kTest = kTests[i]; + var e = document.createEvent(kTest.createEventArg); + e.initKeyEvent(kTest.type, kTest.bubbles, kTest.cancelable, kTest.view, + kTest.ctrlKey, kTest.altKey, kTest.shiftKey, kTest.metaKey, + kTest.keyCode, kTest.charCode); + is(e.toString(), "[object KeyboardEvent]", + description + 'class string should be "KeyboardEvent"'); + + for (var attr in kTest) { + if (attr == "createEventArg") { + continue; + } + if (attr == "keyCode") { + // If this is keydown, keyup of keypress event, keycod must be correct. + if (kTest.type == "keydown" || kTest.type == "keyup" || kTest.type == "keypress") { + is(e[attr], kTest[attr], description + attr + " returns wrong value"); + // Otherwise, should be always zero (why?) + } else { + is(e[attr], 0, description + attr + " returns non-zero for invalid event"); + } + } else if (attr == "charCode") { + // If this is keydown or keyup event, charCode always 0. + if (kTest.type == "keydown" || kTest.type == "keyup") { + is(e[attr], 0, description + attr + " returns non-zero for keydown or keyup event"); + // If this is keypress event, charCode must be correct. + } else if (kTest.type == "keypress") { + is(e[attr], kTest[attr], description + attr + " returns wrong value"); + // Otherwise, we have a bug. + } else { + if (e[attr] != kTest[attr]) { // avoid random unexpected pass. + todo_is(e[attr], kTest[attr], description + attr + " returns wrong value"); + } + } + } else { + is(e[attr], kTest[attr], description + attr + " returns wrong value"); + } + } + is(e.isTrusted, false, description + "isTrusted returns wrong value"); + + // key and code values cannot be initialized with initKeyEvent(). + is(e.key, "", description + "key must return empty string"); + if (gCodeEnabled) { + is(e.code, "", description + "code must be empty string"); + } + + // getModifierState() tests + is(e.getModifierState("Shift"), kTest.shiftKey, + description + "getModifierState(\"Shift\") returns wrong value"); + is(e.getModifierState("Control"), kTest.ctrlKey, + description + "getModifierState(\"Control\") returns wrong value"); + is(e.getModifierState("Alt"), kTest.altKey, + description + "getModifierState(\"Alt\") returns wrong value"); + is(e.getModifierState("Meta"), kTest.metaKey, + description + "getModifierState(\"Meta\") returns wrong value"); + + for (var j = 0; j < kOtherModifierName.length; j++) { + ok(!e.getModifierState(kOtherModifierName[j]), + description + "getModifierState(\"" + kOtherModifierName[j] + "\") returns wrong value"); + } + for (var k = 0; k < kInvalidModifierName.length; k++) { + ok(!e.getModifierState(kInvalidModifierName[k]), + description + "getModifierState(\"" + kInvalidModifierName[k] + "\") returns wrong value"); + } + } +} + +function testSynthesizedKeyLocation() +{ + const kTests = [ + { key: "a", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + }, + { key: "VK_SHIFT", isModifier: true, + event: { shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + }, + { key: "VK_SHIFT", isModifier: true, + event: { shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + }, + { key: "VK_CONTROL", isModifier: true, + event: { shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + }, + { key: "VK_CONTROL", isModifier: true, + event: { shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + }, +/* XXX Alt key activates menubar even if we consume the key events. + { key: "VK_ALT", isModifier: true, + event: { shiftKey: false, ctrlKey: false, altKey: true, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + }, + { key: "VK_ALT", isModifier: true, + event: { shiftKey: false, ctrlKey: false, altKey: true, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + }, +*/ + { key: "VK_META", isModifier: true, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: true, + location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + }, + { key: "VK_META", isModifier: true, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: true, + location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + }, + { key: "VK_DOWN", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + }, + { key: "VK_DOWN", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + }, + { key: "5", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + }, + { key: "VK_NUMPAD5", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + }, + { key: "+", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + }, + { key: "VK_ADD", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + }, + { key: "VK_RETURN", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + }, + { key: "VK_RETURN", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + }, + { key: "VK_NUM_LOCK", isModifier: true, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + }, + { key: "VK_INSERT", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + }, + { key: "VK_INSERT", isModifier: false, + event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + }, + ]; + + function getLocationName(aLocation) + { + switch (aLocation) { + case KeyboardEvent.DOM_KEY_LOCATION_STANDARD: + return "DOM_KEY_LOCATION_STANDARD"; + case KeyboardEvent.DOM_KEY_LOCATION_LEFT: + return "DOM_KEY_LOCATION_LEFT"; + case KeyboardEvent.DOM_KEY_LOCATION_RIGHT: + return "DOM_KEY_LOCATION_RIGHT"; + case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD: + return "DOM_KEY_LOCATION_NUMPAD"; + default: + return "Invalid value (" + aLocation + ")"; + } + } + + var currentTest, description; + var events = { keydown: false, keypress: false, keyup: false }; + + function handler(aEvent) + { + is(aEvent.location, currentTest.event.location, + description + "location of " + aEvent.type + " was invalid"); + events[aEvent.type] = true; + if (aEvent.type != "keydown" || + (currentTest.event.isModifier && aEvent.type == "keydown")) { + aEvent.preventDefault(); + } + } + + window.addEventListener("keydown", handler, true); + window.addEventListener("keypress", handler, true); + window.addEventListener("keyup", handler, true); + + for (var i = 0; i < kTests.length; i++) { + currentTest = kTests[i]; + events = { keydown: false, keypress: false, keyup: false }; + description = "testSynthesizedKeyLocation, " + i + ", key: " + + currentTest.key + ", location: " + + getLocationName(currentTest.event.location) + ": "; + synthesizeKey(currentTest.key, currentTest.event); + ok(events.keydown, description + "keydown event wasn't fired"); + if (currentTest.isModifier) { + todo(events.keypress, description + "keypress event was fired for modifier key"); + } else { + ok(events.keypress, description + "keypress event wasn't fired"); + } + ok(events.keyup, description + "keyup event wasn't fired"); + } + + window.removeEventListener("keydown", handler, true); + window.removeEventListener("keypress", handler, true); + window.removeEventListener("keyup", handler, true); +} + +function runTests() +{ + testInitializingUntrustedEvent(); + testSynthesizedKeyLocation(); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_dom_mouse_event.html b/dom/events/test/test_dom_mouse_event.html new file mode 100644 index 000000000..d1433eb35 --- /dev/null +++ b/dom/events/test/test_dom_mouse_event.html @@ -0,0 +1,143 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM MouseEvent</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests, window); + +function testInitializingUntrustedEvent() +{ + const kTests = [ + { createEventArg: "MouseEvent", + type: "mousedown", bubbles: true, cancelable: true, view: null, detail: 1, + screenX: 0, screenY: 0, clientX: 0, clientY: 0, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, + button: 6, relatedTarget: null }, + + { createEventArg: "mouseevent", + type: "mouseup", bubbles: false, cancelable: true, view: window, detail: 2, + screenX: 0, screenY: 0, clientX: 0, clientY: 400, + ctrlKey: true, altKey: false, shiftKey: false, metaKey: false, + button: 1, relatedTarget: document.getElementById("content") }, + + { createEventArg: "Mouseevent", + type: "click", bubbles: true, cancelable: false, view: null, detail: -5, + screenX: 0, screenY: 0, clientX: 300, clientY: 0, + ctrlKey: false, altKey: true, shiftKey: false, metaKey: false, + button: 2, relatedTarget: document.getElementById("test") }, + + { createEventArg: "mouseEvent", + type: "dblclick", bubbles: false, cancelable: false, view: window, detail: -1, + screenX: 0, screenY: 200, clientX: 0, clientY: 0, + ctrlKey: false, altKey: false, shiftKey: true, metaKey: false, + button: 12, relatedTarget: document.getElementById("content") }, + + { createEventArg: "MouseEvents", + type: "mouseenter", bubbles: true, cancelable: true, view: null, detail: 111000, + screenX: 100, screenY: 0, clientX: 0, clientY: 0, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: true, + button: 2, relatedTarget: document.getElementById("test") }, + + { createEventArg: "mouseevents", + type: "mouseleave", bubbles: false, cancelable: true, view: window, detail: 500, + screenX: 100, screenY: 500, clientX: 0, clientY: 0, + ctrlKey: true, altKey: true, shiftKey: false, metaKey: false, + button: 8, relatedTarget: document.getElementById("content") }, + + { createEventArg: "Mouseevents", + type: "mouseover", bubbles: true, cancelable: false, view: null, detail: 3, + screenX: 0, screenY: 0, clientX: 200, clientY: 300, + ctrlKey: false, altKey: true, shiftKey: false, metaKey: true, + button: 7, relatedTarget: document.getElementById("test") }, + + { createEventArg: "mouseEvents", + type: "mouseout", bubbles: false, cancelable: false, view: window, detail: 5, + screenX: -100, screenY: 300, clientX: 600, clientY: -500, + ctrlKey: true, altKey: false, shiftKey: true, metaKey: false, + button: 8, relatedTarget: document.getElementById("content") }, + + { createEventArg: "MouseEvent", + type: "mousemove", bubbles: false, cancelable: false, view: window, detail: 30, + screenX: 500, screenY: -100, clientX: -8888, clientY: -5000, + ctrlKey: true, altKey: false, shiftKey: true, metaKey: true, + button: 8, relatedTarget: document.getElementById("test") }, + + { createEventArg: "MouseEvent", + type: "foo", bubbles: false, cancelable: false, view: window, detail: 100, + screenX: 2000, screenY: 6000, clientX: 5000, clientY: 3000, + ctrlKey: true, altKey: true, shiftKey: true, metaKey: true, + button: 8, relatedTarget: document.getElementById("test") }, + ]; + + const kOtherModifierName = [ + "CapsLock", "NumLock", "ScrollLock", "Symbol", "SymbolLock", "Fn", "FnLock", "OS", "AltGraph" + ]; + + const kInvalidModifierName = [ + "shift", "control", "alt", "meta", "capslock", "numlock", "scrolllock", + "symbollock", "fn", "os", "altgraph", "Invalid", "Shift Control", + "Win", "Scroll" + ]; + + for (var i = 0; i < kTests.length; i++) { + var description = "testInitializingUntrustedEvent, Index: " + i + ", "; + const kTest = kTests[i]; + var e = document.createEvent(kTest.createEventArg); + e.initMouseEvent(kTest.type, kTest.bubbles, kTest.cancelable, kTest.view, + kTest.detail, kTest.screenX, kTest.screenY, kTest.clientX, kTest.clientY, + kTest.ctrlKey, kTest.altKey, kTest.shiftKey, kTest.metaKey, + kTest.button, kTest.relatedTarget); + + for (var attr in kTest) { + if (attr == "createEventArg") { + continue; + } + is(e[attr], kTest[attr], description + attr + " returns wrong value"); + } + is(e.isTrusted, false, description + "isTrusted returns wrong value"); + is(e.buttons, 0, description + "buttons returns wrong value"); + is(e.movementX, 0, description + "movementX returns wrong value"); + is(e.movementY, 0, description + "movementY returns wrong value"); + + // getModifierState() tests + is(e.getModifierState("Shift"), kTest.shiftKey, + description + "getModifierState(\"Shift\") returns wrong value"); + is(e.getModifierState("Control"), kTest.ctrlKey, + description + "getModifierState(\"Control\") returns wrong value"); + is(e.getModifierState("Alt"), kTest.altKey, + description + "getModifierState(\"Alt\") returns wrong value"); + is(e.getModifierState("Meta"), kTest.metaKey, + description + "getModifierState(\"Meta\") returns wrong value"); + + for (var j = 0; j < kOtherModifierName.length; j++) { + ok(!e.getModifierState(kOtherModifierName[j]), + description + "getModifierState(\"" + kOtherModifierName[j] + "\") returns wrong value"); + } + for (var k = 0; k < kInvalidModifierName.length; k++) { + ok(!e.getModifierState(kInvalidModifierName[k]), + description + "getModifierState(\"" + kInvalidModifierName[k] + "\") returns wrong value"); + } + } +} + +function runTests() +{ + testInitializingUntrustedEvent(); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_dom_storage_event.html b/dom/events/test/test_dom_storage_event.html new file mode 100644 index 000000000..4ac967dfe --- /dev/null +++ b/dom/events/test/test_dom_storage_event.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM StorageEvent</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +const kTests = [ + { createEventArg: "StorageEvent", + type: "aaa", bubbles: true, cancelable: true, + key: null, oldValue: 'a', newValue: 'b', url: 'c', storageArea: null }, + + { createEventArg: "storageevent", + type: "bbb", bubbles: false, cancelable: true, + key: 'key', oldValue: null, newValue: 'b', url: 'c', storageArea: null }, + + { createEventArg: "Storageevent", + type: "ccc", bubbles: true, cancelable: false, + key: 'key', oldValue: 'a', newValue: null, url: 'c', storageArea: null }, + + { createEventArg: "storageEvent", + type: "ddd", bubbles: false, cancelable: false, + key: 'key', oldValue: 'a', newValue: 'b', url: null, storageArea: null }, + + { createEventArg: "StorageEvent", + type: "eee", bubbles: true, cancelable: true, + key: 'key', oldValue: 'a', newValue: 'b', url: 'c', storageArea: null }, + + { createEventArg: "storageevent", + type: "fff", bubbles: false, cancelable: true, + key: null, oldValue: null, newValue: null, url: null, storageArea: null }, + ]; + +for (var i = 0; i < kTests.length; i++) { + var description = "test, Index: " + i + ", "; + const kTest = kTests[i]; + var e = document.createEvent(kTest.createEventArg); + e.initStorageEvent(kTest.type, kTest.bubbles, kTest.cancelable, + kTest.key, kTest.oldValue, kTest.newValue, kTest.url, + kTest.storageArea); + + for (var attr in kTest) { + if (attr == 'createEventArg') + continue; + + is(e[attr], kTest[attr], description + attr + " returns wrong value"); + } + is(e.isTrusted, false, description + "isTrusted returns wrong value"); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_dom_wheel_event.html b/dom/events/test/test_dom_wheel_event.html new file mode 100644 index 000000000..043eaa06c --- /dev/null +++ b/dom/events/test/test_dom_wheel_event.html @@ -0,0 +1,811 @@ +<!DOCTYPE HTML> +<html style="font-size: 32px;"> +<head> + <title>Test for D3E WheelEvent</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="scrollable" style="font-family: monospace; font-size: 16px; line-height: 1; overflow: auto; width: 200px; height: 200px;"> + <div id="scrolled" style="font-size: 64px; width: 5000px; height: 5000px;"> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + </div> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTest, window); + +var gScrollableElement = document.getElementById("scrollable"); +var gScrolledElement = document.getElementById("scrolled"); + +var gLineHeight = 0; +var gHorizontalLine = 0; +var gPageHeight = 0; +var gPageWidth = 0; + +function sendWheelAndWait(aX, aY, aEvent) +{ + sendWheelAndPaint(gScrollableElement, aX, aY, aEvent, continueTest); +} + +function* prepareScrollUnits() +{ + var result = -1; + function handler(aEvent) + { + result = aEvent.detail; + aEvent.preventDefault(); + } + window.addEventListener("MozMousePixelScroll", handler, true); + + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gLineHeight = result; + ok(gLineHeight > 10 && gLineHeight < 25, "prepareScrollUnits: gLineHeight may be illegal value, got " + gLineHeight); + + result = -1; + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gHorizontalLine = result; + ok(gHorizontalLine > 5 && gHorizontalLine < 16, "prepareScrollUnits: gHorizontalLine may be illegal value, got " + gHorizontalLine); + + result = -1; + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gPageHeight = result; + // XXX Cannot we know the actual scroll port size? + ok(gPageHeight >= 150 && gPageHeight <= 200, + "prepareScrollUnits: gPageHeight is strange value, got " + gPageHeight); + + result = -1; + yield sendWheelAndWait(10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gPageWidth = result; + ok(gPageWidth >= 150 && gPageWidth <= 200, + "prepareScrollUnits: gPageWidth is strange value, got " + gPageWidth); + + window.removeEventListener("MozMousePixelScroll", handler, true); +} + +function testMakingUntrustedEvent() +{ + const kCreateEventArgs = [ + "WheelEvent", "wheelevent", "wheelEvent", "Wheelevent" + ]; + + for (var i = 0; i < kCreateEventArgs.length; i++) { + try { + // We never support WheelEvent construction with document.createEvent(). + var event = document.createEvent(kCreateEventArgs[i]); + ok(false, "document.createEvent(" + kCreateEventArgs[i] + ") should throw an error"); + } catch (e) { + ok(true, "document.createEvent(" + kCreateEventArgs[i] + ") threw an error"); + } + } + + var wheelEvent = new WheelEvent("wheel"); + ok(wheelEvent instanceof WheelEvent, + "new WheelEvent() should create an instance of WheelEvent"); + ok(typeof(wheelEvent.initWheelEvent) != "function", + "WheelEvent must not have initWheelEvent()"); +} + +// delta_multiplier prefs should cause changing delta values of trusted events only. +// And also legacy events' detail value should be changed too. +function* testDeltaMultiplierPrefs() +{ + const kModifierAlt = 0x01; + const kModifierControl = 0x02; + const kModifierMeta = 0x04; + const kModifierShift = 0x08; + const kModifierWin = 0x10; + + const kTests = [ + { name: "default", + expected: [ 0, kModifierShift | kModifierAlt, kModifierShift | kModifierControl, + kModifierShift | kModifierMeta, kModifierShift | kModifierWin, + kModifierControl | kModifierAlt, kModifierMeta | kModifierAlt ], + unexpected: [ kModifierAlt, kModifierControl, kModifierMeta, kModifierShift, kModifierWin ] }, + { name: "with_alt", + expected: [ kModifierAlt ], + unexpected: [0, kModifierControl, kModifierMeta, kModifierShift, kModifierWin, + kModifierShift | kModifierAlt, kModifierControl | kModifierAlt, + kModifierMeta | kModifierAlt ] }, + { name: "with_control", + expected: [ kModifierControl ], + unexpected: [0, kModifierAlt, kModifierMeta, kModifierShift, kModifierWin, + kModifierShift | kModifierControl, kModifierControl | kModifierAlt, + kModifierMeta | kModifierControl ] }, + { name: "with_meta", + expected: [ kModifierMeta ], + unexpected: [0, kModifierAlt, kModifierControl, kModifierShift, kModifierWin, + kModifierShift | kModifierMeta, kModifierControl | kModifierMeta, + kModifierMeta | kModifierAlt ] }, + { name: "with_shift", + expected: [ kModifierShift ], + unexpected: [0, kModifierAlt, kModifierControl, kModifierMeta, kModifierWin, + kModifierShift | kModifierAlt, kModifierControl | kModifierShift, + kModifierMeta | kModifierShift ] }, + { name: "with_win", + expected: [ kModifierWin ], + unexpected: [0, kModifierAlt, kModifierControl, kModifierMeta, kModifierShift, + kModifierShift | kModifierWin ] }, + ]; + + // Note that this test doesn't support complicated lineOrPageDelta values which are computed with + // accumulated delta values by the prefs. If you need to test the lineOrPageDelta accumulation, + // use test_continuous_dom_wheel_event.html. + const kEvents = [ + { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: gHorizontalLine, deltaY: gLineHeight, deltaZ: gLineHeight, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -gHorizontalLine, deltaY: -gLineHeight, deltaZ: -gLineHeight, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, deltaZ: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, deltaZ: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + ]; + + const kDeltaMultiplierPrefs = [ + "delta_multiplier_x", "delta_multiplier_y", "delta_multiplier_z" + ]; + + const kPrefValues = [ + 200, 50, 0, -50, -150 + ]; + + var currentTest, currentModifiers, currentEvent, currentPref, currentMultiplier, testingExpected; + var expectedAsyncHandlerCalls; + var description; + var calledHandlers = { wheel: false, + DOMMouseScroll: { horizontal: false, vertical: false }, + MozMousePixelScroll: { horizontal: false, vertical: false } }; + + function wheelEventHandler(aEvent) { + calledHandlers.wheel = true; + + var expectedDeltaX = currentEvent.deltaX; + var expectedDeltaY = currentEvent.deltaY; + var expectedDeltaZ = currentEvent.deltaZ; + + if (testingExpected) { + switch (currentPref.charAt(currentPref.length - 1)) { + case "x": + expectedDeltaX *= currentMultiplier; + break; + case "y": + expectedDeltaY *= currentMultiplier; + break; + case "z": + expectedDeltaZ *= currentMultiplier; + break; + } + } + is(aEvent.deltaX, expectedDeltaX, description + "deltaX (" + currentEvent.deltaX + ") was invalid"); + is(aEvent.deltaY, expectedDeltaY, description + "deltaY (" + currentEvent.deltaY + ") was invalid"); + is(aEvent.deltaZ, expectedDeltaZ, description + "deltaZ (" + currentEvent.deltaZ + ") was invalid"); + + if (expectedAsyncHandlerCalls > 0 && --expectedAsyncHandlerCalls == 0) { + setTimeout(continueTest, 0); + } + } + + function legacyEventHandler(aEvent) { + var isHorizontal = (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS); + var isScrollEvent = (aEvent.type == "DOMMouseScroll"); + if (isScrollEvent) { + if (isHorizontal) { + calledHandlers.DOMMouseScroll.horizontal = true; + } else { + calledHandlers.DOMMouseScroll.vertical = true; + } + } else { + if (isHorizontal) { + calledHandlers.MozMousePixelScroll.horizontal = true; + } else { + calledHandlers.MozMousePixelScroll.vertical = true; + } + } + var eventName = (isHorizontal ? "Horizontal " : "Vertical ") + aEvent.type + " "; + var expectedDetail; + if (isScrollEvent) { + expectedDetail = isHorizontal ? currentEvent.lineOrPageDeltaX : currentEvent.lineOrPageDeltaY; + if (currentEvent.deltaMode == WheelEvent.DOM_DELTA_PAGE && expectedDetail) { + expectedDetail = ((expectedDetail > 0) ? UIEvent.SCROLL_PAGE_DOWN : UIEvent.SCROLL_PAGE_UP); + } + } else { + expectedDetail = isHorizontal ? currentEvent.deltaX : currentEvent.deltaY; + if (expectedDetail) { + if (currentEvent.deltaMode == WheelEvent.DOM_DELTA_LINE) { + expectedDetail *= (isHorizontal ? gHorizontalLine : gLineHeight); + } else if (currentEvent.deltaMode == WheelEvent.DOM_DELTA_PAGE) { + if (expectedDetail > 0) { + expectedDetail = (isHorizontal ? gPageWidth : gPageHeight); + } else { + expectedDetail = (isHorizontal ? -gPageWidth : -gPageHeight); + } + } + } + } + if (testingExpected) { + if ((isHorizontal && currentPref.charAt(currentPref.length - 1) == "x") || + (!isHorizontal && currentPref.charAt(currentPref.length - 1) == "y")) { + // If it's a page scroll event, the detail value is UIEvent.SCROLL_PAGE_DOWN or + // UIEvent.SCROLL_PAGE_UP. If the delta value sign is reverted, we need to + // revert the expected detail value too. Otherwise, don't touch it. + if (isScrollEvent && currentEvent.deltaMode == WheelEvent.DOM_DELTA_PAGE) { + if (currentMultiplier < 0) { + expectedDetail = ((expectedDetail == UIEvent.SCROLL_PAGE_UP) ? UIEvent.SCROLL_PAGE_DOWN : UIEvent.SCROLL_PAGE_UP); + } + } else { + expectedDetail *= currentMultiplier; + expectedDetail = expectedDetail < 0 ? Math.ceil(expectedDetail) : Math.floor(expectedDetail); + } + } + } + is(aEvent.detail, expectedDetail, description + eventName + "detail was invalid"); + + aEvent.preventDefault(); + + if (expectedAsyncHandlerCalls > 0 && --expectedAsyncHandlerCalls == 0) { + setTimeout(continueTest, 0); + } + } + + window.addEventListener("wheel", wheelEventHandler, true); + window.addEventListener("DOMMouseScroll", legacyEventHandler, true); + window.addEventListener("MozMousePixelScroll", legacyEventHandler, true); + + function* dispatchEvent(aIsExpected) { + for (var i = 0; i < kEvents.length; i++) { + currentEvent = kEvents[i]; + currentEvent.shiftKey = (currentModifiers & kModifierShift) != 0; + currentEvent.ctrlKey = (currentModifiers & kModifierControl) != 0; + currentEvent.altKey = (currentModifiers & kModifierAlt) != 0; + currentEvent.metaKey = (currentModifiers & kModifierMeta) != 0; + currentEvent.osKey = (currentModifiers & kModifierWin) != 0; + var modifierList = ""; + if (currentEvent.shiftKey) { + modifierList += "Shift "; + } + if (currentEvent.ctrlKey) { + modifierList += "Control "; + } + if (currentEvent.altKey) { + modifierList += "Alt "; + } + if (currentEvent.metaKey) { + modifierList += "Meta "; + } + if (currentEvent.osKey) { + modifierList += "Win "; + } + + for (var j = 0; j < kPrefValues.length; j++) { + currentMultiplier = kPrefValues[j] / 100; + for (var k = 0; k < kDeltaMultiplierPrefs.length; k++) { + currentPref = "mousewheel." + currentTest.name + "." + kDeltaMultiplierPrefs[k]; + + yield SpecialPowers.pushPrefEnv({"set": [[currentPref, kPrefValues[j]]]}, continueTest); + + gScrollableElement.scrollTop = gScrollableElement.scrollBottom = 1000; + + // trusted event's delta valuses should be reverted by the pref. + testingExpected = aIsExpected; + + var expectedProps = { + deltaX: currentEvent.deltaX * currentMultiplier, + deltaY: currentEvent.deltaY * currentMultiplier, + dletaZ: currentEvent.deltaZ * currentMultiplier, + lineOrPageDeltaX: currentEvent.lineOrPageDeltaX * currentMultiplier, + lineOrPageDeltaY: currentEvent.lineOrPageDeltaY * currentMultiplier, + }; + + var expectedWheel = expectedProps.deltaX != 0 || expectedProps.deltaY != 0 || expectedProps.deltaZ != 0; + var expectedDOMMouseX = expectedProps.lineOrPageDeltaX >= 1 || expectedProps.lineOrPageDeltaX <= -1; + var expectedDOMMouseY = expectedProps.lineOrPageDeltaY >= 1 || expectedProps.lineOrPageDeltaY <= -1; + var expectedMozMouseX = expectedProps.deltaX >= 1 || expectedProps.deltaX <= -1; + var expectedMozMouseY = expectedProps.deltaY >= 1 || expectedProps.deltaY <= -1; + + expectedAsyncHandlerCalls = 0; + if (expectedWheel) ++expectedAsyncHandlerCalls; + if (expectedDOMMouseX) ++expectedAsyncHandlerCalls; + if (expectedDOMMouseY) ++expectedAsyncHandlerCalls; + if (expectedMozMouseX) ++expectedAsyncHandlerCalls; + if (expectedMozMouseY) ++expectedAsyncHandlerCalls; + + description = "testDeltaMultiplierPrefs, pref: " + currentPref + "=" + kPrefValues[j] + + ", deltaMode: " + currentEvent.deltaMode + ", modifiers: \"" + modifierList + "\", (trusted event): "; + yield synthesizeWheel(gScrollableElement, 10, 10, currentEvent); + + is(calledHandlers.wheel, + expectedWheel, + description + "wheel event was (not) fired"); + is(calledHandlers.DOMMouseScroll.horizontal, + expectedDOMMouseX, + description + "Horizontal DOMMouseScroll event was (not) fired"); + is(calledHandlers.DOMMouseScroll.vertical, + expectedDOMMouseY, + description + "Vertical DOMMouseScroll event was (not) fired"); + is(calledHandlers.MozMousePixelScroll.horizontal, + expectedMozMouseX, + description + "Horizontal MozMousePixelScroll event was (not) fired"); + is(calledHandlers.MozMousePixelScroll.vertical, + expectedMozMouseY, + description + "Vertical MozMousePixelScroll event was (not) fired"); + + calledHandlers = { wheel: false, + DOMMouseScroll: { horizontal: false, vertical: false }, + MozMousePixelScroll: { horizontal: false, vertical: false } }; + + // untrusted event's delta values shouldn't be reverted by the pref. + testingExpected = false; + var props = { + bubbles: true, + cancelable: true, + shiftKey: currentEvent.shiftKey, + ctrlKey: currentEvent.ctrlKey, + altKey: currentEvent.altKey, + metaKey: currentEvent.metaKey, + deltaX: currentEvent.deltaX, + deltaY: currentEvent.deltaY, + deltaZ: currentEvent.deltaZ, + deltaMode: currentEvent.deltaMode, + }; + var untrustedEvent = new WheelEvent("wheel", props); + + description = "testDeltaMultiplierPrefs, pref: " + currentPref + "=" + kPrefValues[j] + + ", deltaMode: " + currentEvent.deltaMode + ", modifiers: \"" + modifierList + "\", (untrusted event): "; + gScrollableElement.dispatchEvent(untrustedEvent); + + ok(calledHandlers.wheel, description + "wheel event was not fired for untrusted event"); + ok(!calledHandlers.DOMMouseScroll.horizontal, + description + "Horizontal DOMMouseScroll event was fired for untrusted event"); + ok(!calledHandlers.DOMMouseScroll.vertical, + description + "Vertical DOMMouseScroll event was fired for untrusted event"); + ok(!calledHandlers.MozMousePixelScroll.horizontal, + description + "Horizontal MozMousePixelScroll event was fired for untrusted event"); + ok(!calledHandlers.MozMousePixelScroll.vertical, + description + "Vertical MozMousePixelScroll event was fired for untrusted event"); + + yield SpecialPowers.pushPrefEnv({"set": [[currentPref, 100]]}, continueTest); + + calledHandlers = { wheel: false, + DOMMouseScroll: { horizontal: false, vertical: false }, + MozMousePixelScroll: { horizontal: false, vertical: false } }; + + } + // We should skip other value tests if testing with modifier key. + // If we didn't do so, it would test too many times, but we don't need to do so. + if (kTests.name != "default") { + break; + } + } + } + } + + for (var i = 0; i < kTests.length; i++) { + currentTest = kTests[i]; + for (var j = 0; j < currentTest.expected.length; j++) { + currentModifiers = currentTest.expected[j]; + yield* dispatchEvent(true); + } + for (var k = 0; k < currentTest.unexpected.length; k++) { + currentModifiers = currentTest.unexpected[k]; + yield* dispatchEvent(false); + } + } + + window.removeEventListener("wheel", wheelEventHandler, true); + window.removeEventListener("DOMMouseScroll", legacyEventHandler, true); + window.removeEventListener("MozMousePixelScroll", legacyEventHandler, true); +} + +// Untrusted wheel events shouldn't cause legacy mouse scroll events. +function testDispatchingUntrustEvent() +{ + var descriptionBase = "testDispatchingUntrustEvent, "; + var description, wheelEventFired; + function wheelEventHandler(aEvent) + { + wheelEventFired = true; + } + + function legacyEventHandler(aEvent) + { + ok(false, aEvent.type + " must not be fired"); + } + + window.addEventListener("wheel", wheelEventHandler, true); + window.addEventListener("DOMMouseScroll", legacyEventHandler, true); + window.addEventListener("MozMousePixelScroll", legacyEventHandler, true); + + description = descriptionBase + "dispatching a pixel wheel event: "; + wheelEventFired = false; + var untrustedPixelEvent = new WheelEvent("wheel", { + bubbles: true, cancelable: true, + deltaX: 24.0, deltaY: 24.0, + deltaMode: WheelEvent.DOM_DELTA_PIXEL, + }); + gScrolledElement.dispatchEvent(untrustedPixelEvent); + ok(wheelEventFired, description + "wheel event wasn't fired"); + + description = descriptionBase + "dispatching a line wheel event: "; + wheelEventFired = false; + var untrustedLineEvent = new WheelEvent("wheel", { + bubbles: true, cancelable: true, + deltaX: 3.0, deltaY: 3.0, + deltaMode: WheelEvent.DOM_DELTA_LINE, + }); + gScrolledElement.dispatchEvent(untrustedLineEvent); + ok(wheelEventFired, description + "wheel event wasn't fired"); + + description = descriptionBase + "dispatching a page wheel event: "; + wheelEventFired = false; + var untrustedPageEvent = new WheelEvent("wheel", { + bubbles: true, cancelable: true, + deltaX: 1.0, deltaY: 1.0, + deltaMode: WheelEvent.DOM_DELTA_PAGE, + }); + gScrolledElement.dispatchEvent(untrustedPageEvent); + ok(wheelEventFired, description + "wheel event wasn't fired"); + + window.removeEventListener("wheel", wheelEventHandler, true); + window.removeEventListener("DOMMouseScroll", legacyEventHandler, true); + window.removeEventListener("MozMousePixelScroll", legacyEventHandler, true); +} + +function* testEventOrder() +{ + const kWheelEvent = 0x0001; + const kDOMMouseScrollEvent = 0x0002; + const kMozMousePixelScrollEvent = 0x0004; + const kVerticalScrollEvent = 0x0010; + const kHorizontalScrollEvent = 0x0020; + const kInSystemGroup = 0x0100; + const kDefaultPrevented = 0x1000; + + var currentTest; + + const kTests = [ + { + description: "Testing the order of the events without preventDefault()", + expectedEvents: [ kWheelEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kVerticalScrollEvent, + kMozMousePixelScrollEvent | kVerticalScrollEvent | kInSystemGroup, + kDOMMouseScrollEvent | kHorizontalScrollEvent, + kDOMMouseScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kHorizontalScrollEvent, + kMozMousePixelScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kWheelEvent | kInSystemGroup], + resultEvents: [], + doPreventDefaultAt: 0, + }, + { + description: "Testing the order of the events, calling preventDefault() at default group wheel event", + expectedEvents: [ kWheelEvent, + kWheelEvent | kInSystemGroup | kDefaultPrevented], + resultEvents: [], + doPreventDefaultAt: kWheelEvent, + }, + { + description: "Testing the order of the events, calling preventDefault() at default group DOMMouseScroll event", + expectedEvents: [ kWheelEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent | kInSystemGroup | kDefaultPrevented, + kMozMousePixelScrollEvent | kVerticalScrollEvent | kDefaultPrevented, + kMozMousePixelScrollEvent | kVerticalScrollEvent | kInSystemGroup | kDefaultPrevented, + kDOMMouseScrollEvent | kHorizontalScrollEvent, + kDOMMouseScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kHorizontalScrollEvent, + kMozMousePixelScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kWheelEvent | kInSystemGroup | kDefaultPrevented], + resultEvents: [], + doPreventDefaultAt: kDOMMouseScrollEvent | kVerticalScrollEvent, + }, + { + description: "Testing the order of the events, calling preventDefault() at default group MozMousePixelScroll event", + expectedEvents: [ kWheelEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kVerticalScrollEvent, + kMozMousePixelScrollEvent | kVerticalScrollEvent | kInSystemGroup | kDefaultPrevented, + kDOMMouseScrollEvent | kHorizontalScrollEvent, + kDOMMouseScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kHorizontalScrollEvent, + kMozMousePixelScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kWheelEvent | kInSystemGroup | kDefaultPrevented], + resultEvents: [], + doPreventDefaultAt: kMozMousePixelScrollEvent | kVerticalScrollEvent, + }, + { + description: "Testing the order of the events, calling preventDefault() at system group DOMMouseScroll event", + expectedEvents: [ kWheelEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kVerticalScrollEvent | kDefaultPrevented, + kMozMousePixelScrollEvent | kVerticalScrollEvent | kInSystemGroup | kDefaultPrevented, + kDOMMouseScrollEvent | kHorizontalScrollEvent, + kDOMMouseScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kHorizontalScrollEvent, + kMozMousePixelScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kWheelEvent | kInSystemGroup | kDefaultPrevented], + resultEvents: [], + doPreventDefaultAt: kDOMMouseScrollEvent | kVerticalScrollEvent | kInSystemGroup, + }, + { + description: "Testing the order of the events, calling preventDefault() at system group MozMousePixelScroll event", + expectedEvents: [ kWheelEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent, + kDOMMouseScrollEvent | kVerticalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kVerticalScrollEvent, + kMozMousePixelScrollEvent | kVerticalScrollEvent | kInSystemGroup, + kDOMMouseScrollEvent | kHorizontalScrollEvent, + kDOMMouseScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kMozMousePixelScrollEvent | kHorizontalScrollEvent, + kMozMousePixelScrollEvent | kHorizontalScrollEvent | kInSystemGroup, + kWheelEvent | kInSystemGroup | kDefaultPrevented], + resultEvents: [], + doPreventDefaultAt: kMozMousePixelScrollEvent | kVerticalScrollEvent | kInSystemGroup, + }, + ]; + + function getEventDescription(aEvent) + { + var result = ""; + if (aEvent & kWheelEvent) { + result = "wheel" + } else { + if (aEvent & kDOMMouseScrollEvent) { + result = "DOMMouseScroll"; + } else if (aEvent & kMozMousePixelScrollEvent) { + result = "MozMousePixelScroll"; + } + if (aEvent & kVerticalScrollEvent) { + result += ", vertical"; + } else { + result += ", horizontal"; + } + } + if (aEvent & kInSystemGroup) { + result += ", system group"; + } + if (aEvent & kDefaultPrevented) { + result += ", defaultPrevented"; + } + return result; + } + + function pushEvent(aEvent, aIsSystemGroup) + { + var event = 0; + if (aEvent.type == "wheel") { + event = kWheelEvent; + } else { + if (aEvent.type == "DOMMouseScroll") { + event = kDOMMouseScrollEvent; + } else if (aEvent.type == "MozMousePixelScroll") { + event = kMozMousePixelScrollEvent; + } + if (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS) { + event |= kHorizontalScrollEvent; + } else { + event |= kVerticalScrollEvent; + } + } + if (aIsSystemGroup) { + event |= kInSystemGroup; + } + if (aEvent.defaultPrevented) { + event |= kDefaultPrevented; + } + currentTest.resultEvents.push(event); + + if (event == currentTest.doPreventDefaultAt) { + aEvent.preventDefault(); + } + + if (currentTest.resultEvents.length == currentTest.expectedEvents.length) { + setTimeout(continueTest, 0); + } + } + + function handler(aEvent) + { + pushEvent(aEvent, false); + } + + function systemHandler(aEvent) + { + pushEvent(aEvent, true); + } + + window.addEventListener("wheel", handler, true); + window.addEventListener("DOMMouseScroll", handler, true); + window.addEventListener("MozMousePixelScroll", handler, true); + + SpecialPowers.addSystemEventListener(window, "wheel", systemHandler, true); + SpecialPowers.addSystemEventListener(window, "DOMMouseScroll", systemHandler, true); + SpecialPowers.addSystemEventListener(window, "MozMousePixelScroll", systemHandler, true); + + for (var i = 0; i < kTests.length; i++) { + currentTest = kTests[i]; + yield synthesizeWheel(gScrollableElement, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, deltaX: 1.0, deltaY: 1.0 }); + + for (var j = 0; j < currentTest.expectedEvents.length; j++) { + if (currentTest.resultEvents.length == j) { + ok(false, currentTest.description + ": " + + getEventDescription(currentTest.expectedEvents[j]) + " wasn't fired"); + break; + } + is(getEventDescription(currentTest.resultEvents[j]), + getEventDescription(currentTest.expectedEvents[j]), + currentTest.description + ": " + (j + 1) + "th event is mismatched"); + } + if (currentTest.expectedEvents.length < currentTest.resultEvents.length) { + ok(false, currentTest.description + ": " + + getEventDescription(currentTest.resultEvents[currentTest.expectedEvents.length]) + + " was fired unexpectedly"); + } + } + + window.removeEventListener("wheel", handler, true); + window.removeEventListener("DOMMouseScroll", handler, true); + window.removeEventListener("MozMousePixelScroll", handler, true); + + SpecialPowers.removeSystemEventListener(window, "wheel", systemHandler, true); + SpecialPowers.removeSystemEventListener(window, "DOMMouseScroll", systemHandler, true); + SpecialPowers.removeSystemEventListener(window, "MozMousePixelScroll", systemHandler, true); +} + +var gOnWheelAttrHandled = new Array; +var gOnWheelAttrCount = 0; + +function* testOnWheelAttr() +{ + function onWheelHandledString(attr) { + return `gOnWheelAttrHandled['${attr}'] = true; + ++gOnWheelAttrCount; + if (gOnWheelAttrCount == 3) { + setTimeout(continueTest, 0); + };`; + } + + document.documentElement.setAttribute("onwheel", onWheelHandledString("html")); + document.body.setAttribute("onwheel", onWheelHandledString("body")); + gScrollableElement.setAttribute("onwheel", onWheelHandledString("div")); + var target = document.getElementById("onwheel"); + yield synthesizeWheel(gScrollableElement, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 2.0 }); + ok(gOnWheelAttrHandled['html'], "html element's onwheel attribute isn't performed"); + ok(gOnWheelAttrHandled['body'], "body element's onwheel attribute isn't performed"); + ok(gOnWheelAttrHandled['div'], "div element's onwheel attribute isn't performed"); +} + +var gOnWheelPropHandled = new Array; +var gOnWheelPropCount = 0; + +function* testOnWheelProperty() +{ + const handleOnWheelProp = prop => e => { + gOnWheelPropHandled[prop] = true; + ++gOnWheelPropCount; + if (gOnWheelPropCount == 5) { + setTimeout(continueTest, 0); + } + } + + window.onwheel = handleOnWheelProp('window'); + document.onwheel = handleOnWheelProp('document'); + document.documentElement.onwheel = handleOnWheelProp('html'); + document.body.onwheel = handleOnWheelProp('body'); + gScrollableElement.onwheel = handleOnWheelProp('div'); + + var target = document.getElementById("onwheel"); + yield synthesizeWheel(gScrollableElement, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 2.0 }); + + ok(gOnWheelPropHandled['window'], "window's onwheel property isn't performed"); + ok(gOnWheelPropHandled['document'], "document's onwheel property isn't performed"); + ok(gOnWheelPropHandled['html'], "html element's onwheel property isn't performed"); + ok(gOnWheelPropHandled['body'], "body element's onwheel property isn't performed"); + ok(gOnWheelPropHandled['div'], "div element's onwheel property isn't performed"); +} + +function* testBody() +{ + yield* prepareScrollUnits(); + testMakingUntrustedEvent(); + yield* testDeltaMultiplierPrefs(); + testDispatchingUntrustEvent(); + yield* testEventOrder(); + yield* testOnWheelAttr(); + yield* testOnWheelProperty(); +} + +var gTestContinuation = null; + +function continueTest() +{ + if (!gTestContinuation) { + gTestContinuation = testBody(); + } + var ret = gTestContinuation.next(); + if (ret.done) { + SimpleTest.finish(); + } +} + +function runTest() +{ + SpecialPowers.pushPrefEnv({"set": [ + ["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100], + ["mousewheel.default.delta_multiplier_z", 100], + ["mousewheel.with_alt.delta_multiplier_x", 100], + ["mousewheel.with_alt.delta_multiplier_y", 100], + ["mousewheel.with_alt.delta_multiplier_z", 100], + ["mousewheel.with_control.delta_multiplier_x", 100], + ["mousewheel.with_control.delta_multiplier_y", 100], + ["mousewheel.with_control.delta_multiplier_z", 100], + ["mousewheel.with_meta.delta_multiplier_x", 100], + ["mousewheel.with_meta.delta_multiplier_y", 100], + ["mousewheel.with_meta.delta_multiplier_z", 100], + ["mousewheel.with_shift.delta_multiplier_x", 100], + ["mousewheel.with_shift.delta_multiplier_y", 100], + ["mousewheel.with_shift.delta_multiplier_z", 100], + ["mousewheel.with_win.delta_multiplier_x", 100], + ["mousewheel.with_win.delta_multiplier_y", 100], + ["mousewheel.with_win.delta_multiplier_z", 100]]}, + continueTest); +} +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_draggableprop.html b/dom/events/test/test_draggableprop.html new file mode 100644 index 000000000..7a5b5914d --- /dev/null +++ b/dom/events/test/test_draggableprop.html @@ -0,0 +1,89 @@ +<html> +<head> + <title>Tests for the draggable property on HTML elements</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<span id="elem1">One</span> +<span id="elem2" draggable="true">Two</span> +<span id="elem3" draggable="">Three</span> +<span id="elem4" draggable="false">Four</span> +<span id="elem5" draggable="other">Five</span> + +<img id="img1" src="../happy.png"> +<img id="img2" src="../happy.png" draggable="true"> +<img id="img3" src="../happy.png" draggable=""> +<img id="img4" src="../happy.png" draggable="false"> +<img id="img5" src="../happy.png" draggable="other"> + +<a id="a1">One</a> +<a id="a2" draggable="true">Two</a> +<a id="a3" draggable="">Three</a> +<a id="a4" draggable="false">Four</a> +<a id="a5" draggable="other">Five</a> + +<a id="ahref1" href="http://www.mozilla.org">One</a> +<a id="ahref2" href="http://www.mozilla.org" draggable="true">Two</a> +<a id="ahref3" href="http://www.mozilla.org" draggable="">Three</a> +<a id="ahref4" href="http://www.mozilla.org" draggable="false">Four</a> +<a id="ahref5" href="http://www.mozilla.org" draggable="other">Five</a> + +<script> +function check() +{ + try { + checkElements(1, false, true, false, true); + checkElements(2, true, true, true, true); + checkElements(3, false, true, false, true); + checkElements(4, false, false, false, false); + checkElements(5, false, true, false, true); + } + catch (ex) { + is("script error", ex, "fail"); + } +} + +function checkElements(idx, estate, istate, astate, ahrefstate) +{ + checkElement("elem" + idx, estate, false); + checkElement("img" + idx, istate, true); + checkElement("a" + idx, astate, false); + checkElement("ahref" + idx, ahrefstate, true); +} + +function checkElement(elemid, state, statedef) +{ + var elem = document.getElementById(elemid); + + is(elem.draggable, state, elemid + "-initial"); + elem.draggable = true; + is(elem.draggable, true, elemid + "-true"); + elem.draggable = false; + is(elem.draggable, false, elemid + "-false"); + + elem.setAttribute("draggable", "true"); + is(elem.draggable, true, elemid + "-attr-true"); + elem.setAttribute("draggable", "false"); + is(elem.draggable, false, elemid + "-attr-false"); + elem.setAttribute("draggable", "other"); + is(elem.draggable, statedef, elemid + "-attr-other"); + elem.setAttribute("draggable", ""); + is(elem.draggable, statedef, elemid + "-attr-empty"); + elem.removeAttribute("draggable"); + is(elem.draggable, statedef, elemid + "-attr-removed"); +} + +check(); + +</script> + +</body> +</html> + + diff --git a/dom/events/test/test_dragstart.html b/dom/events/test/test_dragstart.html new file mode 100644 index 000000000..3c76d4f16 --- /dev/null +++ b/dom/events/test/test_dragstart.html @@ -0,0 +1,585 @@ +<html> +<head> + <title>Tests for the dragstart event</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test checks the dragstart event and the DataTransfer object + --> + +<script> + +SimpleTest.waitForExplicitFinish(); + +var gDragInfo; +var gDataTransfer = null; +var gExtraDragTests = 0; + +function runTests() +{ + // first, create a selection and try dragging it + var draggable = $("draggable"); + window.getSelection().selectAllChildren(draggable); + synthesizeMouse(draggable, 6, 6, { type: "mousedown" }); + synthesizeMouse(draggable, 14, 14, { type: "mousemove" }); + // drags are asynchronous on Linux, so this extra event is needed to make + // sure the drag gets processed + synthesizeMouse(draggable, 15, 15, { type: "mousemove" }); +} + +function afterDragTests() +{ + // the dragstart should have occurred due to moving the mouse. gDataTransfer + // caches the dataTransfer that was used, however it should now be empty and + // be read only. + ok(gDataTransfer instanceof DataTransfer, "DataTransfer after dragstart event"); + checkTypes(gDataTransfer, [], 0, "after dragstart event"); + + expectError(() => gDataTransfer.setData("text/plain", "Some Text"), + "NoModificationAllowedError", "setData when read only"); + expectError(() => gDataTransfer.clearData("text/plain"), + "NoModificationAllowedError", "clearData when read only"); + expectError(() => gDataTransfer.mozSetDataAt("text/plain", "Some Text", 0), + "NoModificationAllowedError", "setDataAt when read only"); + expectError(() => gDataTransfer.mozClearDataAt("text/plain", 0), + "NoModificationAllowedError", "clearDataAt when read only"); + expectError(() => gDataTransfer.setDragImage(draggable, 10, 10), + "NoModificationAllowedError", "setDragImage when read only"); + expectError(() => gDataTransfer.addElement(draggable), + "NoModificationAllowedError", "addElement when read only"); + + var evt = document.createEvent("dragevent"); + ok(evt instanceof DragEvent, "synthetic dragevent class") + ok(evt instanceof MouseEvent, "synthetic event inherits from MouseEvent") + evt.initDragEvent("dragstart", true, true, window, 1, 40, 35, 20, 15, + false, true, false, false, 0, null, null); + $("synthetic").dispatchEvent(evt); + + var evt = document.createEvent("dragevent"); + ok(evt instanceof DragEvent, "synthetic dragevent class") + evt.initDragEvent("dragover", true, true, window, 0, 40, 35, 20, 15, + true, false, true, true, 2, document.documentElement, null); + $("synthetic2").dispatchEvent(evt); + + // next, dragging links and images + sendMouseEventsForDrag("link"); + sendMouseEventsForDrag("image"); + +// disable testing input dragging for now, as it doesn't seem to be testable +// draggable = $("input"); +// draggable.setSelectionRange(0, 4); +// synthesizeMouse(draggable, 8, 8, { type: "mousedown" }); +// synthesizeMouse(draggable, 15, 15, { type: "mousemove" }); +// sendMouseEventsForDrag("input"); + + // next, check if the draggable attribute can be used to adjust the drag target + gDragInfo = { target: $("dragtrue"), testid: "draggable true node" }; + sendMouseEventsForDrag("dragtrue"); + gDragInfo = { target: $("dragtrue"), testid: "draggable true child" }; + sendMouseEventsForDrag("spantrue"); + gDragInfo = { target: $("dragfalse").firstChild, testid: "draggable false node" }; + sendMouseEventsForDrag("dragfalse"); + gDragInfo = { target: $("spanfalse").firstChild, testid: "draggable false child" }; + sendMouseEventsForDrag("spanfalse"); + + synthesizeMouse(draggable, 12, 12, { type: "mouseup" }); + if (gExtraDragTests == 4) + SimpleTest.finish(); +} + +function sendMouseEventsForDrag(nodeid) +{ + var draggable = $(nodeid); + synthesizeMouse(draggable, 3, 3, { type: "mousedown" }); + synthesizeMouse(draggable, 10, 10, { type: "mousemove" }); + synthesizeMouse(draggable, 12, 12, { type: "mousemove" }); +} + +function doDragStartSelection(event) +{ + is(event.type, "dragstart", "dragstart event type"); + is(event.target, $("draggable").firstChild, "dragstart event target"); + is(event.bubbles, true, "dragstart event bubbles"); + is(event.cancelable, true, "dragstart event cancelable"); + + is(event.clientX, 14, "dragstart clientX"); + is(event.clientY, 14, "dragstart clientY"); + ok(event.screenX > 0, "dragstart screenX"); + ok(event.screenY > 0, "dragstart screenY"); + is(event.layerX, 14, "dragstart layerX"); + is(event.layerY, 14, "dragstart layerY"); + is(event.pageX, 14, "dragstart pageX"); + is(event.pageY, 14, "dragstart pageY"); + + var dt = event.dataTransfer; + ok(dt instanceof DataTransfer, "dataTransfer is DataTransfer"); + gDataTransfer = dt; + + var types = dt.types; + ok(Array.isArray(types), "initial types is an Array"); + checkTypes(dt, ["text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial selection"); + + is(dt.getData("text/plain"), "This is a draggable bit of text.", "initial selection text/plain"); + is(dt.getData("text/html"), "<div id=\"draggable\" ondragstart=\"doDragStartSelection(event)\">This is a <em>draggable</em> bit of text.</div>", + "initial selection text/html"); + + // text/unicode and Text are available for compatibility. They retrieve the + // text/plain data + is(dt.getData("text/unicode"), "This is a draggable bit of text.", "initial selection text/unicode"); + is(dt.getData("Text"), "This is a draggable bit of text.", "initial selection Text"); + is(dt.getData("TEXT"), "This is a draggable bit of text.", "initial selection TEXT"); + is(dt.getData("text/UNICODE"), "This is a draggable bit of text.", "initial selection text/UNICODE"); + + is(dt.mozItemCount, 1, "initial selection item count"); + + dt.clearData("text/plain"); + dt.clearData("text/html"); + dt.clearData("text/_moz_htmlinfo"); + dt.clearData("text/_moz_htmlcontext"); + + test_DataTransfer(dt); + setTimeout(afterDragTests, 0); +} + +function test_DataTransfer(dt) +{ + is(dt.mozItemCount, 0, "empty itemCount"); + + var types = dt.types; + ok(Array.isArray(types), "empty types is an Array"); + checkTypes(dt, [], 0, "empty"); + is(dt.getData("text/plain"), "", "empty data is empty"); + + // calling setDataAt requires an index that is 0 <= index <= dt.itemCount + expectError(() => dt.mozSetDataAt("text/plain", "Some Text", 1), + "IndexSizeError", "setDataAt index too high"); + + is(dt.mozUserCancelled, false, "userCancelled"); + + // because an exception occurred, the data should not have been added + is(dt.mozItemCount, 0, "empty setDataAt index too high itemCount"); + dt.getData("text/plain", "", "empty setDataAt index too high getData"); + + // if the type is '', do nothing, or return '' + dt.setData("", "Invalid Type"); + is(dt.types.length, 0, "invalid type setData"); + is(dt.getData(""), "", "invalid type getData"), + dt.mozSetDataAt("", "Invalid Type", 0); + is(dt.types.length, 0, "invalid type setDataAt"); + is(dt.mozGetDataAt("", 0), null, "invalid type getDataAt"), + + // similar with clearDataAt and getDataAt + expectError(() => dt.mozGetDataAt("text/plain", 1), + "IndexSizeError", "getDataAt index too high"); + expectError(() => dt.mozClearDataAt("text/plain", 1), + "IndexSizeError", "clearDataAt index too high"); + + dt.setData("text/plain", "Sample Text"); + is(dt.mozItemCount, 1, "added plaintext itemCount"); + checkOneDataItem(dt, ["text/plain"], ["Sample Text"], 0, "added plaintext"); + + // after all those exceptions, the data should still be the same + checkOneDataItem(dt, ["text/plain"], ["Sample Text"], 0, "added plaintext after exception"); + + // modifying the data associated with the format should give it the new value + dt.setData("text/plain", "Modified Text"); + is(dt.mozItemCount, 1, "modified plaintext itemCount"); + checkOneDataItem(dt, ["text/plain"], ["Modified Text"], 0, "modified plaintext"); + + dt.setData("text/html", "<strong>Modified Text</strong>"); + is(dt.mozItemCount, 1, "modified html itemCount"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["Modified Text", "<strong>Modified Text</strong>"], + 0, "modified html"); + + // modifying data for a type that already exists should adjust it in place, + // not reinsert it at the beginning + dt.setData("text/plain", "New Text"); + is(dt.mozItemCount, 1, "modified text again itemCount"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["New Text", "<strong>Modified Text</strong>"], + 0, "modified text again"); + + var draggable = $("draggable"); + dt.setData("application/-moz-node", draggable); + checkOneDataItem(dt, ["text/plain", "text/html", "application/-moz-node"], + ["New Text", "<strong>Modified Text</strong>", draggable.toString()], + 0, "added node"); + + dt.clearData(""); // null means clear all + is(dt.mozItemCount, 0, "itemCount after clearData empty string"); + checkTypes(dt, [], 0, "empty after clearData empty string"); + + dt.setData("text/plain", 22); + dt.setData("text/html", 5.6); + dt.setData("text/xml", 5.6); + checkTypes(dt, ["text/plain", "text/html", "text/xml"], ["22", "5.6", ""], 0, "add numeric and empty data"); + + dt.clearData(); // no argument means clear all + is(dt.mozItemCount, 0, "itemCount after clearData no argument"); + checkTypes(dt, [], 0, "empty after clearData no argument"); + + // check 'Text' type which should convert into text/plain + dt.setData("Text", "Sample Text"); + checkOneDataItem(dt, ["text/plain"], ["Sample Text"], 0, "set Text"); + is(dt.getData("Text"), "Sample Text", "getData Text"); + is(dt.mozGetDataAt("Text", 0), "Sample Text", "getDataAt Text"); + dt.setData("text/plain", "More Text"); + checkOneDataItem(dt, ["text/plain"], ["More Text"], 0, "set text/plain after set Text"); + + dt.mozClearDataAt("", 0); // null means clear all + is(dt.mozItemCount, 0, "itemCount after clearDataAt empty string"); + checkTypes(dt, [], 0, "empty after clearDataAt empty string"); + + // check text/uri-list type + dt.setData("text/uri-list", "http://www.mozilla.org"); + checkURL(dt, "http://www.mozilla.org", "http://www.mozilla.org", 0, "set text/uri-list"); + + // check URL type which should add text/uri-list data + dt.setData("URL", "ftp://ftp.example.com"); + checkURL(dt, "ftp://ftp.example.com", "ftp://ftp.example.com", 0, "set URL"); + checkTypes(dt, ["text/uri-list"], ["ftp://ftp.example.com"], "url types"); + + // clearing text/uri-list data + dt.clearData("text/uri-list"); + is(dt.mozItemCount, 0, "itemCount after clear url-list"); + is(dt.getData("text/uri-list"), "", "text/uri-list after clear url-list"); + is(dt.getData("URL"), "", "URL after clear url-list"); + + // check text/uri-list parsing + dt.setData("text/uri-list", "#http://www.mozilla.org\nhttp://www.xulplanet.com\nhttp://www.example.com"); + checkURL(dt, "http://www.xulplanet.com", + "#http://www.mozilla.org\nhttp://www.xulplanet.com\nhttp://www.example.com", + 0, "uri-list 3 lines"); + + dt.setData("text/uri-list", "#http://www.mozilla.org"); + is(dt.getData("URL"), "", "uri-list commented"); + dt.setData("text/uri-list", "#http://www.mozilla.org\n"); + is(dt.getData("URL"), "", "uri-list commented with newline"); + + // check that clearing the URL type also clears the text/uri-list type + dt.clearData("URL"); + is(dt.getData("text/uri-list"), "", "clear URL"); + + dt.setData("text/uri-list", "#http://www.mozilla.org\n\n\n\n\n"); + is(dt.getData("URL"), "", "uri-list with blank lines"); + dt.setData("text/uri-list", ""); + is(dt.getData("URL"), "", "empty uri-list"); + dt.setData("text/uri-list", "#http://www.mozilla.org\n#Sample\nhttp://www.xulplanet.com \r\n"); + is(dt.getData("URL"), "http://www.xulplanet.com", "uri-list mix"); + dt.setData("text/uri-list", "\nhttp://www.mozilla.org"); + is(dt.getData("URL"), "", "empty line to start uri-list"); + dt.setData("text/uri-list", " http://www.mozilla.org#anchor "); + is(dt.getData("URL"), "http://www.mozilla.org#anchor", "uri-list with spaces and hash"); + + // ensure that setDataAt works the same way + dt.mozSetDataAt("text/uri-list", "#http://www.mozilla.org\n#Sample\nhttp://www.xulplanet.com \r\n", 0); + checkURL(dt, "http://www.xulplanet.com", + "#http://www.mozilla.org\n#Sample\nhttp://www.xulplanet.com \r\n", + 0, "uri-list mix setDataAt"); + + // now test adding multiple items to be dragged using the setDataAt method + dt.clearData(); + dt.mozSetDataAt("text/plain", "First Item", 0); + dt.mozSetDataAt("text/plain", "Second Item", 1); + expectError(() => dt.mozSetDataAt("text/plain", "Some Text", 3), + "IndexSizeError", "setDataAt index too high with two items"); + is(dt.mozItemCount, 2, "setDataAt item itemCount"); + checkOneDataItem(dt, ["text/plain"], ["First Item"], 0, "setDataAt item at index 0"); + checkOneDataItem(dt, ["text/plain"], ["Second Item"], 1, "setDataAt item at index 1"); + + dt.mozSetDataAt("text/html", "<em>First Item</em>", 0); + dt.mozSetDataAt("text/html", "<em>Second Item</em>", 1); + is(dt.mozItemCount, 2, "setDataAt two types item itemCount"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["First Item", "<em>First Item</em>"], 0, "setDataAt two types item at index 0"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["Second Item", "<em>Second Item</em>"], 1, "setDataAt two types item at index 1"); + + dt.mozSetDataAt("text/html", "<em>Changed First Item</em>", 0); + dt.mozSetDataAt("text/plain", "Changed Second Item", 1); + is(dt.mozItemCount, 2, "changed with setDataAt item itemCount"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["First Item", "<em>Changed First Item</em>"], 0, "changed with setDataAt item at index 0"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["Changed Second Item", "<em>Second Item</em>"], 1, "changed with setDataAt item at index 1"); + + dt.setData("text/html", "Changed with setData"); + is(dt.mozItemCount, 2, "changed with setData"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["First Item", "Changed with setData"], 0, "changed with setData item at index 0"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["Changed Second Item", "<em>Second Item</em>"], 1, "changed with setData item at index 1"); + + dt.mozSetDataAt("application/-moz-node", "draggable", 2); + is(dt.mozItemCount, 3, "setDataAt node itemCount"); + checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 2, "setDataAt node item at index 2"); + + dt.mozClearDataAt("text/html", 1); + is(dt.mozItemCount, 3, "clearDataAt itemCount"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["First Item", "Changed with setData"], 0, "clearDataAt item at index 0"); + checkOneDataItem(dt, ["text/plain"], ["Changed Second Item"], 1, "clearDataAt item at index 1"); + + dt.mozClearDataAt("text/plain", 1); + is(dt.mozItemCount, 2, "clearDataAt last type itemCount"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["First Item", "Changed with setData"], 0, "clearDataAt last type at index 0"); + checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 1, "clearDataAt last type item at index 2"); + expectError(() => dt.mozGetDataAt("text/plain", 2), + "IndexSizeError", "getDataAt after item removed index too high"); + + dt.mozSetDataAt("text/unknown", "Unknown type", 2); + dt.mozSetDataAt("text/unknown", "Unknown type", 1); + is(dt.mozItemCount, 3, "add unknown type"); + checkOneDataItem(dt, ["application/-moz-node", "text/unknown"], + ["draggable", "Unknown type"], 1, "add unknown type item at index 1"); + checkOneDataItem(dt, ["text/unknown"], ["Unknown type"], 2, "add unknown type item at index 2"); + + dt.mozClearDataAt("", 1); + is(dt.mozItemCount, 2, "clearDataAt empty string"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["First Item", "Changed with setData"], 0, "clearDataAt empty string item at index 0"); + checkOneDataItem(dt, ["text/unknown"], + ["Unknown type"], 1, "clearDataAt empty string item at index 1"); + + // passing a format that doesn't exist to clearData or clearDataAt should just + // do nothing + dt.clearData("text/something"); + dt.mozClearDataAt("text/something", 1); + is(dt.mozItemCount, 2, "clearData type that does not exist"); + checkOneDataItem(dt, ["text/plain", "text/html"], + ["First Item", "Changed with setData"], 0, "clearData type that does not exist item at index 0"); + checkOneDataItem(dt, ["text/unknown"], + ["Unknown type"], 1, "clearData type that does not exist item at index 1"); + + expectError(() => dt.mozClearDataAt("text/plain", 3), + "IndexSizeError", "clearData index too high with two items"); + + // ensure that clearData() removes all data associated with the first item, but doesn't + // shift the second item down into the first item's slot. + dt.clearData(); + is(dt.mozItemCount, 2, "clearData no argument with multiple items itemCount"); + checkOneDataItem(dt, [], [], 0, + "clearData no argument with multiple items item at index 0"); + checkOneDataItem(dt, ["text/unknown"], + ["Unknown type"], 1, "clearData no argument with multiple items item at index 1"); + + // remove tha remaining data in index 1. As index 0 is empty at this point, this will actually + // drop mozItemCount to 0. (XXX: This is because of spec-compliance reasons related + // to the more-recent dt.item API. It's an unfortunate, but hopefully rare edge case) + dt.mozClearDataAt("", 1); + is(dt.mozItemCount, 0, "all data cleared"); + + // now check the effectAllowed and dropEffect properties + is(dt.dropEffect, "none", "initial dropEffect"); + is(dt.effectAllowed, "uninitialized", "initial effectAllowed"); + + ["copy", "none", "link", "", "other", "copyMove", "all", "uninitialized", "move"].forEach( + function (i) { + dt.dropEffect = i; + is(dt.dropEffect, i == "" || i == "other" || i == "copyMove" || + i == "all" || i == "uninitialized" ? "link" : i, + "dropEffect set to " + i); + is(dt.effectAllowed, "uninitialized", "effectAllowed not modified by dropEffect set to " + i); + } + ); + + ["move", "copy", "link", "", "other", "moveCopy", "copyMove", + "linkMove", "copyLink", "all", "uninitialized", "none"].forEach( + function (i) { + dt.effectAllowed = i; + is(dt.dropEffect, "move", "dropEffect not modified by effectAllowed set to " + i); + is(dt.effectAllowed, i == "" || i == "other" || i == "moveCopy" ? "link" : i, + "effectAllowed set to " + i); + } + ); +} + +function doDragStartLink(event) +{ + var dt = event.dataTransfer; + checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list", + "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial link"); + + is(dt.mozItemCount, 1, "initial link item count"); + is(dt.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list"); + is(dt.getData("text/plain"), "http://www.mozilla.org/", "link text/plain"); + + event.preventDefault(); + + gExtraDragTests++; +} + +function doDragStartImage(event) +{ + var dataurl = $("image").src; + + var dt = event.dataTransfer; + checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list", + "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial image"); + + is(dt.mozItemCount, 1, "initial image item count"); + is(dt.getData("text/uri-list"), dataurl, "image text/uri-list"); + is(dt.getData("text/plain"), dataurl, "image text/plain"); + + event.preventDefault(); + + gExtraDragTests++; +} + +function doDragStartInput(event) +{ + var dt = event.dataTransfer; + checkTypes(dt, ["text/plain"], 0, "initial input"); + + is(dt.mozItemCount, 1, "initial input item count"); +// is(dt.getData("text/plain"), "Text", "input text/plain"); + +// event.preventDefault(); +} + +function doDragStartSynthetic(event) +{ + is(event.type, "dragstart", "synthetic dragstart event type"); + + var dt = event.dataTransfer; + todo(dt instanceof DataTransfer, "synthetic dragstart dataTransfer is DataTransfer"); +// Uncomment next line once the todo instanceof above is fixed. +// checkTypes(dt, [], 0, "synthetic dragstart"); + + is(event.detail, 1, "synthetic dragstart detail"); + is(event.screenX, 40, "synthetic dragstart screenX"); + is(event.screenY, 35, "synthetic dragstart screenY"); + is(event.clientX, 20, "synthetic dragstart clientX"); + is(event.clientY, 15, "synthetic dragstart clientY"); + is(event.ctrlKey, false, "synthetic dragstart ctrlKey"); + is(event.altKey, true, "synthetic dragstart altKey"); + is(event.shiftKey, false, "synthetic dragstart shiftKey"); + is(event.metaKey, false, "synthetic dragstart metaKey"); + is(event.button, 0, "synthetic dragstart button "); + is(event.relatedTarget, null, "synthetic dragstart relatedTarget"); + +// Uncomment next two lines once the todo instanceof above is fixed. +// dt.setData("text/plain", "Text"); +// is(dt.getData("text/plain"), "Text", "synthetic dragstart data is set after adding"); +} + +function doDragOverSynthetic(event) +{ + is(event.type, "dragover", "synthetic dragover event type"); + + var dt = event.dataTransfer; + todo(dt instanceof DataTransfer, "synthetic dragover dataTransfer is DataTransfer"); +// Uncomment next line once the todo instanceof above is fixed. +// checkTypes(dt, [], 0, "synthetic dragover"); + + is(event.detail, 0, "synthetic dragover detail"); + is(event.screenX, 40, "synthetic dragover screenX"); + is(event.screenY, 35, "synthetic dragover screenY"); + is(event.clientX, 20, "synthetic dragover clientX"); + is(event.clientY, 15, "synthetic dragover clientY"); + is(event.ctrlKey, true, "synthetic dragover ctrlKey"); + is(event.altKey, false, "synthetic dragover altKey"); + is(event.shiftKey, true, "synthetic dragover shiftKey"); + is(event.metaKey, true, "synthetic dragover metaKey"); + is(event.button, 2, "synthetic dragover button"); + is(event.relatedTarget, document.documentElement, "synthetic dragover relatedTarget"); + +// Uncomment next two lines once the todo instanceof above is fixed. +// dt.setData("text/plain", "Text"); +// is(dt.getData("text/plain"), "Text", "synthetic dragover data is set after adding"); +} + +function onDragStartDraggable(event) +{ + var dt = event.dataTransfer; + ok(dt.mozItemCount == 0 && dt.types.length == 0 && event.originalTarget == gDragInfo.target, gDragInfo.testid); + + gExtraDragTests++; +} + +function checkOneDataItem(dt, expectedtypes, expecteddata, index, testid) +{ + checkTypes(dt, expectedtypes, index, testid); + for (var f = 0; f < expectedtypes.length; f++) { + if (index == 0) + is(dt.getData(expectedtypes[f]), expecteddata[f], testid + " getData " + expectedtypes[f]); + is(dt.mozGetDataAt(expectedtypes[f], index), expecteddata[f] ? expecteddata[f] : null, + testid + " getDataAt " + expectedtypes[f]); + } +} + +function checkTypes(dt, expectedtypes, index, testid) +{ + if (index == 0) { + var types = dt.types; + is(types.length, expectedtypes.length, testid + " types length"); + for (var f = 0; f < expectedtypes.length; f++) { + is(types[f], expectedtypes[f], testid + " " + types[f] + " check"); + } + } + + types = dt.mozTypesAt(index); + is(types.length, expectedtypes.length, testid + " typesAt length"); + for (var f = 0; f < expectedtypes.length; f++) { + is(types[f], expectedtypes[f], testid + " " + types[f] + " at " + index + " check"); + } +} + +function checkURL(dt, url, fullurllist, index, testid) +{ + is(dt.getData("text/uri-list"), fullurllist, testid + " text/uri-list"); + is(dt.getData("URL"), url, testid + " URL"); + is(dt.mozGetDataAt("text/uri-list", 0), fullurllist, testid + " text/uri-list"); + is(dt.mozGetDataAt("URL", 0), fullurllist, testid + " URL"); +} + +function expectError(fn, eid, testid) +{ + var error = ""; + try { + fn(); + } catch (ex) { + error = ex.name; + } + is(error, eid, testid + " causes exception " + eid); +} + +</script> + +</head> + +<body style="height: 300px; overflow: auto;" onload="setTimeout(runTests, 0)"> + +<div id="draggable" ondragstart="doDragStartSelection(event)">This is a <em>draggable</em> bit of text.</div> + +<fieldset> +<a id="link" href="http://www.mozilla.org/" ondragstart="doDragStartLink(event)">mozilla.org</a> +</fieldset> + +<label> +<img id="image" src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82" + ondragstart="doDragStartImage(event)"> +</label> + +<input id="input" value="Text in a box" ondragstart="doDragStartInput(event)"> + +<div ondragstart="onDragStartDraggable(event)"> + <div id="dragtrue" draggable="true"> + This is a <span id="spantrue">draggable</span> area. + </div> + <div id="dragfalse" draggable="false"> + This is a <span id="spanfalse">non-draggable</span> area. + </div> +</div> + +<!--iframe src="http://www.mozilla.org" width="400" height="400"></iframe--> + +<div id="synthetic" ondragstart="doDragStartSynthetic(event)">Synthetic Event Dispatch</div> +<div id="synthetic2" ondragover="doDragOverSynthetic(event)">Synthetic Event Dispatch</div> + +</body> +</html> diff --git a/dom/events/test/test_error_events.html b/dom/events/test/test_error_events.html new file mode 100644 index 000000000..62cbb68de --- /dev/null +++ b/dom/events/test/test_error_events.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for error events being ErrorEvent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + setup({allow_uncaught_exception:true}); + var errorEvent; + var file; + var line; + var msg; + var column; + var error; + window.addEventListener("error", function errorListener(e) { + window.removeEventListener("error", errorListener); + errorEvent = e; + }); + var oldOnerror = window.onerror; + window.onerror = function(message, filename, lineno, columnno, errorObject) { + window.onerror = oldOnerror; + file = filename; + line = lineno; + msg = message; + column = columnno; + error = errorObject; + } + var thrown = new Error("hello"); + throw thrown; +</script> +<script> + generate_tests(assert_equals, [ + [ "Event filename", errorEvent.filename, location.href ], + [ "Callback filename", file, location.href ], + [ "Event line number", errorEvent.lineno, 28 ], + [ "Callback line number", line, 28 ], + [ "Event message", errorEvent.message, "Error: hello" ], + [ "Callback message", msg, "Error: hello" ], + [ "Event error-object", errorEvent.error, thrown], + [ "Callback error-object", error, thrown ], + [ "Event column", errorEvent.colno, 16 ], + [ "Callback column", column, 16 ] + ]); +</script> +<script> + var workerLocation = location.protocol + "//" + location.host + + location.pathname.replace("test_error_events.html", "error_event_worker.js"); + var eventFileTest = async_test("Worker event filename"); + var eventLineTest = async_test("Worker event line number"); + var eventMessageTest = async_test("Worker event message"); + var callbackFileTest = async_test("Worker callback filename"); + var callbackLineTest = async_test("Worker callback line number"); + var callbackMessageTest = async_test("Worker callback message"); + var w = new Worker("error_event_worker.js"); + w.addEventListener("message", function(msg) { + if (msg.data.type == "event") { + eventFileTest.step(function() { assert_equals(msg.data.filename, workerLocation); }); + eventFileTest.done(); + eventLineTest.step(function() { assert_equals(msg.data.lineno, 15); }); + eventLineTest.done(); + eventMessageTest.step(function() { assert_equals(msg.data.message, "Error: workerhello"); }); + eventMessageTest.done(); + } else { + callbackFileTest.step(function() { assert_equals(msg.data.filename, workerLocation); }); + callbackFileTest.done(); + callbackLineTest.step(function() { assert_equals(msg.data.lineno, 15); }); + callbackLineTest.done(); + callbackMessageTest.step(function() { assert_equals(msg.data.message, "Error: workerhello"); }); + callbackMessageTest.done(); + } + }); +</script> diff --git a/dom/events/test/test_eventTimeStamp.html b/dom/events/test/test_eventTimeStamp.html new file mode 100644 index 000000000..a3d096432 --- /dev/null +++ b/dom/events/test/test_eventTimeStamp.html @@ -0,0 +1,121 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=77992 +--> +<head> + <meta charset="utf-8"> + <title>Test for Event.timeStamp (Bug 77992)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=77992">Mozilla Bug 77992</a> +<p id="display"></p> +<pre id="test"> +<script type="text/js-worker" id="worker-src"> + // Simply returns the event timestamp + onmessage = function(evt) { + postMessage(evt.timeStamp); + } +</script> +<script type="text/js-worker" id="shared-worker-src"> + // Simply returns the event timestamp + onconnect = function(evt) { + var port = evt.ports[0]; + port.onmessage = function(messageEvt) { + port.postMessage(messageEvt.timeStamp); + }; + }; +</script> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +// We don't use SpecialPowers.pushPrefEnv since it can delay the test +// function until after the load event has fired which means we can't +// test the timestamp of the load event. +const kPrefName = "dom.event.highrestimestamp.enabled"; +var prevPrefValue = SpecialPowers.getBoolPref(kPrefName); +SpecialPowers.setBoolPref(kPrefName, true); +testRegularEvents(); + +// Event.timeStamp should be relative to the time origin which is: +// +// Non-worker context: navigation start +// Dedicated worker: navigation start of the document that created the worker +// Shared worker: creation time of the shared worker +// +// See: https://w3c.github.io/web-performance/specs/HighResolutionTime2/Overview.html#sec-time-origin + +function testRegularEvents() { + if (document.readyState === "complete") { + ok(false, "Onload event has already fired"); + finishTests(); + return; + } + var timeBeforeEvent = window.performance.now(); + window.addEventListener("load", function(evt) { + var timeAfterEvent = window.performance.now(); + ok(evt.timeStamp > timeBeforeEvent && + evt.timeStamp < timeAfterEvent, + "Event timestamp (" + evt.timeStamp + ") is in expected range: (" + + timeBeforeEvent + ", " + timeAfterEvent + ")"); + testWorkerEvents(); + }); +} + +function testWorkerEvents() { + var blob = new Blob([ document.getElementById("worker-src").textContent ], + { type: "text/javascript" }); + var worker = new Worker(window.URL.createObjectURL(blob)); + worker.onmessage = function(evt) { + var timeAfterEvent = window.performance.now(); + ok(evt.data > timeBeforeEvent && + evt.data < timeAfterEvent, + "Event timestamp in dedicated worker (" + evt.data + + ") is in expected range: (" + + timeBeforeEvent + ", " + timeAfterEvent + ")"); + worker.terminate(); + testSharedWorkerEvents(); + }; + var timeBeforeEvent = window.performance.now(); + worker.postMessage(""); +} + +function testSharedWorkerEvents() { + var blob = + new Blob([ document.getElementById("shared-worker-src").textContent ], + { type: "text/javascript" }); + // Delay creation of worker slightly so it is easier to distinguish + // shared worker creation time from this document's navigation start + window.setTimeout(function() { + var timeBeforeWorkerCreation = window.performance.now(); + var worker = new SharedWorker(window.URL.createObjectURL(blob)); + worker.port.onmessage = function(evt) { + var timeAfterEvent = window.performance.now(); + ok(evt.data > 0 && + evt.data < timeAfterEvent - timeBeforeWorkerCreation, + "Event timestamp in shared worker (" + evt.data + + ") is in expected range: (0, " + + (timeAfterEvent - timeBeforeWorkerCreation) + ")"); + worker.port.close(); + finishTests(); + }; + worker.port.start(); + worker.port.postMessage(""); + }, 500); +} + +var finishTests = function() { + SpecialPowers.setBoolPref(kPrefName, prevPrefValue); + SimpleTest.finish(); +}; + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_eventctors.html b/dom/events/test/test_eventctors.html new file mode 100644 index 000000000..a18c2a5ed --- /dev/null +++ b/dom/events/test/test_eventctors.html @@ -0,0 +1,953 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=675884 +--> +<head> + <title>Test for Bug 675884</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=675884">Mozilla Bug 675884</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 675884 **/ + +var receivedEvent; +document.addEventListener("hello", function(e) { receivedEvent = e; }, true); + +function isMethodResultInitializer(aPropName) +{ + return aPropName.startsWith("modifier"); +} + +function getPropValue(aEvent, aPropName) +{ + if (aPropName.startsWith("modifier")) { + return aEvent.getModifierState(aPropName.substr("modifier".length)); + } + return aEvent[aPropName]; +} + +// Event +var e; +var ex = false; +try { + e = new Event(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +try { + e = new Event("foo", 123); +} catch(exp) { + ex = true; +} +ok(ex, "2nd parameter should be an object!"); +ex = false; + +try { + e = new Event("foo", "asdf"); +} catch(exp) { + ex = true; +} +ok(ex, "2nd parameter should be an object!"); +ex = false; + + +try { + e = new Event("foo", false); +} catch(exp) { + ex = true; +} +ok(ex, "2nd parameter should be an object!"); +ex = false; + + +e = new Event("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +e.isTrusted = true; +ok(!e.isTrusted, "Event shouldn't be trusted!"); + +try { + e.__defineGetter__("isTrusted", function() { return true }); +} catch (exp) { + ex = true; +} +ok(ex, "Shouldn't be able to re-define the getter for isTrusted."); +ex = false; +ok(!e.isTrusted, "Event shouldn't be trusted!"); + +ok(!("isTrusted" in Object.getPrototypeOf(e))) + +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +document.dispatchEvent(e); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +is(receivedEvent, e, "Wrong event!"); + +e = new Event("hello", null); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +document.dispatchEvent(e); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +is(receivedEvent, e, "Wrong event!"); + +e = new Event("hello", undefined); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +document.dispatchEvent(e); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +is(receivedEvent, e, "Wrong event!"); + +e = new Event("hello", {}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +document.dispatchEvent(e); +is(e.eventPhase, Event.NONE, "Wrong event phase"); +is(receivedEvent, e, "Wrong event!"); + +e = new Event("hello", { bubbles: true, cancelable: true }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// CustomEvent + +try { + e = new CustomEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +e = new CustomEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new CustomEvent("hello", { bubbles: true, cancelable: true, detail: window }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.detail, window , "Wrong event.detail!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new CustomEvent("hello", { cancelable: true, detail: window }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.detail, window , "Wrong event.detail!"); + +e = new CustomEvent("hello", { detail: 123 }); +is(e.detail, 123, "Wrong event.detail!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); + +var dict = { get detail() { return document.body } }; +e = new CustomEvent("hello", dict); +is(e.detail, dict.detail, "Wrong event.detail!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); + +var dict = { get detail() { throw "foo"; } }; + +try { + e = new CustomEvent("hello", dict); +} catch (exp) { + ex = true; +} +ok(ex, "Should have thrown an exception!"); +ex = false; + +// BlobEvent + +try { + e = new BlobEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +e = new BlobEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +try { + e.__defineGetter__("isTrusted", function() { return true }); +} catch (exp) { + ex = true; +} +ok(ex, "Shouldn't be able to re-define the getter for isTrusted."); +ex = false; +ok(!e.isTrusted, "BlobEvent shouldn't be trusted!"); + +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +var blob = new Blob(); +e = new BlobEvent("hello", { bubbles: true, cancelable: true, data: blob }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.data, blob , "Wrong event.data!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + + +e = new BlobEvent("hello", {data: blob}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event should be cancelable1!"); +is(e.data, blob , "Wrong event.data!"); + +e = new BlobEvent("hello", { data: null }); +is(e.data, null, "Wrong event.data!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +blob = null; +// CloseEvent + +try { + e = new CloseEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +e = new CloseEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.wasClean, false, "wasClean should be false!"); +is(e.code, 0, "code should be 0!"); +is(e.reason, "", "reason should be ''!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new CloseEvent("hello", + { bubbles: true, cancelable: true, wasClean: true, code: 1, reason: "foo" }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.wasClean, true, "wasClean should be true!"); +is(e.code, 1, "code should be 1!"); +is(e.reason, "foo", "reason should be 'foo'!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new CloseEvent("hello", + { bubbles: true, cancelable: true, wasClean: true, code: 1 }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.wasClean, true, "wasClean should be true!"); +is(e.code, 1, "code should be 1!"); +is(e.reason, "", "reason should be ''!"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + + +// HashChangeEvent + +try { + e = new HashChangeEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +e = new HashChangeEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.oldURL, "", "oldURL should be ''"); +is(e.newURL, "", "newURL should be ''"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new HashChangeEvent("hello", + { bubbles: true, cancelable: true, oldURL: "old", newURL: "new" }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.oldURL, "old", "oldURL should be 'old'"); +is(e.newURL, "new", "newURL should be 'new'"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new HashChangeEvent("hello", + { bubbles: true, cancelable: true, newURL: "new" }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.oldURL, "", "oldURL should be ''"); +is(e.newURL, "new", "newURL should be 'new'"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// InputEvent + +e = new InputEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.detail, 0, "detail should be 0"); +ok(!e.isComposing, "isComposing should be false"); + +e = new InputEvent("hi!", { bubbles: true, detail: 5, isComposing: false }); +is(e.type, "hi!", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.detail, 5, "detail should be 5"); +ok(!e.isComposing, "isComposing should be false"); + +e = new InputEvent("hi!", { cancelable: true, detail: 0, isComposing: true }); +is(e.type, "hi!", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.detail, 0, "detail should be 0"); +ok(e.isComposing, "isComposing should be true"); + +// KeyboardEvent + +try { + e = new KeyboardEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "KeyboardEvent: First parameter is required!"); +ex = false; + +e = new KeyboardEvent("hello"); +ok(e.type, "hello", "KeyboardEvent: Wrong event type!"); +ok(!e.isTrusted, "KeyboardEvent: Event shouldn't be trusted!"); +ok(!e.bubbles, "KeyboardEvent: Event shouldn't bubble!"); +ok(!e.cancelable, "KeyboardEvent: Event shouldn't be cancelable!"); +document.dispatchEvent(e); +is(receivedEvent, e, "KeyboardEvent: Wrong event!"); + +var keyboardEventProps = +[ + { bubbles: false }, + { cancelable: false }, + { view: null }, + { detail: 0 }, + { key: "" }, + { code: "" }, + { location: 0 }, + { ctrlKey: false }, + { shiftKey: false }, + { altKey: false }, + { metaKey: false }, + { modifierAltGraph: false }, + { modifierCapsLock: false }, + { modifierFn: false }, + { modifierFnLock: false }, + { modifierNumLock: false }, + { modifierOS: false }, + { modifierScrollLock: false }, + { modifierSymbol: false }, + { modifierSymbolLock: false }, + { repeat: false }, + { isComposing: false }, + { charCode: 0 }, + { keyCode: 0 }, + { which: 0 }, +]; + +var testKeyboardProps = +[ + { bubbles: true }, + { cancelable: true }, + { view: window }, + { detail: 1 }, + { key: "CustomKey" }, + { code: "CustomCode" }, + { location: 1 }, + { ctrlKey: true }, + { shiftKey: true }, + { altKey: true }, + { metaKey: true }, + { modifierAltGraph: true }, + { modifierCapsLock: true }, + { modifierFn: true }, + { modifierFnLock: true }, + { modifierNumLock: true }, + { modifierOS: true }, + { modifierScrollLock: true }, + { modifierSymbol: true }, + { modifierSymbolLock: true }, + { repeat: true }, + { isComposing: true }, + { charCode: 2 }, + { keyCode: 3 }, + { which: 4 }, + { charCode: 5, which: 6 }, + { keyCode: 7, which: 8 }, + { keyCode: 9, charCode: 10 }, + { keyCode: 11, charCode: 12, which: 13 }, +]; + +var codeEnabled = SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled"); +var defaultKeyboardEventValues = {}; +for (var i = 0; i < keyboardEventProps.length; ++i) { + for (prop in keyboardEventProps[i]) { + if (!codeEnabled && prop == "code") { + continue; + } + if (!isMethodResultInitializer(prop)) { + ok(prop in e, "keyboardEvent: KeyboardEvent doesn't have property " + prop + "!"); + } + defaultKeyboardEventValues[prop] = keyboardEventProps[i][prop]; + } +} + +while (testKeyboardProps.length) { + var p = testKeyboardProps.shift(); + e = new KeyboardEvent("foo", p); + for (var def in defaultKeyboardEventValues) { + if (!codeEnabled && def == "code") { + continue; + } + if (!(def in p)) { + is(getPropValue(e, def), defaultKeyboardEventValues[def], + "KeyboardEvent: Wrong default value for " + def + "!"); + } else { + is(getPropValue(e, def), p[def], + "KeyboardEvent: Wrong event init value for " + def + "!"); + } + } +} + +// PageTransitionEvent + +try { + e = new PageTransitionEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +e = new PageTransitionEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.persisted, false, "persisted should be false"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new PageTransitionEvent("hello", + { bubbles: true, cancelable: true, persisted: true}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.persisted, true, "persisted should be true"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new PageTransitionEvent("hello", { persisted: true}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.persisted, true, "persisted should be true"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// PopStateEvent + +try { + e = new PopStateEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +e = new PopStateEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.state, null, "persisted should be null"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new PopStateEvent("hello", + { bubbles: true, cancelable: true, state: window}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.state, window, "persisted should be window"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + + +e = new PopStateEvent("hello", { state: window}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.state, window, "persisted should be window"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// UIEvent + +try { + e = new UIEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +try { + e = new UIEvent("foo", { view: {} }); + e.view.onunload; +} catch(exp) { + ex = true; +} +ok(ex, "{} isn't a valid value."); +ex = false; + +try { + e = new UIEvent("foo", { view: null }); +} catch(exp) { + ex = true; +} +ok(!ex, "null is a valid value."); +is(e.view, null); +ex = false; + +e = new UIEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.detail, 0, "detail should be 0"); +is(e.view, null, "view should be null"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new UIEvent("hello", + { bubbles: true, cancelable: true, view: window, detail: 1}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.detail, 1, "detail should be 1"); +is(e.view, window, "view should be window"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// StorageEvent + +e = document.createEvent("StorageEvent"); +ok(e, "Should have created an event!"); + +try { + e = new StorageEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "First parameter is required!"); +ex = false; + +e = new StorageEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(!e.bubbles, "Event shouldn't bubble!"); +ok(!e.cancelable, "Event shouldn't be cancelable!"); +is(e.key, null, "key should be null"); +is(e.oldValue, null, "oldValue should be null"); +is(e.newValue, null, "newValue should be null"); +is(e.url, "", "url should be ''"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +e = new StorageEvent("hello", + { bubbles: true, cancelable: true, key: "key", + oldValue: "oldValue", newValue: "newValue", url: "url", + storageArea: localStorage }); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event shouldn't be trusted!"); +ok(e.bubbles, "Event should bubble!"); +ok(e.cancelable, "Event should be cancelable!"); +is(e.key, "key", "Wrong value"); +is(e.oldValue, "oldValue", "Wrong value"); +is(e.newValue, "newValue", "Wrong value"); +is(e.url, "url", "Wrong value"); +is(e.storageArea, localStorage, "Wrong value"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// DeviceProximityEvent +e = new DeviceProximityEvent("hello", {min: 0, value: 1, max: 2}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event should not be trusted"); +is(e.value, 1, "value should be 1"); +is(e.min, 0, "min should be 0"); +is(e.max, 2, "max should be 2"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); +e = new DeviceProximityEvent("hello"); +is(e.value, Infinity, "Uninitialized value should be infinity"); +is(e.min, -Infinity, "Uninitialized min should be -infinity"); +is(e.max, Infinity, "Uninitialized max should be infinity"); + +// UserProximityEvent +e = new UserProximityEvent("hello", {near: true}); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event should not be trusted"); +is(e.near, true, "near should be true"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// DeviceLightEvent +e = new DeviceLightEvent("hello", {value: 1} ); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event should not be trusted"); +is(e.value, 1, "value should be 1"); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); +e = new DeviceLightEvent("hello", {value: Infinity} ); +is(e.value, Infinity, "value should be positive infinity"); +e = new DeviceLightEvent("hello", {value: -Infinity} ); +is(e.value, -Infinity, "value should be negative infinity"); +e = new DeviceLightEvent("hello"); +is(e.value, Infinity, "Uninitialized value should be positive infinity"); + +// DeviceOrientationEvent +e = new DeviceOrientationEvent("hello"); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event should not be trusted"); +is(e.alpha, null); +is(e.beta, null); +is(e.gamma, null); +is(e.absolute, false); + +e = new DeviceOrientationEvent("hello", { alpha: 1, beta: 2, gamma: 3, absolute: true } ); +is(e.type, "hello", "Wrong event type!"); +ok(!e.isTrusted, "Event should not be trusted"); +is(e.alpha, 1); +is(e.beta, 2); +is(e.gamma, 3); +is(e.absolute, true); +document.dispatchEvent(e); +is(receivedEvent, e, "Wrong event!"); + +// MouseEvent + +try { + e = new MouseEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "MouseEvent: First parameter is required!"); +ex = false; + +e = new MouseEvent("hello", { buttons: 1, movementX: 2, movementY: 3}); +is(e.type, "hello", "MouseEvent: Wrong event type!"); +ok(!e.isTrusted, "MouseEvent: Event shouldn't be trusted!"); +ok(!e.bubbles, "MouseEvent: Event shouldn't bubble!"); +ok(!e.cancelable, "MouseEvent: Event shouldn't be cancelable!"); +is(e.buttons, 1); +is(e.movementX, 2); +is(e.movementY, 3); +document.dispatchEvent(e); +is(receivedEvent, e, "MouseEvent: Wrong event!"); + +var mouseEventProps = +[ { screenX: 0 }, + { screenY: 0 }, + { clientX: 0 }, + { clientY: 0 }, + { ctrlKey: false }, + { shiftKey: false }, + { altKey: false }, + { metaKey: false }, + { modifierAltGraph: false }, + { modifierCapsLock: false }, + { modifierFn: false }, + { modifierFnLock: false }, + { modifierNumLock: false }, + { modifierOS: false }, + { modifierScrollLock: false }, + { modifierSymbol: false }, + { modifierSymbolLock: false }, + { button: 0 }, + { buttons: 0 }, + { relatedTarget: null }, +]; + +var testProps = +[ + { screenX: 1 }, + { screenY: 2 }, + { clientX: 3 }, + { clientY: 4 }, + { ctrlKey: true }, + { shiftKey: true }, + { altKey: true }, + { metaKey: true }, + { modifierAltGraph: true }, + { modifierCapsLock: true }, + { modifierFn: true }, + { modifierFnLock: true }, + { modifierNumLock: true }, + { modifierOS: true }, + { modifierScrollLock: true }, + { modifierSymbol: true }, + { modifierSymbolLock: true }, + { button: 5 }, + { buttons: 6 }, + { relatedTarget: window } +]; + +var defaultMouseEventValues = {}; +for (var i = 0; i < mouseEventProps.length; ++i) { + for (prop in mouseEventProps[i]) { + if (!isMethodResultInitializer(prop)) { + ok(prop in e, "MouseEvent: MouseEvent doesn't have property " + prop + "!"); + } + defaultMouseEventValues[prop] = mouseEventProps[i][prop]; + } +} + +while (testProps.length) { + var p = testProps.shift(); + e = new MouseEvent("foo", p); + for (var def in defaultMouseEventValues) { + if (!(def in p)) { + is(getPropValue(e, def), defaultMouseEventValues[def], + "MouseEvent: Wrong default value for " + def + "!"); + } else { + is(getPropValue(e, def), p[def], "MouseEvent: Wrong event init value for " + def + "!"); + } + } +} + +// PopupBlockedEvent + +try { + e = new PopupBlockedEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "PopupBlockedEvent: First parameter is required!"); +ex = false; + +e = new PopupBlockedEvent("hello"); +is(e.type, "hello", "PopupBlockedEvent: Wrong event type!"); +ok(!e.isTrusted, "PopupBlockedEvent: Event shouldn't be trusted!"); +ok(!e.bubbles, "PopupBlockedEvent: Event shouldn't bubble!"); +ok(!e.cancelable, "PopupBlockedEvent: Event shouldn't be cancelable!"); +document.dispatchEvent(e); +is(receivedEvent, e, "PopupBlockedEvent: Wrong event!"); + +e = new PopupBlockedEvent("hello", + { requestingWindow: window, + popupWindowFeatures: "features", + popupWindowName: "name" + }); +is(e.requestingWindow, window); +is(e.popupWindowFeatures, "features"); +is(e.popupWindowName, "name"); + +// WheelEvent + +try { + e = new WheelEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "WheelEvent: First parameter is required!"); +ex = false; + +e = new WheelEvent("hello", { buttons: 1, movementX: 2, movementY: 3}); +is(e.type, "hello", "WheelEvent: Wrong event type!"); +is(e.buttons, 1); +is(e.movementX, 2); +is(e.movementY, 3); +ok(!e.isTrusted, "WheelEvent: Event shouldn't be trusted!"); +ok(!e.bubbles, "WheelEvent: Event shouldn't bubble!"); +ok(!e.cancelable, "WheelEvent: Event shouldn't be cancelable!"); +document.dispatchEvent(e); +is(receivedEvent, e, "WheelEvent: Wrong event!"); + +var wheelEventProps = +[ { screenX: 0 }, + { screenY: 0 }, + { clientX: 0 }, + { clientY: 0 }, + { ctrlKey: false }, + { shiftKey: false }, + { altKey: false }, + { metaKey: false }, + { modifierAltGraph: false }, + { modifierCapsLock: false }, + { modifierFn: false }, + { modifierFnLock: false }, + { modifierNumLock: false }, + { modifierOS: false }, + { modifierScrollLock: false }, + { modifierSymbol: false }, + { modifierSymbolLock: false }, + { button: 0 }, + { buttons: 0 }, + { relatedTarget: null }, + { deltaX: 0.0 }, + { deltaY: 0.0 }, + { deltaZ: 0.0 }, + { deltaMode: 0 } +]; + +var testWheelProps = +[ + { screenX: 1 }, + { screenY: 2 }, + { clientX: 3 }, + { clientY: 4 }, + { ctrlKey: true }, + { shiftKey: true }, + { altKey: true }, + { metaKey: true }, + { modifierAltGraph: true }, + { modifierCapsLock: true }, + { modifierFn: true }, + { modifierFnLock: true }, + { modifierNumLock: true }, + { modifierOS: true }, + { modifierScrollLock: true }, + { modifierSymbol: true }, + { modifierSymbolLock: true }, + { button: 5 }, + { buttons: 6 }, + { relatedTarget: window }, + { deltaX: 7.8 }, + { deltaY: 9.1 }, + { deltaZ: 2.3 }, + { deltaMode: 4 } +]; + +var defaultWheelEventValues = {}; +for (var i = 0; i < wheelEventProps.length; ++i) { + for (prop in wheelEventProps[i]) { + if (!isMethodResultInitializer(prop)) { + ok(prop in e, "WheelEvent: WheelEvent doesn't have property " + prop + "!"); + } + defaultWheelEventValues[prop] = wheelEventProps[i][prop]; + } +} + +while (testWheelProps.length) { + var p = testWheelProps.shift(); + e = new WheelEvent("foo", p); + for (var def in defaultWheelEventValues) { + if (!(def in p)) { + is(getPropValue(e, def), defaultWheelEventValues[def], + "WheelEvent: Wrong default value for " + def + "!"); + } else { + is(getPropValue(e, def), p[def], "WheelEvent: Wrong event init value for " + def + "!"); + } + } +} + +// DragEvent + +try { + e = new DragEvent(); +} catch(exp) { + ex = true; +} +ok(ex, "DragEvent: First parameter is required!"); +ex = false; + +e = new DragEvent("hello", { buttons: 1, movementX: 2, movementY: 3}); +is(e.type, "hello", "DragEvent: Wrong event type!"); +is(e.buttons, 1); +is(e.movementX, 2); +is(e.movementY, 3); +document.dispatchEvent(e); +is(receivedEvent, e, "DragEvent: Wrong event!"); + +// TransitionEvent +e = new TransitionEvent("hello", { propertyName: "color", elapsedTime: 3.5, pseudoElement: "", foobar: "baz" }) +is("propertyName" in e, true, "Transition events have propertyName property"); +is("foobar" in e, false, "Transition events do not copy random properties from event init"); +is(e.propertyName, "color", "Transition event copies propertyName from TransitionEventInit"); +is(e.elapsedTime, 3.5, "Transition event copies elapsedTime from TransitionEventInit"); +is(e.pseudoElement, "", "Transition event copies pseudoElement from TransitionEventInit"); +is(e.bubbles, false, "Lack of bubbles property in TransitionEventInit"); +is(e.cancelable, false, "Lack of cancelable property in TransitionEventInit"); +is(e.type, "hello", "Wrong event type!"); +is(e.isTrusted, false, "Event shouldn't be trusted!"); +is(e.eventPhase, Event.NONE, "Wrong event phase"); + +// AnimationEvent +e = new AnimationEvent("hello", { animationName: "bounce3", elapsedTime: 3.5, pseudoElement: "", foobar: "baz" }) +is("animationName" in e, true, "Animation events have animationName property"); +is("foobar" in e, false, "Animation events do not copy random properties from event init"); +is(e.animationName, "bounce3", "Animation event copies animationName from AnimationEventInit"); +is(e.elapsedTime, 3.5, "Animation event copies elapsedTime from AnimationEventInit"); +is(e.pseudoElement, "", "Animation event copies pseudoElement from AnimationEventInit"); +is(e.bubbles, false, "Lack of bubbles property in AnimationEventInit"); +is(e.cancelable, false, "Lack of cancelable property in AnimationEventInit"); +is(e.type, "hello", "Wrong event type!"); +is(e.isTrusted, false, "Event shouldn't be trusted!"); +is(e.eventPhase, Event.NONE, "Wrong event phase"); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_eventctors.xul b/dom/events/test/test_eventctors.xul new file mode 100644 index 000000000..ba3c81c8a --- /dev/null +++ b/dom/events/test/test_eventctors.xul @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=675884 +--> +<window title="Mozilla Bug 675884" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=675884" + target="_blank">Mozilla Bug 675884</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 675884 **/ + + // Most of the tests are in .html file, but test here that + // isTrusted is handled correctly in chrome. + + var receivedEvent; + document.addEventListener("hello", function(e) { receivedEvent = e; }, true); + + // Event + var e; + var ex = false; + try { + e = new Event(); + } catch(exp) { + ex = true; + } + ok(ex, "First parameter is required!"); + ex = false; + + e = new Event("hello"); + ok(e.type, "hello", "Wrong event type!"); + ok(e.isTrusted, "Event should be trusted!"); + ok(!e.bubbles, "Event shouldn't bubble!"); + ok(!e.cancelable, "Event shouldn't be cancelable!"); + document.dispatchEvent(e); + is(receivedEvent, e, "Wrong event!"); + + ]]> + </script> +</window> diff --git a/dom/events/test/test_eventhandler_scoping.html b/dom/events/test/test_eventhandler_scoping.html new file mode 100644 index 000000000..f15238a0c --- /dev/null +++ b/dom/events/test/test_eventhandler_scoping.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for event handler scoping</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var queryResult; +test(function() { + var d = document.createElement("div"); + d.setAttribute("onclick", "queryResult = querySelector('span')"); + var s = document.createElement("span"); + d.appendChild(s); + d.dispatchEvent(new Event("click")); + assert_equals(queryResult, s, "Should have gotten the right object"); +}, "Test for bareword calls in an event handler using the element as 'this'"); +</script> diff --git a/dom/events/test/test_focus_disabled.html b/dom/events/test/test_focus_disabled.html new file mode 100644 index 000000000..52748226a --- /dev/null +++ b/dom/events/test/test_focus_disabled.html @@ -0,0 +1,125 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=375008 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 375008</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"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375008">Mozilla Bug 375008</a> +<p id="display"></p> +<div id="content"> + <div id='not-focusable'> + <!-- Disabled elements --> + <button hidden disabled>foo</button> + <input hidden disabled> + <fieldset hidden disabled>foo</fieldset> + <select hidden disabled><option>foo</option></select> + <textarea hidden disabled></textarea> + <optgroup hidden disabled><option>foo</option></optgroup> + <option hidden disabled>foo</option> + </div> + + <div id='focusable'> + <button hidden>foo</button> + <input hidden> + <select hidden><option>foo</option></select> + <textarea hidden></textarea> + + <!-- Those elements are not focusable by default. --> + <fieldset tabindex=1 hidden>foo</fieldset> + <optgroup tabindex=1 hidden><option>foo</option></optgroup> + <option tabindex=1 hidden>foo</option> + </div> + + <!-- Hidden elements, they have a frame but focus will go through them. --> + <div id='hidden' style='visibility: hidden;'> + <button hidden>foo</button> + <input hidden> + <fieldset hidden>foo</fieldset> + <select hidden><option>foo</option></select> + <textarea hidden></textarea> + <optgroup hidden><option>foo</option></optgroup> + <option hidden>foo</option> + </div> + + <div> + <input id='witness'> + </div> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 375008 **/ + +/* + * This test is divided in three parts: + * - cases where focus isn't doable but blur should not happen; + * - cases where focus is doable; + * - cases where focus isn't doable but blur should still happen. + */ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + // On Mac, this preference needs to be turned on to be able to focus all the + // form controls we want to focus. + SpecialPowers.pushPrefEnv({"set": [[ "accessibility.mouse_focuses_formcontrol", true ]]}, + runTest); +}); + +function runTest() +{ + var witness = document.getElementById('witness'); + witness.focus(); + + var notFocusableElements = document.getElementById('not-focusable').children; + for (var i=0; i<notFocusableElements.length; ++i) { + var element = notFocusableElements[i]; + element.hidden = false; + synthesizeMouseAtCenter(element, {}); + is(document.activeElement, witness, + "[" + element.tagName + "] witness should still be focused"); + + // Cleanup. + element.hidden = true; + witness.focus(); + } + + var focusableElements = document.getElementById('focusable').children; + for (var i=0; i<focusableElements.length; ++i) { + var element = focusableElements[i]; + element.hidden = false; + synthesizeMouseAtCenter(element, {}); + is(document.activeElement, element, "focus should have moved to " + element); + + // Cleanup. + element.hidden = true; + witness.focus(); + } + + var hiddenElements = document.getElementById('hidden').children; + for (var i=0; i<hiddenElements.length; ++i) { + var element = hiddenElements[i]; + element.hidden = false; + synthesizeMouseAtCenter(element, {}); + is(document.activeElement, document.body, + "focus should have moved to the body"); + + // Cleanup. + element.hidden = true; + witness.focus(); + } + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_legacy_event.html b/dom/events/test/test_legacy_event.html new file mode 100644 index 000000000..d772be106 --- /dev/null +++ b/dom/events/test/test_legacy_event.html @@ -0,0 +1,304 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1236979 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1236979 (events that have legacy alternative versions)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + @keyframes anim1 { + 0% { margin-left: 0px } + 100% { margin-left: 100px } + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1236979">Mozilla Bug 1236979</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 1236979 **/ + +'use strict'; +SimpleTest.waitForExplicitFinish(); + +// Array of info-bundles about each legacy event to be tested: +var gLegacyEventInfo = [ + { + legacy_name: "webkitTransitionEnd", + modern_name: "transitionend", + trigger_event: triggerShortTransition, + }, + { + legacy_name: "webkitAnimationStart", + modern_name: "animationstart", + trigger_event: triggerShortAnimation, + }, + { + legacy_name: "webkitAnimationEnd", + modern_name: "animationend", + trigger_event: triggerShortAnimation, + }, + { + legacy_name: "webkitAnimationIteration", + modern_name: "animationiteration", + trigger_event: triggerAnimationIteration, + } +]; + +// EVENT-TRIGGERING FUNCTIONS +// -------------------------- +// This function triggers a very short (1ms long) transition, which will cause +// events to fire for the transition ending. +function triggerShortTransition(node) { + node.style.transition = "1ms color linear" ; + node.style.color = "purple"; + // Flush style, so that the above assignment value actually takes effect + // in the computed style, so that a transition will get triggered when it + // changes. + window.getComputedStyle(node, "").color; + node.style.color = "teal"; +} + +// This function triggers a very short (1ms long) animation, which will cause +// events to fire for the animation beginning & ending. +function triggerShortAnimation(node) { + node.style.animation = "anim1 1ms linear"; +} + +// This function triggers a long animation with two iterations, which is +// *nearly* at the end of its first iteration. It will hit the end of that +// iteration (firing an event) almost immediately, 1ms in the future. +// +// NOTE: It's important that this animation have a *long* duration. If it were +// short (e.g. 1ms duration), then we might jump past all its iterations in +// a single refresh-driver tick. And if that were to happens, we'd *never* fire +// any animationiteration events -- the CSS Animations spec says this event +// must not be fired "...when an animationend event would fire at the same time" +// (which would be the case in this example with a 1ms duration). So, to make +// sure our event does fire, we use a long duration and a nearly-as-long +// negative delay. This ensures we hit the end of the first iteration right +// away, and that we don't risk hitting the end of the second iteration at the +// same time. +function triggerAnimationIteration(node) { + node.style.animation = "anim1 300s -299.999s linear 2"; +} + +// GENERAL UTILITY FUNCTIONS +// ------------------------- +// Creates a new div and appends it as a child of the specified parentNode, or +// (if no parent is specified) as a child of the element with ID 'display'. +function createChildDiv(parentNode) { + if (!parentNode) { + parentNode = document.getElementById("display"); + if (!parentNode) { + ok(false, "no 'display' element to append to"); + } + } + var div = document.createElement("div"); + parentNode.appendChild(div); + return div; +} + +// Returns an event-handler function, which (when invoked) simply checks that +// the event's type matches what's expected. If a callback is passed in, then +// the event-handler will invoke that callback as well. +function createHandlerWithTypeCheck(expectedEventType, extraHandlerLogic) { + var handler = function(e) { + is(e.type, expectedEventType, + "When an event handler for '" + expectedEventType + "' is invoked, " + + "the event's type field should be '" + expectedEventType + "'."); + if (extraHandlerLogic) { + extraHandlerLogic(e); + } + } + return handler; +} + +// TEST FUNCTIONS +// -------------- +// These functions expect to be passed an entry from gEventInfo, and they +// return a Promise which performs the test & resolves when it's complete. +// The function names all begin with "mp", which stands for "make promise". +// So e.g. "mpTestLegacyEventSent" means "make a promise to test that the +// legacy event is sent". + +// Tests that the legacy event type is sent, when only a legacy handler is +// registered. +function mpTestLegacyEventSent(eventInfo) { + return new Promise( + function(resolve, reject) { + // Create a node & register an event-handler for the legacy event: + var div = createChildDiv(); + + var handler = createHandlerWithTypeCheck(eventInfo.legacy_name, + function() { + // When event-handler is done, clean up & resolve: + div.parentNode.removeChild(div); + resolve(); + }); + div.addEventListener(eventInfo.legacy_name, handler); + + // Trigger the event: + eventInfo.trigger_event(div); + } + ); +} + +// Test that the modern event type (and only the modern event type) is fired, +// when listeners of both modern & legacy types are registered. The legacy +// listener should not be invoked. +function mpTestModernBeatsLegacy(eventInfo) { + return new Promise( + function(resolve, reject) { + var div = createChildDiv(); + + var legacyHandler = function(e) { + reject("Handler for legacy event '" + eventInfo.legacy_name + + "' should not be invoked when there's a handler registered " + + "for both modern & legacy event type on the same node"); + }; + + var modernHandler = createHandlerWithTypeCheck(eventInfo.modern_name, + function() { + // Indicate that the test has passed (we invoked the modern handler): + ok(true, "Handler for modern event '" + eventInfo.modern_name + + "' should be invoked when there's a handler registered for " + + "both modern & legacy event type on the same node"); + // When event-handler is done, clean up & resolve: + div.parentNode.removeChild(div); + resolve(); + }); + + div.addEventListener(eventInfo.legacy_name, legacyHandler); + div.addEventListener(eventInfo.modern_name, modernHandler); + eventInfo.trigger_event(div); + } + ); +} + +// Test that an event which bubbles may fire listeners of different flavors +// (modern vs. legacy) at each bubbling level, depending on what's registered +// at that level. +function mpTestDiffListenersEventBubbling(eventInfo) { + return new Promise( + function(resolve, reject) { + var grandparent = createChildDiv(); + var parent = createChildDiv(grandparent); + var target = createChildDiv(parent); + var didEventFireOnTarget = false; + var didEventFireOnParent = false; + var eventSentToTarget; + + target.addEventListener(eventInfo.modern_name, + createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { + ok(e.bubbles, "Expecting event to bubble"); + eventSentToTarget = e; + didEventFireOnTarget = true; + })); + + parent.addEventListener(eventInfo.legacy_name, + createHandlerWithTypeCheck(eventInfo.legacy_name, function(e) { + is(e, eventSentToTarget, + "Same event object should bubble, despite difference in type"); + didEventFireOnParent = true; + })); + + grandparent.addEventListener(eventInfo.modern_name, + createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { + ok(didEventFireOnTarget, + "Event should have fired on child"); + ok(didEventFireOnParent, + "Event should have fired on parent"); + is(e, eventSentToTarget, + "Same event object should bubble, despite difference in type"); + // Clean up. + grandparent.parentNode.removeChild(grandparent); + resolve(); + })); + + eventInfo.trigger_event(target); + } + ); +} + +// Test that an event in the capture phase may fire listeners of different +// flavors (modern vs. legacy) at each level, depending on what's registered +// at that level. +function mpTestDiffListenersEventCapturing(eventInfo) { + return new Promise( + function(resolve, reject) { + var grandparent = createChildDiv(); + var parent = createChildDiv(grandparent); + var target = createChildDiv(parent); + var didEventFireOnTarget = false; + var didEventFireOnParent = false; + var didEventFireOnGrandparent = false; + var eventSentToGrandparent; + + grandparent.addEventListener(eventInfo.modern_name, + createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { + eventSentToGrandparent = e; + didEventFireOnGrandparent = true; + }), true); + + parent.addEventListener(eventInfo.legacy_name, + createHandlerWithTypeCheck(eventInfo.legacy_name, function(e) { + is(e.eventPhase, Event.CAPTURING_PHASE, + "event should be in capturing phase"); + is(e, eventSentToGrandparent, + "Same event object should capture, despite difference in type"); + ok(didEventFireOnGrandparent, + "Event should have fired on grandparent"); + didEventFireOnParent = true; + }), true); + + target.addEventListener(eventInfo.modern_name, + createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { + is(e.eventPhase, Event.AT_TARGET, + "event should be at target phase"); + is(e, eventSentToGrandparent, + "Same event object should capture, despite difference in type"); + ok(didEventFireOnParent, + "Event should have fired on parent"); + // Clean up. + grandparent.parentNode.removeChild(grandparent); + resolve(); + }), true); + + eventInfo.trigger_event(target); + } + ); +} + +// MAIN FUNCTION: Kick off the tests. +function main() { + Promise.resolve().then(function() { + return Promise.all(gLegacyEventInfo.map(mpTestLegacyEventSent)) + }).then(function() { + return Promise.all(gLegacyEventInfo.map(mpTestModernBeatsLegacy)); + }).then(function() { + return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventCapturing)); + }).then(function() { + return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventBubbling)); + }).then(function() { + SimpleTest.finish(); + }).catch(function(reason) { + ok(false, "Test failed: " + reason); + SimpleTest.finish(); + }); +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_messageEvent.html b/dom/events/test/test_messageEvent.html new file mode 100644 index 000000000..0402649af --- /dev/null +++ b/dom/events/test/test_messageEvent.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=848294 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 848294</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + function testMessageEvent(e, test) { + ok(e, "MessageEvent created"); + is(e.type, 'message', 'MessageEvent.type is right'); + + is(e.data, 'data' in test ? test.data : null, 'MessageEvent.data is ok'); + is(e.origin, 'origin' in test ? test.origin : '', 'MessageEvent.origin is ok'); + is(e.lastEventId, 'lastEventId' in test ? test.lastEventId : '', 'MessageEvent.lastEventId is ok'); + is(e.source, 'source' in test ? test.source : null, 'MessageEvent.source is ok'); + + if (test.ports != undefined) { + is(e.ports.length, test.ports.length, 'MessageEvent.ports is ok'); + is(e.ports, e.ports, 'MessageEvent.ports is ok'); + } else { + ok(!('ports' in test) || test.ports == null, 'MessageEvent.ports is ok'); + } + } + + function runTest() { + var channel = new MessageChannel(); + + var tests = [ + {}, + { data: 42 }, + { data: {} }, + { data: true, origin: 'wow' }, + { data: [], lastEventId: 'wow2' }, + { data: null, source: null }, + { data: window, source: window }, + { data: window, source: channel.port1 }, + { data: window, source: channel.port1, ports: [ channel.port1, channel.port2 ] }, + { data: null, ports: [] }, + ]; + + while (tests.length) { + var test = tests.shift(); + + var e = new MessageEvent('message', test); + testMessageEvent(e, test); + + e = new MessageEvent('message'); + e.initMessageEvent('message', true, true, + 'data' in test ? test.data : null, + 'origin' in test ? test.origin : '', + 'lastEventId' in test ? test.lastEventId : '', + 'source' in test ? test.source : null, + 'ports' in test ? test.ports : []); + testMessageEvent(e, test); + } + + try { + var e = new MessageEvent('foobar', { source: 42 }); + ok(false, "Source has to be a window or a port"); + } catch(e) { + ok(true, "Source has to be a window or a port"); + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + runTest(); + </script> +</body> +</html> diff --git a/dom/events/test/test_messageEvent_init.html b/dom/events/test/test_messageEvent_init.html new file mode 100644 index 000000000..0e015b3be --- /dev/null +++ b/dom/events/test/test_messageEvent_init.html @@ -0,0 +1,25 @@ +<html><head> +<title>Test for bug 1308956</title> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + +<body> + <script> + +var a = new MessageEvent("message") +ok(!!a, "We have a MessageEvent"); +is(a.ports.length, 0, "By default MessageEvent.ports is an empty array"); + +a.initMessageEvent("message", true, false, {}, window.location.href, "", null, []); +ok(Array.isArray(a.ports), "After InitMessageEvent() we have an array"); +is(a.ports.length, 0, "Length is 0"); + +var mc = new MessageChannel(); +a.initMessageEvent("message", true, false, {}, window.location.href, "", null, [mc.port1]); +ok(Array.isArray(a.ports), "After InitMessageEvent() we have an array"); +is(a.ports.length, 1, "Length is 1"); + + </script> +</body> +</html> diff --git a/dom/events/test/test_moz_mouse_pixel_scroll_event.html b/dom/events/test/test_moz_mouse_pixel_scroll_event.html new file mode 100644 index 000000000..c2919ce44 --- /dev/null +++ b/dom/events/test/test_moz_mouse_pixel_scroll_event.html @@ -0,0 +1,1363 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for MozMousePixelScroll events</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + .scrollable { + overflow: auto; + line-height: 1; + margin: 15px; + } + .scrollable > div { + width: 1000px; + height: 1000px; + font-size: 1000px; + line-height: 1; + } + </style> +</head> +<body> +<p id="display"></p> +<div id="Scrollable128" class="scrollable" style="font-size: 128px; width: 100px; height: 100px;"> + <div> + <div id="Scrollable96" class="scrollable" style="font-size: 96px; width: 150px; height: 150px;"> + <div> + <div id="Scrollable64" class="scrollable" style="font-size: 64px; width: 200px; height: 200px;"> + <div> + </div> + </div> + </div> + </div> + </div> +</div> +<div id="Scrollable32" class="scrollable" style="font-size: 32px; width: 50px; height: 50px;"> + <div> + </div> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(startTest, window); + +var gScrollable128 = document.getElementById("Scrollable128"); +var gScrollable96 = document.getElementById("Scrollable96"); +var gScrollable64 = document.getElementById("Scrollable64"); +var gScrollable32 = document.getElementById("Scrollable32"); +var gRoot = document.documentElement; + +function* prepareScrollUnits() +{ + var result = -1; + function handler(aEvent) + { + result = aEvent.detail; + aEvent.preventDefault(); + setTimeout(runTest, 0); + } + window.addEventListener("MozMousePixelScroll", handler, true); + + yield waitForAllPaints(function () { setTimeout(runTest, 0); }); + + yield synthesizeWheel(gScrollable128, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable128.wheelLineHeight = result; + ok(result > 96 && result < 200, "prepareScrollUnits: gScrollable128.wheelLineHeight may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable96, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable96.wheelLineHeight = result; + ok(result > 64 && result < gScrollable128.wheelLineHeight, "prepareScrollUnits: gScrollable96.wheelLineHeight may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable64, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable64.wheelLineHeight = result; + ok(result > 32 && result < gScrollable96.wheelLineHeight, "prepareScrollUnits: gScrollable64.wheelLineHeight may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable32, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable32.wheelLineHeight = result; + ok(result > 16 && result < gScrollable64.wheelLineHeight, "prepareScrollUnits: gScrollable32.wheelLineHeight may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gRoot, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gRoot.wheelLineHeight = result; + ok(result > 10 && result < gScrollable32.wheelLineHeight, "prepareScrollUnits: gRoot.wheelLineHeight may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable128, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable128.wheelHorizontalLine = result; + ok(result > 50 && result < 200, "prepareScrollUnits: gScrollable128.wheelHorizontalLine may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable96, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable96.wheelHorizontalLine = result; + ok(result > 30 && result < gScrollable128.wheelHorizontalLine, "prepareScrollUnits: gScrollable96.wheelHorizontalLine may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable64, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable64.wheelHorizontalLine = result; + ok(result > 20 && result < gScrollable96.wheelHorizontalLine, "prepareScrollUnits: gScrollable64.wheelHorizontalLine may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable32, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable32.wheelHorizontalLine = result; + ok(result > 12 && result < gScrollable64.wheelHorizontalLine, "prepareScrollUnits: gScrollable32.wheelHorizontalLine may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gRoot, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gRoot.wheelHorizontalLine = result; + ok(result > 5 && result < gScrollable32.wheelHorizontalLine, "prepareScrollUnits: gRoot.wheelHorizontalLine may be illegal value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable128, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable128.wheelPageHeight = result; + ok(result >= (100 - gScrollable128.wheelLineHeight * 2) && result <= 100, + "prepareScrollUnits: gScrollable128.wheelLineHeight is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable96, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable96.wheelPageHeight = result; + ok(result >= (150 - gScrollable96.wheelLineHeight * 2) && result <= 150, + "prepareScrollUnits: gScrollable96.wheelLineHeight is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable64, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable64.wheelPageHeight = result; + ok(result >= (200 - gScrollable64.wheelLineHeight * 2) && result <= 200, + "prepareScrollUnits: gScrollable64.wheelLineHeight is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable32, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gScrollable32.wheelPageHeight = result; + ok(result >= (50 - gScrollable32.wheelLineHeight * 2) && result <= 50, + "prepareScrollUnits: gScrollable32.wheelLineHeight is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gRoot, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaY: 1.0, lineOrPageDeltaY: 1 }); + gRoot.wheelPageHeight = result; + ok(window.innerHeight - result < 100 && window.innerHeight - result > 0, + "prepareScrollUnits: gRoot.wheelLineHeight is strange value, got " + result); + + + result = -1; + yield synthesizeWheel(gScrollable128, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable128.wheelPageWidth = result; + ok(result >= (100 - gScrollable128.wheelLineHeight * 2) && result <= 100, + "prepareScrollUnits: gScrollable128.wheelPageWidth is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable96, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable96.wheelPageWidth = result; + ok(result >= (150 - gScrollable96.wheelLineHeight * 2) && result <= 150, + "prepareScrollUnits: gScrollable96.wheelPageWidth is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable64, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable64.wheelPageWidth = result; + ok(result >= (200 - gScrollable64.wheelLineHeight * 2) && result <= 200, + "prepareScrollUnits: gScrollable64.wheelPageWidth is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gScrollable32, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gScrollable32.wheelPageWidth = result; + ok(result >= (50 - gScrollable32.wheelLineHeight * 2) && result <= 50, + "prepareScrollUnits: gScrollable32.wheelPageWidth is strange value, got " + result); + + result = -1; + yield synthesizeWheel(gRoot, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, lineOrPageDeltaX: 1 }); + gRoot.wheelPageWidth = result; + ok(window.innerWidth - result < 100 && window.innerWidth - result > 0, + "prepareScrollUnits: gRoot.wheelPageWidth is strange value, got " + result); + + window.removeEventListener("MozMousePixelScroll", handler, true); +} + +function* doTests() +{ + const kTests = [ + // DOM_DELTA_LINE + { description: "Should be computed from nearest scrollable element, 128", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: gScrollable32 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: gScrollable32 + } + }, + { description: "Should be computed from root element if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: gRoot + } + }, + { description: "Should be computed from root element, even if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: gRoot + } + }, + { description: "Should be computed from nearest scrollable element, 128", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable32 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable32 + } + }, + { description: "Should be computed from root element if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: null, y: gRoot + } + }, + { description: "Should be computed from root element, even if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: null, y: gRoot + } + }, + { description: "Should be computed from nearest scrollable element, 128", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: null + } + }, + { description: "Should be computed from nearest scrollable element, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: null + } + }, + { description: "Should be computed from nearest scrollable element, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: null + } + }, + { description: "Should be computed from nearest scrollable element, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: null + } + }, + { description: "Should be computed from root element if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: null + } + }, + { description: "Should be computed from root element, even if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: null + } + }, + + // DOM_DELTA_PAGE + { description: "Should be computed from nearest scrollable element, 128", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: gScrollable32 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: gScrollable32 + } + }, + { description: "Should be computed from root element if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: gRoot + } + }, + { description: "Should be computed from root element, even if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: -1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: gRoot + } + }, + { description: "Should be computed from nearest scrollable element, 128", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable128 + } + }, + { description: "Should be computed from nearest scrollable element, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable96 + } + }, + { description: "Should be computed from nearest scrollable element, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable32 + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: null, y: gScrollable32 + } + }, + { description: "Should be computed from root element if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: null, y: gRoot + } + }, + { description: "Should be computed from root element, even if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: null, y: gRoot + } + }, + { description: "Should be computed from nearest scrollable element, 128", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction", + target: gScrollable128, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable128.scrollLeft = 0; + gScrollable128.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable128, y: null + } + }, + { description: "Should be computed from nearest scrollable element, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 96", + target: gScrollable96, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable96, y: null + } + }, + { description: "Should be computed from nearest scrollable element, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable64, y: null + } + }, + { description: "Should be computed from nearest scrollable element, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: null + } + }, + { description: "Should be computed from nearest scrollable element, even if not scrollable to the direction, 32", + target: gScrollable32, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable32.scrollLeft = 0; + gScrollable32.scrollTop = 0; + }, + cleanup: function () { + }, + expected: { + x: gScrollable32, y: null + } + }, + { description: "Should be computed from root element if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: null + } + }, + { description: "Should be computed from root element, even if there is no scrollable element, root", + target: gRoot, + event: { + deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + prepare: function () { + }, + cleanup: function () { + }, + expected: { + x: gRoot, y: null + } + }, + + // Overflow: hidden; boxes shouldn't be ignored. + { description: "Should be computed from nearest scrollable element even if it hides overflow content, 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.style.overflow = "hidden"; + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + gScrollable64.style.overflow = "auto"; + }, + expected: { + x: gScrollable64, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element even if it hides overflow content (X), 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.style.overflowX = "hidden"; + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + gScrollable64.style.overflow = "auto"; + }, + expected: { + x: gScrollable64, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element even if it hides overflow content (Y), 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.style.overflowY = "hidden"; + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + gScrollable64.style.overflow = "auto"; + }, + expected: { + x: gScrollable64, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element even if it hides overflow content (X), 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.style.overflowX = "hidden"; + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + gScrollable64.style.overflow = "auto"; + }, + expected: { + x: null, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element even if it hides overflow content (Y), 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + prepare: function () { + gScrollable64.style.overflowY = "hidden"; + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + gScrollable64.style.overflow = "auto"; + }, + expected: { + x: null, y: gScrollable64 + } + }, + { description: "Should be computed from nearest scrollable element even if it hides overflow content (X), 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable64.style.overflowX = "hidden"; + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + gScrollable64.style.overflow = "auto"; + }, + expected: { + x: gScrollable64, y: null + } + }, + { description: "Should be computed from nearest scrollable element even if it hides overflow content (Y), 64", + target: gScrollable64, + event: { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + prepare: function () { + gScrollable64.style.overflowY = "hidden"; + gScrollable96.scrollLeft = 0; + gScrollable96.scrollTop = 0; + gScrollable64.scrollLeft = 0; + gScrollable64.scrollTop = 0; + }, + cleanup: function () { + gScrollable64.style.overflow = "auto"; + }, + expected: { + x: gScrollable64, y: null + } + }, + ]; + + var currentTest, description, firedX, firedY; + var expectedHandlerCalls; + + function handler(aEvent) + { + aEvent.preventDefault(); + + if (aEvent.axis != MouseScrollEvent.HORIZONTAL_AXIS && + aEvent.axis != MouseScrollEvent.VERTICAL_AXIS) { + ok(false, + description + "The event had invalid axis (" + aEvent.axis + ")"); + if (--expectedHandlerCalls == 0) { + setTimeout(runTest, 0); + } + return; + } + + var isHorizontal = (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS); + if ((isHorizontal && !currentTest.expected.x) || + (!isHorizontal && !currentTest.expected.y)) { + ok(false, + description + "The event fired unexpectedly (" + + (isHorizontal ? "Horizontal" : "Vertical") + ")"); + if (--expectedHandlerCalls == 0) { + setTimeout(runTest, 0); + } + return; + } + + if (isHorizontal) { + firedX = true; + } else { + firedY = true; + } + + var expectedDetail = + (currentTest.event.deltaMode == WheelEvent.DOM_DELTA_LINE) ? + (isHorizontal ? currentTest.expected.x.wheelHorizontalLine : + currentTest.expected.y.wheelLineHeight) : + (isHorizontal ? currentTest.expected.x.wheelPageWidth : + currentTest.expected.y.wheelPageHeight); + is(Math.abs(aEvent.detail), expectedDetail, + description + ((isHorizontal) ? "horizontal" : "vertical") + " event detail is wrong"); + + if (--expectedHandlerCalls == 0) { + setTimeout(runTest, 0); + } + } + + window.addEventListener("MozMousePixelScroll", handler, true); + + for (var i = 0; i < kTests.length; i++) { + currentTest = kTests[i]; + description = "doTests, " + currentTest.description + " (deltaMode: " + + (currentTest.event.deltaMode == WheelEvent.DOM_DELTA_LINE ? + "DOM_DELTA_LINE" : "DOM_DELTA_PAGE") + + ", deltaX: " + currentTest.event.deltaX + + ", deltaY: " + currentTest.event.deltaY + "): "; + currentTest.prepare(); + firedX = firedY = false; + expectedHandlerCalls = (currentTest.expected.x ? 1 : 0) + + (currentTest.expected.y ? 1 : 0); + yield synthesizeWheel(currentTest.target, 10, 10, currentTest.event); + if (currentTest.expected.x) { + ok(firedX, description + "Horizontal MozMousePixelScroll event wasn't fired"); + } + if (currentTest.expected.y) { + ok(firedY, description + "Vertical MozMousePixelScroll event wasn't fired"); + } + currentTest.cleanup(); + } + + window.removeEventListener("MozMousePixelScroll", handler, true); +} + +function* testBody() +{ + yield* prepareScrollUnits(); + yield* doTests(); +} + +var gTestContinuation = null; + +function runTest() +{ + if (!gTestContinuation) { + gTestContinuation = testBody(); + } + var ret = gTestContinuation.next(); + if (ret.done) { + SimpleTest.finish(); + } +} + +function startTest() { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.delta_multiplier_x", 100], + ["mousewheel.default.delta_multiplier_y", 100], + ["mousewheel.default.delta_multiplier_z", 100], + ["mousewheel.with_alt.delta_multiplier_x", 100], + ["mousewheel.with_alt.delta_multiplier_y", 100], + ["mousewheel.with_alt.delta_multiplier_z", 100], + ["mousewheel.with_control.delta_multiplier_x", 100], + ["mousewheel.with_control.delta_multiplier_y", 100], + ["mousewheel.with_control.delta_multiplier_z", 100], + ["mousewheel.with_meta.delta_multiplier_x", 100], + ["mousewheel.with_meta.delta_multiplier_y", 100], + ["mousewheel.with_meta.delta_multiplier_z", 100], + ["mousewheel.with_shift.delta_multiplier_x", 100], + ["mousewheel.with_shift.delta_multiplier_y", 100], + ["mousewheel.with_shift.delta_multiplier_z", 100], + ["mousewheel.with_win.delta_multiplier_x", 100], + ["mousewheel.with_win.delta_multiplier_y", 100], + ["mousewheel.with_win.delta_multiplier_z", 100], + // If APZ is enabled we should ensure the preventDefault calls work even + // if the test is running slowly. + ["apz.content_response_timeout", 2000], + ]}, runTest); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_offsetxy.html b/dom/events/test/test_offsetxy.html new file mode 100644 index 000000000..849b1ea67 --- /dev/null +++ b/dom/events/test/test_offsetxy.html @@ -0,0 +1,98 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM MouseEvent offsetX/Y</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<div id="d" style="position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div> +<div id="d2" style="position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px; transform:translateX(100px)"></div> +<div id="d3" style="display:none; position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div> +<div id="d4" style="transform:scale(0); position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div> + +<pre id="test"> +<script type="application/javascript"> + +var offsetX = -1, offsetY = -1; +var ev = new MouseEvent("click", {clientX:110, clientY:110}); +is(ev.offsetX, 110); +is(ev.offsetY, 110); +is(ev.offsetX, ev.pageX); +is(ev.offsetY, ev.pageY); +d.addEventListener("click", function (event) { + is(ev, event, "Event objects must match"); + offsetX = event.offsetX; + offsetY = event.offsetY; +}); +d.dispatchEvent(ev); +is(offsetX, 5); +is(offsetY, 5); +is(ev.offsetX, 5); +is(ev.offsetY, 5); + +var ev2 = new MouseEvent("click", {clientX:220, clientY:130}); +is(ev2.offsetX, 220); +is(ev2.offsetY, 130); +is(ev2.offsetX, ev2.pageX); +is(ev2.offsetY, ev2.pageY); +d2.addEventListener("click", function (event) { + is(ev2, event, "Event objects must match"); + offsetX = event.offsetX; + offsetY = event.offsetY; +}); +d2.dispatchEvent(ev2); +is(offsetX, 15); +is(offsetY, 25); +is(ev2.offsetX, 15); +is(ev2.offsetY, 25); + +var ev3 = new MouseEvent("click", {clientX:110, clientY:110}); +is(ev3.offsetX, 110); +is(ev3.offsetY, 110); +is(ev3.offsetX, ev3.pageX); +is(ev3.offsetY, ev3.pageY); +d3.addEventListener("click", function (event) { + is(ev3, event, "Event objects must match"); + offsetX = event.offsetX; + offsetY = event.offsetY; +}); +d3.dispatchEvent(ev3); +is(offsetX, 0); +is(offsetY, 0); +is(ev3.offsetX, 0); +is(ev3.offsetY, 0); + +var ev4 = new MouseEvent("click", {clientX:110, clientY:110}); +is(ev4.offsetX, 110); +is(ev4.offsetY, 110); +is(ev4.offsetX, ev4.pageX); +is(ev4.offsetY, ev4.pageY); +d4.addEventListener("click", function (event) { + is(ev4, event, "Event objects must match"); + offsetX = event.offsetX; + offsetY = event.offsetY; +}); +d4.dispatchEvent(ev4); +is(offsetX, 0); +is(offsetY, 0); +is(ev4.offsetX, 0); +is(ev4.offsetY, 0); + +// Now redispatch ev4 to "d" to make sure that its offsetX gets updated +// relative to the new target. Have to set "ev" to "ev4", because the listener +// on "d" expects to see "ev" as the event. +ev = ev4; +d.dispatchEvent(ev4); +is(offsetX, 5); +is(offsetY, 5); +is(ev.offsetX, 5); +is(ev.offsetY, 5); +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/test_onerror_handler_args.html b/dom/events/test/test_onerror_handler_args.html new file mode 100644 index 000000000..629434814 --- /dev/null +++ b/dom/events/test/test_onerror_handler_args.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1007790 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1007790</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1007790 **/ + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + is(frames[0].onerror.toString(), + "function onerror(event, source, lineno, colno, error) {\n\n}", + "Should have the right arguments for onerror on window"); + is($("content").onerror.toString(), + "function onerror(event) {\n\n}", + "Should have the right arguments for onerror on element"); + SimpleTest.finish(); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1007790">Mozilla Bug 1007790</a> +<p id="display"></p> +<div id="content" style="display: none" onerror=""> + <iframe src="data:text/html,<body onerror=''>"></iframe> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_passive_listeners.html b/dom/events/test/test_passive_listeners.html new file mode 100644 index 000000000..d55d16a5f --- /dev/null +++ b/dom/events/test/test_passive_listeners.html @@ -0,0 +1,118 @@ +<html> +<head> + <title>Tests for passive event listeners</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + +<body> +<p id="display"></p> +<div id="dummy"> +</div> + +<script> +var listenerHitCount; +var doPreventDefault; + +function listener(e) +{ + listenerHitCount++; + if (doPreventDefault) { + // When this function is registered as a passive listener, this + // call should be a no-op and might report a console warning. + e.preventDefault(); + } +} + +function listener2(e) +{ + if (doPreventDefault) { + e.preventDefault(); + } +} + +var elem = document.getElementById('dummy'); + +function doTest(description, passiveArg) +{ + listenerHitCount = 0; + + elem.addEventListener('test', listener, { passive: passiveArg }); + + // Test with a cancelable event + var e1 = new Event('test', { cancelable: true }); + elem.dispatchEvent(e1); + is(listenerHitCount, 1, description + ' | hit count'); + var expectedDefaultPrevented = (doPreventDefault && !passiveArg); + is(e1.defaultPrevented, expectedDefaultPrevented, description + ' | default prevented'); + + // Test with a non-cancelable event + var e2 = new Event('test', { cancelable: false }); + elem.dispatchEvent(e2); + is(listenerHitCount, 2, description + ' | hit count after non-cancelable event'); + is(e2.defaultPrevented, false, description + ' | default prevented on non-cancelable event'); + + // Test combining passive-enabled and "traditional" listeners + elem.addEventListener('test', listener2, false); + var e3 = new Event('test', { cancelable: true }); + elem.dispatchEvent(e3); + is(listenerHitCount, 3, description + ' | hit count with second listener'); + is(e3.defaultPrevented, doPreventDefault, description + ' | default prevented with second listener'); + elem.removeEventListener('test', listener2, false); + + elem.removeEventListener('test', listener, false); +} + +function testAddListenerKey(passiveListenerFirst) +{ + listenerHitCount = 0; + doPreventDefault = true; + + elem.addEventListener('test', listener, { capture: false, passive: passiveListenerFirst }); + // This second listener should not be registered, because the "key" of + // { type, callback, capture } is the same, even though the 'passive' flag + // is different. + elem.addEventListener('test', listener, { capture: false, passive: !passiveListenerFirst }); + + var e1 = new Event('test', { cancelable: true }); + elem.dispatchEvent(e1); + + is(listenerHitCount, 1, 'Duplicate addEventListener was correctly ignored'); + is(e1.defaultPrevented, !passiveListenerFirst, 'Prevent-default result based on first registered listener'); + + // Even though passive is the opposite of the first addEventListener call, it + // should remove the listener registered above. + elem.removeEventListener('test', listener, { capture: false, passive: !passiveListenerFirst }); + + var e2 = new Event('test', { cancelable: true }); + elem.dispatchEvent(e2); + + is(listenerHitCount, 1, 'non-passive listener was correctly unregistered'); + is(e2.defaultPrevented, false, 'no listener was registered to preventDefault this event'); +} + +function test() +{ + doPreventDefault = false; + + doTest('base case', undefined); + doTest('non-passive listener', false); + doTest('passive listener', true); + + doPreventDefault = true; + + doTest('base case', undefined); + doTest('non-passive listener', false); + doTest('passive listener', true); + + testAddListenerKey(false); + testAddListenerKey(true); +} + +test(); + +</script> + +</body> +</html> + + diff --git a/dom/events/test/test_paste_image.html b/dom/events/test/test_paste_image.html new file mode 100644 index 000000000..716fafeaf --- /dev/null +++ b/dom/events/test/test_paste_image.html @@ -0,0 +1,196 @@ +<html><head> +<title>Test for bug 891247</title> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + +<script class="testbody" type="application/javascript"> + function ImageTester() { + var counter = 0; + var images = []; + var that = this; + + this.add = function(aFile) { + images.push(aFile); + }; + + this.test = function() { + for (var i = 0; i < images.length; i++) { + testImageSize(images[i]); + } + }; + + this.returned = function() { + counter++; + info("returned=" + counter + " images.length=" + images.length); + if (counter == images.length) { + info("test finish"); + SimpleTest.finish(); + } + }; + + function testImageSize(aFile) { + var source = window.URL.createObjectURL(aFile); + var image = new Image(); + image.src = source; + var imageTester = that; + image.onload = function() { + is(this.width, 62, "Check generated image width"); + is(this.height, 71, "Check generated image height"); + if (aFile.type == "image/gif") { + // this test fails for image/jpeg and image/png because the images + // generated are slightly different + testImageCanvas(image); + } + + imageTester.returned(); + } + + document.body.appendChild(image); + }; + + function testImageCanvas(aImage) { + var canvas = drawToCanvas(aImage); + + var refImage = document.getElementById('image'); + var refCanvas = drawToCanvas(refImage); + + is(canvas.toDataURL(), refCanvas.toDataURL(), "Image should map pixel-by-pixel"); + } + + function drawToCanvas(aImage) { + var canvas = document.createElement("CANVAS"); + document.body.appendChild(canvas); + canvas.width = aImage.width; + canvas.height = aImage.height; + canvas.getContext('2d').drawImage(aImage, 0, 0); + return canvas; + } + } + + function copyImage(aImageId) { + // selection of the node + var node = document.getElementById(aImageId); + var webnav = SpecialPowers.wrap(window) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + + var docShell = webnav.QueryInterface(SpecialPowers.Ci.nsIDocShell); + + // let's copy the node + var documentViewer = docShell.contentViewer + .QueryInterface(SpecialPowers.Ci.nsIContentViewerEdit); + documentViewer.setCommandNode(node); + documentViewer.copyImage(documentViewer.COPY_IMAGE_ALL); + } + + function doTest() { + SimpleTest.waitForExplicitFinish(); + + copyImage('image'); + + //--------- now check the content of the clipboard + var clipboard = SpecialPowers.Cc["@mozilla.org/widget/clipboard;1"] + .getService(SpecialPowers.Ci.nsIClipboard); + // does the clipboard contain text/unicode data ? + ok(clipboard.hasDataMatchingFlavors(["text/unicode"], 1, clipboard.kGlobalClipboard), + "clipboard contains unicode text"); + // does the clipboard contain text/html data ? + ok(clipboard.hasDataMatchingFlavors(["text/html"], 1, clipboard.kGlobalClipboard), + "clipboard contains html text"); + // does the clipboard contain image data ? + ok(clipboard.hasDataMatchingFlavors(["image/png"], 1, clipboard.kGlobalClipboard), + "clipboard contains image"); + + window.addEventListener("paste", onPaste); + + var textarea = SpecialPowers.wrap(document.getElementById('textarea')); + textarea.focus(); + textarea.editor.paste(clipboard.kGlobalClipboard); + } + + function onPaste(e) { + var imageTester = new ImageTester; + testFiles(e, imageTester); + testItems(e, imageTester); + imageTester.test(); + } + + function testItems(e, imageTester) { + var items = e.clipboardData.items; + is(items, e.clipboardData.items, + "Getting @items twice should return the same object"); + var haveFiles = false; + ok(items instanceof DataTransferItemList, "@items implements DataTransferItemList"); + ok(items.length > 0, "@items is not empty"); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + ok(item instanceof DataTransferItem, "each element of @items must implement DataTransferItem"); + if (item.kind == "file") { + var file = item.getAsFile(); + ok(file instanceof File, ".getAsFile() returns a File object"); + ok(file.size > 0, "Files shouldn't have size 0"); + imageTester.add(file); + } + } + } + + function testFiles(e, imageTester) { + var files = e.clipboardData.files; + + is(files, e.clipboardData.files, + "Getting the files array twice should return the same array"); + ok(files.length > 0, "There should be at least one file in the clipboard"); + for (var i = 0; i < files.length; i++) { + var file = files[i]; + ok(file instanceof File, ".files should contain only File objects"); + ok(file.size > 0, "This file shouldn't have size 0"); + if (file.name == "image.png") { + is(file.type, "image/png", "This file should be a image/png"); + } else if (file.name == "image.jpeg") { + is(file.type, "image/jpeg", "This file should be a image/jpeg"); + } else if (file.name == "image.gif") { + is(file.type, "image/gif", "This file should be a image/gif"); + } else { + info("file.name=" + file.name); + ok(false, "Unexpected file name"); + } + + testSlice(file); + imageTester.add(file); + // Adding the same image again so we can test concurrency + imageTester.add(file); + } + } + + function testSlice(aFile) { + var blob = aFile.slice(); + ok(blob instanceof Blob, ".slice returns a blob"); + is(blob.size, aFile.size, "the blob has the same size"); + + blob = aFile.slice(123123); + is(blob.size, 0, ".slice overflow check"); + + blob = aFile.slice(123, 123141); + is(blob.size, aFile.size - 123, ".slice @size check"); + + blob = aFile.slice(123, 12); + is(blob.size, 0, ".slice @size check 2"); + + blob = aFile.slice(124, 134, "image/png"); + is(blob.size, 10, ".slice @size check 3"); + is(blob.type, "image/png", ".slice @type check"); + } + +</script> +<body onload="doTest();"> + <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABHCA + IAAADQjmMaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3goUAwAgSAORBwAAABl0RVh0Q29 + tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABPSURBVGje7c4BDQAACAOga//OmuMbJGAurTbq + 6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6s31B0IqAY2/t + QVCAAAAAElFTkSuQmCC" /> + <form> + <textarea id="textarea"></textarea> + </form> +</body> +</html> diff --git a/dom/events/test/test_wheel_default_action.html b/dom/events/test/test_wheel_default_action.html new file mode 100644 index 000000000..5ead8be63 --- /dev/null +++ b/dom/events/test/test_wheel_default_action.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for default action of WheelEvent</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"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +SpecialPowers.pushPrefEnv({"set": [ + ["mousewheel.system_scroll_override_on_root_content.enabled", false] +]}, runTest); + +var subWin = null; + +function runTest() { + subWin = window.open("window_wheel_default_action.html", "_blank", + "width=500,height=500,scrollbars=yes"); +} + +function finish() +{ + subWin.close(); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/events/test/window_bug493251.html b/dom/events/test/window_bug493251.html new file mode 100644 index 000000000..d2891a9ef --- /dev/null +++ b/dom/events/test/window_bug493251.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script> + SimpleTest.waitForExplicitFinish(); + SimpleTest.waitForFocus(window.opener.doTest, window); + </script> +</head> +<body> + <input> +</body> +</html> diff --git a/dom/events/test/window_bug617528.xul b/dom/events/test/window_bug617528.xul new file mode 100644 index 000000000..fc87f9d78 --- /dev/null +++ b/dom/events/test/window_bug617528.xul @@ -0,0 +1,9 @@ +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="640" height="480"> + + <browser id="browser" type="content-primary" flex="1" src="about:blank" + disablehistory="true" disablesecurity="true"/> + +</window> diff --git a/dom/events/test/window_bug659071.html b/dom/events/test/window_bug659071.html new file mode 100644 index 000000000..5fb2efed4 --- /dev/null +++ b/dom/events/test/window_bug659071.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 659071</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"/> +</head> +<body> +<video id="v" controls></video> +<script type="application/javascript"> + +SimpleTest.waitForFocus(startTests, window); +SimpleTest.requestFlakyTimeout("untriaged"); + +function is() +{ + window.opener.is.apply(window.opener, arguments); +} + +function isnot() +{ + window.opener.isnot.apply(window.opener, arguments); +} + +function hitEventLoop(aFunc, aTimes) +{ + if (--aTimes) { + setTimeout(hitEventLoop, 0, aFunc, aTimes); + } else { + setTimeout(aFunc, 20); + } +} + +function startTests() { + SpecialPowers.pushPrefEnv({"set": [["mousewheel.with_control.action", 3]]}, runTests); +} + +function runTests() +{ + synthesizeKey("0", { accelKey: true }); + + var video = document.getElementById("v"); + hitEventLoop(function () { + is(SpecialPowers.getFullZoom(window), 1.0, + "failed to reset zoom"); + synthesizeWheel(video, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, ctrlKey: true, + deltaX: 0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }); + hitEventLoop(function () { + isnot(SpecialPowers.getFullZoom(window), 1.0, + "failed to zoom by ctrl+wheel"); + + synthesizeWheel(video, 10, 10, + { deltaMode: WheelEvent.DOM_DELTA_LINE, ctrlKey: true, + deltaX: 0, deltaY: 1.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }); + hitEventLoop(function () { + is(SpecialPowers.getFullZoom(window), 1.0, + "failed to reset zoom"); + + hitEventLoop(window.opener.finish, 20); + }, 20); + }, 20); + }, 20); +} + +</script> +</body> +</html> diff --git a/dom/events/test/window_wheel_default_action.html b/dom/events/test/window_wheel_default_action.html new file mode 100644 index 000000000..fa6abf188 --- /dev/null +++ b/dom/events/test/window_wheel_default_action.html @@ -0,0 +1,1848 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for default action of WheelEvent</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="scrollable" style="overflow: auto; width: 200px; height: 200px;"> + <div id="clipper" style="margin: 0; padding: 0; overflow: hidden; width: 3000px; height: 3000px;"> + <div id="scrolled" style="width: 5000px; height: 5000px;"> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br> + </div> + </div> +</div> +<div id="spacerForBody"></div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForFocus(runTests, window); +SimpleTest.requestFlakyTimeout("untriaged"); + +var winUtils = SpecialPowers.getDOMWindowUtils(window); +// grab refresh driver +winUtils.advanceTimeAndRefresh(100); + +var gScrollableElement = document.getElementById("scrollable"); +var gScrolledElement = document.getElementById("scrolled"); +var gSpacerForBodyElement = document.getElementById("spacerForBody"); + +function is() +{ + window.opener.is.apply(window.opener, arguments); +} + +function ok() +{ + window.opener.ok.apply(window.opener, arguments); +} + +function sendWheelAndWait(aX, aY, aEvent, aCallback) +{ + sendWheelAndPaint(gScrollableElement, aX, aY, aEvent, aCallback); +} + +function hitEventLoop(aFunc, aTimes) +{ + winUtils.advanceTimeAndRefresh(100); + + if (--aTimes) { + setTimeout(hitEventLoop, 0, aFunc, aTimes); + } else { + setTimeout(aFunc, 20); + } +} + +const zoomResetTopic = "browser-fullZoom:zoomReset"; +SpecialPowers.registerObservers(zoomResetTopic); + +function onZoomReset(aCallback) { + var specialPowersTopic = "specialpowers-" + zoomResetTopic; + SpecialPowers.addObserver(function observe() { + SpecialPowers.removeObserver(observe, specialPowersTopic); + SimpleTest.executeSoon(aCallback); + }, specialPowersTopic, false); +} + +function setDeltaMultiplierSettings(aSettings, aCallback) +{ + SpecialPowers.pushPrefEnv({"set": [ + ["mousewheel.default.delta_multiplier_x", aSettings.deltaMultiplierX * 100], + ["mousewheel.default.delta_multiplier_y", aSettings.deltaMultiplierY * 100], + ["mousewheel.default.delta_multiplier_z", aSettings.deltaMultiplierZ * 100], + ["mousewheel.with_alt.delta_multiplier_x", aSettings.deltaMultiplierX * 100], + ["mousewheel.with_alt.delta_multiplier_y", aSettings.deltaMultiplierY * 100], + ["mousewheel.with_alt.delta_multiplier_z", aSettings.deltaMultiplierZ * 100], + ["mousewheel.with_control.delta_multiplier_x", aSettings.deltaMultiplierX * 100], + ["mousewheel.with_control.delta_multiplier_y", aSettings.deltaMultiplierY * 100], + ["mousewheel.with_control.delta_multiplier_z", aSettings.deltaMultiplierZ * 100], + ["mousewheel.with_meta.delta_multiplier_x", aSettings.deltaMultiplierX * 100], + ["mousewheel.with_meta.delta_multiplier_y", aSettings.deltaMultiplierY * 100], + ["mousewheel.with_meta.delta_multiplier_z", aSettings.deltaMultiplierZ * 100], + ["mousewheel.with_shift.delta_multiplier_x", aSettings.deltaMultiplierX * 100], + ["mousewheel.with_shift.delta_multiplier_y", aSettings.deltaMultiplierY * 100], + ["mousewheel.with_shift.delta_multiplier_z", aSettings.deltaMultiplierZ * 100], + ["mousewheel.with_win.delta_multiplier_x", aSettings.deltaMultiplierX * 100], + ["mousewheel.with_win.delta_multiplier_y", aSettings.deltaMultiplierY * 100], + ["mousewheel.with_win.delta_multiplier_z", aSettings.deltaMultiplierZ * 100] + ]}, aCallback); +} + +function doTestScroll(aSettings, aCallback) +{ + const kNoScroll = 0x00; + const kScrollUp = 0x01; + const kScrollDown = 0x02; + const kScrollLeft = 0x04; + const kScrollRight = 0x08; + + const kTests = [ + { description: "Scroll to bottom by pixel scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to bottom by pixel scroll when lineOrPageDelta is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to top by pixel scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to top by pixel scroll when lineOrPageDelta is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to right by pixel scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to right by pixel scroll when lineOrPageDelta is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to left by pixel scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to left by pixel scroll when lineOrPageDelta is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to bottom-right by pixel scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollRight }, + { description: "Scroll to bottom-left by pixel scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollLeft }, + { description: "Scroll to top-left by pixel scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollLeft }, + { description: "Scroll to top-right by pixel scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollRight }, + { description: "Not Scroll by pixel scroll for z", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + + { description: "Scroll to bottom by line scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to bottom by line scroll when lineOrPageDelta is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to top by line scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to top by line scroll when lineOrPageDelta is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to right by line scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to right by line scroll when lineOrPageDelta is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to left by line scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to left by line scroll when lineOrPageDelta is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to bottom-right by line scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollRight }, + { description: "Scroll to bottom-left by line scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.5, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollLeft }, + { description: "Scroll to top-left by line scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.5, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollLeft }, + { description: "Scroll to top-right by line scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollRight }, + { description: "Not Scroll by line scroll for z", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + + { description: "Scroll to bottom by page scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to bottom by page scroll when lineOrPageDelta is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to top by page scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to top by page scroll when lineOrPageDelta is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to right by page scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to right by page scroll when lineOrPageDelta is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to left by page scroll even if lineOrPageDelta is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to left by page scroll when lineOrPageDelta is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to bottom-right by page scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollRight }, + { description: "Scroll to bottom-left by page scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.5, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollLeft }, + { description: "Scroll to top-left by page scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.5, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollLeft }, + { description: "Scroll to top-right by page scroll", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollRight }, + { description: "Not Scroll by page scroll for z", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + + // special cases. + + // momentum scroll should cause scroll even if the action is zoom, but if the default action is none, + // shouldn't do it. + { description: "Scroll to bottom by momentum pixel scroll when lineOrPageDelta is 0, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to bottom by momentum pixel scroll when lineOrPageDelta is 1, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to top by momentum pixel scroll when lineOrPageDelta is 0, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to top by momentum pixel scroll when lineOrPageDelta is -1, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to right by momentum pixel scroll when lineOrPageDelta is 0, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to right by momentum pixel scroll when lineOrPageDelta is 1, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to left by momentum pixel scroll when lineOrPageDelta is 0, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to left by momentum pixel scroll when lineOrPageDelta is -1, even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to bottom-right by momentum pixel scroll even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollRight }, + { description: "Scroll to bottom-left by momentum pixel scroll even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollLeft }, + { description: "Scroll to top-left by momentum pixel scroll even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollLeft }, + { description: "Scroll to top-right by momentum pixel scroll even if the action is zoom", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollRight }, + { description: "Not Scroll by momentum pixel scroll for z (action is zoom)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + { description: "Not Scroll by momentum pixel scroll if default action is none (action is zoom)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: true, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll, + prepare: function (cb) { SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.action", 0]]}, cb); }, + cleanup: function (cb) { SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.action", 1]]}, cb); } }, + + // momentum scroll should cause scroll even if the action is history, but if the default action is none, + // shouldn't do it. + { description: "Scroll to bottom by momentum pixel scroll when lineOrPageDelta is 0, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to bottom by momentum pixel scroll when lineOrPageDelta is 1, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to top by momentum pixel scroll when lineOrPageDelta is 0, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to top by momentum pixel scroll when lineOrPageDelta is -1, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to right by momentum pixel scroll when lineOrPageDelta is 0, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to right by momentum pixel scroll when lineOrPageDelta is 1, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to left by momentum pixel scroll when lineOrPageDelta is 0, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to left by momentum pixel scroll when lineOrPageDelta is -1, even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to bottom-right by momentum pixel scroll even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollRight }, + { description: "Scroll to bottom-left by momentum pixel scroll even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown | kScrollLeft }, + { description: "Scroll to top-left by momentum pixel scroll even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollLeft }, + { description: "Scroll to top-right by momentum pixel scroll even if the action is history", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp | kScrollRight }, + { description: "Not Scroll by momentum pixel scroll for z (action is history)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + { description: "Not Scroll by momentum pixel scroll if default action is none (action is history)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: true, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: true, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll, + prepare: function (cb) { SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.action", 0]]}, cb); }, + cleanup: function (cb) { SpecialPowers.pushPrefEnv({"set": [["mousewheel.default.action", 1]]}, cb); } }, + + // Don't scroll along axis whose overflow style is hidden. + { description: "Scroll to only bottom by oblique pixel wheel event with overflow-x: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 1, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown, + prepare: function(cb) { gScrollableElement.style.overflowX = "hidden"; cb(); } }, + { description: "Scroll to only bottom by oblique line wheel event with overflow-x: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 1, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to only bottom by oblique page wheel event with overflow-x: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 1, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to only top by oblique pixel wheel event with overflow-x: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: -16.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: -1, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to only top by oblique line wheel event with overflow-x: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: -1, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to only top by oblique page wheel event with overflow-x: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: -1, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp, + cleanup: function (cb) { gScrollableElement.style.overflowX = "auto"; cb(); } }, + { description: "Scroll to only right by oblique pixel wheel event with overflow-y: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight, + prepare: function(cb) { gScrollableElement.style.overflowY = "hidden"; cb(); } }, + { description: "Scroll to only right by oblique line wheel event with overflow-y: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to only right by oblique page wheel event with overflow-y: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to only left by oblique pixel wheel event with overflow-y: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: -16.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: -1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to only top by oblique line wheel event with overflow-y: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: -1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to only top by oblique page wheel event with overflow-y: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: -1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft, + cleanup: function (cb) { gScrollableElement.style.overflowY = "auto"; cb(); } }, + { description: "Don't be scrolled by oblique pixel wheel event with overflow: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 1, expectedOverflowDeltaY: 1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll, + prepare: function(cb) { gScrollableElement.style.overflow = "hidden"; cb(); } }, + { description: "Don't be scrolled by oblique line wheel event with overflow: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 1, expectedOverflowDeltaY: 1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + { description: "Don't be scrolled by oblique page wheel event with overflow: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 1, expectedOverflowDeltaY: 1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + { description: "Don't be scrolled by oblique pixel wheel event with overflow: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: -16.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: -1, expectedOverflowDeltaY: -1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + { description: "Don't be scrolled by oblique line wheel event with overflow: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: -1, expectedOverflowDeltaY: -1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll }, + { description: "Don't be scrolled by oblique page wheel event with overflow: hidden", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: -1, expectedOverflowDeltaY: -1, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kNoScroll, + cleanup: function (cb) { gScrollableElement.style.overflow = "auto"; cb(); } }, + + // Don't scroll along axis whose overflow style is hidden and overflow delta values should + // be zero if there is ancestor scrollable element. + { description: "Scroll to only bottom by oblique pixel wheel event with overflow-x: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown, + prepare: function(cb) { + gScrollableElement.style.overflowX = "hidden"; + gScrollableElement.style.position = "fixed"; + gScrollableElement.style.top = "30px"; + gScrollableElement.style.left = "30px"; + // Make body element scrollable. + gSpacerForBodyElement.style.width = "5000px"; + gSpacerForBodyElement.style.height = "5000px"; + document.documentElement.scrollTop = 500; + document.documentElement.scrollLeft = 500; + cb(); + } }, + { description: "Scroll to only bottom by oblique line wheel event with overflow-x: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to only bottom by oblique page wheel event with overflow-x: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollDown }, + { description: "Scroll to only top by oblique pixel wheel event with overflow-x: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: -16.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to only top by oblique line wheel event with overflow-x: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp }, + { description: "Scroll to only top by oblique page wheel event with overflow-x: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollUp, + cleanup: function (cb) { gScrollableElement.style.overflowX = "auto"; cb(); } }, + { description: "Scroll to only right by oblique pixel wheel event with overflow-y: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, deltaY: 16.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight, + prepare: function(cb) { gScrollableElement.style.overflowY = "hidden"; cb(); } }, + { description: "Scroll to only right by oblique line wheel event with overflow-y: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to only right by oblique page wheel event with overflow-y: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 1.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollRight }, + { description: "Scroll to only left by oblique pixel wheel event with overflow-y: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -16.0, deltaY: -16.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to only top by oblique line wheel event with overflow-y: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft }, + { description: "Scroll to only top by oblique page wheel event with overflow-y: hidden (body is scrollable)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: -1.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false }, + expected: kScrollLeft, + cleanup: function (cb) { + gScrollableElement.style.overflowY = "auto"; + gScrollableElement.style.position = "static"; + gSpacerForBodyElement.style.width = ""; + gSpacerForBodyElement.style.height = ""; + cb(); + } }, + ]; + + var description; + + var currentTestIndex = -1; + var isXReverted = (aSettings.deltaMultiplierX < 0); + var isYReverted = (aSettings.deltaMultiplierY < 0); + + function doNextTest() + { + if (++currentTestIndex >= kTests.length) { + SimpleTest.executeSoon(aCallback); + return; + } + + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + + var currentTest = kTests[currentTestIndex]; + description = "doTestScroll(aSettings=" + aSettings.description + "), " + currentTest.description + ": "; + if (currentTest.prepare) { + currentTest.prepare(doTestCurrentScroll); + } else { + doTestCurrentScroll(); + } + } + + function doTestCurrentScroll() { + var currentTest = kTests[currentTestIndex]; + sendWheelAndWait(10, 10, currentTest.event, function () { + if (currentTest.expected == kNoScroll) { + is(gScrollableElement.scrollTop, 1000, description + "scrolled vertical"); + is(gScrollableElement.scrollLeft, 1000, description + "scrolled horizontal"); + } else { + var scrollUp = !isYReverted ? (currentTest.expected & kScrollUp) : + (currentTest.expected & kScrollDown); + var scrollDown = !isYReverted ? (currentTest.expected & kScrollDown) : + (currentTest.expected & kScrollUp); + if (scrollUp) { + ok(gScrollableElement.scrollTop < 1000, description + "not scrolled up, got " + gScrollableElement.scrollTop); + } else if (scrollDown) { + ok(gScrollableElement.scrollTop > 1000, description + "not scrolled down, got " + gScrollableElement.scrollTop); + } else { + is(gScrollableElement.scrollTop, 1000, description + "scrolled vertical"); + } + var scrollLeft = !isXReverted ? (currentTest.expected & kScrollLeft) : + (currentTest.expected & kScrollRight); + var scrollRight = !isXReverted ? (currentTest.expected & kScrollRight) : + (currentTest.expected & kScrollLeft); + if (scrollLeft) { + ok(gScrollableElement.scrollLeft < 1000, description + "not scrolled to left, got " + gScrollableElement.scrollLeft); + } else if (scrollRight) { + ok(gScrollableElement.scrollLeft > 1000, description + "not scrolled to right, got " + gScrollableElement.scrollLeft); + } else { + is(gScrollableElement.scrollLeft, 1000, description + "scrolled horizontal"); + } + } + if (currentTest.cleanup) { + currentTest.cleanup(nextStep); + } else { + nextStep(); + } + + function nextStep() { + winUtils.advanceTimeAndRefresh(100); + doNextTest(); + } + }); + } + doNextTest(); +} + +function doTestZoom(aSettings, aCallback) +{ + if ((aSettings.deltaMultiplierX != 1.0 && aSettings.deltaMultiplierX != -1.0) || + (aSettings.deltaMultiplierY != 1.0 && aSettings.deltaMultiplierY != -1.0)) { + todo(false, "doTestZoom doesn't support to test with aSettings=" + aSettings.description); + SimpleTest.executeSoon(aCallback); + return; + } + + const kNone = 0x00; + const kPositive = 0x01; + const kNegative = 0x02; + const kUseX = 0x10; + const kUseY = 0x20; + const kTests = [ + { description: "by vertical/positive pixel event when its lineOrPageDeltaY is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by vertical/positive pixel event when its lineOrPageDeltaY is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kPositive | kUseY }, + { description: "by vertical/negative pixel event when its lineOrPageDeltaY is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by vertical/negative pixel event when its lineOrPageDeltaY is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNegative | kUseY }, + { description: "by horizotal/positive pixel event when its lineOrPageDeltaX is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by horizotal/positive pixel event when its lineOrPageDeltaX is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kPositive | kUseX }, + { description: "by horizotal/negative pixel event when its lineOrPageDeltaX is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by horizotal/negative pixel event when its lineOrPageDeltaX is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNegative | kUseX }, + { description: "by z pixel event", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 0.0, deltaZ: 16.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + + { description: "by vertical/positive line event when its lineOrPageDeltaY is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by vertical/positive line event when its lineOrPageDeltaY is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kPositive | kUseY }, + { description: "by vertical/negative line event when its lineOrPageDeltaY is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by vertical/negative line event when its lineOrPageDeltaY is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNegative | kUseY }, + { description: "by horizotal/positive line event when its lineOrPageDeltaX is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by horizotal/positive line event when its lineOrPageDeltaX is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kPositive | kUseX }, + { description: "by horizotal/negative line event when its lineOrPageDeltaX is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by horizotal/negative line event when its lineOrPageDeltaX is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNegative | kUseX }, + { description: "by z line event", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + + { description: "by vertical/positive page event when its lineOrPageDeltaY is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by vertical/positive page event when its lineOrPageDeltaY is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kPositive | kUseY }, + { description: "by vertical/negative page event when its lineOrPageDeltaY is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by vertical/negative page event when its lineOrPageDeltaY is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -0.5, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNegative | kUseY }, + { description: "by horizotal/positive page event when its lineOrPageDeltaX is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by horizotal/positive page event when its lineOrPageDeltaX is 1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kPositive | kUseX }, + { description: "by horizotal/negative page event when its lineOrPageDeltaX is 0", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + { description: "by horizotal/negative page event when its lineOrPageDeltaX is -1", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -0.5, deltaY: 0.0, deltaZ: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNegative | kUseX }, + { description: "by z page event", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 0.0, deltaZ: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, + expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0 }, + expected: kNone }, + ]; + + var description, currentTest; + var currentTestIndex = -1; + var isXReverted = (aSettings.deltaMultiplierX < 0); + var isYReverted = (aSettings.deltaMultiplierY < 0); + + function doNextTest() + { + if (++currentTestIndex >= kTests.length) { + SimpleTest.executeSoon(aCallback); + return; + } + + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + + currentTest = kTests[currentTestIndex]; + description = "doTestZoom(aSettings=" + aSettings.description + "), "; + if (currentTest.expected == kNone) { + description += "Shouldn't "; + } else { + description += "Should "; + } + description += "zoom " + currentTest.description + ": "; + + var event = currentTest.event; + event.ctrlKey = true; + + // NOTE: Zooming might change scrollTop and scrollLeft by rounding fraction. + // This test assume that zoom happens synchronously and scrolling + // happens asynchronously. + var scrollTop = gScrollableElement.scrollTop; + var scrollLeft = gScrollableElement.scrollLeft; + + sendWheelAndWait(10, 10, event, function () { + is(gScrollableElement.scrollTop, scrollTop, description + "scrolled vertical"); + is(gScrollableElement.scrollLeft, scrollLeft, description + "scrolled horizontal"); + if (!(currentTest.expected & (kNegative | kPositive))) { + is(SpecialPowers.getFullZoom(window), 1.0, description + "zoomed"); + } else { + var isReverted = (currentTest.expected & kUseX) ? isXReverted : + (currentTest.expected & kUseY) ? isYReverted : false; + if ((!isReverted && (currentTest.expected & kNegative)) || + (isReverted && (currentTest.expected & kPositive))) { + ok(SpecialPowers.getFullZoom(window) > 1.0, + description + "not zoomed in, got " + SpecialPowers.getFullZoom(window)); + } else { + ok(SpecialPowers.getFullZoom(window) < 1.0, + description + "not zoomed out, got " + SpecialPowers.getFullZoom(window)); + } + } + + synthesizeKey("0", { accelKey: true }); + onZoomReset(function () { + hitEventLoop(doNextTest, 20); + }); + }); + } + doNextTest(); +} + +function doTestZoomedScroll(aCallback) +{ + var zoom = 1.0; + function setFullZoom(aWindow, aZoom) + { + zoom = aZoom; + SpecialPowers.setFullZoom(aWindow, aZoom); + } + + function prepareTestZoomedPixelScroll() + { + // Reset zoom and store the scroll amount into the data. + synthesizeKey("0", { accelKey: true }); + zoom = 1.0; + onZoomReset(testZoomedPixelScroll); + } + + function testZoomedPixelScroll() + { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + // Ensure not to be in reflow. + hitEventLoop(function () { + function mousePixelScrollHandler(aEvent) + { + if (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS) { + is(aEvent.detail, 16, + "doTestZoomedScroll: The detail of horizontal MozMousePixelScroll for pixel wheel event is wrong"); + } else if (aEvent.axis == MouseScrollEvent.VERTICAL_AXIS) { + is(aEvent.detail, 16, + "doTestZoomedScroll: The detail of vertical MozMousePixelScroll for pixel wheel event is wrong"); + } else { + ok(false, "doTestZoomedScroll: The axis of MozMousePixelScroll for pixel wheel event is invalid, got " + aEvent.axis); + } + } + function wheelHandler(aEvent) + { + is(aEvent.deltaX, 16.0 / zoom, + "doTestZoomedScroll: The deltaX of wheel for pixel is wrong"); + is(aEvent.deltaY, 16.0 / zoom, + "doTestZoomedScroll: The deltaY of wheel for pixel is wrong"); + } + window.addEventListener("MozMousePixelScroll", mousePixelScrollHandler, true); + window.addEventListener("wheel", wheelHandler, true); + var event = { + deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, + deltaY: 16.0, + lineOrPageDeltaX: 0, + lineOrPageDeltaY: 0 + }; + // wait scrolled actually. + sendWheelAndWait(10, 10, event, function () { + var scrolledX = gScrollableElement.scrollLeft; + var scrolledY = gScrollableElement.scrollTop; + ok(scrolledX > 1000, + "doTestZoomedScroll: scrolledX must be larger than 1000 for pixel wheel event, got " + scrolledX); + ok(scrolledY > 1000, + "doTestZoomedScroll: scrolledY must be larger than 1000 for pixel wheel event, got " + scrolledY); + + // Zoom + setFullZoom(window, 2.0); + // Ensure not to be in reflow. + hitEventLoop(function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + var event = { + deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 16.0, + deltaY: 16.0, + lineOrPageDeltaX: 0, + lineOrPageDeltaY: 0 + }; + // wait scrolled actually. + sendWheelAndWait(10, 10, event, function () { + ok(Math.abs(gScrollableElement.scrollLeft - (1000 + (scrolledX - 1000) / 2)) <= 1, + "doTestZoomedScroll: zoomed horizontal scroll amount by pixel wheel event is different from normal, scrollLeft=" + + gScrollableElement.scrollLeft + ", scrolledX=" + scrolledX); + ok(Math.abs(gScrollableElement.scrollTop - (1000 + (scrolledY - 1000) / 2)) <= 1, + "doTestZoomedScroll: zoomed vertical scroll amount by pixel wheel event is different from normal, scrollTop=" + + gScrollableElement.scrollTop + ", scrolledY=" + scrolledY); + window.removeEventListener("MozMousePixelScroll", mousePixelScrollHandler, true); + window.removeEventListener("wheel", wheelHandler, true); + + synthesizeKey("0", { accelKey: true }); + onZoomReset(prepareTestZoomedLineScroll); + }); + }, 20); + }); + }, 20); + } + + function prepareTestZoomedLineScroll() + { + // Reset zoom and store the scroll amount into the data. + synthesizeKey("0", { accelKey: true }); + zoom = 1.0; + onZoomReset(testZoomedLineScroll); + } + function testZoomedLineScroll() + { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + // Ensure not to be in reflow. + hitEventLoop(function () { + var lineHeightX, lineHeightY; + function handler(aEvent) + { + if (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS) { + if (lineHeightX == undefined) { + lineHeightX = aEvent.detail; + } else { + ok(Math.abs(aEvent.detail - lineHeightX) <= 1, + "doTestZoomedScroll: The detail of horizontal MozMousePixelScroll for line wheel event is wrong, aEvent.detail=" + + aEvent.detail + ", lineHeightX=" + lineHeightX); + } + } else if (aEvent.axis == MouseScrollEvent.VERTICAL_AXIS) { + if (lineHeightY == undefined) { + lineHeightY = aEvent.detail; + } else { + ok(Math.abs(aEvent.detail - lineHeightY) <= 1, + "doTestZoomedScroll: The detail of vertical MozMousePixelScroll for line wheel event is wrong, aEvent.detail=" + + aEvent.detail + ", lineHeightY=" + lineHeightY); + } + } else { + ok(false, "doTestZoomedScroll: The axis of MozMousePixelScroll for line wheel event is invalid, got " + aEvent.axis); + } + } + window.addEventListener("MozMousePixelScroll", handler, true); + var event = { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, + deltaY: 1.0, + lineOrPageDeltaX: 1, + lineOrPageDeltaY: 1 + }; + // wait scrolled actually. + sendWheelAndWait(10, 10, event, function () { + var scrolledX = gScrollableElement.scrollLeft; + var scrolledY = gScrollableElement.scrollTop; + ok(scrolledX > 1000, + "doTestZoomedScroll: scrolledX must be larger than 1000 for line wheel event, got " + scrolledX); + ok(scrolledY > 1000, + "doTestZoomedScroll: scrolledY must be larger than 1000 for line wheel event, got " + scrolledY); + + // Zoom + setFullZoom(window, 2.0); + // Ensure not to be in reflow. + hitEventLoop(function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + var event = { + deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, + deltaY: 1.0, + lineOrPageDeltaX: 1, + lineOrPageDeltaY: 1 + }; + // wait scrolled actually. + sendWheelAndWait(10, 10, event, function () { + ok(Math.abs(gScrollableElement.scrollLeft - scrolledX) <= 1, + "doTestZoomedScroll: zoomed horizontal scroll amount by line wheel event is different from normal, scrollLeft=" + + gScrollableElement.scrollLeft + ", scrolledX=" + scrolledX); + ok(Math.abs(gScrollableElement.scrollTop - scrolledY) <= 1, + "doTestZoomedScroll: zoomed vertical scroll amount by line wheel event is different from normal, scrollTop=" + + gScrollableElement.scrollTop + ", scrolledY=" + scrolledY); + + window.removeEventListener("MozMousePixelScroll", handler, true); + + synthesizeKey("0", { accelKey: true }); + onZoomReset(aCallback); + }); + }, 20); + }); + }, 20); + } + + // XXX It's too difficult to test page scroll because the page scroll amount + // is computed by complex logic. + + prepareTestZoomedPixelScroll(); +} + +function doTestWholeScroll(aCallback) +{ + SpecialPowers.pushPrefEnv({"set": [ + ["mousewheel.default.delta_multiplier_x", 999999], + ["mousewheel.default.delta_multiplier_y", 999999]]}, + function() { doTestWholeScroll2(aCallback); }); +} + +function doTestWholeScroll2(aCallback) +{ + const kTests = [ + { description: "try whole-scroll to top (line)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + expectedScrollTop: 0, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to top when scrollTop is already top-most (line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: -1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + expectedScrollTop: 0, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to bottom (line)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.0, deltaY: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + expectedScrollTop: gScrollableElement.scrollTopMax, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to bottom when scrollTop is already bottom-most (line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0, deltaY: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + expectedScrollTop: gScrollableElement.scrollTopMax, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to left (line)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: 0 + }, + { description: "try whole-scroll to left when scrollLeft is already left-most (line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: -1.0, deltaY: 0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: 0 + }, + { description: "try whole-scroll to right (line)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: gScrollableElement.scrollLeftMax + }, + { description: "try whole-scroll to right when scrollLeft is already right-most (line)", + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: gScrollableElement.scrollLeftMax + }, + + + { description: "try whole-scroll to top (pixel)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: 0, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to top when scrollTop is already top-most (pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: -1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: 0, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to bottom (pixel)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0.0, deltaY: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: gScrollableElement.scrollTopMax, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to bottom when scrollTop is already bottom-most (pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 0, deltaY: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: gScrollableElement.scrollTopMax, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to left (pixel)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -1.0, deltaY: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: 0 + }, + { description: "try whole-scroll to left when scrollLeft is already left-most (pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: -1.0, deltaY: 0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: 0 + }, + { description: "try whole-scroll to right (pixel)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 1.0, deltaY: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: gScrollableElement.scrollLeftMax + }, + { description: "try whole-scroll to right when scrollLeft is already right-most (pixel)", + event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL, + deltaX: 1.0, deltaY: 0.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: gScrollableElement.scrollLeftMax + }, + + + { description: "try whole-scroll to top (page)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + expectedScrollTop: 0, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to top when scrollTop is already top-most (page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: -1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: -1 }, + expectedScrollTop: 0, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to bottom (page)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0.0, deltaY: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + expectedScrollTop: gScrollableElement.scrollTopMax, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to bottom when scrollTop is already bottom-most (page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 0, deltaY: 1.0, + lineOrPageDeltaX: 0, lineOrPageDeltaY: 1 }, + expectedScrollTop: gScrollableElement.scrollTopMax, + expectedScrollLeft: 1000 + }, + { description: "try whole-scroll to left (page)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0.0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: 0 + }, + { description: "try whole-scroll to left when scrollLeft is already left-most (page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: -1.0, deltaY: 0, + lineOrPageDeltaX: -1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: 0 + }, + { description: "try whole-scroll to right (page)", + prepare: function () { + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + }, + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: gScrollableElement.scrollLeftMax + }, + { description: "try whole-scroll to right when scrollLeft is already right-most (page)", + event: { deltaMode: WheelEvent.DOM_DELTA_PAGE, + deltaX: 1.0, deltaY: 0.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 0 }, + expectedScrollTop: 1000, + expectedScrollLeft: gScrollableElement.scrollLeftMax + }, + ]; + + var index = 0; + + function doIt() + { + const kTest = kTests[index]; + if (kTest.prepare) { + kTest.prepare(); + } + sendWheelAndWait(10, 10, kTest.event, function () { + is(gScrollableElement.scrollTop, kTest.expectedScrollTop, + "doTestWholeScroll, " + kTest.description + ": unexpected scrollTop"); + is(gScrollableElement.scrollLeft, kTest.expectedScrollLeft, + "doTestWholeScroll, " + kTest.description + ": unexpected scrollLeft"); + if (++index == kTests.length) { + SimpleTest.executeSoon(aCallback); + } else { + doIt(); + } + }); + } + doIt(); +} + +function doTestActionOverride(aCallback) +{ + const kNoScroll = 0x00; + const kScrollUp = 0x01; + const kScrollDown = 0x02; + const kScrollLeft = 0x04; + const kScrollRight = 0x08; + + const kTests = [ + { action: 1, override_x: -1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 1, override_x: 0, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 1, override_x: 1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 0, override_x: -1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 0, override_x: 0, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 0, override_x: 1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 1, override_x: -1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.5, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 1, override_x: 0, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.5, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 1, override_x: 1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.5, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 0, override_x: -1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.5, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 0, override_x: 0, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.5, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 0, override_x: 1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 1.0, deltaY: 0.5, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 1, override_x: -1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 1, override_x: 0, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 1, override_x: 1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kScrollDown | kScrollRight + }, + { action: 0, override_x: -1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 0, override_x: 0, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + { action: 0, override_x: 1, + event: { deltaMode: WheelEvent.DOM_DELTA_LINE, + deltaX: 0.5, deltaY: 1.0, + lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 }, + expected: kNoScroll + }, + ]; + + var index = 0; + + function doIt() + { + const kTest = kTests[index]; + SpecialPowers.pushPrefEnv({"set": [ + ["mousewheel.default.action", kTest.action], + ["mousewheel.default.action.override_x", kTest.override_x]]}, + doIt2 + ); + } + + function doIt2() + { + const kTest = kTests[index]; + description = "doTestActionOverride(action=" + kTest.action + ", " + + "override_x=" + kTest.override_x + ", " + + "deltaX=" + kTest.event.deltaX + ", " + + "deltaY=" + kTest.event.deltaY + "): "; + gScrollableElement.scrollTop = 1000; + gScrollableElement.scrollLeft = 1000; + sendWheelAndWait(10, 10, kTest.event, function () { + if (kTest.expected & kScrollUp) { + ok(gScrollableElement.scrollTop < 1000, description + "not scrolled up, got " + gScrollableElement.scrollTop); + } else if (kTest.expected & kScrollDown) { + ok(gScrollableElement.scrollTop > 1000, description + "not scrolled down, got " + gScrollableElement.scrollTop); + } else { + is(gScrollableElement.scrollTop, 1000, description + "scrolled vertical"); + } + if (kTest.expected & kScrollLeft) { + ok(gScrollableElement.scrollLeft < 1000, description + "not scrolled to left, got " + gScrollableElement.scrollLeft); + } else if (kTest.expected & kScrollRight) { + ok(gScrollableElement.scrollLeft > 1000, description + "not scrolled to right, got " + gScrollableElement.scrollLeft); + } else { + is(gScrollableElement.scrollLeft, 1000, description + "scrolled horizontal"); + } + if (++index == kTests.length) { + SimpleTest.executeSoon(aCallback); + } else { + doIt(); + } + }); + } + doIt(); +} + +function runTests() +{ + SpecialPowers.pushPrefEnv({"set": [ + ["test.events.async.enabled", true], + ["general.smoothScroll", false], + ["mousewheel.default.action", 1], // scroll + ["mousewheel.default.action.override_x", -1], + ["mousewheel.with_shift.action", 2], // history + ["mousewheel.with_shift.action.override_x", -1], + ["mousewheel.with_control.action", 3], // zoom + ["mousewheel.with_control.action.override_x", -1]]}, + runTests2); +} + +function runTests2() +{ + const kSettings = [ + { description: "all delta values are not customized", + deltaMultiplierX: 1.0, deltaMultiplierY: 1.0, deltaMultiplierZ: 1.0 }, + { description: "deltaX is reverted", + deltaMultiplierX: -1.0, deltaMultiplierY: 1.0, deltaMultiplierZ: 1.0 }, + { description: "deltaY is reverted", + deltaMultiplierX: 1.0, deltaMultiplierY: -1.0, deltaMultiplierZ: 1.0 }, + { description: "deltaZ is reverted", + deltaMultiplierX: 1.0, deltaMultiplierY: 1.0, deltaMultiplierZ: -1.0 }, + { description: "deltaX is 2.0", + deltaMultiplierX: 2.0, deltaMultiplierY: 1.0, deltaMultiplierZ: 1.0 }, + { description: "deltaY is 2.0", + deltaMultiplierX: 1.0, deltaMultiplierY: 2.0, deltaMultiplierZ: 1.0 }, + { description: "deltaZ is 2.0", + deltaMultiplierX: 1.0, deltaMultiplierY: 1.0, deltaMultiplierZ: 2.0 }, + { description: "deltaX is -2.0", + deltaMultiplierX: -2.0, deltaMultiplierY: 1.0, deltaMultiplierZ: 1.0 }, + { description: "deltaY is -2.0", + deltaMultiplierX: 1.0, deltaMultiplierY: -2.0, deltaMultiplierZ: 1.0 }, + { description: "deltaZ is -2.0", + deltaMultiplierX: 1.0, deltaMultiplierY: 1.0, deltaMultiplierZ: -2.0 }, + ]; + + var index = 0; + + function doTest() { + setDeltaMultiplierSettings(kSettings[index], function () { + doTestScroll(kSettings[index], function () { + doTestZoom(kSettings[index], function() { + if (++index == kSettings.length) { + setDeltaMultiplierSettings(kSettings[0], function() { + doTestZoomedScroll(function() { + doTestWholeScroll(function() { + doTestActionOverride(function() { + finishTests(); + }); + }); + }); + }); + } else { + doTest(); + } + }); + }); + }); + } + doTest(); +} + +function finishTests() +{ + winUtils.restoreNormalRefresh(); + + window.opener.finish(); +} + +</script> +</pre> +</body> +</html> |