Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Wed, 19 Dec 2018 17:46:11 +0200
changeset 451302 6b4a345b50e94310091b01e8db32b3028ca58f89
parent 451290 335cc6f2cbb1fd8d7f0d9581e0a85e6faabf948b (current diff)
parent 451301 91fb09d21018429082ba647b8079a7834ad48ee3 (diff)
child 451321 1a27d1c7cb0cd4e3410658df97d457757c6b8112
child 451374 1acd86ad823cffb7c4d2616c5edee30122346cf0
push id35233
push userebalazs@mozilla.com
push dateWed, 19 Dec 2018 15:46:33 +0000
treeherdermozilla-central@6b4a345b50e9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
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
Merge inbound to mozilla-central. a=merge
layout/base/PresShell.cpp
layout/generic/nsGfxScrollFrame.cpp
--- a/browser/components/enterprisepolicies/EnterprisePolicies.js
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.js
@@ -378,16 +378,21 @@ class JSONPoliciesProvider {
     return configFile;
   }
 
   _readData() {
     try {
       let data = Cu.readUTF8File(this._getConfigurationFile());
       if (data) {
         this._policies = JSON.parse(data).policies;
+
+        if (!this._policies) {
+          log.error("Policies file doesn't contain a 'policies' object");
+          this._failed = true;
+        }
       }
     } catch (ex) {
       if (ex instanceof Components.Exception &&
           ex.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
         // Do nothing, _policies will remain null
       } else if (ex instanceof SyntaxError) {
         log.error("Error parsing JSON file");
         this._failed = true;
--- a/browser/components/enterprisepolicies/content/aboutPolicies.js
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.js
@@ -300,21 +300,36 @@ function init() {
 }
 
 function show(button) {
   let current_tab = document.querySelector(".active");
   let category = button.getAttribute("id").substring("category-".length);
   let content = document.getElementById(category);
   if (current_tab == content)
     return;
+  saveScrollPosition(current_tab.id);
   current_tab.classList.remove("active");
   current_tab.hidden = true;
   content.classList.add("active");
   content.hidden = false;
 
   let current_button = document.querySelector("[selected=true]");
   current_button.removeAttribute("selected");
   button.setAttribute("selected", "true");
 
   let title = document.getElementById("sectionTitle");
   title.textContent = button.children[0].textContent;
   location.hash = category;
+  restoreScrollPosition(category);
 }
+
+const scrollPositions = {};
+function saveScrollPosition(category) {
+  const mainContent = document.querySelector(".main-content");
+  scrollPositions[category] = mainContent.scrollTop;
+}
+
+function restoreScrollPosition(category) {
+  const scrollY = scrollPositions[category] || 0;
+  const mainContent = document.querySelector(".main-content");
+  mainContent.scrollTo(0, scrollY);
+}
+
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -13,16 +13,17 @@ support-files =
   ../../../../../toolkit/components/antitracking/test/browser/subResources.sjs
 
 [browser_policies_basic_tests.js]
 [browser_policies_broken_json.js]
 [browser_policies_enterprise_only.js]
 [browser_policies_getActivePolicies.js]
 [browser_policies_macosparser_unflatten.js]
 skip-if = os != 'mac'
+[browser_policies_mistyped_json.js]
 [browser_policies_notice_in_aboutpreferences.js]
 [browser_policies_popups_cookies_addons_flash.js]
 [browser_policies_runOnce_helper.js]
 [browser_policies_setAndLockPref_API.js]
 [browser_policies_simple_pref_policies.js]
 [browser_policies_sorted_alphabetically.js]
 [browser_policy_app_update.js]
 [browser_policy_app_update_URL.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_mistyped_json.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_json_with_mistyped_policies() {
+    // Note: The "polcies" string is intentionally mistyped
+    await setupPolicyEngineWithJson({
+        "polcies": {},
+    });
+
+    is(Services.policies.status, Ci.nsIEnterprisePolicies.FAILED, "Engine was correctly set to the error state");
+});
+
--- a/browser/components/extensions/parent/ext-browsingData.js
+++ b/browser/components/extensions/parent/ext-browsingData.js
@@ -109,16 +109,27 @@ const clearIndexedDB = async function(op
 };
 
 const clearLocalStorage = async function(options) {
   if (options.since) {
     return Promise.reject(
       {message: "Firefox does not support clearing localStorage with 'since'."});
   }
 
+  // The legacy LocalStorage implementation that will eventually be removed
+  // depends on this observer notification.  Some other subsystems like
+  // Reporting headers depend on this too.
+  if (options.hostnames) {
+    for (let hostname of options.hostnames) {
+      Services.obs.notifyObservers(null, "extension:purge-localStorage", hostname);
+    }
+  } else {
+    Services.obs.notifyObservers(null, "extension:purge-localStorage");
+  }
+
   if (Services.lsm.nextGenLocalStorageEnabled) {
     // Ideally we could reuse the logic in Sanitizer.jsm or nsIClearDataService,
     // but this API exposes an ability to wipe data at a much finger granularity
     // than those APIs.  So custom logic is used here to wipe only the QM
     // localStorage client (when in use).
 
     let promises = [];
 
@@ -147,24 +158,16 @@ const clearLocalStorage = async function
         }
 
         resolve();
       });
     });
 
     return Promise.all(promises);
   }
