Merge mozilla-central to inbound. a=merge CLOSED TREE
authorOana Pop Rus <opoprus@mozilla.com>
Mon, 01 Jul 2019 13:08:01 +0300
changeset 543619 26bf5c05734f636d58fd350b8758a849d9d12868
parent 543618 c4ed2de25e6db29bc346f82b8a198c5b4a533b22 (current diff)
parent 543604 b3eb1ee3b85dfb87789c92d417e20f377ebcfca3 (diff)
child 543710 ec3a228a84f28cbc4049549e44fab77ef227163b
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone69.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 mozilla-central to inbound. a=merge CLOSED TREE
--- a/.hgtags
+++ b/.hgtags
@@ -157,8 +157,9 @@ 58a0412e15574f063cd380517a0369bfb48b22e0
 9ad82455dcee2bc1d438e46016b8db00e88758a8 FIREFOX_BETA_65_BASE
 3386ff76878d83496bb822d09115c77472808b53 FIREFOX_NIGHTLY_65_END
 254bd88c107271f3d1c2ca9969acc0ed507f0a8d FIREFOX_BETA_66_BASE
 5d9c5aa981a5b824e6132c923c2f201b3fa49397 FIREFOX_NIGHTLY_66_END
 f4c23517cec8626038a915bfe3bc7c0e1f6af55d FIREFOX_BETA_67_BASE
 9421b477d67cfc4c9e03350cd554a9e6acc7f435 FIREFOX_NIGHTLY_67_END
 390914f7108f4f6065834d8983af9ac855cbf2df FIREFOX_BETA_68_BASE
 97dae745c1b3ef2292127ba1c4e90b1345c8f576 FIREFOX_NIGHTLY_68_END
