merge mozilla-inbound. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Mon, 23 Oct 2017 23:50:37 +0200
changeset 387737 9056f2ee492fa481aa86146aba236c074628e9fd
parent 387736 ebde6678a101ad0dfc785eafde6da3471bb59042 (current diff)
parent 387617 baa55120263b9763e48dec6e63d94ea04e20583c (diff)
child 387738 336949264d203af00b3d58bfe4013f07ba29a24a
child 387803 d2edccc49998aa5ddc9bb242f7115b5ba3367f28
push id96487
push userarchaeopteryx@coole-files.de
push dateMon, 23 Oct 2017 21:55:46 +0000
treeherdermozilla-inbound@336949264d20 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
9056f2ee492f / 58.0a1 / 20171023220222 / files
nightly linux64
9056f2ee492f / 58.0a1 / 20171023220222 / files
nightly mac
9056f2ee492f / 58.0a1 / 20171023220222 / files
nightly win32
9056f2ee492f / 58.0a1 / 20171023220222 / files
nightly win64
9056f2ee492f / 58.0a1 / 20171023220222 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound. r=merge a=merge MozReview-Commit-ID: B09kHrHK42C
layout/base/PresShell.cpp
toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js
toolkit/components/places/tests/unit/test_autocomplete_stopSearch_no_throw.js
toolkit/components/places/tests/unit/test_history_autocomplete_tags.js
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -258,19 +258,26 @@ case "$target" in
     android_tools="$android_sdk_root"/tools
     AC_MSG_CHECKING([for Android tools])
     if test -d "$android_tools" -a -f "$android_tools/emulator"; then
         AC_MSG_RESULT([$android_tools])
     else
         AC_MSG_ERROR([You must install the Android tools.  Try |mach bootstrap|.  (Looked for $android_tools)])
     fi
 
-    MOZ_PATH_PROG(EMULATOR, emulator, :, [$android_tools])
+    dnl Android Tools 26 changes emulator path.
+    dnl Although android_sdk_root/tools still has emulator command,
+    dnl it doesn't work correctly
+    MOZ_PATH_PROG(EMULATOR, emulator, :, [$android_sdk_root/emulator])
     if test -z "$EMULATOR" -o "$EMULATOR" = ":"; then
-      AC_MSG_ERROR([The program emulator was not found.  Try |mach bootstrap|.])
+        dnl old emulator path until Android Tools 25.x
+        MOZ_PATH_PROG(EMULATOR, emulator, :, [$android_tools])
+        if test -z "$EMULATOR" -o "$EMULATOR" = ":"; then
+            AC_MSG_ERROR([The program emulator was not found.  Try |mach bootstrap|.])
+        fi
     fi
 
     # `compileSdkVersion ANDROID_COMPILE_SDK_VERSION` is Gradle-only,
     # so there's no associated configure check.
     ANDROID_COMPILE_SDK_VERSION=$1
     ANDROID_TARGET_SDK="${android_target_sdk}"
     ANDROID_SDK="${android_sdk}"
     ANDROID_SDK_ROOT="${android_sdk_root}"
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -595,23 +595,18 @@ nsFrameMessageManager::SendMessage(const
                                    JS::Handle<JS::Value> aJSON,
                                    JS::Handle<JS::Value> aObjects,
                                    nsIPrincipal* aPrincipal,
                                    JSContext* aCx,
                                    uint8_t aArgc,
                                    JS::MutableHandle<JS::Value> aRetval,
                                    bool aIsSync)
 {
-#ifdef MOZ_GECKO_PROFILER
-  if (profiler_is_active()) {
-    NS_LossyConvertUTF16toASCII messageNameCStr(aMessageName);
-    AUTO_PROFILER_LABEL_DYNAMIC("nsFrameMessageManager::SendMessage", EVENTS,
-                                messageNameCStr.get());
-  }
-#endif
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "nsFrameMessageManager::SendMessage", EVENTS, aMessageName);
 
   NS_ASSERTION(!IsGlobal(), "Should not call SendSyncMessage in chrome");
   NS_ASSERTION(!IsBroadcaster(), "Should not call SendSyncMessage in chrome");
   NS_ASSERTION(!mParentManager, "Should not have parent manager in content!");
 
   aRetval.setUndefined();
   NS_ENSURE_TRUE(mCallback, NS_ERROR_NOT_INITIALIZED);
 