-
-  if (options.hostnames) {
-    for (let hostname of options.hostnames) {
-      Services.obs.notifyObservers(null, "extension:purge-localStorage", hostname);
-    }
-  } else {
-    Services.obs.notifyObservers(null, "extension:purge-localStorage");
-  }
 };
 
 const clearPasswords = async function(options) {
   let loginManager = Services.logins;
   let yieldCounter = 0;
 
   if (options.since) {
     // Iterate through the logins and delete any updated after our cutoff.
--- a/browser/extensions/formautofill/test/browser/browser.ini
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -26,9 +26,9 @@ skip-if = verify
 [browser_insecure_form.js]
 skip-if = (os == 'linux' && !debug) || (os == 'win') # bug 1456284
 [browser_manageAddressesDialog.js]
 [browser_manageCreditCardsDialog.js]
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_privacyPreferences.js]
 [browser_submission_in_private_mode.js]
 [browser_update_doorhanger.js]
-skip-if = (os == "linux") || (os == "mac" && debug) || (os == "win") # bug 1426981
+skip-if = true # bug 1426981 # Bug 1445538
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1315,16 +1315,17 @@ nsIDocument::nsIDocument()
       mParserAborted(false),
       mReportedUseCounters(false),
       mHasReportedShadowDOMUsage(false),
       mDocTreeHadAudibleMedia(false),
       mDocTreeHadPlayRevoked(false),
 #ifdef DEBUG
       mWillReparent(false),
 #endif
+      mHasDelayedRefreshEvent(false),
       mPendingFullscreenRequests(0),
       mXMLDeclarationBits(0),
       mOnloadBlockCount(0),
       mAsyncOnloadBlockCount(0),
       mCompatMode(eCompatibility_FullStandards),
       mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
 #ifdef MOZILLA_INTERNAL_API
       mVisibilityState(dom::VisibilityState::Hidden),
@@ -8584,16 +8585,28 @@ void nsIDocument::UnsuppressEventHandlin
 
   if (!EventHandlingSuppressed()) {
     MOZ_ASSERT(NS_IsMainThread());
     nsTArray<RefPtr<net::ChannelEventQueue>> queues;
     mSuspendedQueues.SwapElements(queues);
     for (net::ChannelEventQueue* queue : queues) {
       queue->Resume();
     }
+
+    // If there have been any events driven by the refresh driver which were
+    // delayed due to events being suppressed in this document, make sure there
+    // is a refresh scheduled soon so the events will run.
+    if (mHasDelayedRefreshEvent) {
+      mHasDelayedRefreshEvent = false;
+
+      if (mPresShell) {
+        nsRefreshDriver* rd = mPresShell->GetPresContext()->RefreshDriver();
+        rd->RunDelayedEventsSoon();
+      }
+    }
   }
 }
 
 void nsIDocument::AddSuspendedChannelEventQueue(
     net::ChannelEventQueue* aQueue) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(EventHandlingSuppressed());
   mSuspendedQueues.AppendElement(aQueue);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2451,16 +2451,20 @@ class nsIDocument : public nsINode,
   /**
    * Note a ChannelEventQueue which has been suspended on the document's behalf
    * to prevent XHRs from running content scripts while event handling is
    * suppressed. The document is responsible for resuming the queue after
    * event handling is unsuppressed.
    */
   void AddSuspendedChannelEventQueue(mozilla::net::ChannelEventQueue* aQueue);
 