+adc59d50adf815ad6764ff235f833a5ba74291b6 FIREFOX_BETA_69_BASE
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -225,17 +225,17 @@ body {
 
 .description .general {
   display: flex;
   align-items: center;
   margin-bottom: 1em;
 }
 
 .description img {
-  margin-right: 12px;
+  margin-inline-end: 12px;
   flex-basis: 42px;
   flex-shrink: 0;
   -moz-context-properties: fill;
   fill: var(--grey-40);
 }
 
 .description .devtools-button {
   display: flex;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -8296,53 +8296,59 @@ nsresult nsDocShell::CreateContentViewer
 
     // Update the notification callbacks, so that progress and
     // status information are sent to the right docshell...
     aOpenedChannel->SetNotificationCallbacks(this);
   }
 
   if (DocGroup::TryToLoadIframesInBackground()) {
     if ((!mContentViewer || GetDocument()->IsInitialDocument()) && IsFrame()) {
-      // At this point, we know we just created a new iframe document based on the
-      // response from the server, and we check if it's a cross-domain iframe
+      // At this point, we know we just created a new iframe document based on
+      // the response from the server, and we check if it's a cross-domain
+      // iframe
 
       RefPtr<Document> newDoc = viewer->GetDocument();
 
       RefPtr<nsDocShell> parent = GetParentDocshell();
-      nsCOMPtr<nsIPrincipal> parentPrincipal = parent->GetDocument()->NodePrincipal();
+      nsCOMPtr<nsIPrincipal> parentPrincipal =
+          parent->GetDocument()->NodePrincipal();
       nsCOMPtr<nsIPrincipal> thisPrincipal = newDoc->NodePrincipal();
 
       SiteIdentifier parentSite;
       SiteIdentifier thisSite;
 
-      nsresult rv = BasePrincipal::Cast(parentPrincipal)->GetSiteIdentifier(parentSite);
+      nsresult rv =
+          BasePrincipal::Cast(parentPrincipal)->GetSiteIdentifier(parentSite);
       NS_ENSURE_SUCCESS(rv, rv);
 
       rv = BasePrincipal::Cast(thisPrincipal)->GetSiteIdentifier(thisSite);
       NS_ENSURE_SUCCESS(rv, rv);
 
       if (!parentSite.Equals(thisSite)) {
 #ifdef MOZ_GECKO_PROFILER
         nsCOMPtr<nsIURI> prinURI;
         thisPrincipal->GetURI(getter_AddRefs(prinURI));
-        nsPrintfCString marker("Iframe loaded in background: %s", prinURI->GetSpecOrDefault().get());
+        nsPrintfCString marker("Iframe loaded in background: %s",
+                               prinURI->GetSpecOrDefault().get());
         TimeStamp now = TimeStamp::Now();
-        profiler_add_text_marker("Background Iframe", marker, JS::ProfilingCategoryPair::DOM, now, now, Nothing(), Nothing());
+        profiler_add_text_marker("Background Iframe", marker,
+                                 JS::ProfilingCategoryPair::DOM, now, now,
+                                 Nothing(), Nothing());
 #endif
         SetBackgroundLoadIframe();
       }
     }
   }
 
   NS_ENSURE_SUCCESS(Embed(viewer, "", nullptr), NS_ERROR_FAILURE);
 
   if (TreatAsBackgroundLoad()) {
-    nsCOMPtr<nsIRunnable> triggerParentCheckDocShell = NewRunnableMethod(
-        "nsDocShell::TriggerParentCheckDocShellIsEmpty", this,
-        &nsDocShell::TriggerParentCheckDocShellIsEmpty);
+    nsCOMPtr<nsIRunnable> triggerParentCheckDocShell =
+        NewRunnableMethod("nsDocShell::TriggerParentCheckDocShellIsEmpty", this,
+                          &nsDocShell::TriggerParentCheckDocShellIsEmpty);
     nsresult rv = NS_DispatchToCurrentThread(triggerParentCheckDocShell);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mSavedRefreshURIList = nullptr;
   mSavingOldViewer = false;
   mEODForCurrentDocument = false;
 
@@ -10595,16 +10601,29 @@ nsresult nsDocShell::OpenInitializedChan
   auto cleanupInitialClient =
       MakeScopeExit([&] { mInitialClientSource.reset(); });
 
   nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
   NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
 
   MaybeCreateInitialClientSource();
 
+  nsCOMPtr<nsILoadInfo> loadInfo;
+  aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+
+  LoadInfo* li = static_cast<LoadInfo*>(loadInfo.get());
+  if (loadInfo->GetExternalContentPolicyType() ==
+      nsIContentPolicy::TYPE_DOCUMENT) {
+    li->UpdateBrowsingContextID(mBrowsingContext->Id());
+  } else if (loadInfo->GetExternalContentPolicyType() ==
+             nsIContentPolicy::TYPE_SUBDOCUMENT) {
+    li->UpdateFrameBrowsingContextID(mBrowsingContext->Id());
+  }
+  // TODO: more attributes need to be updated on the LoadInfo (bug 1561706)
+
   // Since we are loading a document we need to make sure the proper reserved
   // and initial client data is stored on the nsILoadInfo.  The
   // ClientChannelHelper does this and ensures that it is propagated properly
   // on redirects.  We pass no reserved client here so that the helper will
   // create the reserved ClientSource if necessary.
   Maybe<ClientInfo> noReservedClient;
   rv = AddClientChannelHelper(aChannel, std::move(noReservedClient),
                               GetInitialClientInfo(),
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -620,23 +620,22 @@ void APZCTreeManager::UpdateHitTestingTr
 
   LayerMetricsWrapper root(aRoot);
   UpdateHitTestingTreeImpl(root, aIsFirstPaint,
                            WRRootId::NonWebRender(aOriginatingLayersId),
                            aPaintSequenceNumber);
 }
 
 void APZCTreeManager::UpdateHitTestingTree(
-    const WebRenderScrollDataWrapper& aScrollWrapper,
-    bool aIsFirstPaint, WRRootId aOriginatingWrRootId,
-    uint32_t aPaintSequenceNumber) {
+    const WebRenderScrollDataWrapper& aScrollWrapper, bool aIsFirstPaint,
+    WRRootId aOriginatingWrRootId, uint32_t aPaintSequenceNumber) {
   AssertOnUpdaterThread();
 
-  UpdateHitTestingTreeImpl(aScrollWrapper, aIsFirstPaint,
-                           aOriginatingWrRootId, aPaintSequenceNumber);
+  UpdateHitTestingTreeImpl(aScrollWrapper, aIsFirstPaint, aOriginatingWrRootId,
+                           aPaintSequenceNumber);
 }
 
 void APZCTreeManager::SampleForWebRender(wr::TransactionWrapper& aTxn,
                                          const TimeStamp& aSampleTime,
                                          wr::RenderRoot aRenderRoot) {
   AssertOnSamplerThread();
   MutexAutoLock lock(mMapLock);
 
@@ -3268,30 +3267,35 @@ void APZCTreeManager::SendSubtreeTransfo
   nsTArray<MatrixMessage> messages;
   bool underAncestor = (aAncestor == nullptr);
   {
     RecursiveMutexAutoLock lock(mTreeLock);
     // This formulation duplicates matrix multiplications closer
     // to the root of the tree. For now, aiming for separation
     // of concerns rather than minimum number of multiplications.
     ForEachNode<ReverseIterator>(
-        mRootNode.get(), [&](HitTestingTreeNode* aNode) {
+        mRootNode.get(),
+        [&](HitTestingTreeNode* aNode) {
           bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor);
           MOZ_ASSERT(!(underAncestor && atAncestor));
           underAncestor |= atAncestor;
           if (!underAncestor) {
             return;
           }
           LayersId layersId = aNode->GetLayersId();
           HitTestingTreeNode* parent = aNode->GetParent();
-          if (!parent || layersId != parent->GetLayersId()) {
+          if (!parent) {
+            messages.AppendElement(
+                MatrixMessage(LayerToScreenMatrix4x4(), layersId));
+          } else if (layersId != parent->GetLayersId()) {
             messages.AppendElement(
-                MatrixMessage(aNode->GetTransformToGecko(), layersId));
+                MatrixMessage(parent->GetTransformToGecko(), layersId));
           }
-        }, [&](HitTestingTreeNode* aNode) {
+        },
+        [&](HitTestingTreeNode* aNode) {
           bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor);
           if (atAncestor) {
             MOZ_ASSERT(underAncestor);
             underAncestor = false;
           }
         });
   }
   controller->NotifyLayerTransforms(messages);
--- a/gfx/layers/apz/test/mochitest/helper_fission_transforms.html
+++ b/gfx/layers/apz/test/mochitest/helper_fission_transforms.html
@@ -44,17 +44,17 @@ function failsafe() {
 async function* test() {
   let iframeElement = document.getElementById("testframe");
 
   let iframeResponse = await FissionTestHelper.sendToOopif(iframeElement, code_for_oopif_to_run.toSource() + "()");
   dump("OOPIF response: " + JSON.stringify(iframeResponse) + "\n");
   ok(iframeResponse, "code_for_oopif_to_run successfully installed");
 
   iframePromise = promiseOneEvent("OOPIF:ClickData", null);
-  synthesizeNativeClick(document.body, 200, 200, function() {
+  synthesizeNativeClick(document.body, 400, 400, function() {
     dump("Finished synthesizing click, waiting for OOPIF message...\n");
   });
   iframeResponse = await iframePromise;
   dump("OOPIF response: " + JSON.stringify(iframeResponse.data) + "\n");
 
   let expected_coord = 200 / Math.sqrt(2); // because the iframe is rotated 45 deg
   ok(Math.abs(iframeResponse.data.x - expected_coord) < 3,
      `x-coord ${iframeResponse.data.x} landed near expected value ${expected_coord}`);
@@ -64,17 +64,17 @@ async function* test() {
 
   </script>
   <style>
     body, html {
         margin: 0;
     }
     div {
         transform-origin: top left;
-        transform: translateX(200px) rotate(45deg);
+        transform: translateX(400px) scale(2) rotate(45deg);
         width: 500px;
     }
     iframe {
         width: 400px;
         height: 300px;
         border: solid 1px black;
     }
   </style>
--- a/gfx/layers/wr/RenderRootBoundary.h
+++ b/gfx/layers/wr/RenderRootBoundary.h
@@ -2,16 +2,19 @@
  * 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 GFX_RENDERROOTBOUNDARY_H
 #define GFX_RENDERROOTBOUNDARY_H
 
 #include "mozilla/webrender/WebRenderTypes.h"
 
+#include <string>
+#include <sstream>
+
 namespace mozilla {
 namespace layers {
 
 // clang-format off
 // A display list can have multiple nsDisplayRenderRoot instances in it, one
 // at each point that the display list transitions from being in one render root
 // to another. In particular, this means that a display list can transition
 // from e.g. the "Default" render root to the "Content" render root multiple
@@ -69,16 +72,22 @@ class RenderRootBoundary {
   bool operator==(const RenderRootBoundary& aOther) const {
     return mChildType == aOther.mChildType && mId == aOther.mId;
   }
 
   friend struct IPC::ParamTraits<RenderRootBoundary>;
   // constructor for IPC
   RenderRootBoundary() = default;
 
+  std::string ToString() const {
+    std::stringstream str;
+    str << "childType=" << (int)mChildType << "; id=" << mId;
+    return str.str();
+  }
+
  private:
   wr::RenderRoot mChildType;
   // The id is what distinguishes different transition points within a display
   // list (i.e. what would be different in C, D, and E in the example above).
   uint64_t mId;
 };
 
 }  // namespace layers
--- a/gfx/layers/wr/WebRenderScrollData.cpp
+++ b/gfx/layers/wr/WebRenderScrollData.cpp
@@ -126,16 +126,24 @@ void WebRenderLayerScrollData::Dump(cons
                 Stringify(mAncestorTransform).c_str());
   printf_stderr("  transform: %s perspective: %d visible: %s\n",
                 Stringify(mTransform).c_str(), mTransformIsPerspective,
                 Stringify(mVisibleRegion).c_str());
   printf_stderr("  event regions override: 0x%x\n", mEventRegionsOverride);
   if (mReferentId) {
     printf_stderr("  ref layers id: 0x%" PRIx64 "\n", uint64_t(*mReferentId));
   }
+  if (mBoundaryRoot) {
+    printf_stderr("  boundary root for: %s\n",
+                  mBoundaryRoot->ToString().c_str());
+  }
+  if (mReferentRenderRoot) {
+    printf_stderr("  ref renderroot: %s\n",
+                  mReferentRenderRoot->ToString().c_str());
+  }
   printf_stderr("  scrollbar type: %d animation: %" PRIx64 "\n",
                 (int)mScrollbarData.mScrollbarLayerType,
                 mScrollbarAnimationId.valueOr(0));
   printf_stderr("  fixed pos container: %" PRIu64 "\n",
                 mFixedPosScrollContainerId);
 }
 
 WebRenderScrollData::WebRenderScrollData()
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7018,59 +7018,69 @@ bool nsDisplayRenderRoot::UpdateScrollDa
   return true;
 }
 
 bool nsDisplayRenderRoot::CreateWebRenderCommands(
     mozilla::wr::DisplayListBuilder& aBuilder,
     mozilla::wr::IpcResourceUpdateQueue& aResources,
     const StackingContextHelper& aSc, RenderRootStateManager* aManager,
     nsDisplayListBuilder* aDisplayListBuilder) {
-  if (aDisplayListBuilder->GetNeedsDisplayListBuild(mRenderRoot) ||
-      !mBuiltWRCommands) {
-    if (aBuilder.GetRenderRoot() == mRenderRoot) {
-      nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, aSc,
-                                                 aManager, aDisplayListBuilder);
-    } else {
-      RefPtr<WebRenderRenderRootData> userData =
-          aManager->CommandBuilder()
-              .CreateOrRecycleWebRenderUserData<WebRenderRenderRootData>(
-                  this, aBuilder.GetRenderRoot());
-      mBoundary = Some(userData->EnsureHasBoundary(mRenderRoot));
-
-      WebRenderCommandBuilder::ScrollDataBoundaryWrapper wrapper(
-          aManager->CommandBuilder(), *mBoundary);
-
-      aBuilder.SetSendSubBuilderDisplayList(mRenderRoot);
-      wr::DisplayListBuilder& builder = aBuilder.SubBuilder(mRenderRoot);
-      wr::IpcResourceUpdateQueue& resources = aResources.SubQueue(mRenderRoot);
-
-      wr::StackingContextParams params;
-      params.clip =
-          wr::WrStackingContextClip::ClipChain(builder.CurrentClipChainId());
-      LayoutDeviceRect rrRect =
-          aDisplayListBuilder->GetRenderRootRect(mRenderRoot);
-      LayoutDevicePoint scOrigin = aSc.GetOrigin();
-      // Subtract the render root rect from this, as it already acts as the
-      // origin for our whole display list in WebRender (via SetDocumentView).
-      // However, we can't simply ignore the value of aSc.GetOrigin(), even
-      // though they will often be the same. This is because there might be
-      // multiple cousin nsDisplayRenderRoots in a tree of nsDisplayItems, and
-      // the RenderRootRect will be the union of their areas.
-      scOrigin.x -= rrRect.x;
-      scOrigin.y -= rrRect.y;
-      StackingContextHelper sc(
-          aManager->CommandBuilder().GetRootStackingContextHelper(mRenderRoot),
-          nullptr, nullptr, nullptr, builder, params,
-          LayoutDeviceRect(scOrigin, LayoutDeviceSize()));
-
-      nsDisplayWrapList::CreateWebRenderCommands(builder, resources, sc,
-                                                 aManager, aDisplayListBuilder);
-    }
-    mBuiltWRCommands = true;
-  }
+
+  // It's important to get the userData here even in the early-return case,
+  // because this has the important side-effect of marking the user data "used"
+  // so it doesn't get discarded at the end of the transaction.
+  RefPtr<WebRenderRenderRootData> userData =
+      aManager->CommandBuilder()
+          .CreateOrRecycleWebRenderUserData<WebRenderRenderRootData>(
+              this, aBuilder.GetRenderRoot());
+  // Technically the next line is redundant but maybe it will stop people who
+  // don't read comments from accidentally moving the above line of code back
+  // down below the early-return.
+  userData->SetUsed(true);
+
+  if (!aDisplayListBuilder->GetNeedsDisplayListBuild(mRenderRoot) &&
+      mBuiltWRCommands) {
+    return true;
+  }
+  if (aBuilder.GetRenderRoot() == mRenderRoot) {
+    nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, aSc,
+                                               aManager, aDisplayListBuilder);
+  } else {
+    mBoundary = Some(userData->EnsureHasBoundary(mRenderRoot));
+
+    WebRenderCommandBuilder::ScrollDataBoundaryWrapper wrapper(
+        aManager->CommandBuilder(), *mBoundary);
+
+    aBuilder.SetSendSubBuilderDisplayList(mRenderRoot);
+    wr::DisplayListBuilder& builder = aBuilder.SubBuilder(mRenderRoot);
+    wr::IpcResourceUpdateQueue& resources = aResources.SubQueue(mRenderRoot);
+
+    wr::StackingContextParams params;
+    params.clip =
+        wr::WrStackingContextClip::ClipChain(builder.CurrentClipChainId());
+    LayoutDeviceRect rrRect =
+        aDisplayListBuilder->GetRenderRootRect(mRenderRoot);
+    LayoutDevicePoint scOrigin = aSc.GetOrigin();
+    // Subtract the render root rect from this, as it already acts as the
+    // origin for our whole display list in WebRender (via SetDocumentView).
+    // However, we can't simply ignore the value of aSc.GetOrigin(), even
+    // though they will often be the same. This is because there might be
+    // multiple cousin nsDisplayRenderRoots in a tree of nsDisplayItems, and
+    // the RenderRootRect will be the union of their areas.
+    scOrigin.x -= rrRect.x;
+    scOrigin.y -= rrRect.y;
+    StackingContextHelper sc(
+        aManager->CommandBuilder().GetRootStackingContextHelper(mRenderRoot),
+        nullptr, nullptr, nullptr, builder, params,
+        LayoutDeviceRect(scOrigin, LayoutDeviceSize()));
+
+    nsDisplayWrapList::CreateWebRenderCommands(builder, resources, sc,
+                                               aManager, aDisplayListBuilder);
+  }
+  mBuiltWRCommands = true;
   return true;
 }
 
 void nsDisplayRenderRoot::ExpandDisplayListBuilderRenderRootRect(
     nsDisplayListBuilder* aBuilder) {
   if (mFrame->GetRect().IsEmpty()) {
     return;
   }
--- a/media/libcubeb/moz.yaml
+++ b/media/libcubeb/moz.yaml
@@ -14,10 +14,10 @@ bugzilla:
 origin:
   name: "cubeb"
   description: "Cross platform audio library"
 
   url: "https://github.com/kinetiknz/cubeb"
   license: "ISC"
 
   # update.sh will update this value
-  release: "b9e2c50e51fc58b31b553b5364efacec24ebb76e (2019-05-17 09:21:59 +1200)"
+  release: "98a1c8e61c78d4e3b8839352617a1e2432717a42 (2019-06-20 13:30:20 -0700)"
 
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -37,25 +37,31 @@ int pulse_rust_init(cubeb ** contet, cha
 int jack_init (cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_ALSA)
 int alsa_init(cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_AUDIOUNIT)
 int audiounit_init(cubeb ** context, char const * context_name);
 #endif
+#if defined(USE_AUDIOUNIT_RUST)
+int audiounit_rust_init(cubeb ** contet, char const * context_name);
+#endif
 #if defined(USE_WINMM)
 int winmm_init(cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_WASAPI)
 int wasapi_init(cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_SNDIO)
 int sndio_init(cubeb ** context, char const * context_name);
 #endif
+#if defined(USE_SUN)
+int sun_init(cubeb ** context, char const * context_name);
+#endif
 #if defined(USE_OPENSL)
 int opensl_init(cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_AUDIOTRACK)
 int audiotrack_init(cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_KAI)
 int kai_init(cubeb ** context, char const * context_name);
@@ -131,28 +137,36 @@ cubeb_init(cubeb ** context, char const 
     } else if (!strcmp(backend_name, "alsa")) {
 #if defined(USE_ALSA)
       init_oneshot = alsa_init;
 #endif
     } else if (!strcmp(backend_name, "audiounit")) {
 #if defined(USE_AUDIOUNIT)
       init_oneshot = audiounit_init;
 #endif
+    } else if (!strcmp(backend_name, "audiounit-rust")) {
+#if defined(USE_AUDIOUNIT_RUST)
+      init_oneshot = audiounit_rust_init;
+#endif
     } else if (!strcmp(backend_name, "wasapi")) {
 #if defined(USE_WASAPI)
       init_oneshot = wasapi_init;
 #endif
     } else if (!strcmp(backend_name, "winmm")) {
 #if defined(USE_WINMM)
       init_oneshot = winmm_init;
 #endif
     } else if (!strcmp(backend_name, "sndio")) {
 #if defined(USE_SNDIO)
       init_oneshot = sndio_init;
 #endif
+    } else if (!strcmp(backend_name, "sun")) {
+#if defined(USE_SUN)
+      init_oneshot = sun_init;
+#endif
     } else if (!strcmp(backend_name, "opensl")) {
 #if defined(USE_OPENSL)
       init_oneshot = opensl_init;
 #endif
     } else if (!strcmp(backend_name, "audiotrack")) {
 #if defined(USE_AUDIOTRACK)
       init_oneshot = audiotrack_init;
 #endif
@@ -181,25 +195,31 @@ cubeb_init(cubeb ** context, char const 
     jack_init,
 #endif
 #if defined(USE_ALSA)
     alsa_init,
 #endif
 #if defined(USE_AUDIOUNIT)
     audiounit_init,
 #endif
+#if defined(USE_AUDIOUNIT_RUST)
+    audiounit_rust_init,
+#endif
 #if defined(USE_WASAPI)
     wasapi_init,
 #endif
 #if defined(USE_WINMM)
     winmm_init,
 #endif
 #if defined(USE_SNDIO)
     sndio_init,
 #endif
+#if defined(USE_SUN)
+    sun_init,
+#endif
 #if defined(USE_OPENSL)
     opensl_init,
 #endif
 #if defined(USE_AUDIOTRACK)
     audiotrack_init,
 #endif
 #if defined(USE_KAI)
     kai_init,
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/src/cubeb_sun.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright © 2019 Nia Alarie
+ *
+ * This program is made available under an ISC-style license.  See the
+ * accompanying file LICENSE for details.
+ */
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+
+#define BYTES_TO_FRAMES(bytes, channels) \
+  (bytes / (channels * sizeof(int16_t)))
+
+#define FRAMES_TO_BYTES(frames, channels) \
+  (frames * (channels * sizeof(int16_t)))
+
+/* Default to 4 + 1 for the default device. */
+#ifndef SUN_DEVICE_COUNT
+#define SUN_DEVICE_COUNT (5)
+#endif
+
+/* Supported well by most hardware. */
+#ifndef SUN_PREFER_RATE
+#define SUN_PREFER_RATE (48000)
+#endif
+
+/* Standard acceptable minimum. */
+#ifndef SUN_LATENCY_MS
+#define SUN_LATENCY_MS (40)
+#endif
+
+#ifndef SUN_DEFAULT_DEVICE
+#define SUN_DEFAULT_DEVICE "/dev/audio"
+#endif
+
+#ifndef SUN_POLL_TIMEOUT
+#define SUN_POLL_TIMEOUT (1000)
+#endif
+
+#ifndef SUN_BUFFER_FRAMES
+#define SUN_BUFFER_FRAMES (32)
+#endif
+
+/*
+ * Supported on NetBSD regardless of hardware.
+ */
+
+#ifndef SUN_MAX_CHANNELS
+# ifdef __NetBSD__
+#  define SUN_MAX_CHANNELS (12)
+# else
+#  define SUN_MAX_CHANNELS (2)
+# endif
+#endif
+
+#ifndef SUN_MIN_RATE
+#define SUN_MIN_RATE (1000)
+#endif
+
+#ifndef SUN_MAX_RATE
+#define SUN_MAX_RATE (192000)
+#endif
+
+static struct cubeb_ops const sun_ops;
+
+struct cubeb {
+  struct cubeb_ops const * ops;
+};
+
+struct cubeb_stream {
+  struct cubeb * context;
+  void * user_ptr;
+  pthread_t thread;
+  pthread_mutex_t mutex; /* protects running, volume, frames_written */
+  int floating;
+  int running;
+  int play_fd;
+  int record_fd;
+  float volume;
+  struct audio_info p_info; /* info for the play fd */
+  struct audio_info r_info; /* info for the record fd */
+  cubeb_data_callback data_cb;
+  cubeb_state_callback state_cb;
+  int16_t * play_buf;
+  int16_t * record_buf;
+  float * f_play_buf;
+  float * f_record_buf;
+  char input_name[32];
+  char output_name[32];
+  uint64_t frames_written;
+  uint64_t blocks_written;
+};
+
+int
+sun_init(cubeb ** context, char const * context_name)
+{
+  cubeb * c;
+
+  (void)context_name;
+  if ((c = calloc(1, sizeof(cubeb))) == NULL) {
+    return CUBEB_ERROR;
+  }
+  c->ops = &sun_ops;
+  *context = c;
+  return CUBEB_OK;
+}
+
+static void
+sun_destroy(cubeb * context)
+{
+  free(context);
+}
+
+static char const *
+sun_get_backend_id(cubeb * context)
+{
+  return "sun";
+}
+
+static int
+sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
+{
+  (void)context;
+
+  *rate = SUN_PREFER_RATE;
+  return CUBEB_OK;
+}
+
+static int
+sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+  (void)context;
+
+  *max_channels = SUN_MAX_CHANNELS;
+  return CUBEB_OK;
+}
+
+static int
+sun_get_min_latency(cubeb * context, cubeb_stream_params params,
+                    uint32_t * latency_frames)
+{
+  (void)context;
+
+  *latency_frames = SUN_LATENCY_MS * params.rate / 1000;
+  return CUBEB_OK;
+}
+
+static int
+sun_get_hwinfo(const char * device, struct audio_info * format,
+               int * props, struct audio_device * dev)
+{
+  int fd = -1;
+
+  if ((fd = open(device, O_RDONLY)) == -1) {
+    goto error;
+  }
+#ifdef AUDIO_GETFORMAT
+  if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
+    goto error;
+  }
+#endif
+#ifdef AUDIO_GETPROPS
+  if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
+    goto error;
+  }
+#endif
+  if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
+    goto error;
+  }
+  close(fd);
+  return CUBEB_OK;
+error:
+  if (fd != -1) {
+    close(fd);
+  }
+  return CUBEB_ERROR;
+}
+
+/*
+ * XXX: PR kern/54264
+ */
+static int
+sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
+{
+   return prinfo->precision >= 8 && prinfo->precision <= 32 &&
+     prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
+     prinfo->sample_rate < SUN_MAX_RATE && prinfo->sample_rate > SUN_MIN_RATE;
+}
+
+static int
+sun_enumerate_devices(cubeb * context, cubeb_device_type type,
+                      cubeb_device_collection * collection)
+{
+  unsigned i;
+  cubeb_device_info device = {0};
+  char dev[16] = SUN_DEFAULT_DEVICE;
+  char dev_friendly[64];
+  struct audio_info hwfmt;
+  struct audio_device hwname;
+  struct audio_prinfo *prinfo = NULL;
+  int hwprops;
+
+  collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
+  if (collection->device == NULL) {
+    return CUBEB_ERROR;
+  }
+  collection->count = 0;
+
+  for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
+    if (i > 0) {
+      (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
+    }
+    if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
+      continue;
+    }
+#ifdef AUDIO_GETPROPS
+    device.type = 0;
+    if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
+        sun_prinfo_verify_sanity(&hwfmt.record)) {
+      /* the device supports recording, probably */
+      device.type |= CUBEB_DEVICE_TYPE_INPUT;
+    }
+    if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
+        sun_prinfo_verify_sanity(&hwfmt.play)) {
+      /* the device supports playback, probably */
+      device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+    }
+    switch (device.type) {
+    case 0:
+      /* device doesn't do input or output, aliens probably involved */
+      continue;
+    case CUBEB_DEVICE_TYPE_INPUT:
+      if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
+        /* this device is input only, not scanning for those, skip it */
+        continue;
+      }
+      break;
+    case CUBEB_DEVICE_TYPE_OUTPUT:
+      if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
+        /* this device is output only, not scanning for those, skip it */
+        continue;
+      }
+      break;
+    }
+    if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
+      prinfo = &hwfmt.record;
+    }
+    if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
+      prinfo = &hwfmt.play;
+    }
+#endif
+    if (i > 0) {
+      (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
+                     hwname.name, hwname.version, hwname.config, i - 1);
+    } else {
+      (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
+                     hwname.name, hwname.version, hwname.config);
+    }
+    device.devid = (void *)(uintptr_t)i;
+    device.device_id = strdup(dev);
+    device.friendly_name = strdup(dev_friendly);
+    device.group_id = strdup(dev);
+    device.vendor_name = strdup(hwname.name);
+    device.type = type;
+    device.state = CUBEB_DEVICE_STATE_ENABLED;
+    device.preferred = (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+#ifdef AUDIO_GETFORMAT
+    device.max_channels = prinfo->channels;
+    device.default_rate = prinfo->sample_rate;
+#else
+    device.max_channels = 2;
+    device.default_rate = SUN_PREFER_RATE;
+#endif
+    device.default_format = CUBEB_DEVICE_FMT_S16NE;
+    device.format = CUBEB_DEVICE_FMT_S16NE;
+    device.min_rate = SUN_MIN_RATE;
+    device.max_rate = SUN_MAX_RATE;
+    device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
+    device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
+    collection->device[collection->count++] = device;
+  }
+  return CUBEB_OK;
+}
+
+static int
+sun_device_collection_destroy(cubeb * context,
+                              cubeb_device_collection * collection)
+{
+  unsigned i;
+
+  for (i = 0; i < collection->count; ++i) {
+    free((char *)collection->device[i].device_id);
+    free((char *)collection->device[i].friendly_name);
+    free((char *)collection->device[i].group_id);
+    free((char *)collection->device[i].vendor_name);
+  }
+  free(collection->device);
+  return CUBEB_OK;
+}
+
+static int
+sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
+                struct audio_info * info, struct audio_prinfo * prinfo)
+{
+  prinfo->channels = params->channels;
+  prinfo->sample_rate = params->rate;
+  prinfo->precision = 16;
+#ifdef AUDIO_ENCODING_SLINEAR_LE
+  switch (params->format) {
+  case CUBEB_SAMPLE_S16LE:
+    prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
+    break;
+  case CUBEB_SAMPLE_S16BE:
+    prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
+    break;
+  case CUBEB_SAMPLE_FLOAT32NE:
+    stream->floating = 1;
+    prinfo->encoding = AUDIO_ENCODING_SLINEAR;
+    break;
+  default:
+    LOG("Unsupported format");
+    return CUBEB_ERROR_INVALID_FORMAT;
+  }
+#else
+  switch (params->format) {
+  case CUBEB_SAMPLE_S16NE:
+    prinfo->encoding = AUDIO_ENCODING_LINEAR;
+    break;
+  case CUBEB_SAMPLE_FLOAT32NE:
+    stream->floating = 1;
+    prinfo->encoding = AUDIO_ENCODING_LINEAR;
+    break;
+  default:
+    LOG("Unsupported format");
+    return CUBEB_ERROR_INVALID_FORMAT;
+  }
+#endif
+  if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
+    return CUBEB_ERROR;
+  }
+  if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
+    return CUBEB_ERROR;
+  }
+  return CUBEB_OK;
+}
+
+static int
+sun_stream_stop(cubeb_stream * s)
+{
+  pthread_mutex_lock(&s->mutex);
+  if (s->running) {
+    s->running = 0;
+    pthread_mutex_unlock(&s->mutex);
+    pthread_join(s->thread, NULL);
+  } else {
+    pthread_mutex_unlock(&s->mutex);
+  }
+  return CUBEB_OK;
+}
+
+static void
+sun_stream_destroy(cubeb_stream * s)
+{
+  pthread_mutex_destroy(&s->mutex);
+  sun_stream_stop(s);
+  if (s->play_fd != -1) {
+    close(s->play_fd);
+  }
+  if (s->record_fd != -1) {
+    close(s->record_fd);
+  }
+  free(s->f_play_buf);
+  free(s->f_record_buf);
+  free(s->play_buf);
+  free(s->record_buf);
+  free(s);
+}
+
+static void
+sun_float_to_linear(float * in, int16_t * out,
+                    unsigned channels, long frames, float vol)
+{
+  unsigned i, sample_count = frames * channels;
+  float multiplier = vol * 0x8000;
+
+  for (i = 0; i < sample_count; ++i) {
+    int32_t sample = lrintf(in[i] * multiplier);
+    if (sample < -0x8000) {
+      out[i] = -0x8000;
+    } else if (sample > 0x7fff) {
+      out[i] = 0x7fff;
+    } else {
+      out[i] = sample;
+    }
+  }
+}
+
+static void
+sun_linear_to_float(int16_t * in, float * out,
+                    unsigned channels, long frames)
+{
+  unsigned i, sample_count = frames * channels;
+
+  for (i = 0; i < sample_count; ++i) {
+    out[i] = (1.0 / 0x8000) * in[i];
+  }
+}
+
+static void
+sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol)
+{
+  unsigned i, sample_count = frames * channels;
+  int32_t multiplier = vol * 0x8000;
+
+  for (i = 0; i < sample_count; ++i) {
+    buf[i] = (buf[i] * multiplier) >> 15;
+  }
+}
+
+static void *
+sun_io_routine(void * arg)
+{
+  cubeb_stream *s = arg;
+  cubeb_state state = CUBEB_STATE_STARTED;
+  size_t to_read = 0;
+  long to_write = 0;
+  size_t write_ofs = 0;
+  size_t read_ofs = 0;
+  int drain = 0;
+
+  s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
+  while (state != CUBEB_STATE_ERROR) {
+    pthread_mutex_lock(&s->mutex);
+    if (!s->running) {
+      pthread_mutex_unlock(&s->mutex);
+      state = CUBEB_STATE_STOPPED;
+      break;
+    }
+    pthread_mutex_unlock(&s->mutex);
+    if (s->floating) {
+      if (s->record_fd != -1) {
+        sun_linear_to_float(s->record_buf, s->f_record_buf,
+                            s->r_info.record.channels, SUN_BUFFER_FRAMES);
+      }
+      to_write = s->data_cb(s, s->user_ptr,
+                            s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES);
+      if (to_write == CUBEB_ERROR) {
+        state = CUBEB_STATE_ERROR;
+        break;
+      }
+      if (s->play_fd != -1) {
+        pthread_mutex_lock(&s->mutex);
+        sun_float_to_linear(s->f_play_buf, s->play_buf,
+                            s->p_info.play.channels, to_write, s->volume);
+        pthread_mutex_unlock(&s->mutex);
+      }
+    } else {
+      to_write = s->data_cb(s, s->user_ptr,
+                            s->record_buf, s->play_buf, SUN_BUFFER_FRAMES);
+      if (to_write == CUBEB_ERROR) {
+        state = CUBEB_STATE_ERROR;
+        break;
+      }
+      if (s->play_fd != -1) {
+        pthread_mutex_lock(&s->mutex);
+        sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume);
+        pthread_mutex_unlock(&s->mutex);
+      }
+    }
+    if (to_write < SUN_BUFFER_FRAMES) {
+      drain = 1;
+    }
+    to_write = s->play_fd != -1 ? to_write : 0;
+    to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0;
+    write_ofs = 0;
+    read_ofs = 0;
+    while (to_write > 0 || to_read > 0) {
+      size_t bytes;
+      ssize_t n, frames;
+
+      if (to_write > 0) {
+        bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels);
+        if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) {
+          state = CUBEB_STATE_ERROR;
+          break;
+        }
+        frames = BYTES_TO_FRAMES(n, s->p_info.play.channels);
+        pthread_mutex_lock(&s->mutex);
+        s->frames_written += frames;
+        pthread_mutex_unlock(&s->mutex);
+        to_write -= frames;
+        write_ofs += frames;
+      }
+      if (to_read > 0) {
+        bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels);
+        if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) {
+          state = CUBEB_STATE_ERROR;
+          break;
+        }
+        frames = BYTES_TO_FRAMES(n, s->r_info.record.channels);
+        to_read -= frames;
+        read_ofs += frames;
+      }
+    }
+    if (drain && state != CUBEB_STATE_ERROR) {
+      state = CUBEB_STATE_DRAINED;
+      break;
+    }
+  }
+  s->state_cb(s, s->user_ptr, state);
+  return NULL;
+}
+
+static int
+sun_stream_init(cubeb * context,
+                cubeb_stream ** stream,
+                char const * stream_name,
+                cubeb_devid input_device,
+                cubeb_stream_params * input_stream_params,
+                cubeb_devid output_device,
+                cubeb_stream_params * output_stream_params,
+                unsigned latency_frames,
+                cubeb_data_callback data_callback,
+                cubeb_state_callback state_callback,
+                void * user_ptr)
+{
+  int ret = CUBEB_OK;
+  cubeb_stream *s = NULL;
+
+  (void)stream_name;
+  (void)latency_frames;
+  if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
+    ret = CUBEB_ERROR;
+    goto error;
+  }
+  s->record_fd = -1;
+  s->play_fd = -1;
+  if (input_device != 0) {
+    snprintf(s->input_name, sizeof(s->input_name),
+      "/dev/audio%zu", (uintptr_t)input_device - 1);
+  } else {
+    snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE);
+  }
+  if (output_device != 0) {
+    snprintf(s->output_name, sizeof(s->output_name),
+      "/dev/audio%zu", (uintptr_t)output_device - 1);
+  } else {
+    snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE);
+  }
+  if (input_stream_params != NULL) {
+    if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+      LOG("Loopback not supported");
+      ret = CUBEB_ERROR_NOT_SUPPORTED;
+      goto error;
+    }
+    if (s->record_fd == -1) {
+      if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) {
+        LOG("Audio device cannot be opened as read-only");
+        ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+        goto error;
+      }
+    }
+    AUDIO_INITINFO(&s->r_info);
+#ifdef AUMODE_RECORD
+    s->r_info.mode = AUMODE_RECORD;
+#endif
+    if ((ret = sun_copy_params(s->record_fd, s, input_stream_params,
+                               &s->r_info, &s->r_info.record)) != CUBEB_OK) {
+      LOG("Setting record params failed");
+      goto error;
+    }
+  }
+  if (output_stream_params != NULL) {
+    if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+      LOG("Loopback not supported");
+      ret = CUBEB_ERROR_NOT_SUPPORTED;
+      goto error;
+    }
+    if (s->play_fd == -1) {
+      if ((s->play_fd = open(s->output_name, O_WRONLY)) == -1) {
+        LOG("Audio device cannot be opened as write-only");
+        ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+        goto error;
+      }
+    }
+    AUDIO_INITINFO(&s->p_info);
+#ifdef AUMODE_PLAY
+    s->p_info.mode = AUMODE_PLAY;
+#endif
+    if ((ret = sun_copy_params(s->play_fd, s, output_stream_params,
+                               &s->p_info, &s->p_info.play)) != CUBEB_OK) {
+      LOG("Setting play params failed");
+      goto error;
+    }
+  }
+  s->context = context;
+  s->volume = 1.0;
+  s->state_cb = state_callback;
+  s->data_cb = data_callback;
+  s->user_ptr = user_ptr;
+  if (pthread_mutex_init(&s->mutex, NULL) != 0) {
+    LOG("Failed to create mutex");
+    goto error;
+  }
+  if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES,
+      s->p_info.play.channels * sizeof(int16_t))) == NULL) {
+    ret = CUBEB_ERROR;
+    goto error;
+  }
+  if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES,
+      s->r_info.record.channels * sizeof(int16_t))) == NULL) {
+    ret = CUBEB_ERROR;
+    goto error;
+  }
+  if (s->floating) {
+    if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES,
+        s->p_info.play.channels * sizeof(float))) == NULL) {
+      ret = CUBEB_ERROR;
+      goto error;
+    }
+    if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES,
+        s->r_info.record.channels * sizeof(float))) == NULL) {
+      ret = CUBEB_ERROR;
+      goto error;
+    }
+  }
+  *stream = s;
+  return CUBEB_OK;
+error:
+  if (s != NULL) {
+    sun_stream_destroy(s);
+  }
+  return ret;
+}
+
+static int
+sun_stream_start(cubeb_stream * s)
+{
+  s->running = 1;
+  if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
+    LOG("Couldn't create thread");
+    return CUBEB_ERROR;
+  }
+  return CUBEB_OK;
+}
+
+static int
+sun_stream_get_position(cubeb_stream * s, uint64_t * position)
+{
+#ifdef AUDIO_GETOOFFS
+  struct audio_offset offset;
+
+  if (ioctl(s->play_fd, AUDIO_GETOOFFS, &offset) == -1) {
+    return CUBEB_ERROR;
+  }
+  s->blocks_written += offset.deltablks;
+  *position = BYTES_TO_FRAMES(s->blocks_written * s->p_info.blocksize,
+                              s->p_info.play.channels);
+  return CUBEB_OK;
+#else
+  pthread_mutex_lock(&s->mutex);
+  *position = s->frames_written;
+  pthread_mutex_unlock(&s->mutex);
+  return CUBEB_OK;
+#endif
+}
+
+static int
+sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
+{
+#ifdef AUDIO_GETBUFINFO
+  struct audio_info info;
+
+  if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) {
+    return CUBEB_ERROR;
+  }
+
+  *latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize,
+                             info.play.channels);
+  return CUBEB_OK;
+#else
+  cubeb_stream_params params;
+
+  params.rate = stream->p_info.play.sample_rate;
+
+  return sun_get_min_latency(NULL, params, latency);
+#endif
+}
+
+static int
+sun_stream_set_volume(cubeb_stream * stream, float volume)
+{
+  pthread_mutex_lock(&stream->mutex);
+  stream->volume = volume;
+  pthread_mutex_unlock(&stream->mutex);
+  return CUBEB_OK;
+}
+
+static int
+sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
+{
+  *device = calloc(1, sizeof(cubeb_device));
+  if (*device == NULL) {
+    return CUBEB_ERROR;
+  }
+  (*device)->input_name = stream->record_fd != -1 ?
+    strdup(stream->input_name) : NULL;
+  (*device)->output_name = stream->play_fd != -1 ?
+    strdup(stream->output_name) : NULL;
+  return CUBEB_OK;
+}
+
+static int
+sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
+{
+  (void)stream;
+  free(device->input_name);
+  free(device->output_name);
+  free(device);
+  return CUBEB_OK;
+}
+
+static struct cubeb_ops const sun_ops = {
+  .init = sun_init,
+  .get_backend_id = sun_get_backend_id,
+  .get_max_channel_count = sun_get_max_channel_count,
+  .get_min_latency = sun_get_min_latency,
+  .get_preferred_sample_rate = sun_get_preferred_sample_rate,
+  .enumerate_devices = sun_enumerate_devices,
+  .device_collection_destroy = sun_device_collection_destroy,
+  .destroy = sun_destroy,
+  .stream_init = sun_stream_init,
+  .stream_destroy = sun_stream_destroy,
+  .stream_start = sun_stream_start,
+  .stream_stop = sun_stream_stop,
+  .stream_reset_default_device = NULL,
+  .stream_get_position = sun_stream_get_position,
+  .stream_get_latency = sun_stream_get_latency,
+  .stream_set_volume = sun_stream_set_volume,
+  .stream_set_panning = NULL,
+  .stream_get_current_device = sun_get_current_device,
+  .stream_device_destroy = sun_stream_device_destroy,
+  .stream_register_device_changed_callback = NULL,
+  .register_device_collection_changed = NULL
+};
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -2460,17 +2460,17 @@ utf8_to_wstr(char const * str)
 {
   int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
   if (size <= 0) {
     return nullptr;
   }
 
   std::unique_ptr<wchar_t []> ret(new wchar_t[size]);
   ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
-  return std::move(ret);
+  return ret;
 }
 
 static com_ptr<IMMDevice>
 wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
 {
   com_ptr<IMMDevice> ret;
   com_ptr<IDeviceTopology> devtopo;
   com_ptr<IConnector> connector;
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -169,16 +169,26 @@ class LoadInfo final : public nsILoadInf
   void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow);
 
   // This function is the *only* function which can change the securityflags
   // of a loadinfo. It only exists because of the XHR code. Don't call it
   // from anywhere else!
   void SetIncludeCookiesSecFlag();
   friend class mozilla::dom::XMLHttpRequestMainThread;
 