@@ -1536,24 +1531,18 @@ nsMessageManagerScriptExecutor::Shutdown
     sScriptCacheCleaner = nullptr;
   }
 }
 
 void
 nsMessageManagerScriptExecutor::LoadScriptInternal(const nsAString& aURL,
                                                    bool aRunInGlobalScope)
 {
-#ifdef MOZ_GECKO_PROFILER
-  if (profiler_is_active()) {
-    NS_LossyConvertUTF16toASCII urlCStr(aURL);
-    AUTO_PROFILER_LABEL_DYNAMIC(
-      "nsMessageManagerScriptExecutor::LoadScriptInternal", OTHER,
-      urlCStr.get());
-  }
-#endif
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "nsMessageManagerScriptExecutor::LoadScriptInternal", OTHER, aURL);
 
   if (!mGlobal || !sCachedScripts) {
     return;
   }
 
   JS::RootingContext* rcx = RootingCx();
   JS::Rooted<JSScript*> script(rcx);
 
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1164,18 +1164,18 @@ FullGCTimerFired(nsITimer* aTimer, void*
 
 //static
 void
 nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason,
                                IsIncremental aIncremental,
                                IsShrinking aShrinking,
                                int64_t aSliceMillis)
 {
-  AUTO_PROFILER_LABEL_DYNAMIC("nsJSContext::GarbageCollectNow", GC,
-                              JS::gcreason::ExplainReason(aReason));
+  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("nsJSContext::GarbageCollectNow", GC,
+                                   JS::gcreason::ExplainReason(aReason));
 
   MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);
 
   KillGCTimer();
 
   // Reset sPendingLoadCount in case the timer that fired was a
   // timer we scheduled due to a normal GC timer firing while
   // documents were loading. If this happens we're waiting for a
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1268,20 +1268,18 @@ EventListenerManager::HandleEventInterna
 #ifdef MOZ_GECKO_PROFILER
             if (profiler_is_active()) {
               // Add a profiler label and a profiler marker for the actual
               // dispatch of the event.
               // This is a very hot code path, so we need to make sure not to
               // do this extra work when we're not profiling.
               nsAutoString typeStr;
               (*aDOMEvent)->GetType(typeStr);
-              NS_LossyConvertUTF16toASCII typeCStr(typeStr);
-              AUTO_PROFILER_LABEL_DYNAMIC(
-                "EventListenerManager::HandleEventInternal", EVENTS,
-                typeCStr.get());
+              AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+                "EventListenerManager::HandleEventInternal", EVENTS, typeStr);
               TimeStamp startTime = TimeStamp::Now();
 
               rv = HandleEventSubType(listener, *aDOMEvent, aCurrentTarget);
 
               TimeStamp endTime = TimeStamp::Now();
               uint16_t phase;
               (*aDOMEvent)->GetEventPhase(&phase);
               profiler_add_marker(
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2503,19 +2503,18 @@ ContentChild::RecvLoadProcessScript(cons
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvAsyncMessage(const nsString& aMsg,
                                InfallibleTArray<CpowEntry>&& aCpows,
                                const IPC::Principal& aPrincipal,
                                const ClonedMessageData& aData)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMsg);
-  AUTO_PROFILER_LABEL_DYNAMIC("ContentChild::RecvAsyncMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "ContentChild::RecvAsyncMessage", EVENTS, aMsg);
 
   CrossProcessCpowHolder cpows(this, aCpows);
   RefPtr<nsFrameMessageManager> cpm =
     nsFrameMessageManager::GetChildProcessManager();
   if (cpm) {
     StructuredCloneData data;
     ipc::UnpackClonedMessageDataForChild(aData, data);
     cpm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(cpm.get()),
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2404,19 +2404,18 @@ TabChild::RecvLoadRemoteScript(const nsS
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvAsyncMessage(const nsString& aMessage,
                            InfallibleTArray<CpowEntry>&& aCpows,
                            const IPC::Principal& aPrincipal,
                            const ClonedMessageData& aData)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMessage);
-  AUTO_PROFILER_LABEL_DYNAMIC("TabChild::RecvAsyncMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "TabChild::RecvAsyncMessage", EVENTS, aMessage);
 
   CrossProcessCpowHolder cpows(Manager(), aCpows);
   if (!mTabChildGlobal) {
     return IPC_OK();
   }
 
   // We should have a message manager if the global is alive, but it
   // seems sometimes we don't.  Assert in aurora/nightly, but don't
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1702,19 +1702,18 @@ TabParent::SendHandleTap(TapType aType,
 
 mozilla::ipc::IPCResult
 TabParent::RecvSyncMessage(const nsString& aMessage,
                            const ClonedMessageData& aData,
                            InfallibleTArray<CpowEntry>&& aCpows,
                            const IPC::Principal& aPrincipal,
                            nsTArray<StructuredCloneData>* aRetVal)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMessage);
-  AUTO_PROFILER_LABEL_DYNAMIC("TabParent::RecvSyncMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "TabParent::RecvSyncMessage", EVENTS, aMessage);
 
   StructuredCloneData data;
   ipc::UnpackClonedMessageDataForParent(aData, data);
 
   CrossProcessCpowHolder cpows(Manager(), aCpows);
   if (!ReceiveMessage(aMessage, true, &data, &cpows, aPrincipal, aRetVal)) {
     return IPC_FAIL_NO_REASON(this);
   }
@@ -1723,19 +1722,18 @@ TabParent::RecvSyncMessage(const nsStrin
 
 mozilla::ipc::IPCResult
 TabParent::RecvRpcMessage(const nsString& aMessage,
                           const ClonedMessageData& aData,
                           InfallibleTArray<CpowEntry>&& aCpows,
                           const IPC::Principal& aPrincipal,
                           nsTArray<StructuredCloneData>* aRetVal)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMessage);
-  AUTO_PROFILER_LABEL_DYNAMIC("TabParent::RecvRpcMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "TabParent::RecvRpcMessage", EVENTS, aMessage);
 
   StructuredCloneData data;
   ipc::UnpackClonedMessageDataForParent(aData, data);
 
   CrossProcessCpowHolder cpows(Manager(), aCpows);
   if (!ReceiveMessage(aMessage, true, &data, &cpows, aPrincipal, aRetVal)) {
     return IPC_FAIL_NO_REASON(this);
   }
@@ -1743,19 +1741,18 @@ TabParent::RecvRpcMessage(const nsString
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvAsyncMessage(const nsString& aMessage,
                             InfallibleTArray<CpowEntry>&& aCpows,
                             const IPC::Principal& aPrincipal,
                             const ClonedMessageData& aData)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMessage);
-  AUTO_PROFILER_LABEL_DYNAMIC("TabParent::RecvAsyncMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "TabParent::RecvAsyncMessage", EVENTS, aMessage);
 
   StructuredCloneData data;
   ipc::UnpackClonedMessageDataForParent(aData, data);
 
   CrossProcessCpowHolder cpows(Manager(), aCpows);
   if (!ReceiveMessage(aMessage, false, &data, &cpows, aPrincipal, nullptr)) {
     return IPC_FAIL_NO_REASON(this);
   }
--- a/dom/ipc/nsIContentChild.cpp
+++ b/dom/ipc/nsIContentChild.cpp
@@ -167,19 +167,18 @@ nsIContentChild::DeallocPFileDescriptorS
 }
 
 mozilla::ipc::IPCResult
 nsIContentChild::RecvAsyncMessage(const nsString& aMsg,
                                   InfallibleTArray<CpowEntry>&& aCpows,
                                   const IPC::Principal& aPrincipal,
                                   const ClonedMessageData& aData)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMsg);
-  AUTO_PROFILER_LABEL_DYNAMIC("nsIContentChild::RecvAsyncMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "nsIContentChild::RecvAsyncMessage", EVENTS, aMsg);
 
   CrossProcessCpowHolder cpows(this, aCpows);
   RefPtr<nsFrameMessageManager> cpm = nsFrameMessageManager::GetChildProcessManager();
   if (cpm) {
     ipc::StructuredCloneData data;
     ipc::UnpackClonedMessageDataForChild(aData, data);
 
     cpm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(cpm.get()), nullptr,
--- a/dom/ipc/nsIContentParent.cpp
+++ b/dom/ipc/nsIContentParent.cpp
@@ -242,19 +242,18 @@ nsIContentParent::DeallocPIPCBlobInputSt
 
 mozilla::ipc::IPCResult
 nsIContentParent::RecvSyncMessage(const nsString& aMsg,
                                   const ClonedMessageData& aData,
                                   InfallibleTArray<CpowEntry>&& aCpows,
                                   const IPC::Principal& aPrincipal,
                                   nsTArray<ipc::StructuredCloneData>* aRetvals)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMsg);
-  AUTO_PROFILER_LABEL_DYNAMIC("nsIContentParent::RecvSyncMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "nsIContentParent::RecvSyncMessage", EVENTS, aMsg);
 
   CrossProcessCpowHolder cpows(this, aCpows);
   RefPtr<nsFrameMessageManager> ppm = mMessageManager;
   if (ppm) {
     ipc::StructuredCloneData data;
     ipc::UnpackClonedMessageDataForParent(aData, data);
 
     ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()), nullptr,
@@ -265,19 +264,18 @@ nsIContentParent::RecvSyncMessage(const 
 
 mozilla::ipc::IPCResult
 nsIContentParent::RecvRpcMessage(const nsString& aMsg,
                                  const ClonedMessageData& aData,
                                  InfallibleTArray<CpowEntry>&& aCpows,
                                  const IPC::Principal& aPrincipal,
                                  nsTArray<ipc::StructuredCloneData>* aRetvals)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMsg);
-  AUTO_PROFILER_LABEL_DYNAMIC("nsIContentParent::RecvRpcMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "nsIContentParent::RecvRpcMessage", EVENTS, aMsg);
 
   CrossProcessCpowHolder cpows(this, aCpows);
   RefPtr<nsFrameMessageManager> ppm = mMessageManager;
   if (ppm) {
     ipc::StructuredCloneData data;
     ipc::UnpackClonedMessageDataForParent(aData, data);
 
     ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()), nullptr,
@@ -326,19 +324,18 @@ nsIContentParent::DeallocPParentToChildS
 }
 
 mozilla::ipc::IPCResult
 nsIContentParent::RecvAsyncMessage(const nsString& aMsg,
                                    InfallibleTArray<CpowEntry>&& aCpows,
                                    const IPC::Principal& aPrincipal,
                                    const ClonedMessageData& aData)
 {
-  NS_LossyConvertUTF16toASCII messageNameCStr(aMsg);
-  AUTO_PROFILER_LABEL_DYNAMIC("nsIContentParent::RecvAsyncMessage", EVENTS,
-                              messageNameCStr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+    "nsIContentParent::RecvAsyncMessage", EVENTS, aMsg);
 
   CrossProcessCpowHolder cpows(this, aCpows);
   RefPtr<nsFrameMessageManager> ppm = mMessageManager;
   if (ppm) {
     ipc::StructuredCloneData data;
     ipc::UnpackClonedMessageDataForParent(aData, data);
 
     ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()), nullptr,
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -27,21 +27,26 @@ support-files =
   ting-44.1k-1ch.wav
   ting-44.1k-2ch.wav
   ting-48k-1ch.wav
   ting-48k-2ch.wav
   sine-440-10s.opus
   webaudio.js
 
 [test_analyserNode.html]
+skip-if = !asan || toolkit != android
 [test_analyserScale.html]
+skip-if = !asan || toolkit != android
 [test_analyserNodeOutput.html]
+skip-if = !asan || toolkit != android
 [test_analyserNodePassThrough.html]
 [test_analyserNodeWithGain.html]
+skip-if = !asan || toolkit != android
 [test_analyserNodeMinimum.html]
+skip-if = !asan || toolkit != android
 [test_AudioBuffer.html]
 [test_audioBufferSourceNode.html]
 [test_audioBufferSourceNodeEnded.html]
 [test_audioBufferSourceNodeLazyLoopParam.html]
 [test_audioBufferSourceNodeLoop.html]
 [test_audioBufferSourceNodeLoopStartEnd.html]
 [test_audioBufferSourceNodeLoopStartEndSame.html]
 [test_audioBufferSourceNodeDetached.html]
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -314,36 +314,36 @@ DecodePool::AsyncRun(IDecodingTask* aTas
 }
 
 bool
 DecodePool::SyncRunIfPreferred(IDecodingTask* aTask, const nsCString& aURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aTask);
 
-  AUTO_PROFILER_LABEL_DYNAMIC("DecodePool::SyncRunIfPreferred", GRAPHICS,
-                              aURI.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "DecodePool::SyncRunIfPreferred", GRAPHICS, aURI);
 
   if (aTask->ShouldPreferSyncRun()) {
     aTask->Run();
     return true;
   }
 
   AsyncRun(aTask);
   return false;
 }
 
 void
 DecodePool::SyncRunIfPossible(IDecodingTask* aTask, const nsCString& aURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aTask);
 
-  AUTO_PROFILER_LABEL_DYNAMIC("DecodePool::SyncRunIfPossible", GRAPHICS,
-                              aURI.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "DecodePool::SyncRunIfPossible", GRAPHICS, aURI);
 
   aTask->Run();
 }
 
 already_AddRefed<nsIEventTarget>
 DecodePool::GetIOEventTarget()
 {
   MutexAutoLock threadPoolLock(mMutex);
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -364,18 +364,18 @@ mozJSComponentLoader::LoadModule(FileLoc
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     if (!mInitialized) {
         rv = ReallyInit();
         if (NS_FAILED(rv))
             return nullptr;
     }
 
-    AUTO_PROFILER_LABEL_DYNAMIC("mozJSComponentLoader::LoadModule", OTHER,
-                                spec.get());
+    AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+      "mozJSComponentLoader::LoadModule", OTHER, spec);
 
     ModuleEntry* mod;
     if (mModules.Get(spec, &mod))
         return mod;
 
     dom::AutoJSAPI jsapi;
     jsapi.Init();
     JSContext* cx = jsapi.cx();
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -636,20 +636,19 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
     JSAutoCompartment ac(cx, targetObj);
 
     nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID);
     if (!serv) {
         ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOSERVICE));
         return NS_OK;
     }
 
-    const nsCString& asciiUrl = NS_LossyConvertUTF16toASCII(url);
-    AUTO_PROFILER_LABEL_DYNAMIC(
-        "mozJSSubScriptLoader::DoLoadSubScriptWithOptions", OTHER,
-        asciiUrl.get());
+    NS_LossyConvertUTF16toASCII asciiUrl(url);
+    AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+        "mozJSSubScriptLoader::DoLoadSubScriptWithOptions", OTHER, asciiUrl);
 
     // Make sure to explicitly create the URI, since we'll need the
     // canonicalized spec.
     rv = NS_NewURI(getter_AddRefs(uri), asciiUrl.get(), nullptr, serv);
     if (NS_FAILED(rv)) {
         ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOURI));
         return NS_OK;
     }
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2308,21 +2308,18 @@ nsXPCComponents_Utils::Import(const nsAC
                               HandleValue targetObj,
                               JSContext* cx,
                               uint8_t optionalArgc,
                               MutableHandleValue retval)
 {
     RefPtr<mozJSComponentLoader> moduleloader = mozJSComponentLoader::Get();
     MOZ_ASSERT(moduleloader);
 
-#ifdef MOZ_GECKO_PROFILER
-    const nsCString& flatLocation = PromiseFlatCString(registryLocation);
-    AUTO_PROFILER_LABEL_DYNAMIC("nsXPCComponents_Utils::Import", OTHER,
-                                flatLocation.get());
-#endif
+    AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+      "nsXPCComponents_Utils::Import", OTHER, registryLocation);
 
     return moduleloader->Import(registryLocation, targetObj, cx, optionalArgc, retval);
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::IsModuleLoaded(const nsACString& registryLocation, bool* retval)
 {
     RefPtr<mozJSComponentLoader> moduleloader = mozJSComponentLoader::Get();
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -4090,18 +4090,18 @@ PresShell::DoFlushPendingNotifications(m
     // As far as the profiler is concerned, EnsurePresShellInitAndFrames and
     // Frames are the same
     "Style",
     "Style",
     "InterruptibleLayout",
     "Layout",
     "Display"
   };
-  AUTO_PROFILER_LABEL_DYNAMIC("PresShell::DoFlushPendingNotifications",
-                              GRAPHICS, flushTypeNames[flushType]);
+  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("PresShell::DoFlushPendingNotifications",
+                                   GRAPHICS, flushTypeNames[flushType]);
 #endif
 
 #ifdef ACCESSIBILITY
 #ifdef DEBUG
   nsAccessibilityService* accService = GetAccService();
   if (accService) {
     NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
                  "Flush during accessible tree update!");
@@ -6309,23 +6309,26 @@ PresShell::RecordShadowStyleChange(Shado
   mStyleSet->RecordShadowStyleChange(aShadowRoot);
 }
 
 void
 PresShell::Paint(nsView*         aViewToPaint,
                  const nsRegion& aDirtyRegion,
                  uint32_t        aFlags)
 {
+#ifdef MOZ_GECKO_PROFILER
   nsIURI* uri = mDocument->GetDocumentURI();
   nsIDocument* contentRoot = GetPrimaryContentDocument();
   if (contentRoot) {
     uri = contentRoot->GetDocumentURI();
   }
-  nsCString uriString = uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A");
-  AUTO_PROFILER_LABEL_DYNAMIC("PresShell::Paint", GRAPHICS, uriString.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "PresShell::Paint", GRAPHICS,
+    uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A"));
+#endif
 
   Maybe<js::AutoAssertNoContentJS> nojs;
 
   // On Android, Flash can call into content JS during painting, so we can't
   // assert there. However, we don't rely on this assertion on Android because
   // we don't paint while JS is running.
 #if !defined(MOZ_WIDGET_ANDROID)
   if (!(aFlags & nsIPresShell::PAINT_COMPOSITE)) {
@@ -8884,19 +8887,22 @@ PresShell::DoReflow(nsIFrame* target, bo
   }
 
   // Schedule a paint, but don't actually mark this frame as changed for
   // retained DL building purposes. If any child frames get moved, then
   // they will schedule paint again. We could probaby skip this, and just
   // schedule a similar paint when a frame is deleted.
   target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
 
+#ifdef MOZ_GECKO_PROFILER
   nsIURI* uri = mDocument->GetDocumentURI();
-  nsCString uriString = uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A");
-  AUTO_PROFILER_LABEL_DYNAMIC("PresShell::DoReflow", GRAPHICS, uriString.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "PresShell::DoReflow", GRAPHICS,
+    uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A"));
+#endif
 
   nsDocShell* docShell = static_cast<nsDocShell*>(GetPresContext()->GetDocShell());
   RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
   bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
 
   if (isTimelineRecording) {
     timelines->AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::START);
   }
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -111,18 +111,18 @@ RestyleTracker::DoProcessRestyles()
 #ifdef MOZ_GECKO_PROFILER
   nsAutoCString docURL("N/A");
   if (profiler_is_active()) {
     nsIURI *uri = Document()->GetDocumentURI();
     if (uri) {
       docURL = uri->GetSpecOrDefault();
     }
   }
-  AUTO_PROFILER_LABEL_DYNAMIC("RestyleTracker::DoProcessRestyles", CSS,
-                              docURL.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "RestyleTracker::DoProcessRestyles", CSS, docURL);
 #endif
 
   // Create a AnimationsWithDestroyedFrame during restyling process to
   // stop animations and transitions on elements that have no frame at the end
   // of the restyling process.
   RestyleManager::AnimationsWithDestroyedFrame
     animationsWithDestroyedFrame(mRestyleManager);
 
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -5983,18 +5983,18 @@ FrameLayerBuilder::PaintItems(nsTArray<C
   for (uint32_t i = 0; i < aItems.Length(); ++i) {
     ClippedDisplayItem* cdi = &aItems[i];
 
     nsRect paintRect = cdi->mItem->GetVisibleRect().Intersect(boundRect);
     if (paintRect.IsEmpty())
       continue;
 
 #ifdef MOZ_DUMP_PAINTING
-    AUTO_PROFILER_LABEL_DYNAMIC("FrameLayerBuilder::PaintItems", GRAPHICS,
-                                cdi->mItem->Name());
+    AUTO_PROFILER_LABEL_DYNAMIC_CSTR("FrameLayerBuilder::PaintItems", GRAPHICS,
+                                     cdi->mItem->Name());
 #else
     AUTO_PROFILER_LABEL("FrameLayerBuilder::PaintItems", GRAPHICS);
 #endif
 
     // If the new desired clip state is different from the current state,
     // update the clip.
     const DisplayItemClip* clip = &cdi->mItem->GetClip();
     if (clip->GetRoundedRectCount() > 0 &&
new file mode 100644
--- /dev/null
+++ b/netwerk/base/rust-helper/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "netwerk_helper"
+version = "0.0.1"
+authors = ["Jeff Hemphill <jthemphill@mozilla.com>"]
+
+[dependencies]
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
new file mode 100644
--- /dev/null
+++ b/netwerk/base/rust-helper/src/helper.h
@@ -0,0 +1,14 @@
+#ifndef RUST_NS_NET_HELPER
+#define RUST_NS_NET_HELPER
+
+#include "nsError.h"
+#include "nsString.h"
+
+extern "C" {
+
+nsresult
+rust_prepare_accept_languages(const nsACString* i_accept_languages,
+                              nsACString* o_accept_languages);
+}
+
+#endif // RUST_NS_NET_HELPER
new file mode 100644
--- /dev/null
+++ b/netwerk/base/rust-helper/src/lib.rs
@@ -0,0 +1,143 @@
+use std::ascii::AsciiExt;
+
+extern crate nserror;
+use self::nserror::*;
+
+extern crate nsstring;
+use self::nsstring::nsACString;
+
+/// HTTP leading whitespace, defined in netwerk/protocol/http/nsHttp.h
+static HTTP_LWS: &'static [u8] = &[' ' as u8, '\t' as u8];
+
+/// Trim leading whitespace, trailing whitespace, and quality-value
+/// from a token.
+fn trim_token(token: &[u8]) -> &[u8] {
+    // Trim left whitespace
+    let ltrim = token.iter()
+        .take_while(|c| HTTP_LWS.iter().any(|ws| &ws == c))
+        .count();
+
+    // Trim right whitespace
+    // remove "; q=..." if present
+    let rtrim = token[ltrim..]
+        .iter()
+        .take_while(|c| **c != (';' as u8) && HTTP_LWS.iter().all(|ws| ws != *c))
+        .count();
+
+    &token[ltrim..ltrim + rtrim]
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+/// Allocates an nsACString that contains a ISO 639 language list
+/// notated with HTTP "q" values for output with an HTTP Accept-Language
+/// header. Previous q values will be stripped because the order of
+/// the langs implies the q value. The q values are calculated by dividing
+/// 1.0 amongst the number of languages present.
+///
+/// Ex: passing: "en, ja"
+///     returns: "en,ja;q=0.5"
+///
+///     passing: "en, ja, fr_CA"
+///     returns: "en,ja;q=0.7,fr_CA;q=0.3"
+pub extern "C" fn rust_prepare_accept_languages<'a, 'b>(i_accept_languages: &'a nsACString,
+                                                        o_accept_languages: &'b mut nsACString)
+                                                        -> nsresult {
+    if i_accept_languages.is_empty() {
+        return NS_OK;
+    }
+
+    let make_tokens = || {
+        i_accept_languages.split(|c| *c == (',' as u8))
+            .map(|token| trim_token(token))
+            .filter(|token| token.len() != 0)
+    };
+
+    let n = make_tokens().count();
+
+    for (count_n, i_token) in make_tokens().enumerate() {
+
+        // delimiter if not first item
+        if count_n != 0 {
+            o_accept_languages.append(",");
+        }
+
+        let token_pos = o_accept_languages.len();
+        o_accept_languages.append(&i_token as &[u8]);
+
+        {
+            let o_token = o_accept_languages.to_mut();
+            canonicalize_language_tag(&mut o_token[token_pos..]);
+        }
+
+        // Divide the quality-values evenly among the languages.
+        let q = 1.0 - count_n as f32 / n as f32;
+
+        let u: u32 = ((q + 0.005) * 100.0) as u32;
+        // Only display q-value if less than 1.00.
+        if u < 100 {
+            // With a small number of languages, one decimal place is
+            // enough to prevent duplicate q-values.
+            // Also, trailing zeroes do not add any information, so
+            // they can be removed.
+            if n < 10 || u % 10 == 0 {
+                let u = (u + 5) / 10;
+                o_accept_languages.append(&format!(";q=0.{}", u));
+            } else {
+                // Values below 10 require zero padding.
+                o_accept_languages.append(&format!(";q=0.{:02}", u));
+            }
+        }
+    }
+
+    NS_OK
+}
+
+/// Defines a consistent capitalization for a given language string.
+///
+/// # Arguments
+/// * `token` - a narrow char slice describing a language.
+///
+/// Valid language tags are of the form
+/// "*", "fr", "en-US", "es-419", "az-Arab", "x-pig-latin", "man-Nkoo-GN"
+///
+/// Language tags are defined in the
+/// [rfc5646](https://tools.ietf.org/html/rfc5646) spec. According to
+/// the spec:
+///
+/// > At all times, language tags and their subtags, including private
+/// > use and extensions, are to be treated as case insensitive: there
+/// > exist conventions for the capitalization of some of the subtags,
+/// > but these MUST NOT be taken to carry meaning.
+///
+/// So why is this code even here? See bug 1108183, I guess.
+fn canonicalize_language_tag(token: &mut [u8]) {
+    for c in token.iter_mut() {
+        *c = AsciiExt::to_ascii_lowercase(c);
+    }
+
+    let sub_tags = token.split_mut(|c| *c == ('-' as u8));
+    for (i, mut sub_tag) in sub_tags.enumerate() {
+        if i == 0 {
+            // ISO 639-1 language code, like the "en" in "en-US"
+            continue;
+        }
+
+        match sub_tag.len() {
+            // Singleton tag, like "x" or "i". These signify a
+            // non-standard language, so we stop capitalizing after
+            // these.
+            1 => break,
+            // ISO 3166-1 Country code, like "US"
+            2 => {
+                sub_tag[0] = AsciiExt::to_ascii_uppercase(&sub_tag[0]);
+                sub_tag[1] = AsciiExt::to_ascii_uppercase(&sub_tag[1]);
+            },
+            // ISO 15924 script code, like "Nkoo"
+            4  => {
+                sub_tag[0] = AsciiExt::to_ascii_uppercase(&sub_tag[0]);
+            },
+            _ => {},
+        };
+    }
+}
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -4,16 +4,17 @@
  * 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/. */
 
 // HttpLog.h should generally be included first
 #include "HttpLog.h"
 
 #include "prsystem.h"
 
+#include "nsError.h"
 #include "nsHttp.h"
 #include "nsHttpHandler.h"
 #include "nsHttpChannel.h"
 #include "nsHttpAuthCache.h"
 #include "nsStandardURL.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMNavigator.h"
 #include "nsIMozNavigatorNetwork.h"
@@ -53,16 +54,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsSocketTransportService2.h"
 #include "nsIOService.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIXULRuntime.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsRFPService.h"
+#include "rust-helper/src/helper.h"
 
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 #include "mozilla/BasePrincipal.h"
 
@@ -1903,61 +1905,16 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     // Enable HTTP response timeout if TCP Keepalives are disabled.
     mResponseTimeoutEnabled = !mTCPKeepaliveShortLivedEnabled &&
                               !mTCPKeepaliveLongLivedEnabled;
 
 #undef PREF_CHANGED
 #undef MULTI_PREF_CHANGED
 }
 
-
-/**
- * Currently, only regularizes the case of subtags.
- */
-static void
-CanonicalizeLanguageTag(char *languageTag)
-{
-    char *s = languageTag;
-    while (*s != '\0') {
-        *s = nsCRT::ToLower(*s);
-        s++;
-    }
-
-    s = languageTag;
-    bool isFirst = true;
-    bool seenSingleton = false;
-    while (*s != '\0') {
-        char *subTagEnd = strchr(s, '-');
-        if (subTagEnd == nullptr) {
-            subTagEnd = strchr(s, '\0');
-        }
-
-        if (isFirst) {
-            isFirst = false;
-        } else if (seenSingleton) {
-            // Do nothing
-        } else {
-            size_t subTagLength = subTagEnd - s;
-            if (subTagLength == 1) {
-                seenSingleton = true;
-            } else if (subTagLength == 2) {
-                *s = nsCRT::ToUpper(*s);
-                *(s + 1) = nsCRT::ToUpper(*(s + 1));
-            } else if (subTagLength == 4) {
-                *s = nsCRT::ToUpper(*s);
-            }
-        }
-
-        s = subTagEnd;
-        if (*s != '\0') {
-            s++;
-        }
-    }
-}
-
 /**
  *  Allocates a C string into that contains a ISO 639 language list
  *  notated with HTTP "q" values for output with a HTTP Accept-Language
  *  header. Previous q values will be stripped because the order of
  *  the langs imply the q value. The q values are calculated by dividing
  *  1.0 amongst the number of languages present.
  *
  *  Ex: passing: "en, ja"
@@ -1967,88 +1924,19 @@ CanonicalizeLanguageTag(char *languageTa
  *      returns: "en,ja;q=0.7,fr_CA;q=0.3"
  */
 static nsresult
 PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLanguages)
 {
     if (!i_AcceptLanguages)
         return NS_OK;
 
-    uint32_t n, count_n, size, wrote;
-    double q, dec;
-    char *p, *p2, *token, *q_Accept, *o_Accept;
-    const char *comma;
-    int32_t available;
-
-    o_Accept = strdup(i_AcceptLanguages);
-    if (!o_Accept)
-        return NS_ERROR_OUT_OF_MEMORY;
-    for (p = o_Accept, n = size = 0; '\0' != *p; p++) {
-        if (*p == ',') n++;
-            size++;
-    }
-
-    available = size + ++n * 11 + 1;
-    q_Accept = new char[available];
-    if (!q_Accept) {
-        free(o_Accept);
-        return NS_ERROR_OUT_OF_MEMORY;
-    }
-    *q_Accept = '\0';
-    q = 1.0;
-    dec = q / (double) n;
-    count_n = 0;
-    p2 = q_Accept;
-    for (token = nsCRT::strtok(o_Accept, ",", &p);
-         token != nullptr;
-         token = nsCRT::strtok(p, ",", &p))
-    {
-        token = net_FindCharNotInSet(token, HTTP_LWS);
-        char* trim;
-        trim = net_FindCharInSet(token, ";" HTTP_LWS);
-        if (trim != nullptr)  // remove "; q=..." if present
-            *trim = '\0';
-
-        if (*token != '\0') {
-            CanonicalizeLanguageTag(token);
-
-            comma = count_n++ != 0 ? "," : ""; // delimiter if not first item
-            uint32_t u = QVAL_TO_UINT(q);
-
-            // Only display q-value if less than 1.00.
-            if (u < 100) {
-                const char *qval_str;
-
-                // With a small number of languages, one decimal place is enough to prevent duplicate q-values.
-                // Also, trailing zeroes do not add any information, so they can be removed.
-                if ((n < 10) || ((u % 10) == 0)) {
-                    u = (u + 5) / 10;
-                    qval_str = "%s%s;q=0.%u";
-                } else {
-                    // Values below 10 require zero padding.
-                    qval_str = "%s%s;q=0.%02u";
-                }
-
-                wrote = snprintf(p2, available, qval_str, comma, token, u);
-            } else {
-                wrote = snprintf(p2, available, "%s%s", comma, token);
-            }
-
-            q -= dec;
-            p2 += wrote;
-            available -= wrote;
-            MOZ_ASSERT(available > 0, "allocated string not long enough");
-        }
-    }
-    free(o_Accept);
-
-    o_AcceptLanguages.Assign((const char *) q_Accept);
-    delete [] q_Accept;
-
-    return NS_OK;
+    const nsAutoCString ns_accept_languages(i_AcceptLanguages);
+    return rust_prepare_accept_languages(&ns_accept_languages,
+                                         &o_AcceptLanguages);
 }
 
 nsresult
 nsHttpHandler::SetAcceptLanguages()
 {
     mAcceptLanguagesIsDirty = false;
 
     nsAutoCString acceptLanguages;
--- a/netwerk/test/unit/test_header_Accept-Language_case.js
+++ b/netwerk/test/unit/test_header_Accept-Language_case.js
@@ -17,16 +17,18 @@ function run_test() {
     ["zh-hant-hk",      "zh-Hant-HK"],
     ["ZH-HANT-HK",      "zh-Hant-HK"],
     ["en-us-x-priv",    "en-US-x-priv"],
     ["en-us-x-twain",   "en-US-x-twain"],
     ["de, en-US, en",   "de,en-US;q=0.7,en;q=0.3"],
     ["de,en-us,en",     "de,en-US;q=0.7,en;q=0.3"],
     ["en-US, en",       "en-US,en;q=0.5"],
     ["EN-US;q=0.2, EN", "en-US,en;q=0.5"],
+    ["en ;q=0.8, de  ", "en,de;q=0.5"],
+    [",en,",            "en"],
   ];
 
   for (let i = 0; i < testData.length; i++) {
     let acceptLangPref = testData[i][0];
     let expectedHeader = testData[i][1];
 
     intlPrefs.setCharPref("accept_languages", acceptLangPref);
     let acceptLangHeader = setupChannel(testpath).getRequestHeader("Accept-Language");
--- a/parser/html/javasrc/AttributeName.java
+++ b/parser/html/javasrc/AttributeName.java
@@ -286,30 +286,29 @@ public final class AttributeName
      * @param offset
      *            ignored
      * @param length
      *            length of data
      * @param checkNcName
      *            whether to check ncnameness
      * @return an <code>AttributeName</code> corresponding to the argument data
      */
-    @Inline static AttributeName nameByBuffer(@NoLength char[] buf, int offset,
-            int length
-            , Interner interner) {
+    @Inline static AttributeName nameByBuffer(@NoLength char[] buf,
+            int length, Interner interner) {
         // XXX deal with offset
         @Unsigned int hash = AttributeName.bufToHash(buf, length);
         int[] hashes;
         hashes = AttributeName.ATTRIBUTE_HASHES;
         int index = levelOrderBinarySearch(hashes, hash);
         if (index < 0) {
             return null;
         }
         AttributeName attributeName = AttributeName.ATTRIBUTE_NAMES[index];
         @Local String name = attributeName.getLocal(0);
-        if (!Portability.localEqualsBuffer(name, buf, offset, length)) {
+        if (!Portability.localEqualsBuffer(name, buf, length)) {
             return null;
         }
         return attributeName;
     }
 
     /**
      * This method has to return a unique positive integer for each well-known
      * lower-cased attribute name.
--- a/parser/html/javasrc/ElementName.java
+++ b/parser/html/javasrc/ElementName.java
@@ -153,27 +153,27 @@ public final class ElementName
                 return i;
             }
         }
 
         return -1;
     }
 
     @Inline static ElementName elementNameByBuffer(@NoLength char[] buf,
-            int offset, int length, Interner interner) {
+            int length, Interner interner) {
         @Unsigned int hash = ElementName.bufToHash(buf, length);
         int[] hashes;
         hashes = ElementName.ELEMENT_HASHES;
         int index = levelOrderBinarySearch(hashes, hash);
         if (index < 0) {
             return null;
         } else {
             ElementName elementName = ElementName.ELEMENT_NAMES[index];
             @Local String name = elementName.name;
-            if (!Portability.localEqualsBuffer(name, buf, offset, length)) {
+            if (!Portability.localEqualsBuffer(name, buf, length)) {
                 return null;
             }
             return elementName;
         }
     }
 
     /**
      * This method has to return a unique positive integer for each well-known
--- a/parser/html/javasrc/Portability.java
+++ b/parser/html/javasrc/Portability.java
@@ -30,18 +30,18 @@ import nu.validator.htmlparser.common.In
 public final class Portability {
 
     // Allocating methods
 
     /**
      * Allocates a new local name object. In C++, the refcount must be set up in such a way that
      * calling <code>releaseLocal</code> on the return value balances the refcount set by this method.
      */
-    public static @Local String newLocalNameFromBuffer(@NoLength char[] buf, int offset, int length, Interner interner) {
-        return new String(buf, offset, length).intern();
+    public static @Local String newLocalNameFromBuffer(@NoLength char[] buf, int length, Interner interner) {
+        return new String(buf, 0, length).intern();
     }
 
     public static String newStringFromBuffer(@NoLength char[] buf, int offset, int length
         // CPPONLY: , TreeBuilder treeBuilder, boolean maybeAtomize
     ) {
         return new String(buf, offset, length);
     }
 
@@ -73,22 +73,22 @@ public final class Portability {
     // Deallocation methods
 
     public static void releaseString(String str) {
         // No-op in Java
     }
 
     // Comparison methods
 
-    public static boolean localEqualsBuffer(@Local String local, @NoLength char[] buf, int offset, int length) {
+    public static boolean localEqualsBuffer(@Local String local, @NoLength char[] buf, int length) {
         if (local.length() != length) {
             return false;
         }
         for (int i = 0; i < length; i++) {
-            if (local.charAt(i) != buf[offset + i]) {
+            if (local.charAt(i) != buf[i]) {
                 return false;
             }
         }
         return true;
     }
 
     public static boolean lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(@Literal String lowerCaseLiteral,
             String string) {
--- a/parser/html/javasrc/Tokenizer.java
+++ b/parser/html/javasrc/Tokenizer.java
@@ -707,17 +707,17 @@ public class Tokenizer implements Locato
      */
     public void setStateAndEndTagExpectation(int specialTokenizerState,
             @Local String endTagExpectation) {
         this.stateSave = specialTokenizerState;
         if (specialTokenizerState == Tokenizer.DATA) {
             return;
         }
         @Auto char[] asArray = Portability.newCharArrayFromLocal(endTagExpectation);
-        this.endTagExpectation = ElementName.elementNameByBuffer(asArray, 0,
+        this.endTagExpectation = ElementName.elementNameByBuffer(asArray,
                 asArray.length, interner);
         assert this.endTagExpectation != null;
         endTagExpectationToArray();
     }
 
     /**
      * Sets the tokenizer state and the associated element name. This should
      * only ever used to put the tokenizer into one of the states that have
@@ -905,18 +905,17 @@ public class Tokenizer implements Locato
 
     /**
      * Returns the buffer as a local name. The return value is released in
      * emitDoctypeToken().
      *
      * @return the buffer as local name
      */
     private void strBufToDoctypeName() {
-        doctypeName = Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
-                interner);
+        doctypeName = Portability.newLocalNameFromBuffer(strBuf, strBufLen, interner);
         clearStrBufAfterUse();
     }
 
     /**
      * Emits the buffer as character tokens.
      *
      * @throws SAXException
      *             if the token handler threw
@@ -1111,30 +1110,29 @@ public class Tokenizer implements Locato
         SAXParseException spe = new SAXParseException(message, this);
         errorHandler.warning(spe);
     }
 
     private void strBufToElementNameString() {
         if (containsHyphen) {
             // We've got a custom element or annotation-xml.
             @Local String annotationName = ElementName.ANNOTATION_XML.getName();
-            if (Portability.localEqualsBuffer(annotationName, strBuf, 0, strBufLen)) {
+            if (Portability.localEqualsBuffer(annotationName, strBuf, strBufLen)) {
                 tagName = ElementName.ANNOTATION_XML;
             } else {
-                nonInternedTagName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
+                nonInternedTagName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, strBufLen,
                         interner)
                         // CPPONLY: , true
                         );
                 tagName = nonInternedTagName;
             }
         } else {
-            tagName = ElementName.elementNameByBuffer(strBuf, 0, strBufLen,
-                    interner);
+            tagName = ElementName.elementNameByBuffer(strBuf, strBufLen, interner);
             if (tagName == null) {
-                nonInternedTagName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
+                nonInternedTagName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, strBufLen,
                     interner)
                         // CPPONLY: , false
                         );
                 tagName = nonInternedTagName;
             }
         }
         containsHyphen = false;
         clearStrBufAfterUse();
@@ -1178,21 +1176,21 @@ public class Tokenizer implements Locato
         /*
          * The token handler may have called setStateAndEndTagExpectation
          * and changed stateSave since the start of this method.
          */
         return stateSave;
     }
 
     private void attributeNameComplete() throws SAXException {
-        attributeName = AttributeName.nameByBuffer(strBuf, 0, strBufLen, interner);
+        attributeName = AttributeName.nameByBuffer(strBuf, strBufLen, interner);
         if (attributeName == null) {
             // [NOCPP[
             attributeName = AttributeName.createAttributeName(
-                    Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
+                    Portability.newLocalNameFromBuffer(strBuf, strBufLen,
                             interner),
                     namePolicy != XmlViolationPolicy.ALLOW);
             // ]NOCPP]
             // CPPONLY:     nonInternedAttributeName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen, interner));
             // CPPONLY:     attributeName = nonInternedAttributeName;
         }
         clearStrBufAfterUse();
 
--- a/parser/html/nsHtml5AttributeName.h
+++ b/parser/html/nsHtml5AttributeName.h
@@ -88,31 +88,30 @@ class nsHtml5AttributeName
         } else {
           return i;
         }
       }
       return -1;
     }
 
     inline static nsHtml5AttributeName* nameByBuffer(char16_t* buf,
-                                                     int32_t offset,
                                                      int32_t length,
                                                      nsHtml5AtomTable* interner)
     {
       uint32_t hash = nsHtml5AttributeName::bufToHash(buf, length);
       jArray<int32_t, int32_t> hashes;
       hashes = nsHtml5AttributeName::ATTRIBUTE_HASHES;
       int32_t index = levelOrderBinarySearch(hashes, hash);
       if (index < 0) {
         return nullptr;
       }
       nsHtml5AttributeName* attributeName =
         nsHtml5AttributeName::ATTRIBUTE_NAMES[index];
       nsAtom* name = attributeName->getLocal(0);
-      if (!nsHtml5Portability::localEqualsBuffer(name, buf, offset, length)) {
+      if (!nsHtml5Portability::localEqualsBuffer(name, buf, length)) {
         return nullptr;
       }
       return attributeName;
     }
 
   private:
     inline static uint32_t bufToHash(char16_t* buf, int32_t length)
     {
--- a/parser/html/nsHtml5ElementName.h
+++ b/parser/html/nsHtml5ElementName.h
@@ -122,31 +122,30 @@ public:
         return i;
       }
     }
     return -1;
   }
 
   inline static nsHtml5ElementName* elementNameByBuffer(
     char16_t* buf,
-    int32_t offset,
     int32_t length,
     nsHtml5AtomTable* interner)
   {
     uint32_t hash = nsHtml5ElementName::bufToHash(buf, length);
     jArray<int32_t, int32_t> hashes;
     hashes = nsHtml5ElementName::ELEMENT_HASHES;
     int32_t index = levelOrderBinarySearch(hashes, hash);
     if (index < 0) {
       return nullptr;
     } else {
       nsHtml5ElementName* elementName =
         nsHtml5ElementName::ELEMENT_NAMES[index];
       nsAtom* name = elementName->name;
-      if (!nsHtml5Portability::localEqualsBuffer(name, buf, offset, length)) {
+      if (!nsHtml5Portability::localEqualsBuffer(name, buf, length)) {
         return nullptr;
       }
       return elementName;
     }
     }
 
   private:
     inline static uint32_t bufToHash(char16_t* buf, int32_t length)
--- a/parser/html/nsHtml5Portability.cpp
+++ b/parser/html/nsHtml5Portability.cpp
@@ -4,19 +4,18 @@
 
 #include "nsAtom.h"
 #include "nsString.h"
 #include "jArray.h"
 #include "nsHtml5Portability.h"
 #include "nsHtml5TreeBuilder.h"
 
 nsAtom*
-nsHtml5Portability::newLocalNameFromBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner)
+nsHtml5Portability::newLocalNameFromBuffer(char16_t* buf, int32_t length, nsHtml5AtomTable* interner)
 {
-  NS_ASSERTION(!offset, "The offset should always be zero here.");
   NS_ASSERTION(interner, "Didn't get an atom service.");
   return interner->GetAtom(nsDependentSubstring(buf, buf + length));
 }
 
 static bool
 ContainsWhiteSpace(mozilla::Span<char16_t> aSpan)
 {
   for (char16_t c : aSpan) {
@@ -92,19 +91,19 @@ nsHtml5Portability::newLocalFromLocal(ns
     nsAutoString str;
     local->ToString(str);
     local = interner->GetAtom(str);
   }
   return local;
 }
 
 bool
-nsHtml5Portability::localEqualsBuffer(nsAtom* local, char16_t* buf, int32_t offset, int32_t length)
+nsHtml5Portability::localEqualsBuffer(nsAtom* local, char16_t* buf, int32_t length)
 {
-  return local->Equals(buf + offset, length);
+  return local->Equals(buf, length);
 }
 
 bool
 nsHtml5Portability::lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(
   const char* lowerCaseLiteral,
   nsHtml5String string)
 {
   return string.LowerCaseStartsWithASCII(lowerCaseLiteral);
--- a/parser/html/nsHtml5Portability.h
+++ b/parser/html/nsHtml5Portability.h
@@ -52,30 +52,30 @@ class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
 
 
 class nsHtml5Portability
 {
   public:
-    static nsAtom* newLocalNameFromBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner);
+    static nsAtom* newLocalNameFromBuffer(char16_t* buf, int32_t length, nsHtml5AtomTable* interner);
     static nsHtml5String newStringFromBuffer(char16_t* buf,
                                              int32_t offset,
                                              int32_t length,
                                              nsHtml5TreeBuilder* treeBuilder,
                                              bool maybeAtomize);
     static nsHtml5String newEmptyString();
     static nsHtml5String newStringFromLiteral(const char* literal);
     static nsHtml5String newStringFromString(nsHtml5String string);
     static jArray<char16_t,int32_t> newCharArrayFromLocal(nsAtom* local);
     static jArray<char16_t, int32_t> newCharArrayFromString(
       nsHtml5String string);
     static nsAtom* newLocalFromLocal(nsAtom* local, nsHtml5AtomTable* interner);
-    static bool localEqualsBuffer(nsAtom* local, char16_t* buf, int32_t offset, int32_t length);
+    static bool localEqualsBuffer(nsAtom* local, char16_t* buf, int32_t length);
     static bool lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(
       const char* lowerCaseLiteral,
       nsHtml5String string);
     static bool lowerCaseLiteralEqualsIgnoreAsciiCaseString(
       const char* lowerCaseLiteral,
       nsHtml5String string);
     static bool literalEqualsString(const char* literal, nsHtml5String string);
     static bool stringEqualsString(nsHtml5String one, nsHtml5String other);
--- a/parser/html/nsHtml5Tokenizer.cpp
+++ b/parser/html/nsHtml5Tokenizer.cpp
@@ -133,17 +133,17 @@ nsHtml5Tokenizer::isViewingXmlSource()
 void 
 nsHtml5Tokenizer::setStateAndEndTagExpectation(int32_t specialTokenizerState, nsAtom* endTagExpectation)
 {
   this->stateSave = specialTokenizerState;
   if (specialTokenizerState == nsHtml5Tokenizer::DATA) {
     return;
   }
   autoJArray<char16_t,int32_t> asArray = nsHtml5Portability::newCharArrayFromLocal(endTagExpectation);
-  this->endTagExpectation = nsHtml5ElementName::elementNameByBuffer(asArray, 0, asArray.length, interner);
+  this->endTagExpectation = nsHtml5ElementName::elementNameByBuffer(asArray, asArray.length, interner);
   MOZ_ASSERT(!!this->endTagExpectation);
   endTagExpectationToArray();
 }
 
 void 
 nsHtml5Tokenizer::setStateAndEndTagExpectation(int32_t specialTokenizerState, nsHtml5ElementName* endTagExpectation)
 {
   this->stateSave = specialTokenizerState;
@@ -240,17 +240,17 @@ nsHtml5Tokenizer::strBufToString()
       attributeName == nsHtml5AttributeName::ATTR_CLASS);
   clearStrBufAfterUse();
   return str;
 }
 
 void 
 nsHtml5Tokenizer::strBufToDoctypeName()
 {
-  doctypeName = nsHtml5Portability::newLocalNameFromBuffer(strBuf, 0, strBufLen, interner);
+  doctypeName = nsHtml5Portability::newLocalNameFromBuffer(strBuf, strBufLen, interner);
   clearStrBufAfterUse();
 }
 
 void 
 nsHtml5Tokenizer::emitStrBuf()
 {
   if (strBufLen > 0) {
     tokenHandler->characters(strBuf, 0, strBufLen);
@@ -290,33 +290,31 @@ nsHtml5Tokenizer::flushChars(char16_t* b
 }
 
 void 
 nsHtml5Tokenizer::strBufToElementNameString()
 {
   if (containsHyphen) {
     nsAtom* annotationName = nsHtml5ElementName::ELT_ANNOTATION_XML->getName();
     if (nsHtml5Portability::localEqualsBuffer(
-          annotationName, strBuf, 0, strBufLen)) {
+          annotationName, strBuf, strBufLen)) {
       tagName = nsHtml5ElementName::ELT_ANNOTATION_XML;
     } else {
       nonInternedTagName->setNameForNonInterned(
         nsHtml5Portability::newLocalNameFromBuffer(
-          strBuf, 0, strBufLen, interner),
-        true);
+          strBuf, strBufLen, interner), true);
       tagName = nonInternedTagName;
     }
   } else {
     tagName =
-      nsHtml5ElementName::elementNameByBuffer(strBuf, 0, strBufLen, interner);
+      nsHtml5ElementName::elementNameByBuffer(strBuf, strBufLen, interner);
     if (!tagName) {
       nonInternedTagName->setNameForNonInterned(
         nsHtml5Portability::newLocalNameFromBuffer(
-          strBuf, 0, strBufLen, interner),
-        false);
+          strBuf, strBufLen, interner), false);
       tagName = nonInternedTagName;
     }
   }
   containsHyphen = false;
   clearStrBufAfterUse();
 }
 
 int32_t 
@@ -351,21 +349,20 @@ nsHtml5Tokenizer::emitCurrentTagToken(bo
     attributes->clear(0);
   }
   return stateSave;
 }
 
 void 
 nsHtml5Tokenizer::attributeNameComplete()
 {
-  attributeName = nsHtml5AttributeName::nameByBuffer(strBuf, 0, strBufLen, interner);
+  attributeName = nsHtml5AttributeName::nameByBuffer(strBuf, strBufLen, interner);
   if (!attributeName) {
     nonInternedAttributeName->setNameForNonInterned(
-      nsHtml5Portability::newLocalNameFromBuffer(
-        strBuf, 0, strBufLen, interner));
+      nsHtml5Portability::newLocalNameFromBuffer(strBuf, strBufLen, interner));
     attributeName = nonInternedAttributeName;
   }
   clearStrBufAfterUse();
   if (!attributes) {
     attributes = new nsHtml5HtmlAttributes(0);
   }
   if (attributes->contains(attributeName)) {
     errDuplicateAttribute();
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -380088,16 +380088,46 @@
     ]
    ],
    "web-share/share-without-user-gesture.https.html": [
     [
      "/web-share/share-without-user-gesture.https.html",
      {}
     ]
    ],
+   "webaudio/the-audio-api/the-analysernode-interface/test-analyser-gain.html": [
+    [
+     "/webaudio/the-audio-api/the-analysernode-interface/test-analyser-gain.html",
+     {}
+    ]
+   ],
+   "webaudio/the-audio-api/the-analysernode-interface/test-analyser-minimum.html": [
+    [
+     "/webaudio/the-audio-api/the-analysernode-interface/test-analyser-minimum.html",
+     {}
+    ]
+   ],
+   "webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html": [
+    [
+     "/webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html",
+     {}
+    ]
+   ],
+   "webaudio/the-audio-api/the-analysernode-interface/test-analyser-scale.html": [
+    [
+     "/webaudio/the-audio-api/the-analysernode-interface/test-analyser-scale.html",
+     {}
+    ]
+   ],
+   "webaudio/the-audio-api/the-analysernode-interface/test-analysernode.html": [
+    [
+     "/webaudio/the-audio-api/the-analysernode-interface/test-analysernode.html",
+     {}
+    ]
+   ],
    "webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html": [
     [
      "/webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html",
      {}
     ]
    ],
    "webaudio/the-audio-api/the-audiodestinationnode-interface/idl-test.html": [
     [
@@ -631574,17 +631604,17 @@
    "230684ec60fd2e408f9b6014417f3eddfe2dc95a",
    "support"
   ],
   "webaudio/js/buffer-loader.js": [
    "4d564eae0b3d7d1045626d1f144cd2638dba64e5",
    "support"
   ],
   "webaudio/js/helpers.js": [
-   "c5d44cf8101c50b59c366ed1971205193f32e1bf",
+   "dff18a7e57adb3847b70fa7f1f3752b591b38d6e",
    "support"
   ],
   "webaudio/js/lodash.js": [
    "58c3eae918fedad54c56d488a14f99ce87943500",
    "support"
   ],
   "webaudio/js/vendor-prefixes.js": [
    "3b7d06164273077415b7fc99652355bcf0e10e17",
@@ -631601,16 +631631,36 @@
   "webaudio/specification.html": [
    "1bea5d5d8983a3505328f6878bfe09c20c25aa8c",
    "support"
   ],
   "webaudio/the-audio-api/the-analysernode-interface/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
+  "webaudio/the-audio-api/the-analysernode-interface/test-analyser-gain.html": [
+   "c3f5f5969ed0ab58a9df332196e138aef8e693f3",
+   "testharness"
+  ],
+  "webaudio/the-audio-api/the-analysernode-interface/test-analyser-minimum.html": [
+   "cfbeb7283e7375974943ccf689cca73942e6259f",
+   "testharness"
+  ],
+  "webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html": [
+   "f27d081b9b3df8af7449f130a455b90c6e93ca7b",
+   "testharness"
+  ],
+  "webaudio/the-audio-api/the-analysernode-interface/test-analyser-scale.html": [
+   "1909a2970f0529ad0433c8e6e75733695d44d3e0",
+   "testharness"
+  ],
+  "webaudio/the-audio-api/the-analysernode-interface/test-analysernode.html": [
+   "8478aa405a4641a9c47554529762e85a37d7593a",
+   "testharness"
+  ],
   "webaudio/the-audio-api/the-audiobuffer-interface/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
   "webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html": [
    "11ec68dec57c960cea256c28237715866c58ac84",
    "testharness"
   ],
--- a/testing/web-platform/tests/webaudio/js/helpers.js
+++ b/testing/web-platform/tests/webaudio/js/helpers.js
@@ -16,8 +16,203 @@ function trimEmptyElements(array) {
   while (end > 0) {
     end--;
     if (array[end] !== 0) {
       break;
     }
   }
   return array.subarray(start, end);
 }
+
+
+function fuzzyCompare(a, b) {
+  return Math.abs(a - b) < 9e-3;
+}
+
+function compareChannels(buf1, buf2,
+                        /*optional*/ length,
+                        /*optional*/ sourceOffset,
+                        /*optional*/ destOffset,
+                        /*optional*/ skipLengthCheck) {
+  if (!skipLengthCheck) {
+    assert_equals(buf1.length, buf2.length, "Channels must have the same length");
+  }
+  sourceOffset = sourceOffset || 0;
+  destOffset = destOffset || 0;
+  if (length == undefined) {
+    length = buf1.length - sourceOffset;
+  }
+  var difference = 0;
+  var maxDifference = 0;
+  var firstBadIndex = -1;
+  for (var i = 0; i < length; ++i) {
+    if (!fuzzyCompare(buf1[i + sourceOffset], buf2[i + destOffset])) {
+      difference++;
+      maxDifference = Math.max(maxDifference, Math.abs(buf1[i + sourceOffset] - buf2[i + destOffset]));
+      if (firstBadIndex == -1) {
+        firstBadIndex = i;
+      }
+    }
+  };
+
+  assert_equals(difference, 0, "maxDifference: " + maxDifference +
+     ", first bad index: " + firstBadIndex + " with test-data offset " +
+     sourceOffset + " and expected-data offset " + destOffset +
+     "; corresponding values " + buf1[firstBadIndex + sourceOffset] + " and " +
+     buf2[firstBadIndex + destOffset] + " --- differences");
+}
+
+function compareBuffers(got, expected) {
+  if (got.numberOfChannels != expected.numberOfChannels) {
+    assert_equals(got.numberOfChannels, expected.numberOfChannels,
+                  "Correct number of buffer channels");
+    return;
+  }
+  if (got.length != expected.length) {
+    assert_equals(got.length, expected.length,
+                  "Correct buffer length");
+    return;
+  }
+  if (got.sampleRate != expected.sampleRate) {
+    assert_equals(got.sampleRate, expected.sampleRate,
+                  "Correct sample rate");
+    return;
+  }
+
+  for (var i = 0; i < got.numberOfChannels; ++i) {
+    compareChannels(got.getChannelData(i), expected.getChannelData(i),
+                    got.length, 0, 0, true);
+  }
+}
+
+/**
+ * This function assumes that the test is a "single page test" [0], and defines a
+ * single gTest variable with the following properties and methods:
+ *
+ * + numberOfChannels: optional property which specifies the number of channels
+ *                     in the output.  The default value is 2.
+ * + createGraph: mandatory method which takes a context object and does
+ *                everything needed in order to set up the Web Audio graph.
+ *                This function returns the node to be inspected.
+ * + createGraphAsync: async version of createGraph.  This function takes
+ *                     a callback which should be called with an argument
+ *                     set to the node to be inspected when the callee is
+ *                     ready to proceed with the test.  Either this function
+ *                     or createGraph must be provided.
+ * + createExpectedBuffers: optional method which takes a context object and
+ *                          returns either one expected buffer or an array of
+ *                          them, designating what is expected to be observed
+ *                          in the output.  If omitted, the output is expected
+ *                          to be silence.  All buffers must have the same
+ *                          length, which must be a bufferSize supported by
+ *                          ScriptProcessorNode.  This function is guaranteed
+ *                          to be called before createGraph.
+ * + length: property equal to the total number of frames which we are waiting
+ *           to see in the output, mandatory if createExpectedBuffers is not
+ *           provided, in which case it must be a bufferSize supported by
+ *           ScriptProcessorNode (256, 512, 1024, 2048, 4096, 8192, or 16384).
+ *           If createExpectedBuffers is provided then this must be equal to
+ *           the number of expected buffers * the expected buffer length.
+ *
+ * + skipOfflineContextTests: optional. when true, skips running tests on an offline
+ *                            context by circumventing testOnOfflineContext.
+ *
+ * [0]: http://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests
+ */
+function runTest(name)
+{
+  function runTestFunction () {
+    if (!gTest.numberOfChannels) {
+      gTest.numberOfChannels = 2; // default
+    }
+
+    var testLength;
+
+    function runTestOnContext(context, callback, testOutput) {
+      if (!gTest.createExpectedBuffers) {
+        // Assume that the output is silence
+        var expectedBuffers = getEmptyBuffer(context, gTest.length);
+      } else {
+        var expectedBuffers = gTest.createExpectedBuffers(context);
+      }
+      if (!(expectedBuffers instanceof Array)) {
+        expectedBuffers = [expectedBuffers];
+      }
+      var expectedFrames = 0;
+      for (var i = 0; i < expectedBuffers.length; ++i) {
+        assert_equals(expectedBuffers[i].numberOfChannels, gTest.numberOfChannels,
+                      "Correct number of channels for expected buffer " + i);
+        expectedFrames += expectedBuffers[i].length;
+      }
+      if (gTest.length && gTest.createExpectedBuffers) {
+        assert_equals(expectedFrames,
+                      gTest.length, "Correct number of expected frames");
+      }
+
+      if (gTest.createGraphAsync) {
+        gTest.createGraphAsync(context, function(nodeToInspect) {
+          testOutput(nodeToInspect, expectedBuffers, callback);
+        });
+      } else {
+        testOutput(gTest.createGraph(context), expectedBuffers, callback);
+      }
+    }
+
+    function testOnNormalContext(callback) {
+      function testOutput(nodeToInspect, expectedBuffers, callback) {
+        testLength = 0;
+        var sp = context.createScriptProcessor(expectedBuffers[0].length, gTest.numberOfChannels, 0);
+        nodeToInspect.connect(sp);
+        sp.onaudioprocess = function(e) {
+          var expectedBuffer = expectedBuffers.shift();
+          testLength += expectedBuffer.length;
+          compareBuffers(e.inputBuffer, expectedBuffer);
+          if (expectedBuffers.length == 0) {
+            sp.onaudioprocess = null;
+            callback();
+          }
+        };
+      }
+      var context = new AudioContext();
+      runTestOnContext(context, callback, testOutput);
+    }
+
+    function testOnOfflineContext(callback, sampleRate) {
+      function testOutput(nodeToInspect, expectedBuffers, callback) {
+        nodeToInspect.connect(context.destination);
+        context.oncomplete = function(e) {
+          var samplesSeen = 0;
+          while (expectedBuffers.length) {
+            var expectedBuffer = expectedBuffers.shift();
+            assert_equals(e.renderedBuffer.numberOfChannels, expectedBuffer.numberOfChannels,
+                          "Correct number of input buffer channels");
+            for (var i = 0; i < e.renderedBuffer.numberOfChannels; ++i) {
+              compareChannels(e.renderedBuffer.getChannelData(i),
+                             expectedBuffer.getChannelData(i),
+                             expectedBuffer.length,
+                             samplesSeen,
+                             undefined,
+                             true);
+            }
+            samplesSeen += expectedBuffer.length;
+          }
+          callback();
+        };
+        context.startRendering();
+      }
+
+      var context = new OfflineAudioContext(gTest.numberOfChannels, testLength, sampleRate);
+      runTestOnContext(context, callback, testOutput);
+    }
+
+    testOnNormalContext(function() {
+      if (!gTest.skipOfflineContextTests) {
+        testOnOfflineContext(function() {
+          testOnOfflineContext(done, 44100);
+        }, 48000);
+      } else {
+        done();
+      }
+    });
+  };
+
+  runTestFunction();
+}
copy from dom/media/webaudio/test/test_analyserNodeWithGain.html
copy to testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-gain.html
--- a/dom/media/webaudio/test/test_analyserNodeWithGain.html
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-gain.html
@@ -1,13 +1,15 @@
 <!DOCTYPE html>
-<title>Test effect of AnalyserNode on GainNode output</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
 promise_test(function() {
   // fftSize <= bufferSize so that the time domain data is full of input after
   // processing the buffer.
   const fftSize = 32;
   const bufferSize = 128;
 
   var context = new OfflineAudioContext(1, bufferSize, 48000);
 
@@ -27,21 +29,22 @@ promise_test(function() {
   var buffer = context.createBuffer(1, 1, context.sampleRate);
   buffer.getChannelData(0)[0] = 1.0 / gain.gain.value;
   var source = context.createBufferSource();
   source.buffer = buffer;
   source.loop = true;
   source.connect(gain);
   source.start();
 
-  return context.startRendering().
-    then(function(buffer) {
-      assert_equals(buffer.getChannelData(0)[0], 1.0,
-                    "analyser1 output");
+  return context.startRendering().then(function(buffer) {
+    assert_equals(buffer.getChannelData(0)[0], 1.0, "analyser1 output");
 
-      var data = new Float32Array(1);
-      analyser1.getFloatTimeDomainData(data);
-      assert_equals(data[0], 1.0, "analyser1 time domain data");
-      analyser2.getFloatTimeDomainData(data);
-      assert_equals(data[0], 1.0, "analyser2 time domain data");
-    });
-});
-</script>
+    var data = new Float32Array(1);
+    analyser1.getFloatTimeDomainData(data);
+    assert_equals(data[0], 1.0, "analyser1 time domain data");
+    analyser2.getFloatTimeDomainData(data);
+    assert_equals(data[0], 1.0, "analyser2 time domain data");
+  });
+}, "Test effect of AnalyserNode on GainNode output");
+  </script>
+</head>
+</body>
+</html>
copy from dom/media/webaudio/test/test_analyserNodeMinimum.html
copy to testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-minimum.html
--- a/dom/media/webaudio/test/test_analyserNodeMinimum.html
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-minimum.html
@@ -1,51 +1,42 @@
-<!DOCTYPE HTML>
+<!DOCTYPE html>
 <html>
 <head>
+  <meta charset="utf-8">
   <title>Test AnalyserNode when the input is silent</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="webaudio.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
 
-SimpleTest.waitForExplicitFinish();
-addLoadEvent(function() {
+    var ac = new AudioContext();
+    var analyser = ac.createAnalyser();
+    var constant = ac.createConstantSource();
+    var sp = ac.createScriptProcessor(2048, 1, 0);
 
-  var ac = new AudioContext();
-  var analyser = ac.createAnalyser();
-  var constant = ac.createConstantSource();
-  var sp = ac.createScriptProcessor(2048, 1, 0);
+    constant.offset.value = 0.0;
 
-  constant.offset.value = 0.0;
+    constant.connect(analyser).connect(ac.destination);
 
-  constant.connect(analyser)
-          .connect(ac.destination);
+    constant.connect(sp);
 
-  constant.connect(sp);
+    var buf = new Float32Array(analyser.frequencyBinCount);
+    var iteration_count = 10;
+    sp.onaudioprocess = function() {
+      analyser.getFloatFrequencyData(buf);
+      var correct = true;
+      for (var i = 0; i < buf.length; i++) {
+        correct &= buf[i] == -Infinity;
+      }
+      assert_true(!!correct, "silent input process -Infinity in decibel bins");
+      if (!iteration_count--) {
+        sp.onaudioprocess = null;
+        constant.stop();
+        ac.close();
+        done();
+      }
+    };
 
-  var buf = new Float32Array(analyser.frequencyBinCount);
-  var iteration_count = 10;
-  sp.onaudioprocess = function() {
-    analyser.getFloatFrequencyData(buf);
-    var correct = true;
-    for (var i = 0; i < buf.length; i++) {
-     correct &= buf[i] == -Infinity;
-    }
-    ok(correct, "silent input process -Infinity in decibel bins");
-    if(!(iteration_count--)) {
-      sp.onaudioprocess = null;
-      constant.stop();
-      ac.close();
-      SimpleTest.finish();
-    }
-  }
-
-  constant.start();
-});
-
-</script>
-</pre>
+    constant.start();
+  </script>
+</head>
 </body>
 </html>
copy from dom/media/webaudio/test/test_analyserNodeOutput.html
copy to testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html
--- a/dom/media/webaudio/test/test_analyserNodeOutput.html
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html
@@ -1,20 +1,17 @@
-<!DOCTYPE HTML>
+<!DOCTYPE html>
 <html>
 <head>
-  <title>Test AnalyserNode</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="webaudio.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
+  <meta charset="utf-8">
+  <title>AnalyserNode output</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/webaudio/js/helpers.js"></script>
+  <script>
 var gTest = {
   length: 2048,
   numberOfChannels: 1,
   createGraph: function(context) {
     var source = context.createBufferSource();
 
     var analyser = context.createAnalyser();
 
@@ -23,21 +20,22 @@ var gTest = {
     source.connect(analyser);
 
     source.start(0);
     return analyser;
   },
   createExpectedBuffers: function(context) {
     this.buffer = context.createBuffer(1, 2048, context.sampleRate);
     for (var i = 0; i < 2048; ++i) {
-      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+      this.buffer.getChannelData(0)[i] = Math.sin(
+        440 * 2 * Math.PI * i / context.sampleRate
+      );
     }
 
     return [this.buffer];
-  },
+  }
 };
 
-runTest();
-
-</script>
-</pre>
+runTest("AnalyserNode output");
+  </script>
+</head>
 </body>
 </html>
copy from dom/media/webaudio/test/test_analyserScale.html
copy to testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-scale.html
--- a/dom/media/webaudio/test/test_analyserScale.html
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-scale.html
@@ -1,59 +1,49 @@
-<!DOCTYPE HTML>
+<!DOCTYPE html>
 <html>
 <head>
-  <title>Test AnalyserNode when the input is scaled </title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="webaudio.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-addLoadEvent(function() {
+  <meta charset="utf-8">
+  <title>Test AnalyserNode when the input is scaled</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
+    var context = new AudioContext();
 
-  var context = new AudioContext();
-
-  var gain = context.createGain();
-  var analyser = context.createAnalyser();
-  var osc = context.createOscillator();
-
+    var gain = context.createGain();
+    var analyser = context.createAnalyser();
+    var osc = context.createOscillator();
 
-  osc.connect(gain);
-  gain.connect(analyser);
+    osc.connect(gain);
+    gain.connect(analyser);
 
-  osc.start();
+    osc.start();
 
-  var array = new Uint8Array(analyser.frequencyBinCount);
+    var array = new Uint8Array(analyser.frequencyBinCount);
 
-  function getAnalyserData() {
-    gain.gain.setValueAtTime(currentGain, context.currentTime);
-    analyser.getByteTimeDomainData(array);
-    var inrange = true;
-    var max = -1;
-    for (var i = 0; i < array.length; i++) {
-      if (array[i] > max) {
-        max = Math.abs(array[i] - 128);
+    function getAnalyserData() {
+      gain.gain.setValueAtTime(currentGain, context.currentTime);
+      analyser.getByteTimeDomainData(array);
+      var inrange = true;
+      var max = -1;
+      for (var i = 0; i < array.length; i++) {
+        if (array[i] > max) {
+          max = Math.abs(array[i] - 128);
+        }
       }
+      if (max <= currentGain * 128) {
+        assert_true(true, "Analyser got scaled data for " + currentGain);
+        currentGain = tests.shift();
+        if (currentGain == undefined) {
+          done();
+          return;
+        }
+      }
+      requestAnimationFrame(getAnalyserData);
     }
-    if (max <= currentGain * 128) {
-      ok(true, "Analyser got scaled data for " + currentGain);
-      currentGain = tests.shift();
-      if (currentGain == undefined) {
-        SimpleTest.finish();
-        return;
-      }
-    }
+
+    var tests = [1.0, 0.5, 0.0];
+    var currentGain = tests.shift();
     requestAnimationFrame(getAnalyserData);
-  }
-
-  var tests = [1.0, 0.5, 0.0];
-  var currentGain = tests.shift();
-  requestAnimationFrame(getAnalyserData);
-});
-
-</script>
-</pre>
+  </script>
+</head>
 </body>
 </html>
copy from dom/media/webaudio/test/test_analyserNode.html
copy to testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analysernode.html
--- a/dom/media/webaudio/test/test_analyserNode.html
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-analysernode-interface/test-analysernode.html
@@ -1,178 +1,237 @@
-<!DOCTYPE HTML>
+<!DOCTYPE html>
 <html>
 <head>
-  <title>Test AnalyserNode</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="webaudio.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
+  <meta charset="utf-8">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
+    function testNode() {
+      var context = new AudioContext();
+      var buffer = context.createBuffer(1, 2048, context.sampleRate);
+      for (var i = 0; i < 2048; ++i) {
+        buffer.getChannelData(0)[i] = Math.sin(
+          440 * 2 * Math.PI * i / context.sampleRate
+        );
+      }
+
+      var destination = context.destination;
+
+      var source = context.createBufferSource();
+
+      var analyser = context.createAnalyser();
 
-function testNode() {
-  var context = new AudioContext();
-  var buffer = context.createBuffer(1, 2048, context.sampleRate);
-  for (var i = 0; i < 2048; ++i) {
-    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
-  }
+      source.buffer = buffer;
 
-  var destination = context.destination;
-
-  var source = context.createBufferSource();
+      source.connect(analyser);
+      analyser.connect(destination);
 
-  var analyser = context.createAnalyser();
-
-  source.buffer = buffer;
-
-  source.connect(analyser);
-  analyser.connect(destination);
-
-  is(analyser.channelCount, 1, "analyser node has 1 input channels by default");
-  is(analyser.channelCountMode, "max", "Correct channelCountMode for the analyser node");
-  is(analyser.channelInterpretation, "speakers", "Correct channelCountInterpretation for the analyser node");
+      assert_equals(
+        analyser.channelCount,
+        1,
+        "analyser node has 1 input channels by default"
+      );
+      assert_equals(
+        analyser.channelCountMode,
+        "max",
+        "Correct channelCountMode for the analyser node"
+      );
+      assert_equals(
+        analyser.channelInterpretation,
+        "speakers",
+        "Correct channelCountInterpretation for the analyser node"
+      );
 
-  is(analyser.fftSize, 2048, "Correct default value for fftSize");
-  is(analyser.frequencyBinCount, 1024, "Correct default value for frequencyBinCount");
-  expectException(function() {
-    analyser.fftSize = 0;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 1;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 8;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 100; // non-power of two
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 2049;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 4097;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 8193;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 16385;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 32769;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.fftSize = 65536;
-  }, DOMException.INDEX_SIZE_ERR);
-  analyser.fftSize = 1024;
-  is(analyser.frequencyBinCount, 512, "Correct new value for frequencyBinCount");
+      assert_equals(
+        analyser.fftSize,
+        2048,
+        "Correct default value for fftSize"
+      );
+      assert_equals(
+        analyser.frequencyBinCount,
+        1024,
+        "Correct default value for frequencyBinCount"
+      );
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 0;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 1;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 8;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 100;
+      }); // non-power of two
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 2049;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 4097;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 8193;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 16385;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 32769;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.fftSize = 65536;
+      });
+      analyser.fftSize = 1024;
+      assert_equals(
+        analyser.frequencyBinCount,
+        512,
+        "Correct new value for frequencyBinCount"
+      );
 
-  is(analyser.minDecibels, -100, "Correct default value for minDecibels");
-  is(analyser.maxDecibels, -30, "Correct default value for maxDecibels");
-  expectException(function() {
-    analyser.minDecibels = -30;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.minDecibels = -29;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.maxDecibels = -100;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.maxDecibels = -101;
-  }, DOMException.INDEX_SIZE_ERR);
+      assert_equals(
+        analyser.minDecibels,
+        -100,
+        "Correct default value for minDecibels"
+      );
+      assert_equals(
+        analyser.maxDecibels,
+        -30,
+        "Correct default value for maxDecibels"
+      );
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.minDecibels = -30;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.minDecibels = -29;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.maxDecibels = -100;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.maxDecibels = -101;
+      });
 
-  ok(Math.abs(analyser.smoothingTimeConstant - 0.8) < 0.001, "Correct default value for smoothingTimeConstant");
-  expectException(function() {
-    analyser.smoothingTimeConstant = -0.1;
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser.smoothingTimeConstant = 1.1;
-  }, DOMException.INDEX_SIZE_ERR);
-  analyser.smoothingTimeConstant = 0;
-  analyser.smoothingTimeConstant = 1;
-}
+      assert_true(
+        Math.abs(analyser.smoothingTimeConstant - 0.8) < 0.001,
+        "Correct default value for smoothingTimeConstant"
+      );
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.smoothingTimeConstant = -0.1;
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser.smoothingTimeConstant = 1.1;
+      });
+      analyser.smoothingTimeConstant = 0;
+      analyser.smoothingTimeConstant = 1;
+    }
+
+    function testConstructor() {
+      var context = new AudioContext();
 
-function testConstructor() {
-  var context = new AudioContext();
+      var analyser = new AnalyserNode(context);
+      assert_equals(
+        analyser.channelCount,
+        1,
+        "analyser node has 1 input channels by default"
+      );
+      assert_equals(
+        analyser.channelCountMode,
+        "max",
+        "Correct channelCountMode for the analyser node"
+      );
+      assert_equals(
+        analyser.channelInterpretation,
+        "speakers",
+        "Correct channelCountInterpretation for the analyser node"
+      );
 
-  var analyser = new AnalyserNode(context);
-  is(analyser.channelCount, 1, "analyser node has 1 input channels by default");
-  is(analyser.channelCountMode, "max", "Correct channelCountMode for the analyser node");
-  is(analyser.channelInterpretation, "speakers", "Correct channelCountInterpretation for the analyser node");
-
-  is(analyser.fftSize, 2048, "Correct default value for fftSize");
-  is(analyser.frequencyBinCount, 1024, "Correct default value for frequencyBinCount");
-  is(analyser.minDecibels, -100, "Correct default value for minDecibels");
-  is(analyser.maxDecibels, -30, "Correct default value for maxDecibels");
-  ok(Math.abs(analyser.smoothingTimeConstant - 0.8) < 0.001, "Correct default value for smoothingTimeConstant");
+      assert_equals(
+        analyser.fftSize,
+        2048,
+        "Correct default value for fftSize"
+      );
+      assert_equals(
+        analyser.frequencyBinCount,
+        1024,
+        "Correct default value for frequencyBinCount"
+      );
+      assert_equals(
+        analyser.minDecibels,
+        -100,
+        "Correct default value for minDecibels"
+      );
+      assert_equals(
+        analyser.maxDecibels,
+        -30,
+        "Correct default value for maxDecibels"
+      );
+      assert_true(
+        Math.abs(analyser.smoothingTimeConstant - 0.8) < 0.001,
+        "Correct default value for smoothingTimeConstant"
+      );
 
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 0 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 1 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 8 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 100 }); // non-power of two
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 2049 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 4097 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 8193 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 16385 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 32769 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { fftSize: 65536 });
-  }, DOMException.INDEX_SIZE_ERR);
-  analyser = new AnalyserNode(context, { fftSize: 1024 });
-  is(analyser.frequencyBinCount, 512, "Correct new value for frequencyBinCount");
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 0 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 1 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 8 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 100 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 2049 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 4097 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 8193 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 16385 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 32769 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { fftSize: 65536 });
+      });
+      analyser = new AnalyserNode(context, { fftSize: 1024 });
+      assert_equals(
+        analyser.frequencyBinCount,
+        512,
+        "Correct new value for frequencyBinCount"
+      );
 