+  void SetHasDelayedRefreshEvent() {
+    mHasDelayedRefreshEvent = true;
+  }
+
   /**
    * Increment https://html.spec.whatwg.org/#ignore-destructive-writes-counter
    */
   void IncrementIgnoreDestructiveWritesCounter() {
     ++mIgnoreDestructiveWritesCounter;
   }
 
   /**
@@ -3916,16 +3920,20 @@ class nsIDocument : public nsINode,
 
 #ifdef DEBUG
  public:
   bool mWillReparent : 1;
 
  protected:
 #endif
 
+  // Whether an event triggered by the refresh driver was delayed because this
+  // document has suppressed events.
+  bool mHasDelayedRefreshEvent : 1;
+
   uint8_t mPendingFullscreenRequests;
 
   uint8_t mXMLDeclarationBits;
 
   // Currently active onload blockers.
   uint32_t mOnloadBlockCount;
 
   // Onload blockers which haven't been activated yet.
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -62,16 +62,18 @@ skip-if = verify
 [test_bug1063837.xul]
 [test_bug1139964.xul]
 [test_bug1209621.xul]
 [test_bug1346936.html]
 [test_chromeOuterWindowID.xul]
 support-files =
   window_chromeOuterWindowID.xul
 [test_cpows.xul]
+support-files =
+  cpows_child.html
 [test_getElementsWithGrid.html]
 [test_custom_element_content.xul]
 [test_custom_element_ep.xul]
 [test_document-element-inserted.xul]
 support-files =
   file_document-element-inserted.xul
   file_document-element-inserted-inner.xul
 [test_domparsing.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/cpows_child.html
@@ -0,0 +1,4 @@
+<html>
+<body>
+</body>
+</html>
--- a/dom/base/test/chrome/cpows_child.js
+++ b/dom/base/test/chrome/cpows_child.js
@@ -18,16 +18,17 @@ var is_remote;
     postmessage_test,
     sync_test,
     async_test,
     rpc_test,
     lifetime_test,
     cancel_test,
     cancel_test2,
     dead_test,
+    localStorage_test,
     unsafe_test,
   ];
 
   function go() {
     if (tests.length == 0) {
       sendRpcMessage("cpows:done", {});
       return;
     }
@@ -360,8 +361,31 @@ function dead_test(finish) {
 
   {
     let thing = { value: "Gonna croak" };
     sendAsyncMessage("cpows:dead", null, { thing, gcTrigger });
   }
 
   addMessageListener("cpows:dead_done", finish);
 }
+
+function localStorage_test(finish) {
+  // This test exits because a synchronous message can be sent from the parent
+  // while localStorage is synchronously blocking the main thread in the child
+  // which can result in deadlock.  When unsafe CPOWS go away:
+  //   1. The test can go away.
+  //   2. LocalStorage can be further cleaned up and a bug should be filed to
+  //      clean it up.
+  if (!is_remote) {
+    // Only run this test when running out-of-process.
+    finish();
+    return;
+  }
+
+  function f() {}
+
+  sendAsyncMessage("cpows:localStorage", null, {f});
+  addMessageListener("cpows:localStorage_done", finish);
+
+  for (let i = 0; i < 3; i++) {
+    try { content.localStorage.setItem("foo", "bar"); } catch (ex) {}
+  }
+}
--- a/dom/base/test/chrome/cpows_parent.xul
+++ b/dom/base/test/chrome/cpows_parent.xul
@@ -442,16 +442,21 @@
           }
           ok(true, "Got the expected dead CPOW");
           ok(e.stack, "The exception has a stack");
         }
         msg.target.messageManager.sendAsyncMessage("cpows:dead_done");
       }, 0);
     }
 
+    function recvLocalStorage(msg) {
+      msg.objects.f();
+      msg.target.messageManager.sendAsyncMessage("cpows:localStorage_done");
+    }
+
     function run_tests(type) {
       info("Running tests: " + type);
       var node = document.getElementById('cpowbrowser_' + type);
 
       test_state = type;
       test_node = node;
 
       function recvIsRemote(message) {
@@ -484,20 +489,27 @@
       mm.addMessageListener("cpows:lifetime_test_1", recvLifetimeTest1);
       mm.addMessageListener("cpows:lifetime_test_2", recvLifetimeTest2);
       mm.addMessageListener("cpows:cancel_test", recvCancelTest);
       mm.addMessageListener("cpows:cancel_sync_message", recvCancelSyncMessage);
       mm.addMessageListener("cpows:cancel_test2", recvCancelTest2);
       mm.addMessageListener("cpows:unsafe", recvUnsafe);
       mm.addMessageListener("cpows:safe", recvSafe);
       mm.addMessageListener("cpows:dead", recvDead);
+      mm.addMessageListener("cpows:localStorage", recvLocalStorage);
       mm.loadFrameScript("chrome://mochitests/content/chrome/dom/base/test/chrome/cpows_child.js", true);
     }
 
-    function start() {
+    async function start() {
+      ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
+      let browser = document.getElementById("cpowbrowser_remote");
+      BrowserTestUtils.loadURI(browser, "http://mochi.test:8888/tests/dom/base/test/chrome/cpows_child.html");
+      await BrowserTestUtils.browserLoaded(browser);
+
       run_tests('remote');
     }
 
     function finish() {
       ok(gReceivedErrorProbe, "Should have reported error probe");
       opener.setTimeout("done()", 0);
       window.close();
     }
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3442,18 +3442,18 @@ already_AddRefed<nsIEventTarget> Content
       return do_AddRef(SystemGroup::EventTargetFor(TaskCategory::Other));
 
     default:
       return nullptr;
   }
 }
 
 void ContentChild::OnChannelReceivedMessage(const Message& aMsg) {
-  if (aMsg.is_sync()) {
-    LSObject::CancelSyncLoop();
+  if (aMsg.is_sync() && !aMsg.is_reply()) {
+    LSObject::OnSyncMessageReceived();
   }
 
 #ifdef NIGHTLY_BUILD
   if (nsContentUtils::IsMessageInputEvent(aMsg)) {
     mPendingInputEvents++;
   }
 #endif
 }
@@ -3462,22 +3462,31 @@ void ContentChild::OnChannelReceivedMess
 PContentChild::Result ContentChild::OnMessageReceived(const Message& aMsg) {
   if (nsContentUtils::IsMessageInputEvent(aMsg)) {
     DebugOnly<uint32_t> prevEvts = mPendingInputEvents--;
     MOZ_ASSERT(prevEvts > 0);
   }
 
   return PContentChild::OnMessageReceived(aMsg);
 }
+#endif
 
 PContentChild::Result ContentChild::OnMessageReceived(const Message& aMsg,
                                                       Message*& aReply) {
-  return PContentChild::OnMessageReceived(aMsg, aReply);
+  Result result = PContentChild::OnMessageReceived(aMsg, aReply);
+
+  if (aMsg.is_sync()) {
+    // OnMessageReceived shouldn't be called for sync replies.
+    MOZ_ASSERT(!aMsg.is_reply());
+
+    LSObject::OnSyncMessageHandled();
+  }
+
+  return result;
 }
-#endif
 
 }  // namespace dom
 
 #if defined(__OpenBSD__) && defined(MOZ_CONTENT_SANDBOX)
 #include <unistd.h>
 
 static LazyLogModule sPledgeLog("SandboxPledge");
 
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -723,20 +723,20 @@ class ContentChild final : public PConte
 
   virtual already_AddRefed<nsIEventTarget> GetSpecificMessageEventTarget(
       const Message& aMsg) override;
 
   virtual void OnChannelReceivedMessage(const Message& aMsg) override;
 
 #ifdef NIGHTLY_BUILD
   virtual PContentChild::Result OnMessageReceived(const Message& aMsg) override;
+#endif
 
   virtual PContentChild::Result OnMessageReceived(const Message& aMsg,
                                                   Message*& aReply) override;
-#endif
 
   InfallibleTArray<nsAutoPtr<AlertObserver>> mAlertObservers;
   RefPtr<ConsoleListener> mConsoleListener;
 
   nsTHashtable<nsPtrHashKey<nsIObserver>> mIdleObservers;
 
   InfallibleTArray<nsString> mAvailableDictionaries;
 
--- a/dom/localstorage/LSObject.cpp
+++ b/dom/localstorage/LSObject.cpp
@@ -21,17 +21,47 @@
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 class RequestHelper;
 
 StaticMutex gRequestHelperMutex;
-RequestHelper* gRequestHelper = nullptr;
+nsISerialEventTarget* gSyncLoopEventTarget = nullptr;
+/**
+ * Tracks whether a sync message has been received to the main thread but not
+ * yet processed.  Used by RequestHelper logic to abort effectively synchronous
+ * calls if a sync IPC message is received which could result in deadlock.
+ * This is a boolean because, by definition, the parent can only send one sync
+ * message to the child at a time.
+*/
+bool gPendingSyncMessage = false;
+
+/*
+ * Wrapper for the pushed event queue.  The wrapper automatically dispatches
+ * runnables to the main thread when pushed event queue is no longer active.
+ * This exists because the event loop spinning can be aborted.
+ */
+class NestedEventTargetWrapper final : public nsISerialEventTarget {
+  nsCOMPtr<nsISerialEventTarget> mNestedEventTarget;
+  bool mDisconnected;
+
+ public:
+  explicit NestedEventTargetWrapper(nsISerialEventTarget* aNestedEventTarget)
+      : mNestedEventTarget(aNestedEventTarget)
+      , mDisconnected(false) {}
+
+ private:
+  ~NestedEventTargetWrapper() {}
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  NS_DECL_NSIEVENTTARGET_FULL
+};
 
 /**
  * Main-thread helper that implements the blocking logic required by
  * LocalStorage's synchronous semantics.  StartAndReturnResponse pushes an
  * event queue which is a new event target and spins its nested event loop until
  * a result is received or an abort is necessary due to a PContent-managed sync
  * IPC message being received.  Note that because the event queue is its own
  * event target, there is no re-entrancy.  Normal main-thread runnables will not
@@ -88,17 +118,21 @@ class RequestHelper final : public Runna
   // return to the main-thread in Finish().
   RefPtr<LSObject> mObject;
   // The thread the RequestHelper was created on.  This should be the main
   // thread.
   nsCOMPtr<nsIEventTarget> mOwningEventTarget;
   // The pushed event queue that we use to spin the event loop without
   // processing any of the events dispatched at the mOwningEventTarget (which
   // would result in re-entrancy and violate LocalStorage semantics).
-  nsCOMPtr<nsIEventTarget> mNestedEventTarget;
+  nsCOMPtr<nsISerialEventTarget> mNestedEventTarget;
+  // The wrapper for the pushed event queue.  The wrapper automatically
+  // dispatches runnables to the main thread when pushed event queue is no
+  // longer active.  This exists because the event loop spinning can be aborted.
+  nsCOMPtr<nsISerialEventTarget> mNestedEventTargetWrapper;
   // The IPC actor handling the request with standard IPC allocation rules.
   // Our reference is nulled in OnResponse which corresponds to the actor's
   // __destroy__ method.
   LSRequestChild* mActor;
   const LSRequestParams mParams;
   LSRequestResponse mResponse;
   nsresult mResultCode;
   State mState;
@@ -109,52 +143,35 @@ class RequestHelper final : public Runna
   RequestHelper(LSObject* aObject, const LSRequestParams& aParams)
       : Runnable("dom::RequestHelper"),
         mObject(aObject),
         mOwningEventTarget(GetCurrentThreadEventTarget()),
         mActor(nullptr),
         mParams(aParams),
         mResultCode(NS_OK),
         mState(State::Initial),
-        mWaiting(true) {
-    StaticMutexAutoLock lock(gRequestHelperMutex);
-    gRequestHelper = this;
-  }
+        mWaiting(true) {}
 
   bool IsOnOwningThread() const {
     MOZ_ASSERT(mOwningEventTarget);
 
     bool current;
     return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
            current;
   }
 
   void AssertIsOnOwningThread() const {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(IsOnOwningThread());
   }
 
-  // Used for requests from the parent process to the parent process; in that
-  // case we want ActorsParent to know our event-target and this is better than
-  // trying to tunnel the pointer through IPC.
-  const nsCOMPtr<nsIEventTarget>& GetSyncLoopEventTarget() const {
-    MOZ_ASSERT(XRE_IsParentProcess());
-
-    return mNestedEventTarget;
-  }
-
   nsresult StartAndReturnResponse(LSRequestResponse& aResponse);
 
-  nsresult CancelOnAnyThread();
-
  private:
-  ~RequestHelper() {
-    StaticMutexAutoLock lock(gRequestHelperMutex);
-    gRequestHelper = nullptr;
-  }
+  ~RequestHelper() {}
 
   nsresult Start();
 
   void Finish();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIRUNNABLE
@@ -294,46 +311,54 @@ nsresult LSObject::CreateForPrincipal(ns
   object->mOrigin = origin;
   object->mDocumentURI = aDocumentURI;
 
   object.forget(aObject);
   return NS_OK;
 }
 
 // static
-already_AddRefed<nsIEventTarget> LSObject::GetSyncLoopEventTarget() {
-  RefPtr<RequestHelper> helper;
+already_AddRefed<nsISerialEventTarget> LSObject::GetSyncLoopEventTarget() {
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  nsCOMPtr<nsISerialEventTarget> target;
 
   {
     StaticMutexAutoLock lock(gRequestHelperMutex);
-    helper = gRequestHelper;
-  }
-
-  nsCOMPtr<nsIEventTarget> target;
-  if (helper) {
-    target = helper->GetSyncLoopEventTarget();
+    target = gSyncLoopEventTarget;
   }
 
   return target.forget();
 }
 
 // static
-void LSObject::CancelSyncLoop() {
-  RefPtr<RequestHelper> helper;
+void LSObject::OnSyncMessageReceived() {
+  nsCOMPtr<nsISerialEventTarget> target;
 
   {
     StaticMutexAutoLock lock(gRequestHelperMutex);
-    helper = gRequestHelper;
+    target = gSyncLoopEventTarget;
+    gPendingSyncMessage = true;
   }
 
-  if (helper) {
-    Unused << NS_WARN_IF(NS_FAILED(helper->CancelOnAnyThread()));
+  if (target) {
+    RefPtr<Runnable> runnable =
+        NS_NewRunnableFunction("LSObject::CheckFlagRunnable", []() {});
+
+    MOZ_ALWAYS_SUCCEEDS(
+      target->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
   }
 }
 
+// static
+void LSObject::OnSyncMessageHandled() {
+  StaticMutexAutoLock lock(gRequestHelperMutex);
+  gPendingSyncMessage = false;
+}
+
 LSRequestChild* LSObject::StartRequest(nsIEventTarget* aMainEventTarget,
                                        const LSRequestParams& aParams,
                                        LSRequestChildCallback* aCallback) {
   AssertIsOnDOMFileThread();
 
   PBackgroundChild* backgroundActor =
       BackgroundChild::GetOrCreateForCurrentThread(aMainEventTarget);
   if (NS_WARN_IF(!backgroundActor)) {
@@ -866,16 +891,73 @@ nsresult LSObject::EndExplicitSnapshotIn
 }
 
 void LSObject::LastRelease() {
   AssertIsOnOwningThread();
 
   DropDatabase();
 }
 
+NS_IMPL_ISUPPORTS(NestedEventTargetWrapper, nsIEventTarget,
+                  nsISerialEventTarget);
+
+NS_IMETHODIMP_(bool)
+NestedEventTargetWrapper::IsOnCurrentThreadInfallible() {
+  MOZ_CRASH("IsOnCurrentThreadInfallible should never be called on "
+            "NestedEventTargetWrapper");
+}
+
+NS_IMETHODIMP
+NestedEventTargetWrapper::IsOnCurrentThread(bool* aResult) {
+  MOZ_CRASH("IsOnCurrentThread should never be called on "
+            "NestedEventTargetWrapper");
+}
+
+NS_IMETHODIMP
+NestedEventTargetWrapper::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+                                   uint32_t aFlags) {
+  MOZ_ASSERT(mNestedEventTarget);
+
+  if (mDisconnected) {
+    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(std::move(aEvent), aFlags));
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIRunnable> event(aEvent);
+
+  nsresult rv = mNestedEventTarget->Dispatch(event, aFlags);
+  if (rv == NS_ERROR_UNEXPECTED) {
+    mDisconnected = true;
+
+    // Dispatch leaked the event object on the NS_ERROR_UNEXPECTED failure, so
+    // we explicitly release this object once for that.
+    event.get()->Release();
+
+    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event.forget(), aFlags));
+  } else if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NestedEventTargetWrapper::DispatchFromScript(nsIRunnable* aEvent,
+                                             uint32_t aFlags) {
+  MOZ_CRASH("DispatchFromScript should never be called on "
+            "NestedEventTargetWrapper");
+}
+
+NS_IMETHODIMP
+NestedEventTargetWrapper::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+                                          uint32_t aDelayMs) {
+  MOZ_CRASH("DelayedDispatch should never be called on "
+            "NestedEventTargetWrapper");
+}
+
 nsresult RequestHelper::StartAndReturnResponse(LSRequestResponse& aResponse) {
   AssertIsOnOwningThread();
 
   // Normally, we would use the standard way of blocking the thread using
   // a monitor.
   // The problem is that BackgroundChild::GetOrCreateForCurrentThread()
   // called on the DOM File thread may dispatch a runnable to the main
   // thread to finish initialization of PBackground. A monitor would block
@@ -895,71 +977,121 @@ nsresult RequestHelper::StartAndReturnRe
         static_cast<ThreadEventQueue<EventQueue>*>(thread->EventQueue());
 
     mNestedEventTarget = queue->PushEventQueue();
     MOZ_ASSERT(mNestedEventTarget);
 
     auto autoPopEventQueue = mozilla::MakeScopeExit(
         [&] { queue->PopEventQueue(mNestedEventTarget); });
 
+    mNestedEventTargetWrapper =
+      new NestedEventTargetWrapper(mNestedEventTarget);
+
     nsCOMPtr<nsIEventTarget> domFileThread =
         IPCBlobInputStreamThread::GetOrCreate();
     if (NS_WARN_IF(!domFileThread)) {
       return NS_ERROR_FAILURE;
     }
 
-    nsresult rv = domFileThread->Dispatch(this, NS_DISPATCH_NORMAL);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+    nsresult rv;
+
+    {
+      {
+        StaticMutexAutoLock lock(gRequestHelperMutex);
+
+        if (NS_WARN_IF(gPendingSyncMessage)) {
+          return NS_ERROR_FAILURE;
+        }
+
+        gSyncLoopEventTarget = mNestedEventTargetWrapper;
+      }
+
+      auto autoClearSyncLoopEventTarget = mozilla::MakeScopeExit([&] {
+        StaticMutexAutoLock lock(gRequestHelperMutex);
+        gSyncLoopEventTarget = nullptr;
+      });
+
+      rv = domFileThread->Dispatch(this, NS_DISPATCH_NORMAL);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() {
+        if (!mWaiting) {
+          return true;
+        }
+
+        {
+          StaticMutexAutoLock lock(gRequestHelperMutex);
+          if (NS_WARN_IF(gPendingSyncMessage)) {
+            return true;
+          }
+        }
+
+        return false;
+      }));
     }
 
-    MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return !mWaiting; }));
+    // If mWaiting is still set to true, it means that the event loop spinning
+    // was aborted and we need to cancel the request in the parent since we
+    // don't care about the result anymore.
+    // We can check mWaiting here because it's only ever touched on the main
+    // thread.
+    if (NS_WARN_IF(mWaiting)) {
+      // Don't touch mResponse, mResultCode or mState here! The DOM File Thread
+      // may be accessing them at the same moment.
+
+      RefPtr<RequestHelper> self = this;
+
+      RefPtr<Runnable> runnable =
+          NS_NewRunnableFunction("RequestHelper::SendCancelRunnable", [self]() {
+            LSRequestChild* actor = self->mActor;
+
+            // Start() could fail or it hasn't had a chance to run yet, so we
+            // need to check if actor is not null.
+            // The actor can also be in the final (finishing) state, in that
+            // case we are not allowed to send the cancel message and it
+            // wouldn't make sense because the request is about to be destroyed
+            // anyway.
+            if (actor && !actor->Finishing()) {
+              actor->SendCancel();
+            }
+          });
+
+      rv = domFileThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      return NS_ERROR_FAILURE;
+    }
+
+    // PopEventQueue will be called automatically when we leave this scope.
+    // If the event loop spinning was aborted and other threads dispatched new
+    // runnables to the nested event queue, they will be moved to the main
+    // event queue here and later asynchronusly processed.  So nothing will be
+    // lost.
   }
 
   if (NS_WARN_IF(NS_FAILED(mResultCode))) {
     return mResultCode;
   }
 
   aResponse = std::move(mResponse);
   return NS_OK;
 }
 
-nsresult RequestHelper::CancelOnAnyThread() {
-  RefPtr<RequestHelper> self = this;
-
-  RefPtr<Runnable> runnable =
-      NS_NewRunnableFunction("RequestHelper::CancelOnAnyThread", [self]() {
-        LSRequestChild* actor = self->mActor;
-        if (actor && !actor->Finishing()) {
-          actor->SendCancel();
-        }
-      });
-
-  nsCOMPtr<nsIEventTarget> domFileThread =
-      IPCBlobInputStreamThread::GetOrCreate();
-  if (NS_WARN_IF(!domFileThread)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = domFileThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
 nsresult RequestHelper::Start() {
   AssertIsOnDOMFileThread();
   MOZ_ASSERT(mState == State::Initial);
 
   mState = State::ResponsePending;
 
   LSRequestChild* actor =
-      mObject->StartRequest(mNestedEventTarget, mParams, this);
+      mObject->StartRequest(mNestedEventTargetWrapper, mParams, this);
   if (NS_WARN_IF(!actor)) {
     return NS_ERROR_FAILURE;
   }
 
   mActor = actor;
 
   return NS_OK;
 }
@@ -1000,29 +1132,31 @@ RequestHelper::Run() {
     }
 
     mState = State::Finishing;
 
     if (IsOnOwningThread()) {
       Finish();
     } else {
       MOZ_ALWAYS_SUCCEEDS(
-          mNestedEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+          mNestedEventTargetWrapper->Dispatch(this, NS_DISPATCH_NORMAL));
     }
   }
 
   return NS_OK;
 }
 
 void RequestHelper::OnResponse(const LSRequestResponse& aResponse) {
   AssertIsOnDOMFileThread();
   MOZ_ASSERT(mState == State::ResponsePending);
 
   mActor = nullptr;
 
   mResponse = aResponse;
 
   mState = State::Finishing;
-  MOZ_ALWAYS_SUCCEEDS(mNestedEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+
+  MOZ_ALWAYS_SUCCEEDS(
+      mNestedEventTargetWrapper->Dispatch(this, NS_DISPATCH_NORMAL));
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/localstorage/LSObject.h
+++ b/dom/localstorage/LSObject.h
@@ -91,29 +91,40 @@ class LSObject final : public Storage {
                                      const nsAString& aDocumentURI,
                                      bool aPrivate, LSObject** aObject);
 
   /**
    * Used for requests from the parent process to the parent process; in that
    * case we want ActorsParent to know our event-target and this is better than
    * trying to tunnel the pointer through IPC.
    */