+  // nsDocShell::OpenInitializedChannel needs to update the loadInfo with
+  // the correct browsingContext.
+  friend class ::nsDocShell;
+  void UpdateBrowsingContextID(uint64_t aBrowsingContextID) {
+    mBrowsingContextID = aBrowsingContextID;
+  }
+  void UpdateFrameBrowsingContextID(uint64_t aFrameBrowsingContextID) {
+    mFrameBrowsingContextID = aFrameBrowsingContextID;
+  }
+
   // if you add a member, please also update the copy constructor and consider
   // if it should be merged from parent channel through
   // ParentLoadInfoForwarderArgs.
   nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
   nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
   nsCOMPtr<nsIPrincipal> mSandboxedLoadingPrincipal;
   nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -4055,14 +4055,23 @@ void HttpChannelChild::MaybeCallSynthesi
   mSynthesizedCallback->BodyComplete(mStatus);
   mSynthesizedCallback = nullptr;
 }
 
 nsresult HttpChannelChild::CrossProcessRedirectFinished(nsresult aStatus) {
   if (!CanSend()) {
     return NS_BINDING_FAILED;
   }
-  Unused << SendCrossProcessRedirectDone(aStatus);
+
+  // The loadInfo is updated in nsDocShell::OpenInitializedChannel to have the
+  // correct attributes (such as browsingContextID).
+  // We need to send it to the parent channel so the two match, which is done
+  nsCOMPtr<nsILoadInfo> loadInfo;
+  MOZ_ALWAYS_SUCCEEDS(GetLoadInfo(getter_AddRefs(loadInfo)));
+  Maybe<LoadInfoArgs> loadInfoArgs;
+  MOZ_ALWAYS_SUCCEEDS(
+      mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
+  Unused << SendCrossProcessRedirectDone(aStatus, loadInfoArgs);
   return NS_OK;
 }
 
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -1244,29 +1244,43 @@ static void FinishCrossProcessRedirect(n
     // the same time cancels the old channel.
     parentListener->OnRedirectResult(status == NS_OK);
   }
 
   channel->OnRedirectVerifyCallback(status);
 }
 
 mozilla::ipc::IPCResult HttpChannelParent::RecvCrossProcessRedirectDone(
-    const nsresult& aResult) {
+    const nsresult& aResult,
+    const mozilla::Maybe<LoadInfoArgs>& aLoadInfoArgs) {
   RefPtr<nsHttpChannel> chan = do_QueryObject(mChannel);
+  nsresult rv = NS_OK;
+  auto sendReply =
+      MakeScopeExit([&]() { FinishCrossProcessRedirect(chan, rv); });
+
+  nsCOMPtr<nsILoadInfo> newLoadInfo;
+  rv = LoadInfoArgsToLoadInfo(aLoadInfoArgs, getter_AddRefs(newLoadInfo));
+  if (NS_FAILED(rv)) {
+    return IPC_OK();
+  }
+
+  if (newLoadInfo) {
+    chan->SetLoadInfo(newLoadInfo);
+  }
+
   if (!mBgParent) {
+    sendReply.release();
     RefPtr<HttpChannelParent> self = this;
     WaitForBgParent()->Then(
         GetMainThreadSerialEventTarget(), __func__,
         [self, chan, aResult]() { FinishCrossProcessRedirect(chan, aResult); },
         [self, chan](const nsresult& aRejectionRv) {
           MOZ_ASSERT(NS_FAILED(aRejectionRv), "This should be an error code");
           FinishCrossProcessRedirect(chan, aRejectionRv);
         });
-  } else {
-    FinishCrossProcessRedirect(chan, aResult);
   }
 
   return IPC_OK();
 }
 
 void HttpChannelParent::ResponseSynthesized() {
   // Suspend now even though the FinishSynthesizeResponse runnable has
   // not executed.  We want to suspend after we get far enough to trigger
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -196,17 +196,18 @@ class HttpChannelParent final : public n
   virtual mozilla::ipc::IPCResult RecvMarkOfflineCacheEntryAsForeign() override;
   virtual mozilla::ipc::IPCResult RecvDivertOnDataAvailable(
       const nsCString& data, const uint64_t& offset,
       const uint32_t& count) override;
   virtual mozilla::ipc::IPCResult RecvDivertOnStopRequest(
       const nsresult& statusCode) override;
   virtual mozilla::ipc::IPCResult RecvDivertComplete() override;
   virtual mozilla::ipc::IPCResult RecvCrossProcessRedirectDone(
-      const nsresult& aResult) override;
+      const nsresult& aResult,
+      const mozilla::Maybe<LoadInfoArgs>& aLoadInfoArgs) override;
   virtual mozilla::ipc::IPCResult RecvRemoveCorsPreflightCacheEntry(
       const URIParams& uri,
       const mozilla::ipc::PrincipalInfo& requestingPrincipal) override;
   virtual mozilla::ipc::IPCResult RecvBytesRead(const int32_t& aCount) override;
   virtual mozilla::ipc::IPCResult RecvOpenOriginalCacheInputStream() override;
   virtual mozilla::ipc::IPCResult RecvOpenAltDataCacheInputStream(
       const nsCString& aType) override;
   virtual void ActorDestroy(ActorDestroyReason why) override;
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -47,17 +47,19 @@ parent:
                         ChildLoadInfoForwarderArgs loadInfoForwarder,
                         uint32_t loadFlags, nsIReferrerInfo referrerInfo,
                         URIParams? apiRedirectTo,
                         CorsPreflightArgs? corsPreflightArgs,
                         bool chooseAppcache);
 
   // Sent to the parent in order signal that the child side listeners have been
   // set up and the parent side of the channel can be opened.
