summaryrefslogtreecommitdiffstats
path: root/dom/events
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:33 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:33 +0000
commit086c044dc34dfc0f74fbe41f4ecb402b2cd34884 (patch)
treea4f824bd33cb075dd5aa3eb5a0a94af221bbe83a /dom/events
parentAdding debian version 124.0.1-1. (diff)
downloadfirefox-086c044dc34dfc0f74fbe41f4ecb402b2cd34884.tar.xz
firefox-086c044dc34dfc0f74fbe41f4ecb402b2cd34884.zip
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/events')
-rw-r--r--dom/events/Clipboard.cpp84
-rw-r--r--dom/events/Clipboard.h7
-rw-r--r--dom/events/DataTransfer.cpp114
-rw-r--r--dom/events/DataTransfer.h25
-rw-r--r--dom/events/EventListenerService.cpp63
-rw-r--r--dom/events/EventNameList.h3
-rw-r--r--dom/events/PointerEventHandler.cpp51
-rw-r--r--dom/events/PointerEventHandler.h38
-rw-r--r--dom/events/TouchEvent.cpp2
-rw-r--r--dom/events/nsIEventListenerService.idl30
-rw-r--r--dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js163
-rw-r--r--dom/events/test/mochitest.toml3
-rw-r--r--dom/events/test/pointerevents/mochitest_support_external.js2
-rw-r--r--dom/events/test/pointerevents/test_bug1315862.html4
-rw-r--r--dom/events/test/test_accesskey.html2
-rw-r--r--dom/events/test/test_bug226361.xhtml9
-rw-r--r--dom/events/test/test_bug238987.html2
-rw-r--r--dom/events/test/test_bug336682.js2
-rw-r--r--dom/events/test/test_bug448602.html21
-rw-r--r--dom/events/test/test_mouse_events_after_touchend.html232
-rw-r--r--dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html2
-rw-r--r--dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html2
22 files changed, 636 insertions, 225 deletions
diff --git a/dom/events/Clipboard.cpp b/dom/events/Clipboard.cpp
index 560002dd68..b163bc816f 100644
--- a/dom/events/Clipboard.cpp
+++ b/dom/events/Clipboard.cpp
@@ -29,6 +29,7 @@
#include "nsArrayUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
+#include "nsGlobalWindowInner.h"
#include "nsIClipboard.h"
#include "nsIInputStream.h"
#include "nsIParserUtils.h"
@@ -86,6 +87,16 @@ class ClipboardGetCallback : public nsIAsyncClipboardGetCallback {
RefPtr<Promise> mPromise;
};
+static nsTArray<nsCString> MandatoryDataTypesAsCStrings() {
+ // Mandatory data types defined in
+ // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x. The types
+ // should be in the same order as kNonPlainTextExternalFormats in
+ // DataTransfer.
+ return nsTArray<nsCString>{nsLiteralCString(kHTMLMime),
+ nsLiteralCString(kTextMime),
+ nsLiteralCString(kPNGImageMime)};
+}
+
class ClipboardGetCallbackForRead final : public ClipboardGetCallback {
public:
explicit ClipboardGetCallbackForRead(nsIGlobalObject* aGlobal,
@@ -109,11 +120,15 @@ class ClipboardGetCallbackForRead final : public ClipboardGetCallback {
}
AutoTArray<RefPtr<ClipboardItem::ItemEntry>, 3> entries;
- for (const auto& format : flavorList) {
- auto entry = MakeRefPtr<ClipboardItem::ItemEntry>(
- mGlobal, NS_ConvertUTF8toUTF16(format));
- entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData);
- entries.AppendElement(std::move(entry));
+ // We might reuse the request from DataTransfer created for paste event,
+ // which could contain more types that are not in the mandatory list.
+ for (const auto& format : MandatoryDataTypesAsCStrings()) {
+ if (flavorList.Contains(format)) {
+ auto entry = MakeRefPtr<ClipboardItem::ItemEntry>(
+ mGlobal, NS_ConvertUTF8toUTF16(format));
+ entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData);
+ entries.AppendElement(std::move(entry));
+ }
}
RefPtr<Promise> p(std::move(mPromise));
@@ -214,6 +229,36 @@ NS_IMPL_ISUPPORTS(ClipboardGetCallbackForReadText, nsIAsyncClipboardGetCallback,
} // namespace
+void Clipboard::RequestRead(Promise& aPromise, const ReadRequestType& aType,
+ nsPIDOMWindowInner& aOwner,
+ nsIPrincipal& aSubjectPrincipal,
+ nsIAsyncGetClipboardData& aRequest) {
+#ifdef DEBUG
+ bool isValid = false;
+ MOZ_ASSERT(NS_SUCCEEDED(aRequest.GetValid(&isValid)) && isValid);
+#endif
+
+ RefPtr<ClipboardGetCallback> callback;
+ switch (aType) {
+ case ReadRequestType::eRead: {
+ callback =
+ MakeRefPtr<ClipboardGetCallbackForRead>(aOwner.AsGlobal(), &aPromise);
+ break;
+ }
+ case ReadRequestType::eReadText: {
+ callback = MakeRefPtr<ClipboardGetCallbackForReadText>(&aPromise);
+ break;
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Unknown read type");
+ return;
+ }
+ }
+
+ MOZ_ASSERT(callback);
+ callback->OnSuccess(&aRequest);
+}
+
void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType,
nsPIDOMWindowInner* aOwner,
nsIPrincipal& aPrincipal) {
@@ -239,19 +284,14 @@ void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType,
callback = MakeRefPtr<ClipboardGetCallbackForRead>(global, std::move(p));
rv = clipboardService->AsyncGetData(
- // Mandatory data types defined in
- // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x
- AutoTArray<nsCString, 3>{nsDependentCString(kHTMLMime),
- nsDependentCString(kTextMime),
- nsDependentCString(kPNGImageMime)},
- nsIClipboard::kGlobalClipboard, owner->GetWindowContext(),
- &aPrincipal, callback);
+ MandatoryDataTypesAsCStrings(), nsIClipboard::kGlobalClipboard,
+ owner->GetWindowContext(), &aPrincipal, callback);
break;
}
case ReadRequestType::eReadText: {
callback = MakeRefPtr<ClipboardGetCallbackForReadText>(std::move(p));
rv = clipboardService->AsyncGetData(
- AutoTArray<nsCString, 1>{nsDependentCString(kTextMime)},
+ AutoTArray<nsCString, 1>{nsLiteralCString(kTextMime)},
nsIClipboard::kGlobalClipboard, owner->GetWindowContext(),
&aPrincipal, callback);
break;
@@ -288,6 +328,24 @@ already_AddRefed<Promise> Clipboard::ReadHelper(nsIPrincipal& aSubjectPrincipal,
return p.forget();
}
+ // If a "paste" clipboard event is actively being processed, we're
+ // intentionally skipping permission/user-activation checks and giving the
+ // webpage access to the clipboard.
+ if (RefPtr<DataTransfer> dataTransfer =
+ nsGlobalWindowInner::Cast(owner)->GetCurrentPasteDataTransfer()) {
+ // If there is valid nsIAsyncGetClipboardData, use it directly.
+ if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
+ dataTransfer->GetAsyncGetClipboardData()) {
+ bool isValid = false;
+ asyncGetClipboardData->GetValid(&isValid);
+ if (isValid) {
+ RequestRead(*p, aType, *owner, aSubjectPrincipal,
+ *asyncGetClipboardData);
+ return p.forget();
+ }
+ }
+ }
+
if (IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal)) {
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("%s: testing pref enabled or has read permission", __FUNCTION__));
diff --git a/dom/events/Clipboard.h b/dom/events/Clipboard.h
index 2df4df1ec5..a9952e6052 100644
--- a/dom/events/Clipboard.h
+++ b/dom/events/Clipboard.h
@@ -13,7 +13,8 @@
#include "mozilla/Logging.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
-#include "mozilla/dom/DataTransfer.h"
+
+class nsIAsyncGetClipboardData;
namespace mozilla::dom {
@@ -75,6 +76,10 @@ class Clipboard : public DOMEventTargetHelper {
void RequestRead(Promise* aPromise, ReadRequestType aType,
nsPIDOMWindowInner* aOwner, nsIPrincipal& aPrincipal);
+
+ void RequestRead(Promise& aPromise, const ReadRequestType& aType,
+ nsPIDOMWindowInner& aOwner, nsIPrincipal& aSubjectPrincipal,
+ nsIAsyncGetClipboardData& aRequest);
};
} // namespace mozilla::dom
diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp
index ad8c7059da..ba56575749 100644
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -622,52 +622,64 @@ already_AddRefed<DataTransfer> DataTransfer::MozCloneForEvent(
}
// The order of the types matters. `kFileMime` needs to be one of the first two
-// types.
-static const char* kNonPlainTextExternalFormats[] = {
- kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime, kURLMime,
- kURLDataMime, kTextMime, kPNGImageMime, kPDFJSMime};
-
-/* static */
-void DataTransfer::GetExternalClipboardFormats(const int32_t& aWhichClipboard,
- const bool& aPlainTextOnly,
- nsTArray<nsCString>* aResult) {
- MOZ_ASSERT(aResult);
-
+// types. And the order should be the same as the types order defined in
+// MandatoryDataTypesAsCStrings() for Clipboard API.
+static const nsCString kNonPlainTextExternalFormats[] = {
+ nsLiteralCString(kCustomTypesMime), nsLiteralCString(kFileMime),
+ nsLiteralCString(kHTMLMime), nsLiteralCString(kRTFMime),
+ nsLiteralCString(kURLMime), nsLiteralCString(kURLDataMime),
+ nsLiteralCString(kTextMime), nsLiteralCString(kPNGImageMime),
+ nsLiteralCString(kPDFJSMime)};
+
+void DataTransfer::GetExternalClipboardFormats(const bool& aPlainTextOnly,
+ nsTArray<nsCString>& aResult) {
// NOTE: When you change this method, you may need to change
// GetExternalTransferableFormats() too since those methods should
// work similarly.
+ MOZ_ASSERT(!mAsyncGetClipboardData);
+
+ RefPtr<WindowContext> wc = GetWindowContext();
+ if (NS_WARN_IF(!wc)) {
+ MOZ_ASSERT_UNREACHABLE(
+ "How could this DataTransfer be created with a non-window global?");
+ return;
+ }
+
nsCOMPtr<nsIClipboard> clipboard =
do_GetService("@mozilla.org/widget/clipboard;1");
- if (!clipboard || aWhichClipboard < 0) {
+ if (!clipboard || mClipboardType < 0) {
return;
}
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData;
if (aPlainTextOnly) {
- bool hasType;
- AutoTArray<nsCString, 1> textMime = {nsDependentCString(kTextMime)};
- nsresult rv =
- clipboard->HasDataMatchingFlavors(textMime, aWhichClipboard, &hasType);
- NS_SUCCEEDED(rv);
- if (hasType) {
- aResult->AppendElement(kTextMime);
- }
+ rv = clipboard->GetDataSnapshotSync(
+ AutoTArray<nsCString, 1>{nsLiteralCString(kTextMime)}, mClipboardType,
+ wc, getter_AddRefs(asyncGetClipboardData));
+ } else {
+ AutoTArray<nsCString, ArrayLength(kNonPlainTextExternalFormats)> formats;
+ formats.AppendElements(Span<const nsCString>(kNonPlainTextExternalFormats));
+ rv = clipboard->GetDataSnapshotSync(formats, mClipboardType, wc,
+ getter_AddRefs(asyncGetClipboardData));
+ }
+
+ if (NS_FAILED(rv) || !asyncGetClipboardData) {
return;
}
- // If not plain text only, then instead check all the other types
- for (uint32_t f = 0; f < mozilla::ArrayLength(kNonPlainTextExternalFormats);
- ++f) {
- bool hasType;
- AutoTArray<nsCString, 1> format = {
- nsDependentCString(kNonPlainTextExternalFormats[f])};
- nsresult rv =
- clipboard->HasDataMatchingFlavors(format, aWhichClipboard, &hasType);
- NS_SUCCEEDED(rv);
- if (hasType) {
- aResult->AppendElement(kNonPlainTextExternalFormats[f]);
+ // Order is important for DataTransfer; ensure the returned list items follow
+ // the sequence specified in kNonPlainTextExternalFormats.
+ AutoTArray<nsCString, ArrayLength(kNonPlainTextExternalFormats)> flavors;
+ asyncGetClipboardData->GetFlavorList(flavors);
+ for (const auto& format : kNonPlainTextExternalFormats) {
+ if (flavors.Contains(format)) {
+ aResult.AppendElement(format);
}
}
+
+ mAsyncGetClipboardData = asyncGetClipboardData;
}
/* static */
@@ -695,10 +707,10 @@ void DataTransfer::GetExternalTransferableFormats(
}
// If not plain text only, then instead check all the other types
- for (const char* format : kNonPlainTextExternalFormats) {
- auto index = flavors.IndexOf(nsCString(format));
+ for (const auto& format : kNonPlainTextExternalFormats) {
+ auto index = flavors.IndexOf(format);
if (index != flavors.NoIndex) {
- aResult->AppendElement(nsCString(format));
+ aResult->AppendElement(format);
}
}
}
@@ -1192,7 +1204,10 @@ void DataTransfer::Disconnect() {
}
}
-void DataTransfer::ClearAll() { mItems->ClearAllItems(); }
+void DataTransfer::ClearAll() {
+ mItems->ClearAllItems();
+ mAsyncGetClipboardData = nullptr;
+}
uint32_t DataTransfer::MozItemCount() const { return mItems->MozItemCount(); }
@@ -1260,6 +1275,24 @@ already_AddRefed<nsIGlobalObject> DataTransfer::GetGlobal() const {
return global.forget();
}
+already_AddRefed<WindowContext> DataTransfer::GetWindowContext() const {
+ nsCOMPtr<nsIGlobalObject> global = GetGlobal();
+ if (!global) {
+ return nullptr;
+ }
+
+ const auto* innerWindow = global->GetAsInnerWindow();
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ return do_AddRef(innerWindow->GetWindowContext());
+}
+
+nsIAsyncGetClipboardData* DataTransfer::GetAsyncGetClipboardData() const {
+ return mAsyncGetClipboardData;
+}
+
nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
nsIPrincipal* aPrincipal,
bool aHidden) {
@@ -1357,20 +1390,13 @@ void DataTransfer::CacheExternalClipboardFormats(bool aPlainTextOnly) {
"caching clipboard data for invalid event");
nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
-
nsTArray<nsCString> typesArray;
-
- if (XRE_IsContentProcess()) {
- ContentChild::GetSingleton()->SendGetExternalClipboardFormats(
- mClipboardType, aPlainTextOnly, &typesArray);
- } else {
- GetExternalClipboardFormats(mClipboardType, aPlainTextOnly, &typesArray);
- }
-
+ GetExternalClipboardFormats(aPlainTextOnly, typesArray);
if (aPlainTextOnly) {
// The only thing that will be in types is kTextMime
MOZ_ASSERT(typesArray.IsEmpty() || typesArray.Length() == 1);
if (typesArray.Length() == 1) {
+ MOZ_ASSERT(typesArray.Contains(kTextMime));
CacheExternalData(kTextMime, 0, sysPrincipal, false);
}
return;
diff --git a/dom/events/DataTransfer.h b/dom/events/DataTransfer.h
index ea0368216a..7ff2bff54b 100644
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -23,6 +23,7 @@
#include "mozilla/dom/DataTransferItemList.h"
#include "mozilla/dom/File.h"
+class nsIAsyncGetClipboardData;
class nsINode;
class nsITransferable;
class nsILoadContext;
@@ -386,14 +387,6 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
}
bool MozShowFailAnimation() const { return mShowFailAnimation; }
- // Retrieve a list of clipboard formats supported
- //
- // If kFileMime is supported, then it will be placed either at
- // index 0 or at index 1 in aResult
- static void GetExternalClipboardFormats(const int32_t& aWhichClipboard,
- const bool& aPlainTextOnly,
- nsTArray<nsCString>* aResult);
-
// Retrieve a list of supporting formats in aTransferable.
//
// If kFileMime is supported, then it will be placed either at
@@ -428,7 +421,18 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
already_AddRefed<nsIGlobalObject> GetGlobal() const;
+ already_AddRefed<WindowContext> GetWindowContext() const;
+
+ nsIAsyncGetClipboardData* GetAsyncGetClipboardData() const;
+
protected:
+ // Retrieve a list of clipboard formats supported
+ //
+ // If kFileMime is supported, then it will be placed either at
+ // index 0 or at index 1 in aResult
+ void GetExternalClipboardFormats(const bool& aPlainTextOnly,
+ nsTArray<nsCString>& aResult);
+
// 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,
@@ -506,6 +510,11 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
// drag and drop.
int32_t mClipboardType;
+ // The nsIAsyncGetClipboardData that is used for getting clipboard formats.
+ // XXXedgar we should get the actual data from this in the future, see bug
+ // 1879401.
+ nsCOMPtr<nsIAsyncGetClipboardData> mAsyncGetClipboardData;
+
// The items contained with the DataTransfer
RefPtr<DataTransferItemList> mItems;
diff --git a/dom/events/EventListenerService.cpp b/dom/events/EventListenerService.cpp
index 7e4919d938..3398f0dd02 100644
--- a/dom/events/EventListenerService.cpp
+++ b/dom/events/EventListenerService.cpp
@@ -220,21 +220,6 @@ EventListenerService::GetListenerInfoFor(
}
NS_IMETHODIMP
-EventListenerService::GetEventTargetChainFor(
- EventTarget* aEventTarget, bool aComposed,
- nsTArray<RefPtr<EventTarget>>& aOutArray) {
- 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);
- aOutArray.AppendElements(targets);
- return NS_OK;
-}
-
-NS_IMETHODIMP
EventListenerService::HasListenersFor(EventTarget* aEventTarget,
const nsAString& aType, bool* aRetVal) {
NS_ENSURE_TRUE(aEventTarget, NS_ERROR_UNEXPECTED);
@@ -258,54 +243,6 @@ static already_AddRefed<EventListener> ToEventListener(
}
NS_IMETHODIMP
-EventListenerService::AddSystemEventListener(EventTarget* aTarget,
- const nsAString& aType,
- JS::Handle<JS::Value> aListener,
- bool aUseCapture, JSContext* aCx) {
- MOZ_ASSERT(aTarget, "Missing target");
-
- NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED);
-
- RefPtr<EventListener> listener = ToEventListener(aCx, aListener);
- if (!listener) {
- return NS_ERROR_UNEXPECTED;
- }
-
- EventListenerManager* manager = aTarget->GetOrCreateListenerManager();
- NS_ENSURE_STATE(manager);
-
- EventListenerFlags flags = aUseCapture ? TrustedEventsAtSystemGroupCapture()
- : TrustedEventsAtSystemGroupBubble();
- manager->AddEventListenerByType(listener, aType, flags);
- return NS_OK;
-}
-
-NS_IMETHODIMP
-EventListenerService::RemoveSystemEventListener(EventTarget* aTarget,
- const nsAString& aType,
- JS::Handle<JS::Value> aListener,
- bool aUseCapture,
- JSContext* aCx) {
- MOZ_ASSERT(aTarget, "Missing target");
-
- NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED);
-
- RefPtr<EventListener> listener = ToEventListener(aCx, aListener);
- if (!listener) {
- return NS_ERROR_UNEXPECTED;
- }
-
- EventListenerManager* manager = aTarget->GetExistingListenerManager();
- if (manager) {
- EventListenerFlags flags = aUseCapture ? TrustedEventsAtSystemGroupCapture()
- : TrustedEventsAtSystemGroupBubble();
- manager->RemoveEventListenerByType(listener, aType, flags);
- }
-
- return NS_OK;
-}
-
-NS_IMETHODIMP
EventListenerService::AddListenerForAllEvents(
EventTarget* aTarget, JS::Handle<JS::Value> aListener, bool aUseCapture,
bool aWantsUntrusted, bool aSystemEventGroup, JSContext* aCx) {
diff --git a/dom/events/EventNameList.h b/dom/events/EventNameList.h
index 6c9119d5f9..92c76d000e 100644
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -228,6 +228,9 @@ EVENT(lostpointercapture, ePointerLostCapture, EventNameType_All,
ePointerEventClass)
EVENT(selectstart, eSelectStart, EventNameType_HTMLXUL, eBasicEventClass)
+EVENT(contextlost, eContextLost, EventNameType_HTML, eBasicEventClass)
+EVENT(contextrestored, eContextRestored, EventNameType_HTML, eBasicEventClass)
+
// Not supported yet; probably never because "wheel" is a better idea.
// EVENT(mousewheel)
EVENT(pause, ePause, EventNameType_HTML, eBasicEventClass)
diff --git a/dom/events/PointerEventHandler.cpp b/dom/events/PointerEventHandler.cpp
index 6c537bfb67..ef6f2b12c0 100644
--- a/dom/events/PointerEventHandler.cpp
+++ b/dom/events/PointerEventHandler.cpp
@@ -610,15 +610,16 @@ EventMessage PointerEventHandler::ToPointerEventMessage(
/* static */
void PointerEventHandler::DispatchPointerFromMouseOrTouch(
- PresShell* aShell, nsIFrame* aFrame, nsIContent* aContent,
- WidgetGUIEvent* aEvent, bool aDontRetargetEvents, nsEventStatus* aStatus,
- nsIContent** aTargetContent) {
- MOZ_ASSERT(aFrame || aContent);
- MOZ_ASSERT(aEvent);
+ PresShell* aShell, nsIFrame* aEventTargetFrame,
+ nsIContent* aEventTargetContent, WidgetGUIEvent* aMouseOrTouchEvent,
+ bool aDontRetargetEvents, nsEventStatus* aStatus,
+ nsIContent** aMouseOrTouchEventTarget /* = nullptr */) {
+ MOZ_ASSERT(aEventTargetFrame || aEventTargetContent);
+ MOZ_ASSERT(aMouseOrTouchEvent);
EventMessage pointerMessage = eVoidEvent;
- if (aEvent->mClass == eMouseEventClass) {
- WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (aMouseOrTouchEvent->mClass == eMouseEventClass) {
+ WidgetMouseEvent* mouseEvent = aMouseOrTouchEvent->AsMouseEvent();
// Don't dispatch pointer events caused by a mouse when simulating touch
// devices in RDM.
Document* doc = aShell->GetDocument();
@@ -636,7 +637,7 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch(
// 2. We don't synthesize pointer events for those events that are not
// dispatched to DOM.
if (!mouseEvent->convertToPointer ||
- !aEvent->IsAllowedToDispatchDOMEvent()) {
+ !aMouseOrTouchEvent->IsAllowedToDispatchDOMEvent()) {
return;
}
@@ -648,20 +649,20 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch(
InitPointerEventFromMouse(&event, mouseEvent, pointerMessage);
event.convertToPointer = mouseEvent->convertToPointer = false;
RefPtr<PresShell> shell(aShell);
- if (!aFrame) {
- shell = PresShell::GetShellForEventTarget(nullptr, aContent);
+ if (!aEventTargetFrame) {
+ shell = PresShell::GetShellForEventTarget(nullptr, aEventTargetContent);
if (!shell) {
return;
}
}
- PreHandlePointerEventsPreventDefault(&event, aEvent);
+ PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
// Dispatch pointer event to the same target which is found by the
// corresponding mouse event.
- shell->HandleEventWithTarget(&event, aFrame, aContent, aStatus, true,
- aTargetContent);
- PostHandlePointerEventsPreventDefault(&event, aEvent);
- } else if (aEvent->mClass == eTouchEventClass) {
- WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ shell->HandleEventWithTarget(&event, aEventTargetFrame, aEventTargetContent,
+ aStatus, true, aMouseOrTouchEventTarget);
+ PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
+ } else if (aMouseOrTouchEvent->mClass == eTouchEventClass) {
+ WidgetTouchEvent* touchEvent = aMouseOrTouchEvent->AsTouchEvent();
// loop over all touches and dispatch pointer events on each touch
// copy the event
pointerMessage = PointerEventHandler::ToPointerEventMessage(touchEvent);
@@ -681,7 +682,7 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch(
InitPointerEventFromTouch(event, *touchEvent, *touch, i == 0);
event.convertToPointer = touch->convertToPointer = false;
event.mCoalescedWidgetEvents = touch->mCoalescedWidgetEvents;
- if (aEvent->mMessage == eTouchStart) {
+ if (aMouseOrTouchEvent->mMessage == eTouchStart) {
// We already did hit test for touchstart in PresShell. We should
// dispatch pointerdown to the same target as touchstart.
nsCOMPtr<nsIContent> content =
@@ -696,18 +697,22 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch(
continue;
}
- PreHandlePointerEventsPreventDefault(&event, aEvent);
+ PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
shell->HandleEventWithTarget(&event, frame, content, aStatus, true,
- nullptr);
- PostHandlePointerEventsPreventDefault(&event, aEvent);
+ aMouseOrTouchEventTarget);
+ PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
} else {
// We didn't hit test for other touch events. Spec doesn't mention that
// all pointer events should be dispatched to the same target as their
// corresponding touch events. Call PresShell::HandleEvent so that we do
// hit test for pointer events.
- PreHandlePointerEventsPreventDefault(&event, aEvent);
- shell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus);
- PostHandlePointerEventsPreventDefault(&event, aEvent);
+ // FIXME: If aDontRetargetEvents is true and the event is fired on
+ // different document, we cannot track the pointer event target when
+ // it's removed from the tree.
+ PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
+ shell->HandleEvent(aEventTargetFrame, &event, aDontRetargetEvents,
+ aStatus);
+ PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent);
}
}
}
diff --git a/dom/events/PointerEventHandler.h b/dom/events/PointerEventHandler.h
index d1a5b31b5a..0211bfe0d8 100644
--- a/dom/events/PointerEventHandler.h
+++ b/dom/events/PointerEventHandler.h
@@ -168,11 +168,39 @@ class PointerEventHandler final {
static void PostHandlePointerEventsPreventDefault(
WidgetPointerEvent* aPointerEvent, WidgetGUIEvent* aMouseOrTouchEvent);
- MOZ_CAN_RUN_SCRIPT
- static void DispatchPointerFromMouseOrTouch(
- PresShell* aShell, nsIFrame* aFrame, nsIContent* aContent,
- WidgetGUIEvent* aEvent, bool aDontRetargetEvents, nsEventStatus* aStatus,
- nsIContent** aTargetContent);
+ /**
+ * Dispatch a pointer event for aMouseOrTouchEvent to aEventTargetContent.
+ *
+ * @param aShell The PresShell which is handling the event.
+ * @param aEventTargetFrame The frame for aEventTargetContent.
+ * @param aEventTargetContent The event target node.
+ * @param aMouseOrTouchEvent A mouse or touch event.
+ * @param aDontRetargetEvents If true, this won't dispatch event with
+ * different PresShell from aShell. Otherwise,
+ * pointer events may be fired on different
+ * document if and only if aMouseOrTOuchEvent is a
+ * touch event except eTouchStart.
+ * @param aState [out] The result of the pointer event.
+ * @param aMouseOrTouchEventTarget
+ * [out] The event target for the following mouse
+ * or touch event. If aEventTargetContent has not
+ * been removed from the tree, this is always set
+ * to it. If aEventTargetContent is removed from
+ * the tree and aMouseOrTouchEvent is a mouse
+ * event, this is set to inclusive ancestor of
+ * aEventTargetContent which is still connected.
+ * If aEventTargetContent is removed from the tree
+ * and aMouseOrTouchEvent is a touch event, this is
+ * set to aEventTargetContent because touch event
+ * should be dispatched even on disconnected node.
+ * FIXME: If the event is a touch event but the
+ * message is not eTouchStart, this won't be set.
+ */
+ MOZ_CAN_RUN_SCRIPT static void DispatchPointerFromMouseOrTouch(
+ PresShell* aShell, nsIFrame* aEventTargetFrame,
+ nsIContent* aEventTargetContent, WidgetGUIEvent* aMouseOrTouchEvent,
+ bool aDontRetargetEvents, nsEventStatus* aStatus,
+ nsIContent** aMouseOrTouchEventTarget = nullptr);
static void InitPointerEventFromMouse(WidgetPointerEvent* aPointerEvent,
WidgetMouseEvent* aMouseEvent,
diff --git a/dom/events/TouchEvent.cpp b/dom/events/TouchEvent.cpp
index 24068703be..6e50c81db8 100644
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -233,7 +233,7 @@ bool TouchEvent::PrefEnabled(nsIDocShell* aDocShell) {
// The touch screen data seems to be inaccurate in the parent process,
// and we really need the crash annotation in child processes.
if (firstTime && !XRE_IsParentProcess()) {
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationBool(
CrashReporter::Annotation::HasDeviceTouchScreen, enabled);
firstTime = false;
}
diff --git a/dom/events/nsIEventListenerService.idl b/dom/events/nsIEventListenerService.idl
index ead3cc4af1..b8f36e5d0e 100644
--- a/dom/events/nsIEventListenerService.idl
+++ b/dom/events/nsIEventListenerService.idl
@@ -75,41 +75,11 @@ interface nsIEventListenerService : nsISupports
Array<nsIEventListenerInfo> getListenerInfoFor(in EventTarget aEventTarget);
/**
- * 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.
- */
- [can_run_script]
- Array<EventTarget> getEventTargetChainFor(in EventTarget aEventTarget,
- in boolean composed);
-
- /**
* Returns true if a event target has any listener for the given type.
*/
boolean hasListenersFor(in EventTarget aEventTarget,
in AString aType);
- /**
- * Add a system-group eventlistener to a event target.
- */
- [implicit_jscontext]
- void addSystemEventListener(in EventTarget target,
- in AString type,
- in jsval listener,
- in boolean useCapture);
-
- /**
- * Remove a system-group eventlistener from a event target.
- */
- [implicit_jscontext]
- void removeSystemEventListener(in EventTarget target,
- in AString type,
- in jsval listener,
- in boolean useCapture);
-
[implicit_jscontext]
void addListenerForAllEvents(in EventTarget target,
in jsval listener,
diff --git a/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js b/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js
index f504e499c9..97066cd2eb 100644
--- a/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js
+++ b/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js
@@ -238,16 +238,10 @@ add_task(async function test_context_menu_suppression_image() {
await pasteButtonIsShown;
info("Test read from same-origin frame before paste contextmenu is closed");
- const clipboarCacheEnabled = SpecialPowers.getBoolPref(
- "widget.clipboard.use-cached-data.enabled",
- false
- );
// If the cached data is used, it uses type order in cached transferable.
SimpleTest.isDeeply(
await readTypes(browser.browsingContext.children[0]),
- clipboarCacheEnabled
- ? ["text/plain", "text/html", "image/png"]
- : ["text/html", "text/plain", "image/png"],
+ ["text/html", "text/plain", "image/png"],
"read from same-origin should just be resolved without showing paste contextmenu shown"
);
@@ -262,3 +256,158 @@ add_task(async function test_context_menu_suppression_image() {
);
});
});
+
+function testPasteContextMenuSuppressionPasteEvent(
+ aTriggerPasteFun,
+ aSuppress,
+ aMsg
+) {
+ add_task(async function test_context_menu_suppression_paste_event() {
+ await BrowserTestUtils.withNewTab(
+ kContentFileUrl,
+ async function (browser) {
+ info(`Write data by in cross-origin frame`);
+ const clipboardText = "X" + Math.random();
+ await SpecialPowers.spawn(
+ browser.browsingContext.children[1],
+ [clipboardText],
+ async text => {
+ content.document.notifyUserGestureActivation();
+ return content.eval(`navigator.clipboard.writeText("${text}");`);
+ }
+ );
+
+ info("Test read should show contextmenu");
+ let pasteButtonIsShown = waitForPasteContextMenu();
+ let readTextRequest = readText(browser);
+ await pasteButtonIsShown;
+
+ info("Click paste button, request should be resolved");
+ await promiseClickPasteButton();
+ is(await readTextRequest, clipboardText, "Request should be resolved");
+
+ info("Test read in paste event handler");
+ readTextRequest = SpecialPowers.spawn(browser, [], async () => {
+ content.document.notifyUserGestureActivation();
+ return content.eval(`
+ (() => {
+ return new Promise(resolve => {
+ document.addEventListener("paste", function(e) {
+ e.preventDefault();
+ resolve(navigator.clipboard.readText());
+ }, { once: true });
+ });
+ })();
+ `);
+ });
+
+ if (aSuppress) {
+ let listener = function (e) {
+ if (e.target.getAttribute("id") == kPasteMenuPopupId) {
+ ok(!aSuppress, "paste contextmenu should not be shown");
+ }
+ };
+ document.addEventListener("popupshown", listener);
+ info(`Trigger paste event by ${aMsg}`);
+ // trigger paste event
+ await aTriggerPasteFun(browser);
+ is(
+ await readTextRequest,
+ clipboardText,
+ "Request should be resolved"
+ );
+ document.removeEventListener("popupshown", listener);
+ } else {
+ let pasteButtonIsShown = waitForPasteContextMenu();
+ info(
+ `Trigger paste event by ${aMsg}, read should still show contextmenu`
+ );
+ // trigger paste event
+ await aTriggerPasteFun(browser);
+ await pasteButtonIsShown;
+
+ info("Click paste button, request should be resolved");
+ await promiseClickPasteButton();
+ is(
+ await readTextRequest,
+ clipboardText,
+ "Request should be resolved"
+ );
+ }
+
+ info("Test read should still show contextmenu");
+ pasteButtonIsShown = waitForPasteContextMenu();
+ readTextRequest = readText(browser);
+ await pasteButtonIsShown;
+
+ info("Click paste button, request should be resolved");
+ await promiseClickPasteButton();
+ is(await readTextRequest, clipboardText, "Request should be resolved");
+ }
+ );
+ });
+}
+
+// If platform supports selection clipboard, the middle click paste the content
+// from selection clipboard instead, in such case, we don't suppress the
+// contextmenu when access global clipboard via async clipboard API.
+if (
+ !Services.clipboard.isClipboardTypeSupported(
+ Services.clipboard.kSelectionClipboard
+ )
+) {
+ testPasteContextMenuSuppressionPasteEvent(
+ async browser => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["middlemouse.paste", true]],
+ });
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ EventUtils.synthesizeMouse(
+ content.document.documentElement,
+ 1,
+ 1,
+ { button: 1 },
+ content.window
+ );
+ });
+ },
+ true,
+ "middle click"
+ );
+}
+
+testPasteContextMenuSuppressionPasteEvent(
+ async browser => {
+ await EventUtils.synthesizeAndWaitKey(
+ "v",
+ kIsMac ? { accelKey: true } : { ctrlKey: true }
+ );
+ },
+ true,
+ "keyboard shortcut"
+);
+
+testPasteContextMenuSuppressionPasteEvent(
+ async browser => {
+ await SpecialPowers.spawn(browser, [], async () => {
+ return SpecialPowers.doCommand(content.window, "cmd_paste");
+ });
+ },
+ true,
+ "paste command"
+);
+
+testPasteContextMenuSuppressionPasteEvent(
+ async browser => {
+ await SpecialPowers.spawn(browser, [], async () => {
+ let div = content.document.createElement("div");
+ div.setAttribute("contenteditable", "true");
+ content.document.documentElement.appendChild(div);
+ div.focus();
+ return SpecialPowers.doCommand(content.window, "cmd_pasteNoFormatting");
+ });
+ },
+ false,
+ "pasteNoFormatting command"
+);
diff --git a/dom/events/test/mochitest.toml b/dom/events/test/mochitest.toml
index b6b9e58368..53675a8f49 100644
--- a/dom/events/test/mochitest.toml
+++ b/dom/events/test/mochitest.toml
@@ -472,6 +472,9 @@ skip-if = [
"http2",
]
+["test_mouse_events_after_touchend.html"]
+skip-if = ["os == 'mac'"] # Bug 1881864
+
["test_mouse_over_at_removing_down_target.html"]
["test_moving_and_expanding_selection_per_page.html"]
diff --git a/dom/events/test/pointerevents/mochitest_support_external.js b/dom/events/test/pointerevents/mochitest_support_external.js
index 7f22166fdd..7a758a8c5f 100644
--- a/dom/events/test/pointerevents/mochitest_support_external.js
+++ b/dom/events/test/pointerevents/mochitest_support_external.js
@@ -2,6 +2,8 @@
// to tests on auto MochiTest system with minimum changes.
// Author: Maksim Lebedev <alessarik@gmail.com>
+/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */
+
// Function allows to prepare our tests after load document
addEventListener(
"load",
diff --git a/dom/events/test/pointerevents/test_bug1315862.html b/dom/events/test/pointerevents/test_bug1315862.html
index 92d61a518f..657947c156 100644
--- a/dom/events/test/pointerevents/test_bug1315862.html
+++ b/dom/events/test/pointerevents/test_bug1315862.html
@@ -36,10 +36,10 @@ function runTests() {
let target = iframe.contentDocument.body.firstChild;
allPointerEvents.forEach((event, idx, arr) => {
- SpecialPowers.addSystemEventListener(target, event, () => {
+ SpecialPowers.wrap(target).addEventListener(event, () => {
ok(false, "Shouldn't dispatch " + event + " in the system group");
receivePointerEvents = true;
- });
+ }, { mozSystemGroup: true });
});
target.addEventListener("pointerdown", (e) => {
target.setPointerCapture(e.pointerId);
diff --git a/dom/events/test/test_accesskey.html b/dom/events/test/test_accesskey.html
index cdfff54a28..4d382f2270 100644
--- a/dom/events/test/test_accesskey.html
+++ b/dom/events/test/test_accesskey.html
@@ -143,7 +143,7 @@ add_task(async function modifyAccessKey() {
add_task(async function file_picker() {
const file = document.getElementById("file");
const MockFilePicker = SpecialPowers.MockFilePicker;
- MockFilePicker.init(window);
+ MockFilePicker.init(SpecialPowers.wrap(window).browsingContext);
MockFilePicker.returnValue = MockFilePicker.returnCancel;
let clicked = false;
diff --git a/dom/events/test/test_bug226361.xhtml b/dom/events/test/test_bug226361.xhtml
index 143a485757..c2c19ee2a7 100644
--- a/dom/events/test/test_bug226361.xhtml
+++ b/dom/events/test/test_bug226361.xhtml
@@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=226361
</p>
<div id="content" style="display: none">
-
+
</div>
<pre id="test">
<script type="application/javascript">
@@ -43,8 +43,11 @@ function tab_to(id) {
}
function tab_iframe() {
- doc = document;
- tab_to('iframe');
+ let canTabMoveFocusToRootElement = !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");
+ if (canTabMoveFocusToRootElement) {
+ doc = document;
+ tab_to('iframe');
+ }
// inside iframe
doc = document.getElementById('iframe').contentDocument
diff --git a/dom/events/test/test_bug238987.html b/dom/events/test/test_bug238987.html
index b2712a25d0..34f870eeac 100644
--- a/dom/events/test/test_bug238987.html
+++ b/dom/events/test/test_bug238987.html
@@ -22,7 +22,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=238987
var shouldStop = false;
var activateShift = false;
- var expectedResult = "i1,i2,i3,i4,i5,i6,i7,i8,number,i9,i10,i11,i12";
+ var expectedResult = "i1,i2,i3,i4,i5,i7,i8,number,i9,i10,i11,i12";
var forwardFocusArray = expectedResult.split(",");
var backwardFocusArray = expectedResult.split(",");
var forwardBlurArray = expectedResult.split(",");
diff --git a/dom/events/test/test_bug336682.js b/dom/events/test/test_bug336682.js
index e673f1eb99..11bd7d46bb 100644
--- a/dom/events/test/test_bug336682.js
+++ b/dom/events/test/test_bug336682.js
@@ -4,6 +4,8 @@
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
+/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */
+
var gState = 0;
/**
* After all the on/offline handlers run,
diff --git a/dom/events/test/test_bug448602.html b/dom/events/test/test_bug448602.html
index 18b4cb2d2f..452a361a52 100644
--- a/dom/events/test/test_bug448602.html
+++ b/dom/events/test/test_bug448602.html
@@ -174,28 +174,7 @@ function runTests() {
is(SpecialPowers.unwrap(infos[0].listenerObject), l,
"Should have the right listener object (4)");
- // Event target chain tests
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);
diff --git a/dom/events/test/test_mouse_events_after_touchend.html b/dom/events/test/test_mouse_events_after_touchend.html
new file mode 100644
index 0000000000..146c37e489
--- /dev/null
+++ b/dom/events/test/test_mouse_events_after_touchend.html
@@ -0,0 +1,232 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Tests for mouse events after touchend</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+#parent, #child {
+ width: 300px;
+ height: 64px;
+ padding: 16px;
+}
+#parent {
+ background-color: black;
+}
+#child {
+ background-color: gray;
+}
+</style>
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("Required for waiting to prevent double tap at second tap");
+SimpleTest.waitForFocus(async () => {
+ function stringifyEvent(event) {
+ return `{ type: ${event.type}, target: ${
+ event.target.id || event.target.nodeName
+ }${
+ event.detail !== undefined ? `, detail: ${event.detail}` : ""
+ }${
+ event.button !== undefined ? `, button: ${event.button}` : ""
+ }${
+ event.buttons !== undefined ? `, buttons: ${event.buttons}` : ""
+ } }`;
+ }
+ function stringifyEvents(arrayOfEvents) {
+ if (!arrayOfEvents.length) {
+ return "[]";
+ }
+ let ret = "";
+ for (const event of arrayOfEvents) {
+ if (ret === "") {
+ ret = "[ ";
+ } else {
+ ret += ", ";
+ }
+ ret += stringifyEvent(event);
+ }
+ return ret + " ]";
+ }
+
+ let events = [];
+ for (const type of ["mousemove",
+ "mousedown",
+ "mouseup",
+ "click",
+ "dblclick",
+ "contextmenu",
+ "touchend"]) {
+ if (type == "touchend") {
+ addEventListener(type, event => {
+ info(`Received: ${stringifyEvent(event)}`);
+ events.push({type, target: event.target});
+ }, {capture: true});
+ } else {
+ addEventListener(type, event => {
+ info(`Received: ${stringifyEvent(event)}`);
+ events.push({
+ type: event.type,
+ target: event.target,
+ detail: event.detail,
+ button: event.button,
+ buttons: event.buttons,
+ });
+ }, {capture: true});
+ }
+ }
+
+ function shiftEventsBefore(arrayOfEvents, aType) {
+ const index = arrayOfEvents.findIndex(event => event.type == aType);
+ if (index <= 0) {
+ return [];
+ }
+ let ret = [];
+ for (let i = 0; i < index; i++) {
+ ret.push(arrayOfEvents.shift());
+ }
+ return ret;
+ }
+
+ const parent = document.getElementById("parent");
+ const child = document.getElementById("child");
+
+ function promiseEvent(aType) {
+ return new Promise(resolve =>
+ addEventListener(aType, resolve, {once: true})
+ );
+ }
+
+ async function promiseFlushingAPZGestureState() {
+ await promiseApzFlushedRepaints();
+ // Wait for a while to avoid that the next tap will be treated as 2nd tap of
+ // a double tap.
+ return new Promise(
+ resolve => setTimeout(
+ resolve,
+ // NOTE: x1.0 is not enough to avoid intermittent failures.
+ SpecialPowers.getIntPref("apz.max_tap_time") * 1.2
+ )
+ );
+ }
+
+ await waitUntilApzStable();
+ for (const prefValue of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["test.events.async.enabled", prefValue],
+ ["ui.click_hold_context_menus.delay", 15000], // disable long tap
+ ]
+ });
+ const desc = `(test.events.async.enabled=${prefValue})`;
+
+ await (async function test_single_tap() {
+ await promiseFlushingAPZGestureState();
+ info("test_single_tap: testing...");
+ events = [];
+ const waitForClick = promiseEvent("click");
+ synthesizeTouch(child, 5, 5);
+ await waitForClick;
+ is(
+ stringifyEvents(events),
+ stringifyEvents([
+ { type: "touchend", target: child },
+ { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
+ { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
+ { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
+ { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
+ ]),
+ `Single tap should cause a click ${desc}`
+ );
+ })();
+
+ await (async function test_single_tap_with_consuming_touchstart() {
+ await promiseFlushingAPZGestureState();
+ info("test_single_tap_with_consuming_touchstart: testing...");
+ events = [];
+ const waitForTouchEnd = promiseEvent("touchend");
+ child.addEventListener("touchstart", event => {
+ event.preventDefault();
+ }, {once: true});
+ synthesizeTouch(child, 5, 5);
+ await waitForTouchEnd;
+ const result = stringifyEvents(events);
+ const expected = stringifyEvents([{ type: "touchend", target: child }]);
+ // If testing this with APZ, the result is really unstable. Let's allow to
+ // fail for now.
+ (prefValue && result != expected ? todo_is : is)(
+ result,
+ expected,
+ `Single tap should not cause mouse events if touchstart is consumed ${desc}`
+ );
+ })();
+
+
+ await (async function test_single_tap_with_consuming_touchend() {
+ await promiseFlushingAPZGestureState();
+ info("test_single_tap_with_consuming_touchend: testing...");
+ events = [];
+ const waitForTouchEnd = promiseEvent("touchend");
+ child.addEventListener("touchend", event => {
+ event.preventDefault();
+ }, {once: true});
+ synthesizeTouch(child, 5, 5);
+ await waitForTouchEnd;
+ is(
+ stringifyEvents(shiftEventsBefore(events)),
+ stringifyEvents([]),
+ `test_single_tap_with_consuming_touchstart() shouldn't cause mouse events after touchend`
+ )
+ is(
+ stringifyEvents(events),
+ stringifyEvents([
+ { type: "touchend", target: child },
+ ]),
+ `Single tap should not cause mouse events if touchend is consumed ${desc}`
+ );
+ })();
+
+ await (async function test_multi_touch() {
+ await promiseFlushingAPZGestureState();
+ events = [];
+ info("test_multi_touch: testing...");
+ const waitForTouchEnd = new Promise(resolve => {
+ let count = 0;
+ function onTouchEnd(event) {
+ if (++count == 2) {
+ removeEventListener("touchend", onTouchEnd, {capture: true});
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ }
+ }
+ addEventListener("touchend", onTouchEnd, {capture: true});
+ });
+ synthesizeTouch(child, [5, 25], 5);
+ await waitForTouchEnd;
+ is(
+ stringifyEvents(shiftEventsBefore(events)),
+ stringifyEvents([]),
+ `test_single_tap_with_consuming_touchend() shouldn't cause mouse events after touchend`
+ )
+ is(
+ stringifyEvents(events),
+ stringifyEvents([
+ { type: "touchend", target: child },
+ { type: "touchend", target: child },
+ ]),
+ `Multiple touch should not cause mouse events ${desc}`
+ );
+ })();
+ }
+ SimpleTest.finish();
+});
+</script>
+</head>
+<body><div id="parent"><div id="child"></div></div></body>
+</html>
diff --git a/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html b/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html
index 3898584b0a..43d8081606 100644
--- a/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html
+++ b/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html
@@ -39,7 +39,7 @@ SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async function doTests() {
let iframe = document.getElementById("iframe");
let waitForCheckKeyPressEventModelEvent = new Promise(resolve => {
- SpecialPowers.addSystemEventListener(iframe.contentDocument, "CheckKeyPressEventModel", resolve, {once: true});
+ SpecialPowers.wrap(iframe.contentDocument).addEventListener("CheckKeyPressEventModel", resolve, {mozSystemGroup: true, once: true});
});
iframe.contentDocument.body.setAttribute("contenteditable", "true");
await waitForCheckKeyPressEventModelEvent;
diff --git a/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html b/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html
index 68f52be41d..02047fef90 100644
--- a/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html
+++ b/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html
@@ -23,7 +23,7 @@ function srcdocLoaded() {
waitForCheckKeyPressEventModelEvent = new Promise(resolve => {
dump(document.querySelector("iframe").contentDocument.location + "\n");
var doc = document.querySelector("iframe").contentDocument;
- SpecialPowers.addSystemEventListener(doc, "CheckKeyPressEventModel", resolve, {once: true});
+ SpecialPowers.wrap(doc).addEventListener("CheckKeyPressEventModel", resolve, {mozSystemGroup: true, once: true});
doc.getElementById("WACViewPanel_EditingElement").contentEditable = "true";
});
}