-  expectException(function() {
-    analyser = new AnalyserNode(context, { minDecibels: -30 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { minDecibels: -29 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { maxDecibels: -100 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { maxDecibels: -101 });
-  }, DOMException.INDEX_SIZE_ERR);
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { minDecibels: -30 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { minDecibels: -29 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { maxDecibels: -100 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { maxDecibels: -101 });
+      });
 
-  expectException(function() {
-    analyser = new AnalyserNode(context, { smoothingTimeConstant: -0.1 });
-  }, DOMException.INDEX_SIZE_ERR);
-  expectException(function() {
-    analyser = new AnalyserNode(context, { smoothingTimeConstant: -1.1 });
-  }, DOMException.INDEX_SIZE_ERR);
-  analyser = new AnalyserNode(context, { smoothingTimeConstant: 0 });
-  analyser = new AnalyserNode(context, { smoothingTimeConstant: 1 });
-}
-
-SimpleTest.waitForExplicitFinish();
-addLoadEvent(function() {
-
-  testNode();
-  testConstructor();
-
-  SimpleTest.finish();
-});
-
-</script>
-</pre>
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { smoothingTimeConstant: -0.1 });
+      });
+      assert_throws("INDEX_SIZE_ERR", function() {
+        analyser = new AnalyserNode(context, { smoothingTimeConstant: -1.1 });
+      });
+      analyser = new AnalyserNode(context, { smoothingTimeConstant: 0 });
+      analyser = new AnalyserNode(context, { smoothingTimeConstant: 1 });
+    }
+    test(testNode, "Test AnalyserNode API");
+    test(testConstructor, "Test AnalyserNode's ctor API");
+  </script>
+</head>
 </body>
 </html>