-  async CrossProcessRedirectDone(nsresult result);
+  // The passed loadInfo needs to be set on the channel - since the channel
+  // moved to a new process it now has different properties.
+  async CrossProcessRedirectDone(nsresult result, LoadInfoArgs? loadInfo);
 
   // For document loads we keep this protocol open after child's
   // OnStopRequest, and send this msg (instead of __delete__) to allow
   // partial cleanup on parent.
   async DocumentChannelCleanup(bool clearCacheEntry);
 
   // This might have to be sync. If this fails we must fail the document load
   // to avoid endless loop.
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -248,17 +248,17 @@ partner-urls:
             default: null
             firefox:
                 by-release-type:
                     default: null
                     beta|release.*:
                         by-release-level:
                             production: 'git@github.com:mozilla-partners/repack-manifests.git'
                             staging: 'git@github.com:moz-releng-automation-stage/repack-manifests.git'
-                    esr60:
+                    esr(60|68):
                         by-release-level:
                             production: 'git@github.com:mozilla-partners/esr-repack-manifests.git'
                             staging: 'git@github.com:moz-releng-automation-stage/esr-repack-manifests.git'
     release-eme-free-repack:
         by-release-product:
             default: null
             firefox:
                 by-release-type:
--- a/taskcluster/ci/release-secondary-snap-push/kind.yml
+++ b/taskcluster/ci/release-secondary-snap-push/kind.yml
@@ -11,28 +11,28 @@ transforms:
 
 kind-dependencies:
     - release-snap-repackage
 
 job-defaults:
     description: Pushes (Ubuntu) Snaps onto Snap Store
     run-on-projects: []  # to make sure this never runs as part of CI
     shipping-phase: ship  # ship-rc phase