-  static already_AddRefed<nsIEventTarget> GetSyncLoopEventTarget();
+  static already_AddRefed<nsISerialEventTarget> GetSyncLoopEventTarget();
 
   /**
    * Helper invoked by ContentChild::OnChannelReceivedMessage when a sync IPC
-   * message is received.  This will be invoked on the IPC I/O thread and it's
-   * necessary to unblock the main thread when this happens to avoid the
-   * potential for browser deadlock.  This should only occur in (ugly) testing
-   * scenarios where CPOWs are in use.
+   * message is received.  This will be invoked on the IPC I/O thread and it
+   * will set the gPendingSyncMessage flag to true.  It will also force the sync
+   * loop (if it's active) to check the gPendingSyncMessage flag which will
+   * result in premature finish of the loop.
    *
-   * Cancellation will result in the underlying LSRequest being explicitly
+   * This is necessary to unblock the main thread when a sync IPC message is
+   * received to avoid the potential for browser deadlock.  This should only
+   * occur in (ugly) testing scenarios where CPOWs are in use.
+   *
+   * Aborted sync loop will result in the underlying LSRequest being explicitly
    * canceled, resulting in the parent sending an NS_ERROR_FAILURE result.
    */
-  static void CancelSyncLoop();
+  static void OnSyncMessageReceived();
+
+  /*
+   * Helper invoked by ContentChild::OnMessageReceived when a sync IPC message
+   * has been handled.  This will be invoked on the main thread and it will
+   * set the gPendingSyncMessage flag to false.
+   */
+  static void OnSyncMessageHandled();
 
   void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(LSObject); }
 
   const nsString& DocumentURI() const { return mDocumentURI; }
 
   LSRequestChild* StartRequest(nsIEventTarget* aMainEventTarget,
                                const LSRequestParams& aParams,
                                LSRequestChildCallback* aCallback);
