diff --git a/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js b/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js index 93f9578ac3b4..2ef648f0c931 100644 --- a/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js +++ b/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js @@ -7,6 +7,10 @@ const ORIGIN = "https://example.com"; const PERMISSIONS_PAGE = getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) + "permissions.html"; +// Ignore promise rejection caused by clicking Deny button. +const { PromiseTestUtils } = Cu.import("resource://testing-common/PromiseTestUtils.jsm", {}); +PromiseTestUtils.whitelistRejectionsGlobally(/The request is not allowed/); + const EXPIRE_TIME_MS = 100; const TIMEOUT_MS = 500; diff --git a/browser/base/content/test/webrtc/head.js b/browser/base/content/test/webrtc/head.js index 4cda357e38fb..9384ab77aa94 100644 --- a/browser/base/content/test/webrtc/head.js +++ b/browser/base/content/test/webrtc/head.js @@ -54,7 +54,7 @@ function promiseWindow(url) { } Services.obs.removeObserver(obs, "domwindowopened"); - resolve(win); + executeSoon(() => resolve(win)); }, {once: true}); }, "domwindowopened"); }); @@ -120,7 +120,7 @@ async function assertWebRTCIndicatorStatus(expected) { win.addEventListener("unload", function listener(e) { if (e.target == win.document) { win.removeEventListener("unload", listener); - resolve(); + executeSoon(resolve); } }); }); @@ -141,7 +141,7 @@ async function assertWebRTCIndicatorStatus(expected) { if (document.readyState != "complete") return; document.removeEventListener("readystatechange", onReadyStateChange); - resolve(); + executeSoon(resolve); }); }); } @@ -166,7 +166,7 @@ function promisePopupEvent(popup, eventSuffix) { let eventType = "popup" + eventSuffix; return new Promise(resolve => { popup.addEventListener(eventType, function(event) { - resolve(); + executeSoon(resolve); }, {once: true}); }); @@ -297,7 +297,7 @@ function promisePopupNotificationShown(aName, aAction) { ok(PopupNotifications.isPanelOpen, "notification panel open"); ok(!!PopupNotifications.panel.firstChild, "notification panel populated"); - resolve(); + executeSoon(resolve); }, {once: true}); if (aAction) diff --git a/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js index 2f1f607d4e72..74efd4bc150e 100644 --- a/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js +++ b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js @@ -30,18 +30,25 @@ add_task(async function testAddOnBeforeCreatedWidget() { let viewNode = document.getElementById(kWidgetId + "idontexistyet"); ok(widgetNode, "Widget should exist"); ok(viewNode, "Panelview should exist"); + + let widgetPanel; + let panelShownPromise; + let viewShownPromise = new Promise(resolve => { + viewNode.addEventListener("ViewShown", () => { + widgetPanel = document.getElementById("customizationui-widget-panel"); + ok(widgetPanel, "Widget panel should exist"); + // Add the popupshown event listener directly inside the ViewShown event + // listener to avoid missing the event. + panelShownPromise = promisePanelElementShown(window, widgetPanel); + resolve(); + }, { once: true }); + }); widgetNode.click(); + await viewShownPromise; + await panelShownPromise; - let tempPanel = document.getElementById("customizationui-widget-panel"); - let panelShownPromise = promisePanelElementShown(window, tempPanel); - - await Promise.all([ - BrowserTestUtils.waitForEvent(viewNode, "ViewShown"), - panelShownPromise - ]); - - let panelHiddenPromise = promisePanelElementHidden(window, tempPanel); - tempPanel.hidePopup(); + let panelHiddenPromise = promisePanelElementHidden(window, widgetPanel); + widgetPanel.hidePopup(); await panelHiddenPromise; CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL); diff --git a/browser/components/customizableui/test/browser_customizemode_uidensity.js b/browser/components/customizableui/test/browser_customizemode_uidensity.js index cb4589334656..10a2bf5a7b9c 100644 --- a/browser/components/customizableui/test/browser_customizemode_uidensity.js +++ b/browser/components/customizableui/test/browser_customizemode_uidensity.js @@ -25,9 +25,11 @@ async function testModeMenuitem(mode, modePref) { is(normalItem.getAttribute("active"), "true", "Normal mode menuitem should be active by default"); - // Hover over the mode menuitem and wait until the UI density is updated. + // Hover over the mode menuitem and wait for the event that updates the UI + // density. + let mouseoverPromise = BrowserTestUtils.waitForEvent(item, "mouseover"); EventUtils.synthesizeMouseAtCenter(item, { type: "mouseover" }); - await BrowserTestUtils.waitForAttribute("uidensity", win, mode); + await mouseoverPromise; is(win.getAttribute("uidensity"), mode, `UI Density should be set to ${mode} on ${mode} menuitem hover`); diff --git a/browser/components/sessionstore/test/browser_crashedTabs.js b/browser/components/sessionstore/test/browser_crashedTabs.js index b1db20c620a1..0dde56c46b05 100644 --- a/browser/components/sessionstore/test/browser_crashedTabs.js +++ b/browser/components/sessionstore/test/browser_crashedTabs.js @@ -361,7 +361,7 @@ add_task(async function test_close_tab_after_crash() { // Crash the tab await BrowserTestUtils.crashBrowser(browser); - let promise = promiseEvent(gBrowser.tabContainer, "TabClose"); + let promise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose"); // Click the close tab button clickButton(browser, "closeTab"); diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js index 7ffa10fff706..0c2cf94fc2ef 100644 --- a/browser/components/sessionstore/test/head.js +++ b/browser/components/sessionstore/test/head.js @@ -471,20 +471,12 @@ function promiseDelayedStartupFinished(aWindow) { return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve)); } -function promiseEvent(element, eventType, isCapturing = false) { - return new Promise(resolve => { - element.addEventListener(eventType, function(event) { - resolve(event); - }, {capture: isCapturing, once: true}); - }); -} - function promiseTabRestored(tab) { - return promiseEvent(tab, "SSTabRestored"); + return BrowserTestUtils.waitForEvent(tab, "SSTabRestored"); } function promiseTabRestoring(tab) { - return promiseEvent(tab, "SSTabRestoring"); + return BrowserTestUtils.waitForEvent(tab, "SSTabRestoring"); } function sendMessage(browser, name, data = {}) { diff --git a/browser/components/translation/test/browser_translation_exceptions.js b/browser/components/translation/test/browser_translation_exceptions.js index 89abd8fa93bd..10dce94834cb 100644 --- a/browser/components/translation/test/browser_translation_exceptions.js +++ b/browser/components/translation/test/browser_translation_exceptions.js @@ -75,7 +75,7 @@ function openPopup(aPopup) { return new Promise(resolve => { aPopup.addEventListener("popupshown", function() { - resolve(); + TestUtils.executeSoon(resolve); }, {once: true}); aPopup.focus(); @@ -90,7 +90,7 @@ function waitForWindowLoad(aWin) { return new Promise(resolve => { aWin.addEventListener("load", function() { - resolve(); + TestUtils.executeSoon(resolve); }, {capture: true, once: true}); }); diff --git a/dom/base/DOMIntersectionObserver.cpp b/dom/base/DOMIntersectionObserver.cpp index de5f5ae0dd48..b73a338e0664 100644 --- a/dom/base/DOMIntersectionObserver.cpp +++ b/dom/base/DOMIntersectionObserver.cpp @@ -48,6 +48,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMIntersectionObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot) NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries) NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -55,6 +56,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMIntersectionObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -80,10 +82,7 @@ DOMIntersectionObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal, RefPtr observer = new DOMIntersectionObserver(window.forget(), aCb); - if (aOptions.mRoot) { - observer->mRoot = aOptions.mRoot; - observer->mRoot->RegisterIntersectionObserver(observer); - } + observer->mRoot = aOptions.mRoot; if (!observer->SetRootMargin(aOptions.mRootMargin)) { aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR, @@ -182,13 +181,8 @@ DOMIntersectionObserver::Unobserve(Element& aTarget) } void -DOMIntersectionObserver::UnlinkElement(Element& aTarget) +DOMIntersectionObserver::UnlinkTarget(Element& aTarget) { - if (mRoot && mRoot == &aTarget) { - mRoot = nullptr; - Disconnect(); - return; - } mObservationTargets.RemoveElement(&aTarget); if (mObservationTargets.Length() == 0) { Disconnect(); diff --git a/dom/base/DOMIntersectionObserver.h b/dom/base/DOMIntersectionObserver.h index a5cbeb1a6c87..553129591775 100644 --- a/dom/base/DOMIntersectionObserver.h +++ b/dom/base/DOMIntersectionObserver.h @@ -116,11 +116,7 @@ class DOMIntersectionObserver final : public nsISupports, public: DOMIntersectionObserver(already_AddRefed&& aOwner, mozilla::dom::IntersectionCallback& aCb) - : mOwner(aOwner), - mDocument(mOwner->GetExtantDoc()), - mCallback(&aCb), - mRoot(nullptr), - mConnected(false) + : mOwner(aOwner), mDocument(mOwner->GetExtantDoc()), mCallback(&aCb), mConnected(false) { } NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -156,7 +152,7 @@ public: void Observe(Element& aTarget); void Unobserve(Element& aTarget); - void UnlinkElement(Element& aTarget); + void UnlinkTarget(Element& aTarget); void Disconnect(); void TakeRecords(nsTArray>& aRetVal); @@ -180,12 +176,11 @@ protected: nsCOMPtr mOwner; RefPtr mDocument; RefPtr mCallback; - // Raw pointer which is explicitly cleared by UnlinkElement(). - Element* mRoot; + RefPtr mRoot; nsCSSRect mRootMargin; nsTArray mThresholds; - // Holds raw pointers which are explicitly cleared by UnlinkElement(). + // Holds raw pointers which are explicitly cleared by UnlinkTarget(). nsTArray mObservationTargets; nsTArray> mQueuedEntries; diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index fd074058928d..d9fff18e4587 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -4332,7 +4332,7 @@ IntersectionObserverPropertyDtor(void* aObject, nsAtom* aPropertyName, static_cast(aPropertyValue); for (auto iter = observers->Iter(); !iter.Done(); iter.Next()) { DOMIntersectionObserver* observer = iter.Key(); - observer->UnlinkElement(*element); + observer->UnlinkTarget(*element); } delete observers; } @@ -4354,7 +4354,7 @@ Element::RegisterIntersectionObserver(DOMIntersectionObserver* aObserver) } observers->LookupForAdd(aObserver).OrInsert([]() { - // If element is being observed, value can be: + // Value can be: // -2: Makes sure next calculated threshold always differs, leading to a // notification task being scheduled. // -1: Non-intersecting. @@ -4387,7 +4387,7 @@ Element::UnlinkIntersectionObservers() } for (auto iter = observers->Iter(); !iter.Done(); iter.Next()) { DOMIntersectionObserver* observer = iter.Key(); - observer->UnlinkElement(*this); + observer->UnlinkTarget(*this); } observers->Clear(); } diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 3cff6f5ec8f1..c54d39064353 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -610,7 +610,6 @@ skip-if = toolkit == 'android' [test_bug1375050.html] [test_bug1381710.html] [test_bug1384661.html] -[test_bug1399603.html] [test_bug1399605.html] [test_bug1404385.html] [test_bug1406102.html] diff --git a/dom/base/test/test_bug1399603.html b/dom/base/test/test_bug1399603.html deleted file mode 100644 index f35c37f6b698..000000000000 --- a/dom/base/test/test_bug1399603.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - Test for Bug 1399603 - - - - -Mozilla Bug 1399603 -