-    scopes:
-        by-project:
-            mozilla-release: ["project:releng:snapcraft:firefox:beta"]  # ship-rc pushes to beta
-            default: ["project:releng:snapcraft:firefox:mock"]
     treeherder:
         platform: linux64/opt
         kind: build
         tier: 2
     worker-type:
         by-release-level:
             production: scriptworker-prov-v1/pushsnap-v1
             staging: scriptworker-prov-v1/dep-pushsnap
     worker:
         implementation: push-snap
+        channel:
+            by-release-type:
+                release: "beta"
+                default: "mock"
 
 jobs:
     firefox-rc:
         shipping-product: firefox
         treeherder:
             symbol: Snap(push-beta)
--- a/taskcluster/ci/release-snap-push/kind.yml
+++ b/taskcluster/ci/release-snap-push/kind.yml
@@ -11,30 +11,31 @@ transforms:
 
 kind-dependencies:
     - release-snap-repackage
 
 job-defaults:
     description: Pushes (Ubuntu) Snaps onto Snap Store
     run-on-projects: []  # to make sure this never runs as part of CI
     shipping-phase: push
-    scopes:
-        by-project:
-            mozilla-beta: ["project:releng:snapcraft:firefox:beta"]
-            mozilla-release: ["project:releng:snapcraft:firefox:candidate"]
-            mozilla-esr60: ["project:releng:snapcraft:firefox:esr"]
-            default: ["project:releng:snapcraft:firefox:mock"]
     treeherder:
         platform: linux64/opt
         kind: build
         tier: 2
     worker-type:
         by-release-level:
             production: scriptworker-prov-v1/pushsnap-v1
             staging: scriptworker-prov-v1/dep-pushsnap
     worker:
         implementation: push-snap