--- a/dom/svg/SVGAElement.cpp
+++ b/dom/svg/SVGAElement.cpp
@@ -206,65 +206,57 @@ static bool IsNodeInEditableRegion(nsINo
     if (aNode->IsEditable()) {
       return true;
     }
     aNode = aNode->GetParent();
   }
   return false;
 }
 
-bool SVGAElement::IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex) {
-  if (SVGGraphicsElement::IsSVGFocusable(aIsFocusable, aTabIndex)) {
-    return true;
+bool SVGAElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
+  bool isFocusable = false;
+  if (IsSVGFocusable(&isFocusable, aTabIndex)) {
+    return isFocusable;
   }
 
   // cannot focus links if there is no link handler
   nsIDocument* doc = GetComposedDoc();
   if (doc) {
     nsPresContext* presContext = doc->GetPresContext();
     if (presContext && !presContext->GetLinkHandler()) {
-      *aIsFocusable = false;
       return false;
     }
   }
 
   // Links that are in an editable region should never be focusable, even if
   // they are in a contenteditable="false" region.
   if (IsNodeInEditableRegion(this)) {
     if (aTabIndex) {
       *aTabIndex = -1;
     }
-
-    *aIsFocusable = false;
-
-    return true;
+    return false;
   }
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
     // check whether we're actually a link
     if (!Link::HasURI()) {
       // Not tabbable or focusable without href (bug 17605), unless
       // forced to be via presence of nonnegative tabindex attribute
       if (aTabIndex) {
         *aTabIndex = -1;
       }
-
-      *aIsFocusable = false;
-
       return false;
     }
   }
 
   if (aTabIndex && (sTabFocusModel & eTabFocus_linksMask) == 0) {
     *aTabIndex = -1;
   }
 
-  *aIsFocusable = true;
-
-  return false;
+  return true;
 }
 
 bool SVGAElement::IsLink(nsIURI** aURI) const {
   // To be a clickable XLink for styling and interaction purposes, we require:
   //
   //   xlink:href    - must be set
   //   xlink:type    - must be unset or set to "" or set to "simple"
   //   xlink:show    - must be unset or set to "", "new" or "replace"
--- a/dom/svg/SVGAElement.h
+++ b/dom/svg/SVGAElement.h
@@ -47,17 +47,17 @@ class SVGAElement final : public SVGAEle
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
   virtual int32_t TabIndexDefault() override;
-  virtual bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex) override;
+  bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual void GetLinkTarget(nsAString& aTarget) override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
   virtual EventStates IntrinsicState() const override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aMaybeScriptedPrincipal,
