Bug 1513895 - Unify PopupBlocker algorithm in 1 single file, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Sun, 16 Dec 2018 10:21:16 +0100
changeset 450880 9f36859b7db2cdd468082000cc7a9bc6436c1c5a
parent 450862 fca2da2300420bb5a1108ae9fa2c8b4f2b990a40
child 450881 81afead63671c9397c8379d52788d1bfb84813fb
push id35218
push usercsabou@mozilla.com
push dateMon, 17 Dec 2018 00:01:48 +0000
treeherdermozilla-central@3725bb5bafa0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1513895
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1513895 - Unify PopupBlocker algorithm in 1 single file, r=smaug
docshell/base/nsDocShell.cpp
dom/base/PopupBlocker.cpp
dom/base/PopupBlocker.h
dom/base/Timeout.cpp
dom/base/Timeout.h
dom/base/TimeoutManager.cpp
dom/base/moz.build
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsGlobalWindowInner.cpp
dom/base/nsGlobalWindowInner.h
dom/base/nsGlobalWindowOuter.cpp
dom/base/nsGlobalWindowOuter.h
dom/base/nsPIDOMWindow.h
dom/events/Event.cpp
dom/events/Event.h
dom/events/EventListenerManager.cpp
dom/events/EventListenerManager.h
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
dom/html/HTMLFormElement.cpp
dom/html/HTMLFormElement.h
dom/html/HTMLInputElement.cpp
dom/html/HTMLLabelElement.cpp
dom/html/nsGenericHTMLElement.cpp
dom/jsurl/nsJSProtocolHandler.cpp
dom/plugins/base/nsNPAPIPluginInstance.cpp
dom/plugins/base/nsNPAPIPluginInstance.h
dom/plugins/base/nsPluginInstanceOwner.cpp
layout/base/PresShell.cpp
layout/base/nsDocumentViewer.cpp
layout/build/nsLayoutStatics.cpp
modules/libpref/init/StaticPrefList.h
modules/libpref/init/all.js
toolkit/components/windowwatcher/nsWindowWatcher.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -42,16 +42,17 @@
 #include "mozilla/dom/ClientSource.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentFrameMessageManager.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLAnchorElement.h"
 #include "mozilla/dom/PerformanceNavigation.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/dom/ScreenOrientation.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/ServiceWorkerInterceptController.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/dom/SessionStorageManager.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabGroup.h"
@@ -3961,22 +3962,22 @@ nsDocShell::LoadURIWithOptions(const nsA
       return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
     }
   }
 
   if (NS_FAILED(rv) || !uri) {
     return NS_ERROR_FAILURE;
   }
 
-  PopupControlState popupState;
+  PopupBlocker::PopupControlState popupState;
   if (aLoadFlags & LOAD_FLAGS_ALLOW_POPUPS) {
-    popupState = openAllowed;
+    popupState = PopupBlocker::openAllowed;
     aLoadFlags &= ~LOAD_FLAGS_ALLOW_POPUPS;
   } else {
-    popupState = openOverridden;
+    popupState = PopupBlocker::openOverridden;
   }
   nsAutoPopupStatePusher statePusher(popupState);
 
   bool forceAllowDataURI = aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
 
   // Don't pass certain flags that aren't needed and end up confusing
   // ConvertLoadTypeToDocShellInfoLoadType.  We do need to ensure that they are
   // passed to LoadURI though, since it uses them.
@@ -12515,17 +12516,17 @@ class OnLinkClickEvent : public Runnable
  private:
   RefPtr<nsDocShell> mHandler;
   nsCOMPtr<nsIURI> mURI;
   nsString mTargetSpec;
   nsString mFileName;
   nsCOMPtr<nsIInputStream> mPostDataStream;
   nsCOMPtr<nsIInputStream> mHeadersDataStream;
   nsCOMPtr<nsIContent> mContent;
-  PopupControlState mPopupState;
+  PopupBlocker::PopupControlState mPopupState;
   bool mNoOpenerImplied;
   bool mIsUserTriggered;
   bool mIsTrusted;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
 };
 
 OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
                                    nsIURI* aURI, const nsAString& aTargetSpec,