+        channel:
+            by-release-type:
+                beta: beta
+                release: candidate
+                esr60: esr/stable
+                esr68: esr/candidate
+                default: mock
 
 jobs:
     firefox:
         shipping-product: firefox
         treeherder:
             symbol: Snap(push)
--- a/taskcluster/taskgraph/transforms/release_snap_push.py
+++ b/taskcluster/taskgraph/transforms/release_snap_push.py
@@ -5,29 +5,30 @@
 Transform the release-snap-push kind into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.task import task_description_schema
 from taskgraph.util.schema import optionally_keyed_by, resolve_keyed_by, Schema
+from taskgraph.util.scriptworker import add_scope_prefix
 
 from voluptuous import Optional, Required
 
 push_snap_description_schema = Schema({
     Required('name'): basestring,
     Required('job-from'): task_description_schema['job-from'],
     Required('dependencies'): task_description_schema['dependencies'],
     Required('description'): task_description_schema['description'],
     Required('treeherder'): task_description_schema['treeherder'],
     Required('run-on-projects'): task_description_schema['run-on-projects'],
     Required('worker-type'): optionally_keyed_by('release-level', basestring),
     Required('worker'): object,
-    Required('scopes'): optionally_keyed_by('project', [basestring]),
+    Optional('scopes'): [basestring],
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Required('shipping-product'): task_description_schema['shipping-product'],
     Optional('extra'): task_description_schema['extra'],
     Optional('attributes'): task_description_schema['attributes'],
 })
 
 transforms = TransformSequence()
 transforms.add_validate(push_snap_description_schema)