--- a/dom/svg/SVGGraphicsElement.h
+++ b/dom/svg/SVGGraphicsElement.h
@@ -20,17 +20,21 @@ class SVGGraphicsElement : public SVGGra
   explicit SVGGraphicsElement(
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
   ~SVGGraphicsElement();
 
  public:
   // interfaces:
   NS_DECL_ISUPPORTS_INHERITED
 
-  virtual bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex);
   bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
   nsSVGElement* AsSVGElement() final { return this; }
+
+ protected:
+  // returns true if focusability has been definitively determined otherwise
+  // false
+  bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex);
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_SVGGraphicsElement_h
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -2025,16 +2025,28 @@ nsresult PresShell::ResizeReflowIgnoreOv
   return NS_OK;  // XXX this needs to be real. MMP
 }
 
 void PresShell::FireResizeEvent() {
   if (mIsDocumentGone) {
     return;
   }
 
+  // If event handling is suppressed, repost the resize event to the refresh
+  // driver. The event is marked as delayed so that the refresh driver does not
+  // continue ticking.
+  if (mDocument->EventHandlingSuppressed()) {
+    if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
+      mDocument->SetHasDelayedRefreshEvent();
+      mPresContext->RefreshDriver()->AddResizeEventFlushObserver
+          (this, /* aDelayed = */ true);
+    }
+    return;
+  }
+
   mResizeEventPending = false;
 
   // Send resize event from here.
   WidgetEvent event(true, mozilla::eResize);
   nsEventStatus status = nsEventStatus_eIgnore;
 
   if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
     EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1139,19 +1139,23 @@ void nsRefreshDriver::AddTimerAdjustment
 }
 
 void nsRefreshDriver::RemoveTimerAdjustmentObserver(
     nsATimerAdjustmentObserver* aObserver) {
   MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver));
   mTimerAdjustmentObservers.RemoveElement(aObserver);
 }
 