-
-
-
-
-
-
-
-
-
-
- - diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index 6170e746b78b..366c1c763233 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -4136,33 +4136,36 @@ EventStateManager::DispatchMouseOrPointerEvent(WidgetMouseEvent* aMouseEvent, return nullptr; } + nsCOMPtr targetContent = aTargetContent; + nsCOMPtr relatedContent = aRelatedContent; + nsAutoPtr dispatchEvent; CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage, - aRelatedContent, dispatchEvent); + relatedContent, dispatchEvent); AutoWeakFrame previousTarget = mCurrentTarget; - mCurrentTargetContent = aTargetContent; + mCurrentTargetContent = targetContent; nsIFrame* targetFrame = nullptr; nsEventStatus status = nsEventStatus_eIgnore; - ESMEventCB callback(aTargetContent); - EventDispatcher::Dispatch(aTargetContent, mPresContext, dispatchEvent, nullptr, + 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(aTargetContent); + targetFrame = mPresContext->GetPrimaryFrameFor(targetContent); // If we are entering/leaving remote content, dispatch a mouse enter/exit // event to the remote frame. - if (IsRemoteTarget(aTargetContent)) { + if (IsRemoteTarget(targetContent)) { if (aMessage == eMouseOut) { // For remote content, send a "top-level" widget mouse exit event. nsAutoPtr remoteEvent; CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget, - aRelatedContent, remoteEvent); + relatedContent, remoteEvent); remoteEvent->mExitFrom = WidgetMouseEvent::eTopLevel; // mCurrentTarget is set to the new target, so we must reset it to the @@ -4174,7 +4177,7 @@ EventStateManager::DispatchMouseOrPointerEvent(WidgetMouseEvent* aMouseEvent, } else if (aMessage == eMouseOver) { nsAutoPtr remoteEvent; CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseEnterIntoWidget, - aRelatedContent, remoteEvent); + relatedContent, remoteEvent); HandleCrossProcessEvent(remoteEvent, &status); } } diff --git a/layout/xul/test/browser_bug703210.js b/layout/xul/test/browser_bug703210.js index eee87cd71126..87f5968d4dcd 100644 --- a/layout/xul/test/browser_bug703210.js +++ b/layout/xul/test/browser_bug703210.js @@ -13,19 +13,19 @@ add_task(async function() { SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve); }); - // Send a mousemove at a known position to start the test. - await BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser); let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", false, event => { is(event.originalTarget.localName, "tooltip", "tooltip is showing"); return true; }); - await BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" }, browser); - await popupShownPromise; - let popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden", false, event => { is(event.originalTarget.localName, "tooltip", "tooltip is hidden"); return true; }); + + // Send a mousemove at a known position to start the test. + await BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser); + await BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" }, browser); + await popupShownPromise; await BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser); await popupHiddenPromise; diff --git a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm index b0d9e335736d..70aca92e7013 100644 --- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm +++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm @@ -741,8 +741,28 @@ this.BrowserTestUtils = { * let promiseEvent = BrowserTestUtils.waitForEvent(element, "eventName"); * // Do some processing here that will cause the event to be fired * // ... - * // Now yield until the Promise is fulfilled - * let receivedEvent = yield promiseEvent; + * // Now wait until the Promise is fulfilled + * let receivedEvent = await promiseEvent; + * + * The promise resolution/rejection handler for the returned promise is + * guaranteed not to be called until the next event tick after the event + * listener gets called, so that all other event listeners for the element + * are executed before the handler is executed. + * + * let promiseEvent = BrowserTestUtils.waitForEvent(element, "eventName"); + * // Same event tick here. + * await promiseEvent; + * // Next event tick here. + * + * If some code, such like adding yet another event listener, needs to be + * executed in the same event tick, use raw addEventListener instead and + * place the code inside the event listener. + * + * element.addEventListener("load", () => { + * // Add yet another event listener in the same event tick as the load + * // event listener. + * p = BrowserTestUtils.waitForEvent(element, "ready"); + * }, { once: true }); * * @param {Element} subject * The element that should receive the event. @@ -773,14 +793,14 @@ this.BrowserTestUtils = { return; } subject.removeEventListener(eventName, listener, capture); - resolve(event); + TestUtils.executeSoon(() => resolve(event)); } catch (ex) { try { subject.removeEventListener(eventName, listener, capture); } catch (ex2) { // Maybe the provided object does not support removeEventListener. } - reject(ex); + TestUtils.executeSoon(() => reject(ex)); } }, capture, wantsUntrusted); }); diff --git a/testing/mochitest/tests/browser/browser_BrowserTestUtils.js b/testing/mochitest/tests/browser/browser_BrowserTestUtils.js index f7090c56e735..317b3676e06d 100644 --- a/testing/mochitest/tests/browser/browser_BrowserTestUtils.js +++ b/testing/mochitest/tests/browser/browser_BrowserTestUtils.js @@ -68,3 +68,30 @@ add_task(async function() { ok(true, "Should have returned a rejected promise trying to unregister an unknown about page"); }); }); + +add_task(async function testWaitForEvent() { + // A promise returned by BrowserTestUtils.waitForEvent should not be resolved + // in the same event tick as the event listener is called. + let eventListenerCalled = false; + let waitForEventResolved = false; + // Use capturing phase to make sure the event listener added by + // BrowserTestUtils.waitForEvent is called before the normal event listener + // below. + let eventPromise = BrowserTestUtils.waitForEvent(gBrowser, "dummyevent", true); + eventPromise.then(() => { + waitForEventResolved = true; + }); + // Add normal event listener that is called after the event listener added by + // BrowserTestUtils.waitForEvent. + gBrowser.addEventListener("dummyevent", () => { + eventListenerCalled = true; + is(waitForEventResolved, false, "BrowserTestUtils.waitForEvent promise resolution handler shouldn't be called at this point."); + }, { once: true }); + + var event = new CustomEvent("dummyevent"); + gBrowser.dispatchEvent(event); + + await eventPromise; + + is(eventListenerCalled, true, "dummyevent listener should be called"); +}); diff --git a/toolkit/content/tests/browser/head.js b/toolkit/content/tests/browser/head.js index 4dc2f699e2c5..f7a7f461d4c9 100644 --- a/toolkit/content/tests/browser/head.js +++ b/toolkit/content/tests/browser/head.js @@ -143,7 +143,10 @@ class DateTimeTestHelper { async openPicker(pageUrl) { this.tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser); - // If dateTimePopupFrame doesn't exist yet, wait for the binding to be attached + // If dateTimePopupFrame doesn't exist yet, wait for the binding to be + // attached. + // FIXME: This has a race condition and we may miss the following events. + // (bug 1423498) if (!this.panel.dateTimePopupFrame) { await BrowserTestUtils.waitForEvent(this.panel, "DateTimePickerBindingReady"); } @@ -152,9 +155,19 @@ class DateTimeTestHelper { } async waitForPickerReady() { - await BrowserTestUtils.waitForEvent(this.frame, "load", true); + let readyPromise; + let loadPromise = new Promise(resolve => { + this.frame.addEventListener("load", () => { + // Add the PickerReady event listener directly inside the load event + // listener to avoid missing the event. + readyPromise = BrowserTestUtils.waitForEvent(this.frame.contentDocument, "PickerReady"); + resolve(); + }, { capture: true, once: true }); + }); + + await loadPromise; // Wait for picker elements to be ready - await BrowserTestUtils.waitForEvent(this.frame.contentDocument, "PickerReady"); + await readyPromise; } /**