@@ -36,21 +37,31 @@ transforms.add_validate(push_snap_descri
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         if len(job['dependencies']) != 1:
             raise Exception('Exactly 1 dependency is required')
 
         job['worker']['upstream-artifacts'] = generate_upstream_artifacts(job['dependencies'])
 
-        resolve_keyed_by(job, 'scopes', item_name=job['name'], project=config.params['project'])
+        resolve_keyed_by(
+            job, 'worker.channel', item_name=job['name'],
+            **{'release-type': config.params['release_type']}
+        )
         resolve_keyed_by(
             job, 'worker-type', item_name=job['name'],
             **{'release-level': config.params.release_level()}
         )
+        if config.params.release_level() == 'production':
+            job.setdefault('scopes', []).append(
+                add_scope_prefix(
+                    config,
+                    "snapcraft:firefox:{}".format(job['worker']['channel'].split('/')[0]),
+                )
+            )
 
         yield job
 
 
 def generate_upstream_artifacts(dependencies):
     return [{
         'taskId': {'task-reference': '<{}>'.format(task_kind)},
         # TODO bug 1417960
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -1154,26 +1154,28 @@ def build_push_apk_payload(config, task,
         'google_play_track': worker['google-play-track'],
     }
 
     if worker.get('rollout-percentage', None):
         task_def['payload']['rollout_percentage'] = worker['rollout-percentage']
 
 
 @payload_builder('push-snap', schema={
+    Required('channel'): basestring,
     Required('upstream-artifacts'): [{
         Required('taskId'): taskref_or_string,
         Required('taskType'): basestring,
         Required('paths'): [basestring],
     }],
 })
 def build_push_snap_payload(config, task, task_def):
     worker = task['worker']
 
     task_def['payload'] = {
+        'channel': worker['channel'],
         'upstreamArtifacts':  worker['upstream-artifacts'],
     }
 
 
 @payload_builder('shipit-shipped', schema={
     Required('release-name'): basestring,
 })
 def build_ship_it_shipped_payload(config, task, task_def):
--- a/testing/mozharness/configs/merge_day/release_to_esr.py
+++ b/testing/mozharness/configs/merge_day/release_to_esr.py
@@ -1,19 +1,17 @@
 import os
 
 ABS_WORK_DIR = os.path.join(os.getcwd(), "build")
-NEW_ESR_REPO = "https://hg.mozilla.org/releases/mozilla-esr60"
+NEW_ESR_REPO = "https://hg.mozilla.org/releases/mozilla-esr68"
 
 config = {
     "log_name": "relese_to_esr",
     "version_files": [
-        {"file": "browser/config/version.txt", "suffix": ""},
-        {"file": "browser/config/version_display.txt", "suffix": ""},
-        {"file": "config/milestone.txt", "suffix": ""},
+        {"file": "browser/config/version_display.txt", "suffix": "esr"},
     ],
     "replacements": [
         # File, from, to
         ("browser/confvars.sh",
          "ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-release",
          "ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-esr"),
         ("browser/confvars.sh",
          "MAR_CHANNEL_ID=firefox-mozilla-release",
@@ -21,18 +19,19 @@ config = {
         ("build/mozconfig.common",
          "# Enable enforcing that add-ons are signed by the trusted root",
          "# Disable enforcing that add-ons are signed by the trusted root"),
         ("build/mozconfig.common",
          "MOZ_REQUIRE_SIGNING=${MOZ_REQUIRE_SIGNING-1}",
          "MOZ_REQUIRE_SIGNING=${MOZ_REQUIRE_SIGNING-0}"),
     ],
     "vcs_share_base": os.path.join(ABS_WORK_DIR, 'hg-shared'),
-    # "hg_share_base": None,
-    "from_repo_url": "https://hg.mozilla.org/releases/mozilla-release",
+    # Pull from ESR repo, since we have already branched it and have landed esr-specific patches on it
+    # We will need to manually merge mozilla-release into before runnning this.
+    "from_repo_url": NEW_ESR_REPO,
     "to_repo_url": NEW_ESR_REPO,
 
     "base_tag": "FIREFOX_ESR_%(major_version)s_BASE",
     "migration_behavior": "release_to_esr",
     "require_remove_locales": False,
     "requires_head_merge": False,
     "pull_all_branches": False,
 }
--- a/testing/mozharness/scripts/merge_day/gecko_migration.py
+++ b/testing/mozharness/scripts/merge_day/gecko_migration.py
@@ -385,16 +385,19 @@ class GeckoMigration(MercurialScript, Vi
             )
         self.touch_clobber_file(dirs['abs_to_dir'])
 
     def release_to_esr(self, *args, **kwargs):
         """ mozilla-release -> mozilla-esrNN behavior. """
         dirs = self.query_abs_dirs()
         self.apply_replacements()
         self.touch_clobber_file(dirs['abs_to_dir'])
+        next_esr_version = self.get_version(dirs['abs_to_dir'])[0]
+        self.bump_version(dirs['abs_to_dir'], next_esr_version, next_esr_version, "", "",
+                          use_config_suffix=True)
 
     def apply_replacements(self):
         dirs = self.query_abs_dirs()
         for f, from_, to in self.config["replacements"]:
             self.replace(os.path.join(dirs['abs_to_dir'], f), from_, to)
 
     def pull_from_repo(self, from_dir, to_dir, revision=None, branch=None):
         """ Pull from one repo to another. """
--- a/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
@@ -52,10 +52,11 @@ skip-if = verify
 [test_annotation_vs_TP.html]
 [test_fingerprinting.html]
 skip-if = toolkit == 'android' && debug && !is_fennec
 [test_fingerprinting_annotate.html]
 skip-if = toolkit == 'android' && debug && !is_fennec
 [test_cryptomining.html]
 [test_cryptomining_annotate.html]
 [test_socialtracking.html]
+skip-if = debug && (os == 'android') #Bug 1560736
 [test_socialtracking_annotate.html]
 skip-if = debug && (os == 'android') #Bug 1560736
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -1311,17 +1311,34 @@ void nsWindow::NativeMoveResizeWaylandPo
     menuAnchor = GDK_GRAVITY_NORTH_EAST;
   }
 
   GdkAnchorHints hints = GdkAnchorHints(GDK_ANCHOR_SLIDE | GDK_ANCHOR_FLIP);
   if (aSize) {
     hints = GdkAnchorHints(hints | GDK_ANCHOR_RESIZE);
   }
 
+  // A workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1986
+  // gdk_window_move_to_rect() does not reposition visible windows.
+  static auto sGtkWidgetIsVisible =
+      (gboolean(*)(GtkWidget*))dlsym(RTLD_DEFAULT, "gtk_widget_is_visible");
+
+  bool isWidgetVisible =
+      (sGtkWidgetIsVisible != nullptr) && sGtkWidgetIsVisible(mShell);
+  if (isWidgetVisible) {
+    HideWaylandWindow();
+  }
+
   sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints, 0, 0);
+
+  if (isWidgetVisible) {
+    // We show the popup with the same configuration so no need to call
+    // ConfigureWaylandPopupWindows() before gtk_widget_show().
+    gtk_widget_show(mShell);
+  }
 }
 
 void nsWindow::NativeMove() {
   GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
 
   LOG(("nsWindow::NativeMove [%p] %d %d\n", (void*)this, point.x, point.y));
 
   if (IsWaylandPopup()) {