-void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent) {
-  mScrollEvents.AppendElement(aScrollEvent);
-  EnsureTimerStarted();
+void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed) {
+  if (aDelayed) {
+    mDelayedScrollEvents.AppendElement(aScrollEvent);
+  } else {
+    mScrollEvents.AppendElement(aScrollEvent);
+    EnsureTimerStarted();
+  }
 }
 
 void nsRefreshDriver::DispatchScrollEvents() {
   // Scroll events are one-shot, so after running them we can drop them.
   // However, dispatching a scroll event can potentially cause more scroll
   // events to be posted, so we move the initial set into a temporary array
   // first. (Newly posted scroll events will be dispatched on the next tick.)
   ScrollEventArray events;
@@ -1205,16 +1209,31 @@ void nsRefreshDriver::NotifyDOMContentLo
   // flushed now.
   if (!HasObservers()) {
     GetPresContext()->NotifyDOMContentFlushed();
   } else {
     mNotifyDOMContentFlushed = true;
   }
 }
 
+void nsRefreshDriver::RunDelayedEventsSoon() {
+  // Place entries for delayed events into their corresponding normal list,
+  // and schedule a refresh. When these delayed events run, if their document
+  // still has events suppressed then they will be readded to the delayed
+  // events list.
+
+  mScrollEvents.AppendElements(mDelayedScrollEvents);
+  mDelayedScrollEvents.Clear();
+
+  mResizeEventFlushObservers.AppendElements(mDelayedResizeEventFlushObservers);
+  mDelayedResizeEventFlushObservers.Clear();
+
+  EnsureTimerStarted();
+}
+
 void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
   // FIXME: Bug 1346065: We should also assert the case where we have
   // STYLO_THREADS=1.
   MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal() || NS_IsMainThread(),
              "EnsureTimerStarted should be called only when we are not "
              "in servo traversal or on the main-thread");
 
   if (mTestControllingRefreshes) return;
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -141,17 +141,17 @@ class nsRefreshDriver final : public moz
                              mozilla::FlushType aFlushType);
   /**
    * Add / remove an observer wants to know the time when the refresh driver
    * updated the most recent refresh time due to its active timer changes.
    */
   void AddTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
   void RemoveTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
 
-  void PostScrollEvent(mozilla::Runnable* aScrollEvent);
+  void PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed = false);
   void DispatchScrollEvents();
 
   /**
    * Add an observer that will be called after each refresh. The caller
    * must remove the observer before it is deleted. This does not trigger
    * refresh driver ticks.
    */
   void AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
@@ -172,25 +172,31 @@ class nsRefreshDriver final : public moz
    * @returns whether the operation succeeded, or void in the case of removal.
    */
   bool AddImageRequest(imgIRequest* aRequest);
   void RemoveImageRequest(imgIRequest* aRequest);
 
   /**
    * Add / remove presshells which have pending resize event.
    */
-  void AddResizeEventFlushObserver(nsIPresShell* aShell) {
-    MOZ_DIAGNOSTIC_ASSERT(!mResizeEventFlushObservers.Contains(aShell),
+  void AddResizeEventFlushObserver(nsIPresShell* aShell, bool aDelayed = false) {
+    MOZ_DIAGNOSTIC_ASSERT(!mResizeEventFlushObservers.Contains(aShell) &&
+                          !mDelayedResizeEventFlushObservers.Contains(aShell),
                           "Double-adding resize event flush observer");
-    mResizeEventFlushObservers.AppendElement(aShell);
-    EnsureTimerStarted();
+    if (aDelayed) {
+      mDelayedResizeEventFlushObservers.AppendElement(aShell);
+    } else {
+      mResizeEventFlushObservers.AppendElement(aShell);
+      EnsureTimerStarted();
+    }
   }
 
   void RemoveResizeEventFlushObserver(nsIPresShell* aShell) {
     mResizeEventFlushObservers.RemoveElement(aShell);
+    mDelayedResizeEventFlushObservers.RemoveElement(aShell);
   }
 
   /**
    * Add / remove presshells that we should flush style and layout on
    */
   void AddStyleFlushObserver(nsIPresShell* aShell) {
     MOZ_DIAGNOSTIC_ASSERT(!mStyleFlushObservers.Contains(aShell),
                           "Double-adding style flush observer");
@@ -395,16 +401,19 @@ class nsRefreshDriver final : public moz
   static mozilla::Maybe<mozilla::TimeStamp> GetNextTickHint();
 
   static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
                                             uint32_t aDelay);
   static void CancelIdleRunnable(nsIRunnable* aRunnable);
 
   void NotifyDOMContentLoaded();
 
+  // Schedule a refresh so that any delayed events will run soon.
+  void RunDelayedEventsSoon();
+
  private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
   typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
   typedef nsTHashtable<nsISupportsHashKey> RequestTable;
   struct ImageStartData {
     ImageStartData() {}
 
     mozilla::Maybe<mozilla::TimeStamp> mStartTime;
@@ -521,17 +530,21 @@ class nsRefreshDriver final : public moz
   // to be called when the timer is re-started and should not influence its
   // starting or stopping.
   nsTObserverArray<nsATimerAdjustmentObserver*> mTimerAdjustmentObservers;
   RequestTable mRequests;
   ImageStartTable mStartTable;
   AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
   ScrollEventArray mScrollEvents;
 
+  // Scroll events on documents that might have events suppressed.
+  ScrollEventArray mDelayedScrollEvents;
+
   AutoTArray<nsIPresShell*, 16> mResizeEventFlushObservers;
+  AutoTArray<nsIPresShell*, 16> mDelayedResizeEventFlushObservers;
   AutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   AutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
   nsTArray<nsIDocument*> mThrottledFrameRequestCallbackDocs;
   nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
   nsTArray<mozilla::UniquePtr<mozilla::PendingFullscreenEvent>>
       mPendingFullscreenEvents;
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -4840,19 +4840,19 @@ void ScrollFrameHelper::CurPosAttributeC
         isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT,
         nsGkAtoms::scrollbars, &allowedRange);
   }
   // 'this' might be destroyed here
 }
 
 /* ============= Scroll events ========== */
 
