From fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:14:29 +0200 Subject: Merging upstream version 125.0.1. Signed-off-by: Daniel Baumann --- dom/events/Clipboard.cpp | 84 ++++++-- dom/events/Clipboard.h | 7 +- dom/events/DataTransfer.cpp | 114 ++++++---- dom/events/DataTransfer.h | 25 ++- dom/events/EventListenerService.cpp | 63 ------ dom/events/EventNameList.h | 3 + dom/events/PointerEventHandler.cpp | 51 +++-- dom/events/PointerEventHandler.h | 38 +++- dom/events/TouchEvent.cpp | 2 +- dom/events/nsIEventListenerService.idl | 30 --- ..._navigator_clipboard_contextmenu_suppression.js | 163 ++++++++++++++- dom/events/test/mochitest.toml | 3 + .../pointerevents/mochitest_support_external.js | 2 + dom/events/test/pointerevents/test_bug1315862.html | 4 +- dom/events/test/test_accesskey.html | 2 +- dom/events/test/test_bug226361.xhtml | 9 +- dom/events/test/test_bug238987.html | 2 +- dom/events/test/test_bug336682.js | 2 + dom/events/test/test_bug448602.html | 21 -- .../test/test_mouse_events_after_touchend.html | 232 +++++++++++++++++++++ ...lit_keypress_event_model_on_old_Confluence.html | 2 +- ...ss_event_model_on_old_Office_Online_Server.html | 2 +- 22 files changed, 636 insertions(+), 225 deletions(-) create mode 100644 dom/events/test/test_mouse_events_after_touchend.html (limited to 'dom/events') 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 mPromise; }; +static nsTArray 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{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, 3> entries; - for (const auto& format : flavorList) { - auto entry = MakeRefPtr( - 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( + mGlobal, NS_ConvertUTF8toUTF16(format)); + entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData); + entries.AppendElement(std::move(entry)); + } } RefPtr 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 callback; + switch (aType) { + case ReadRequestType::eRead: { + callback = + MakeRefPtr(aOwner.AsGlobal(), &aPromise); + break; + } + case ReadRequestType::eReadText: { + callback = MakeRefPtr(&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(global, std::move(p)); rv = clipboardService->AsyncGetData( - // Mandatory data types defined in - // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x - AutoTArray{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(std::move(p)); rv = clipboardService->AsyncGetData( - AutoTArray{nsDependentCString(kTextMime)}, + AutoTArray{nsLiteralCString(kTextMime)}, nsIClipboard::kGlobalClipboard, owner->GetWindowContext(), &aPrincipal, callback); break; @@ -288,6 +328,24 @@ already_AddRefed 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 = + nsGlobalWindowInner::Cast(owner)->GetCurrentPasteDataTransfer()) { + // If there is valid nsIAsyncGetClipboardData, use it directly. + if (nsCOMPtr 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::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* 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& aResult) { // NOTE: When you change this method, you may need to change // GetExternalTransferableFormats() too since those methods should // work similarly. + MOZ_ASSERT(!mAsyncGetClipboardData); + + RefPtr wc = GetWindowContext(); + if (NS_WARN_IF(!wc)) { + MOZ_ASSERT_UNREACHABLE( + "How could this DataTransfer be created with a non-window global?"); + return; + } + nsCOMPtr clipboard = do_GetService("@mozilla.org/widget/clipboard;1"); - if (!clipboard || aWhichClipboard < 0) { + if (!clipboard || mClipboardType < 0) { return; } + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr asyncGetClipboardData; if (aPlainTextOnly) { - bool hasType; - AutoTArray textMime = {nsDependentCString(kTextMime)}; - nsresult rv = - clipboard->HasDataMatchingFlavors(textMime, aWhichClipboard, &hasType); - NS_SUCCEEDED(rv); - if (hasType) { - aResult->AppendElement(kTextMime); - } + rv = clipboard->GetDataSnapshotSync( + AutoTArray{nsLiteralCString(kTextMime)}, mClipboardType, + wc, getter_AddRefs(asyncGetClipboardData)); + } else { + AutoTArray formats; + formats.AppendElements(Span(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 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 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 DataTransfer::GetGlobal() const { return global.forget(); } +already_AddRefed DataTransfer::GetWindowContext() const { + nsCOMPtr 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 sysPrincipal = nsContentUtils::GetSystemPrincipal(); - nsTArray 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* 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 GetGlobal() const; + already_AddRefed 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& 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 mAsyncGetClipboardData; + // The items contained with the DataTransfer RefPtr 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 @@ -219,21 +219,6 @@ EventListenerService::GetListenerInfoFor( return NS_OK; } -NS_IMETHODIMP -EventListenerService::GetEventTargetChainFor( - EventTarget* aEventTarget, bool aComposed, - nsTArray>& aOutArray) { - NS_ENSURE_ARG(aEventTarget); - WidgetEvent event(true, eVoidEvent); - event.SetComposed(aComposed); - nsTArray 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) { @@ -257,54 +242,6 @@ static already_AddRefed ToEventListener( return listener.forget(); } -NS_IMETHODIMP -EventListenerService::AddSystemEventListener(EventTarget* aTarget, - const nsAString& aType, - JS::Handle aListener, - bool aUseCapture, JSContext* aCx) { - MOZ_ASSERT(aTarget, "Missing target"); - - NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED); - - RefPtr 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 aListener, - bool aUseCapture, - JSContext* aCx) { - MOZ_ASSERT(aTarget, "Missing target"); - - NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED); - - RefPtr 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 aListener, bool aUseCapture, 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 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 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 @@ -74,42 +74,12 @@ interface nsIEventListenerService : nsISupports */ Array 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 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 +/* 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

 
+
+
+
+
+
+
+
+
+
+ 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"; }); } -- cgit v1.2.3