rename from toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js
rename to toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.js
--- a/toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.js
@@ -1,22 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Cu.import("resource://gre/modules/PlacesSearchAutocompleteProvider.jsm");
 
-function run_test() {
-  // Tell the search service we are running in the US.  This also has the
-  // desired side-effect of preventing our geoip lookup.
-  Services.prefs.setBoolPref("browser.search.isUS", true);
-  Services.prefs.setCharPref("browser.search.countryCode", "US");
-  Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
-  run_next_test();
-}
+add_task(async function() {
+    // Tell the search service we are running in the US.  This also has the
+    // desired side-effect of preventing our geoip lookup.
+   Services.prefs.setBoolPref("browser.search.isUS", true);
+   Services.prefs.setCharPref("browser.search.countryCode", "US");
+   Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
+
+   Services.search.restoreDefaultEngines();
+   Services.search.resetToOriginalDefaultEngine();
+});
 
 add_task(async function search_engine_match() {
   let engine = await promiseDefaultSearchEngine();
   let token = engine.getResultDomain();
   let match = await PlacesSearchAutocompleteProvider.findMatchByToken(token.substr(0, 1));
   do_check_eq(match.url, engine.searchForm);
   do_check_eq(match.engineName, engine.name);
   do_check_eq(match.iconUrl, engine.iconURI ? engine.iconURI.spec : null);
rename from toolkit/components/places/tests/unit/test_autocomplete_stopSearch_no_throw.js
rename to toolkit/components/places/tests/unifiedcomplete/test_autocomplete_stopSearch_no_throw.js
rename from toolkit/components/places/tests/unit/test_history_autocomplete_tags.js
rename to toolkit/components/places/tests/unifiedcomplete/test_history_autocomplete_tags.js
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
@@ -19,16 +19,19 @@ async function cleanUpSuggestions() {
   await cleanup();
   if (previousSuggestionsFn) {
     suggestionsFn = previousSuggestionsFn;
     previousSuggestionsFn = null;
   }
 }
 
 add_task(async function setup() {
+  Services.prefs.setCharPref("browser.urlbar.matchBuckets", "general:5,suggestion:Infinity");
+  Services.prefs.setBoolPref("browser.urlbar.geoSpecificDefaults", false);
+
   // Set up a server that provides some suggestions by appending strings onto
   // the search query.
   let server = makeTestServer(SERVER_PORT);
   server.registerPathHandler("/suggest", (req, resp) => {
     // URL query params are x-www-form-urlencoded, which converts spaces into
     // plus signs, so un-convert any plus signs back to spaces.
     let searchStr = decodeURIComponent(req.queryString.replace(/\+/g, " "));
     let suggestions = suggestionsFn(searchStr);
@@ -554,23 +557,24 @@ add_task(async function mixup_frecency()
         title: "low frecency 2" },
       { uri: NetUtil.newURI("http://example.com/lo1"),
         title: "low frecency 1" },
       { uri: NetUtil.newURI("http://example.com/lo0"),
         title: "low frecency 0" },
     ],
   });
 
-  Services.prefs.clearUserPref("browser.urlbar.matchBuckets");
+  Services.prefs.setCharPref("browser.urlbar.matchBuckets", "general:5,suggestion:Infinity");
   Services.prefs.clearUserPref("browser.urlbar.matchBucketsSearch");
   await cleanUpSuggestions();
 });
 
 add_task(async function prohibit_suggestions() {
   Services.prefs.setBoolPref(SUGGEST_PREF, true);
+  Services.prefs.setBoolPref("browser.fixup.domainwhitelist.localhost", false);
 
   await check_autocomplete({
     search: "localhost",
     searchParam: "enable-actions",
     matches: [
       makeSearchMatch("localhost", { engineName: ENGINE_NAME, heuristic: true }),
       {
         uri: makeActionURI(("searchengine"), {
@@ -593,17 +597,17 @@ add_task(async function prohibit_suggest
         title: ENGINE_NAME,
         style: ["action", "searchengine", "suggestion"],
         icon: "",
       },
     ],
   });
   Services.prefs.setBoolPref("browser.fixup.domainwhitelist.localhost", true);
   do_register_cleanup(() => {
-    Services.prefs.clearUserPref("browser.fixup.domainwhitelist.localhost");
+    Services.prefs.setBoolPref("browser.fixup.domainwhitelist.localhost", false);
   });
   await check_autocomplete({
     search: "localhost",
     searchParam: "enable-actions",
     matches: [
       makeVisitMatch("localhost", "http://localhost/", { heuristic: true }),
       makeSearchMatch("localhost", { engineName: ENGINE_NAME, heuristic: false })
     ],
@@ -637,17 +641,17 @@ add_task(async function prohibit_suggest
         style: ["action", "searchengine", "suggestion"],
         icon: "",
       },
     ],
   });
 
   // Clear the whitelist for localhost, and try preferring DNS for any single
   // word instead:
-  Services.prefs.clearUserPref("browser.fixup.domainwhitelist.localhost");
+  Services.prefs.setBoolPref("browser.fixup.domainwhitelist.localhost", false);
   Services.prefs.setBoolPref("browser.fixup.dns_first_for_single_words", true);
   do_register_cleanup(() => {
     Services.prefs.clearUserPref("browser.fixup.dns_first_for_single_words");
   });
 
   await check_autocomplete({
     search: "localhost",
     searchParam: "enable-actions",
--- a/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
+++ b/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
@@ -1,40 +1,44 @@
 [DEFAULT]
 head = head_autocomplete.js
 skip-if = toolkit == 'android'
+firefox-appdir = browser
 support-files =
   data/engine-rel-searchform.xml
   data/engine-suggestions.xml
   !/toolkit/components/places/tests/favicons/favicon-normal16.png
 
 [test_416211.js]
 [test_416214.js]
 [test_417798.js]
 [test_418257.js]
 [test_422277.js]
 [test_autocomplete_functional.js]
+[test_autocomplete_stopSearch_no_throw.js]
 [test_autofill_default_behavior.js]
 [test_avoid_middle_complete.js]
 [test_avoid_stripping_to_empty_tokens.js]
 [test_casing.js]
 [test_do_not_trim.js]
 [test_download_embed_bookmarks.js]
 [test_dupe_urls.js]
 [test_empty_search.js]
 [test_enabled.js]
 [test_encoded_urls.js]
 [test_escape_self.js]
 [test_extension_matches.js]
+[test_history_autocomplete_tags.js]
 [test_ignore_protocol.js]
 [test_keyword_search.js]
 [test_keyword_search_actions.js]
 [test_keywords.js]
 [test_match_beginning.js]
 [test_multi_word_search.js]
+[test_PlacesSearchAutocompleteProvider.js]
 [test_preloaded_sites.js]
 [test_query_url.js]
 [test_remote_tab_matches.js]
 skip-if = !sync
 [test_search_engine_alias.js]
 [test_search_engine_current.js]
 [test_search_engine_host.js]
 [test_search_engine_restyle.js]
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -55,17 +55,16 @@ skip-if = os == "linux"
 [test_1105866.js]
 [test_adaptive.js]
 [test_adaptive_bug527311.js]
 [test_annotations.js]
 [test_asyncExecuteLegacyQueries.js]
 [test_async_in_batchmode.js]
 [test_async_transactions.js]
 skip-if = (os == "win" && os_version == "5.1") # Bug 1158887
-[test_autocomplete_stopSearch_no_throw.js]
 [test_bookmark_catobs.js]
 [test_bookmarks_json.js]
 [test_bookmarks_html.js]
 [test_bookmarks_html_corrupt.js]
 [test_bookmarks_html_escape_entities.js]
 [test_bookmarks_html_import_tags.js]
 [test_bookmarks_html_singleframe.js]
 [test_bookmarks_restore_notification.js]
@@ -78,17 +77,16 @@ skip-if = (os == "win" && os_version == 
 [test_database_replaceOnStartup.js]
 [test_download_history.js]
 [test_frecency.js]
 [test_frecency_decay.js]
 [test_frecency_zero_updated.js]
 [test_getChildIndex.js]
 [test_hash.js]
 [test_history.js]
-[test_history_autocomplete_tags.js]
 [test_history_catobs.js]
 [test_history_clear.js]
 [test_history_notifications.js]
 [test_history_observer.js]
 [test_history_sidebar.js]
 [test_hosts_triggers.js]
 [test_import_mobile_bookmarks.js]
 [test_isPageInDB.js]
@@ -103,17 +101,16 @@ skip-if = (os == "win" && os_version == 
 [test_nsINavHistoryViewer.js]
 # Bug 902248: intermittent timeouts on all platforms
 skip-if = true
 [test_null_interfaces.js]
 [test_onItemChanged_tags.js]
 [test_pageGuid_bookmarkGuid.js]
 [test_frecency_observers.js]
 [test_placeURIs.js]
-[test_PlacesSearchAutocompleteProvider.js]
 [test_PlacesUtils_invalidateCachedGuidFor.js]
 [test_preventive_maintenance.js]
 [test_preventive_maintenance_checkAndFixDatabase.js]
 [test_preventive_maintenance_runTasks.js]
 [test_promiseBookmarksTree.js]
 [test_resolveNullBookmarkTitles.js]
 [test_result_sort.js]
 [test_resultsAsVisit_details.js]
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -574,16 +574,17 @@ dependencies = [
  "cubeb-backend 0.2.0",
  "cubeb-core 0.1.0",
  "cubeb-pulse 0.0.1",
  "encoding_c 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_glue 0.1.0",
  "geckoservo 0.0.1",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "mp4parse_capi 0.8.0",
+ "netwerk_helper 0.0.1",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "rust_url_capi 0.0.1",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "u2fhid 0.1.0",
  "webrender_bindings 0.1.0",
 ]
 
@@ -861,16 +862,24 @@ dependencies = [
  "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "netwerk_helper"
+version = "0.0.1"
+dependencies = [
+ "nserror 0.1.0",
+ "nsstring 0.1.0",
+]
+
+[[package]]
 name = "nodrop"
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -573,16 +573,17 @@ dependencies = [
  "cubeb-backend 0.2.0",
  "cubeb-core 0.1.0",
  "cubeb-pulse 0.0.1",
  "encoding_c 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_glue 0.1.0",
  "geckoservo 0.0.1",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "mp4parse_capi 0.8.0",
+ "netwerk_helper 0.0.1",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "rust_url_capi 0.0.1",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "u2fhid 0.1.0",
  "webrender_bindings 0.1.0",
 ]
 
@@ -856,16 +857,24 @@ dependencies = [
  "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "netwerk_helper"
+version = "0.0.1"
+dependencies = [
+ "nserror 0.1.0",
+ "nsstring 0.1.0",
+]
+
+[[package]]
 name = "nodrop"
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
--- a/toolkit/library/rust/shared/Cargo.toml
+++ b/toolkit/library/rust/shared/Cargo.toml
@@ -5,16 +5,17 @@ authors = ["nobody@mozilla.org"]
 license = "MPL-2.0"
 description = "Shared Rust code for libxul"
 
 [dependencies]
 geckoservo = { path = "../../../../servo/ports/geckolib", optional = true }
 mp4parse_capi = { path = "../../../../media/libstagefright/binding/mp4parse_capi" }
 nsstring = { path = "../../../../xpcom/rust/nsstring" }
 nserror = { path = "../../../../xpcom/rust/nserror" }
+netwerk_helper = { path = "../../../../netwerk/base/rust-helper" }
 rust_url_capi = { path = "../../../../netwerk/base/rust-url-capi" }
 webrender_bindings = { path = "../../../../gfx/webrender_bindings", optional = true }
 cubeb-pulse = { path = "../../../../media/libcubeb/cubeb-pulse-rs", optional = true, features=["pulse-dlopen"] }
 cubeb-core = { path = "../../../../media/cubeb-rs/cubeb-core", optional = true }
 cubeb = { path = "../../../../media/cubeb-rs/cubeb-api", optional = true }
 cubeb-backend = { path = "../../../../media/cubeb-rs/cubeb-backend", optional = true }
 encoding_c = "0.8.0"
 encoding_glue = { path = "../../../../intl/encoding_glue" }
--- a/toolkit/library/rust/shared/lib.rs
+++ b/toolkit/library/rust/shared/lib.rs
@@ -4,16 +4,17 @@
 
 #[cfg(feature="servo")]
 extern crate geckoservo;
 
 extern crate mp4parse_capi;
 extern crate nsstring;
 extern crate nserror;
 extern crate rust_url_capi;
+extern crate netwerk_helper;
 #[cfg(feature = "quantum_render")]
 extern crate webrender_bindings;
 #[cfg(feature = "cubeb_pulse_rust")]
 extern crate cubeb_pulse;
 extern crate encoding_c;
 extern crate encoding_glue;
 #[cfg(feature = "cubeb-remoting")]
 extern crate audioipc_client;
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -33,17 +33,19 @@
 #define AUTO_PROFILER_THREAD_WAKE
 
 #define PROFILER_JS_INTERRUPT_CALLBACK()
 
 #define PROFILER_SET_JS_CONTEXT(cx)
 #define PROFILER_CLEAR_JS_CONTEXT()
 
 #define AUTO_PROFILER_LABEL(label, category)
-#define AUTO_PROFILER_LABEL_DYNAMIC(label, category, dynamicStr)
+#define AUTO_PROFILER_LABEL_DYNAMIC_CSTR(label, category, cStr)
+#define AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(label, category, nsCStr)
+#define AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(label, category, nsStr)
 
 #define PROFILER_ADD_MARKER(markerName)
 
 #define PROFILER_TRACING(category, markerName, kind)
 #define AUTO_PROFILER_TRACING(category, markerName)
 
 #else // !MOZ_GECKO_PROFILER
 
@@ -374,47 +376,83 @@ static inline void profiler_get_buffer_i
 PseudoStack* profiler_get_pseudo_stack();
 
 //---------------------------------------------------------------------------
 // Put profiling data into the profiler (labels and markers)
 //---------------------------------------------------------------------------
 
 // Insert an RAII object in this scope to enter a pseudo stack frame. Any
 // samples collected in this scope will contain this label in their pseudo
-// stack. The label argument must be a string literal. It is usually of the
+// stack. The label argument must be a static C string. It is usually of the
 // form "ClassName::FunctionName". (Ideally we'd use the compiler to provide
 // that for us, but __func__ gives us the function name without the class
 // name.) If the label applies to only part of a function, you can qualify it
 // like this: "ClassName::FunctionName:PartName".
 //
-// Use AUTO_PROFILER_LABEL_DYNAMIC if you want to add additional / dynamic
+// Use AUTO_PROFILER_LABEL_DYNAMIC_* if you want to add additional / dynamic
 // information to the pseudo stack frame.
 #define AUTO_PROFILER_LABEL(label, category) \
   mozilla::AutoProfilerLabel PROFILER_RAII(label, nullptr, __LINE__, \
                                            js::ProfileEntry::Category::category)
 
 // Similar to AUTO_PROFILER_LABEL, but with an additional string. The inserted
-// RAII object stores the dynamicStr pointer in a field; it does not copy the
-// string. This means that the string you pass to this macro needs to live at
-// least until the end of the current scope.
+// RAII object stores the cStr pointer in a field; it does not copy the string.
+//
+// WARNING: This means that the string you pass to this macro needs to live at
+// least until the end of the current scope. Be careful using this macro with
+// ns[C]String; the other AUTO_PROFILER_LABEL_DYNAMIC_* macros below are
+// preferred because they avoid this problem.
 //
 // If the profiler samples the current thread and walks the pseudo stack while
 // this RAII object is on the stack, it will copy the supplied string into the
 // profile buffer. So there's one string copy operation, and it happens at
 // sample time.
 //
 // Compare this to the plain AUTO_PROFILER_LABEL macro, which only accepts
 // literal strings: When the pseudo stack frames generated by
 // AUTO_PROFILER_LABEL are sampled, no string copy needs to be made because the
 // profile buffer can just store the raw pointers to the literal strings.
 // Consequently, AUTO_PROFILER_LABEL frames take up considerably less space in
-// the profile buffer than AUTO_PROFILER_LABEL_DYNAMIC frames.
-#define AUTO_PROFILER_LABEL_DYNAMIC(label, category, dynamicStr) \
-  mozilla::AutoProfilerLabel PROFILER_RAII(label, dynamicStr, __LINE__, \
-                                           js::ProfileEntry::Category::category)
+// the profile buffer than AUTO_PROFILER_LABEL_DYNAMIC_* frames.
+#define AUTO_PROFILER_LABEL_DYNAMIC_CSTR(label, category, cStr) \
+  mozilla::AutoProfilerLabel \
+    PROFILER_RAII(label, cStr, __LINE__, js::ProfileEntry::Category::category)
+
+// Similar to AUTO_PROFILER_LABEL_DYNAMIC_CSTR, but takes an nsACString.
+//
+// Note: The use of the Maybe<>s ensures the scopes for the dynamic string and
+// the AutoProfilerLabel are appropriate, while also not incurring the runtime
+// cost of the string assignment unless the profiler is active. Therefore,
+// unlike AUTO_PROFILER_LABEL and AUTO_PROFILER_LABEL_DYNAMIC_CSTR, this macro
+// doesn't push/pop a label when the profiler is inactive.
+#define AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(label, category, nsCStr) \
+  mozilla::Maybe<nsAutoCString> autoCStr; \
+  mozilla::Maybe<AutoProfilerLabel> raiiObjectNsCString; \
+  if (profiler_is_active()) { \
+    autoCStr.emplace(nsCStr); \
+    raiiObjectNsCString.emplace(label, autoCStr->get(), __LINE__, \
+                                js::ProfileEntry::Category::category); \
+  }
+
+// Similar to AUTO_PROFILER_LABEL_DYNAMIC_CSTR, but takes an nsString that is
+// is lossily converted to an ASCII string.
+//
+// Note: The use of the Maybe<>s ensures the scopes for the converted dynamic
+// string and the AutoProfilerLabel are appropriate, while also not incurring
+// the runtime cost of the string conversion unless the profiler is active.
+// Therefore, unlike AUTO_PROFILER_LABEL and AUTO_PROFILER_LABEL_DYNAMIC_CSTR,
+// this macro doesn't push/pop a label when the profiler is inactive.
+#define AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(label, category, nsStr) \
+  mozilla::Maybe<NS_LossyConvertUTF16toASCII> asciiStr; \
+  mozilla::Maybe<AutoProfilerLabel> raiiObjectLossyNsString; \
+  if (profiler_is_active()) { \
+    asciiStr.emplace(nsStr); \
+    raiiObjectLossyNsString.emplace(label, asciiStr->get(), __LINE__, \
+                                    js::ProfileEntry::Category::category); \
+  }
 
 // Insert a marker in the profile timeline. This is useful to delimit something
 // important happening such as the first paint. Unlike labels, which are only
 // recorded in the profile buffer if a sample is collected while the label is
 // on the pseudostack, markers will always be recorded in the profile buffer.
 // aMarkerName is copied, so the caller does not need to ensure it lives for a
 // certain length of time. A no-op if the profiler is inactive or in privacy
 // mode.
--- a/tools/profiler/tests/gtest/GeckoProfiler.cpp
+++ b/tools/profiler/tests/gtest/GeckoProfiler.cpp
@@ -490,19 +490,19 @@ TEST(GeckoProfiler, Markers)
   for (size_t i = 0; i < kMax; i++) {
     okstr1[i] = 'a';
     okstr2[i] = 'b';
     longstr[i] = 'c';
   }
   okstr1[kMax - 1] = '\0';
   okstr2[kMax - 1] = '\0';
   longstr[kMax] = '\0';
-  AUTO_PROFILER_LABEL_DYNAMIC("", CSS, okstr1.get());
-  AUTO_PROFILER_LABEL_DYNAMIC("okstr2", CSS, okstr2.get());
-  AUTO_PROFILER_LABEL_DYNAMIC("", CSS, longstr.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("", CSS, okstr1.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("okstr2", CSS, okstr2.get());
+  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("", CSS, longstr.get());
 
   // Sleep briefly to ensure a sample is taken and the pending markers are
   // processed.
   PR_Sleep(PR_MillisecondsToInterval(500));
 
   SpliceableChunkedJSONWriter w;
   ASSERT_TRUE(profiler_stream_json_for_this_process(w));
 
@@ -693,17 +693,21 @@ TEST(GeckoProfiler, PseudoStack)
 {
   uint32_t features = ProfilerFeature::StackWalk;
   const char* filters[] = { "GeckoMain" };
 
   AUTO_PROFILER_LABEL("A::B", OTHER);
 
   UniqueFreePtr<char> dynamic(strdup("dynamic"));
   {
-    AUTO_PROFILER_LABEL_DYNAMIC("A::C", JS, dynamic.get());
+    AUTO_PROFILER_LABEL_DYNAMIC_CSTR("A::C", JS, dynamic.get());
+    AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+      "A::C2", JS, nsDependentCString(dynamic.get()));
+    AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+      "A::C3", JS, NS_ConvertUTF8toUTF16(dynamic.get()));
 
     profiler_start(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                    features, filters, MOZ_ARRAY_LENGTH(filters));
 
     ASSERT_TRUE(profiler_get_backtrace());
   }
 
   AutoProfilerLabel label1("A", nullptr, 888,
--- a/xpcom/ds/nsObserverService.cpp
+++ b/xpcom/ds/nsObserverService.cpp
@@ -283,18 +283,18 @@ NS_IMETHODIMP nsObserverService::NotifyO
 
   NS_ENSURE_VALIDCALL
   if (NS_WARN_IF(!aTopic)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   mozilla::TimeStamp start = TimeStamp::Now();
 
-  AUTO_PROFILER_LABEL_DYNAMIC("nsObserverService::NotifyObservers", OTHER,
-                              aTopic);
+  AUTO_PROFILER_LABEL_DYNAMIC_CSTR(
+    "nsObserverService::NotifyObservers", OTHER, aTopic);
 
   nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
   if (observerList) {
     observerList->NotifyObservers(aSubject, aTopic, aSomeData);
   }
 
 #ifdef NOTIFY_GLOBAL_OBSERVERS
   observerList = mObserverTopicTable.GetEntry("*");
--- a/xpcom/threads/nsILabelableRunnable.cpp
+++ b/xpcom/threads/nsILabelableRunnable.cpp
@@ -40,17 +40,19 @@ nsILabelableRunnable::IsReadyToRun()
 }
 
 void
 nsILabelableRunnable::SchedulerGroupSet::Put(mozilla::SchedulerGroup* aGroup)
 {
   if (mSingle) {
     MOZ_ASSERT(mMulti.isNothing());
     mMulti.emplace();
-    mMulti.ref().PutEntry(mSingle);
+    auto& multi = mMulti.ref();
+    multi.PutEntry(mSingle);
+    multi.PutEntry(aGroup);
     mSingle = nullptr;
     return;
   }
 
   if (mMulti.isSome()) {
     MOZ_ASSERT(!mSingle);
     mMulti.ref().PutEntry(aGroup);
     return;