-ScrollFrameHelper::ScrollEvent::ScrollEvent(ScrollFrameHelper* aHelper)
+ScrollFrameHelper::ScrollEvent::ScrollEvent(ScrollFrameHelper* aHelper, bool aDelayed)
     : Runnable("ScrollFrameHelper::ScrollEvent"), mHelper(aHelper) {
-  mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this);
+  mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this, aDelayed);
 }
 
 NS_IMETHODIMP
 ScrollFrameHelper::ScrollEvent::Run() {
   if (mHelper) {
     mHelper->FireScrollEvent();
   }
   return NS_OK;
@@ -4878,16 +4878,26 @@ void ScrollFrameHelper::FireScrollEvent(
   nsCOMPtr<nsIDocShell> docShell = prescontext->GetDocShell();
   AUTO_PROFILER_TRACING_DOCSHELL("Paint", "FireScrollEvent", docShell);
 #endif
 
   MOZ_ASSERT(mScrollEvent);
   mScrollEvent->Revoke();
   mScrollEvent = nullptr;
 
+  // If event handling is suppressed, keep posting the scroll event to the
+  // refresh driver until it is unsuppressed. The event is marked as delayed so
+  // that the refresh driver does not continue ticking.
+  if (content->GetComposedDoc() &&
+      content->GetComposedDoc()->EventHandlingSuppressed()) {
+    content->GetComposedDoc()->SetHasDelayedRefreshEvent();
+    PostScrollEvent(/* aDelayed = */ true);
+    return;
+  }
+
   ActiveLayerTracker::SetCurrentScrollHandlerFrame(mOuter);
   WidgetGUIEvent event(true, eScroll, nullptr);
   nsEventStatus status = nsEventStatus_eIgnore;
   // Fire viewport scroll events at the document (where they
   // will bubble to the window)
   mozilla::layers::ScrollLinkedEffectDetector detector(
       content->GetComposedDoc());
   if (mIsRoot) {
@@ -4899,23 +4909,23 @@ void ScrollFrameHelper::FireScrollEvent(
     // scroll events fired at elements don't bubble (although scroll events
     // fired at documents do, to the window)
     event.mFlags.mBubbles = false;
     EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status);
   }
   ActiveLayerTracker::SetCurrentScrollHandlerFrame(nullptr);
 }
 
-void ScrollFrameHelper::PostScrollEvent() {
+void ScrollFrameHelper::PostScrollEvent(bool aDelayed) {
   if (mScrollEvent) {
     return;
   }
 
   // The ScrollEvent constructor registers itself with the refresh driver.
-  mScrollEvent = new ScrollEvent(this);
+  mScrollEvent = new ScrollEvent(this, aDelayed);
 }
 
 NS_IMETHODIMP
 ScrollFrameHelper::AsyncScrollPortEvent::Run() {
   if (mHelper) {
     mHelper->mOuter->PresContext()->Document()->FlushPendingNotifications(
         FlushType::InterruptibleLayout);
   }
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -92,17 +92,17 @@ class ScrollFrameHelper : public nsIRefl
   virtual void ReflowCallbackCanceled() override;
 
   /**
    * @note This method might destroy the frame, pres shell and other objects.
    * Called when the 'curpos' attribute on one of the scrollbars changes.
    */
   void CurPosAttributeChanged(nsIContent* aChild, bool aDoScroll = true);
 
-  void PostScrollEvent();
+  void PostScrollEvent(bool aDelayed = false);
   void FireScrollEvent();
   void PostScrolledAreaEvent();
   void FireScrolledAreaEvent();
 
   bool IsSmoothScrollingEnabled();
 
   /**
    * This class handles the dispatching of scroll events to content.
@@ -122,17 +122,17 @@ class ScrollFrameHelper : public nsIRefl
    * causes the refresh driver to tick.
    *
    * ScrollEvents are one-shot runnables; the refresh driver drops them after
    * running them.
    */
   class ScrollEvent : public Runnable {
    public:
     NS_DECL_NSIRUNNABLE
-    explicit ScrollEvent(ScrollFrameHelper* aHelper);
+    explicit ScrollEvent(ScrollFrameHelper* aHelper, bool aDelayed);
     void Revoke() { mHelper = nullptr; }
 
    private:
     ScrollFrameHelper* mHelper;
   };
 
   class ScrollEndEvent : public Runnable {
    public:
--- a/toolkit/components/cleardata/ClearDataService.js
+++ b/toolkit/components/cleardata/ClearDataService.js
@@ -286,23 +286,22 @@ const AppCacheCleaner = {
     // AppCache: this doesn't wait for the cleanup to be complete.
     OfflineAppCacheHelper.clear();
     return Promise.resolve();
   },
 };
 
 const QuotaCleaner = {
   deleteByPrincipal(aPrincipal) {
-    if (!Services.lsm.nextGenLocalStorageEnabled) {
-      // localStorage: The legacy LocalStorage implementation that will
-      // eventually be removed depends on this observer notification to clear by
-      // principal.  Only generate it if we're using the legacy implementation.
-      Services.obs.notifyObservers(null, "browser:purge-domain-data",
-                                   aPrincipal.URI.host);
-    }
+    // localStorage: The legacy LocalStorage implementation that will
+    // eventually be removed depends on this observer notification to clear by
+    // principal.  Some other subsystems like Reporting headers depend on this
+    // too.
+    Services.obs.notifyObservers(null, "browser:purge-domain-data",
+                                 aPrincipal.URI.host);
 
     // ServiceWorkers: they must be removed before cleaning QuotaManager.
     return ServiceWorkerCleanUp.removeFromPrincipal(aPrincipal)
       .then(_ => /* exceptionThrown = */ false, _ => /* exceptionThrown = */ true)
       .then(exceptionThrown => {
         // QuotaManager: In the event of a failure, we call reject to propagate
         // the error upwards.
         return new Promise((aResolve, aReject) => {
@@ -314,22 +313,20 @@ const QuotaCleaner = {
               aResolve();
             }
           };
         });
       });
   },
 
   deleteByHost(aHost, aOriginAttributes) {
-    if (!Services.lsm.nextGenLocalStorageEnabled) {
-      // localStorage: The legacy LocalStorage implementation that will
-      // eventually be removed depends on this observer notification to clear by
-      // principal.  Only generate it if we're using the legacy implementation.
-      Services.obs.notifyObservers(null, "browser:purge-domain-data", aHost);
-    }
+    // localStorage: The legacy LocalStorage implementation that will
+    // eventually be removed depends on this observer notification to clear by
+    // host.  Some other subsystems like Reporting headers depend on this too.
+    Services.obs.notifyObservers(null, "browser:purge-domain-data", aHost);
 
     let exceptionThrown = false;
 
     // ServiceWorkers: they must be removed before cleaning QuotaManager.
     return Promise.all([
       ServiceWorkerCleanUp.removeFromHost("http://" + aHost).catch(_ => { exceptionThrown = true; }),
       ServiceWorkerCleanUp.removeFromHost("https://" + aHost).catch(_ => { exceptionThrown = true; }),
     ]).then(() => {