@@ -12538,17 +12539,17 @@ OnLinkClickEvent::OnLinkClickEvent(nsDoc
     : mozilla::Runnable("OnLinkClickEvent"),
       mHandler(aHandler),
       mURI(aURI),
       mTargetSpec(aTargetSpec),
       mFileName(aFileName),
       mPostDataStream(aPostDataStream),
       mHeadersDataStream(aHeadersDataStream),
       mContent(aContent),
-      mPopupState(mHandler->mScriptGlobal->GetPopupControlState()),
+      mPopupState(PopupBlocker::GetPopupControlState()),
       mNoOpenerImplied(aNoOpenerImplied),
       mIsUserTriggered(aIsUserTriggered),
       mIsTrusted(aIsTrusted),
       mTriggeringPrincipal(aTriggeringPrincipal) {}
 
 NS_IMETHODIMP
 nsDocShell::OnLinkClick(nsIContent* aContent, nsIURI* aURI,
                         const nsAString& aTargetSpec,
new file mode 100644
--- /dev/null
+++ b/dom/base/PopupBlocker.cpp
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include "nsXULPopupManager.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+static char* sPopupAllowedEvents;
+
+static PopupBlocker::PopupControlState sPopupControlState = PopupBlocker::openAbused;
+static uint32_t sPopupStatePusherCount = 0;
+
+// This token is by default set to false. When a popup/filePicker is shown, it
+// is set to true.
+static bool sUnusedPopupToken = false;
+
+void PopupAllowedEventsChanged() {
+  if (sPopupAllowedEvents) {
+    free(sPopupAllowedEvents);
+  }
+
+  nsAutoCString str;
+  Preferences::GetCString("dom.popup_allowed_events", str);
+
+  // We'll want to do this even if str is empty to avoid looking up
+  // this pref all the time if it's not set.
+  sPopupAllowedEvents = ToNewCString(str);
+}
+
+// return true if eventName is contained within events, delimited by
+// spaces
+bool PopupAllowedForEvent(const char* eventName) {
+  if (!sPopupAllowedEvents) {
+    PopupAllowedEventsChanged();
+
+    if (!sPopupAllowedEvents) {
+      return false;
+    }
+  }
+
+  nsDependentCString events(sPopupAllowedEvents);
+
+  nsCString::const_iterator start, end;
+  nsCString::const_iterator startiter(events.BeginReading(start));
+  events.EndReading(end);
+
+  while (startiter != end) {
+    nsCString::const_iterator enditer(end);
+
+    if (!FindInReadable(nsDependentCString(eventName), startiter, enditer))
+      return false;
+
+    // the match is surrounded by spaces, or at a string boundary
+    if ((startiter == start || *--startiter == ' ') &&
+        (enditer == end || *enditer == ' ')) {
+      return true;
+    }
+
+    // Move on and see if there are other matches. (The delimitation
+    // requirement makes it pointless to begin the next search before
+    // the end of the invalid match just found.)
+    startiter = enditer;
+  }
+
+  return false;
+}
+
+// static
+void OnPrefChange(const char* aPrefName, void*) {
+  nsDependentCString prefName(aPrefName);
+  if (prefName.EqualsLiteral("dom.popup_allowed_events")) {
+    PopupAllowedEventsChanged();
+  }
+}
+
+}  // namespace
+
+/* static */
+PopupBlocker::PopupControlState PopupBlocker::PushPopupControlState(
+    PopupBlocker::PopupControlState aState, bool aForce) {
+  MOZ_ASSERT(NS_IsMainThread());
+  PopupBlocker::PopupControlState old = sPopupControlState;
+  if (aState < old || aForce) {
+    sPopupControlState = aState;
+  }
+  return old;
+}
+
+/* static */ void PopupBlocker::PopPopupControlState(
+    PopupBlocker::PopupControlState aState) {
+  MOZ_ASSERT(NS_IsMainThread());
+  sPopupControlState = aState;
+}
+
+/* static */ PopupBlocker::PopupControlState
+PopupBlocker::GetPopupControlState() {
+  return sPopupControlState;
+}
+
+/* static */ bool PopupBlocker::CanShowPopupByPermission(
+    nsIPrincipal* aPrincipal) {
+  MOZ_ASSERT(aPrincipal);
+  uint32_t permit;
+  nsCOMPtr<nsIPermissionManager> permissionManager =
+      services::GetPermissionManager();
+
+  if (permissionManager &&
+      NS_SUCCEEDED(permissionManager->TestPermissionFromPrincipal(
+          aPrincipal, "popup", &permit))) {
+    if (permit == nsIPermissionManager::ALLOW_ACTION) {
+      return true;
+    }
+    if (permit == nsIPermissionManager::DENY_ACTION) {
+      return false;
+    }
+  }
+
+  return !StaticPrefs::dom_disable_open_during_load();
+}
+
+/* static */ bool PopupBlocker::TryUsePopupOpeningToken() {
+  MOZ_ASSERT(sPopupStatePusherCount);
+
+  if (!sUnusedPopupToken) {
+    sUnusedPopupToken = true;
+    return true;
+  }
+
+  return false;
+}
+
+/* static */ void PopupBlocker::PopupStatePusherCreated() {
+  ++sPopupStatePusherCount;
+}
+
+/* static */ void PopupBlocker::PopupStatePusherDestroyed() {
+  MOZ_ASSERT(sPopupStatePusherCount);
+
+  if (!--sPopupStatePusherCount) {
+    sUnusedPopupToken = false;
+  }
+}
+
+// static
+PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState(
+    WidgetEvent* aEvent, Event* aDOMEvent) {
+  // generally if an event handler is running, new windows are disallowed.
+  // check for exceptions:
+  PopupBlocker::PopupControlState abuse = PopupBlocker::openAbused;
+
+  if (aDOMEvent && aDOMEvent->GetWantsPopupControlCheck()) {
+    nsAutoString type;
+    aDOMEvent->GetType(type);
+    if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) {
+      return PopupBlocker::openAllowed;
+    }
+  }
+
+  switch (aEvent->mClass) {
+    case eBasicEventClass:
+      // For these following events only allow popups if they're
+      // triggered while handling user input. See
+      // nsPresShell::HandleEventInternal() for details.
+      if (EventStateManager::IsHandlingUserInput()) {
+        abuse = PopupBlocker::openBlocked;
+        switch (aEvent->mMessage) {
+          case eFormSelect:
+            if (PopupAllowedForEvent("select")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eFormChange:
+            if (PopupAllowedForEvent("change")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    case eEditorInputEventClass:
+      // For this following event only allow popups if it's triggered
+      // while handling user input. See
+      // nsPresShell::HandleEventInternal() for details.
+      if (EventStateManager::IsHandlingUserInput()) {
+        abuse = PopupBlocker::openBlocked;
+        switch (aEvent->mMessage) {
+          case eEditorInput:
+            if (PopupAllowedForEvent("input")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    case eInputEventClass:
+      // For this following event only allow popups if it's triggered
+      // while handling user input. See
+      // nsPresShell::HandleEventInternal() for details.
+      if (EventStateManager::IsHandlingUserInput()) {
+        abuse = PopupBlocker::openBlocked;
+        switch (aEvent->mMessage) {
+          case eFormChange:
+            if (PopupAllowedForEvent("change")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eXULCommand:
+            abuse = PopupBlocker::openControlled;
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    case eKeyboardEventClass:
+      if (aEvent->IsTrusted()) {
+        abuse = PopupBlocker::openBlocked;
+        uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode;
+        switch (aEvent->mMessage) {
+          case eKeyPress:
+            // return key on focused button. see note at eMouseClick.
+            if (key == NS_VK_RETURN) {
+              abuse = PopupBlocker::openAllowed;
+            } else if (PopupAllowedForEvent("keypress")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eKeyUp:
+            // space key on focused button. see note at eMouseClick.
+            if (key == NS_VK_SPACE) {
+              abuse = PopupBlocker::openAllowed;
+            } else if (PopupAllowedForEvent("keyup")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eKeyDown:
+            if (PopupAllowedForEvent("keydown")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    case eTouchEventClass:
+      if (aEvent->IsTrusted()) {
+        abuse = PopupBlocker::openBlocked;
+        switch (aEvent->mMessage) {
+          case eTouchStart:
+            if (PopupAllowedForEvent("touchstart")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eTouchEnd:
+            if (PopupAllowedForEvent("touchend")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    case eMouseEventClass:
+      if (aEvent->IsTrusted() &&
+          aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
+        abuse = PopupBlocker::openBlocked;
+        switch (aEvent->mMessage) {
+          case eMouseUp:
+            if (PopupAllowedForEvent("mouseup")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eMouseDown:
+            if (PopupAllowedForEvent("mousedown")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eMouseClick:
+            /* Click events get special treatment because of their
+               historical status as a more legitimate event handler. If
+               click popups are enabled in the prefs, clear the popup
+               status completely. */
+            if (PopupAllowedForEvent("click")) {
+              abuse = PopupBlocker::openAllowed;
+            }
+            break;
+          case eMouseDoubleClick:
+            if (PopupAllowedForEvent("dblclick")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    case ePointerEventClass:
+      if (aEvent->IsTrusted() &&
+          aEvent->AsPointerEvent()->button == WidgetMouseEvent::eLeftButton) {
+        switch (aEvent->mMessage) {
+          case ePointerUp:
+            if (PopupAllowedForEvent("pointerup")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case ePointerDown:
+            if (PopupAllowedForEvent("pointerdown")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    case eFormEventClass:
+      // For these following events only allow popups if they're
+      // triggered while handling user input. See
+      // nsPresShell::HandleEventInternal() for details.
+      if (EventStateManager::IsHandlingUserInput()) {
+        abuse = PopupBlocker::openBlocked;
+        switch (aEvent->mMessage) {
+          case eFormSubmit:
+            if (PopupAllowedForEvent("submit")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          case eFormReset:
+            if (PopupAllowedForEvent("reset")) {
+              abuse = PopupBlocker::openControlled;
+            }
+            break;
+          default:
+            break;
+        }
+      }
+      break;
+    default:
+      break;
+  }
+
+  return abuse;
+}
+
+/* static */ void PopupBlocker::Initialize() {
+  DebugOnly<nsresult> rv =
+      Preferences::RegisterCallback(OnPrefChange, "dom.popup_allowed_events");
+  MOZ_ASSERT(NS_SUCCEEDED(rv),
+             "Failed to observe \"dom.popup_allowed_events\"");
+}
+
+/* static */ void PopupBlocker::Shutdown() {
+  if (sPopupAllowedEvents) {
+    free(sPopupAllowedEvents);
+  }
+
+  Preferences::UnregisterCallback(OnPrefChange, "dom.popup_allowed_events");
+}
+
+}  // namespace dom
+}  // namespace mozilla
+
+nsAutoPopupStatePusherInternal::nsAutoPopupStatePusherInternal(
+    mozilla::dom::PopupBlocker::PopupControlState aState, bool aForce)
+    : mOldState(
+          mozilla::dom::PopupBlocker::PushPopupControlState(aState, aForce)) {
+  mozilla::dom::PopupBlocker::PopupStatePusherCreated();
+}
+
+nsAutoPopupStatePusherInternal::~nsAutoPopupStatePusherInternal() {
+  mozilla::dom::PopupBlocker::PopPopupControlState(mOldState);
+  mozilla::dom::PopupBlocker::PopupStatePusherDestroyed();
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/PopupBlocker.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PopupBlocker_h
+#define mozilla_dom_PopupBlocker_h
+
+#include "mozilla/BasicEvents.h"
+
+class nsIDocument;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace dom {
+
+class PopupBlocker final {
+ public:
+  // Popup control state enum. The values in this enum must go from most
+  // permissive to least permissive so that it's safe to push state in
+  // all situations. Pushing popup state onto the stack never makes the
+  // current popup state less permissive.
+  enum PopupControlState {
+    openAllowed = 0,  // open that window without worries
+    openControlled,   // it's a popup, but allow it
+    openBlocked,      // it's a popup, but not from an allowed event
+    openAbused,       // it's a popup. disallow it, but allow domain override.
+    openOverridden    // disallow window open
+  };
+
+  static PopupControlState PushPopupControlState(PopupControlState aState,
+                                                 bool aForce);
+
+  static void PopPopupControlState(PopupControlState aState);
+
+  static PopupControlState GetPopupControlState();
+
+  static void PopupStatePusherCreated();
+  static void PopupStatePusherDestroyed();
+
+  // This method checks if the principal is allowed by open popups by user
+  // permissions. In this case, the caller should not block popups.
+  static bool CanShowPopupByPermission(nsIPrincipal* aPrincipal);
+
+  // This method returns true if the caller is allowed to show a popup, and it
+  // consumes the popup token for the current event. There is just 1 popup
+  // allowed per event.
+  static bool TryUsePopupOpeningToken();
+
+  static PopupBlocker::PopupControlState GetEventPopupControlState(
+      WidgetEvent* aEvent, Event* aDOMEvent = nullptr);
+
+  static void Initialize();
+  static void Shutdown();
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#ifdef MOZILLA_INTERNAL_API
+#define NS_AUTO_POPUP_STATE_PUSHER nsAutoPopupStatePusherInternal
+#else
+#define NS_AUTO_POPUP_STATE_PUSHER nsAutoPopupStatePusherExternal
+#endif
+
+// Helper class that helps with pushing and popping popup control
+// state. Note that this class looks different from within code that's
+// part of the layout library than it does in code outside the layout
+// library.  We give the two object layouts different names so the symbols
+// don't conflict, but code should always use the name
+// |nsAutoPopupStatePusher|.
+class NS_AUTO_POPUP_STATE_PUSHER {
+ public:
+#ifdef MOZILLA_INTERNAL_API
+  explicit NS_AUTO_POPUP_STATE_PUSHER(
+      mozilla::dom::PopupBlocker::PopupControlState aState,
+      bool aForce = false);
+  ~NS_AUTO_POPUP_STATE_PUSHER();
+#else
+  NS_AUTO_POPUP_STATE_PUSHER(
+      nsPIDOMWindowOuter* aWindow,
+      mozilla::dom::PopupBlocker::PopupControlState aState)
+      : mWindow(aWindow), mOldState(openAbused) {
+    if (aWindow) {
+      mOldState = PopupBlocker::PushPopupControlState(aState, false);
+    }
+  }
+
+  ~NS_AUTO_POPUP_STATE_PUSHER() {
+    if (mWindow) {
+      PopupBlocker::PopPopupControlState(mOldState);
+    }
+  }
+#endif
+
+ protected:
+#ifndef MOZILLA_INTERNAL_API
+  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+#endif
+  mozilla::dom::PopupBlocker::PopupControlState mOldState;
+
+ private:
+  // Hide so that this class can only be stack-allocated
+  static void* operator new(size_t /*size*/) CPP_THROW_NEW { return nullptr; }
+  static void operator delete(void* /*memory*/) {}
+};
+
+#define nsAutoPopupStatePusher NS_AUTO_POPUP_STATE_PUSHER
+
+#endif  // mozilla_PopupBlocker_h
--- a/dom/base/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -2,24 +2,25 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Timeout.h"
 
 #include "mozilla/dom/TimeoutManager.h"
+#include "nsGlobalWindowInner.h"
 
 namespace mozilla {
 namespace dom {
 
 Timeout::Timeout()
     : mTimeoutId(0),
       mFiringId(TimeoutManager::InvalidFiringId),
-      mPopupState(openAllowed),
+      mPopupState(PopupBlocker::openAllowed),
       mReason(Reason::eTimeoutOrInterval),
       mNestingLevel(0),
       mCleared(false),
       mRunning(false),
       mIsInterval(false) {}
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Timeout)
 
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -2,26 +2,27 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_timeout_h
 #define mozilla_dom_timeout_h
 
+#include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsGlobalWindow.h"
 #include "nsITimeoutHandler.h"
 
 class nsIEventTarget;
 class nsIPrincipal;
 class nsIEventTarget;
+class nsGlobalWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 /*
  * Timeout struct that holds information about each script
  * timeout.  Holds a strong reference to an nsITimeoutHandler, which
  * abstracts the language specific cruft.
@@ -78,17 +79,17 @@ class Timeout final : public LinkedListE
   uint32_t mTimeoutId;
 
   // Identifies which firing level this Timeout is being processed in
   // when sync loops trigger nested firing.
   uint32_t mFiringId;
 
   // The popup state at timeout creation time if not created from
   // another timeout
-  PopupControlState mPopupState;
+  PopupBlocker::PopupControlState mPopupState;
 
   // Used to allow several reasons for setting a timeout, where each
   // 'Reason' value is using a possibly overlapping set of id:s.
   Reason mReason;
 
   // Between 0 and DOM_CLAMP_TIMEOUT_NESTING_LEVEL.  Currently we don't
   // care about nesting levels beyond that value.
   uint8_t mNestingLevel;
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -1,27 +1,27 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TimeoutManager.h"
-#include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "mozilla/Logging.h"
 #include "mozilla/PerformanceCounter.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/ThrottledEventQueue.h"
 #include "mozilla/TimeStamp.h"
 #include "nsIDocShell.h"
 #include "nsINamed.h"
 #include "nsITimeoutHandler.h"
 #include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/dom/TabGroup.h"
 #include "TimeoutExecutor.h"
 #include "TimeoutBudgetManager.h"
 #include "mozilla/net/WebSocketEventService.h"
 #include "mozilla/MediaManager.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -491,17 +491,17 @@ nsresult TimeoutManager::SetTimeout(nsIT
   RefPtr<Timeout> timeout = new Timeout();
   timeout->mWindow = &mWindow;
   timeout->mIsInterval = aIsInterval;
   timeout->mInterval = TimeDuration::FromMilliseconds(interval);
   timeout->mScriptHandler = aHandler;
   timeout->mReason = aReason;
 
   // No popups from timeouts by default
-  timeout->mPopupState = openAbused;
+  timeout->mPopupState = PopupBlocker::openAbused;
 
   timeout->mNestingLevel = sNestingLevel < DOM_CLAMP_TIMEOUT_NESTING_LEVEL
                                ? sNestingLevel + 1
                                : sNestingLevel;
 
   // Now clamp the actual interval we will use for the timer based on
   TimeDuration realInterval = CalculateDelay(timeout);
   TimeStamp now = TimeStamp::Now();
@@ -511,27 +511,27 @@ nsresult TimeoutManager::SetTimeout(nsIT
   if (!mWindow.IsSuspended()) {
     nsresult rv = MaybeSchedule(timeout->When(), now);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   if (gRunningTimeoutDepth == 0 &&
-      nsContentUtils::GetPopupControlState() < openBlocked) {
+      PopupBlocker::GetPopupControlState() < PopupBlocker::openBlocked) {
     // This timeout is *not* set from another timeout and it's set
     // while popups are enabled. Propagate the state to the timeout if
     // its delay (interval) is equal to or less than what
     // "dom.disable_open_click_delay" is set to (in ms).
 
     // This is checking |interval|, not realInterval, on purpose,
     // because our lower bound for |realInterval| could be pretty high
     // in some cases.
     if (interval <= gDisableOpenClickDelay) {
-      timeout->mPopupState = nsContentUtils::GetPopupControlState();
+      timeout->mPopupState = PopupBlocker::GetPopupControlState();
     }
   }
 
   Timeouts::SortBy sort(mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
                                            : Timeouts::SortBy::TimeWhen);
   mTimeouts.Insert(timeout, sort);
 
   timeout->mTimeoutId = GetTimeoutId(aReason);
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -204,16 +204,17 @@ EXPORTS.mozilla.dom += [
     'NodeIterator.h',
     'ParentProcessMessageManager.h',
     'PlacesBookmark.h',
     'PlacesBookmarkAddition.h',
     'PlacesEvent.h',
     'PlacesObservers.h',
     'PlacesVisit.h',
     'PlacesWeakCallbackWrapper.h',
+    'PopupBlocker.h',
     'Pose.h',
     'ProcessMessageManager.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenLuminance.h',
     'ScreenOrientation.h',
     'Selection.h',
     'ShadowRoot.h',
@@ -358,16 +359,17 @@ UNIFIED_SOURCES += [
     'nsTreeSanitizer.cpp',
     'nsViewportInfo.cpp',
     'nsWindowMemoryReporter.cpp',
     'nsWindowRoot.cpp',
     'nsWrapperCache.cpp',
     'nsXHTMLContentSerializer.cpp',
     'nsXMLContentSerializer.cpp',
     'ParentProcessMessageManager.cpp',
+    'PopupBlocker.cpp',
     'Pose.cpp',
     'PostMessageEvent.cpp',
     'ProcessMessageManager.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenLuminance.cpp',
     'ScreenOrientation.cpp',
     'Selection.cpp',
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -285,17 +285,16 @@ uint32_t nsContentUtils::sScriptBlockerC
 uint32_t nsContentUtils::sDOMNodeRemovedSuppressCount = 0;
 AutoTArray<nsCOMPtr<nsIRunnable>, 8>* nsContentUtils::sBlockedScriptRunners =
     nullptr;
 uint32_t nsContentUtils::sRunnersCountAtFirstBlocker = 0;
 nsIInterfaceRequestor* nsContentUtils::sSameOriginChecker = nullptr;
 
 bool nsContentUtils::sIsHandlingKeyBoardEvent = false;
 bool nsContentUtils::sAllowXULXBL_for_file = false;
-bool nsContentUtils::sDisablePopups = false;
 
 nsString* nsContentUtils::sShiftText = nullptr;
 nsString* nsContentUtils::sControlText = nullptr;
 nsString* nsContentUtils::sMetaText = nullptr;
 nsString* nsContentUtils::sOSText = nullptr;
 nsString* nsContentUtils::sAltText = nullptr;
 nsString* nsContentUtils::sModifierSeparator = nullptr;
 
@@ -345,20 +344,16 @@ nsIFragmentContentSink* nsContentUtils::
 bool nsContentUtils::sFragmentParsingActive = false;
 
 bool nsContentUtils::sDoNotTrackEnabled = false;
 
 bool nsContentUtils::sAntiTrackingControlCenterUIEnabled = false;
 
 mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump");
 
-PopupControlState nsContentUtils::sPopupControlState = openAbused;
-uint32_t nsContentUtils::sPopupStatePusherCount = 0;
-bool nsContentUtils::sUnusedPopupToken = false;
-
 int32_t nsContentUtils::sInnerOrOuterWindowCount = 0;
 uint32_t nsContentUtils::sInnerOrOuterWindowSerialCounter = 0;
 
 // Subset of
 // http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name
 enum AutocompleteUnsupportedFieldName : uint8_t {
 #define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_) \
   eAutocompleteUnsupportedFieldName_##name_,
@@ -698,19 +693,16 @@ nsresult nsContentUtils::Init() {
                                "dom.placeholder.show_on_focus", true);
 
   Preferences::AddBoolVarCache(&sAutoFocusEnabled, "browser.autofocus", true);
 
   Preferences::AddBoolVarCache(&sIsBytecodeCacheEnabled,
                                "dom.script_loader.bytecode_cache.enabled",
                                false);
 
-  Preferences::AddBoolVarCache(&sDisablePopups, "dom.disable_open_during_load",
-                               false);
-
   Preferences::AddBoolVarCache(
       &sAntiTrackingControlCenterUIEnabled,
       "browser.contentblocking.rejecttrackers.control-center.ui.enabled",
       false);
 
   Preferences::AddIntVarCache(&sBytecodeCacheStrategy,
                               "dom.script_loader.bytecode_cache.strategy", 0);
 
@@ -10407,48 +10399,16 @@ nsContentUtils::TryGetTabChildGlobal(nsI
 }
 
 /* static */ void nsContentUtils::InnerOrOuterWindowDestroyed() {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInnerOrOuterWindowCount > 0);
   --sInnerOrOuterWindowCount;
 }
 
-/* static */ bool nsContentUtils::CanShowPopupByPermission(
-    nsIPrincipal* aPrincipal) {
-  MOZ_ASSERT(aPrincipal);
-  uint32_t permit;
-  nsCOMPtr<nsIPermissionManager> permissionManager =
-      services::GetPermissionManager();
-
-  if (permissionManager &&
-      NS_SUCCEEDED(permissionManager->TestPermissionFromPrincipal(
-          aPrincipal, "popup", &permit))) {
-    if (permit == nsIPermissionManager::ALLOW_ACTION) {
-      return true;
-    }
-    if (permit == nsIPermissionManager::DENY_ACTION) {
-      return false;
-    }
-  }
-
-  return !sDisablePopups;
-}
-
-/* static */ bool nsContentUtils::TryUsePopupOpeningToken() {
-  MOZ_ASSERT(sPopupStatePusherCount);
-
-  if (!sUnusedPopupToken) {
-    sUnusedPopupToken = true;
-    return true;
-  }
-
-  return false;
-}
-
 static bool JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData) {
   nsAString* result = static_cast<nsAString*>(aData);
   result->Append(static_cast<const char16_t*>(aBuf),
                  static_cast<uint32_t>(aLen));
   return true;
 }
 
 /* static */ bool nsContentUtils::StringifyJSON(
@@ -10541,20 +10501,8 @@ static bool JSONCreator(const char16_t* 
         host.Find(".", false, startIndexOfCurrentLevel + 1);
     if (startIndexOfNextLevel <= 0) {
       return false;
     }
     host = NS_LITERAL_CSTRING("*") +
            nsDependentCSubstring(host, startIndexOfNextLevel);
   }
 }
-
-/* static */ void nsContentUtils::PopupStatePusherCreated() {
-  ++sPopupStatePusherCount;
-}
-
-/* static */ void nsContentUtils::PopupStatePusherDestroyed() {
-  MOZ_ASSERT(sPopupStatePusherCount);
-
-  if (!--sPopupStatePusherCount) {
-    sUnusedPopupToken = false;
-  }
-}
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3275,54 +3275,25 @@ class nsContentUtils {
 
   static void AsyncPrecreateStringBundles();
 
   static bool ContentIsLink(nsIContent* aContent);
 
   static already_AddRefed<mozilla::dom::ContentFrameMessageManager>
   TryGetTabChildGlobal(nsISupports* aFrom);
 
-  static PopupControlState PushPopupControlState(PopupControlState aState,
-                                                 bool aForce) {
-    MOZ_ASSERT(NS_IsMainThread());
-    PopupControlState old = sPopupControlState;
-    if (aState < old || aForce) {
-      sPopupControlState = aState;
-    }
-    return old;
-  }
-
-  static void PopPopupControlState(PopupControlState aState) {
-    MOZ_ASSERT(NS_IsMainThread());
-    sPopupControlState = aState;
-  }
-
-  static PopupControlState GetPopupControlState() { return sPopupControlState; }
-
-  static void PopupStatePusherCreated();
-  static void PopupStatePusherDestroyed();
-
   // Get a serial number for a newly created inner or outer window.
   static uint32_t InnerOrOuterWindowCreated();
   // Record that an inner or outer window has been destroyed.
   static void InnerOrOuterWindowDestroyed();
   // Get the current number of inner or outer windows.
   static int32_t GetCurrentInnerOrOuterWindowCount() {
     return sInnerOrOuterWindowCount;
   }
 
-  // This method checks if the principal is allowed by open popups by user
-  // permissions. In this case, the caller should not block popups.
-  static bool CanShowPopupByPermission(nsIPrincipal* aPrincipal);
-
-  // This method returns true if the caller is allowed to show a popup, and it
-  // consumes the popup token for the current event. There is just 1 popup
-  // allowed per event.
-  static bool TryUsePopupOpeningToken();
-
   /**
    * Serializes a JSON-like JS::Value into a string.
    *
    * Usage:
    *   nsAutoString serializedValue;
    *   nsContentUtils::StringifyJSON(cx, &value, serializedValue);
    */
   static bool StringifyJSON(JSContext* aCx, JS::MutableHandle<JS::Value> vp,
@@ -3443,17 +3414,16 @@ class nsContentUtils {
   static AutoTArray<nsCOMPtr<nsIRunnable>, 8>* sBlockedScriptRunners;
   static uint32_t sRunnersCountAtFirstBlocker;
   static uint32_t sScriptBlockerCountWhereRunnersPrevented;
 
   static nsIInterfaceRequestor* sSameOriginChecker;
 
   static bool sIsHandlingKeyBoardEvent;
   static bool sAllowXULXBL_for_file;
-  static bool sDisablePopups;
   static bool sIsFullscreenApiEnabled;
   static bool sIsUnprefixedFullscreenApiEnabled;
   static bool sTrustedFullscreenOnly;
   static bool sIsCutCopyAllowed;
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
   static bool sIsPerformanceNavigationTimingEnabled;
@@ -3503,23 +3473,16 @@ class nsContentUtils {
 
   // Alternate data mime type, used by the ScriptLoader to register and read the
   // bytecode out of the nsCacheInfoChannel.
   static nsCString* sJSBytecodeMimeType;
 
   static bool sDoNotTrackEnabled;
   static mozilla::LazyLogModule sDOMDumpLog;
 
-  static PopupControlState sPopupControlState;
-  static uint32_t sPopupStatePusherCount;
-
-  // This token is by default set to false. When a popup/filePicker is shown, it
-  // is set to true.
-  static bool sUnusedPopupToken;
-
   static int32_t sInnerOrOuterWindowCount;
   static uint32_t sInnerOrOuterWindowSerialCounter;
 };
 
 /* static */ inline nsContentPolicyType
 nsContentUtils::InternalContentPolicyTypeToExternal(nsContentPolicyType aType) {
   switch (aType) {
     case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -224,16 +224,17 @@
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/cache/CacheStorage.h"
 #include "mozilla/dom/Console.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/HashChangeEvent.h"
 #include "mozilla/dom/IntlUtils.h"
 #include "mozilla/dom/PopStateEvent.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/dom/PopupBlockedEvent.h"
 #include "mozilla/dom/PrimitiveConversions.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/dom/ImageBitmap.h"
@@ -1589,20 +1590,16 @@ nsIScriptContext* nsGlobalWindowInner::G
 JSObject* nsGlobalWindowInner::GetGlobalJSObject() {
   return FastGetGlobalJSObject();
 }
 
 void nsGlobalWindowInner::TraceGlobalJSObject(JSTracer* aTrc) {
   TraceWrapper(aTrc, "active window global");
 }
 
-PopupControlState nsGlobalWindowInner::GetPopupControlState() const {
-  return nsContentUtils::GetPopupControlState();
-}
-
 nsresult nsGlobalWindowInner::SetNewDocument(nsIDocument* aDocument,
                                              nsISupports* aState,
                                              bool aForceReuseInnerWindow) {
   MOZ_ASSERT(mDocumentPrincipal == nullptr,
              "mDocumentPrincipal prematurely set!");
   MOZ_ASSERT(aDocument);
 
   if (!mOuterWindow) {
@@ -1932,17 +1929,17 @@ bool nsGlobalWindowInner::DialogsAreBein
   }
 
   TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime);
   if (dialogInterval.ToSeconds() <
       Preferences::GetInt("dom.successive_dialog_time_limit",
                           DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) {
     mDialogAbuseCount++;
 
-    return GetPopupControlState() > openAllowed ||
+    return PopupBlocker::GetPopupControlState() > PopupBlocker::openAllowed ||
            mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT;
   }
 
   // Reset the abuse counter
   mDialogAbuseCount = 0;
 
   return false;
 }
@@ -5948,17 +5945,17 @@ bool nsGlobalWindowInner::RunTimeoutHand
   // Push this timeout's popup control state, which should only be
   // eabled the first time a timeout fires that was created while
   // popups were enabled and with a delay less than
   // "dom.disable_open_click_delay".
   nsAutoPopupStatePusher popupStatePusher(timeout->mPopupState);
 
   // Clear the timeout's popup state, if any, to prevent interval
   // timeouts from repeatedly opening poups.
-  timeout->mPopupState = openAbused;
+  timeout->mPopupState = PopupBlocker::openAbused;
 
   bool trackNestingLevel = !timeout->mIsInterval;
   uint32_t nestingLevel;
   if (trackNestingLevel) {
     nestingLevel = TimeoutManager::GetNestingLevel();
     TimeoutManager::SetNestingLevel(timeout->mNestingLevel);
   }
 
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -316,18 +316,16 @@ class nsGlobalWindowInner final : public
   bool DispatchEvent(mozilla::dom::Event& aEvent,
                      mozilla::dom::CallerType aCallerType,
                      mozilla::ErrorResult& aRv) override;
 
   void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
 
   nsresult PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
 
-  virtual PopupControlState GetPopupControlState() const override;
-
   void Suspend();
   void Resume();
   virtual bool IsSuspended() const override;
 
   // Calling Freeze() on a window will automatically Suspend() it.  In
   // addition, the window and its children are further treated as no longer
   // suitable for interaction with the user.  For example, it may be marked
   // non-visible, cannot be focused, etc.  All worker threads are also frozen
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1351,29 +1351,16 @@ void nsGlobalWindowOuter::SetInitialPrin
 
   if (shell && !shell->DidInitialize()) {
     // Ensure that if someone plays with this document they will get
     // layout happening.
     shell->Initialize();
   }
 }
 
-PopupControlState nsGlobalWindowOuter::PushPopupControlState(
-    PopupControlState aState, bool aForce) const {
-  return nsContentUtils::PushPopupControlState(aState, aForce);
-}
-
-void nsGlobalWindowOuter::PopPopupControlState(PopupControlState aState) const {
-  nsContentUtils::PopPopupControlState(aState);
-}
-
-PopupControlState nsGlobalWindowOuter::GetPopupControlState() const {
-  return nsContentUtils::GetPopupControlState();
-}
-
 #define WINDOWSTATEHOLDER_IID                        \
   {                                                  \
     0x0b917c3e, 0xbd50, 0x4683, {                    \
       0xaf, 0xc9, 0xc7, 0x81, 0x07, 0xae, 0x33, 0x26 \
     }                                                \
   }
 
 class WindowStateHolder final : public nsISupports {
@@ -2369,17 +2356,17 @@ bool nsGlobalWindowOuter::ConfirmDialogI
 
   if (!promptSvc) {
     return true;
   }
 
   // Reset popup state while opening a modal dialog, and firing events
   // about the dialog, to prevent the current state from being active
   // the whole time a modal dialog is open.
-  nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+  nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
 
   bool disableDialog = false;
   nsAutoString label, title;
   nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
                                      "ScriptDialogLabel", label);
   nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
                                      "ScriptDialogPreventTitle", title);
   promptSvc->Confirm(this, title.get(), label.get(), &disableDialog);
@@ -4311,17 +4298,17 @@ bool nsGlobalWindowOuter::AlertOrConfirm
     // ignored.  In the case of confirm(), returning false is the same thing as
     // would happen if the user cancels.
     return false;
   }
 
   // Reset popup state while opening a modal dialog, and firing events
   // about the dialog, to prevent the current state from being active
   // the whole time a modal dialog is open.
-  nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+  nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
 
   // Before bringing up the window, unsuppress painting and flush
   // pending reflows.
   EnsureReflowFlushAndPaint();
 
   nsAutoString title;
   MakeScriptDialogTitle(title, &aSubjectPrincipal);
 
@@ -4399,17 +4386,17 @@ void nsGlobalWindowOuter::PromptOuter(co
   if (!AreDialogsEnabled()) {
     // Return null, as if the user just canceled the prompt.
     return;
   }
 
   // Reset popup state while opening a modal dialog, and firing events
   // about the dialog, to prevent the current state from being active
   // the whole time a modal dialog is open.
-  nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+  nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
 
   // Before bringing up the window, unsuppress painting and flush
   // pending reflows.
   EnsureReflowFlushAndPaint();
 
   nsAutoString title;
   MakeScriptDialogTitle(title, &aSubjectPrincipal);
 
@@ -4495,17 +4482,18 @@ void nsGlobalWindowOuter::FocusOuter(Err
   nsPIDOMWindowOuter* callerOuter = caller ? caller->GetOuterWindow() : nullptr;
   nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpener();
 
   // Enforce dom.disable_window_flip (for non-chrome), but still allow the
   // window which opened us to raise us at times when popups are allowed
   // (bugs 355482 and 369306).
   bool canFocus = CanSetProperty("dom.disable_window_flip") ||
                   (opener == callerOuter &&
-                   RevisePopupAbuseLevel(GetPopupControlState()) < openBlocked);
+                   RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
+                       PopupBlocker::openBlocked);
 
   nsCOMPtr<mozIDOMWindowProxy> activeDOMWindow;
   fm->GetActiveWindow(getter_AddRefs(activeDOMWindow));
 
   nsCOMPtr<nsIDocShellTreeItem> rootItem;
   mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
   nsCOMPtr<nsPIDOMWindowOuter> rootWin =
       rootItem ? rootItem->GetWindow() : nullptr;
@@ -5124,17 +5112,17 @@ bool nsGlobalWindowOuter::CanSetProperty
   }
 
   // If the pref is set to true, we can not set the property
   // and vice versa.
   return !Preferences::GetBool(aPrefName, true);
 }
 
 bool nsGlobalWindowOuter::PopupWhitelisted() {
-  if (mDoc && nsContentUtils::CanShowPopupByPermission(mDoc->NodePrincipal())) {
+  if (mDoc && PopupBlocker::CanShowPopupByPermission(mDoc->NodePrincipal())) {
     return true;
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
   if (parent == this) {
     return false;
   }
 
@@ -5142,55 +5130,58 @@ bool nsGlobalWindowOuter::PopupWhitelist
 }
 
 /*
  * Examine the current document state to see if we're in a way that is
  * typically abused by web designers. The window.open code uses this
  * routine to determine whether to allow the new window.
  * Returns a value from the PopupControlState enum.
  */
-PopupControlState nsGlobalWindowOuter::RevisePopupAbuseLevel(
-    PopupControlState aControl) {
+PopupBlocker::PopupControlState nsGlobalWindowOuter::RevisePopupAbuseLevel(
+    PopupBlocker::PopupControlState aControl) {
   NS_ASSERTION(mDocShell, "Must have docshell");
 
   if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) {
-    return openAllowed;
-  }
-
-  PopupControlState abuse = aControl;
+    return PopupBlocker::openAllowed;
+  }
+
+  PopupBlocker::PopupControlState abuse = aControl;
   switch (abuse) {
-    case openControlled:
-    case openBlocked:
-    case openOverridden:
-      if (PopupWhitelisted()) abuse = PopupControlState(abuse - 1);
+    case PopupBlocker::openControlled:
+    case PopupBlocker::openBlocked:
+    case PopupBlocker::openOverridden:
+      if (PopupWhitelisted())
+        abuse = PopupBlocker::PopupControlState(abuse - 1);
       break;
-    case openAbused:
+    case PopupBlocker::openAbused:
       if (PopupWhitelisted())
-        // Skip openBlocked
-        abuse = openControlled;
+        // Skip PopupBlocker::openBlocked
+        abuse = PopupBlocker::openControlled;
       break;
-    case openAllowed:
+    case PopupBlocker::openAllowed:
       break;
     default:
       NS_WARNING("Strange PopupControlState!");
   }
 
   // limit the number of simultaneously open popups
-  if (abuse == openAbused || abuse == openBlocked || abuse == openControlled) {
+  if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked ||
+      abuse == PopupBlocker::openControlled) {
     int32_t popupMax = Preferences::GetInt("dom.popup_maximum", -1);
     if (popupMax >= 0 && gOpenPopupSpamCount >= popupMax)
-      abuse = openOverridden;
+      abuse = PopupBlocker::openOverridden;
   }
 
   // If this popup is allowed, let's block any other for this event, forcing
-  // openBlocked state.
-  if ((abuse == openAllowed || abuse == openControlled) &&
+  // PopupBlocker::openBlocked state.
+  if ((abuse == PopupBlocker::openAllowed ||
+       abuse == PopupBlocker::openControlled) &&
       StaticPrefs::dom_block_multiple_popups() && !PopupWhitelisted() &&
-      !nsContentUtils::TryUsePopupOpeningToken()) {
-    abuse = openBlocked;
+      !PopupBlocker::TryUsePopupOpeningToken()) {
+    abuse = PopupBlocker::openBlocked;
   }
 
   return abuse;
 }
 
 /* If a window open is blocked, fire the appropriate DOM events. */
 void nsGlobalWindowOuter::FireAbuseEvents(
     const nsAString& aPopupURL, const nsAString& aPopupWindowName,
@@ -6619,20 +6610,21 @@ nsresult nsGlobalWindowOuter::OpenIntern
     // If we're not navigating, we assume that whoever *does* navigate the
     // window will do a security check of their own.
     if (!url.IsVoid() && !aDialog && aNavigate)
       rv = SecurityCheckURL(url.get(), getter_AddRefs(uri));
   }
 
   if (NS_FAILED(rv)) return rv;
 
-  PopupControlState abuseLevel = GetPopupControlState();
+  PopupBlocker::PopupControlState abuseLevel =
+      PopupBlocker::GetPopupControlState();
   if (checkForPopup) {
     abuseLevel = RevisePopupAbuseLevel(abuseLevel);
-    if (abuseLevel >= openBlocked) {
+    if (abuseLevel >= PopupBlocker::openBlocked) {
       if (!aCalledNoScript) {
         // If script in some other window is doing a window.open on us and
         // it's being blocked, then it's OK to close us afterwards, probably.
         // But if we're doing a window.open on ourselves and block the popup,
         // prevent this window from closing until after this script terminates
         // so that whatever popup blocker UI the app has will be visible.
         nsCOMPtr<nsPIDOMWindowInner> entryWindow =
             do_QueryInterface(GetEntryGlobal());
@@ -6658,28 +6650,30 @@ nsresult nsGlobalWindowOuter::OpenIntern
   NS_ConvertUTF16toUTF8 name(aName);
 
   const char* options_ptr = options.IsEmpty() ? nullptr : options.get();
   const char* name_ptr = aName.IsEmpty() ? nullptr : name.get();
 
   nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
   NS_ENSURE_STATE(pwwatch);
 
-  MOZ_ASSERT_IF(checkForPopup, abuseLevel < openBlocked);
+  MOZ_ASSERT_IF(checkForPopup, abuseLevel < PopupBlocker::openBlocked);
   // At this point we should know for a fact that if checkForPopup then
-  // abuseLevel < openBlocked, so we could just check for abuseLevel ==
-  // openControlled.  But let's be defensive just in case and treat anything
-  // that fails the above assert as a spam popup too, if it ever happens.
-  bool isPopupSpamWindow = checkForPopup && (abuseLevel >= openControlled);
+  // abuseLevel < PopupBlocker::openBlocked, so we could just check for
+  // abuseLevel == PopupBlocker::openControlled.  But let's be defensive just in
+  // case and treat anything that fails the above assert as a spam popup too, if
+  // it ever happens.
+  bool isPopupSpamWindow =
+      checkForPopup && (abuseLevel >= PopupBlocker::openControlled);
 
   {
     // Reset popup state while opening a window to prevent the
     // current state from being active the whole time a modal
     // dialog is open.
-    nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+    nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
 
     if (!aCalledNoScript) {
       // We asserted at the top of this function that aNavigate is true for
       // !aCalledNoScript.
       rv = pwwatch->OpenWindow2(
           this, url.IsVoid() ? nullptr : url.get(), name_ptr, options_ptr,
           /* aCalledFromScript = */ true, aDialog, aNavigate, argv,
           isPopupSpamWindow, forceNoOpener, aLoadState,
@@ -7384,23 +7378,12 @@ nsPIDOMWindowOuter::nsPIDOMWindowOuter(u
       mInnerWindow(nullptr),
       mWindowID(aWindowID),
       mMarkedCCGeneration(0),
       mServiceWorkersTestingEnabled(false),
       mLargeAllocStatus(LargeAllocStatus::NONE) {}
 
 nsPIDOMWindowOuter::~nsPIDOMWindowOuter() {}
 
-nsAutoPopupStatePusherInternal::nsAutoPopupStatePusherInternal(
-    PopupControlState aState, bool aForce)
-    : mOldState(nsContentUtils::PushPopupControlState(aState, aForce)) {
-  nsContentUtils::PopupStatePusherCreated();
-}
-
-nsAutoPopupStatePusherInternal::~nsAutoPopupStatePusherInternal() {
-  nsContentUtils::PopPopupControlState(mOldState);
-  nsContentUtils::PopupStatePusherDestroyed();
-}
-
 mozilla::dom::BrowsingContext* nsPIDOMWindowOuter::GetBrowsingContext() const {
   return mDocShell ? nsDocShell::Cast(mDocShell)->GetBrowsingContext()
                    : nullptr;
 }
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -33,16 +33,17 @@
 #include "nsITimer.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsIPrincipal.h"
 #include "nsSize.h"
 #include "mozilla/FlushType.h"
 #include "prclist.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCacheInlines.h"
@@ -294,21 +295,16 @@ class nsGlobalWindowOuter final : public
   virtual bool IsTopLevelWindowActive() override;
   virtual void SetIsBackground(bool aIsBackground) override;
   virtual void SetChromeEventHandler(
       mozilla::dom::EventTarget* aChromeEventHandler) override;
 
   // Outer windows only.
   virtual void SetInitialPrincipalToSubject() override;
 
-  virtual PopupControlState PushPopupControlState(PopupControlState state,
-                                                  bool aForce) const override;
-  virtual void PopPopupControlState(PopupControlState state) const override;
-  virtual PopupControlState GetPopupControlState() const override;
-
   virtual already_AddRefed<nsISupports> SaveWindowState() override;
   virtual nsresult RestoreWindowState(nsISupports* aState) override;
 
   virtual bool IsSuspended() const override;
   virtual bool IsFrozen() const override;
 
   virtual nsresult FireDelayedDOMEvents() override;
 
@@ -845,17 +841,18 @@ class nsGlobalWindowOuter final : public
  public:
   // Helper Functions
   already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
   already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
   already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
   nsresult SecurityCheckURL(const char* aURL, nsIURI** aURI);
 
   bool PopupWhitelisted();
-  PopupControlState RevisePopupAbuseLevel(PopupControlState);
+  mozilla::dom::PopupBlocker::PopupControlState RevisePopupAbuseLevel(
+      mozilla::dom::PopupBlocker::PopupControlState aState);
   void FireAbuseEvents(const nsAString& aPopupURL,
                        const nsAString& aPopupWindowName,
                        const nsAString& aPopupWindowFeatures);
 
  private:
   void ReportLargeAllocStatus();
 
  public:
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -69,29 +69,16 @@ class ServiceWorkerDescriptor;
 class Timeout;
 class TimeoutManager;
 class WindowGlobalChild;
 class CustomElementRegistry;
 enum class CallerType : uint32_t;
 }  // namespace dom
 }  // namespace mozilla
 
-// Popup control state enum. The values in this enum must go from most
-// permissive to least permissive so that it's safe to push state in
-// all situations. Pushing popup state onto the stack never makes the
-// current popup state less permissive (see
-// nsGlobalWindow::PushPopupControlState()).
-enum PopupControlState {
-  openAllowed = 0,  // open that window without worries
-  openControlled,   // it's a popup, but allow it
-  openBlocked,      // it's a popup, but not from an allowed event
-  openAbused,       // it's a popup. disallow it, but allow domain override.
-  openOverridden    // disallow window open
-};
-
 enum UIStateChangeType {
   UIStateChangeType_NoChange,
   UIStateChangeType_Set,
   UIStateChangeType_Clear,
   UIStateChangeType_Invalid  // used for serialization only
 };
 
 enum class FullscreenReason {
@@ -378,18 +365,16 @@ class nsPIDOMWindowInner : public mozIDO
     }
     return mDoc;
   }
 
   mozilla::dom::WindowGlobalChild* GetWindowGlobalChild() {
     return mWindowGlobalChild;
   }
 
-  virtual PopupControlState GetPopupControlState() const = 0;
-
   // Determine if the window is suspended or frozen.  Outer windows
   // will forward this call to the inner window for convenience.  If
   // there is no inner window then the outer window is considered
   // suspended and frozen by default.
   virtual bool IsSuspended() const = 0;
   virtual bool IsFrozen() const = 0;
 
   // Fire any DOM notification events related to things that happened while
@@ -870,21 +855,16 @@ class nsPIDOMWindowOuter : public mozIDO
     }
     return mDoc;
   }
 
   // Set the window up with an about:blank document with the current subject
   // principal.
   virtual void SetInitialPrincipalToSubject() = 0;
 
-  virtual PopupControlState PushPopupControlState(PopupControlState aState,
-                                                  bool aForce) const = 0;
-  virtual void PopPopupControlState(PopupControlState state) const = 0;
-  virtual PopupControlState GetPopupControlState() const = 0;
-
   // Returns an object containing the window's state.  This also suspends
   // all running timeouts in the window.
   virtual already_AddRefed<nsISupports> SaveWindowState() = 0;
 
   // Restore the window state from aState.
   virtual nsresult RestoreWindowState(nsISupports* aState) = 0;
 
   // Determine if the window is suspended or frozen.  Outer windows
@@ -1205,57 +1185,9 @@ class nsPIDOMWindowOuter : public mozIDO
   using PermissionInfo = std::pair<bool, mozilla::TimeStamp>;
   nsDataHashtable<nsStringHashKey, PermissionInfo> mAutoplayTemporaryPermission;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowOuter, NS_PIDOMWINDOWOUTER_IID)
 
 #include "nsPIDOMWindowInlines.h"
 
-#ifdef MOZILLA_INTERNAL_API
-#define NS_AUTO_POPUP_STATE_PUSHER nsAutoPopupStatePusherInternal
-#else
-#define NS_AUTO_POPUP_STATE_PUSHER nsAutoPopupStatePusherExternal
-#endif
-
-// Helper class that helps with pushing and popping popup control
-// state. Note that this class looks different from within code that's
-// part of the layout library than it does in code outside the layout
-// library.  We give the two object layouts different names so the symbols
-// don't conflict, but code should always use the name
-// |nsAutoPopupStatePusher|.
-class NS_AUTO_POPUP_STATE_PUSHER {
- public:
-#ifdef MOZILLA_INTERNAL_API
-  explicit NS_AUTO_POPUP_STATE_PUSHER(PopupControlState aState,
-                                      bool aForce = false);
-  ~NS_AUTO_POPUP_STATE_PUSHER();
-#else
-  NS_AUTO_POPUP_STATE_PUSHER(nsPIDOMWindowOuter* aWindow,
-                             PopupControlState aState)
-      : mWindow(aWindow), mOldState(openAbused) {
-    if (aWindow) {
-      mOldState = aWindow->PushPopupControlState(aState, false);
-    }
-  }
-
-  ~NS_AUTO_POPUP_STATE_PUSHER() {
-    if (mWindow) {
-      mWindow->PopPopupControlState(mOldState);
-    }
-  }
-#endif
-
- protected:
-#ifndef MOZILLA_INTERNAL_API
-  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
-#endif
-  PopupControlState mOldState;
-
- private:
-  // Hide so that this class can only be stack-allocated
-  static void* operator new(size_t /*size*/) CPP_THROW_NEW { return nullptr; }
-  static void operator delete(void* /*memory*/) {}
-};
-
-#define nsAutoPopupStatePusher NS_AUTO_POPUP_STATE_PUSHER
-
 #endif  // nsPIDOMWindow_h__
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -35,18 +35,16 @@
 #include "nsJSEnvironment.h"
 #include "nsLayoutUtils.h"
 #include "nsPIWindowRoot.h"
 #include "nsRFPService.h"
 
 namespace mozilla {
 namespace dom {
 
-static char* sPopupAllowedEvents;
-
 static bool sReturnHighResTimeStamp = false;
 static bool sReturnHighResTimeStampIsSet = false;
 
 Event::Event(EventTarget* aOwner, nsPresContext* aPresContext,
              WidgetEvent* aEvent) {
   ConstructorInit(aOwner, aPresContext, aEvent);
 }
 
@@ -503,282 +501,16 @@ void Event::DuplicatePrivateData() {
 }
 
 void Event::SetTarget(EventTarget* aTarget) { mEvent->mTarget = aTarget; }
 
 bool Event::IsDispatchStopped() { return mEvent->PropagationStopped(); }
 
 WidgetEvent* Event::WidgetEventPtr() { return mEvent; }
 
-// return true if eventName is contained within events, delimited by
-// spaces
-static bool PopupAllowedForEvent(const char* eventName) {
-  if (!sPopupAllowedEvents) {
-    Event::PopupAllowedEventsChanged();
-
-    if (!sPopupAllowedEvents) {
-      return false;
-    }
-  }
-
-  nsDependentCString events(sPopupAllowedEvents);
-
-  nsCString::const_iterator start, end;
-  nsCString::const_iterator startiter(events.BeginReading(start));
-  events.EndReading(end);
-
-  while (startiter != end) {
-    nsCString::const_iterator enditer(end);
-
-    if (!FindInReadable(nsDependentCString(eventName), startiter, enditer))
-      return false;
-
-    // the match is surrounded by spaces, or at a string boundary
-    if ((startiter == start || *--startiter == ' ') &&
-        (enditer == end || *enditer == ' ')) {
-      return true;
-    }
-
-    // Move on and see if there are other matches. (The delimitation
-    // requirement makes it pointless to begin the next search before
-    // the end of the invalid match just found.)
-    startiter = enditer;
-  }
-
-  return false;
-}
-
-// static
-PopupControlState Event::GetEventPopupControlState(WidgetEvent* aEvent,
-                                                   Event* aDOMEvent) {
-  // generally if an event handler is running, new windows are disallowed.
-  // check for exceptions:
-  PopupControlState abuse = openAbused;
-
-  if (aDOMEvent && aDOMEvent->GetWantsPopupControlCheck()) {
-    nsAutoString type;
-    aDOMEvent->GetType(type);
-    if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) {
-      return openAllowed;
-    }
-  }
-
-  switch (aEvent->mClass) {
-    case eBasicEventClass:
-      // For these following events only allow popups if they're
-      // triggered while handling user input. See
-      // nsPresShell::HandleEventInternal() for details.
-      if (EventStateManager::IsHandlingUserInput()) {
-        abuse = openBlocked;
-        switch (aEvent->mMessage) {
-          case eFormSelect:
-            if (PopupAllowedForEvent("select")) {
-              abuse = openControlled;
-            }
-            break;
-          case eFormChange:
-            if (PopupAllowedForEvent("change")) {
-              abuse = openControlled;
-            }
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    case eEditorInputEventClass:
-      // For this following event only allow popups if it's triggered
-      // while handling user input. See
-      // nsPresShell::HandleEventInternal() for details.
-      if (EventStateManager::IsHandlingUserInput()) {
-        abuse = openBlocked;
-        switch (aEvent->mMessage) {
-          case eEditorInput:
-            if (PopupAllowedForEvent("input")) {
-              abuse = openControlled;
-            }
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    case eInputEventClass:
-      // For this following event only allow popups if it's triggered
-      // while handling user input. See
-      // nsPresShell::HandleEventInternal() for details.
-      if (EventStateManager::IsHandlingUserInput()) {
-        abuse = openBlocked;
-        switch (aEvent->mMessage) {
-          case eFormChange:
-            if (PopupAllowedForEvent("change")) {
-              abuse = openControlled;
-            }
-            break;
-          case eXULCommand:
-            abuse = openControlled;
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    case eKeyboardEventClass:
-      if (aEvent->IsTrusted()) {
-        abuse = openBlocked;
-        uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode;
-        switch (aEvent->mMessage) {
-          case eKeyPress:
-            // return key on focused button. see note at eMouseClick.
-            if (key == NS_VK_RETURN) {
-              abuse = openAllowed;
-            } else if (PopupAllowedForEvent("keypress")) {
-              abuse = openControlled;
-            }
-            break;
-          case eKeyUp:
-            // space key on focused button. see note at eMouseClick.
-            if (key == NS_VK_SPACE) {
-              abuse = openAllowed;
-            } else if (PopupAllowedForEvent("keyup")) {
-              abuse = openControlled;
-            }
-            break;
-          case eKeyDown:
-            if (PopupAllowedForEvent("keydown")) {
-              abuse = openControlled;
-            }
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    case eTouchEventClass:
-      if (aEvent->IsTrusted()) {
-        abuse = openBlocked;
-        switch (aEvent->mMessage) {
-          case eTouchStart:
-            if (PopupAllowedForEvent("touchstart")) {
-              abuse = openControlled;
-            }
-            break;
-          case eTouchEnd:
-            if (PopupAllowedForEvent("touchend")) {
-              abuse = openControlled;
-            }
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    case eMouseEventClass:
-      if (aEvent->IsTrusted() &&
-          aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
-        abuse = openBlocked;
-        switch (aEvent->mMessage) {
-          case eMouseUp:
-            if (PopupAllowedForEvent("mouseup")) {
-              abuse = openControlled;
-            }
-            break;
-          case eMouseDown:
-            if (PopupAllowedForEvent("mousedown")) {
-              abuse = openControlled;
-            }
-            break;
-          case eMouseClick:
-            /* Click events get special treatment because of their
-               historical status as a more legitimate event handler. If
-               click popups are enabled in the prefs, clear the popup
-               status completely. */
-            if (PopupAllowedForEvent("click")) {
-              abuse = openAllowed;
-            }
-            break;
-          case eMouseDoubleClick:
-            if (PopupAllowedForEvent("dblclick")) {
-              abuse = openControlled;
-            }
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    case ePointerEventClass:
-      if (aEvent->IsTrusted() &&
-          aEvent->AsPointerEvent()->button == WidgetMouseEvent::eLeftButton) {
-        switch (aEvent->mMessage) {
-          case ePointerUp:
-            if (PopupAllowedForEvent("pointerup")) {
-              abuse = openControlled;
-            }
-            break;
-          case ePointerDown:
-            if (PopupAllowedForEvent("pointerdown")) {
-              abuse = openControlled;
-            }
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    case eFormEventClass:
-      // For these following events only allow popups if they're
-      // triggered while handling user input. See
-      // nsPresShell::HandleEventInternal() for details.
-      if (EventStateManager::IsHandlingUserInput()) {
-        abuse = openBlocked;
-        switch (aEvent->mMessage) {
-          case eFormSubmit:
-            if (PopupAllowedForEvent("submit")) {
-              abuse = openControlled;
-            }
-            break;
-          case eFormReset:
-            if (PopupAllowedForEvent("reset")) {
-              abuse = openControlled;
-            }
-            break;
-          default:
-            break;
-        }
-      }
-      break;
-    default:
-      break;
-  }
-
-  return abuse;
-}
-
-// static
-void Event::PopupAllowedEventsChanged() {
-  if (sPopupAllowedEvents) {
-    free(sPopupAllowedEvents);
-  }
-
-  nsAutoCString str;
-  Preferences::GetCString("dom.popup_allowed_events", str);
-
-  // We'll want to do this even if str is empty to avoid looking up
-  // this pref all the time if it's not set.
-  sPopupAllowedEvents = ToNewCString(str);
-}
-
-// static
-void Event::Shutdown() {
-  if (sPopupAllowedEvents) {
-    free(sPopupAllowedEvents);
-  }
-}
-
 // static
 CSSIntPoint Event::GetScreenCoords(nsPresContext* aPresContext,
                                    WidgetEvent* aEvent,
                                    LayoutDeviceIntPoint aPoint) {
   if (EventStateManager::sIsPointerLocked) {
     return EventStateManager::sLastScreenPoint;
   }
 
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -11,16 +11,17 @@
 #include "mozilla/BasicEvents.h"
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "nsPIDOMWindow.h"
 #include "nsPoint.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "nsIScriptGlobalObject.h"
 #include "Units.h"
 #include "js/TypeDecls.h"
 #include "nsIGlobalObject.h"
 
 class nsIContent;
 class nsPresContext;
 class PickleIterator;
@@ -136,23 +137,16 @@ class Event : public nsISupports, public
   void StopCrossProcessForwarding();
   void SetTrusted(bool aTrusted);
 
   void InitPresContextData(nsPresContext* aPresContext);
 
   // Returns true if the event should be trusted.
   bool Init(EventTarget* aGlobal);
 
-  static PopupControlState GetEventPopupControlState(
-      WidgetEvent* aEvent, Event* aDOMEvent = nullptr);
-
-  static void PopupAllowedEventsChanged();
-
-  static void Shutdown();
-
   static const char* GetEventName(EventMessage aEventType);
   static CSSIntPoint GetClientCoords(nsPresContext* aPresContext,
                                      WidgetEvent* aEvent,
                                      LayoutDeviceIntPoint aPoint,
                                      CSSIntPoint aDefaultPoint);
   static CSSIntPoint GetPageCoords(nsPresContext* aPresContext,
                                    WidgetEvent* aEvent,
                                    LayoutDeviceIntPoint aPoint,
@@ -279,16 +273,17 @@ class Event : public nsISupports, public
   static void GetWidgetEventType(WidgetEvent* aEvent, nsAString& aType);
 
  protected:
   // Internal helper functions
   void SetEventType(const nsAString& aEventTypeArg);
   already_AddRefed<nsIContent> GetTargetFromFrame();
 
   friend class EventMessageAutoOverride;
+  friend class PopupBlocker;
   friend class WantsPopupControlCheck;
   void SetWantsPopupControlCheck(bool aCheck) {
     mWantsPopupControlCheck = aCheck;
   }
 
   bool GetWantsPopupControlCheck() {
     return IsTrusted() && mWantsPopupControlCheck;
   }
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/JSEventHandler.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/EventTargetBinding.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/TimelineConsumers.h"
 #include "mozilla/EventTimelineMarker.h"
 #include "mozilla/TimeStamp.h"
 
 #include "EventListenerService.h"
 #include "nsCOMArray.h"
@@ -151,18 +152,16 @@ void EventListenerManager::RemoveAllList
   if (mClearingListeners) {
     return;
   }
   mClearingListeners = true;
   mListeners.Clear();
   mClearingListeners = false;
 }
 
-void EventListenerManager::Shutdown() { Event::Shutdown(); }
-
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EventListenerManager, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EventListenerManager, Release)
 
 inline void ImplCycleCollectionTraverse(
     nsCycleCollectionTraversalCallback& aCallback,
     EventListenerManager::Listener& aField, const char* aName,
     unsigned aFlags) {
   if (MOZ_UNLIKELY(aCallback.WantDebugInfo())) {
@@ -1142,17 +1141,17 @@ void EventListenerManager::HandleEventIn
     // Assume that if only aEventStatus claims that the event has already been
     // consumed, the consumer is default event handler.
     aEvent->PreventDefault();
   }
 
   Maybe<nsAutoPopupStatePusher> popupStatePusher;
   if (mIsMainThreadELM) {
     popupStatePusher.emplace(
-        Event::GetEventPopupControlState(aEvent, *aDOMEvent));
+        PopupBlocker::GetEventPopupControlState(aEvent, *aDOMEvent));
   }
 
   bool hasListener = false;
   bool hasListenerForCurrentGroup = false;
   bool usingLegacyMessage = false;
   bool hasRemovedListener = false;
   EventMessage eventMessage = aEvent->mMessage;
 
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -396,18 +396,16 @@ class EventListenerManager final : publi
   /**
    * Sets aList to the list of nsIEventListenerInfo objects representing the
    * listeners managed by this listener manager.
    */
   nsresult GetListenerInfo(nsCOMArray<nsIEventListenerInfo>* aList);
 
   uint32_t GetIdentifierForEvent(nsAtom* aEvent);
 
-  static void Shutdown();
-
   /**
    * Returns true if there may be a paint event listener registered,
    * false if there definitely isn't.
    */
   bool MayHavePaintEventListener() { return mMayHavePaintEventListener; }
 
   /**
    * Returns true if there may be a touch event listener registered,
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -291,17 +291,16 @@ EventStateManager::~EventStateManager() 
     if (gUserInteractionTimerCallback) {
       gUserInteractionTimerCallback->Notify(nullptr);
       NS_RELEASE(gUserInteractionTimerCallback);
     }
     if (gUserInteractionTimer) {
       gUserInteractionTimer->Cancel();
       NS_RELEASE(gUserInteractionTimer);
     }
-    Prefs::Shutdown();
     WheelPrefs::Shutdown();
     DeltaAccumulator::Shutdown();
   }
 
   if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) {
     sDragOverContent = nullptr;
   }
 
@@ -6068,52 +6067,34 @@ bool EventStateManager::WheelPrefs::IsOv
 /* mozilla::EventStateManager::Prefs                              */
 /******************************************************************/
 
 bool EventStateManager::Prefs::sKeyCausesActivation = true;
 bool EventStateManager::Prefs::sClickHoldContextMenu = false;
 
 // static
 void EventStateManager::Prefs::Init() {
-  DebugOnly<nsresult> rv =
-      Preferences::RegisterCallback(OnChange, "dom.popup_allowed_events");
-  MOZ_ASSERT(NS_SUCCEEDED(rv),
-             "Failed to observe \"dom.popup_allowed_events\"");
-
   static bool sPrefsAlreadyCached = false;
   if (sPrefsAlreadyCached) {
     return;
   }
 
-  rv = Preferences::AddBoolVarCache(&sKeyCausesActivation,
-                                    "accessibility.accesskeycausesactivation",
-                                    sKeyCausesActivation);
+  DebugOnly<nsresult> rv = Preferences::AddBoolVarCache(
+      &sKeyCausesActivation, "accessibility.accesskeycausesactivation",
+      sKeyCausesActivation);
   MOZ_ASSERT(NS_SUCCEEDED(rv),
              "Failed to observe \"accessibility.accesskeycausesactivation\"");
   rv = Preferences::AddBoolVarCache(&sClickHoldContextMenu,
                                     "ui.click_hold_context_menus",
                                     sClickHoldContextMenu);
   MOZ_ASSERT(NS_SUCCEEDED(rv),
              "Failed to observe \"ui.click_hold_context_menus\"");
   sPrefsAlreadyCached = true;
 }
 
-// static
-void EventStateManager::Prefs::OnChange(const char* aPrefName, void*) {
-  nsDependentCString prefName(aPrefName);
-  if (prefName.EqualsLiteral("dom.popup_allowed_events")) {
-    Event::PopupAllowedEventsChanged();
-  }
-}
-
-// static
-void EventStateManager::Prefs::Shutdown() {
-  Preferences::UnregisterCallback(OnChange, "dom.popup_allowed_events");
-}
-
 /******************************************************************/
 /* mozilla::AutoHandlingUserInputStatePusher                      */
 /******************************************************************/
 
 AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher(
     bool aIsHandlingUserInput, WidgetEvent* aEvent, nsIDocument* aDocument)
     : mMessage(aEvent ? aEvent->mMessage : eVoidEvent),
       mIsHandlingUserInput(aIsHandlingUserInput) {
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -372,18 +372,16 @@ class EventStateManager : public nsSuppo
    * Prefs class capsules preference management.
    */
   class Prefs {
    public:
     static bool KeyCausesActivation() { return sKeyCausesActivation; }
     static bool ClickHoldContextMenu() { return sClickHoldContextMenu; }
 
     static void Init();
-    static void OnChange(const char* aPrefName, void*);
-    static void Shutdown();
 
    private:
     static bool sKeyCausesActivation;
     static bool sClickHoldContextMenu;
 
     static int32_t GetAccessModifierMask(int32_t aItemType);
   };
 
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -104,17 +104,17 @@ HTMLFormElement::HTMLFormElement(
       mValueMissingRadioGroups(2),
       mPendingSubmission(nullptr),
       mSubmittingRequest(nullptr),
       mDefaultSubmitElement(nullptr),
       mFirstSubmitInElements(nullptr),
       mFirstSubmitNotInElements(nullptr),
       mImageNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
       mPastNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
-      mSubmitPopupState(openAbused),
+      mSubmitPopupState(PopupBlocker::openAbused),
       mInvalidElementsCount(0),
       mGeneratingSubmit(false),
       mGeneratingReset(false),
       mIsSubmitting(false),
       mDeferSubmission(false),
       mNotifiedObservers(false),
       mNotifiedObserversResult(false),
       mSubmitInitiatedFromUserInput(false),
@@ -563,21 +563,20 @@ nsresult HTMLFormElement::DoSubmit(Widge
   if (NS_FAILED(rv)) {
     mIsSubmitting = false;
     return rv;
   }
 
   // XXXbz if the script global is that for an sXBL/XBL2 doc, it won't
   // be a window...
   nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
-
   if (window) {
-    mSubmitPopupState = window->GetPopupControlState();
+    mSubmitPopupState = PopupBlocker::GetPopupControlState();
   } else {
-    mSubmitPopupState = openAbused;
+    mSubmitPopupState = PopupBlocker::openAbused;
   }
 
   mSubmitInitiatedFromUserInput = EventStateManager::IsHandlingUserInput();
 
   if (mDeferSubmission) {
     // we are in an event handler, JS submitted so we have to
     // defer this submission. let's remember it and return
     // without submitting
--- a/dom/html/HTMLFormElement.h
+++ b/dom/html/HTMLFormElement.h
@@ -568,17 +568,17 @@ class HTMLFormElement final : public nsG
   nsInterfaceHashtable<nsStringHashKey, nsISupports> mImageNameLookupTable;
 
   // A map from names to elements that were gotten by those names from this
   // form in that past.  See "past names map" in the HTML5 specification.
 
   nsInterfaceHashtable<nsStringHashKey, nsISupports> mPastNameLookupTable;
 
   /** Keep track of what the popup state was when the submit was initiated */
-  PopupControlState mSubmitPopupState;
+  PopupBlocker::PopupControlState mSubmitPopupState;
 
   /**
    * Number of invalid and candidate for constraint validation elements in the
    * form the last time UpdateValidity has been called.
    * @note Should only be used by UpdateValidity() and GetValidity()!
    */
   int32_t mInvalidElementsCount;
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -649,22 +649,21 @@ NS_IMPL_ISUPPORTS(nsColorPickerShownCall
 bool HTMLInputElement::IsPopupBlocked() const {
   nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
   MOZ_ASSERT(win, "window should not be null");
   if (!win) {
     return true;
   }
 
   // Check if page can open a popup without abuse regardless of allowed events
-  if (win->GetPopupControlState() <= openBlocked) {
-    return !nsContentUtils::TryUsePopupOpeningToken();
-  }
-
-  return !nsContentUtils::CanShowPopupByPermission(
-             OwnerDoc()->NodePrincipal());
+  if (PopupBlocker::GetPopupControlState() <= PopupBlocker::openBlocked) {
+    return !PopupBlocker::TryUsePopupOpeningToken();
+  }
+
+  return !PopupBlocker::CanShowPopupByPermission(OwnerDoc()->NodePrincipal());
 }
 
 nsresult HTMLInputElement::InitColorPicker() {
   if (mPickerRunning) {
     NS_WARNING("Just one nsIColorPicker is allowed");
     return NS_ERROR_FAILURE;
   }
 
--- a/dom/html/HTMLLabelElement.cpp
+++ b/dom/html/HTMLLabelElement.cpp
@@ -196,18 +196,18 @@ bool HTMLLabelElement::PerformAccesskey(
       return false;
     }
 
     // Click on it if the users prefs indicate to do so.
     WidgetMouseEvent event(aIsTrustedEvent, eMouseClick, nullptr,
                            WidgetMouseEvent::eReal);
     event.inputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
 
-    nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ? openAllowed
-                                                            : openAbused);
+    nsAutoPopupStatePusher popupStatePusher(
+        aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused);
 
     EventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
                               &event);
   }
 
   return aKeyCausesActivation;
 }
 
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2284,18 +2284,18 @@ bool nsGenericHTMLElement::PerformAccess
 
     // Return true if the element became the current focus within its window.
     nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
     focused = (window && window->GetFocusedElement());
   }
 
   if (aKeyCausesActivation) {
     // Click on it if the users prefs indicate to do so.
-    nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ? openAllowed
-                                                            : openAbused);
+    nsAutoPopupStatePusher popupStatePusher(
+        aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused);
     DispatchSimulatedClick(this, aIsTrustedEvent, presContext);
   }
 
   return focused;
 }
 
 nsresult nsGenericHTMLElement::DispatchSimulatedClick(
     nsGenericHTMLElement* aElement, bool aIsTrusted,
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -42,16 +42,17 @@
 #include "nsILoadInfo.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsSandboxFlags.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "nsILoadInfo.h"
 #include "nsContentSecurityManager.h"
 
 #include "mozilla/ipc/URIUtils.h"
 
 using mozilla::dom::AutoEntryScript;
 
 static NS_DEFINE_CID(kJSURICID, NS_JSURI_CID);
@@ -59,19 +60,20 @@ static NS_DEFINE_CID(kJSURICID, NS_JSURI
 class nsJSThunk : public nsIInputStream {
  public:
   nsJSThunk();
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_FORWARD_SAFE_NSIINPUTSTREAM(mInnerStream)
 
   nsresult Init(nsIURI* uri);
-  nsresult EvaluateScript(nsIChannel* aChannel, PopupControlState aPopupState,
-                          uint32_t aExecutionPolicy,
-                          nsPIDOMWindowInner* aOriginalInnerWindow);
+  nsresult EvaluateScript(
+      nsIChannel* aChannel,
+      mozilla::dom::PopupBlocker::PopupControlState aPopupState,
+      uint32_t aExecutionPolicy, nsPIDOMWindowInner* aOriginalInnerWindow);
 
  protected:
   virtual ~nsJSThunk();
 
   nsCOMPtr<nsIInputStream> mInnerStream;
   nsCString mScript;
   nsCString mURL;
 };
@@ -121,20 +123,20 @@ static nsIScriptGlobalObject* GetGlobalO
   nsIScriptGlobalObject* global = docShell->GetScriptGlobalObject();
 
   NS_ASSERTION(global,
                "Unable to get an nsIScriptGlobalObject from the "
                "docShell!");
   return global;
 }
 
-nsresult nsJSThunk::EvaluateScript(nsIChannel* aChannel,
-                                   PopupControlState aPopupState,
-                                   uint32_t aExecutionPolicy,
-                                   nsPIDOMWindowInner* aOriginalInnerWindow) {
+nsresult nsJSThunk::EvaluateScript(
+    nsIChannel* aChannel,
+    mozilla::dom::PopupBlocker::PopupControlState aPopupState,
+    uint32_t aExecutionPolicy, nsPIDOMWindowInner* aOriginalInnerWindow) {
   if (aExecutionPolicy == nsIScriptChannel::NO_EXECUTION) {
     // Nothing to do here.
     return NS_ERROR_DOM_RETVAL_UNDEFINED;
   }
 
   NS_ENSURE_ARG_POINTER(aChannel);
 
   // Get principal of code for execution
@@ -340,28 +342,28 @@ class nsJSChannel : public nsIChannel,
   nsCOMPtr<nsIDocument> mDocumentOnloadBlockedOn;
 
   nsresult mStatus;  // Our status
 
   nsLoadFlags mLoadFlags;
   nsLoadFlags mActualLoadFlags;  // See AsyncOpen2
 
   RefPtr<nsJSThunk> mIOThunk;
-  PopupControlState mPopupState;
+  mozilla::dom::PopupBlocker::PopupControlState mPopupState;
   uint32_t mExecutionPolicy;
   bool mIsAsync;
   bool mIsActive;
   bool mOpenedStreamChannel;
 };
 
 nsJSChannel::nsJSChannel()
     : mStatus(NS_OK),
       mLoadFlags(LOAD_NORMAL),
       mActualLoadFlags(LOAD_NORMAL),
-      mPopupState(openOverridden),
+      mPopupState(mozilla::dom::PopupBlocker::openOverridden),
       mExecutionPolicy(NO_EXECUTION),
       mIsAsync(true),
       mIsActive(false),
       mOpenedStreamChannel(false) {}
 
 nsJSChannel::~nsJSChannel() {}
 
 nsresult nsJSChannel::StopAll() {
@@ -568,17 +570,17 @@ nsJSChannel::AsyncOpen(nsIStreamListener
     if (loadFlags & LOAD_DOCUMENT_URI) {
       mDocumentOnloadBlockedOn = mDocumentOnloadBlockedOn->GetParentDocument();
     }
   }
   if (mDocumentOnloadBlockedOn) {
     mDocumentOnloadBlockedOn->BlockOnload();
   }
 
-  mPopupState = win->GetPopupControlState();
+  mPopupState = mozilla::dom::PopupBlocker::GetPopupControlState();
 
   void (nsJSChannel::*method)();
   const char* name;
   if (mIsAsync) {
     // post an event to do the rest
     method = &nsJSChannel::EvaluateScript;
     name = "nsJSChannel::EvaluateScript";
   } else {
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -134,21 +134,17 @@ nsresult nsNPAPIPluginInstance::Initiali
 }
 
 nsresult nsNPAPIPluginInstance::Stop() {
   PLUGIN_LOG(PLUGIN_LOG_NORMAL,
              ("nsNPAPIPluginInstance::Stop this=%p\n", this));
 
   // Make sure the plugin didn't leave popups enabled.
   if (mPopupStates.Length() > 0) {
-    nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow();
-
-    if (window) {
-      window->PopPopupControlState(openAbused);
-    }
+    PopupBlocker::PopPopupControlState(PopupBlocker::openAbused);
   }
 
   if (RUNNING != mRunning) {
     return NS_OK;
   }
 
   // clean up all outstanding timers
   for (uint32_t i = mTimers.Length(); i > 0; i--)
@@ -769,22 +765,24 @@ nsresult nsNPAPIPluginInstance::GetFormV
 
   return NS_OK;
 }
 
 nsresult nsNPAPIPluginInstance::PushPopupsEnabledState(bool aEnabled) {
   nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow();
   if (!window) return NS_ERROR_FAILURE;
 
-  PopupControlState oldState =
-      window->PushPopupControlState(aEnabled ? openAllowed : openAbused, true);
+  PopupBlocker::PopupControlState oldState =
+      PopupBlocker::PushPopupControlState(
+          aEnabled ? PopupBlocker::openAllowed : PopupBlocker::openAbused,
+          true);
 
   if (!mPopupStates.AppendElement(oldState)) {
     // Appending to our state stack failed, pop what we just pushed.
-    window->PopPopupControlState(oldState);
+    PopupBlocker::PopPopupControlState(oldState);
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult nsNPAPIPluginInstance::PopPopupsEnabledState() {
   int32_t last = mPopupStates.Length() - 1;
@@ -792,19 +790,19 @@ nsresult nsNPAPIPluginInstance::PopPopup
   if (last < 0) {
     // Nothing to pop.
     return NS_OK;
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow();
   if (!window) return NS_ERROR_FAILURE;
 
-  PopupControlState& oldState = mPopupStates[last];
+  PopupBlocker::PopupControlState& oldState = mPopupStates[last];
 
-  window->PopPopupControlState(oldState);
+  PopupBlocker::PopPopupControlState(oldState);
 
   mPopupStates.RemoveElementAt(last);
 
   return NS_OK;
 }
 
 nsresult nsNPAPIPluginInstance::GetPluginAPIVersion(uint16_t* version) {
   NS_ENSURE_ARG_POINTER(version);
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -19,16 +19,17 @@
 #include "js/TypeDecls.h"
 #include "nsIAudioChannelAgent.h"
 
 #include "mozilla/EventForwards.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/PluginLibrary.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
+#include "mozilla/dom/PopupBlocker.h"
 
 class nsPluginStreamListenerPeer;   // browser-initiated stream class
 class nsNPAPIPluginStreamListener;  // plugin-initiated stream class
 class nsIPluginInstanceOwner;
 class nsIOutputStream;
 class nsPluginInstanceOwner;
 
 namespace mozilla {
@@ -264,17 +265,17 @@ class nsNPAPIPluginInstance final
 
  private:
   RefPtr<nsNPAPIPlugin> mPlugin;
 
   nsTArray<nsNPAPIPluginStreamListener*> mStreamListeners;
 
   nsTArray<nsPluginStreamListenerPeer*> mFileCachedStreamListeners;
 
-  nsTArray<PopupControlState> mPopupStates;
+  nsTArray<mozilla::dom::PopupBlocker::PopupControlState> mPopupStates;
 
   char* mMIMEType;
 
   // Weak pointer to the owner. The owner nulls this out (by calling
   // InvalidateOwner()) when it's no longer our owner.
   nsPluginInstanceOwner* mOwner;
 
   nsTArray<nsNPAPITimer*> mTimers;
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -420,17 +420,18 @@ NS_IMETHODIMP nsPluginInstanceOwner::Get
 
     rv = sis->SetData((char*)aHeadersData, aHeadersDataLen);
     NS_ENSURE_SUCCESS(rv, rv);
     headersDataStream = sis;
   }
 
   int32_t blockPopups =
       Preferences::GetInt("privacy.popups.disable_from_plugins");
-  nsAutoPopupStatePusher popupStatePusher((PopupControlState)blockPopups);
+  nsAutoPopupStatePusher popupStatePusher(
+      (PopupBlocker::PopupControlState)blockPopups);
 
   // if security checks (in particular CheckLoadURIWithPrincipal) needs
   // to be skipped we are creating a codebasePrincipal to make sure
   // that security check succeeds. Please note that we do not want to
   // fall back to using the systemPrincipal, because that would also
   // bypass ContentPolicy checks which should still be enforced.
   nsCOMPtr<nsIPrincipal> triggeringPrincipal;
   if (!aDoCheckLoadURIChecks) {
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -38,18 +38,18 @@
 #include "gfxPrefs.h"
 #include "gfxUserFontSet.h"
 #include "nsContentList.h"
 #include "nsPresContext.h"
 #include "nsIContent.h"
 #include "nsIContentIterator.h"
 #include "nsIPresShellInlines.h"
 #include "mozilla/dom/Element.h"
-#include "mozilla/dom/Event.h"  // for Event::GetEventPopupControlState()
 #include "mozilla/dom/PointerEventHandler.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "nsIDocument.h"
 #include "nsAnimationManager.h"
 #include "nsNameSpaceManager.h"  // for Pref-related rule management (bugs 22963,20760,31816)
 #include "nsFrame.h"
 #include "FrameLayerBuilder.h"
 #include "nsViewManager.h"
 #include "nsView.h"
 #include "nsCRTGlue.h"
@@ -7260,17 +7260,17 @@ nsresult PresShell::HandleEventInternal(
           EventStateManager::GetActiveEventStateManager() == manager);
 
       mPresContext->RecordInteractionTime(
           nsPresContext::InteractionType::eMouseMoveInteraction,
           aEvent->mTimeStamp);
     }
 
     nsAutoPopupStatePusher popupStatePusher(
-        Event::GetEventPopupControlState(aEvent));
+        PopupBlocker::GetEventPopupControlState(aEvent));
 
     // FIXME. If the event was reused, we need to clear the old target,
     // bug 329430
     aEvent->mTarget = nullptr;
 
     TimeStamp handlerStartTime = TimeStamp::Now();
 
     // 1. Give event to event manager for pre event state changes and
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -13,16 +13,17 @@
 #include "nsCOMPtr.h"
 #include "nsCRT.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsIContent.h"
 #include "nsIContentViewer.h"
 #include "nsIDocumentViewerPrint.h"
 #include "mozilla/dom/BeforeUnloadEvent.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsIFrame.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsSubDocumentFrame.h"
 #include "nsGenericHTMLElement.h"
 #include "nsStubMutationObserver.h"
@@ -1236,17 +1237,17 @@ nsresult nsDocumentViewer::PermitUnloadI
   // In evil cases we might be destroyed while handling the
   // onbeforeunload event, don't let that happen. (see also bug#331040)
   RefPtr<nsDocumentViewer> kungFuDeathGrip(this);
 
   bool dialogsAreEnabled = false;
   {
     // Never permit popups from the beforeunload handler, no matter
     // how we get here.
-    nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+    nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
 
     // Never permit dialogs from the beforeunload handler
     nsGlobalWindowOuter* globalWindow = nsGlobalWindowOuter::Cast(window);
     dialogsAreEnabled = globalWindow->AreDialogsEnabled();
     nsGlobalWindowOuter::TemporarilyDisableDialogs disableDialogs(globalWindow);
 
     nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument);
 
@@ -1444,17 +1445,17 @@ nsDocumentViewer::PageHide(bool aIsUnloa
     nsEventStatus status = nsEventStatus_eIgnore;
     WidgetEvent event(true, eUnload);
     event.mFlags.mBubbles = false;
     // XXX Dispatching to |window|, but using |document| as the target.
     event.mTarget = mDocument;
 
     // Never permit popups from the unload handler, no matter how we get
     // here.
-    nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+    nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
 
     nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument);
 
     EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
   }
 
 #ifdef MOZ_XUL
   // look for open menupopups and close them after the unload event, in case
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -20,17 +20,17 @@
 #include "nsCSSAnonBoxes.h"
 #include "mozilla/css/ErrorReporter.h"
 #include "nsCSSKeywords.h"
 #include "nsCSSProps.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSRendering.h"
 #include "nsGenericHTMLFrameElement.h"
 #include "mozilla/dom/Attr.h"
-#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/PopupBlocker.h"
 #include "nsFrame.h"
 #include "nsFrameState.h"
 #include "nsGlobalWindow.h"
 #include "nsGkAtoms.h"
 #include "nsImageFrame.h"
 #include "nsLayoutStylesheetCache.h"
 #include "nsRange.h"
 #include "nsRegion.h"
@@ -188,16 +188,18 @@ nsresult nsLayoutStatics::Initialize() {
 
   nsMathMLOperators::AddRefTable();
 
 #ifdef DEBUG
   nsFrame::DisplayReflowStartup();
 #endif
   Attr::Initialize();
 
+  PopupBlocker::Initialize();
+
   rv = txMozillaXSLTProcessor::Startup();
   if (NS_FAILED(rv)) {
     NS_ERROR("Could not initialize txMozillaXSLTProcessor");
     return rv;
   }
 
   rv = StorageObserver::Init();
   if (NS_FAILED(rv)) {
@@ -310,17 +312,17 @@ void nsLayoutStatics::Shutdown() {
   nsMessageManagerScriptExecutor::Shutdown();
   nsFocusManager::Shutdown();
 #ifdef MOZ_XUL
   nsXULPopupManager::Shutdown();
 #endif
   StorageObserver::Shutdown();
   txMozillaXSLTProcessor::Shutdown();
   Attr::Shutdown();
-  EventListenerManager::Shutdown();
+  PopupBlocker::Shutdown();
   IMEStateManager::Shutdown();
   nsMediaFeatures::Shutdown();
   nsHTMLDNSPrefetch::Shutdown();
   nsCSSRendering::Shutdown();
   StaticPresData::Shutdown();
 #ifdef DEBUG
   nsFrame::DisplayReflowShutdown();
 #endif
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -463,16 +463,22 @@ VARCACHE_PREF(
 #endif
 VARCACHE_PREF(
   "dom.targetBlankNoOpener.enabled",
    dom_targetBlankNoOpener_enabled,
   bool, PREF_VALUE
 )
 #undef PREF_VALUE
 
+VARCACHE_PREF(
+  "dom.disable_open_during_load",
+   dom_disable_open_during_load,
+  bool, false
+)
+
 //---------------------------------------------------------------------------
 // Clear-Site-Data prefs
 //---------------------------------------------------------------------------
 
 VARCACHE_PREF(
   "dom.clearSiteData.enabled",
    dom_clearSiteData_enabled,
   bool, true
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1271,17 +1271,16 @@ pref("dom.disable_window_open_feature.re
 pref("dom.disable_window_open_feature.minimizable", false);
 pref("dom.disable_window_open_feature.status",      true);
 pref("dom.disable_window_showModalDialog",          true);
 
 pref("dom.allow_scripts_to_close_windows",          false);
 
 pref("dom.require_user_interaction_for_beforeunload", true);
 
-pref("dom.disable_open_during_load",                false);
 pref("dom.popup_maximum",                           20);
 pref("dom.popup_allowed_events", "change click dblclick mouseup pointerup notificationclick reset submit touchend");
 
 pref("dom.disable_open_click_delay", 1000);
 pref("dom.serviceWorkers.disable_open_click_delay", 1000);
 
 pref("dom.storage.enabled", true);
 // Whether or not LSNG (Next Generation Local Storage) is enabled.
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -1197,17 +1197,17 @@ nsresult nsWindowWatcher::OpenWindowInte
     }
     if (parentWidget &&
         ((!newWindowShouldBeModal && parentIsModal) || isAppModal)) {
       parentWidget->SetFakeModal(true);
     } else {
       // Reset popup state while opening a modal dialog, and firing
       // events about the dialog, to prevent the current state from
       // being active the whole time a modal dialog is open.
-      nsAutoPopupStatePusher popupStatePusher(openAbused);
+      nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused);
 
       newChrome->ShowAsModal();
     }
   }
 
   if (aForceNoOpener && windowIsNew) {
     NS_RELEASE(*aResult);
   }