merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 09 Aug 2017 11:37:08 +0200
changeset 423058 4c5fbf49376351679dcc49f4cff26c3c2e055ccc
parent 423016 c93fa2271ee761546d469fb1edc68254f2577b5a (current diff)
parent 423057 9031387c7ef1fe1db4b68495d1527b56e464c817 (diff)
child 423062 ee77b27b55e4d743b801514ea7d76a652f161873
child 423113 76cefe3fcd4445f80d918525df968a10aa37da24
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
4c5fbf493763 / 57.0a1 / 20170809100326 / files
nightly linux64
4c5fbf493763 / 57.0a1 / 20170809100326 / files
nightly mac
4c5fbf493763 / 57.0a1 / 20170809100326 / files
nightly win32
4c5fbf493763 / 57.0a1 / 20170809100326 / files
nightly win64
4c5fbf493763 / 57.0a1 / 20170809100326 / 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 to mozilla-central a=merge
dom/storage/PStorage.ipdl
modules/libpref/init/all.js
services/fxaccounts/FxAccountsManager.jsm
toolkit/modules/AppConstants.jsm
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -13,16 +13,17 @@
 #include "nsAccUtils.h"
 #include "nsAccessibilityService.h"
 #include "ApplicationAccessible.h"
 #include "NotificationController.h"
 #include "nsEventShell.h"
 #include "nsTextEquivUtils.h"
 #include "DocAccessibleChild.h"
 #include "EventTree.h"
+#include "GeckoProfiler.h"
 #include "Relation.h"
 #include "Role.h"
 #include "RootAccessible.h"
 #include "States.h"
 #include "StyleInfo.h"
 #include "TableAccessible.h"
 #include "TableCellAccessible.h"
 #include "TreeWalker.h"
@@ -839,16 +840,23 @@ Accessible::XULElmName(DocAccessible* aD
   }
 }
 
 nsresult
 Accessible::HandleAccEvent(AccEvent* aEvent)
 {
   NS_ENSURE_ARG_POINTER(aEvent);
 
+  if (profiler_is_active()) {
+    nsAutoCString strEventType;
+    GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
+
+    profiler_tracing("A11y Event", strEventType.get());
+  }
+
   if (IPCAccessibilityActive() && Document()) {
     DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
     MOZ_ASSERT(ipcDoc);
     if (ipcDoc) {
       uint64_t id = aEvent->GetAccessible()->IsDoc() ? 0 :
         reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
 
       switch(aEvent->GetEventType()) {
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
@@ -45,16 +45,20 @@ this.ControlCenter = {
         });
         channel = channel.QueryInterface(Ci.nsIFileChannel);
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         let gBrowser = browserWindow.gBrowser;
         BrowserTestUtils.loadURI(gBrowser.selectedBrowser, channel.file.path);
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
         await openIdentityPopup();
       },
+
+      async verifyConfig() {
+        return Promise.reject("Bug 1373563: intermittent controlCenter_localFile on Taskcluster");
+      },
     },
 
     http: {
       async applyConfig() {
         await loadPage(HTTP_PAGE);
         await openIdentityPopup();
       },
     },
@@ -77,30 +81,30 @@ this.ControlCenter = {
       async applyConfig() {
         await loadPage(HTTPS_PAGE);
         await openIdentityPopup(true);
       },
     },
 
     singlePermission: {
       async applyConfig() {
-        let uri = Services.io.newURI(PERMISSIONS_PAGE)
+        let uri = Services.io.newURI(PERMISSIONS_PAGE);
         SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
 
         await loadPage(PERMISSIONS_PAGE);
         await openIdentityPopup();
       },
     },
 
     allPermissions: {
       async applyConfig() {
         // TODO: (Bug 1330601) Rewrite this to consider temporary (TAB) permission states.
         // There are 2 possible non-default permission states, so we alternate between them.
         let states = [SitePermissions.ALLOW, SitePermissions.BLOCK];
-        let uri = Services.io.newURI(PERMISSIONS_PAGE)
+        let uri = Services.io.newURI(PERMISSIONS_PAGE);
         SitePermissions.listPermissions().forEach(function(permission, index) {
           SitePermissions.set(uri, permission, states[index % 2]);
         });
 
         await loadPage(PERMISSIONS_PAGE);
         await openIdentityPopup();
       },
     },
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-reloading.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-reloading.js
@@ -19,17 +19,17 @@ function addBreakpoint(dbg, line) {
 }
 
 function assertEditorBreakpoint(dbg, line) {
   const exists = !!getLineEl(dbg, line).querySelector(".new-breakpoint");
   ok(exists, `Breakpoint exists on line ${line}`);
 }
 
 add_task(function*() {
-  requestLongerTimeout(2);
+  requestLongerTimeout(3);
 
   const dbg = yield initDebugger("doc-scripts.html");
   const { selectors: { getBreakpoints, getBreakpoint }, getState } = dbg;
   const source = findSource(dbg, "simple1.js");
 
   yield selectSource(dbg, source.url);
   yield addBreakpoint(dbg, 5);
   yield addBreakpoint(dbg, 2);
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -922,21 +922,18 @@ GK_ATOM(onMozMouseHittest, "onMozMouseHi
 GK_ATOM(onmouseup, "onmouseup")
 GK_ATOM(onMozAfterPaint, "onMozAfterPaint")
 GK_ATOM(onmozfullscreenchange, "onmozfullscreenchange")
 GK_ATOM(onmozfullscreenerror, "onmozfullscreenerror")
 GK_ATOM(onmozkeydownonplugin, "onmozkeydownonplugin")
 GK_ATOM(onmozkeyuponplugin, "onmozkeyuponplugin")
 GK_ATOM(onmozpointerlockchange, "onmozpointerlockchange")
 GK_ATOM(onmozpointerlockerror, "onmozpointerlockerror")
-GK_ATOM(onmoztimechange, "onmoztimechange")
 GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
 GK_ATOM(onMozScrolledAreaChanged, "onMozScrolledAreaChanged")
-GK_ATOM(onmoznetworkupload, "onmoznetworkupload")
-GK_ATOM(onmoznetworkdownload, "onmoznetworkdownload")
 GK_ATOM(onmapfolderlistingreq, "onmapfolderlistingreq")
 GK_ATOM(onmapmessageslistingreq, "onmapmessageslistingreq")
 GK_ATOM(onmapgetmessagereq, "onmapgetmessagereq")
 GK_ATOM(onmapsetmessagestatusreq, "onmapsetmessagestatusreq")
 GK_ATOM(onmapsendmessagereq, "onmapsendmessagereq")
 GK_ATOM(onmapmessageupdatereq, "onmapmessageupdatereq")
 GK_ATOM(onnewrdsgroup, "onnewrdsgroup")
 GK_ATOM(onnotificationclick, "onnotificationclick")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -252,20 +252,16 @@
 #ifdef HAVE_SIDEBAR
 #include "mozilla/dom/ExternalBinding.h"
 #endif
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
-#ifdef MOZ_B2G
-#include "nsPISocketTransportService.h"
-#endif
-
 // Apple system headers seem to have a check() macro.  <sigh>
 #ifdef check
 class nsIScriptTimeoutHandler;
 #undef check
 #endif // check
 #include "AccessCheck.h"
 
 #ifdef ANDROID
@@ -1618,20 +1614,16 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mFreezeDepth(0),
     mFocusMethod(0),
     mSerial(0),
     mIdleRequestCallbackCounter(1),
     mIdleRequestExecutor(nullptr),
 #ifdef DEBUG
     mSetOpenerWindowCalled(false),
 #endif
-#ifdef MOZ_B2G
-    mNetworkUploadObserverEnabled(false),
-    mNetworkDownloadObserverEnabled(false),
-#endif
     mCleanedUp(false),
     mDialogAbuseCount(0),
     mAreDialogsEnabled(true),
 #ifdef DEBUG
     mIsValidatingTabGroup(false),
 #endif
     mCanSkipCCGeneration(0),
     mAutoActivateVRDisplayID(0),
@@ -1966,21 +1958,16 @@ nsGlobalWindow::CleanUp()
       os->RemoveObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC);
     }
 
     RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
     if (sns) {
      sns->Unregister(mObserver);
     }
 
-#ifdef MOZ_B2G
-    DisableNetworkEvent(eNetworkUpload);
-    DisableNetworkEvent(eNetworkDownload);
-#endif // MOZ_B2G
-
     if (mIdleService) {
       mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
     }
 
     Preferences::RemoveObserver(mObserver, "intl.accept_languages");
 
     // Drop its reference to this dying window, in case for some bogus reason
     // the object stays around.
@@ -2042,19 +2029,16 @@ nsGlobalWindow::CleanUp()
   }
 
   if (IsInnerWindow()) {
     DisableGamepadUpdates();
     mHasGamepad = false;
     DisableVRUpdates();
     mHasVREvents = false;
     mHasVRDisplayActivateEvents = false;
-#ifdef MOZ_B2G
-    DisableTimeChangeNotifications();
-#endif
     DisableIdleCallbackRequests();
   } else {
     MOZ_ASSERT(!mHasGamepad);
     MOZ_ASSERT(!mHasVREvents);
     MOZ_ASSERT(!mHasVRDisplayActivateEvents);
   }
 
   if (mCleanMessageManager) {
@@ -12163,37 +12147,16 @@ nsGlobalWindow::Observe(nsISupports* aSu
     nsCOMPtr<nsIDOMOfflineResourceList> applicationCache = GetApplicationCache();
     nsCOMPtr<nsIObserver> observer = do_QueryInterface(applicationCache);
     if (observer)
       observer->Observe(aSubject, aTopic, aData);
 
     return NS_OK;
   }
 
-#ifdef MOZ_B2G
-  if (!nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC) ||
-      !nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC)) {
-    MOZ_ASSERT(IsInnerWindow());
-    if (!AsInner()->IsCurrentInnerWindow()) {
-      return NS_OK;
-    }
-
-    RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
-    event->InitEvent(
-      !nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC)
-        ? NETWORK_UPLOAD_EVENT_NAME
-        : NETWORK_DOWNLOAD_EVENT_NAME,
-      false, false);
-    event->SetTrusted(true);
-
-    bool dummy;
-    return DispatchEvent(event, &dummy);
-  }
-#endif // MOZ_B2G
-
   if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     MOZ_ASSERT(!NS_strcmp(aData, u"intl.accept_languages"));
     MOZ_ASSERT(IsInnerWindow());
 
     // The user preferred languages have changed, we need to fire an event on
     // Window object and invalidate the cache for navigator.languages. It is
     // done for every change which can be a waste of cycles but those should be
     // fairly rare.
@@ -14662,90 +14625,16 @@ nsGlobalWindow::ReportLargeAllocStatus()
 
   nsContentUtils::ReportToConsole(errorFlags,
                                   NS_LITERAL_CSTRING("DOM"),
                                   mDoc,
                                   nsContentUtils::eDOM_PROPERTIES,
                                   message);
 }
 
-#ifdef MOZ_B2G
-void
-nsGlobalWindow::EnableNetworkEvent(EventMessage aEventMessage)
-{
-  MOZ_ASSERT(IsInnerWindow());
-
-  nsCOMPtr<nsIPermissionManager> permMgr =
-    services::GetPermissionManager();
-  if (!permMgr) {
-    NS_ERROR("No PermissionManager available!");
-    return;
-  }
-
-  uint32_t permission = nsIPermissionManager::DENY_ACTION;
-  permMgr->TestExactPermissionFromPrincipal(GetPrincipal(), "network-events",
-                                            &permission);
-
-  if (permission != nsIPermissionManager::ALLOW_ACTION) {
-    return;
-  }
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (!os) {
-    NS_ERROR("ObserverService should be available!");
-    return;
-  }
-
-  switch (aEventMessage) {
-    case eNetworkUpload:
-      if (!mNetworkUploadObserverEnabled) {
-        mNetworkUploadObserverEnabled = true;
-        os->AddObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC, false);
-      }
-      break;
-    case eNetworkDownload:
-      if (!mNetworkDownloadObserverEnabled) {
-        mNetworkDownloadObserverEnabled = true;
-        os->AddObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC, false);
-      }
-      break;
-    default:
-      break;
-  }
-}
-
-void
-nsGlobalWindow::DisableNetworkEvent(EventMessage aEventMessage)
-{
-  MOZ_ASSERT(IsInnerWindow());
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (!os) {
-    return;
-  }
-
-  switch (aEventMessage) {
-    case eNetworkUpload:
-      if (mNetworkUploadObserverEnabled) {
-        mNetworkUploadObserverEnabled = false;
-        os->RemoveObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC);
-      }
-      break;
-    case eNetworkDownload:
-      if (mNetworkDownloadObserverEnabled) {
-        mNetworkDownloadObserverEnabled = false;
-        os->RemoveObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC);
-      }
-      break;
-    default:
-      break;
-  }
-}
-#endif // MOZ_B2G
-
 void
 nsGlobalWindow::RedefineProperty(JSContext* aCx, const char* aPropName,
                                  JS::Handle<JS::Value> aValue,
                                  ErrorResult& aError)
 {
   JS::Rooted<JSObject*> thisObj(aCx, GetWrapperPreserveColor());
   if (!thisObj) {
     aError.Throw(NS_ERROR_UNEXPECTED);
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -648,23 +648,16 @@ public:
 #if defined(MOZ_WIDGET_ANDROID)
   virtual void EnableOrientationChangeListener() override;
   virtual void DisableOrientationChangeListener() override;
 #endif
 
   virtual void EnableTimeChangeNotifications() override;
   virtual void DisableTimeChangeNotifications() override;
 
-#ifdef MOZ_B2G
-  // Inner windows only.
-  virtual void EnableNetworkEvent(mozilla::EventMessage aEventMessage) override;
-  virtual void DisableNetworkEvent(
-                 mozilla::EventMessage aEventMessage) override;
-#endif // MOZ_B2G
-
   virtual nsresult SetArguments(nsIArray* aArguments) override;
 
   void MaybeForgiveSpamCount();
   bool IsClosedOrClosing() {
     return (mIsClosed ||
             mInClose ||
             mHavePendingClose ||
             mCleanedUp);
@@ -1972,21 +1965,16 @@ protected:
   IdleRequests mIdleRequestCallbacks;
   RefPtr<IdleRequestExecutor> mIdleRequestExecutor;
 
 #ifdef DEBUG
   bool mSetOpenerWindowCalled;
   nsCOMPtr<nsIURI> mLastOpenedURI;
 #endif
 
-#ifdef MOZ_B2G
-  bool mNetworkUploadObserverEnabled;
-  bool mNetworkDownloadObserverEnabled;
-#endif // MOZ_B2G
-
   bool mCleanedUp;
 
   nsCOMPtr<nsIDOMOfflineResourceList> mApplicationCache;
 
   using XBLPrototypeHandlerTable = nsJSThingHashtable<nsPtrHashKey<nsXBLPrototypeHandler>, JSObject*>;
   nsAutoPtr<XBLPrototypeHandlerTable> mCachedXBLPrototypeHandlers;
 
   // mSuspendedDoc is only set on outer windows. It's useful when we get matched
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1682,31 +1682,31 @@ nsJSContext::EndCycleCollectionCallback(
     if (aResults.mForcedGC) {
       gcMsg.AssignLiteral(", forced a GC");
     }
 
     const char16_t *kFmt =
       u"CC(T+%.1f)[%s-%i] max pause: %lums, total time: %lums, slices: %lu, suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
       u"ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, max sync: %lu ms, removed: %lu";
     nsString msg;
-    msg.Adopt(nsTextFormatter::smprintf(kFmt, double(delta) / PR_USEC_PER_SEC,
-                                        ProcessNameForCollectorLog(), getpid(),
-                                        gCCStats.mMaxSliceTime, gCCStats.mTotalSliceTime,
-                                        aResults.mNumSlices, gCCStats.mSuspected,
-                                        aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
-                                        aResults.mFreedRefCounted, aResults.mFreedGCed,
-                                        sCCollectedWaitingForGC, sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
-                                        gcMsg.get(),
-                                        sForgetSkippableBeforeCC,
-                                        minForgetSkippableTime / PR_USEC_PER_MSEC,
-                                        sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
-                                        (sTotalForgetSkippableTime / cleanups) /
-                                          PR_USEC_PER_MSEC,
-                                        sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
-                                        gCCStats.mMaxSkippableDuration, sRemovedPurples));
+    nsTextFormatter::ssprintf(msg, kFmt, double(delta) / PR_USEC_PER_SEC,
+                              ProcessNameForCollectorLog(), getpid(),
+                              gCCStats.mMaxSliceTime, gCCStats.mTotalSliceTime,
+                              aResults.mNumSlices, gCCStats.mSuspected,
+                              aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
+                              aResults.mFreedRefCounted, aResults.mFreedGCed,
+                              sCCollectedWaitingForGC, sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
+                              gcMsg.get(),
+                              sForgetSkippableBeforeCC,
+                              minForgetSkippableTime / PR_USEC_PER_MSEC,
+                              sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
+                              (sTotalForgetSkippableTime / cleanups) /
+                              PR_USEC_PER_MSEC,
+                              sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
+                              gCCStats.mMaxSkippableDuration, sRemovedPurples);
     if (sPostGCEventsToConsole) {
       nsCOMPtr<nsIConsoleService> cs =
         do_GetService(NS_CONSOLESERVICE_CONTRACTID);
       if (cs) {
         cs->LogStringMessage(msg.get());
       }
     }
     if (gCCStats.mFile) {
@@ -1736,37 +1736,37 @@ nsJSContext::EndCycleCollectionCallback(
          u"\"forget_skippable\": { "
              u"\"times_before_cc\": %lu, "
              u"\"min\": %lu, "
              u"\"max\": %lu, "
              u"\"avg\": %lu, "
              u"\"total\": %lu, "
              u"\"removed\": %lu } "
        u"}";
+
     nsString json;
-
-    json.Adopt(nsTextFormatter::smprintf(kJSONFmt, PR_Now(), ccNowDuration,
-                                         gCCStats.mMaxSliceTime,
-                                         gCCStats.mTotalSliceTime,
-                                         gCCStats.mMaxGCDuration,
-                                         gCCStats.mMaxSkippableDuration,
-                                         gCCStats.mSuspected,
-                                         aResults.mVisitedRefCounted, aResults.mVisitedGCed,
-                                         aResults.mFreedRefCounted, aResults.mFreedGCed,
-                                         sCCollectedWaitingForGC,
-                                         sCCollectedZonesWaitingForGC,
-                                         sLikelyShortLivingObjectsNeedingGC,
-                                         aResults.mForcedGC,
-                                         sForgetSkippableBeforeCC,
-                                         minForgetSkippableTime / PR_USEC_PER_MSEC,
-                                         sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
-                                         (sTotalForgetSkippableTime / cleanups) /
-                                           PR_USEC_PER_MSEC,
-                                         sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
-                                         sRemovedPurples));
+    nsTextFormatter::ssprintf(json, kJSONFmt, PR_Now(), ccNowDuration,
+                              gCCStats.mMaxSliceTime,
+                              gCCStats.mTotalSliceTime,
+                              gCCStats.mMaxGCDuration,
+                              gCCStats.mMaxSkippableDuration,
+                              gCCStats.mSuspected,
+                              aResults.mVisitedRefCounted, aResults.mVisitedGCed,
+                              aResults.mFreedRefCounted, aResults.mFreedGCed,
+                              sCCollectedWaitingForGC,
+                              sCCollectedZonesWaitingForGC,
+                              sLikelyShortLivingObjectsNeedingGC,
+                              aResults.mForcedGC,
+                              sForgetSkippableBeforeCC,
+                              minForgetSkippableTime / PR_USEC_PER_MSEC,
+                              sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
+                              (sTotalForgetSkippableTime / cleanups) /
+                              PR_USEC_PER_MSEC,
+                              sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
+                              sRemovedPurples);
     nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
     if (observerService) {
       observerService->NotifyObservers(nullptr, "cycle-collection-statistics", json.get());
     }
   }
 
   // Update global state to indicate we have just run a cycle collection.
   sMinForgetSkippableTime = UINT32_MAX;
@@ -2248,22 +2248,22 @@ DOMGCSliceCallback(JSContext* aCx, JS::G
       sCCLockedOut = true;
       break;
     }
 
     case JS::GC_CYCLE_END: {
       PRTime delta = GetCollectionTimeDelta();
 
       if (sPostGCEventsToConsole) {
-        nsString prefix, gcstats;
+        nsString gcstats;
         gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
-        prefix.Adopt(nsTextFormatter::smprintf(u"GC(T+%.1f)[%s-%i] ",
-                                               double(delta) / PR_USEC_PER_SEC,
-                                               ProcessNameForCollectorLog(),
-                                               getpid()));
+        nsAutoString prefix;
+        nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
+                                  double(delta) / PR_USEC_PER_SEC,
+                                  ProcessNameForCollectorLog(), getpid());
         nsString msg = prefix + gcstats;
         nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (cs) {
           cs->LogStringMessage(msg.get());
         }
       }
 
       if (!sShuttingDown) {
@@ -2334,21 +2334,21 @@ DOMGCSliceCallback(JSContext* aCx, JS::G
              TaskCategory::GarbageCollection);
       }
 
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         nsCycleCollector_dispatchDeferredDeletion();
       }
 
       if (sPostGCEventsToConsole) {
-        nsString prefix, gcstats;
+        nsString gcstats;
         gcstats.Adopt(aDesc.formatSliceMessage(aCx));
-        prefix.Adopt(nsTextFormatter::smprintf(u"[%s-%i] ",
-                                               ProcessNameForCollectorLog(),
-                                               getpid()));
+        nsAutoString prefix;
+        nsTextFormatter::ssprintf(prefix, u"[%s-%i] ",
+                                  ProcessNameForCollectorLog(), getpid());
         nsString msg = prefix + gcstats;
         nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (cs) {
           cs->LogStringMessage(msg.get());
         }
       }
 
       break;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -488,34 +488,16 @@ public:
 #if defined(MOZ_WIDGET_ANDROID)
   virtual void EnableOrientationChangeListener() = 0;
   virtual void DisableOrientationChangeListener() = 0;
 #endif
 
   virtual void EnableTimeChangeNotifications() = 0;
   virtual void DisableTimeChangeNotifications() = 0;
 
-#ifdef MOZ_B2G
-  /**
-   * Tell the window that it should start to listen to the network event of the
-   * given aType.
-   *
-   * Inner windows only.
-   */
-  virtual void EnableNetworkEvent(mozilla::EventMessage aEventMessage) = 0;
-
-  /**
-   * Tell the window that it should stop to listen to the network event of the
-   * given aType.
-   *
-   * Inner windows only.
-   */
-  virtual void DisableNetworkEvent(mozilla::EventMessage aEventMessage) = 0;
-#endif // MOZ_B2G
-
   /**
    * Tell this window that there is an observer for gamepad input
    *
    * Inner windows only.
    */
   virtual void SetHasGamepadEventListener(bool aHasGamepad = true) = 0;
 
   /**
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -8,19 +8,16 @@
 #undef CreateEvent
 
 #include "mozilla/AddonPathService.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
-#ifdef MOZ_B2G
-#include "mozilla/Hal.h"
-#endif // #ifdef MOZ_B2G
 #include "mozilla/HalSensor.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/JSEventHandler.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
@@ -345,24 +342,16 @@ EventListenerManager::AddEventListenerIn
   } else if (aTypeAtom == nsGkAtoms::ondevicelight) {
     EnableDevice(eDeviceLight);
   } else if (aTypeAtom == nsGkAtoms::ondevicemotion) {
     EnableDevice(eDeviceMotion);
 #if defined(MOZ_WIDGET_ANDROID)
   } else if (aTypeAtom == nsGkAtoms::onorientationchange) {
     EnableDevice(eOrientationChange);
 #endif
-#ifdef MOZ_B2G
-  } else if (aTypeAtom == nsGkAtoms::onmoztimechange) {
-    EnableDevice(eTimeChange);
-  } else if (aTypeAtom == nsGkAtoms::onmoznetworkupload) {
-    EnableDevice(eNetworkUpload);
-  } else if (aTypeAtom == nsGkAtoms::onmoznetworkdownload) {
-    EnableDevice(eNetworkDownload);
-#endif // MOZ_B2G
   } else if (aTypeAtom == nsGkAtoms::ontouchstart ||
              aTypeAtom == nsGkAtoms::ontouchend ||
              aTypeAtom == nsGkAtoms::ontouchmove ||
              aTypeAtom == nsGkAtoms::ontouchcancel) {
     mMayHaveTouchEventListener = true;
     nsPIDOMWindowInner* window = GetInnerWindowForTarget();
     // we don't want touchevent listeners added by scrollbars to flip this flag
     // so we ignore listeners created with system event flag
@@ -488,21 +477,16 @@ EventListenerManager::IsDeviceType(Event
     case eAbsoluteDeviceOrientation:
     case eDeviceMotion:
     case eDeviceLight:
     case eDeviceProximity:
     case eUserProximity:
 #if defined(MOZ_WIDGET_ANDROID)
     case eOrientationChange:
 #endif
-#ifdef MOZ_B2G
-    case eTimeChange:
-    case eNetworkUpload:
-    case eNetworkDownload:
-#endif
       return true;
     default:
       break;
   }
   return false;
 }
 
 void
@@ -544,25 +528,16 @@ EventListenerManager::EnableDevice(Event
       window->EnableDeviceSensor(SENSOR_LINEAR_ACCELERATION);
       window->EnableDeviceSensor(SENSOR_GYROSCOPE);
       break;
 #if defined(MOZ_WIDGET_ANDROID)
     case eOrientationChange:
       window->EnableOrientationChangeListener();
       break;
 #endif
-#ifdef MOZ_B2G
-    case eTimeChange:
-      window->EnableTimeChangeNotifications();
-      break;
-    case eNetworkUpload:
-    case eNetworkDownload:
-      window->EnableNetworkEvent(aEventMessage);
-      break;
-#endif
     default:
       NS_WARNING("Enabling an unknown device sensor.");
       break;
   }
 }
 
 void
 EventListenerManager::DisableDevice(EventMessage aEventMessage)
@@ -599,25 +574,16 @@ EventListenerManager::DisableDevice(Even
     case eDeviceLight:
       window->DisableDeviceSensor(SENSOR_LIGHT);
       break;
 #if defined(MOZ_WIDGET_ANDROID)
     case eOrientationChange:
       window->DisableOrientationChangeListener();
       break;
 #endif
-#ifdef MOZ_B2G
-    case eTimeChange:
-      window->DisableTimeChangeNotifications();
-      break;
-    case eNetworkUpload:
-    case eNetworkDownload:
-      window->DisableNetworkEvent(aEventMessage);
-      break;
-#endif // MOZ_B2G
     default:
       NS_WARNING("Disabling an unknown device sensor.");
       break;
   }
 }
 
 void
 EventListenerManager::NotifyEventListenerRemoved(nsIAtom* aUserType,
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -637,31 +637,16 @@ WINDOW_ONLY_EVENT(vrdisplaypresentchange
                   eBasicEventClass)
 // Install events as per W3C Manifest spec
 WINDOW_ONLY_EVENT(appinstalled,
                   eAppInstalled,
                   EventNameType_None,
                   eBasicEventClass)
 
 
-#ifdef MOZ_B2G
-WINDOW_ONLY_EVENT(moztimechange,
-                  eTimeChange,
-                  EventNameType_None,
-                  eBasicEventClass)
-WINDOW_ONLY_EVENT(moznetworkupload,
-                  eNetworkUpload,
-                  EventNameType_None,
-                  eBasicEventClass)
-WINDOW_ONLY_EVENT(moznetworkdownload,
-                  eNetworkDownload,
-                  EventNameType_None,
-                  eBasicEventClass)
-#endif // MOZ_B2G
-
 TOUCH_EVENT(touchstart,
             eTouchStart,
             EventNameType_All,
             eTouchEventClass)
 TOUCH_EVENT(touchend,
             eTouchEnd,
             EventNameType_All,
             eTouchEventClass)
--- a/dom/events/KeyNameList.h
+++ b/dom/events/KeyNameList.h
@@ -27,20 +27,16 @@ DEFINE_KEYNAME_WITH_SAME_NAME(Unidentifi
 
 /******************************************************************************
  * Our Internal Key Values (must have "Moz" prefix)
  *****************************************************************************/
 DEFINE_KEYNAME_INTERNAL(PrintableKey, "MozPrintableKey")
 DEFINE_KEYNAME_INTERNAL(SoftLeft, "MozSoftLeft")
 DEFINE_KEYNAME_INTERNAL(SoftRight, "MozSoftRight")
 
-#ifdef MOZ_B2G
-DEFINE_KEYNAME_INTERNAL(HomeScreen, "MozHomeScreen")
-#endif // #ifdef MOZ_B2G
-
 /******************************************************************************
  * Modifier Keys
  *****************************************************************************/
 DEFINE_KEYNAME_WITH_SAME_NAME(Alt)
 DEFINE_KEYNAME_WITH_SAME_NAME(AltGraph)
 DEFINE_KEYNAME_WITH_SAME_NAME(CapsLock)
 DEFINE_KEYNAME_WITH_SAME_NAME(Control)
 DEFINE_KEYNAME_WITH_SAME_NAME(Fn)
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -198,17 +198,17 @@ TouchEvent::PrefEnabled(nsIDocShell* aDo
 
   bool enabled = false;
   if (touchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_ENABLED) {
     enabled = true;
   } else if (touchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_DISABLED) {
     enabled = false;
   } else {
     if (sPrefCacheValue == 2) {
-#if defined(MOZ_B2G) || defined(MOZ_WIDGET_ANDROID)
+#if defined(MOZ_WIDGET_ANDROID)
       // Touch support is always enabled on B2G and android.
       enabled = true;
 #elif defined(XP_WIN) || MOZ_WIDGET_GTK == 3
       static bool sDidCheckTouchDeviceSupport = false;
       static bool sIsTouchDeviceSupportPresent = false;
       // On Windows and GTK3 we auto-detect based on device support.
       if (!sDidCheckTouchDeviceSupport) {
         sDidCheckTouchDeviceSupport = true;
--- a/dom/events/test/pointerevents/test_touch_action.html
+++ b/dom/events/test/pointerevents/test_touch_action.html
@@ -3,16 +3,18 @@
  <head>
   <meta charset="utf-8">
   <title>W3C pointerevents/*touch-action*.html tests in Mochitest form</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
+var isWindows = (getPlatform() == "windows");
+
 var apz_touch_action_prefs = [
   // Obviously we need touch-action support enabled for testing touch-action.
   ["layout.css.touch_action.enabled", true],
   // Dropping the touch slop to 0 makes the tests easier to write because
   // we can just do a one-pixel drag to get over the pan threshold rather
   // than having to hard-code some larger value.
   ["apz.touch_start_tolerance", "0.0"],
   // The touchstart from the drag can turn into a long-tap if the touch-move
@@ -25,16 +27,17 @@ var apz_touch_action_prefs = [
   // want those pans to turn into fling animations, so we increase the
   // fling-stop threshold velocity to absurdly high.
   ["apz.fling_stopped_threshold", "10000"],
   // The helper_div_pan's div gets a displayport on scroll, but if the
   // test takes too long the displayport can expire before the new scroll
   // position is synced back to the main thread. So we disable displayport
   // expiry for these tests.
   ["apz.displayport_expiry_ms", 0],
+  ["apz.test.fails_with_native_injection", isWindows],
 ];
 
 function apzScriptInjector(name) {
   return function(childWin) {
     childWin._ACTIVE_TEST_NAME = name;
     injectScript('/tests/SimpleTest/paint_listener.js', childWin)()
     .then(injectScript('apz_test_utils.js', childWin))
     .then(injectScript('apz_test_native_event_utils.js', childWin))
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1020,23 +1020,16 @@ UploadLastDir::FetchDirectoryAndDisplayP
 
   nsIURI* docURI = aDoc->GetDocumentURI();
   NS_PRECONDITION(docURI, "docURI is null");
 
   nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
   nsCOMPtr<nsIContentPrefCallback2> prefCallback =
     new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
 
-#ifdef MOZ_B2G
-  if (XRE_IsContentProcess()) {
-    prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
-    return NS_OK;
-  }
-#endif
-
   // Attempt to get the CPS, if it's not present we'll fallback to use the Desktop folder
   nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   if (!contentPrefService) {
     prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
     return NS_OK;
   }
 
@@ -1051,22 +1044,16 @@ UploadLastDir::FetchDirectoryAndDisplayP
 nsresult
 UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir)
 {
   NS_PRECONDITION(aDoc, "aDoc is null");
   if (!aDir) {
     return NS_OK;
   }
 
-#ifdef MOZ_B2G
-  if (XRE_IsContentProcess()) {
-    return NS_OK;
-  }
-#endif
-
   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
   NS_PRECONDITION(docURI, "docURI is null");
 
   // Attempt to get the CPS, if it's not present we'll just return
   nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   if (!contentPrefService)
     return NS_ERROR_NOT_AVAILABLE;
@@ -5916,17 +5903,17 @@ HTMLInputElement::ChooseDirectory(ErrorR
   if (mType != NS_FORM_INPUT_FILE) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   // Script can call this method directly, so even though we don't show the
   // "Pick Folder..." button on platforms that don't have a directory picker
   // we have to redirect to the file picker here.
   InitFilePicker(
-#if defined(ANDROID) || defined(MOZ_B2G)
+#if defined(ANDROID)
                  // No native directory picker - redirect to plain file picker
                  FILE_PICKER_FILE
 #else
                  FILE_PICKER_DIRECTORY
 #endif
                  );
 }
 
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -797,34 +797,38 @@ nsTextInputSelectionImpl::CheckVisibilit
   }
 
   nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak);
   NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
 
   return shell->CheckVisibilityContent(aNode, aStartOffset, aEndOffset, aRetval);
 }
 
-class nsTextInputListener : public nsISelectionListener,
-                            public nsIDOMEventListener,
-                            public nsIEditorObserver,
-                            public nsSupportsWeakReference
+class nsTextInputListener final : public nsISelectionListener,
+                                  public nsIDOMEventListener,
+                                  public nsIEditorObserver,
+                                  public nsSupportsWeakReference
 {
 public:
   /** the default constructor
    */
   explicit nsTextInputListener(nsITextControlElement* aTxtCtrlElement);
 
   /** SetEditor gives an address to the editor that will be accessed
    *  @param aEditor the editor this listener calls for editing operations
    */
   void SetFrame(nsTextControlFrame *aFrame){mFrame = aFrame;}
 
   void SettingValue(bool aValue) { mSettingValue = aValue; }
   void SetValueChanged(bool aSetValueChanged) { mSetValueChanged = aSetValueChanged; }
 
+  // aFrame is an optional pointer to our frame, if not passed the method will
+  // use mFrame to compute it lazily.
+  void HandleValueChanged(nsTextControlFrame* aFrame = nullptr);
+
   NS_DECL_ISUPPORTS
 
   NS_DECL_NSISELECTIONLISTENER
 
   NS_DECL_NSIDOMEVENTLISTENER
 
   NS_DECL_NSIEDITOROBSERVER
 
@@ -1077,28 +1081,39 @@ nsTextInputListener::EditAction()
     mHadUndoItems = numUndoItems != 0;
     mHadRedoItems = numRedoItems != 0;
   }
 
   if (!weakFrame.IsAlive()) {
     return NS_OK;
   }
 
+  HandleValueChanged(frame);
+
+  return NS_OK;
+}
+
+void
+nsTextInputListener::HandleValueChanged(nsTextControlFrame* aFrame)
+{
   // Make sure we know we were changed (do NOT set this to false if there are
   // no undo items; JS could change the value and we'd still need to save it)
   if (mSetValueChanged) {
-    frame->SetValueChanged(true);
+    if (!aFrame) {
+      nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
+      aFrame = static_cast<nsTextControlFrame*> (frameBase);
+      NS_ASSERTION(aFrame, "Where is our frame?");
+    }
+    aFrame->SetValueChanged(true);
   }
 
   if (!mSettingValue) {
     mTxtCtrlElement->OnValueChanged(/* aNotify = */ true,
                                     /* aWasInteractiveUserChange = */ true);
   }
-
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextInputListener::BeforeEditAction()
 {
   return NS_OK;
 }
 
@@ -2679,16 +2694,21 @@ nsTextEditorState::SetValue(const nsAStr
             AutoDisableUndo disableUndo(textEditor);
             if (domSel) {
               // Since we don't use undo transaction, we don't need to store
               // selection state.  SetText will set selection to tail.
               domSel->RemoveAllRanges();
             }
 
             textEditor->SetText(newValue);
+
+            // Call the listener's HandleValueChanged() callback manually, since
+            // we don't use the transaction manager in this path and it could be
+            // that the editor would bypass calling the listener for that reason.
+            mTextListener->HandleValueChanged();
           }
 
           mTextListener->SetValueChanged(true);
           mTextListener->SettingValue(false);
 
           if (!notifyValueChanged) {
             // Listener doesn't update frame, but it is required for placeholder
             ValueWasChanged(true);
--- a/dom/html/test/test_fullscreen-api-race.html
+++ b/dom/html/test/test_fullscreen-api-race.html
@@ -30,17 +30,19 @@ SimpleTest.waitForExplicitFinish();
 // via queuing all exiting request as well as entering request together
 // which we may eventually need to do for bug 1188256.
 SimpleTest.requestFlakyTimeout(
   "Need to wait for potential fullscreen transition");
 addLoadEvent(function () {
   SpecialPowers.pushPrefEnv({
     "set": [
       ["full-screen-api.unprefix.enabled", true],
-      ["full-screen-api.allow-trusted-requests-only", false]
+      ["full-screen-api.allow-trusted-requests-only", false],
+      // Use legacy data: URI behavior to run test.
+      ["security.data_uri.unique_opaque_origin", false]
     ]
   }, next);
 });
 
 const OPEN_WINDOW_FUNCS = [
   function openNewTab() {
     return window.open("about:blank");
   },
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -3642,18 +3642,16 @@ UpgradeSchemaFrom18_0To19_0(mozIStorageC
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(19, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-#if !defined(MOZ_B2G)
-
 class NormalJSContext;
 
 class UpgradeFileIdsFunction final
   : public mozIStorageFunction
 {
   RefPtr<FileManager> mFileManager;
   nsAutoPtr<NormalJSContext> mContext;
 
@@ -3679,41 +3677,25 @@ private:
     }
   }
 
   NS_IMETHOD
   OnFunctionCall(mozIStorageValueArray* aArguments,
                  nsIVariant** aResult) override;
 };
 
-#endif // MOZ_B2G
-
 nsresult
 UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory,
                             mozIStorageConnection* aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
 
   AUTO_PROFILER_LABEL("UpgradeSchemaFrom19_0To20_0", STORAGE);
 
-#if defined(MOZ_B2G)
-
-  // We don't have to do the upgrade of file ids on B2G. The old format was
-  // only used by the previous single process implementation and B2G was
-  // always multi process. This is a nice optimization since the upgrade needs
-  // to deserialize all structured clones which reference a stored file or
-  // a mutable file.
-  nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-#else // MOZ_B2G
-
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT count(*) "
     "FROM object_data "
     "WHERE file_ids IS NOT NULL"
   ), getter_AddRefs(stmt));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -3802,18 +3784,16 @@ UpgradeSchemaFrom19_0To20_0(nsIFile* aFM
     return rv;
   }
 
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-#endif // MOZ_B2G
-
   return NS_OK;
 }
 
 class UpgradeIndexDataValuesFunction final
   : public mozIStorageFunction
 {
 public:
   UpgradeIndexDataValuesFunction()
@@ -19541,18 +19521,16 @@ AutoProgressHandler::OnProgress(mozIStor
 /*******************************************************************************
  * Local class implementations
  ******************************************************************************/
 
 NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction)
 NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction)
 NS_IMPL_ISUPPORTS(StripObsoleteOriginAttributesFunction, mozIStorageFunction);
 
-#if !defined(MOZ_B2G)
-
 nsresult
 UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory,
                              mozIStorageConnection* aConnection)
 {
   // This file manager doesn't need real origin info, etc. The only purpose is
   // to store file ids without adding more complexity or code duplication.
   RefPtr<FileManager> fileManager =
     new FileManager(PERSISTENCE_TYPE_INVALID,
@@ -19634,18 +19612,16 @@ UpgradeFileIdsFunction::OnFunctionCall(m
   }
 
   nsCOMPtr<nsIVariant> result = new mozilla::storage::TextVariant(fileIds);
 
   result.forget(aResult);
   return NS_OK;
 }
 
-#endif // MOZ_B2G
-
 // static
 void
 DatabaseOperationBase::GetBindingClauseForKeyRange(
                                             const SerializedKeyRange& aKeyRange,
                                             const nsACString& aKeyColumnName,
                                             nsAutoCString& aBindingClause)
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -799,18 +799,16 @@ public:
 
     aResult.set(obj);
     return true;
   }
 };
 
 // We don't need to upgrade database on B2G. See the comment in ActorsParent.cpp,
 // UpgradeSchemaFrom18_0To19_0()
-#if !defined(MOZ_B2G)
-
 class UpgradeDeserializationHelper
 {
 public:
   static bool
   CreateAndWrapMutableFile(JSContext* aCx,
                            StructuredCloneFile& aFile,
                            const MutableFileData& aData,
                            JS::MutableHandle<JSObject*> aResult)
@@ -867,18 +865,16 @@ public:
     MOZ_ASSERT(aFile.mType == StructuredCloneFile::eBlob);
 
     MOZ_ASSERT(false, "This should never be possible!");
 
     return false;
   }
 };
 
-#endif // MOZ_B2G
-
 template <class Traits>
 JSObject*
 CommonStructuredCloneReadCallback(JSContext* aCx,
                                   JSStructuredCloneReader* aReader,
                                   uint32_t aTag,
                                   uint32_t aData,
                                   void* aClosure)
 {
@@ -1209,18 +1205,16 @@ IDBObjectStore::DeserializeIndexValue(JS
                               JS::StructuredCloneScope::SameProcessSameThread,
                               aValue, &callbacks, &aCloneReadInfo)) {
     return false;
   }
 
   return true;
 }
 
-#if !defined(MOZ_B2G)
-
 // static
 bool
 IDBObjectStore::DeserializeUpgradeValue(JSContext* aCx,
                                         StructuredCloneReadInfo& aCloneReadInfo,
                                         JS::MutableHandle<JS::Value> aValue)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aCx);
@@ -1247,18 +1241,16 @@ IDBObjectStore::DeserializeUpgradeValue(
                               JS::StructuredCloneScope::SameProcessSameThread,
                               aValue, &callbacks, &aCloneReadInfo)) {
     return false;
   }
 
   return true;
 }
 
-#endif // MOZ_B2G
-
 #ifdef DEBUG
 
 void
 IDBObjectStore::AssertIsOnOwningThread() const
 {
   MOZ_ASSERT(mTransaction);
   mTransaction->AssertIsOnOwningThread();
 }
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -96,22 +96,20 @@ public:
                    StructuredCloneReadInfo& aCloneReadInfo,
                    JS::MutableHandle<JS::Value> aValue);
 
   static bool
   DeserializeIndexValue(JSContext* aCx,
                         StructuredCloneReadInfo& aCloneReadInfo,
                         JS::MutableHandle<JS::Value> aValue);
 
-#if !defined(MOZ_B2G)
   static bool
   DeserializeUpgradeValue(JSContext* aCx,
                           StructuredCloneReadInfo& aCloneReadInfo,
                           JS::MutableHandle<JS::Value> aValue);
-#endif
 
   static const JSClass*
   DummyPropClass()
   {
     return &sDummyPropJSClass;
   }
 
   void
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -30,18 +30,16 @@
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/PushNotifier.h"
-#include "mozilla/dom/LocalStorage.h"
-#include "mozilla/dom/StorageIPC.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/URLClassifierChild.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
@@ -2100,31 +2098,16 @@ ContentChild::AllocPMediaChild()
 }
 
 bool
 ContentChild::DeallocPMediaChild(media::PMediaChild *aActor)
 {
   return media::DeallocPMediaChild(aActor);
 }
 
-PStorageChild*
-ContentChild::AllocPStorageChild()
-{
-  MOZ_CRASH("We should never be manually allocating PStorageChild actors");
-  return nullptr;
-}
-
-bool
-ContentChild::DeallocPStorageChild(PStorageChild* aActor)
-{
-  StorageDBChild* child = static_cast<StorageDBChild*>(aActor);
-  child->ReleaseIPDLReference();
-  return true;
-}
-
 PSpeechSynthesisChild*
 ContentChild::AllocPSpeechSynthesisChild()
 {
 #ifdef MOZ_WEBSPEECH
   MOZ_CRASH("No one should be allocating PSpeechSynthesisChild actors");
 #else
   return nullptr;
 #endif
@@ -2312,17 +2295,17 @@ ContentChild::ProcessingError(Result aCo
     case MsgRouteError:
     case MsgValueError:
       break;
 
     default:
       MOZ_CRASH("not reached");
   }
 
-#if defined(MOZ_CRASHREPORTER) && !defined(MOZ_B2G)
+#if defined(MOZ_CRASHREPORTER)
   nsDependentCString reason(aReason);
   CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ipc_channel_error"), reason);
 #endif
   MOZ_CRASH("Content child abort due to IPC error");
 }
 
 nsresult
 ContentChild::AddRemoteAlertObserver(const nsString& aData,
@@ -3242,29 +3225,16 @@ ContentChild::RecvBlobURLRegistration(co
 mozilla::ipc::IPCResult
 ContentChild::RecvBlobURLUnregistration(const nsCString& aURI)
 {
   nsHostObjectProtocolHandler::RemoveDataEntry(aURI,
                                                /* aBroadcastToOtherProcesses = */ false);
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-ContentChild::RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
-                                             const nsString& aKey,
-                                             const nsString& aOldValue,
-                                             const nsString& aNewValue,
-                                             const IPC::Principal& aPrincipal,
-                                             const bool& aIsPrivate)
-{
-  LocalStorage::DispatchStorageEvent(aDocumentURI, aKey, aOldValue, aNewValue,
-                                     aPrincipal, aIsPrivate, nullptr, true);
-  return IPC_OK();
-}
-
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 bool
 ContentChild::SendGetA11yContentId()
 {
   return PContentChild::SendGetA11yContentId(&mMsaaID);
 }
 #endif // defined(XP_WIN) && defined(ACCESSIBILITY)
 
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -44,17 +44,16 @@ namespace ipc {
 class OptionalURIParams;
 class URIParams;
 }// namespace ipc
 
 namespace dom {
 
 class AlertObserver;
 class ConsoleListener;
-class PStorageChild;
 class ClonedMessageData;
 class TabChild;
 class GetFilesHelperChild;
 class FileCreatorHelper;
 
 class ContentChild final : public PContentChild
                          , public nsIWindowProvider
                          , public nsIContentChild
@@ -295,20 +294,16 @@ public:
   virtual PHandlerServiceChild* AllocPHandlerServiceChild() override;
 
   virtual bool DeallocPHandlerServiceChild(PHandlerServiceChild*) override;
 
   virtual PMediaChild* AllocPMediaChild() override;
 
   virtual bool DeallocPMediaChild(PMediaChild* aActor) override;
 
-  virtual PStorageChild* AllocPStorageChild() override;
-
-  virtual bool DeallocPStorageChild(PStorageChild* aActor) override;
-
   virtual PPresentationChild* AllocPPresentationChild() override;
 
   virtual bool DeallocPPresentationChild(PPresentationChild* aActor) override;
 
   virtual PFlyWebPublishedServerChild*
     AllocPFlyWebPublishedServerChild(const nsString& name,
                                      const FlyWebPublishOptions& params) override;
 
@@ -409,24 +404,16 @@ public:
   const nsAString& GetRemoteType() const;
 
   virtual mozilla::ipc::IPCResult
   RecvInitServiceWorkers(const ServiceWorkerConfiguration& aConfig) override;
 
   virtual mozilla::ipc::IPCResult
   RecvInitBlobURLs(nsTArray<BlobURLRegistrationData>&& aRegistations) override;
 
-  virtual mozilla::ipc::IPCResult
-  RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
-                                 const nsString& aKey,
-                                 const nsString& aOldValue,
-                                 const nsString& aNewValue,
-                                 const IPC::Principal& aPrincipal,
-                                 const bool& aIsPrivate) override;
-
   virtual mozilla::ipc::IPCResult RecvLastPrivateDocShellDestroyed() override;
 
   virtual mozilla::ipc::IPCResult
   RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority) override;
 
   virtual mozilla::ipc::IPCResult RecvMinimizeMemoryUsage() override;
 
   virtual mozilla::ipc::IPCResult RecvLoadAndRegisterSheet(const URIParams& aURI,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -43,18 +43,16 @@
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/GeolocationBinding.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/Notification.h"
 #include "mozilla/dom/PContentBridgeParent.h"
 #include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/PCycleCollectWithLogsParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
-#include "mozilla/dom/Storage.h"
-#include "mozilla/dom/StorageIPC.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/PresentationParent.h"
 #include "mozilla/dom/PPresentationParent.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
@@ -2095,23 +2093,21 @@ ContentParent::ContentParent(ContentPare
   // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the
   // PID along with the warning.
   nsDebugImpl::SetMultiprocessMode("Parent");
 
 #if defined(XP_WIN)
   if (XRE_IsParentProcess()) {
     audio::AudioNotificationSender::Init();
   }
-#if !defined(MOZ_B2G)
   // Request Windows message deferral behavior on our side of the PContent
   // channel. Generally only applies to the situation where we get caught in
   // a deadlock with the plugin process when sending CPOWs.
   GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
-#endif
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   ChildPrivileges privs = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE)
                           ? base::PRIVILEGES_FILEREAD
                           : base::PRIVILEGES_DEFAULT;
   mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, privs);
 }
 
@@ -2929,17 +2925,17 @@ ContentParent::KillHard(const char* aRea
   // process handle becomes invalid on the first call, causing a second call
   // to crash our process - more details in bug 890840.
   if (mCalledKillHard) {
     return;
   }
   mCalledKillHard = true;
   mForceKillTimer = nullptr;
 
-#if defined(MOZ_CRASHREPORTER) && !defined(MOZ_B2G)
+#if defined(MOZ_CRASHREPORTER)
   // We're about to kill the child process associated with this content.
   // Something has gone wrong to get us here, so we generate a minidump
   // of the parent and child for submission to the crash server.
   if (mCrashReporter) {
     // GeneratePairedMinidump creates two minidumps for us - the main
     // one is for the content process we're about to kill, and the other
     // one is for the main browser process. That second one is the extra
     // minidump tagging along, so we have to tell the crash reporter that
@@ -2969,17 +2965,17 @@ ContentParent::KillHard(const char* aRea
   }
 #endif
   OnGenerateMinidumpComplete(false);
 }
 
 void
 ContentParent::OnGenerateMinidumpComplete(bool aDumpResult)
 {
-#if defined(MOZ_CRASHREPORTER) && !defined(MOZ_B2G)
+#if defined(MOZ_CRASHREPORTER)
   if (mCrashReporter && aDumpResult) {
     // CrashReporterHost::GenerateMinidumpAndPair() is successful.
     mCreatedPairedMinidumps = mCrashReporter->FinalizeCrashReport();
   }
 #endif
 
   Unused << aDumpResult; // Don't care about result if no minidump was requested.
 
@@ -3299,30 +3295,16 @@ ContentParent::AllocPMediaParent()
 }
 
 bool
 ContentParent::DeallocPMediaParent(media::PMediaParent *aActor)
 {
   return media::DeallocPMediaParent(aActor);
 }
 
-PStorageParent*
-ContentParent::AllocPStorageParent()
-{
-  return new StorageDBParent();
-}
-
-bool
-ContentParent::DeallocPStorageParent(PStorageParent* aActor)
-{
-  StorageDBParent* child = static_cast<StorageDBParent*>(aActor);
-  child->ReleaseIPDLReference();
-  return true;
-}
-
 PPresentationParent*
 ContentParent::AllocPPresentationParent()
 {
   RefPtr<PresentationParent> actor = new PresentationParent();
   return actor.forget().take();
 }
 
 bool
@@ -4958,35 +4940,16 @@ ContentParent::RecvUnstoreAndBroadcastBl
                                                false /* Don't broadcast */);
   BroadcastBlobURLUnregistration(aURI, this);
   mBlobURLs.RemoveElement(aURI);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-ContentParent::RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
-                                               const nsString& aKey,
-                                               const nsString& aOldValue,
-                                               const nsString& aNewValue,
-                                               const Principal& aPrincipal,
-                                               const bool& aIsPrivate)
-{
-  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
-    if (cp != this) {
-      Unused << cp->SendDispatchLocalStorageChange(
-        nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
-        nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate);
-    }
-  }
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 ContentParent::RecvGetA11yContentId(uint32_t* aContentId)
 {
 #if defined(XP_WIN32) && defined(ACCESSIBILITY)
   *aContentId = a11y::AccessibleWrap::GetContentProcessIdFor(ChildID());
   MOZ_ASSERT(*aContentId);
   return IPC_OK();
 #else
   return IPC_FAIL_NO_REASON(this);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -92,17 +92,16 @@ struct TextureFactoryIdentifier;
 namespace layout {
 class PRenderFrameParent;
 } // namespace layout
 
 namespace dom {
 
 class Element;
 class TabParent;
-class PStorageParent;
 class ClonedMessageData;
 class MemoryReport;
 class TabContext;
 class ContentBridgeParent;
 class GetFilesHelper;
 class MemoryReportRequestHost;
 
 class ContentParent final : public PContentParent
@@ -444,23 +443,16 @@ public:
   virtual mozilla::ipc::IPCResult RecvPHalConstructor(PHalParent* aActor) override
   {
     return PContentParent::RecvPHalConstructor(aActor);
   }
 
   virtual PHeapSnapshotTempFileHelperParent*
   AllocPHeapSnapshotTempFileHelperParent() override;
 
-  virtual PStorageParent* AllocPStorageParent() override;
-
-  virtual mozilla::ipc::IPCResult RecvPStorageConstructor(PStorageParent* aActor) override
-  {
-    return PContentParent::RecvPStorageConstructor(aActor);
-  }
-
   virtual PJavaScriptParent*
   AllocPJavaScriptParent() override;
 
   virtual mozilla::ipc::IPCResult
   RecvPJavaScriptConstructor(PJavaScriptParent* aActor) override
   {
     return PContentParent::RecvPJavaScriptConstructor(aActor);
   }
@@ -562,24 +554,16 @@ public:
   RecvStoreAndBroadcastBlobURLRegistration(const nsCString& aURI,
                                            const IPCBlob& aBlob,
                                            const Principal& aPrincipal) override;
 
   virtual mozilla::ipc::IPCResult
   RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aURI) override;
 
   virtual mozilla::ipc::IPCResult
-  RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
-                                  const nsString& aKey,
-                                  const nsString& aOldValue,
-                                  const nsString& aNewValue,
-                                  const IPC::Principal& aPrincipal,
-                                  const bool& aIsPrivate) override;
-
-  virtual mozilla::ipc::IPCResult
   RecvGetA11yContentId(uint32_t* aContentId) override;
 
   virtual mozilla::ipc::IPCResult
   RecvA11yHandlerControl(const uint32_t& aPid,
                          const IHandlerControlHolder& aHandlerControl) override;
 
   virtual int32_t Pid() const override;
 
@@ -926,18 +910,16 @@ private:
   virtual PHandlerServiceParent* AllocPHandlerServiceParent() override;
 
   virtual bool DeallocPHandlerServiceParent(PHandlerServiceParent*) override;
 
   virtual PMediaParent* AllocPMediaParent() override;
 
   virtual bool DeallocPMediaParent(PMediaParent* aActor) override;
 
-  virtual bool DeallocPStorageParent(PStorageParent* aActor) override;
-
   virtual PPresentationParent* AllocPPresentationParent() override;
 
   virtual bool DeallocPPresentationParent(PPresentationParent* aActor) override;
 
   virtual mozilla::ipc::IPCResult RecvPPresentationConstructor(PPresentationParent* aActor) override;
 
   virtual PFlyWebPublishedServerParent*
     AllocPFlyWebPublishedServerParent(const nsString& name,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -26,17 +26,16 @@ include protocol PGMPService;
 include protocol PPluginModule;
 include protocol PGMP;
 include protocol PPrinting;
 include protocol PChildToParentStream;
 include protocol PParentToChildStream;
 include protocol POfflineCacheUpdate;
 include protocol PRenderFrame;
 include protocol PSpeechSynthesis;
-include protocol PStorage;
 include protocol PTestShell;
 include protocol PJavaScript;
 include protocol PRemoteSpellcheckEngine;
 include protocol PWebBrowserPersistDocument;
 include protocol PWebrtcGlobal;
 include protocol PPresentation;
 include protocol PURLClassifier;
 include protocol PURLClassifierLocal;
@@ -286,17 +285,16 @@ nested(upto inside_cpow) sync protocol P
     manages PIPCBlobInputStream;
     manages PMedia;
     manages PNecko;
     manages POfflineCacheUpdate;
     manages PPrinting;
     manages PChildToParentStream;
     manages PParentToChildStream;
     manages PSpeechSynthesis;
-    manages PStorage;
     manages PTestShell;
     manages PJavaScript;
     manages PRemoteSpellcheckEngine;
     manages PWebBrowserPersistDocument;
     manages PWebrtcGlobal;
     manages PPresentation;
     manages PFlyWebPublishedServer;
     manages PURLClassifier;
@@ -561,23 +559,16 @@ child:
 
     async GetFilesResponse(nsID aID, GetFilesResponseResult aResult);
 
     async BlobURLRegistration(nsCString aURI, IPCBlob aBlob,
                               Principal aPrincipal);
 
     async BlobURLUnregistration(nsCString aURI);
 
-    async DispatchLocalStorageChange(nsString documentURI,
-                                     nsString key,
-                                     nsString oldValue,
-                                     nsString newValue,
-                                     Principal principal,
-                                     bool isPrivate);
-
     async GMPsChanged(GMPCapabilityData[] capabilities);
 
 
     async FileCreationResponse(nsID aID, FileCreationResult aResult);
 
     /**
      * Sending an activate message moves focus to the child.
      */
@@ -708,18 +699,16 @@ parent:
     async PNecko();
 
     async PPrinting();
 
     async PChildToParentStream();
 
     async PSpeechSynthesis();
 
-    nested(inside_cpow) async PStorage();
-
     async PMedia();
 
     async PWebrtcGlobal();
 
     async PPresentation();
 
     async PFlyWebPublishedServer(nsString name, FlyWebPublishOptions params);
 
@@ -1043,23 +1032,16 @@ parent:
                                int64_t lastModified, bool aExistenceCheck,
                                bool aIsFromNsIFile);
 
      async StoreAndBroadcastBlobURLRegistration(nsCString url, IPCBlob blob,
                                                 Principal principal);
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
-     async BroadcastLocalStorageChange(nsString documentURI,
-                                       nsString key,
-                                       nsString oldValue,
-                                       nsString newValue,
-                                       Principal principal,
-                                       bool isPrivate);
-
     /**
      * Messages for communicating child Telemetry to the parent process
      */
     async AccumulateChildHistograms(Accumulation[] accumulations);
     async AccumulateChildKeyedHistograms(KeyedAccumulation[] accumulations);
     async UpdateChildScalars(ScalarAction[] updates);
     async UpdateChildKeyedScalars(KeyedScalarAction[] updates);
     async RecordChildEvents(ChildEventData[] events);
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -241,20 +241,16 @@ MaybeInvalidTabContext::MaybeInvalidTabC
       originAttributes = ipcContext.originAttributes();
       break;
     }
     case IPCTabContext::TUnsafeIPCTabContext: {
       // XXXcatalinb: This used *only* by ServiceWorkerClients::OpenWindow.
       // It is meant as a temporary solution until service workers can
       // provide a TabChild equivalent. Don't allow this on b2g since
       // it might be used to escalate privileges.
-#ifdef MOZ_B2G
-      mInvalidReason = "ServiceWorkerClients::OpenWindow is not supported.";
-      return;
-#endif
       if (!Preferences::GetBool("dom.serviceWorkers.enabled", false)) {
         mInvalidReason = "ServiceWorkers should be enabled.";
         return;
       }
 
       break;
     }
     default: {
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -121,17 +121,16 @@ LOCAL_INCLUDES += [
     '/chrome',
     '/docshell/base',
     '/dom/base',
     '/dom/events',
     '/dom/filesystem',
     '/dom/geolocation',
     '/dom/media/webspeech/synth/ipc',
     '/dom/security',
-    '/dom/storage',
     '/dom/workers',
     '/extensions/cookie',
     '/extensions/spellcheck/src',
     '/gfx/2d',
     '/hal/sandbox',
     '/layout/base',
     '/media/webrtc',
     '/netwerk/base',
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -342,27 +342,19 @@ AudioStream::Init(uint32_t aNumChannels,
 
   mDumpFile = OpenDumpFile(aNumChannels, aRate);
 
   cubeb_stream_params params;
   params.rate = aRate;
   params.channels = mOutChannels;
   params.layout = CubebUtils::ConvertChannelMapToCubebLayout(aChannelMap);
 #if defined(__ANDROID__)
-#if defined(MOZ_B2G)
-  params.stream_type = CubebUtils::ConvertChannelToCubebType(aAudioChannel);
-#else
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
-  if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
-    return NS_ERROR_INVALID_ARG;
-  }
-#endif
-
   params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
   mAudioClock.Init(aRate);
 
   cubeb* cubebContext = CubebUtils::GetCubebContext();
   if (!cubebContext) {
     NS_WARNING("Can't get cubeb context!");
     CubebUtils::ReportCubebStreamInitFailure(true);
     return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR;
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -485,43 +485,16 @@ cubeb_channel_layout ConvertChannelMapTo
     case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE;
     case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE;
     default:
       NS_ERROR("The channel map is unsupported");
       return CUBEB_LAYOUT_UNDEFINED;
   }
 }
 
-#if defined(__ANDROID__) && defined(MOZ_B2G)
-cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel)
-{
-  switch(aChannel) {
-    case dom::AudioChannel::Normal:
-      /* FALLTHROUGH */
-    case dom::AudioChannel::Content:
-      return CUBEB_STREAM_TYPE_MUSIC;
-    case dom::AudioChannel::Notification:
-      return CUBEB_STREAM_TYPE_NOTIFICATION;
-    case dom::AudioChannel::Alarm:
-      return CUBEB_STREAM_TYPE_ALARM;
-    case dom::AudioChannel::Telephony:
-      return CUBEB_STREAM_TYPE_VOICE_CALL;
-    case dom::AudioChannel::Ringer:
-      return CUBEB_STREAM_TYPE_RING;
-    case dom::AudioChannel::System:
-      return CUBEB_STREAM_TYPE_SYSTEM;
-    case dom::AudioChannel::Publicnotification:
-      return CUBEB_STREAM_TYPE_SYSTEM_ENFORCED;
-    default:
-      NS_ERROR("The value of AudioChannel is invalid");
-      return CUBEB_STREAM_TYPE_MAX;
-  }
-}
-#endif
-
 void GetCurrentBackend(nsAString& aBackend)
 {
   cubeb* cubebContext = GetCubebContext();
   if (cubebContext) {
     const char* backend = cubeb_get_backend_id(cubebContext);
     if (backend) {
       aBackend.AssignASCII(backend);
       return;
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -45,19 +45,16 @@ bool GetFirstStream();
 cubeb* GetCubebContext();
 cubeb* GetCubebContextUnlocked();
 void ReportCubebStreamInitFailure(bool aIsFirstStream);
 void ReportCubebBackendUsed();
 uint32_t GetCubebPlaybackLatencyInMilliseconds();
 Maybe<uint32_t> GetCubebMSGLatencyInFrames();
 bool CubebLatencyPrefSet();
 cubeb_channel_layout ConvertChannelMapToCubebLayout(uint32_t aChannelMap);
-#if defined(__ANDROID__) && defined(MOZ_B2G)
-cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel);
-#endif
 void GetCurrentBackend(nsAString& aBackend);
 void GetPreferredChannelLayout(nsAString& aLayout);
 void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
                          Side aSide);
 } // namespace CubebUtils
 } // namespace mozilla
 
 #endif // CubebUtils_h_
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -623,25 +623,17 @@ AudioCallbackDriver::Init()
   bool firstStream = CubebUtils::GetFirstStream();
 
   MOZ_ASSERT(!NS_IsMainThread(),
       "This is blocking and should never run on the main thread.");
 
   mSampleRate = output.rate = CubebUtils::PreferredSampleRate();
 
 #if defined(__ANDROID__)
-#if defined(MOZ_B2G)
-  output.stream_type = CubebUtils::ConvertChannelToCubebType(mAudioChannel);
-#else
   output.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
-  if (output.stream_type == CUBEB_STREAM_TYPE_MAX) {
-    NS_WARNING("Bad stream type");
-    return false;
-  }
 #else
   (void)mAudioChannel;
 #endif
 
   output.channels = mGraphImpl->AudioChannelCount();
   if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
     output.format = CUBEB_SAMPLE_S16NE;
   } else {
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -64,20 +64,16 @@
 
 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
 #include "MediaEngineDefault.h"
 #if defined(MOZ_WEBRTC)
 #include "MediaEngineWebRTC.h"
 #include "browser_logging/WebRtcLog.h"
 #endif
 
-#ifdef MOZ_B2G
-#include "MediaPermissionGonk.h"
-#endif
-
 #if defined (XP_WIN)
 #include "mozilla/WindowsVersion.h"
 #include <winsock2.h>
 #include <iphlpapi.h>
 #include <tchar.h>
 #endif
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
@@ -1932,20 +1928,16 @@ MediaManager::Get() {
     };
 
     sSingleton->mShutdownBlocker = new Blocker();
     nsresult rv = shutdownPhase->AddBlocker(sSingleton->mShutdownBlocker,
                                             NS_LITERAL_STRING(__FILE__),
                                             __LINE__,
                                             NS_LITERAL_STRING("Media shutdown"));
     MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
-#ifdef MOZ_B2G
-    // Init MediaPermissionManager before sending out any permission requests.
-    (void) MediaPermissionManager::GetInstance();
-#endif //MOZ_B2G
   }
   return sSingleton;
 }
 
 /* static */  MediaManager*
 MediaManager::GetIfExists() {
   return sSingleton;
 }
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -823,17 +823,17 @@ class RTCPeerConnection {
         sdp = this._localIdp.addIdentityAttribute(sdp);
       }
       return Cu.cloneInto({ type: "answer", sdp }, this._win);
     });
   }
 
   async _getPermission() {
     if (!this._havePermission) {
-      let privileged = this._isChrome || AppConstants.MOZ_B2G ||
+      let privileged = this._isChrome ||
           Services.prefs.getBoolPref("media.navigator.permission.disabled");
 
       if (privileged) {
         this._havePermission = Promise.resolve();
       } else {
         this._havePermission = new Promise((resolve, reject) => {
           this._settlePermission = { allow: resolve, deny: reject };
           let outerId = this._win.QueryInterface(Ci.nsIInterfaceRequestor).
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -50,21 +50,18 @@ public:
 
   static const int DEFAULT_VIDEO_FPS = 30;
   static const int DEFAULT_VIDEO_MIN_FPS = 10;
   static const int DEFAULT_43_VIDEO_WIDTH = 640;
   static const int DEFAULT_43_VIDEO_HEIGHT = 480;
   static const int DEFAULT_169_VIDEO_WIDTH = 1280;
   static const int DEFAULT_169_VIDEO_HEIGHT = 720;
 
-#ifndef MOZ_B2G
   static const int DEFAULT_SAMPLE_RATE = 32000;
-#else
-  static const int DEFAULT_SAMPLE_RATE = 16000;
-#endif
+
   // This allows using whatever rate the graph is using for the
   // MediaStreamTrack. This is useful for microphone data, we know it's already
   // at the correct rate for insertion in the MSG.
   static const int USE_GRAPH_RATE = -1;
 
   /* Populate an array of video sources in the nsTArray. Also include devices
    * that are currently unavailable. */
   virtual void EnumerateVideoDevices(dom::MediaSourceEnum,
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -809,28 +809,26 @@ MediaEngineWebRTCMicrophoneSource::Alloc
   mChannel = mVoEBase->CreateChannel();
   if (mChannel >= 0) {
     if (!mVoENetwork->RegisterExternalTransport(mChannel, *mNullTransport)) {
       mSampleFrequency = MediaEngine::DEFAULT_SAMPLE_RATE;
       LOG(("%s: sampling rate %u", __FUNCTION__, mSampleFrequency));
 
       // Check for availability.
       if (!mAudioInput->SetRecordingDevice(mCapIndex)) {
-#ifndef MOZ_B2G
         // Because of the permission mechanism of B2G, we need to skip the status
         // check here.
         bool avail = false;
         mAudioInput->GetRecordingDeviceStatus(avail);
         if (!avail) {
           if (sChannelsOpen == 0) {
             DeInitEngine();
           }
           return false;
         }
-#endif // MOZ_B2G
 
         // Set "codec" to PCM, 32kHz on device's channels
         ScopedCustomReleasePtr<webrtc::VoECodec> ptrVoECodec(webrtc::VoECodec::GetInterface(mVoiceEngine));
         if (ptrVoECodec) {
           webrtc::CodecInst codec;
           strcpy(codec.plname, ENCODING);
           codec.channels = CHANNELS;
           uint32_t maxChannels = 0;
--- a/dom/media/webrtc/WebrtcGlobal.h
+++ b/dom/media/webrtc/WebrtcGlobal.h
@@ -12,52 +12,16 @@
 
 typedef mozilla::dom::RTCStatsReportInternal StatsReport;
 typedef nsTArray< nsAutoPtr<StatsReport>> RTCReports;
 typedef mozilla::dom::Sequence<nsString> WebrtcGlobalLog;
 
 namespace IPC {
 
 template<typename T>
-struct ParamTraits<mozilla::dom::Optional<T>>
-{
-  typedef mozilla::dom::Optional<T> paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam)
-  {
-    if (aParam.WasPassed()) {
-      WriteParam(aMsg, true);
-      WriteParam(aMsg, aParam.Value());
-      return;
-    }
-
-    WriteParam(aMsg, false);
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
-  {
-    bool was_passed = false;
-
-    if (!ReadParam(aMsg, aIter, &was_passed)) {
-      return false;
-    }
-
-    aResult->Reset(); //XXX Optional_base seems to reach this point with isSome true.
-
-    if (was_passed) {
-      if (!ReadParam(aMsg, aIter, &(aResult->Construct()))) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-};
-
-template<typename T>
 struct ParamTraits<mozilla::dom::Sequence<T>>
 {
   typedef mozilla::dom::Sequence<T> paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, static_cast<const FallibleTArray<T>&>(aParam));
   }
--- a/dom/notification/DesktopNotification.h
+++ b/dom/notification/DesktopNotification.h
@@ -154,20 +154,16 @@ class AlertServiceObserver: public nsIOb
   NS_IMETHOD
   Observe(nsISupports* aSubject,
           const char* aTopic,
           const char16_t* aData) override
   {
 
     // forward to parent
     if (mNotification) {
-#ifdef MOZ_B2G
-    if (NS_FAILED(mNotification->CheckInnerWindowCorrectness()))
-      return NS_ERROR_NOT_AVAILABLE;
-#endif
       mNotification->HandleAlertServiceNotification(aTopic);
     }
     return NS_OK;
   };
 
  private:
   virtual ~AlertServiceObserver() {}
 
--- a/dom/quota/SerializationHelpers.h
+++ b/dom/quota/SerializationHelpers.h
@@ -5,22 +5,47 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_quota_SerializationHelpers_h
 #define mozilla_dom_quota_SerializationHelpers_h
 
 #include "ipc/IPCMessageUtils.h"
 
 #include "mozilla/dom/quota/PersistenceType.h"
+#include "mozilla/OriginAttributes.h"
 
 namespace IPC {
 
 template <>
 struct ParamTraits<mozilla::dom::quota::PersistenceType> :
   public ContiguousEnumSerializer<
                                mozilla::dom::quota::PersistenceType,
                                mozilla::dom::quota::PERSISTENCE_TYPE_PERSISTENT,
                                mozilla::dom::quota::PERSISTENCE_TYPE_INVALID>
 { };
 
+template <>
+struct ParamTraits<mozilla::OriginAttributesPattern>
+{
+  typedef mozilla::OriginAttributesPattern paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mAppId);
+    WriteParam(aMsg, aParam.mFirstPartyDomain);
+    WriteParam(aMsg, aParam.mInIsolatedMozBrowser);
+    WriteParam(aMsg, aParam.mPrivateBrowsingId);
+    WriteParam(aMsg, aParam.mUserContextId);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    return ReadParam(aMsg, aIter, &aResult->mAppId) &&
+           ReadParam(aMsg, aIter, &aResult->mFirstPartyDomain) &&
+           ReadParam(aMsg, aIter, &aResult->mInIsolatedMozBrowser) &&
+           ReadParam(aMsg, aIter, &aResult->mPrivateBrowsingId) &&
+           ReadParam(aMsg, aIter, &aResult->mUserContextId);
+  }
+};
+
 } // namespace IPC
 
 #endif // mozilla_dom_quota_SerializationHelpers_h
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -863,25 +863,16 @@ nsCSPContext::SendReports(nsISupports* a
                           nsAString& aViolatedDirective,
                           uint32_t aViolatedPolicyIndex,
                           nsAString& aSourceFile,
                           nsAString& aScriptSample,
                           uint32_t aLineNum)
 {
   NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
 
-#ifdef MOZ_B2G
-  // load group information (on process-split necko implementations like b2g).
-  // (fix this in bug 1011086)
-  if (!mCallingChannelLoadGroup) {
-    NS_WARNING("Load group required but not present for report sending; cannot send CSP violation reports");
-    return NS_ERROR_FAILURE;
-  }
-#endif
-
   dom::CSPReport report;
   nsresult rv;
 
   // blocked-uri
   if (aBlockedContentSource) {
     nsAutoCString reportBlockedURI;
     nsCOMPtr<nsIURI> uri = do_QueryInterface(aBlockedContentSource);
     // could be a string or URI
--- a/dom/security/test/csp/file_data_csp_inheritance.html
+++ b/dom/security/test/csp/file_data_csp_inheritance.html
@@ -8,14 +8,16 @@
 <body>
 <iframe id="dataFrame" src="data:text/html,<body>should inherit csp</body>"></iframe>
 
 <script type="application/javascript">
   // get the csp in JSON notation from the principal
   var frame = document.getElementById("dataFrame");
   var principal = SpecialPowers.wrap(frame.contentDocument).nodePrincipal;
   var cspJSON = principal.cspJSON;
-  var result = principal.cspJSON ? "dataInheritsCSP" : "dataDoesNotInheritCSP";
-  window.parent.postMessage({result}, "*");
+  var cspOBJ = JSON.parse(principal.cspJSON);
+  // make sure we got >>one<< policy
+  var policies = cspOBJ["csp-policies"];
+  window.parent.postMessage({result: policies.length}, "*");
 </script>
 
 </body>
 </html>
--- a/dom/security/test/csp/test_data_csp_inheritance.html
+++ b/dom/security/test/csp/test_data_csp_inheritance.html
@@ -17,17 +17,19 @@ SimpleTest.waitForExplicitFinish();
  * We load an iframe using a meta CSP which includes another iframe
  * using a data: URI. We make sure the CSP gets inherited into
  * the data: URI iframe.
  */
 
 window.addEventListener("message", receiveMessage);
 function receiveMessage(event) {
   window.removeEventListener("message", receiveMessage);
-  is(event.data.result, "dataInheritsCSP",
+  // toplevel CSP should apply to data: URI iframe hence resulting
+  // in 1 applied policy.
+  is(event.data.result, 1,
      "data: URI iframe inherits CSP from including context");
   SimpleTest.finish();
 }
 
 document.getElementById("testframe").src = "file_data_csp_inheritance.html";
 
 </script>
 </body>
--- a/dom/storage/LocalStorage.cpp
+++ b/dom/storage/LocalStorage.cpp
@@ -16,16 +16,18 @@
 #include "nsICookiePermission.h"
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/StorageBinding.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/EnumSet.h"
 #include "nsThreadUtils.h"
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 
 namespace mozilla {
@@ -174,23 +176,30 @@ LocalStorage::Clear(nsIPrincipal& aSubje
   }
 }
 
 void
 LocalStorage::BroadcastChangeNotification(const nsAString& aKey,
                                           const nsAString& aOldValue,
                                           const nsAString& aNewValue)
 {
-  if (!XRE_IsParentProcess() && Principal()) {
-    // If we are in a child process, we want to send a message to the parent in
-    // order to broadcast the StorageEvent correctly to any child process.
-    dom::ContentChild* cc = dom::ContentChild::GetSingleton();
-    Unused << NS_WARN_IF(!cc->SendBroadcastLocalStorageChange(
-      mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
-      IPC::Principal(Principal()), mIsPrivate));
+  if (Principal()) {
+    // We want to send a message to the parent in order to broadcast the
+    // StorageEvent correctly to any child process.
+
+    PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
+    MOZ_ASSERT(actor);
+
+    PrincipalInfo principalInfo;
+    nsresult rv = PrincipalToPrincipalInfo(Principal(), &principalInfo);
+    if (!NS_WARN_IF(NS_FAILED(rv))) {
+      Unused << NS_WARN_IF(!actor->SendBroadcastLocalStorageChange(
+        mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
+        principalInfo, mIsPrivate));
+    }
   }
 
   DispatchStorageEvent(mDocumentURI, aKey, aOldValue, aNewValue,
                        Principal(), mIsPrivate, this, false);
 }
 
 /* static */ void
 LocalStorage::DispatchStorageEvent(const nsAString& aDocumentURI,
@@ -199,26 +208,16 @@ LocalStorage::DispatchStorageEvent(const
                                    const nsAString& aNewValue,
                                    nsIPrincipal* aPrincipal,
                                    bool aIsPrivate,
                                    Storage* aStorage,
                                    bool aImmediateDispatch)
 {
   NotifyChange(aStorage, aPrincipal, aKey, aOldValue, aNewValue,
                u"localStorage", aDocumentURI, aIsPrivate, aImmediateDispatch);
-
-  // If we are in the parent process and we have the principal, we want to
-  // broadcast this event to every other process.
-  if (XRE_IsParentProcess() && aPrincipal) {
-    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
-      Unused << cp->SendDispatchLocalStorageChange(
-        nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
-        nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate);
-    }
-  }
 }
 
 void
 LocalStorage::ApplyEvent(StorageEvent* aStorageEvent)
 {
   MOZ_ASSERT(aStorageEvent);
 
   nsAutoString key;
--- a/dom/storage/LocalStorageCache.cpp
+++ b/dom/storage/LocalStorageCache.cpp
@@ -19,20 +19,16 @@
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
 
-// static
-StorageDBBridge* LocalStorageCache::sDatabase = nullptr;
-bool LocalStorageCache::sDatabaseDown = false;
-
 namespace {
 
 const uint32_t kDefaultSet = 0;
 const uint32_t kPrivateSet = 1;
 const uint32_t kSessionSet = 2;
 
 inline uint32_t
 GetDataSetIndex(bool aPrivate, bool aSessionOnly)
@@ -238,23 +234,24 @@ LocalStorageCache::ProcessUsageDelta(uin
 
 void
 LocalStorageCache::Preload()
 {
   if (mLoaded || !mPersistent) {
     return;
   }
 
-  if (!StartDatabase()) {
+  StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+  if (!storageChild) {
     mLoaded = true;
     mLoadResult = NS_ERROR_FAILURE;
     return;
   }
 
-  sDatabase->AsyncPreload(this);
+  storageChild->AsyncPreload(this);
 }
 
 namespace {
 
 // The AutoTimer provided by telemetry headers is only using static,
 // i.e. compile time known ID, but here we know the ID only at run time.
 // Hence a new class.
 class TelemetryAutoTimer
@@ -304,17 +301,17 @@ LocalStorageCache::WaitForPreload(Teleme
   // SyncPreload will just wait for it to finish rather then synchronously
   // read from the database.  It seems to me more optimal.
 
   // TODO place for A/B testing (force main thread load vs. let preload finish)
 
   // No need to check sDatabase for being non-null since preload is either
   // done before we've shut the DB down or when the DB could not start,
   // preload has not even be started.
-  sDatabase->SyncPreload(this);
+  StorageDBChild::Get()->SyncPreload(this);
 }
 
 nsresult
 LocalStorageCache::GetLength(const LocalStorage* aStorage, uint32_t* aRetval)
 {
   if (Persist(aStorage)) {
     WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
     if (NS_FAILED(mLoadResult)) {
@@ -424,27 +421,28 @@ LocalStorageCache::SetItem(const LocalSt
 
   if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
     return NS_SUCCESS_DOM_NO_OPERATION;
   }
 
   data.mKeys.Put(aKey, aValue);
 
   if (aSource == ContentMutation && Persist(aStorage)) {
-    if (!sDatabase) {
+    StorageDBChild* storageChild = StorageDBChild::Get();
+    if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
     if (DOMStringIsNull(aOld)) {
-      return sDatabase->AsyncAddItem(this, aKey, aValue);
+      return storageChild->AsyncAddItem(this, aKey, aValue);
     }
 
-    return sDatabase->AsyncUpdateItem(this, aKey, aValue);
+    return storageChild->AsyncUpdateItem(this, aKey, aValue);
   }
 
   return NS_OK;
 }
 
 nsresult
 LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
                               const nsAString& aKey,
@@ -465,23 +463,24 @@ LocalStorageCache::RemoveItem(const Loca
 
   // Recalculate the cached data size
   const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
                           static_cast<int64_t>(aKey.Length()));
   Unused << ProcessUsageDelta(aStorage, delta, aSource);
   data.mKeys.Remove(aKey);
 
   if (aSource == ContentMutation && Persist(aStorage)) {
-    if (!sDatabase) {
+    StorageDBChild* storageChild = StorageDBChild::Get();
+    if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
-    return sDatabase->AsyncRemoveItem(this, aKey);
+    return storageChild->AsyncRemoveItem(this, aKey);
   }
 
   return NS_OK;
 }
 
 nsresult
 LocalStorageCache::Clear(const LocalStorage* aStorage,
                          const MutationSource aSource)
@@ -506,23 +505,24 @@ LocalStorageCache::Clear(const LocalStor
   bool hadData = !!data.mKeys.Count();
 
   if (hadData) {
     Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
     data.mKeys.Clear();
   }
 
   if (aSource == ContentMutation && Persist(aStorage) && (refresh || hadData)) {
-    if (!sDatabase) {
+    StorageDBChild* storageChild = StorageDBChild::Get();
+    if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
-    return sDatabase->AsyncClear(this);
+    return storageChild->AsyncClear(this);
   }
 
   return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
 }
 
 int64_t
 LocalStorageCache::GetOriginQuotaUsage(const LocalStorage* aStorage) const
 {
@@ -667,78 +667,10 @@ StorageUsage::CheckAndSetETLD1UsageDelta
       aDelta > 0 && newUsage > LocalStorageManager::GetQuota()) {
     return false;
   }
 
   mUsage[aDataSetIndex] = newUsage;
   return true;
 }
 
-
-// static
-StorageDBBridge*
-LocalStorageCache::StartDatabase()
-{
-  if (sDatabase || sDatabaseDown) {
-    // When sDatabaseDown is at true, sDatabase is null.
-    // Checking sDatabaseDown flag here prevents reinitialization of
-    // the database after shutdown.
-    return sDatabase;
-  }
-
-  if (XRE_IsParentProcess()) {
-    nsAutoPtr<StorageDBThread> db(new StorageDBThread());
-
-    nsresult rv = db->Init();
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    sDatabase = db.forget();
-  } else {
-    // Use LocalStorageManager::Ensure in case we're called from
-    // DOMSessionStorageManager's initializer and we haven't yet initialized the
-    // local storage manager.
-    RefPtr<StorageDBChild> db = new StorageDBChild(
-        LocalStorageManager::Ensure());
-
-    nsresult rv = db->Init();
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    db.forget(&sDatabase);
-  }
-
-  return sDatabase;
-}
-
-// static
-StorageDBBridge*
-LocalStorageCache::GetDatabase()
-{
-  return sDatabase;
-}
-
-// static
-nsresult
-LocalStorageCache::StopDatabase()
-{
-  if (!sDatabase) {
-    return NS_OK;
-  }
-
-  sDatabaseDown = true;
-
-  nsresult rv = sDatabase->Shutdown();
-  if (XRE_IsParentProcess()) {
-    delete sDatabase;
-  } else {
-    StorageDBChild* child = static_cast<StorageDBChild*>(sDatabase);
-    NS_RELEASE(child);
-  }
-
-  sDatabase = nullptr;
-  return rv;
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/storage/LocalStorageCache.h
+++ b/dom/storage/LocalStorageCache.h
@@ -126,23 +126,16 @@ public:
   nsresult RemoveItem(const LocalStorage* aStorage, const nsAString& aKey,
                       nsString& aOld,
                       const MutationSource aSource=ContentMutation);
   nsresult Clear(const LocalStorage* aStorage,
                  const MutationSource aSource=ContentMutation);
 
   void GetKeys(const LocalStorage* aStorage, nsTArray<nsString>& aKeys);
 
-  // Starts the database engine thread or the IPC bridge
-  static StorageDBBridge* StartDatabase();
-  static StorageDBBridge* GetDatabase();
-
-  // Stops the thread and flushes all uncommited data
-  static nsresult StopDatabase();
-
   // LocalStorageCacheBridge
 
   virtual const nsCString Origin() const;
   virtual const nsCString& OriginNoSuffix() const { return mOriginNoSuffix; }
   virtual const nsCString& OriginSuffix() const { return mOriginSuffix; }
   virtual bool Loaded() { return mLoaded; }
   virtual uint32_t LoadedCount();
   virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
@@ -255,31 +248,24 @@ private:
   // - True after access to session-only data has been made for the first time.
   // We also fill session-only data set with the default one at that moment.
   // Drops back to false when session-only data are cleared from chrome.
   bool mSessionOnlyDataSetActive : 1;
 
   // Whether we have already captured state of the cache preload on our first
   // access.
   bool mPreloadTelemetryRecorded : 1;
-
-  // StorageDBThread on the parent or single process,
-  // StorageDBChild on the child process.
-  static StorageDBBridge* sDatabase;
-
-  // False until we shut the database down.
-  static bool sDatabaseDown;
 };
 
 // StorageUsage
 // Infrastructure to manage and check eTLD+1 quota
 class StorageUsageBridge
 {
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StorageUsageBridge)
+  NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(StorageUsageBridge)
 
   virtual const nsCString& OriginScope() = 0;
   virtual void LoadUsage(const int64_t aUsage) = 0;
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~StorageUsageBridge() {}
 };
--- a/dom/storage/LocalStorageManager.cpp
+++ b/dom/storage/LocalStorageManager.cpp
@@ -71,17 +71,17 @@ LocalStorageManager::LocalStorageManager
 
   NS_ASSERTION(!sSelf, "Somebody is trying to do_CreateInstance(\"@mozilla/dom/localStorage-manager;1\"");
   sSelf = this;
 
   if (!XRE_IsParentProcess()) {
     // Do this only on the child process.  The thread IPC bridge
     // is also used to communicate chrome observer notifications.
     // Note: must be called after we set sSelf
-    LocalStorageCache::StartDatabase();
+    StorageDBChild::GetOrCreate();
   }
 }
 
 LocalStorageManager::~LocalStorageManager()
 {
   StorageObserver* observer = StorageObserver::Self();
   if (observer) {
     observer->RemoveSink(this);
@@ -163,19 +163,19 @@ LocalStorageManager::GetOriginUsage(cons
 {
   RefPtr<StorageUsage> usage;
   if (mUsages.Get(aOriginNoSuffix, &usage)) {
     return usage.forget();
   }
 
   usage = new StorageUsage(aOriginNoSuffix);
 
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (db) {
-    db->AsyncGetUsage(usage);
+  StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+  if (storageChild) {
+    storageChild->AsyncGetUsage(usage);
   }
 
   mUsages.Put(aOriginNoSuffix, usage);
 
   return usage.forget();
 }
 
 already_AddRefed<LocalStorageCache>
@@ -229,17 +229,17 @@ LocalStorageManager::GetStorageInternal(
     if (aCreateMode == CreateMode::UseIfExistsNeverCreate) {
       *aRetval = nullptr;
       return NS_OK;
     }
 
     if (aCreateMode == CreateMode::CreateIfShouldPreload) {
       // This is a demand to just preload the cache, if the scope has
       // no data stored, bypass creation and preload of the cache.
-      StorageDBBridge* db = LocalStorageCache::GetDatabase();
+      StorageDBChild* db = StorageDBChild::Get();
       if (db) {
         if (!db->ShouldPreloadOrigin(LocalStorageManager::CreateOrigin(originAttrSuffix, originKey))) {
           return NS_OK;
         }
       } else {
         if (originKey.EqualsLiteral("knalb.:about")) {
           return NS_OK;
         }
--- a/dom/storage/LocalStorageManager.h
+++ b/dom/storage/LocalStorageManager.h
@@ -126,16 +126,17 @@ private:
   // Like Self, but creates an instance if we're not yet initialized.
   static LocalStorageManager* Ensure();
 
 private:
   // Keeps usage cache objects for eTLD+1 scopes we have touched.
   nsDataHashtable<nsCStringHashKey, RefPtr<StorageUsage> > mUsages;
 
   friend class LocalStorageCache;
+  friend class StorageDBChild;
   // Releases cache since it is no longer referrered by any Storage object.
   virtual void DropCache(LocalStorageCache* aCache);
 
   static LocalStorageManager* sSelf;
 };
 
 } // namespace dom
 } // namespace mozilla
rename from dom/storage/PStorage.ipdl
rename to dom/storage/PBackgroundStorage.ipdl
--- a/dom/storage/PStorage.ipdl
+++ b/dom/storage/PBackgroundStorage.ipdl
@@ -1,47 +1,64 @@
 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
 /* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-include protocol PContent;
+include protocol PBackground;
+
+include "mozilla/dom/quota/SerializationHelpers.h";
+
+using mozilla::OriginAttributesPattern
+  from "mozilla/OriginAttributes.h";
 
 namespace mozilla {
 namespace dom {
 
 /* This protocol bridges async access to the database thread running on the
  * parent process and caches running on the child process.
  */
-nested(upto inside_cpow) sync protocol PStorage
+sync protocol PBackgroundStorage
 {
-  manager PContent;
+  manager PBackground;
 
 parent:
-  async __delete__();
+  async DeleteMe();
 
-  nested(inside_cpow) sync Preload(nsCString originSuffix,
-                                   nsCString originNoSuffix,
-                                   uint32_t alreadyLoadedCount)
+  sync Preload(nsCString originSuffix,
+               nsCString originNoSuffix,
+               uint32_t alreadyLoadedCount)
     returns (nsString[] keys, nsString[] values, nsresult rv);
 
   async AsyncPreload(nsCString originSuffix, nsCString originNoSuffix,
                      bool priority);
   async AsyncGetUsage(nsCString scope);
   async AsyncAddItem(nsCString originSuffix, nsCString originNoSuffix,
                      nsString key, nsString value);
   async AsyncUpdateItem(nsCString originSuffix, nsCString originNoSuffix,
                         nsString key, nsString value);
   async AsyncRemoveItem(nsCString originSuffix, nsCString originNoSuffix,
                         nsString key);
   async AsyncClear(nsCString originSuffix, nsCString originNoSuffix);
   async AsyncFlush();
-  
+
+  // These are privileged operations for use only by the observer API for
+  // delayed initialization and clearing origins and will never be used from
+  // content process children.  Ideally these would be guarded by checks or
+  // exist on a separate, privileged interface, but PBackgroundStorage is
+  // already insecure.
+  async Startup();
+  async ClearAll();
+  async ClearMatchingOrigin(nsCString originNoSuffix);
+  async ClearMatchingOriginAttributes(OriginAttributesPattern pattern);
+
 child:
+  async __delete__();
+
   async Observe(nsCString topic,
                 nsString originAttributesPattern,
                 nsCString originScope);
   async OriginsHavingData(nsCString[] origins);
   async LoadItem(nsCString originSuffix, nsCString originNoSuffix, nsString key,
                  nsString value);
   async LoadDone(nsCString originSuffix, nsCString originNoSuffix, nsresult rv);
   async LoadUsage(nsCString scope, int64_t usage);
--- a/dom/storage/StorageDBThread.cpp
+++ b/dom/storage/StorageDBThread.cpp
@@ -18,16 +18,17 @@
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "mozIStorageService.h"
 #include "mozIStorageBindingParamsArray.h"
 #include "mozIStorageBindingParams.h"
 #include "mozIStorageValueArray.h"
 #include "mozIStorageFunction.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/ipc/BackgroundParent.h"
 #include "nsIObserverService.h"
 #include "nsVariant.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/Services.h"
 #include "mozilla/Tokenizer.h"
 #include "GeckoProfiler.h"
 
 // How long we collect write oprerations
@@ -43,16 +44,21 @@
 
 namespace mozilla {
 namespace dom {
 
 using namespace StorageUtils;
 
 namespace { // anon
 
+StorageDBThread* sStorageThread = nullptr;
+
+// False until we shut the storage thread down.
+bool sStorageThreadDown = false;
+
 // This is only a compatibility code for schema version 0.  Returns the 'scope'
 // key in the schema version 0 format for the scope column.
 nsCString
 Scheme0Scope(LocalStorageCacheBridge* aCache)
 {
   nsCString result;
 
   nsCString suffix = aCache->OriginSuffix();
@@ -101,74 +107,213 @@ Scheme0Scope(LocalStorageCacheBridge* aC
 
   result.Append(aCache->OriginNoSuffix());
 
   return result;
 }
 
 } // anon
 
-
+// XXX Fix me!
+#if 0
 StorageDBBridge::StorageDBBridge()
 {
 }
+#endif
 
+class StorageDBThread::InitHelper final
+  : public Runnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  mozilla::Mutex mMutex;
+  mozilla::CondVar mCondVar;
+  nsString mProfilePath;
+  nsresult mMainThreadResultCode;
+  bool mWaiting;
+
+public:
+  InitHelper()
+    : Runnable("dom::StorageDBThread::InitHelper")
+    , mOwningThread(GetCurrentThreadEventTarget())
+    , mMutex("InitHelper::mMutex")
+    , mCondVar(mMutex, "InitHelper::mCondVar")
+    , mMainThreadResultCode(NS_OK)
+    , mWaiting(true)
+  { }
+
+  // Because of the `sync Preload` IPC, we need to be able to synchronously
+  // initialize, which includes consulting and initializing
+  // some main-thread-only APIs. Bug 1386441 discusses improving this situation.
+  nsresult
+  SyncDispatchAndReturnProfilePath(nsAString& aProfilePath);
+
+private:
+  ~InitHelper() override = default;
+
+  nsresult
+  RunOnMainThread();
+
+  NS_DECL_NSIRUNNABLE
+};
+
+class StorageDBThread::NoteBackgroundThreadRunnable final
+  : public Runnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+
+public:
+  NoteBackgroundThreadRunnable()
+    : Runnable("dom::StorageDBThread::NoteBackgroundThreadRunnable")
+    , mOwningThread(GetCurrentThreadEventTarget())
+  { }
+
+private:
+  ~NoteBackgroundThreadRunnable() override = default;
+
+  NS_DECL_NSIRUNNABLE
+};
 
 StorageDBThread::StorageDBThread()
   : mThread(nullptr)
   , mThreadObserver(new ThreadObserver())
   , mStopIOThread(false)
   , mWALModeEnabled(false)
   , mDBReady(false)
   , mStatus(NS_OK)
   , mWorkerStatements(mWorkerConnection)
   , mReaderStatements(mReaderConnection)
   , mDirtyEpoch(0)
   , mFlushImmediately(false)
   , mPriorityCounter(0)
 {
 }
 
+// static
+StorageDBThread*
+StorageDBThread::Get()
+{
+  AssertIsOnBackgroundThread();
+
+  return sStorageThread;
+}
+
+// static
+StorageDBThread*
+StorageDBThread::GetOrCreate(const nsString& aProfilePath)
+{
+  AssertIsOnBackgroundThread();
+
+  if (sStorageThread || sStorageThreadDown) {
+    // When sStorageThreadDown is at true, sStorageThread is null.
+    // Checking sStorageThreadDown flag here prevents reinitialization of
+    // the storage thread after shutdown.
+    return sStorageThread;
+  }
+
+  nsAutoPtr<StorageDBThread> storageThread(new StorageDBThread());
+
+  nsresult rv = storageThread->Init(aProfilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  sStorageThread = storageThread.forget();
+
+  return sStorageThread;
+}
+
+// static
 nsresult
-StorageDBThread::Init()
+StorageDBThread::GetProfilePath(nsString& aProfilePath)
 {
-  nsresult rv;
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
 
   // Need to determine location on the main thread, since
-  // NS_GetSpecialDirectory access the atom table that can
-  // be accessed only on the main thread.
-  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                              getter_AddRefs(mDatabaseFile));
-  NS_ENSURE_SUCCESS(rv, rv);
+  // NS_GetSpecialDirectory accesses the atom table that can
+  // only be accessed on the main thread.
+  nsCOMPtr<nsIFile> profileDir;
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                                       getter_AddRefs(profileDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = profileDir->GetPath(aProfilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // This service has to be started on the main thread currently.
+  nsCOMPtr<mozIStorageService> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+StorageDBThread::Init(const nsString& aProfilePath)
+{
+  AssertIsOnBackgroundThread();
+
+  nsresult rv;
+
+  nsString profilePath;
+  if (aProfilePath.IsEmpty()) {
+    RefPtr<InitHelper> helper = new InitHelper();
+
+    rv = helper->SyncDispatchAndReturnProfilePath(profilePath);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    profilePath = aProfilePath;
+  }
+
+  mDatabaseFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mDatabaseFile->InitWithPath(profilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Ensure mozIStorageService init on the main thread first.
-  nsCOMPtr<mozIStorageService> service =
-    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // Need to keep the lock to avoid setting mThread later then
   // the thread body executes.
   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
   mThread = PR_CreateThread(PR_USER_THREAD, &StorageDBThread::ThreadFunc, this,
                             PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
                             PR_JOINABLE_THREAD, 262144);
   if (!mThread) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  RefPtr<NoteBackgroundThreadRunnable> runnable =
+    new NoteBackgroundThreadRunnable();
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+
   return NS_OK;
 }
 
 nsresult
 StorageDBThread::Shutdown()
 {
+  AssertIsOnBackgroundThread();
+
+  sStorageThreadDown = true;
+
   if (!mThread) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
 
   {
     MonitorAutoLock monitor(mThreadObserver->GetMonitor());
@@ -899,17 +1044,17 @@ StorageDBThread::DBOperation::Perform(St
   case opPreloadUrgent:
   {
     // Already loaded?
     if (mCache->Loaded()) {
       break;
     }
 
     StatementCache* statements;
-    if (MOZ_UNLIKELY(NS_IsMainThread())) {
+    if (MOZ_UNLIKELY(IsOnBackgroundThread())) {
       statements = &aThread->mReaderStatements;
     } else {
       statements = &aThread->mWorkerStatements;
     }
 
     // OFFSET is an optimization when we have to do a sync load
     // and cache has already loaded some parts asynchronously.
     // It skips keys we have already loaded.
@@ -1489,10 +1634,89 @@ StorageDBThread::PendingOperations::IsOr
     if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
       return true;
     }
   }
 
   return false;
 }
 
+nsresult
+StorageDBThread::
+InitHelper::SyncDispatchAndReturnProfilePath(nsAString& aProfilePath)
+{
+  AssertIsOnBackgroundThread();
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+  mozilla::MutexAutoLock autolock(mMutex);
+  while (mWaiting) {
+    mCondVar.Wait();
+  }
+
+  if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
+    return mMainThreadResultCode;
+  }
+
+  aProfilePath = mProfilePath;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::
+InitHelper::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv = GetProfilePath(mProfilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mMainThreadResultCode = rv;
+  }
+
+  mozilla::MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mWaiting);
+
+  mWaiting = false;
+  mCondVar.Notify();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::
+NoteBackgroundThreadRunnable::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  StorageObserver* observer = StorageObserver::Self();
+  MOZ_ASSERT(observer);
+
+  observer->NoteBackgroundThread(mOwningThread);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::
+ShutdownRunnable::Run()
+{
+  if (NS_IsMainThread()) {
+    mDone = true;
+
+    return NS_OK;
+  }
+
+  AssertIsOnBackgroundThread();
+
+  if (sStorageThread) {
+    sStorageThread->Shutdown();
+
+    delete sStorageThread;
+    sStorageThread = nullptr;
+  }
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/storage/StorageDBThread.h
+++ b/dom/storage/StorageDBThread.h
@@ -27,16 +27,25 @@ namespace mozilla {
 namespace dom {
 
 class LocalStorageCacheBridge;
 class StorageUsageBridge;
 class StorageUsage;
 
 typedef mozilla::storage::StatementCache<mozIStorageStatement> StatementCache;
 
+// XXX Fix me!
+//     1. Move comments to StorageDBThread/StorageDBChild.
+//     2. Devirtualize relevant methods in StorageDBThread/StorageDBChild.
+//     3. Remove relevant methods in StorageDBThread/StorageDBChild that are
+//        unused.
+//     4. Remove this class completely.
+//
+//     See bug 1387636 for more details.
+#if 0
 // Interface used by the cache to post operations to the asynchronous
 // database thread or process.
 class StorageDBBridge
 {
 public:
   StorageDBBridge();
   virtual ~StorageDBBridge() {}
 
@@ -95,27 +104,25 @@ public:
   virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern) = 0;
 
   // Forces scheduled DB operations to be early flushed to the disk
   virtual void AsyncFlush() = 0;
 
   // Check whether the scope has any data stored on disk and is thus allowed to
   // preload
   virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix) = 0;
-
-  // Get the complete list of scopes having data
-  virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins) = 0;
 };
+#endif
 
 // The implementation of the the database engine, this directly works
 // with the sqlite or any other db API we are based on
 // This class is resposible for collecting and processing asynchronous
 // DB operations over caches (LocalStorageCache) communicating though
 // LocalStorageCacheBridge interface class
-class StorageDBThread final : public StorageDBBridge
+class StorageDBThread final
 {
 public:
   class PendingOperations;
 
   // Representation of a singe database task, like adding and removing keys,
   // (pre)loading the whole origin data, cleaning.
   class DBOperation
   {
@@ -282,21 +289,56 @@ public:
 
   private:
     virtual ~ThreadObserver() {}
     bool mHasPendingEvents;
     // The monitor we drive the thread with
     Monitor mMonitor;
   };
 
+  class InitHelper;
+
+  class NoteBackgroundThreadRunnable;
+
+  class ShutdownRunnable : public Runnable
+  {
+    // Only touched on the main thread.
+    bool& mDone;
+
+  public:
+    explicit ShutdownRunnable(bool& aDone)
+      : Runnable("dom::StorageDBThread::ShutdownRunnable")
+      , mDone(aDone)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+    }
+
+  private:
+    ~ShutdownRunnable()
+    { }
+
+    NS_DECL_NSIRUNNABLE
+  };
+
 public:
   StorageDBThread();
   virtual ~StorageDBThread() {}
 
-  virtual nsresult Init();
+  static StorageDBThread*
+  Get();
+
+  static StorageDBThread*
+  GetOrCreate(const nsString& aProfilePath);
+
+  static nsresult
+  GetProfilePath(nsString& aProfilePath);
+
+  virtual nsresult Init(const nsString& aProfilePath);
+
+  // Flushes all uncommited data and stops the I/O thread.
   virtual nsresult Shutdown();
 
   virtual void AsyncPreload(LocalStorageCacheBridge* aCache,
                             bool aPriority = false)
   {
     InsertDBOp(new DBOperation(aPriority
                                  ? DBOperation::opPreloadUrgent
                                  : DBOperation::opPreload,
@@ -353,17 +395,19 @@ public:
   {
     InsertDBOp(new DBOperation(DBOperation::opClearMatchingOriginAttributes,
                                aPattern));
   }
 
   virtual void AsyncFlush();
 
   virtual bool ShouldPreloadOrigin(const nsACString& aOrigin);
-  virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins);
+
+  // Get the complete list of scopes having data.
+  void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins);
 
 private:
   nsCOMPtr<nsIFile> mDatabaseFile;
   PRThread* mThread;
 
   // Used to observe runnables dispatched to our thread and to monitor it.
   RefPtr<ThreadObserver> mThreadObserver;
 
--- a/dom/storage/StorageIPC.cpp
+++ b/dom/storage/StorageIPC.cpp
@@ -5,45 +5,58 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "StorageIPC.h"
 
 #include "LocalStorageManager.h"
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundParent.h"
 #include "mozilla/Unused.h"
 #include "nsIDiskSpaceWatcher.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+StorageDBChild* sStorageChild = nullptr;
+
+// False until we shut the storage child down.
+bool sStorageChildDown = false;
+
+}
+
 // ----------------------------------------------------------------------------
 // Child
 // ----------------------------------------------------------------------------
 
-NS_IMPL_ADDREF(StorageDBChild)
-
-NS_IMETHODIMP_(MozExternalRefCountType) StorageDBChild::Release(void)
+class StorageDBChild::ShutdownObserver final
+  : public nsIObserver
 {
-  NS_PRECONDITION(0 != mRefCnt, "dup release");
-  nsrefcnt count = --mRefCnt;
-  NS_LOG_RELEASE(this, count, "StorageDBChild");
-  if (count == 1 && mIPCOpen) {
-    Send__delete__(this);
-    return 0;
+public:
+  ShutdownObserver()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
   }
-  if (count == 0) {
-    mRefCnt = 1;
-    delete this;
-    return 0;
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+private:
+  ~ShutdownObserver()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
   }
-  return count;
-}
+};
 
 void
 StorageDBChild::AddIPDLReference()
 {
   MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references");
   mIPCOpen = true;
   AddRef();
 }
@@ -62,32 +75,96 @@ StorageDBChild::StorageDBChild(LocalStor
   , mIPCOpen(false)
 {
 }
 
 StorageDBChild::~StorageDBChild()
 {
 }
 
+// static
+StorageDBChild*
+StorageDBChild::Get()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  return sStorageChild;
+}
+
+// static
+StorageDBChild*
+StorageDBChild::GetOrCreate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sStorageChild || sStorageChildDown) {
+    // When sStorageChildDown is at true, sStorageChild is null.
+    // Checking sStorageChildDown flag here prevents reinitialization of
+    // the storage child after shutdown.
+    return sStorageChild;
+  }
+
+  // Use LocalStorageManager::Ensure in case we're called from
+  // DOMSessionStorageManager's initializer and we haven't yet initialized the
+  // local storage manager.
+  RefPtr<StorageDBChild> storageChild =
+    new StorageDBChild(LocalStorageManager::Ensure());
+
+  nsresult rv = storageChild->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  storageChild.forget(&sStorageChild);
+
+  return sStorageChild;
+}
+
 nsTHashtable<nsCStringHashKey>&
 StorageDBChild::OriginsHavingData()
 {
   if (!mOriginsHavingData) {
     mOriginsHavingData = new nsTHashtable<nsCStringHashKey>;
   }
 
   return *mOriginsHavingData;
 }
 
 nsresult
 StorageDBChild::Init()
 {
-  ContentChild* child = ContentChild::GetSingleton();
+  MOZ_ASSERT(NS_IsMainThread());
+
+  PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
+  if (NS_WARN_IF(!actor)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsString profilePath;
+  if (XRE_IsParentProcess()) {
+    nsresult rv = StorageDBThread::GetProfilePath(profilePath);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
   AddIPDLReference();
-  child->SendPStorageConstructor(this);
+
+  actor->SendPBackgroundStorageConstructor(this, profilePath);
+
+  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+  MOZ_ASSERT(observerService);
+
+  nsCOMPtr<nsIObserver> observer = new ShutdownObserver();
+
+  MOZ_ALWAYS_SUCCEEDS(
+    observerService->AddObserver(observer,
+                                 "xpcom-shutdown",
+                                 false));
+
   return NS_OK;
 }
 
 nsresult
 StorageDBChild::Shutdown()
 {
   // There is nothing to do here, IPC will release automatically and
   // the actual thread running on the parent process will also stop
@@ -211,16 +288,18 @@ StorageDBChild::ShouldPreloadOrigin(cons
   return !mOriginsHavingData || mOriginsHavingData->Contains(aOrigin);
 }
 
 mozilla::ipc::IPCResult
 StorageDBChild::RecvObserve(const nsCString& aTopic,
                             const nsString& aOriginAttributesPattern,
                             const nsCString& aOriginScope)
 {
+  MOZ_ASSERT(!XRE_IsParentProcess());
+
   StorageObserver::Self()->Notify(
     aTopic.get(), aOriginAttributesPattern, aOriginScope);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBChild::RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins)
 {
@@ -281,20 +360,97 @@ StorageDBChild::RecvLoadUsage(const nsCS
 
 mozilla::ipc::IPCResult
 StorageDBChild::RecvError(const nsresult& aRv)
 {
   mStatus = aRv;
   return IPC_OK();
 }
 
+NS_IMPL_ISUPPORTS(StorageDBChild::ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+StorageDBChild::
+ShutdownObserver::Observe(nsISupports* aSubject,
+                          const char* aTopic,
+                          const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
+
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (NS_WARN_IF(!observerService)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  Unused << observerService->RemoveObserver(this, "xpcom-shutdown");
+
+  if (sStorageChild) {
+    sStorageChildDown = true;
+
+    MOZ_ALWAYS_TRUE(sStorageChild->PBackgroundStorageChild::SendDeleteMe());
+
+    NS_RELEASE(sStorageChild);
+    sStorageChild = nullptr;
+  }
+
+  return NS_OK;
+}
+
 // ----------------------------------------------------------------------------
 // Parent
 // ----------------------------------------------------------------------------
 
+class StorageDBParent::ObserverSink
+  : public StorageObserverSink
+{
+  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+  // Only touched on the PBackground thread.
+  StorageDBParent* MOZ_NON_OWNING_REF mActor;
+
+public:
+  explicit ObserverSink(StorageDBParent* aActor)
+    : mOwningEventTarget(GetCurrentThreadEventTarget())
+    , mActor(aActor)
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(aActor);
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StorageDBParent::ObserverSink);
+
+  void
+  Start();
+
+  void
+  Stop();
+
+private:
+  ~ObserverSink() = default;
+
+  void
+  AddSink();
+
+  void
+  RemoveSink();
+
+  void
+  Notify(const nsCString& aTopic,
+         const nsString& aOriginAttributesPattern,
+         const nsCString& aOriginScope);
+
+  // StorageObserverSink
+  nsresult
+  Observe(const char* aTopic,
+          const nsAString& aOriginAttrPattern,
+          const nsACString& aOriginScope) override;
+};
+
 NS_IMPL_ADDREF(StorageDBParent)
 NS_IMPL_RELEASE(StorageDBParent)
 
 void
 StorageDBParent::AddIPDLReference()
 {
   MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references");
   mIPCOpen = true;
@@ -306,127 +462,176 @@ StorageDBParent::ReleaseIPDLReference()
 {
   MOZ_ASSERT(mIPCOpen, "Attempting to release non-existent IPDL reference");
   mIPCOpen = false;
   Release();
 }
 
 namespace {
 
-class SendInitialChildDataRunnable : public Runnable
+class CheckLowDiskSpaceRunnable
+  : public Runnable
 {
+  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+  RefPtr<StorageDBParent> mParent;
+  bool mLowDiskSpace;
+
 public:
-  explicit SendInitialChildDataRunnable(StorageDBParent* aParent)
-    : Runnable("dom::SendInitialChildDataRunnable")
+  explicit CheckLowDiskSpaceRunnable(StorageDBParent* aParent)
+    : Runnable("dom::CheckLowDiskSpaceRunnable")
+    , mOwningEventTarget(GetCurrentThreadEventTarget())
     , mParent(aParent)
-  {}
+    , mLowDiskSpace(false)
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(aParent);
+  }
 
 private:
   NS_IMETHOD Run() override
   {
-    if (!mParent->IPCOpen()) {
+    if (IsOnBackgroundThread()) {
+      MOZ_ASSERT(mParent);
+
+      if (!mParent->IPCOpen()) {
+        return NS_OK;
+      }
+
+      if (mLowDiskSpace) {
+        mozilla::Unused << mParent->SendObserve(
+          nsDependentCString("low-disk-space"), EmptyString(), EmptyCString());
+      }
+
+      mParent = nullptr;
+
       return NS_OK;
     }
 
-    StorageDBBridge* db = LocalStorageCache::GetDatabase();
-    if (db) {
-      InfallibleTArray<nsCString> scopes;
-      db->GetOriginsHavingData(&scopes);
-      mozilla::Unused << mParent->SendOriginsHavingData(scopes);
-    }
+    MOZ_ASSERT(NS_IsMainThread());
 
-    // We need to check if the device is in a low disk space situation, so
-    // we can forbid in that case any write in localStorage.
     nsCOMPtr<nsIDiskSpaceWatcher> diskSpaceWatcher =
       do_GetService("@mozilla.org/toolkit/disk-space-watcher;1");
     if (!diskSpaceWatcher) {
       return NS_OK;
     }
 
-    bool lowDiskSpace = false;
-    diskSpaceWatcher->GetIsDiskFull(&lowDiskSpace);
+    diskSpaceWatcher->GetIsDiskFull(&mLowDiskSpace);
 
-    if (lowDiskSpace) {
-      mozilla::Unused << mParent->SendObserve(
-        nsDependentCString("low-disk-space"), EmptyString(), EmptyCString());
-    }
+    MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
 
     return NS_OK;
   }
-
-  RefPtr<StorageDBParent> mParent;
 };
 
 } // namespace
 
-StorageDBParent::StorageDBParent()
-: mIPCOpen(false)
+StorageDBParent::StorageDBParent(const nsString& aProfilePath)
+  : mProfilePath(aProfilePath)
+  , mIPCOpen(false)
 {
-  StorageObserver* observer = StorageObserver::Self();
-  if (observer) {
-    observer->AddSink(this);
-  }
+  AssertIsOnBackgroundThread();
 
   // We are always open by IPC only
   AddIPDLReference();
-
-  // Cannot send directly from here since the channel
-  // is not completely built at this moment.
-  RefPtr<SendInitialChildDataRunnable> r =
-    new SendInitialChildDataRunnable(this);
-  NS_DispatchToCurrentThread(r);
 }
 
 StorageDBParent::~StorageDBParent()
 {
-  StorageObserver* observer = StorageObserver::Self();
-  if (observer) {
-    observer->RemoveSink(this);
+  AssertIsOnBackgroundThread();
+
+  if (mObserverSink) {
+    mObserverSink->Stop();
+    mObserverSink = nullptr;
   }
 }
 
+void
+StorageDBParent::Init()
+{
+  AssertIsOnBackgroundThread();
+
+  PBackgroundParent* actor = Manager();
+  MOZ_ASSERT(actor);
+
+  if (BackgroundParent::IsOtherProcessActor(actor)) {
+    mObserverSink = new ObserverSink(this);
+    mObserverSink->Start();
+  }
+
+  StorageDBThread* storageThread = StorageDBThread::Get();
+  if (storageThread) {
+    InfallibleTArray<nsCString> scopes;
+    storageThread->GetOriginsHavingData(&scopes);
+    mozilla::Unused << SendOriginsHavingData(scopes);
+  }
+
+  // We need to check if the device is in a low disk space situation, so
+  // we can forbid in that case any write in localStorage.
+
+  RefPtr<CheckLowDiskSpaceRunnable> runnable =
+    new CheckLowDiskSpaceRunnable(this);
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+}
+
 StorageDBParent::CacheParentBridge*
 StorageDBParent::NewCache(const nsACString& aOriginSuffix,
                           const nsACString& aOriginNoSuffix)
 {
   return new CacheParentBridge(this, aOriginSuffix, aOriginNoSuffix);
 }
 
 void
 StorageDBParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   // Implement me! Bug 1005169
 }
 
 mozilla::ipc::IPCResult
+StorageDBParent::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+
+  IProtocol* mgr = Manager();
+  if (!PBackgroundStorageParent::Send__delete__(this)) {
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncPreload(const nsCString& aOriginSuffix,
                                   const nsCString& aOriginNoSuffix,
                                   const bool& aPriority)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  db->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix), aPriority);
+  storageThread->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix),
+                              aPriority);
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncGetUsage(const nsCString& aOriginNoSuffix)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   // The object releases it self in LoadUsage method
   RefPtr<UsageParentBridge> usage =
     new UsageParentBridge(this, aOriginNoSuffix);
-  db->AsyncGetUsage(usage);
+
+  storageThread->AsyncGetUsage(usage);
+
   return IPC_OK();
 }
 
 namespace {
 
 // We need another implementation of LocalStorageCacheBridge to do
 // synchronous IPC preload.  This class just receives Load* notifications
 // and fills the returning arguments of RecvPreload with the database
@@ -507,131 +712,185 @@ private:
 mozilla::ipc::IPCResult
 StorageDBParent::RecvPreload(const nsCString& aOriginSuffix,
                              const nsCString& aOriginNoSuffix,
                              const uint32_t& aAlreadyLoadedCount,
                              InfallibleTArray<nsString>* aKeys,
                              InfallibleTArray<nsString>* aValues,
                              nsresult* aRv)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   RefPtr<SyncLoadCacheHelper> cache(
     new SyncLoadCacheHelper(aOriginSuffix, aOriginNoSuffix, aAlreadyLoadedCount,
                             aKeys, aValues, aRv));
 
-  db->SyncPreload(cache, true);
+  storageThread->SyncPreload(cache, true);
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncAddItem(const nsCString& aOriginSuffix,
                                   const nsCString& aOriginNoSuffix,
                                   const nsString& aKey,
                                   const nsString& aValue)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncAddItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey,
-                                 aValue);
+  nsresult rv =
+    storageThread->AsyncAddItem(NewCache(aOriginSuffix, aOriginNoSuffix),
+                                aKey,
+                                aValue);
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncUpdateItem(const nsCString& aOriginSuffix,
                                      const nsCString& aOriginNoSuffix,
                                      const nsString& aKey,
                                      const nsString& aValue)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncUpdateItem(NewCache(aOriginSuffix, aOriginNoSuffix),
-                                    aKey, aValue);
+  nsresult rv =
+    storageThread->AsyncUpdateItem(NewCache(aOriginSuffix, aOriginNoSuffix),
+                                   aKey,
+                                   aValue);
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncRemoveItem(const nsCString& aOriginSuffix,
                                      const nsCString& aOriginNoSuffix,
                                      const nsString& aKey)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncRemoveItem(NewCache(aOriginSuffix, aOriginNoSuffix),
-                                    aKey);
+  nsresult rv =
+    storageThread->AsyncRemoveItem(NewCache(aOriginSuffix, aOriginNoSuffix),
+                                   aKey);
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncClear(const nsCString& aOriginSuffix,
                                 const nsCString& aOriginNoSuffix)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix));
+  nsresult rv =
+    storageThread->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix));
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncFlush()
 {
-  StorageDBBridge* db = LocalStorageCache::GetDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::Get();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  db->AsyncFlush();
+  storageThread->AsyncFlush();
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+StorageDBParent::RecvStartup()
+{
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+StorageDBParent::RecvClearAll()
+{
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  storageThread->AsyncClearAll();
+
   return IPC_OK();
 }
 
-// StorageObserverSink
+mozilla::ipc::IPCResult
+StorageDBParent::RecvClearMatchingOrigin(const nsCString& aOriginNoSuffix)
+{
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  storageThread->AsyncClearMatchingOrigin(aOriginNoSuffix);
+
+  return IPC_OK();
+}
 
-nsresult
-StorageDBParent::Observe(const char* aTopic,
-                         const nsAString& aOriginAttributesPattern,
-                         const nsACString& aOriginScope)
+mozilla::ipc::IPCResult
+StorageDBParent::RecvClearMatchingOriginAttributes(
+                                        const OriginAttributesPattern& aPattern)
+{
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath);
+  if (!storageThread) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  storageThread->AsyncClearMatchingOriginAttributes(aPattern);
+
+  return IPC_OK();
+}
+
+void
+StorageDBParent::Observe(const nsCString& aTopic,
+                         const nsString& aOriginAttributesPattern,
+                         const nsCString& aOriginScope)
 {
   if (mIPCOpen) {
-      mozilla::Unused << SendObserve(nsDependentCString(aTopic),
-                                     nsString(aOriginAttributesPattern),
-                                     nsCString(aOriginScope));
+    mozilla::Unused <<
+      SendObserve(aTopic, aOriginAttributesPattern, aOriginScope);
   }
-
-  return NS_OK;
 }
 
 namespace {
 
 // Results must be sent back on the main thread
 class LoadRunnable : public Runnable
 {
 public:
@@ -687,16 +946,18 @@ private:
     case loadItem:
       mozilla::Unused << mParent->SendLoadItem(mSuffix, mOrigin, mKey, mValue);
       break;
     case loadDone:
       mozilla::Unused << mParent->SendLoadDone(mSuffix, mOrigin, mRv);
       break;
     }
 
+    mParent = nullptr;
+
     return NS_OK;
   }
 };
 
 } // namespace
 
 // StorageDBParent::CacheParentBridge
 
@@ -714,43 +975,84 @@ StorageDBParent::CacheParentBridge::Load
     return false;
   }
 
   ++mLoadedCount;
 
   RefPtr<LoadRunnable> r =
     new LoadRunnable(mParent, LoadRunnable::loadItem, mOriginSuffix,
                      mOriginNoSuffix, aKey, aValue);
-  NS_DispatchToMainThread(r);
+
+  MOZ_ALWAYS_SUCCEEDS(
+    mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
+
   return true;
 }
 
 void
 StorageDBParent::CacheParentBridge::LoadDone(nsresult aRv)
 {
   // Prevent send of duplicate LoadDone.
   if (mLoaded) {
     return;
   }
 
   mLoaded = true;
 
   RefPtr<LoadRunnable> r =
     new LoadRunnable(mParent, LoadRunnable::loadDone, mOriginSuffix,
                      mOriginNoSuffix, aRv);
-  NS_DispatchToMainThread(r);
+
+  MOZ_ALWAYS_SUCCEEDS(
+    mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
 }
 
 void
 StorageDBParent::CacheParentBridge::LoadWait()
 {
   // Should never be called on this implementation
   MOZ_ASSERT(false);
 }
 
+// XXX Fix me!
+// This should be just:
+// NS_IMPL_RELEASE_WITH_DESTROY(StorageDBParent::CacheParentBridge, Destroy)
+// But due to different strings used for refcount logging and different return
+// types, this is done manually for now.
+NS_IMETHODIMP_(void)
+StorageDBParent::CacheParentBridge::Release(void)
+{
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
+  if (0 == count) {
+    mRefCnt = 1; /* stabilize */
+    /* enable this to find non-threadsafe destructors: */
+    /* NS_ASSERT_OWNINGTHREAD(_class); */
+    Destroy();
+  }
+}
+
+void
+StorageDBParent::CacheParentBridge::Destroy()
+{
+  if (mOwningEventTarget->IsOnCurrentThread()) {
+    delete this;
+    return;
+  }
+
+  RefPtr<Runnable> destroyRunnable =
+    NewNonOwningRunnableMethod("CacheParentBridge::Destroy",
+                               this,
+                               &CacheParentBridge::Destroy);
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(destroyRunnable,
+                                                   NS_DISPATCH_NORMAL));
+}
+
 // StorageDBParent::UsageParentBridge
 
 namespace {
 
 class UsageRunnable : public Runnable
 {
 public:
   UsageRunnable(StorageDBParent* aParent,
@@ -765,27 +1067,191 @@ public:
 private:
   NS_IMETHOD Run() override
   {
     if (!mParent->IPCOpen()) {
       return NS_OK;
     }
 
     mozilla::Unused << mParent->SendLoadUsage(mOriginScope, mUsage);
+
+    mParent = nullptr;
+
     return NS_OK;
   }
 
   RefPtr<StorageDBParent> mParent;
   nsCString mOriginScope;
   int64_t mUsage;
 };
 
 } // namespace
 
 void
 StorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage)
 {
   RefPtr<UsageRunnable> r = new UsageRunnable(mParent, mOriginScope, aUsage);
-  NS_DispatchToMainThread(r);
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
+}
+
+// XXX Fix me!
+// This should be just:
+// NS_IMPL_RELEASE_WITH_DESTROY(StorageDBParent::UsageParentBridge, Destroy)
+// But due to different strings used for refcount logging, this is done manually
+// for now.
+NS_IMETHODIMP_(MozExternalRefCountType)
+StorageDBParent::UsageParentBridge::Release(void)
+{
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "StorageUsageBridge");
+  if (count == 0) {
+    Destroy();
+    return 0;
+  }
+  return count;
+}
+
+void
+StorageDBParent::UsageParentBridge::Destroy()
+{
+  if (mOwningEventTarget->IsOnCurrentThread()) {
+    delete this;
+    return;
+  }
+
+  RefPtr<Runnable> destroyRunnable =
+    NewNonOwningRunnableMethod("UsageParentBridge::Destroy",
+                               this,
+                               &UsageParentBridge::Destroy);
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(destroyRunnable,
+                                                   NS_DISPATCH_NORMAL));
+}
+
+void
+StorageDBParent::
+ObserverSink::Start()
+{
+  AssertIsOnBackgroundThread();
+
+  RefPtr<Runnable> runnable =
+    NewRunnableMethod("StorageDBParent::ObserverSink::AddSink",
+                      this,
+                      &StorageDBParent::ObserverSink::AddSink);
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+}
+
+void
+StorageDBParent::
+ObserverSink::Stop()
+{
+  AssertIsOnBackgroundThread();
+
+  mActor = nullptr;
+
+  RefPtr<Runnable> runnable =
+    NewRunnableMethod("StorageDBParent::ObserverSink::RemoveSink",
+                      this,
+                      &StorageDBParent::ObserverSink::RemoveSink);
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+}
+
+void
+StorageDBParent::
+ObserverSink::AddSink()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  StorageObserver* observer = StorageObserver::Self();
+  if (observer) {
+    observer->AddSink(this);
+  }
+}
+
+void
+StorageDBParent::
+ObserverSink::RemoveSink()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  StorageObserver* observer = StorageObserver::Self();
+  if (observer) {
+    observer->RemoveSink(this);
+  }
+}
+
+void
+StorageDBParent::
+ObserverSink::Notify(const nsCString& aTopic,
+                     const nsString& aOriginAttributesPattern,
+                     const nsCString& aOriginScope)
+{
+  AssertIsOnBackgroundThread();
+
+  if (mActor) {
+    mActor->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
+  }
+}
+
+nsresult
+StorageDBParent::
+ObserverSink::Observe(const char* aTopic,
+                      const nsAString& aOriginAttributesPattern,
+                      const nsACString& aOriginScope)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<Runnable> runnable =
+    NewRunnableMethod<nsCString, nsString, nsCString>(
+      "StorageDBParent::ObserverSink::Observe2",
+      this,
+      &StorageDBParent::ObserverSink::Notify,
+      aTopic,
+      aOriginAttributesPattern,
+      aOriginScope);
+
+  MOZ_ALWAYS_SUCCEEDS(
+    mOwningEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL));
+
+  return NS_OK;
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+
+PBackgroundStorageParent*
+AllocPBackgroundStorageParent(const nsString& aProfilePath)
+{
+  AssertIsOnBackgroundThread();
+
+  return new StorageDBParent(aProfilePath);
+}
+
+mozilla::ipc::IPCResult
+RecvPBackgroundStorageConstructor(PBackgroundStorageParent* aActor,
+                                  const nsString& aProfilePath)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  auto* actor = static_cast<StorageDBParent*>(aActor);
+  actor->Init();
+  return IPC_OK();
+}
+
+bool
+DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  StorageDBParent* actor = static_cast<StorageDBParent*>(aActor);
+  actor->ReleaseIPDLReference();
+  return true;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/storage/StorageIPC.h
+++ b/dom/storage/StorageIPC.h
@@ -2,46 +2,54 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_StorageIPC_h
 #define mozilla_dom_StorageIPC_h
 
-#include "mozilla/dom/PStorageChild.h"
-#include "mozilla/dom/PStorageParent.h"
+#include "mozilla/dom/PBackgroundStorageChild.h"
+#include "mozilla/dom/PBackgroundStorageParent.h"
 #include "StorageDBThread.h"
 #include "LocalStorageCache.h"
 #include "StorageObserver.h"
 #include "mozilla/Mutex.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 
 class OriginAttributesPattern;
 
 namespace dom {
 
 class LocalStorageManager;
+class PBackgroundStorageParent;
 
 // Child side of the IPC protocol, exposes as DB interface but
 // is responsible to send all requests to the parent process
 // and expects asynchronous answers. Those are then transparently
 // forwarded back to consumers on the child process.
-class StorageDBChild final : public StorageDBBridge
-                           , public PStorageChild
+class StorageDBChild final
+  : public PBackgroundStorageChild
 {
+  class ShutdownObserver;
+
   virtual ~StorageDBChild();
 
 public:
   explicit StorageDBChild(LocalStorageManager* aManager);
 
-  NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
-  NS_IMETHOD_(MozExternalRefCountType) Release(void);
+  static StorageDBChild*
+  Get();
+
+  static StorageDBChild*
+  GetOrCreate();
+
+  NS_INLINE_DECL_REFCOUNTING(StorageDBChild);
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
   virtual nsresult Init();
   virtual nsresult Shutdown();
 
   virtual void AsyncPreload(LocalStorageCacheBridge* aCache,
@@ -63,27 +71,31 @@ public:
   virtual void AsyncClearAll()
   {
     if (mOriginsHavingData) {
       mOriginsHavingData->Clear(); /* NO-OP on the child process otherwise */
     }
   }
 
   virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix)
-    { /* NO-OP on the child process */ }
+  {
+    MOZ_CRASH("Shouldn't be called!");
+  }
 
   virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern)
-    { /* NO-OP on the child process */ }
+  {
+    MOZ_CRASH("Shouldn't be called!");
+  }
 
   virtual void AsyncFlush()
-    { SendAsyncFlush(); }
+  {
+    MOZ_CRASH("Shouldn't be called!");
+  }
 
   virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix);
-  virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
-    { NS_NOTREACHED("Not implemented for child process"); }
 
 private:
   mozilla::ipc::IPCResult RecvObserve(const nsCString& aTopic,
                                       const nsString& aOriginAttributesPattern,
                                       const nsCString& aOriginScope);
   mozilla::ipc::IPCResult RecvLoadItem(const nsCString& aOriginSuffix,
                                        const nsCString& aOriginNoSuffix,
                                        const nsString& aKey,
@@ -93,19 +105,16 @@ private:
                                        const nsresult& aRv);
   mozilla::ipc::IPCResult RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins);
   mozilla::ipc::IPCResult RecvLoadUsage(const nsCString& aOriginNoSuffix,
                                         const int64_t& aUsage);
   mozilla::ipc::IPCResult RecvError(const nsresult& aRv);
 
   nsTHashtable<nsCStringHashKey>& OriginsHavingData();
 
-  ThreadSafeAutoRefCnt mRefCnt;
-  NS_DECL_OWNINGTHREAD
-
   // Held to get caches to forward answers to.
   RefPtr<LocalStorageManager> mManager;
 
   // Origins having data hash, for optimization purposes only
   nsAutoPtr<nsTHashtable<nsCStringHashKey>> mOriginsHavingData;
 
   // List of caches waiting for preload.  This ensures the contract that
   // AsyncPreload call references the cache for time of the preload.
@@ -118,23 +127,27 @@ private:
 };
 
 
 // Receives async requests from child processes and is responsible
 // to send back responses from the DB thread.  Exposes as a fake
 // LocalStorageCache consumer.
 // Also responsible for forwardning all chrome operation notifications
 // such as cookie cleaning etc to the child process.
-class StorageDBParent final : public PStorageParent
-                            , public StorageObserverSink
+class StorageDBParent final : public PBackgroundStorageParent
 {
+  class ObserverSink;
+
   virtual ~StorageDBParent();
 
 public:
-  StorageDBParent();
+  explicit StorageDBParent(const nsString& aProfilePath);
+
+  void
+  Init();
 
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
   NS_IMETHOD_(MozExternalRefCountType) Release(void);
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
   bool IPCOpen() { return mIPCOpen; }
@@ -142,17 +155,18 @@ public:
 public:
   // Fake cache class receiving async callbacks from DB thread, sending
   // them back to appropriate cache object on the child process.
   class CacheParentBridge : public LocalStorageCacheBridge {
   public:
     CacheParentBridge(StorageDBParent* aParentDB,
                       const nsACString& aOriginSuffix,
                       const nsACString& aOriginNoSuffix)
-      : mParent(aParentDB)
+      : mOwningEventTarget(GetCurrentThreadSerialEventTarget())
+      , mParent(aParentDB)
       , mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix)
       , mLoaded(false), mLoadedCount(0) {}
     virtual ~CacheParentBridge() {}
 
     // LocalStorageCacheBridge
     virtual const nsCString Origin() const;
     virtual const nsCString& OriginNoSuffix() const
       { return mOriginNoSuffix; }
@@ -162,44 +176,62 @@ public:
       { return mLoaded; }
     virtual uint32_t LoadedCount()
       { return mLoadedCount; }
 
     virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
     virtual void LoadDone(nsresult aRv);
     virtual void LoadWait();
 
+    NS_IMETHOD_(void)
+    Release(void);
+
   private:
+    void
+    Destroy();
+
+    nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
     RefPtr<StorageDBParent> mParent;
     nsCString mOriginSuffix, mOriginNoSuffix;
     bool mLoaded;
     uint32_t mLoadedCount;
   };
 
   // Fake usage class receiving async callbacks from DB thread
   class UsageParentBridge : public StorageUsageBridge
   {
   public:
     UsageParentBridge(StorageDBParent* aParentDB,
                       const nsACString& aOriginScope)
-      : mParent(aParentDB), mOriginScope(aOriginScope) {}
+      : mOwningEventTarget(GetCurrentThreadSerialEventTarget())
+      , mParent(aParentDB)
+      , mOriginScope(aOriginScope) {}
     virtual ~UsageParentBridge() {}
 
     // StorageUsageBridge
     virtual const nsCString& OriginScope() { return mOriginScope; }
     virtual void LoadUsage(const int64_t usage);
 
+    NS_IMETHOD_(MozExternalRefCountType)
+    Release(void);
+
   private:
+    void
+    Destroy();
+
+    nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
     RefPtr<StorageDBParent> mParent;
     nsCString mOriginScope;
   };
 
 private:
   // IPC
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  mozilla::ipc::IPCResult RecvDeleteMe() override;
+
   mozilla::ipc::IPCResult RecvAsyncPreload(const nsCString& aOriginSuffix,
                                            const nsCString& aOriginNoSuffix,
                                            const bool& aPriority) override;
   mozilla::ipc::IPCResult RecvPreload(const nsCString& aOriginSuffix,
                                       const nsCString& aOriginNoSuffix,
                                       const uint32_t& aAlreadyLoadedCount,
                                       InfallibleTArray<nsString>* aKeys,
                                       InfallibleTArray<nsString>* aValues,
@@ -215,28 +247,53 @@ private:
                                               const nsString& aValue) override;
   mozilla::ipc::IPCResult RecvAsyncRemoveItem(const nsCString& aOriginSuffix,
                                               const nsCString& aOriginNoSuffix,
                                               const nsString& aKey) override;
   mozilla::ipc::IPCResult RecvAsyncClear(const nsCString& aOriginSuffix,
                                          const nsCString& aOriginNoSuffix) override;
   mozilla::ipc::IPCResult RecvAsyncFlush() override;
 
-  // StorageObserverSink
-  virtual nsresult Observe(const char* aTopic,
-                           const nsAString& aOriginAttrPattern,
-                           const nsACString& aOriginScope) override;
+  mozilla::ipc::IPCResult RecvStartup() override;
+  mozilla::ipc::IPCResult RecvClearAll() override;
+  mozilla::ipc::IPCResult RecvClearMatchingOrigin(
+                                     const nsCString& aOriginNoSuffix) override;
+  mozilla::ipc::IPCResult RecvClearMatchingOriginAttributes(
+                              const OriginAttributesPattern& aPattern) override;
+
+  void Observe(const nsCString& aTopic,
+               const nsString& aOriginAttrPattern,
+               const nsCString& aOriginScope);
 
 private:
   CacheParentBridge* NewCache(const nsACString& aOriginSuffix,
                               const nsACString& aOriginNoSuffix);
 
+  RefPtr<ObserverSink> mObserverSink;
+
+  // A hack to deal with deadlock between the parent process main thread and
+  // background thread when invoking StorageDBThread::GetOrCreate because it
+  // cannot safely perform a synchronous dispatch back to the main thread
+  // (because we are already synchronously doing things on the stack).
+  // Populated for the same process actors, empty for other process actors.
+  nsString mProfilePath;
+
   ThreadSafeAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
   // True when IPC channel is open and Send*() methods are OK to use.
   bool mIPCOpen;
 };
 
+PBackgroundStorageParent*
+AllocPBackgroundStorageParent(const nsString& aProfilePath);
+
+mozilla::ipc::IPCResult
+RecvPBackgroundStorageConstructor(PBackgroundStorageParent* aActor,
+                                  const nsString& aProfilePath);
+
+bool
+DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor);
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_StorageIPC_h
--- a/dom/storage/StorageObserver.cpp
+++ b/dom/storage/StorageObserver.cpp
@@ -65,18 +65,19 @@ StorageObserver::Init()
   obs->AddObserver(sSelf, "perm-changed", true);
   obs->AddObserver(sSelf, "browser:purge-domain-data", true);
   obs->AddObserver(sSelf, "last-pb-context-exited", true);
   obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
   obs->AddObserver(sSelf, "extension:purge-localStorage", true);
 
   // Shutdown
   obs->AddObserver(sSelf, "profile-after-change", true);
-  obs->AddObserver(sSelf, "profile-before-change", true);
-  obs->AddObserver(sSelf, "xpcom-shutdown", true);
+  if (XRE_IsParentProcess()) {
+    obs->AddObserver(sSelf, "profile-before-change", true);
+  }
 
   // Observe low device storage notifications.
   obs->AddObserver(sSelf, "disk-space-watcher", true);
 
   // Testing
 #ifdef DOM_STORAGE_TESTS
   Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
 #endif
@@ -140,65 +141,85 @@ StorageObserver::Notify(const char* aTop
                         const nsACString& aOriginScope)
 {
   for (uint32_t i = 0; i < mSinks.Length(); ++i) {
     StorageObserverSink* sink = mSinks[i];
     sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
   }
 }
 
+void
+StorageObserver::NoteBackgroundThread(nsIEventTarget* aBackgroundThread)
+{
+  mBackgroundThread = aBackgroundThread;
+}
+
 NS_IMETHODIMP
 StorageObserver::Observe(nsISupports* aSubject,
                          const char* aTopic,
                          const char16_t* aData)
 {
   nsresult rv;
 
   // Start the thread that opens the database.
   if (!strcmp(aTopic, kStartupTopic)) {
+    MOZ_ASSERT(XRE_IsParentProcess());
+
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->RemoveObserver(this, kStartupTopic);
 
     mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
     if (!mDBThreadStartDelayTimer) {
       return NS_ERROR_UNEXPECTED;
     }
 
     mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay);
 
     return NS_OK;
   }
 
   // Timer callback used to start the database a short timer after startup
   if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
+    MOZ_ASSERT(XRE_IsParentProcess());
+
     nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
     if (!timer) {
       return NS_ERROR_UNEXPECTED;
     }
 
     if (timer == mDBThreadStartDelayTimer) {
       mDBThreadStartDelayTimer = nullptr;
 
-      StorageDBBridge* db = LocalStorageCache::StartDatabase();
-      NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+      StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+      if (NS_WARN_IF(!storageChild)) {
+        return NS_ERROR_FAILURE;
+      }
+
+      storageChild->SendStartup();
     }
 
     return NS_OK;
   }
 
   // Clear everything, caches + database
   if (!strcmp(aTopic, "cookie-changed")) {
     if (!NS_LITERAL_STRING("cleared").Equals(aData)) {
       return NS_OK;
     }
 
-    StorageDBBridge* db = LocalStorageCache::StartDatabase();
-    NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+    StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+    if (NS_WARN_IF(!storageChild)) {
+      return NS_ERROR_FAILURE;
+    }
 
-    db->AsyncClearAll();
+    storageChild->AsyncClearAll();
+
+    if (XRE_IsParentProcess()) {
+      storageChild->SendClearAll();
+    }
 
     Notify("cookie-cleared");
 
     return NS_OK;
   }
 
   // Clear from caches everything that has been stored
   // while in session-only mode
@@ -249,20 +270,26 @@ StorageObserver::Observe(nsISupports* aS
 
     Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
            originScope);
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "extension:purge-localStorage")) {
-    StorageDBBridge* db = LocalStorageCache::StartDatabase();
-    NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+    StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+    if (NS_WARN_IF(!storageChild)) {
+      return NS_ERROR_FAILURE;
+    }
 
-    db->AsyncClearAll();
+    storageChild->AsyncClearAll();
+
+    if (XRE_IsParentProcess()) {
+      storageChild->SendClearAll();
+    }
 
     Notify("extension:purge-localStorage-caches");
 
     return NS_OK;
   }
 
   // Clear everything (including so and pb data) from caches and database
   // for the gived domain and subdomains.
@@ -282,62 +309,79 @@ StorageObserver::Observe(nsISupports* aS
                         fallible);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     nsAutoCString originScope;
     rv = CreateReversedDomain(aceDomain, originScope);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    StorageDBBridge* db = LocalStorageCache::StartDatabase();
-    NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+    if (XRE_IsParentProcess()) {
+      StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+      if (NS_WARN_IF(!storageChild)) {
+        return NS_ERROR_FAILURE;
+      }
 
-    db->AsyncClearMatchingOrigin(originScope);
+      storageChild->SendClearMatchingOrigin(originScope);
+    }
 
     Notify("domain-data-cleared", EmptyString(), originScope);
 
     return NS_OK;
   }
 
   // Clear all private-browsing caches
   if (!strcmp(aTopic, "last-pb-context-exited")) {
     Notify("private-browsing-data-cleared");
 
     return NS_OK;
   }
 
   // Clear data of the origins whose prefixes will match the suffix.
   if (!strcmp(aTopic, "clear-origin-attributes-data")) {
+    MOZ_ASSERT(XRE_IsParentProcess());
+
     OriginAttributesPattern pattern;
     if (!pattern.Init(nsDependentString(aData))) {
       NS_ERROR("Cannot parse origin attributes pattern");
       return NS_ERROR_FAILURE;
     }
 
-    StorageDBBridge* db = LocalStorageCache::StartDatabase();
-    NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+    StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+    if (NS_WARN_IF(!storageChild)) {
+      return NS_ERROR_FAILURE;
+    }
 
-    db->AsyncClearMatchingOriginAttributes(pattern);
+    storageChild->SendClearMatchingOriginAttributes(pattern);
 
     Notify("origin-attr-pattern-cleared", nsDependentString(aData));
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "profile-after-change")) {
     Notify("profile-change");
 
     return NS_OK;
   }
 
-  if (!strcmp(aTopic, "profile-before-change") ||
-      !strcmp(aTopic, "xpcom-shutdown")) {
-    rv = LocalStorageCache::StopDatabase();
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Error while stopping Storage DB background thread");
+  if (!strcmp(aTopic, "profile-before-change")) {
+    MOZ_ASSERT(XRE_IsParentProcess());
+
+    if (mBackgroundThread) {
+      bool done = false;
+
+      RefPtr<StorageDBThread::ShutdownRunnable> shutdownRunnable =
+        new StorageDBThread::ShutdownRunnable(done);
+      MOZ_ALWAYS_SUCCEEDS(
+        mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL));
+
+      MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return done; }));
+
+      mBackgroundThread = nullptr;
     }
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "disk-space-watcher")) {
     if (NS_LITERAL_STRING("full").Equals(aData)) {
       Notify("low-disk-space");
@@ -345,21 +389,23 @@ StorageObserver::Observe(nsISupports* aS
       Notify("no-low-disk-space");
     }
 
     return NS_OK;
   }
 
 #ifdef DOM_STORAGE_TESTS
   if (!strcmp(aTopic, "domstorage-test-flush-force")) {
-    StorageDBBridge* db = LocalStorageCache::GetDatabase();
-    if (db) {
-      db->AsyncFlush();
+    StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+    if (NS_WARN_IF(!storageChild)) {
+      return NS_ERROR_FAILURE;
     }
 
+    storageChild->SendAsyncFlush();
+
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "domstorage-test-flushed")) {
     // Only used to propagate to IPC children
     Notify("test-flushed");
 
     return NS_OK;
--- a/dom/storage/StorageObserver.h
+++ b/dom/storage/StorageObserver.h
@@ -46,23 +46,28 @@ public:
   static StorageObserver* Self() { return sSelf; }
 
   void AddSink(StorageObserverSink* aObs);
   void RemoveSink(StorageObserverSink* aObs);
   void Notify(const char* aTopic,
               const nsAString& aOriginAttributesPattern = EmptyString(),
               const nsACString& aOriginScope = EmptyCString());
 
+  void
+  NoteBackgroundThread(nsIEventTarget* aBackgroundThread);
+
 private:
   virtual ~StorageObserver() {}
 
   static void TestingPrefChanged(const char* aPrefName, void* aClosure);
 
   static StorageObserver* sSelf;
 
+  nsCOMPtr<nsIEventTarget> mBackgroundThread;
+
   // Weak references
   nsTArray<StorageObserverSink*> mSinks;
   nsCOMPtr<nsITimer> mDBThreadStartDelayTimer;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/storage/moz.build
+++ b/dom/storage/moz.build
@@ -29,17 +29,17 @@ UNIFIED_SOURCES += [
     'StorageDBUpdater.cpp',
     'StorageIPC.cpp',
     'StorageNotifierService.cpp',
     'StorageObserver.cpp',
     'StorageUtils.cpp',
 ]
 
 IPDL_SOURCES += [
-    'PStorage.ipdl',
+    'PBackgroundStorage.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
--- a/dom/tests/browser/browser_localStorage_e10s.js
+++ b/dom/tests/browser/browser_localStorage_e10s.js
@@ -150,49 +150,57 @@ async function verifyTabPreload(knownTab
     });
   is(storageExists, expectStorageExists, "Storage existence === preload");
 }
 
 /**
  * Instruct the given tab to execute the given series of mutations.  For
  * simplicity, the mutations representation matches the expected events rep.
  */
-async function mutateTabStorage(knownTab, mutations) {
+async function mutateTabStorage(knownTab, mutations, sentinelValue) {
   await ContentTask.spawn(
     knownTab.tab.linkedBrowser,
-    { mutations },
+    { mutations, sentinelValue },
     function(args) {
-      return content.wrappedJSObject.mutateStorage(args.mutations);
+      return content.wrappedJSObject.mutateStorage(args);
     });
 }
 
 /**
  * Instruct the given tab to add a "storage" event listener and record all
  * received events.  verifyTabStorageEvents is the corresponding method to
  * check and assert the recorded events.
  */
-async function recordTabStorageEvents(knownTab) {
+async function recordTabStorageEvents(knownTab, sentinelValue) {
   await ContentTask.spawn(
     knownTab.tab.linkedBrowser,
-    {},
-    function() {
-      return content.wrappedJSObject.listenForStorageEvents();
+    sentinelValue,
+    function(sentinelValue) {
+      return content.wrappedJSObject.listenForStorageEvents(sentinelValue);
     });
 }
 
 /**
  * Retrieve the current localStorage contents perceived by the tab and assert
  * that they match the provided expected state.
+ *
+ * If maybeSentinel is non-null, it's assumed to be a string that identifies the
+ * value we should be waiting for the sentinel key to take on.  This is
+ * necessary because we cannot make any assumptions about when state will be
+ * propagated to the given process.  See the comments in
+ * page_localstorage_e10s.js for more context.  In general, a sentinel value is
+ * required for correctness unless the process in question is the one where the
+ * writes were performed or verifyTabStorageEvents was used.
  */
-async function verifyTabStorageState(knownTab, expectedState) {
+async function verifyTabStorageState(knownTab, expectedState, maybeSentinel) {
   let actualState = await ContentTask.spawn(
     knownTab.tab.linkedBrowser,
-    {},
-    function() {
-      return content.wrappedJSObject.getStorageState();
+    maybeSentinel,
+    function(maybeSentinel) {
+      return content.wrappedJSObject.getStorageState(maybeSentinel);
     });
 
   for (let [expectedKey, expectedValue] of Object.entries(expectedState)) {
     ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey);
     is(actualState[expectedKey], expectedValue, "value correct");
   }
   for (let actualKey of Object.keys(actualState)) {
     if (!expectedState.hasOwnProperty(actualKey)) {
@@ -200,16 +208,19 @@ async function verifyTabStorageState(kno
     }
   }
 }
 
 /**
  * Retrieve and clear the storage events recorded by the tab and assert that
  * they match the provided expected events.  For simplicity, the expected events
  * representation is the same as that used by mutateTabStorage.
+ *
+ * Note that by convention for test readability we are passed a 3rd argument of
+ * the sentinel value, but we don't actually care what it is.
  */
 async function verifyTabStorageEvents(knownTab, expectedEvents) {
   let actualEvents = await ContentTask.spawn(
     knownTab.tab.linkedBrowser,
     {},
     function() {
       return content.wrappedJSObject.returnAndClearStorageEvents();
     });
@@ -313,19 +324,22 @@ add_task(async function() {
     "lateWriteThenListen", knownTabs);
 
   // Sanity check that preloading did not occur in the tabs.
   await verifyTabPreload(writerTab, false);
   await verifyTabPreload(listenerTab, false);
   await verifyTabPreload(readerTab, false);
 
   // - Configure the tabs.
-  await recordTabStorageEvents(listenerTab);
+  const initialSentinel = 'initial';
+  const noSentinelCheck = null;
+  await recordTabStorageEvents(listenerTab, initialSentinel);
 
   // - Issue the initial batch of writes and verify.
+  info("initial writes");
   const initialWriteMutations = [
     //[key (null=clear), newValue (null=delete), oldValue (verification)]
     ["getsCleared", "1", null],
     ["alsoGetsCleared", "2", null],
     [null, null, null],
     ["stays", "3", null],
     ["clobbered", "pre", null],
     ["getsDeletedLater", "4", null],
@@ -336,75 +350,122 @@ add_task(async function() {
     ["clobbered", "post", "pre"]
   ];
   const initialWriteState = {
     stays: "3",
     clobbered: "post",
     alsoStays: "6"
   };
 
-  await mutateTabStorage(writerTab, initialWriteMutations);
+  await mutateTabStorage(writerTab, initialWriteMutations, initialSentinel);
 
-  await verifyTabStorageState(writerTab, initialWriteState);
-  await verifyTabStorageEvents(listenerTab, initialWriteMutations);
-  await verifyTabStorageState(listenerTab, initialWriteState);
-  await verifyTabStorageState(readerTab, initialWriteState);
+  // We expect the writer tab to have the correct state because it just did the
+  // writes.  We do not perform a sentinel-check because the writes should be
+  // locally available and consistent.
+  await verifyTabStorageState(writerTab, initialWriteState, noSentinelCheck);
+  // We expect the listener tab to have heard all events despite preload not
+  // having occurred and despite not issuing any reads or writes itself.  We
+  // intentionally check the events before the state because we're most
+  // interested in adding the listener having had a side-effect of subscribing
+  // to changes for the process.
+  //
+  // We ensure it had a chance to hear all of the events because we told
+  // recordTabStorageEvents to listen for the given sentinel.  The state check
+  // then does not need to do a sentinel check.
+  await verifyTabStorageEvents(
+    listenerTab, initialWriteMutations, initialSentinel);
+  await verifyTabStorageState(
+    listenerTab, initialWriteState, noSentinelCheck);
+  // We expect the reader tab to retrieve the current localStorage state from
+  // the database.  Because of the above checks, we are confident that the
+  // writes have hit PBackground and therefore that the (synchronous) state
+  // retrieval contains all the data we need.  No sentinel-check is required.
+  await verifyTabStorageState(readerTab, initialWriteState, noSentinelCheck);
 
   // - Issue second set of writes from lateWriteThenListen
+  // This tests that our new tab that begins by issuing only writes is building
+  // on top of the existing state (although we don't verify that until after the
+  // next set of mutations).  We also verify that the initial "writerTab" that
+  // was our first tab and started with only writes sees the writes, even though
+  // it did not add an event listener.
+
+  info("late writes");
+  const lateWriteSentinel = 'lateWrite';
   const lateWriteMutations = [
     ["lateStays", "10", null],
     ["lateClobbered", "latePre", null],
     ["lateDeleted", "11", null],
     ["lateClobbered", "lastPost", "latePre"],
     ["lateDeleted", null, "11"]
   ];
   const lateWriteState = Object.assign({}, initialWriteState, {
     lateStays: "10",
     lateClobbered: "lastPost"
   });
 
-  await mutateTabStorage(lateWriteThenListenTab, lateWriteMutations);
-  await recordTabStorageEvents(lateWriteThenListenTab);
+  await recordTabStorageEvents(listenerTab, lateWriteSentinel);
+
+  await mutateTabStorage(
+    lateWriteThenListenTab, lateWriteMutations, lateWriteSentinel);
 
-  await verifyTabStorageState(writerTab, lateWriteState);
-  await verifyTabStorageEvents(listenerTab, lateWriteMutations);
-  await verifyTabStorageState(listenerTab, lateWriteState);
-  await verifyTabStorageState(readerTab, lateWriteState);
+  // Verify the writer tab saw the writes.  It has to wait for the sentinel to
+  // appear before checking.
+  await verifyTabStorageState(writerTab, lateWriteState, lateWriteSentinel);
+  // Wait for the sentinel event before checking the events and then the state.
+  await verifyTabStorageEvents(
+    listenerTab, lateWriteMutations, lateWriteSentinel);
+  await verifyTabStorageState(listenerTab, lateWriteState, noSentinelCheck);
+  // We need to wait for the sentinel to show up for the reader.
+  await verifyTabStorageState(readerTab, lateWriteState, lateWriteSentinel);
 
   // - Issue last set of writes from writerTab.
+  info("last set of writes");
+  const lastWriteSentinel = 'lastWrite';
   const lastWriteMutations = [
     ["lastStays", "20", null],
     ["lastDeleted", "21", null],
     ["lastClobbered", "lastPre", null],
     ["lastClobbered", "lastPost", "lastPre"],
     ["lastDeleted", null, "21"]
   ];
   const lastWriteState = Object.assign({}, lateWriteState, {
     lastStays: "20",
     lastClobbered: "lastPost"
   });
 
-  await mutateTabStorage(writerTab, lastWriteMutations);
+  await recordTabStorageEvents(listenerTab, lastWriteSentinel);
+  await recordTabStorageEvents(lateWriteThenListenTab, lastWriteSentinel);
+
+  await mutateTabStorage(writerTab, lastWriteMutations, lastWriteSentinel);
 
-  await verifyTabStorageState(writerTab, lastWriteState);
-  await verifyTabStorageEvents(listenerTab, lastWriteMutations);
-  await verifyTabStorageState(listenerTab, lastWriteState);
-  await verifyTabStorageState(readerTab, lastWriteState);
-  await verifyTabStorageEvents(lateWriteThenListenTab, lastWriteMutations);
-  await verifyTabStorageState(lateWriteThenListenTab, lastWriteState);
+  // The writer performed the writes, no need to wait for the sentinel.
+  await verifyTabStorageState(writerTab, lastWriteState, noSentinelCheck);
+  // Wait for the sentinel event to be received, then check.
+  await verifyTabStorageEvents(
+    listenerTab, lastWriteMutations, lastWriteSentinel);
+  await verifyTabStorageState(listenerTab, lastWriteState, noSentinelCheck);
+  // We need to wait for the sentinel to show up for the reader.
+  await verifyTabStorageState(readerTab, lastWriteState, lastWriteSentinel);
+  // Wait for the sentinel event to be received, then check.
+  await verifyTabStorageEvents(
+    lateWriteThenListenTab, lastWriteMutations, lastWriteSentinel);
+  await verifyTabStorageState(
+    lateWriteThenListenTab, lastWriteState, noSentinelCheck);
 
   // - Force a LocalStorage DB flush so mOriginsHavingData is updated.
   // mOriginsHavingData is only updated when the storage thread runs its
   // accumulated operations during the flush.  If we don't initiate and ensure
   // that a flush has occurred before moving on to the next step,
   // mOriginsHavingData may not include our origin when it's sent down to the
   // child process.
+  info("flush to make preload check work");
   await triggerAndWaitForLocalStorageFlush();
 
   // - Open a fresh tab and make sure it sees the precache/preload
+  info("late open preload check");
   const lateOpenSeesPreload =
     await openTestTabInOwnProcess("lateOpenSeesPreload", knownTabs);
   await verifyTabPreload(lateOpenSeesPreload, true);
 
   // - Clean up.
   await cleanupTabs(knownTabs);
 
   clearOriginStorageEnsuringNoPreload();
--- a/dom/tests/browser/page_localstorage_e10s.html
+++ b/dom/tests/browser/page_localstorage_e10s.html
@@ -1,56 +1,137 @@
 <!doctype html>
 <html>
 <head>
   <meta charset="utf-8">
 <script>
 /**
  * Helper page used by browser_localStorage_e10s.js.
+ *
+ * We expose methods to be invoked by ContentTask.spawn() calls.
+ * ContentTask.spawn() uses the message manager and is PContent-based.  When
+ * LocalStorage was PContent-managed, ordering was inherently ensured so we
+ * could assume each page had already received all relevant events.  Now some
+ * explicit type of coordination is required.
+ *
+ * This gets complicated because:
+ * - LocalStorage is an ugly API that gives us almost unlimited implementation
+ *   flexibility in the face of multiple processes.  It's also an API that sites
+ *   may misuse which may encourage us to leverage that flexibility in the
+ *   future to improve performance at the expense of propagation latency, and
+ *   possibly involving content-observable coalescing of events.
+ * - The Quantum DOM effort and its event labeling and separate task queues and
+ *   green threading and current LocalStorage implementation mean that using
+ *   other PBackground-based APIs such as BroadcastChannel may not provide
+ *   reliable ordering guarantees.  Specifically, it's hard to guarantee that
+ *   a BroadcastChannel postMessage() issued after a series of LocalStorage
+ *   writes won't be received by the target window before the writes are
+ *   perceived.  At least not without constraining the implementations of both
+ *   APIs.
+ * - Some of our tests explicitly want to verify LocalStorage behavior without
+ *   having a "storage" listener, so we can't add a storage listener if the test
+ *   didn't already want one.
+ *
+ * We use 2 approaches for coordination:
+ * 1. If we're already listening for events, we listen for the sentinel value to
+ *    be written.  This is efficient and appropriate in this case.
+ * 2. If we're not listening for events, we use setTimeout(0) to poll the
+ *    localStorage key and value until it changes to our expected value.
+ *    setTimeout(0) eventually clamps to setTimeout(4), so in the event we are
+ *    experiencing delays, we have reasonable, non-CPU-consuming back-off in
+ *    place that leaves the CPU free to time out and fail our test if something
+ *    broke.  This is ugly but makes us less brittle.
+ *
+ * Both of these involve mutateStorage writing the sentinel value at the end of
+ * the batch.  All of our result-returning methods accordingly filter out the
+ * sentinel key/value pair.
  **/
 var pageName = document.location.search.substring(1);
 window.addEventListener(
   "load",
   () => { document.getElementById("pageNameH").textContent = pageName; });
 
-var recordedEvents = null;
-function storageListener(event) {
-  recordedEvents.push([event.key, event.newValue, event.oldValue]);
+// Key that conveys the end of a write batch.  Filtered out from state and
+// events.
+const SENTINEL_KEY = 'WRITE_BATCH_SENTINEL';
+
+var storageEventsPromise = null;
+function listenForStorageEvents(sentinelValue) {
+  const recordedEvents = [];
+  storageEventsPromise = new Promise(function(resolve, reject) {
+    window.addEventListener(
+      "storage",
+      function thisHandler(event) {
+        if (event.key === SENTINEL_KEY) {
+          // There should be no way for this to have the wrong value, but reject
+          // if it is wrong.
+          if (event.newValue === sentinelValue) {
+            window.removeEventListener("storage", thisHandler);
+            resolve(recordedEvents);
+          } else {
+            reject(event.newValue);
+          }
+        } else {
+          recordedEvents.push([event.key, event.newValue, event.oldValue]);
+        }
+      });
+  });
 }
 
-function listenForStorageEvents() {
-  recordedEvents = [];
-  window.addEventListener("storage", storageListener);
-}
-
-function mutateStorage(mutations) {
+function mutateStorage({ mutations, sentinelValue }) {
   mutations.forEach(function([key, value]) {
     if (key !== null) {
       if (value === null) {
         localStorage.removeItem(key);
       } else {
         localStorage.setItem(key, value);
       }
     } else {
       localStorage.clear();
     }
   });
+  localStorage.setItem(SENTINEL_KEY, sentinelValue);
 }
 
-function getStorageState() {
+// Returns a promise that is resolve when the sentinel key has taken on the
+// sentinel value.  Oddly structured to make sure promises don't let us
+// accidentally side-step the timeout clamping logic.
+function waitForSentinelValue(sentinelValue) {
+  return new Promise(function(resolve) {
+    function checkFunc() {
+      if (localStorage.getItem(SENTINEL_KEY) === sentinelValue) {
+        resolve();
+      } else {
+        // I believe linters will only yell at us if we use a non-zero constant.
+        // Other forms of back-off were considered, including attempting to
+        // issue a round-trip through PBackground, but that still potentially
+        // runs afoul of labeling while also making us dependent on unrelated
+        // APIs.
+        setTimeout(checkFunc, 0);
+      }
+    }
+    checkFunc();
+  });
+}
+
+async function getStorageState(maybeSentinel) {
+  if (maybeSentinel) {
+    await waitForSentinelValue(maybeSentinel);
+  }
+
   let numKeys = localStorage.length;
   let state = {};
   for (var iKey = 0; iKey < numKeys; iKey++) {
     let key = localStorage.key(iKey);
-    state[key] = localStorage.getItem(key);
+    if (key !== SENTINEL_KEY) {
+      state[key] = localStorage.getItem(key);
+    }
   }
   return state;
 }
 
 function returnAndClearStorageEvents() {
-  let loggedEvents = recordedEvents;
-  recordedEvents = [];
-  return loggedEvents;
+  return storageEventsPromise;
 }
 </script>
 </head>
 <body><h2 id="pageNameH"></h2></body>
 </html>
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -312,22 +312,16 @@ partial interface Window {
 
            attribute EventHandler ondevicemotion;
            attribute EventHandler ondeviceorientation;
            attribute EventHandler onabsolutedeviceorientation;
            attribute EventHandler ondeviceproximity;
            attribute EventHandler onuserproximity;
            attribute EventHandler ondevicelight;
 
-#ifdef MOZ_B2G
-           attribute EventHandler onmoztimechange;
-           attribute EventHandler onmoznetworkupload;
-           attribute EventHandler onmoznetworkdownload;
-#endif
-
   void                      dump(DOMString str);
 
   /**
    * This method is here for backwards compatibility with 4.x only,
    * its implementation is a no-op
    */
   void                      setResizable(boolean resizable);
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -1126,24 +1126,16 @@ if CONFIG['MOZ_BUILD_APP'] in ['browser'
         'BrowserFeedWriter.webidl',
     ]
 
 if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']:
     WEBIDL_FILES += [
         'External.webidl',
     ]
 
-if CONFIG['MOZ_B2G']:
-    WEBIDL_FILES += [
-        'MozApplicationEvent.webidl'
-    ]
-    GENERATED_EVENTS_WEBIDL_FILES += [
-        'MozApplicationEvent.webidl'
-    ]
-
 if CONFIG['ACCESSIBILITY']:
     WEBIDL_FILES += [
         'AccessibleNode.webidl',
     ]
 
 if CONFIG['ENABLE_INTL_API']:
     WEBIDL_FILES += [
         'IntlUtils.webidl',
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -1797,28 +1797,16 @@ nsPermissionManager::AddInternal(nsIPrin
       if (aDBOperation == eWriteToDB) {
         // we'll be writing to the database - generate a known unique id
         id = ++mLargestID;
       } else {
         // we're reading from the database - use the id already assigned
         id = aID;
       }
 
-#ifdef MOZ_B2G
-      // When we do the initial addition of the permissions we don't want to
-      // inherit session specific permissions from other tabs or apps
-      // so we ignore them and set the permission to PROMPT_ACTION if it was
-      // previously allowed or denied by the user.
-      if (aIgnoreSessionPermissions &&
-          aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
-        aPermission = nsIPermissionManager::PROMPT_ACTION;
-        aExpireType = nsIPermissionManager::EXPIRE_NEVER;
-      }
-#endif // MOZ_B2G
-
       entry->GetPermissions().AppendElement(PermissionEntry(id, typeIndex, aPermission,
                                                             aExpireType, aExpireTime,
                                                             aModificationTime));
 
       // Record a count of the number of preload permissions present in the
       // content process.
       if (IsPreloadPermission(mTypeArray[typeIndex].get())) {
         sPreloadPermissionCount++;
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -880,17 +880,17 @@ protected:
 
 class DrawTargetCapture;
 
 /** This is the main class used for all the drawing. It is created through the
  * factory and accepts drawing commands. The results of drawing to a target
  * may be used either through a Snapshot or by flushing the target and directly
  * accessing the backing store a DrawTarget was created with.
  */
-class DrawTarget : public RefCounted<DrawTarget>
+class DrawTarget : public external::AtomicRefCounted<DrawTarget>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTarget)
   DrawTarget() : mTransformDirty(false), mPermitSubpixelAA(false) {}
   virtual ~DrawTarget() {}
 
   virtual bool IsValid() const { return true; };
   virtual DrawTargetType GetType() const = 0;
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -30,19 +30,16 @@
 #include "gfxPrefs.h"
 #include "ScopedGLHelpers.h"
 
 namespace mozilla {
 namespace gl {
 
 StaticMutex GLLibraryEGL::sMutex;
 GLLibraryEGL sEGLLibrary;
-#ifdef MOZ_B2G
-MOZ_THREAD_LOCAL(EGLContext) GLLibraryEGL::sCurrentContext;
-#endif
 
 // should match the order of EGLExtensions, and be null-terminated.
 static const char* sEGLExtensionNames[] = {
     "EGL_KHR_image_base",
     "EGL_KHR_image_pixmap",
     "EGL_KHR_gl_texture_2D_image",
     "EGL_KHR_lock_surface",
     "EGL_ANGLE_surface_d3d_texture_2d_share_handle",
@@ -315,21 +312,16 @@ bool
 GLLibraryEGL::EnsureInitialized(bool forceAccel, nsACString* const out_failureId)
 {
     if (mInitialized) {
         return true;
     }
 
     mozilla::ScopedGfxFeatureReporter reporter("EGL");
 
-#ifdef MOZ_B2G
-    if (!sCurrentContext.init())
-      MOZ_CRASH("GFX: Tls init failed");
-#endif
-
 #ifdef XP_WIN
     if (!mEGLLibrary) {
         // On Windows, the GLESv2, EGL and DXSDK libraries are shipped with libxul and
         // we should look for them there. We have to load the libs in this
         // order, because libEGL.dll depends on libGLESv2.dll which depends on the DXSDK
         // libraries. This matters especially for WebRT apps which are in a different directory.
         // See bug 760323 and bug 749459
 
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -481,42 +481,22 @@ private:
                                                                  EGLStreamKHR stream,
                                                                  void* texture,
                                                                  const EGLAttrib* attrib_list);
         void       (GLAPIENTRY * fANGLEPlatformInitialize)(angle::Platform* platform);
         void       (GLAPIENTRY * fANGLEPlatformShutdown)();
     } mSymbols;
 
 public:
-#ifdef MOZ_B2G
-    EGLContext CachedCurrentContext() {
-        return sCurrentContext.get();
-    }
-    void UnsetCachedCurrentContext() {
-        sCurrentContext.set(nullptr);
-    }
-    void SetCachedCurrentContext(EGLContext aCtx) {
-        sCurrentContext.set(aCtx);
-    }
-    bool CachedCurrentContextMatches() {
-        return sCurrentContext.get() == fGetCurrentContext();
-    }
-
-private:
-    static MOZ_THREAD_LOCAL(EGLContext) sCurrentContext;
-public:
-
-#else
     EGLContext CachedCurrentContext() {
         return nullptr;
     }
     void UnsetCachedCurrentContext() {}
     void SetCachedCurrentContext(EGLContext aCtx) { }
     bool CachedCurrentContextMatches() { return true; }
-#endif
 
 private:
     bool mInitialized;
     PRLibrary* mEGLLibrary;
     EGLDisplay mEGLDisplay;
     RefPtr<GLContext> mReadbackGL;
 
     bool mIsANGLE;
--- a/gfx/layers/apz/test/mochitest/test_group_touchevents.html
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
@@ -30,16 +30,17 @@ var basic_pan_prefs = [
   // expiry for these tests.
   ["apz.displayport_expiry_ms", 0],
 ];
 
 var touch_action_prefs = basic_pan_prefs.slice(); // make a copy
 touch_action_prefs.push(["layout.css.touch_action.enabled", true]);
 
 var isWindows = (getPlatform() == "windows");
+touch_action_prefs.push(["apz.test.fails_with_native_injection", isWindows]);
 
 var subtests = [
   // Simple tests to exercise basic panning behaviour
   {'file': 'helper_basic_pan.html', 'prefs': basic_pan_prefs},
   {'file': 'helper_div_pan.html', 'prefs': basic_pan_prefs},
   {'file': 'helper_iframe_pan.html', 'prefs': basic_pan_prefs},
 
   // Simple test to exercise touch-tapping behaviour
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -1070,17 +1070,18 @@ TextureClient::CreateForDrawing(TextureF
 
   if (!gfx::Factory::AllowedSurfaceSize(aSize)) {
     return nullptr;
   }
 
   TextureData* data = nullptr;
 
 #ifdef XP_WIN
-  if (aLayersBackend == LayersBackend::LAYERS_D3D11 &&
+  if ((aLayersBackend == LayersBackend::LAYERS_D3D11 ||
+       aLayersBackend == LayersBackend::LAYERS_WR) &&
       (moz2DBackend == gfx::BackendType::DIRECT2D ||
        moz2DBackend == gfx::BackendType::DIRECT2D1_1 ||
        (!!(aAllocFlags & ALLOC_FOR_OUT_OF_BAND_CONTENT) &&
         DeviceManagerDx::Get()->GetContentDevice())) &&
       aSize.width <= aMaxTextureSize &&
       aSize.height <= aMaxTextureSize &&
       !(aAllocFlags & ALLOC_UPDATE_FROM_SURFACE))
   {
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -322,16 +322,19 @@ DXGITextureData::PrepareDrawTargetInLock
   // write into.
   if (!mDrawTarget && (aMode & OpenMode::OPEN_WRITE || mNeedsClear || mNeedsClearWhite)) {
     mDrawTarget = BorrowDrawTarget();
     if (!mDrawTarget) {
       return false;
     }
   }
 
+  // Reset transform
+  mDrawTarget->SetTransform(Matrix());
+
   if (mNeedsClear) {
     mDrawTarget->ClearRect(Rect(0, 0, mSize.width, mSize.height));
     mNeedsClear = false;
   }
   if (mNeedsClearWhite) {
     mDrawTarget->FillRect(Rect(0, 0, mSize.width, mSize.height), ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
     mNeedsClearWhite = false;
   }
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -668,16 +668,24 @@ WebRenderLayerManager::EndTransactionInt
       mIsFirstPaint = false;
     }
     mScrollData.SetPaintSequenceNumber(mPaintSequenceNumber);
   }
 
   bool sync = mTarget != nullptr;
   mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true);
 
+  // Skip the synchronization for buffer since we also skip the painting during
+  // device-reset status.
+  if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
+    if (WrBridge()->GetSyncObject() &&
+        WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
+      WrBridge()->GetSyncObject()->Synchronize();
+    }
+  }
   {
     AutoProfilerTracing
       tracing("Paint", sync ? "ForwardDPTransactionSync":"ForwardDPTransaction");
     WrBridge()->DPEnd(builder, size.ToUnknownSize(), sync, mLatestTransactionId, mScrollData);
   }
 
   MakeSnapshotIfRequired(size);
   mNeedsComposite = false;
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -52,16 +52,17 @@
 #include "nsWindowsHelpers.h"
 #include "gfx2DGlue.h"
 
 #include <string>
 
 #include <d3d10_1.h>
 
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
 
 #include "nsMemory.h"
 
 #include <dwmapi.h>
 #include <d3d11.h>
 #include <d2d1_1.h>
 
 #include "nsIMemoryReporter.h"
@@ -473,16 +474,20 @@ gfxWindowsPlatform::UpdateRenderMode()
 mozilla::gfx::BackendType
 gfxWindowsPlatform::GetContentBackendFor(mozilla::layers::LayersBackend aLayers)
 {
   mozilla::gfx::BackendType defaultBackend = gfxPlatform::GetDefaultContentBackend();
   if (aLayers == LayersBackend::LAYERS_D3D11) {
     return defaultBackend;
   }
 
+  if (aLayers == LayersBackend::LAYERS_WR && gfx::gfxVars::UseWebRenderANGLE()) {
+    return defaultBackend;
+  }
+
   if (defaultBackend == BackendType::DIRECT2D1_1) {
     // We can't have D2D without D3D11 layers, so fallback to Skia.
     return BackendType::SKIA;
   }
 
   // Otherwise we have some non-accelerated backend and that's ok.
   return defaultBackend;
 }
--- a/intl/strres/nsIStringBundle.idl
+++ b/intl/strres/nsIStringBundle.idl
@@ -28,21 +28,21 @@ interface nsIStringBundle : nsISupports
   AString GetStringFromName(in AUTF8String aName);
 
   // This method is mostly used from C++, where |string| is appropriate because
   // the names are most often 8-bit string literals (normally ASCII, though
   // u8"foo" literals will also work).
   [noscript, binaryname(GetStringFromName)]
   AString GetStringFromNameCpp(in string aName);
 
-  // this is kind of like smprintf - except that you can
+  // this is kind of like ssprintf - except that you can
   // only pass it unicode strings, using the %S formatting character.
   // the id or name should refer to a string in the bundle that
   // uses %S.. do NOT try to use any other types.
-  // this uses nsTextFormatter::smprintf to do the dirty work.
+  // this uses nsTextFormatter::ssprintf to do the dirty work.
   AString formatStringFromID(in long aID,
                              [array, size_is(length)] in wstring params,
                              in unsigned long length);
 
   // This method is mostly used from JS, where AUTF8String is appropriate.
   [binaryname(FormatStringFromAUTF8Name)]
   AString formatStringFromName(in AUTF8String aName,
                                [array, size_is(length)] in wstring params,
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -20,19 +20,21 @@
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamChild.h"
 #include "mozilla/dom/ipc/PendingIPCBlobChild.h"
 #include "mozilla/dom/quota/PQuotaChild.h"
+#include "mozilla/dom/StorageIPC.h"
 #include "mozilla/dom/GamepadEventChannelChild.h"
 #include "mozilla/dom/GamepadTestChannelChild.h"
 #include "mozilla/dom/MessagePortChild.h"
+#include "mozilla/dom/LocalStorage.h"
 #include "mozilla/ipc/IPCStreamAlloc.h"
 #include "mozilla/ipc/PBackgroundTestChild.h"
 #include "mozilla/ipc/PChildToParentStreamChild.h"
 #include "mozilla/ipc/PParentToChildStreamChild.h"
 #include "mozilla/layout/VsyncChild.h"
 #include "mozilla/net/HttpBackgroundChannelChild.h"
 #include "mozilla/net/PUDPSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
@@ -72,16 +74,18 @@ namespace ipc {
 
 using mozilla::dom::UDPSocketChild;
 using mozilla::net::PUDPSocketChild;
 
 using mozilla::dom::asmjscache::PAsmJSCacheEntryChild;
 using mozilla::dom::cache::PCacheChild;
 using mozilla::dom::cache::PCacheStorageChild;
 using mozilla::dom::cache::PCacheStreamControlChild;
+using mozilla::dom::LocalStorage;
+using mozilla::dom::StorageDBChild;
 
 using mozilla::dom::WebAuthnTransactionChild;
 
 // -----------------------------------------------------------------------------
 // BackgroundChildImpl::ThreadLocal
 // -----------------------------------------------------------------------------
 
 BackgroundChildImpl::
@@ -197,16 +201,33 @@ BackgroundChildImpl::DeallocPBackgroundI
                                          PBackgroundIndexedDBUtilsChild* aActor)
 {
   MOZ_ASSERT(aActor);
 
   delete aActor;
   return true;
 }
 
+BackgroundChildImpl::PBackgroundStorageChild*
+BackgroundChildImpl::AllocPBackgroundStorageChild(const nsString& aProfilePath)
+{
+  MOZ_CRASH("PBackgroundStorageChild actors should be manually constructed!");
+}
+
+bool
+BackgroundChildImpl::DeallocPBackgroundStorageChild(
+                                                PBackgroundStorageChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  StorageDBChild* child = static_cast<StorageDBChild*>(aActor);
+  child->ReleaseIPDLReference();
+  return true;
+}
+
 PPendingIPCBlobChild*
 BackgroundChildImpl::AllocPPendingIPCBlobChild(const IPCBlob& aBlob)
 {
   return new mozilla::dom::PendingIPCBlobChild(aBlob);
 }
 
 bool
 BackgroundChildImpl::DeallocPPendingIPCBlobChild(PPendingIPCBlobChild* aActor)
@@ -573,16 +594,42 @@ BackgroundChildImpl::DeallocPHttpBackgro
   // The reference is increased in BackgroundChannelCreateCallback::ActorCreated
   // of HttpBackgroundChannelChild.cpp. We should decrease it after IPC
   // destroyed.
   RefPtr<net::HttpBackgroundChannelChild> child =
     dont_AddRef(static_cast<net::HttpBackgroundChannelChild*>(aActor));
   return true;
 }
 
+mozilla::ipc::IPCResult
+BackgroundChildImpl::RecvDispatchLocalStorageChange(
+                                            const nsString& aDocumentURI,
+                                            const nsString& aKey,
+                                            const nsString& aOldValue,
+                                            const nsString& aNewValue,
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const bool& aIsPrivate)
+{
+  if (!NS_IsMainThread()) {
+    return IPC_OK();
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIPrincipal> principal =
+    PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  LocalStorage::DispatchStorageEvent(aDocumentURI, aKey, aOldValue, aNewValue,
+                                     principal, aIsPrivate, nullptr, true);
+
+  return IPC_OK();
+}
+
 } // namespace ipc
 } // namespace mozilla
 
 mozilla::ipc::IPCResult
 TestChild::Recv__delete__(const nsCString& aTestArg)
 {
   MOZ_RELEASE_ASSERT(aTestArg == mTestArg,
                      "BackgroundTest message was corrupted!");
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -65,16 +65,22 @@ protected:
 
   virtual PBackgroundIndexedDBUtilsChild*
   AllocPBackgroundIndexedDBUtilsChild() override;
 
   virtual bool
   DeallocPBackgroundIndexedDBUtilsChild(PBackgroundIndexedDBUtilsChild* aActor)
                                         override;
 
+  virtual PBackgroundStorageChild*
+  AllocPBackgroundStorageChild(const nsString& aProfilePath) override;
+
+  virtual bool
+  DeallocPBackgroundStorageChild(PBackgroundStorageChild* aActor) override;
+
   virtual PPendingIPCBlobChild*
   AllocPPendingIPCBlobChild(const IPCBlob& aBlob) override;
 
   virtual bool
   DeallocPPendingIPCBlobChild(PPendingIPCBlobChild* aActor) override;
 
   virtual PIPCBlobInputStreamChild*
   AllocPIPCBlobInputStreamChild(const nsID& aID,
@@ -203,16 +209,24 @@ protected:
   virtual bool
   DeallocPWebAuthnTransactionChild(PWebAuthnTransactionChild* aActor) override;
 
   virtual PHttpBackgroundChannelChild*
   AllocPHttpBackgroundChannelChild(const uint64_t& aChannelId) override;
 
   virtual bool
   DeallocPHttpBackgroundChannelChild(PHttpBackgroundChannelChild* aActor) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
+                                 const nsString& aKey,
+                                 const nsString& aOldValue,
+                                 const nsString& aNewValue,
+                                 const PrincipalInfo& aPrincipalInfo,
+                                 const bool& aIsPrivate) override;
 };
 
 class BackgroundChildImpl::ThreadLocal final
 {
   friend class nsAutoPtr<ThreadLocal>;
 
 public:
   nsAutoPtr<mozilla::dom::indexedDB::ThreadLocal> mIndexedDBThreadLocal;
--- a/ipc/glue/BackgroundImpl.cpp
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -211,16 +211,21 @@ private:
   GetRawContentParentForComparison(PBackgroundParent* aBackgroundActor);
 
   // Forwarded from BackgroundParent.
   static uint64_t
   GetChildID(PBackgroundParent* aBackgroundActor);
 
   // Forwarded from BackgroundParent.
   static bool
+  GetLiveActorArray(PBackgroundParent* aBackgroundActor,
+                    nsTArray<PBackgroundParent*>& aLiveActorArray);
+
+  // Forwarded from BackgroundParent.
+  static bool
   Alloc(ContentParent* aContent,
         Endpoint<PBackgroundParent>&& aEndpoint);
 
   static bool
   CreateBackgroundThread();
 
   static void
   ShutdownBackgroundThread();
@@ -700,16 +705,25 @@ BackgroundParent::GetRawContentParentFor
 uint64_t
 BackgroundParent::GetChildID(PBackgroundParent* aBackgroundActor)
 {
   return ParentImpl::GetChildID(aBackgroundActor);
 }
 
 // static
 bool
+BackgroundParent::GetLiveActorArray(
+                                  PBackgroundParent* aBackgroundActor,
+                                  nsTArray<PBackgroundParent*>& aLiveActorArray)
+{
+  return ParentImpl::GetLiveActorArray(aBackgroundActor, aLiveActorArray);
+}
+
+// static
+bool
 BackgroundParent::Alloc(ContentParent* aContent,
                         Endpoint<PBackgroundParent>&& aEndpoint)
 {
   return ParentImpl::Alloc(aContent, Move(aEndpoint));
 }
 
 // -----------------------------------------------------------------------------
 // BackgroundChild Public Methods
@@ -868,16 +882,43 @@ ParentImpl::GetChildID(PBackgroundParent
     return actor->mContent->ChildID();
   }
 
   return 0;
 }
 
 // static
 bool
+ParentImpl::GetLiveActorArray(PBackgroundParent* aBackgroundActor,
+                              nsTArray<PBackgroundParent*>& aLiveActorArray)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aBackgroundActor);
+  MOZ_ASSERT(aLiveActorArray.IsEmpty());
+
+  auto actor = static_cast<ParentImpl*>(aBackgroundActor);
+  if (actor->mActorDestroyed) {
+    MOZ_ASSERT(false,
+               "GetLiveActorArray called after ActorDestroy was called!");
+    return false;
+  }
+
+  if (!actor->mLiveActorArray) {
+    return true;
+  }
+
+  for (ParentImpl* liveActor : *actor->mLiveActorArray) {
+    aLiveActorArray.AppendElement(liveActor);
+  }
+
+  return true;
+}
+
+// static
+bool
 ParentImpl::Alloc(ContentParent* aContent,
                   Endpoint<PBackgroundParent>&& aEndpoint)
 {
   AssertIsInMainProcess();
   AssertIsOnMainThread();
   MOZ_ASSERT(aEndpoint.IsValid());
 
   if (!sBackgroundThread && !CreateBackgroundThread()) {
--- a/ipc/glue/BackgroundParent.h
+++ b/ipc/glue/BackgroundParent.h
@@ -62,16 +62,20 @@ public:
   // ContentParent after the ContentParent has died. This function may only be
   // called on the background thread.
   static intptr_t
   GetRawContentParentForComparison(PBackgroundParent* aBackgroundActor);
 
   static uint64_t
   GetChildID(PBackgroundParent* aBackgroundActor);
 
+  static bool
+  GetLiveActorArray(PBackgroundParent* aBackgroundActor,
+                    nsTArray<PBackgroundParent*>& aLiveActorArray);
+
 private:
   // Only called by ContentParent for cross-process actors.
   static bool
   Alloc(ContentParent* aContent,
         Endpoint<PBackgroundParent>&& aEndpoint);
 };
 
 // Implemented in BackgroundImpl.cpp.
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -24,16 +24,17 @@
 #include "mozilla/dom/MessagePortParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamParent.h"
 #include "mozilla/dom/ipc/PendingIPCBlobParent.h"
 #include "mozilla/dom/quota/ActorsParent.h"
+#include "mozilla/dom/StorageIPC.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/IPCStreamAlloc.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/ipc/PBackgroundTestParent.h"
 #include "mozilla/ipc/PChildToParentStreamParent.h"
 #include "mozilla/ipc/PParentToChildStreamParent.h"
 #include "mozilla/layout/VsyncParent.h"
@@ -237,16 +238,74 @@ BackgroundParentImpl::RecvFlushPendingFi
   AssertIsOnBackgroundThread();
 
   if (!mozilla::dom::indexedDB::RecvFlushPendingFileDeletions()) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
+auto
+BackgroundParentImpl::AllocPBackgroundStorageParent(const nsString& aProfilePath)
+  -> PBackgroundStorageParent*
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  return mozilla::dom::AllocPBackgroundStorageParent(aProfilePath);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundStorageConstructor(
+                                               PBackgroundStorageParent* aActor,
+                                               const nsString& aProfilePath)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  return mozilla::dom::RecvPBackgroundStorageConstructor(aActor, aProfilePath);
+}
+
+bool
+BackgroundParentImpl::DeallocPBackgroundStorageParent(
+                                               PBackgroundStorageParent* aActor)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  return mozilla::dom::DeallocPBackgroundStorageParent(aActor);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvBroadcastLocalStorageChange(
+                                            const nsString& aDocumentURI,
+                                            const nsString& aKey,
+                                            const nsString& aOldValue,
+                                            const nsString& aNewValue,
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const bool& aIsPrivate)
+{
+  nsTArray<PBackgroundParent*> liveActorArray;
+  if (NS_WARN_IF(!BackgroundParent::GetLiveActorArray(this, liveActorArray))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  for (auto* liveActor : liveActorArray) {
+    if (liveActor != this) {
+      Unused << liveActor->SendDispatchLocalStorageChange(
+        nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
+        nsString(aNewValue), aPrincipalInfo, aIsPrivate);
+    }
+  }
+
+  return IPC_OK();
+}
+
 PPendingIPCBlobParent*
 BackgroundParentImpl::AllocPPendingIPCBlobParent(const IPCBlob& aBlob)
 {
   MOZ_CRASH("PPendingIPCBlobParent actors should be manually constructed!");
 }
 
 bool
 BackgroundParentImpl::DeallocPPendingIPCBlobParent(PPendingIPCBlobParent* aActor)
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -58,16 +58,34 @@ protected:
   virtual bool
   DeallocPBackgroundIndexedDBUtilsParent(
                                         PBackgroundIndexedDBUtilsParent* aActor)
                                         override;
 
   virtual mozilla::ipc::IPCResult
   RecvFlushPendingFileDeletions() override;
 
+  virtual PBackgroundStorageParent*
+  AllocPBackgroundStorageParent(const nsString& aProfilePath) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvPBackgroundStorageConstructor(PBackgroundStorageParent* aActor,
+                                    const nsString& aProfilePath) override;
+
+  virtual bool
+  DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
+                                  const nsString& aKey,
+                                  const nsString& aOldValue,
+                                  const nsString& aNewValue,
+                                  const PrincipalInfo& aPrincipalInfo,
+                                  const bool& aIsPrivate) override;
+
   virtual PPendingIPCBlobParent*
   AllocPPendingIPCBlobParent(const IPCBlob& aBlob) override;
 
   virtual bool
   DeallocPPendingIPCBlobParent(PPendingIPCBlobParent* aActor) override;
 
   virtual PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID,
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -974,11 +974,47 @@ struct ParamTraits<mozilla::Variant<Ts..
     Tag tag;
     if (ReadParam(msg, iter, &tag)) {
       return VariantReader<sizeof...(Ts)>::Read(msg, iter, tag, result);
     }
     return false;
   }
 };
 
+template<typename T>
+struct ParamTraits<mozilla::dom::Optional<T>>
+{
+  typedef mozilla::dom::Optional<T> paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    if (aParam.WasPassed()) {
+      WriteParam(aMsg, true);
+      WriteParam(aMsg, aParam.Value());
+      return;
+    }
+
+    WriteParam(aMsg, false);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    bool wasPassed = false;
+
+    if (!ReadParam(aMsg, aIter, &wasPassed)) {
+      return false;
+    }
+
+    aResult->Reset();
+
+    if (wasPassed) {
+      if (!ReadParam(aMsg, aIter, &aResult->Construct())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
 } /* namespace IPC */
 
 #endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -28,23 +28,18 @@
 using namespace mozilla::tasktracer;
 #endif
 
 using mozilla::Move;
 
 // Undo the damage done by mozzconf.h
 #undef compress
 
-// Logging seems to be somewhat broken on b2g.
-#ifdef MOZ_B2G
-#define IPC_LOG(...)
-#else
 static mozilla::LazyLogModule sLogModule("ipc");
 #define IPC_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__))
-#endif
 
 /*
  * IPC design:
  *
  * There are three kinds of messages: async, sync, and intr. Sync and intr
  * messages are blocking.
  *
  * Terminology: To dispatch a message Foo is to run the RecvFoo code for
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PAsmJSCacheEntry;
 include protocol PBackgroundIDBFactory;
 include protocol PBackgroundIndexedDBUtils;
+include protocol PBackgroundStorage;
 include protocol PBackgroundTest;
 include protocol PBroadcastChannel;
 include protocol PCache;
 include protocol PCacheStorage;
 include protocol PCacheStreamControl;
 include protocol PFileDescriptorSet;
 include protocol PFileSystemRequest;
 include protocol PGamepadEventChannel;
@@ -48,16 +49,17 @@ using mozilla::dom::asmjscache::WritePar
 namespace mozilla {
 namespace ipc {
 
 sync protocol PBackground
 {
   manages PAsmJSCacheEntry;
   manages PBackgroundIDBFactory;
   manages PBackgroundIndexedDBUtils;
+  manages PBackgroundStorage;
   manages PBackgroundTest;
   manages PBroadcastChannel;
   manages PCache;
   manages PCacheStorage;
   manages PCacheStreamControl;
   manages PFileDescriptorSet;
   manages PFileSystemRequest;
   manages PGamepadEventChannel;
@@ -81,16 +83,25 @@ parent:
 
   async PBackgroundIDBFactory(LoggingInfo loggingInfo);
 
   async PBackgroundIndexedDBUtils();
 
   // Use only for testing!
   async FlushPendingFileDeletions();
 
+  async PBackgroundStorage(nsString profilePath);
+
+  async BroadcastLocalStorageChange(nsString documentURI,
+                                    nsString key,
+                                    nsString oldValue,
+                                    nsString newValue,
+                                    PrincipalInfo principalInfo,
+                                    bool isPrivate);
+
   async PVsync();
 
   async PCameras();
 
   async PUDPSocket(OptionalPrincipalInfo pInfo, nsCString filter);
   async PBroadcastChannel(PrincipalInfo pInfo, nsCString origin, nsString channel);
 
   async PServiceWorkerManager();
@@ -124,16 +135,23 @@ parent:
 child:
   async PCache();
   async PCacheStreamControl();
 
   async PParentToChildStream();
 
   async PPendingIPCBlob(IPCBlob blob);
 
+  async DispatchLocalStorageChange(nsString documentURI,
+                                   nsString key,
+                                   nsString oldValue,
+                                   nsString newValue,
+                                   PrincipalInfo principalInfo,
+                                   bool isPrivate);
+
 both:
   // PIPCBlobInputStream is created on the parent side only if the child starts
   // a migration.
   async PIPCBlobInputStream(nsID aID, uint64_t aSize);
 
   async PFileDescriptorSet(FileDescriptor fd);
 };
 
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -184,16 +184,17 @@ if CONFIG['_MSC_VER']:
     # 'reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size
     SOURCES['BackgroundChildImpl.cpp'].flags += ['-wd4312']
     SOURCES['BackgroundParentImpl.cpp'].flags += ['-wd4312']
 
 LOCAL_INCLUDES += [
     '/caps',
     '/dom/broadcastchannel',
     '/dom/indexedDB',
+    '/dom/storage',
     '/dom/workers',
     '/media/webrtc/trunk',
     '/xpcom/build',
 ]
 
 IPDL_SOURCES = [
     'InputStreamParams.ipdlh',
     'IPCStream.ipdlh',
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -925,17 +925,17 @@ description =
 [PGMPVideoEncoder::NeedShmem]
 description =
 [PVideoDecoderManager::PVideoDecoder]
 description =
 [PVideoDecoderManager::Readback]
 description =
 [PBrowserStream::NPN_RequestRead]
 description =
-[PStorage::Preload]
+[PBackgroundStorage::Preload]
 description =
 [PRemoteSpellcheckEngine::Check]
 description =
 [PRemoteSpellcheckEngine::CheckAndSuggest]
 description =
 [PRemoteSpellcheckEngine::SetDictionary]
 description =
 [PGPU::AddLayerTreeIdMapping]
--- a/layout/generic/nsPageFrame.cpp
+++ b/layout/generic/nsPageFrame.cpp
@@ -192,45 +192,46 @@ nsPageFrame::ProcessSpecialCodes(const n
 
   // NOTE: Must search for &PT before searching for &P
   //
   // Search to see if the "page number and page" total code are in the string
   // and replace the page number and page total code with the actual
   // values
   NS_NAMED_LITERAL_STRING(kPageAndTotal, "&PT");
   if (aStr.Find(kPageAndTotal) != kNotFound) {
-    char16_t * uStr = nsTextFormatter::smprintf(mPD->mPageNumAndTotalsFormat.get(), mPageNum, mTotNumPages);
-    aNewStr.ReplaceSubstring(kPageAndTotal, nsDependentString(uStr));
-    free(uStr);
+    nsAutoString uStr;
+    nsTextFormatter::ssprintf(uStr, mPD->mPageNumAndTotalsFormat.get(),
+                              mPageNum, mTotNumPages);
+    aNewStr.ReplaceSubstring(kPageAndTotal, uStr);
   }
 
   // Search to see if the page number code is in the string
   // and replace the page number code with the actual value
   NS_NAMED_LITERAL_STRING(kPage, "&P");
   if (aStr.Find(kPage) != kNotFound) {
-    char16_t * uStr = nsTextFormatter::smprintf(mPD->mPageNumFormat.get(), mPageNum);
-    aNewStr.ReplaceSubstring(kPage, nsDependentString(uStr));
-    free(uStr);
+    nsAutoString uStr;
+    nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), mPageNum);
+    aNewStr.ReplaceSubstring(kPage, uStr);
   }
 
   NS_NAMED_LITERAL_STRING(kTitle, "&T");
   if (aStr.Find(kTitle) != kNotFound) {
     aNewStr.ReplaceSubstring(kTitle, mPD->mDocTitle);
   }
 
   NS_NAMED_LITERAL_STRING(kDocURL, "&U");
   if (aStr.Find(kDocURL) != kNotFound) {
     aNewStr.ReplaceSubstring(kDocURL, mPD->mDocURL);
   }
 
   NS_NAMED_LITERAL_STRING(kPageTotal, "&L");
   if (aStr.Find(kPageTotal) != kNotFound) {
-    char16_t * uStr = nsTextFormatter::smprintf(mPD->mPageNumFormat.get(), mTotNumPages);
-    aNewStr.ReplaceSubstring(kPageTotal, nsDependentString(uStr));
-    free(uStr);
+    nsAutoString uStr;
+    nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), mTotNumPages);
+    aNewStr.ReplaceSubstring(kPageTotal, uStr);
   }
 }
 
 
 //------------------------------------------------------------------------------
 nscoord nsPageFrame::GetXPosition(gfxContext&          aRenderingContext,
                                   nsFontMetrics&       aFontMetrics,
                                   const nsRect&        aRect,
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -1887,32 +1887,36 @@ nsDisplayList::GetBounds(nsDisplayListBu
   for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
     bounds.UnionRect(bounds, i->GetClippedBounds(aBuilder));
   }
   return bounds;
 }
 
 nsRect
 nsDisplayList::GetClippedBoundsWithRespectToASR(nsDisplayListBuilder* aBuilder,
-                                                const ActiveScrolledRoot* aASR) const {
+                                                const ActiveScrolledRoot* aASR,
+                                                nsRect* aVisibleRect) const {
   nsRect bounds;
   for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
     nsRect r = i->GetClippedBounds(aBuilder);
     if (aASR != i->GetActiveScrolledRoot() && !r.IsEmpty()) {
       const DisplayItemClip* clip = DisplayItemClipChain::ClipForASR(i->GetClipChain(), aASR);
 #ifdef DEBUG
       if (!gfxPrefs::LayoutUseContainersForRootFrames()) {
         MOZ_ASSERT(clip,
                    "Need to be clipped wrt aASR. Do not call this function with an ASR that our child items don't have finite bounds wrt.");
       }
 #endif
       if (clip) {
         r = clip->GetClipRect();
       }
     }
+    if (aVisibleRect) {
+      aVisibleRect->UnionRect(*aVisibleRect, i->GetVisibleRect());
+    }
     bounds.UnionRect(bounds, r);
   }
   return bounds;
 }
 
 nsRect
 nsDisplayList::GetVisibleRect() const {
   nsRect result;
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -2512,19 +2512,24 @@ public:
    * Get this list's bounds, respecting clips relative to aASR. The result is
    * the union of each item's clipped bounds with respect to aASR. That means
    * that if an item can move asynchronously with an ASR that is a descendant
    * of aASR, then the clipped bounds with respect to aASR will be the clip of
    * that item for aASR, because the item can move anywhere inside that clip.
    * If there is an item in this list which is not bounded with respect to
    * aASR (i.e. which does not have "finite bounds" with respect to aASR),
    * then this method trigger an assertion failure.
+   * The optional aVisibleRect out argument can be set to non-null if the
+   * caller is also interested to know the visible rect.  This can be used
+   * to get the visible rect efficiently without traversing the display list
+   * twice.
    */
   nsRect GetClippedBoundsWithRespectToASR(nsDisplayListBuilder* aBuilder,
-                                          const ActiveScrolledRoot* aASR) const;
+                                          const ActiveScrolledRoot* aASR,
+                                          nsRect* aVisibleRect = nullptr) const;
 
   /**
    * Find the topmost display item that returns a non-null frame, and return
    * the frame.
    */
   void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                nsDisplayItem::HitTestState* aState,
                nsTArray<nsIFrame*> *aOutFrames) const;
@@ -3817,25 +3822,27 @@ public:
     mBaseVisibleRect = mVisibleRect;
   }
   virtual ~nsDisplayWrapList();
   /**
    * Call this if the wrapped list is changed.
    */
   virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) override
   {
-    mBounds = mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot);
+    nsRect visibleRect;
+    mBounds = mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot,
+                                                     &visibleRect);
     // The display list may contain content that's visible outside the visible
     // rect (i.e. the current dirty rect) passed in when the item was created.
     // This happens when the dirty rect has been restricted to the visual
     // overflow rect of a frame for some reason (e.g. when setting up dirty
     // rects in nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay), but that
     // frame contains placeholders for out-of-flows that aren't descendants of
     // the frame.
-    mVisibleRect.UnionRect(mBaseVisibleRect, mList.GetVisibleRect());
+    mVisibleRect.UnionRect(mBaseVisibleRect, visibleRect);
   }
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                    bool* aSnap) override;
   virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -644,19 +644,17 @@ Preferences::IsServiceAvailable()
 {
   return !!sPreferences;
 }
 
 // static
 bool
 Preferences::InitStaticMembers()
 {
-#ifndef MOZ_B2G
   MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
-#endif
 
   if (!sShutdown && !sPreferences) {
     MOZ_ASSERT(NS_IsMainThread());
     nsCOMPtr<nsIPrefService> prefService =
       do_GetService(NS_PREFSERVICE_CONTRACTID);
   }
 
   return sPreferences != nullptr;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1421,17 +1421,17 @@ pref("javascript.options.strict.debug", 
 #endif
 pref("javascript.options.baselinejit",      true);
 pref("javascript.options.ion",              true);
 pref("javascript.options.asmjs",            true);
 pref("javascript.options.wasm",             true);
 pref("javascript.options.wasm_baselinejit", false);
 pref("javascript.options.native_regexp",    true);
 pref("javascript.options.parallel_parsing", true);
-#if !defined(RELEASE_OR_BETA) && !defined(ANDROID) && !defined(MOZ_B2G) && !defined(XP_IOS)
+#if !defined(RELEASE_OR_BETA) && !defined(ANDROID) && !defined(XP_IOS)
 pref("javascript.options.asyncstack",       true);
 #else
 pref("javascript.options.asyncstack",       false);
 #endif
 pref("javascript.options.throw_on_asmjs_validation_failure", false);
 pref("javascript.options.ion.offthread_compilation", true);
 #ifdef DEBUG
 pref("javascript.options.jit.full_debug_checks", true);
@@ -2490,24 +2490,16 @@ pref("font.name.cursive.x-math", "");
 pref("font.name-list.serif.x-math", "Latin Modern Math, STIX Two Math, XITS Math, Cambria Math, Libertinus Math, DejaVu Math TeX Gyre, TeX Gyre Bonum Math, TeX Gyre Pagella Math, TeX Gyre Schola, TeX Gyre Termes Math, STIX Math, Asana Math, STIXGeneral, DejaVu Serif, DejaVu Sans, serif");
 pref("font.name-list.sans-serif.x-math", "sans-serif");
 pref("font.name-list.monospace.x-math", "monospace");
 
 // Some CJK fonts have bad underline offset, their CJK character glyphs are overlapped (or adjoined)  to its underline.
 // These fonts are ignored the underline offset, instead of it, the underline is lowered to bottom of its em descent.
 pref("font.blacklist.underline_offset", "FangSong,Gulim,GulimChe,MingLiU,MingLiU-ExtB,MingLiU_HKSCS,MingLiU-HKSCS-ExtB,MS Gothic,MS Mincho,MS PGothic,MS PMincho,MS UI Gothic,PMingLiU,PMingLiU-ExtB,SimHei,SimSun,SimSun-ExtB,Hei,Kai,Apple LiGothic,Apple LiSung,Osaka");
 
-#ifdef MOZ_B2G
-// Whitelist of fonts that ship with B2G that do not include space lookups in
-// default features. This allows us to skip analyzing the GSUB/GPOS tables
-// unless features are explicitly enabled.
-// Use NSPR_LOG_MODULES=fontinit:5 to dump out details of space lookups
-pref("font.whitelist.skip_default_features_space_check", "Fira Sans,Fira Mono");
-#endif
-
 pref("images.dither", "auto");
 pref("security.directory",              "");
 
 pref("signed.applets.codebase_principal_support", false);
 pref("security.checkloaduri", true);
 pref("security.xpconnect.plugin.unrestricted", true);
 // security-sensitive dialogs should delay button enabling. In milliseconds.
 pref("security.dialog_enable_delay", 1000);
@@ -4410,17 +4402,17 @@ pref("intl.ime.use_simple_context_on_pas
 pref("gfx.font_rendering.fontconfig.max_generic_substitutions", 3);
 #endif
 
 # XP_UNIX
 #endif
 #endif
 #endif
 
-#if defined(ANDROID) || defined(MOZ_B2G)
+#if defined(ANDROID)
 
 pref("font.size.fixed.ar", 12);
 
 pref("font.default.el", "sans-serif");
 pref("font.size.fixed.el", 12);
 
 pref("font.size.fixed.he", 12);
 
@@ -4428,75 +4420,20 @@ pref("font.default.x-cyrillic", "sans-se
 pref("font.size.fixed.x-cyrillic", 12);
 
 pref("font.default.x-unicode", "sans-serif");
 pref("font.size.fixed.x-unicode", 12);
 
 pref("font.default.x-western", "sans-serif");
 pref("font.size.fixed.x-western", 12);
 
-# ANDROID || MOZ_B2G
+# ANDROID
 #endif
 
-#if defined(MOZ_B2G)
-// Gonk, FxOS Simulator, B2G Desktop and Mulet.
-
-// TODO: some entries could probably be cleaned up.
-
-// ar
-
-pref("font.name-list.serif.el", "Droid Serif"); // not Charis SIL Compact, only has a few Greek chars
-pref("font.name-list.sans-serif.el", "Fira Sans");
-pref("font.name-list.monospace.el", "Fira Mono");
-
-pref("font.name-list.serif.he", "Charis SIL Compact");
-pref("font.name-list.sans-serif.he", "Fira Sans, Droid Sans Hebrew");
-pref("font.name-list.monospace.he", "Fira Mono");
-
-pref("font.name-list.serif.ja", "Charis SIL Compact");
-pref("font.name-list.sans-serif.ja", "Fira Sans, MotoyaLMaru, MotoyaLCedar, Droid Sans Japanese");
-pref("font.name-list.monospace.ja", "MotoyaLMaru, MotoyaLCedar, Fira Mono");
-
-pref("font.name-list.serif.ko", "Charis SIL Compact");
-pref("font.name-list.sans-serif.ko", "Fira Sans");
-pref("font.name-list.monospace.ko", "Fira Mono");
-
-pref("font.name-list.serif.th", "Charis SIL Compact");
-pref("font.name-list.sans-serif.th", "Fira Sans, Noto Sans Thai, Droid Sans Thai");
-pref("font.name-list.monospace.th", "Fira Mono");
-
-pref("font.name-list.serif.x-cyrillic", "Charis SIL Compact");
-pref("font.name-list.sans-serif.x-cyrillic", "Fira Sans");
-pref("font.name-list.monospace.x-cyrillic", "Fira Mono");
-
-pref("font.name-list.serif.x-unicode", "Charis SIL Compact");
-pref("font.name-list.sans-serif.x-unicode", "Fira Sans");
-pref("font.name-list.monospace.x-unicode", "Fira Mono");
-
-pref("font.name-list.serif.x-western", "Charis SIL Compact");
-pref("font.name-list.sans-serif.x-western", "Fira Sans");
-pref("font.name-list.monospace.x-western", "Fira Mono");
-
-pref("font.name-list.serif.zh-CN", "Charis SIL Compact");
-pref("font.name-list.sans-serif.zh-CN", "Fira Sans, Droid Sans Fallback");
-pref("font.name-list.monospace.zh-CN", "Fira Mono");
-
-pref("font.name-list.serif.zh-HK", "Charis SIL Compact");
-pref("font.name-list.sans-serif.zh-HK", "Fira Sans, Droid Sans Fallback");
-pref("font.name-list.monospace.zh-HK", "Fira Mono");
-
-pref("font.name-list.serif.zh-TW", "Charis SIL Compact");
-pref("font.name-list.sans-serif.zh-TW", "Fira Sans, Droid Sans Fallback");
-pref("font.name-list.monospace.zh-TW", "Fira Mono");
-
-pref("font.name-list.serif.x-math", "Latin Modern Math, STIX Two Math, XITS Math, Cambria Math, Libertinus Math, DejaVu Math TeX Gyre, TeX Gyre Bonum Math, TeX Gyre Pagella Math, TeX Gyre Schola, TeX Gyre Termes Math, STIX Math, Asana Math, STIXGeneral, DejaVu Serif, DejaVu Sans, Charis SIL Compact");
-pref("font.name-list.sans-serif.x-math", "Fira Sans");
-pref("font.name-list.monospace.x-math", "Fira Mono");
-
-#elif defined(ANDROID)
+#if defined(ANDROID)
 // We use the bundled fonts for Firefox for Android
 
 pref("font.name-list.serif.ar", "Noto Naskh Arabic, Noto Serif, Droid Serif");
 pref("font.name-list.sans-serif.ar", "Noto Naskh Arabic, Clear Sans, Roboto, Droid Sans");
 pref("font.name-list.monospace.ar", "Noto Naskh Arabic");
 
 pref("font.name-list.serif.el", "Droid Serif, Noto Serif"); // not Charis SIL Compact, only has a few Greek chars
 pref("font.name-list.sans-serif.el", "Clear Sans, Roboto, Droid Sans");
--- a/modules/libpref/prefapi.cpp
+++ b/modules/libpref/prefapi.cpp
@@ -521,19 +521,17 @@ nsresult PREF_GetBoolPref(const char *pr
         }
     }
     return rv;
 }
 
 nsresult
 PREF_DeleteBranch(const char *branch_name)
 {
-#ifndef MOZ_B2G
     MOZ_ASSERT(NS_IsMainThread());
-#endif
 
     int len = (int)strlen(branch_name);
 
     if (!gHashTable)
         return NS_ERROR_NOT_INITIALIZED;
 
     /* The following check insures that if the branch name already has a "."
      * at the end, we don't end up with a "..". This fixes an incompatibility
@@ -584,19 +582,17 @@ PREF_ClearUserPref(const char *pref_name
         MakeDirtyCallback();
     }
     return NS_OK;
 }
 
 nsresult
 PREF_ClearAllUserPrefs()
 {
-#ifndef MOZ_B2G
     MOZ_ASSERT(NS_IsMainThread());
-#endif
 
     if (!gHashTable)
         return NS_ERROR_NOT_INITIALIZED;
 
     std::vector<std::string> prefStrings;
     for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
         auto pref = static_cast<PrefHashEntry*>(iter.Get());
 
@@ -730,19 +726,17 @@ inInitArray(const char* key)
     const char** list = mozilla::dom::ContentPrefs::GetContentPrefs(&prefsLen);
     return BinarySearchIf(list, 0, prefsLen,
                           StringComparator(key), &found);
 }
 #endif
 
 PrefHashEntry* pref_HashTableLookup(const char *key)
 {
-#ifndef MOZ_B2G
     MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
-#endif
     MOZ_ASSERT((!XRE_IsContentProcess() || gPhase != START),
                "pref access before commandline prefs set");
     /* If you're hitting this assertion, you've added a pref access to start up.
      * Consider moving it later or add it to the whitelist in ContentPrefs.cpp
      * and get review from a DOM peer
      */
 #ifdef DEBUG
     if (XRE_IsContentProcess() && gPhase <= END_INIT_PREFS &&
@@ -751,19 +745,17 @@ PrefHashEntry* pref_HashTableLookup(cons
                               key);
     }
 #endif
     return static_cast<PrefHashEntry*>(gHashTable->Search(key));
 }
 
 nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags)
 {
-#ifndef MOZ_B2G
     MOZ_ASSERT(NS_IsMainThread());
-#endif
 
     if (!gHashTable)
         return NS_ERROR_OUT_OF_MEMORY;
 
     auto pref = static_cast<PrefHashEntry*>(gHashTable->Add(key, fallible));
     if (!pref)
         return NS_ERROR_OUT_OF_MEMORY;
 
--- a/mozglue/misc/TimeStamp.h
+++ b/mozglue/misc/TimeStamp.h
@@ -224,20 +224,17 @@ public:
   }
   BaseTimeDuration operator/(const int64_t aDivisor) const
   {
     MOZ_ASSERT(aDivisor != 0, "Division by zero");
     return FromTicks(ValueCalculator::Divide(mValue, aDivisor));
   }
   double operator/(const BaseTimeDuration& aOther) const
   {
-#ifndef MOZ_B2G
-    // Bug 1066388 - This fails on B2G ICS Emulator
     MOZ_ASSERT(aOther.mValue != 0, "Division by zero");
-#endif
     return ValueCalculator::DivideDouble(mValue, aOther.mValue);
   }
   BaseTimeDuration operator%(const BaseTimeDuration& aOther) const
   {
     MOZ_ASSERT(aOther.mValue != 0, "Division by zero");
     return FromTicks(ValueCalculator::Modulo(mValue, aOther.mValue));
   }
 
--- a/netwerk/base/Tickler.h
+++ b/netwerk/base/Tickler.h
@@ -22,17 +22,17 @@
 // complete quickly when on wifi - ARP, DNS, TCP handshake, SSL
 // handshake, HTTP headers, and the TCP slow start phase. The
 // transaction is given up to 400 miliseconds by default to get
 // through those phases before the tickler is disabled.
 //
 // The tickler only applies to wifi on mobile right now. Hopefully it
 // can also be restricted to particular handset models in the future.
 
-#if defined(ANDROID) && !defined(MOZ_B2G)
+#if defined(ANDROID)
 #define MOZ_USE_WIFI_TICKLER
 #endif
 
 #include "mozilla/Attributes.h"
 #include "nsISupports.h"
 #include <stdint.h>
 
 #ifdef MOZ_USE_WIFI_TICKLER
--- a/netwerk/dns/nsEffectiveTLDService.cpp
+++ b/netwerk/dns/nsEffectiveTLDService.cpp
@@ -4,111 +4,57 @@
  * 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/. */
 
 // This service reads a file of rules describing TLD-like domain names.  For a
 // complete description of the expected file format and parsing rules, see
 // http://wiki.mozilla.org/Gecko:Effective_TLD_Service
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/HashFunctions.h"
 #include "mozilla/MemoryReporting.h"
 
+#include "MainThreadUtils.h"
 #include "nsEffectiveTLDService.h"
 #include "nsIIDNService.h"
 #include "nsNetUtil.h"
 #include "prnetdb.h"
 #include "nsIURI.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
 
+namespace etld_dafsa {
+
+// Generated file that includes kDafsa
+#include "etld_data.inc"
+
+} // namespace etld_dafsa
+
 using namespace mozilla;
 
 NS_IMPL_ISUPPORTS(nsEffectiveTLDService, nsIEffectiveTLDService,
                   nsIMemoryReporter)
 
 // ----------------------------------------------------------------------
 
-#define ETLD_STR_NUM_1(line) str##line
-#define ETLD_STR_NUM(line) ETLD_STR_NUM_1(line)
-#define ETLD_ENTRY_OFFSET(name) offsetof(struct etld_string_list, ETLD_STR_NUM(__LINE__))
-
-const ETLDEntry ETLDEntry::entries[] = {
-#define ETLD_ENTRY(name, ex, wild) { ETLD_ENTRY_OFFSET(name), ex, wild },
-#include "etld_data.inc"
-#undef ETLD_ENTRY
-};
-
-const union ETLDEntry::etld_strings ETLDEntry::strings = {
-  {
-#define ETLD_ENTRY(name, ex, wild) name,
-#include "etld_data.inc"
-#undef ETLD_ENTRY
-  }
-};
-
-/* static */ const ETLDEntry*
-ETLDEntry::GetEntry(const char* aDomain)
-{
-  size_t i;
-  if (BinarySearchIf(entries, 0, ArrayLength(ETLDEntry::entries),
-                     Cmp(aDomain), &i)) {
-    return &entries[i];
-  }
-  return nullptr;
-}
-
-// Dummy function to statically ensure that our indices don't overflow
-// the storage provided for them.
-void
-ETLDEntry::FuncForStaticAsserts(void)
-{
-#define ETLD_ENTRY(name, ex, wild)                                      \
-  static_assert(ETLD_ENTRY_OFFSET(name) < (1 << ETLD_ENTRY_N_INDEX_BITS), \
-                "invalid strtab index");
-#include "etld_data.inc"
-#undef ETLD_ENTRY
-}
-
-#undef ETLD_ENTRY_OFFSET
-#undef ETLD_STR_NUM
-#undef ETLD_STR_NUM1
-
-// ----------------------------------------------------------------------
-
 static nsEffectiveTLDService *gService = nullptr;
 
 nsEffectiveTLDService::nsEffectiveTLDService()
+  : mIDNService()
+  , mGraph(etld_dafsa::kDafsa)
 {
 }
 
 nsresult
 nsEffectiveTLDService::Init()
 {
   nsresult rv;
   mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) return rv;
 
-#ifdef DEBUG
-  // Sanity-check the eTLD entries.
-  for (uint32_t i = 0; i < ArrayLength(ETLDEntry::entries); i++) {
-    const char* domain = ETLDEntry::entries[i].GetEffectiveTLDName();
-    nsDependentCString name(domain);
-    nsAutoCString normalizedName(domain);
-    MOZ_ASSERT(NS_SUCCEEDED(NormalizeHostname(normalizedName)),
-               "normalization failure!");
-    MOZ_ASSERT(name.Equals(normalizedName), "domain not normalized!");
-
-    // Domains must be in sorted order for binary search to work.
-    if (i > 0) {
-      const char* domain0 = ETLDEntry::entries[i - 1].GetEffectiveTLDName();
-      MOZ_ASSERT(strcmp(domain0, domain) < 0, "domains not in sorted order!");
-    }
-  }
-#endif
-
   MOZ_ASSERT(!gService);
   gService = this;
   RegisterWeakMemoryReporter(this);
 
   return NS_OK;
 }
 
 nsEffectiveTLDService::~nsEffectiveTLDService()
@@ -239,16 +185,19 @@ nsEffectiveTLDService::GetNextSubDomain(
 // if more subdomain parts are requested than are available, or if the hostname
 // includes characters that are not valid in a URL. Normalization is performed
 // on the host string and the result will be in UTF8.
 nsresult
 nsEffectiveTLDService::GetBaseDomainInternal(nsCString  &aHostname,
                                              int32_t    aAdditionalParts,
                                              nsACString &aBaseDomain)
 {
+  const int kExceptionRule = 1;
+  const int kWildcardRule = 2;
+
   if (aHostname.IsEmpty())
     return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
 
   // chomp any trailing dot, and keep track of it for later
   bool trailingDot = aHostname.Last() == '.';
   if (trailingDot)
     aHostname.Truncate(aHostname.Length() - 1);
 
@@ -258,16 +207,30 @@ nsEffectiveTLDService::GetBaseDomainInte
     return NS_ERROR_INVALID_ARG;
 
   // Check if we're dealing with an IPv4/IPv6 hostname, and return
   PRNetAddr addr;
   PRStatus result = PR_StringToNetAddr(aHostname.get(), &addr);
   if (result == PR_SUCCESS)
     return NS_ERROR_HOST_IS_IP_ADDRESS;
 
+  // Lookup in the cache if this is a normal query.
+  TLDCacheEntry* entry = nullptr;
+  if (aAdditionalParts == 1) {
+    if (LookupForAdd(aHostname, &entry)) {
+      // There was a match, just return the cached value.
+      aBaseDomain = entry->mBaseDomain;
+      if (trailingDot) {
+        aBaseDomain.Append('.');
+      }
+
+      return NS_OK;
+    }
+  }
+
   // Walk up the domain tree, most specific to least specific,
   // looking for matches at each level.  Note that a given level may
   // have multiple attributes (e.g. IsWild() and IsNormal()).
   const char *prevDomain = nullptr;
   const char *currDomain = aHostname.get();
   const char *nextDot = strchr(currDomain, '.');
   const char *end = currDomain + aHostname.Length();
   // Default value of *eTLD is currDomain as set in the while loop below
@@ -275,29 +238,29 @@ nsEffectiveTLDService::GetBaseDomainInte
   while (true) {
     // sanity check the string we're about to look up: it should not begin with
     // a '.'; this would mean the hostname began with a '.' or had an
     // embedded '..' sequence.
     if (*currDomain == '.')
       return NS_ERROR_INVALID_ARG;
 
     // Perform the lookup.
-    const ETLDEntry* entry = ETLDEntry::GetEntry(currDomain);
-    if (entry) {
-      if (entry->IsWild() && prevDomain) {
+    const int result = mGraph.Lookup(Substring(currDomain, end));
+    if (result != Dafsa::kKeyNotFound) {
+      if (result == kWildcardRule && prevDomain) {
         // wildcard rules imply an eTLD one level inferior to the match.
         eTLD = prevDomain;
         break;
       }
-      if (entry->IsNormal() || !nextDot) {
+      if ((result == kWildcardRule || result != kExceptionRule) || !nextDot) {
         // specific match, or we've hit the top domain level
         eTLD = currDomain;
         break;
       }
-      if (entry->IsException()) {
+      if (result == kExceptionRule) {
         // exception rules imply an eTLD one level superior to the match.
         eTLD = nextDot + 1;
         break;
       }
     }
     if (!nextDot) {
       // we've hit the top domain level; use it by default.
       eTLD = currDomain;
@@ -338,16 +301,23 @@ nsEffectiveTLDService::GetBaseDomainInte
       }
     }
   }
 
   if (aAdditionalParts != 0)
     return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
 
   aBaseDomain = Substring(iter, end);
+
+  // Update the MRU table if in use.
+  if (entry) {
+    entry->mHost = aHostname;
+    entry->mBaseDomain = aBaseDomain;
+  }
+
   // add on the trailing dot, if applicable
   if (trailingDot)
     aBaseDomain.Append('.');
 
   return NS_OK;
 }
 
 // Normalizes the given hostname, component by component.  ASCII/ACE
@@ -360,8 +330,18 @@ nsEffectiveTLDService::NormalizeHostname
     nsresult rv = mIDNService->ConvertUTF8toACE(aHostname, aHostname);
     if (NS_FAILED(rv))
       return rv;
   }
 
   ToLowerCase(aHostname);
   return NS_OK;
 }
+
+bool
+nsEffectiveTLDService::LookupForAdd(const nsACString& aHost, TLDCacheEntry** aEntry)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  const uint32_t hash = HashString(aHost.BeginReading(), aHost.Length());
+  *aEntry = &mMruTable[hash % kTableSize];
+  return (*aEntry)->mHost == aHost;
+}
--- a/netwerk/dns/nsEffectiveTLDService.h
+++ b/netwerk/dns/nsEffectiveTLDService.h
@@ -7,77 +7,21 @@
 #define EffectiveTLDService_h
 
 #include "nsIEffectiveTLDService.h"
 
 #include "nsIMemoryReporter.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/BinarySearch.h"
+#include "mozilla/Dafsa.h"
 #include "mozilla/MemoryReporting.h"
 
 class nsIIDNService;
 
-// struct for static data generated from effective_tld_names.dat
-struct ETLDEntry {
-  friend class nsEffectiveTLDService;
-
-public:
-  bool IsNormal()    const { return wild || !exception; }
-  bool IsException() const { return exception; }
-  bool IsWild()      const { return wild; }
-
-  const char* GetEffectiveTLDName() const
-  {
-    return strings.strtab + strtab_index;
-  }
-
-  static const ETLDEntry* GetEntry(const char* aDomain);
-
-  static const size_t ETLD_ENTRY_N_INDEX_BITS = 30;
-
-  // These fields must be public to allow static construction.
-  uint32_t strtab_index : ETLD_ENTRY_N_INDEX_BITS;
-  uint32_t exception : 1;
-  uint32_t wild : 1;
-
-private:
-  struct Cmp {
-    int operator()(const ETLDEntry aEntry) const
-    {
-      return strcmp(mName, aEntry.GetEffectiveTLDName());
-    }
-    explicit Cmp(const char* aName) : mName(aName) {}
-    const char* mName;
-  };
-
-#define ETLD_STR_NUM_1(line) str##line
-#define ETLD_STR_NUM(line) ETLD_STR_NUM_1(line)
-  struct etld_string_list {
-#define ETLD_ENTRY(name, ex, wild) char ETLD_STR_NUM(__LINE__)[sizeof(name)];
-#include "etld_data.inc"
-#undef ETLD_ENTRY
-  };
-
-  // This static string table is all the eTLD domain names packed together.
-  static const union etld_strings {
-    struct etld_string_list list;
-    char strtab[1];
-  } strings;
-
-  // This is the static entries table. Each entry has an index into the string
-  // table. The entries are in sorted order so that binary search can be used.
-  static const ETLDEntry entries[];
-
-  void FuncForStaticAsserts(void);
-#undef ETLD_STR_NUM
-#undef ETLD_STR_NUM1
-};
-
 class nsEffectiveTLDService final
   : public nsIEffectiveTLDService
   , public nsIMemoryReporter
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIEFFECTIVETLDSERVICE
   NS_DECL_NSIMEMORYREPORTER
@@ -88,11 +32,45 @@ public:
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 private:
   nsresult GetBaseDomainInternal(nsCString &aHostname, int32_t aAdditionalParts, nsACString &aBaseDomain);
   nsresult NormalizeHostname(nsCString &aHostname);
   ~nsEffectiveTLDService();
 
   nsCOMPtr<nsIIDNService>     mIDNService;
+
+  // The DAFSA provides a compact encoding of the rather large eTLD list.
+  mozilla::Dafsa mGraph;
+
+  struct TLDCacheEntry
+  {
+    nsCString mHost;
+    nsCString mBaseDomain;
+  };
+
+  // We use a small most recently used cache to compensate for DAFSA lookups
+  // being slightly slower than a binary search on a larger table of strings.
+  //
+  // We first check the cache for a matching result and avoid a DAFSA lookup
+  // if a match is found. Otherwise we lookup the domain in the DAFSA and then
+  // cache the result. During standard browsing the same domains are repeatedly
+  // fed into |GetBaseDomainInternal| so this ends up being an effective
+  // mitigation getting about a 99% hit rate with four tabs open.
+  //
+  // A size of 31 is used rather than a more logical power-of-two such as 32
+  // since it is a prime number and provides fewer collisions when when used
+  // with our hash algorithms.
+  static const uint32_t kTableSize = 31;
+  TLDCacheEntry mMruTable[kTableSize];
+
+  /**
+   * Performs a lookup on the MRU table and provides a pointer to the hash
+   * entry that matched or should be used for adding this host.
+   *
+   * @param aHost The host to lookup.
+   * @param aEntry Out param, the entry in the MRU table to use.
+   * @return True if a match was found, false if there was a miss.
+   */
+  inline bool LookupForAdd(const nsACString& aHost, TLDCacheEntry** aEntry);
 };
 
 #endif // EffectiveTLDService_h
--- a/netwerk/dns/prepare_tlds.py
+++ b/netwerk/dns/prepare_tlds.py
@@ -1,14 +1,16 @@
 # 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/.
 
 import codecs
 import encodings.idna
+import imp
+import os
 import re
 import sys
 
 """
 Processes a file containing effective TLD data.  See the following URL for a
 description of effective TLDs and of the file format that this script
 processes (although for the latter you're better off just reading this file's
 short source code).
@@ -29,22 +31,17 @@ def getEffectiveTLDs(path):
     if line.startswith("//") or "." not in line:
       continue
     line = re.split(r"[ \t\n]", line, 1)[0]
     entry = EffectiveTLDEntry(line)
     domain = entry.domain()
     assert domain not in domains, \
            "repeating domain %s makes no sense" % domain
     domains.add(domain)
-    entries.append(entry)
-
-  # Sort the entries so we can use binary search on them.
-  entries.sort(key=EffectiveTLDEntry.domain)
-
-  return entries
+    yield entry
 
 def _normalizeHostname(domain):
   """
   Normalizes the given domain, component by component.  ASCII components are
   lowercased, while non-ASCII components are processed using the ToASCII
   algorithm.
   """
   def convertLabel(label):
@@ -98,24 +95,42 @@ class EffectiveTLDEntry:
 
 #################
 # DO EVERYTHING #
 #################
 
 def main(output, effective_tld_filename):
   """
   effective_tld_filename is the effective TLD file to parse.
-  A C++ array of { domain, exception, wild } entries representing the
+  A C++ array of a binary representation of a DAFSA representing the
   eTLD file is then printed to output.
   """
 
-  def boolStr(b):
-    if b:
-      return "true"
-    return "false"
+  # Find and load the `make_dafsa.py` script under xpcom/ds.
+  tld_dir = os.path.dirname(effective_tld_filename)
+  make_dafsa_py = os.path.join(tld_dir, '../../xpcom/ds/make_dafsa.py')
+  sys.path.append(os.path.dirname(make_dafsa_py))
+  with open(make_dafsa_py, 'r') as fh:
+    make_dafsa = imp.load_module('script', fh, make_dafsa_py,
+                                 ('.py', 'r', imp.PY_SOURCE))
 
-  for etld in getEffectiveTLDs(effective_tld_filename):
-    exception = boolStr(etld.exception())
-    wild = boolStr(etld.wild())
-    output.write('ETLD_ENTRY("%s", %s, %s)\n' % (etld.domain(), exception, wild))
+  def typeEnum(etld):
+    """
+    Maps the flags to the DAFSA's enum types.
+    """
+    if etld.exception():
+      return 1
+    elif etld.wild():
+      return 2
+    else:
+      return 0
+
+  def dafsa_words():
+    """
+    make_dafsa expects lines of the form "<domain_name><enum_value>"
+    """
+    for etld in getEffectiveTLDs(effective_tld_filename):
+      yield "%s%d" % (etld.domain(), typeEnum(etld))
+
+  output.write(make_dafsa.words_to_cxx(dafsa_words()))
 
 if __name__ == '__main__':
     main(sys.stdout, sys.argv[1])
--- a/old-configure.in
+++ b/old-configure.in
@@ -2193,20 +2193,16 @@ browser)
   AC_DEFINE(MOZ_PHOENIX)
   ;;
 
 xulrunner)
   AC_DEFINE(MOZ_XULRUNNER)
   ;;
 esac
 
-if test -n "$MOZ_B2G"; then
-    AC_DEFINE(MOZ_B2G)
-fi
-
 # Graphene is a desktop runtime for running applications with a HTML UI.
 if test -n "$MOZ_GRAPHENE"; then
     AC_DEFINE(MOZ_GRAPHENE)
 fi
 
 if test -n "$MOZ_MULET"; then
     AC_DEFINE(MOZ_MULET)
 fi
@@ -2214,19 +2210,17 @@ fi
 # Propagate feature switches for code written in rust from confvars.sh
 if test -n "$MOZ_RUST_URLPARSE"; then
     AC_DEFINE(MOZ_RUST_URLPARSE)
     AC_SUBST(MOZ_RUST_URLPARSE)
 fi
 
 AC_SUBST(MOZ_PHOENIX)
 AC_SUBST(MOZ_XULRUNNER)
-AC_SUBST(MOZ_B2G)
 AC_SUBST(MOZ_MULET)
-AC_SUBST(MOZ_B2G_VERSION)
 
 dnl ========================================================
 dnl Ensure Android SDK and build-tools versions depending on
 dnl mobile target.
 dnl ========================================================
 
 case "$MOZ_BUILD_APP" in
 mobile/android)
@@ -4020,20 +4014,16 @@ dnl ====================================
 dnl = Offer a way to disable the startup cache
 dnl ========================================================
 
 MOZ_ARG_DISABLE_BOOL(startupcache,
 [  --disable-startupcache          Disable startup cache ],
     MOZ_DISABLE_STARTUPCACHE=1,
     MOZ_DISABLE_STARTUPCACHE=)
 
-dnl bug 988880: disable startup cache on b2g
-if test -n "$MOZ_B2G"; then
-  MOZ_DISABLE_STARTUPCACHE=1
-fi
 if test -n "$MOZ_DISABLE_STARTUPCACHE"; then
   AC_DEFINE(MOZ_DISABLE_STARTUPCACHE)
 fi
 AC_SUBST(MOZ_DISABLE_STARTUPCACHE)
 
 dnl ========================================================
 dnl = Enable Pico Speech Synthesis (Gonk usually)
 dnl ========================================================
@@ -4737,20 +4727,16 @@ if test -z "$IS_ALPHA"; then
   else
     MOZ_APP_MAXVERSION=`echo $MOZ_APP_VERSION | sed "s|\(^[0-9]*\).*|\1|"`.*
   fi
   changequote([,])
 else
   MOZ_APP_MAXVERSION=$MOZ_APP_VERSION
 fi
 
-MOZ_B2G_VERSION=${MOZ_B2G_VERSION:-"1.0.0"}
-AC_DEFINE_UNQUOTED(MOZ_B2G_VERSION,"$MOZ_B2G_VERSION")
-AC_DEFINE_UNQUOTED(MOZ_B2G_OS_NAME,"$MOZ_B2G_OS_NAME")
-
 AC_SUBST(MOZ_APP_NAME)
 AC_SUBST(MOZ_APP_REMOTINGNAME)
 AC_SUBST(MOZ_APP_DISPLAYNAME)
 AC_SUBST(MOZ_APP_BASENAME)
 AC_SUBST(MOZ_APP_VENDOR)
 AC_SUBST(MOZ_APP_PROFILE)
 AC_SUBST(MOZ_APP_ID)
 AC_SUBST(MOZ_APP_ANDROID_VERSION_CODE)
--- a/parser/htmlparser/nsExpatDriver.cpp
+++ b/parser/htmlparser/nsExpatDriver.cpp
@@ -822,26 +822,18 @@ CreateErrorText(const char16_t* aDescrip
 
   nsAutoString msg;
   nsresult rv =
     nsParserMsgUtils::GetLocalizedStringByName(XMLPARSER_PROPERTIES,
                                                "XMLParsingError", msg);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:
-  char16_t *message = nsTextFormatter::smprintf(msg.get(), aDescription,
-                                                 aSourceURL, aLineNumber,
-                                                 aColNumber);
-  if (!message) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  aErrorString.Assign(message);
-  free(message);
-
+  nsTextFormatter::ssprintf(aErrorString, msg.get(), aDescription,
+                            aSourceURL, aLineNumber, aColNumber);
   return NS_OK;
 }
 
 static nsresult
 AppendErrorPointer(const int32_t aColNumber,
                    const char16_t *aSourceLine,
                    nsString& aSourceString)
 {
@@ -914,24 +906,19 @@ nsExpatDriver::HandleError()
     const char16_t *nameStart = uriEnd ? uriEnd + 1 : mismatch;
     tagName.Append(nameStart, (nameEnd ? nameEnd : pos) - nameStart);
 
     nsAutoString msg;
     nsParserMsgUtils::GetLocalizedStringByName(XMLPARSER_PROPERTIES,
                                                "Expected", msg);
 
     // . Expected: </%S>.
-    char16_t *message = nsTextFormatter::smprintf(msg.get(), tagName.get());
-    if (!message) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
+    nsAutoString message;
+    nsTextFormatter::ssprintf(message, msg.get(), tagName.get());
     description.Append(message);
-
-    free(message);
   }
 
   // Adjust the column number so that it is one based rather than zero based.
   uint32_t colNumber = XML_GetCurrentColumnNumber(mExpatParser) + 1;
   uint32_t lineNumber = XML_GetCurrentLineNumber(mExpatParser);
 
   nsAutoString errorText;
   CreateErrorText(description.get(), XML_GetBase(mExpatParser), lineNumber,
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -74,23 +74,30 @@ SandboxBrokerPolicyFactory::SandboxBroke
   policy->AddDir(rdwr, "/dev/dri");
 
 #ifdef MOZ_ALSA
   // Bug 1309098: ALSA support
   policy->AddDir(rdwr, "/dev/snd");
 #endif
 
 #ifdef MOZ_WIDGET_GTK
-  // Bug 1321134: DConf's single bit of shared memory
   if (const auto userDir = g_get_user_runtime_dir()) {
+    // Bug 1321134: DConf's single bit of shared memory
     // The leaf filename is "user" by default, but is configurable.
     nsPrintfCString shmPath("%s/dconf/", userDir);
     policy->AddPrefix(rdwrcr, shmPath.get());
+#ifdef MOZ_PULSEAUDIO
+    // PulseAudio, if it can't get server info from X11, will break
+    // unless it can open this directory (or create it, but in our use
+    // case we know it already exists).  See bug 1335329.
+    nsPrintfCString pulsePath("%s/pulse", userDir);
+    policy->AddPath(rdonly, pulsePath.get());
+#endif // MOZ_PULSEAUDIO
   }
-#endif
+#endif // MOZ_WIDGET_GTK
 
   // Read permissions
   policy->AddPath(rdonly, "/dev/urandom");
   policy->AddPath(rdonly, "/proc/cpuinfo");
   policy->AddPath(rdonly, "/proc/meminfo");
   policy->AddDir(rdonly, "/lib");
   policy->AddDir(rdonly, "/etc");
   policy->AddDir(rdonly, "/usr/share");
@@ -106,16 +113,23 @@ SandboxBrokerPolicyFactory::SandboxBroke
   policy->AddDir(rdonly, "/nix/store");
 
   // Bug 1384178: Mesa driver loader
   policy->AddPrefix(rdonly, "/sys/dev/char/226:");
 
   // Bug 1385715: NVIDIA PRIME support
   policy->AddPath(rdonly, "/proc/modules");
 
+#ifdef MOZ_PULSEAUDIO
+  // See bug 1384986 comment #1.
+  if (const auto xauth = PR_GetEnv("XAUTHORITY")) {
+    policy->AddPath(rdonly, xauth);
+  }
+#endif
+
   // Configuration dirs in the homedir that we want to allow read
   // access to.
   mozilla::Array<const char*, 3> confDirs = {
     ".config",
     ".themes",
     ".fonts",
   };
 
--- a/security/sandbox/linux/broker/moz.build
+++ b/security/sandbox/linux/broker/moz.build
@@ -14,16 +14,18 @@ SOURCES += [
     'SandboxBroker.cpp',
     'SandboxBrokerCommon.cpp',
     'SandboxBrokerPolicyFactory.cpp',
     'SandboxBrokerRealpath.cpp',
 ]
 
 if CONFIG['MOZ_ALSA']:
     DEFINES['MOZ_ALSA'] = True
+if CONFIG['MOZ_PULSEAUDIO']:
+    DEFINES['MOZ_PULSEAUDIO'] = True
 
 LOCAL_INCLUDES += [
     '/security/sandbox/linux', # SandboxLogging.h, SandboxInfo.h
 ]
 
 # Need this for mozilla::ipc::FileDescriptor etc.
 include('/ipc/chromium/chromium-config.mozbuild')
 
deleted file mode 100644
--- a/services/fxaccounts/FxAccountsManager.jsm
+++ /dev/null
@@ -1,644 +0,0 @@
-/* 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/. */
-
-/**
- * Temporary abstraction layer for common Fx Accounts operations.
- * For now, we will be using this module only from B2G but in the end we might
- * want this to be merged with FxAccounts.jsm and let other products also use
- * it.
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["FxAccountsManager"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
-                                   "@mozilla.org/permissionmanager;1",
-                                   "nsIPermissionManager");
-
-this.FxAccountsManager = {
-
-  init() {
-    Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION);
-    Services.obs.addObserver(this, ON_FXA_UPDATE_NOTIFICATION);
-  },
-
-  observe(aSubject, aTopic, aData) {
-    // Both topics indicate our cache is invalid
-    this._activeSession = null;
-
-    if (aData == ONVERIFIED_NOTIFICATION) {
-      log.debug("FxAccountsManager: cache cleared, broadcasting: " + aData);
-      Services.obs.notifyObservers(null, aData);
-    }
-  },
-
-  // We don't really need to save fxAccounts instance but this way we allow
-  // to mock FxAccounts from tests.
-  _fxAccounts: fxAccounts,
-
-  // We keep the session details here so consumers don't need to deal with
-  // session tokens and are only required to handle the email.
-  _activeSession: null,
-
-  // Are we refreshing our authentication? If so, allow attempts to sign in
-  // while we are already signed in.
-  _refreshing: false,
-
-  // We only expose the email and the verified status so far.
-  get _user() {
-    if (!this._activeSession || !this._activeSession.email) {
-      return null;
-    }
-
-    return {
-      email: this._activeSession.email,
-      verified: this._activeSession.verified,
-      profile: this._activeSession.profile,
-    }
-  },
-
-  _error(aError, aDetails) {
-    log.error(aError);
-    let reason = {
-      error: aError
-    };
-    if (aDetails) {
-      reason.details = aDetails;
-    }
-    return Promise.reject(reason);
-  },
-
-  _getError(aServerResponse) {
-    if (!aServerResponse || !aServerResponse.error || !aServerResponse.error.errno) {
-      return null;
-    }
-    let error = SERVER_ERRNO_TO_ERROR[aServerResponse.error.errno];
-    return error;
-  },
-
-  _serverError(aServerResponse) {
-    let error = this._getError({ error: aServerResponse });
-    return this._error(error ? error : ERROR_SERVER_ERROR, aServerResponse);
-  },
-
-  // As with _fxAccounts, we don't really need this method, but this way we
-  // allow tests to mock FxAccountsClient.  By default, we want to return the
-  // client used by the fxAccounts object because deep down they should have
-  // access to the same hawk request object which will enable them to share
-  // local clock skeq data.
-  _getFxAccountsClient() {
-    return this._fxAccounts.getAccountsClient();
-  },
-
-  _signInSignUp(aMethod, aEmail, aPassword, aFetchKeys) {
-    if (Services.io.offline) {
-      return this._error(ERROR_OFFLINE);
-    }
-
-    if (!aEmail) {
-      return this._error(ERROR_INVALID_EMAIL);
-    }
-
-    if (!aPassword) {
-      return this._error(ERROR_INVALID_PASSWORD);
-    }
-
-    // Check that there is no signed in account first.
-    if ((!this._refreshing) && this._activeSession) {
-      return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
-        user: this._user
-      });
-    }
-
-    let client = this._getFxAccountsClient();
-    return this._fxAccounts.getSignedInUser().then(
-      user => {
-        if ((!this._refreshing) && user) {
-          return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
-            user: this._user
-          });
-        }
-        return client[aMethod](aEmail, aPassword, aFetchKeys);
-      }
-    ).then(
-      user => {
-        let error = this._getError(user);
-        if (!user || !user.uid || !user.sessionToken || error) {
-          return this._error(error ? error : ERROR_INTERNAL_INVALID_USER, {
-            user
-          });
-        }
-
-        // If the user object includes an email field, it may differ in
-        // capitalization from what we sent down.  This is the server's
-        // canonical capitalization and should be used instead.
-        user.email = user.email || aEmail;
-
-        // If we're using server-side sign to refreshAuthentication
-        // we don't need to update local state; also because of two
-        // interacting glitches we need to bypass an event emission.
-        // See https://bugzilla.mozilla.org/show_bug.cgi?id=1031580
-        if (this._refreshing) {
-          return Promise.resolve({user: this._user});
-        }
-
-        return this._fxAccounts.setSignedInUser(user).then(
-          () => {
-            this._activeSession = user;
-            log.debug("User signed in: " + JSON.stringify(this._user) +
-                      " - Account created " + (aMethod == "signUp"));
-
-            // There is no way to obtain the key fetch token afterwards
-            // without login out the user and asking her to log in again.
-            // Also, key fetch tokens are designed to be short-lived, so
-            // we need to fetch kB as soon as we have the key fetch token.
-            if (aFetchKeys) {
-              this._fxAccounts.getKeys();
-            }
-
-            return this._fxAccounts.getSignedInUserProfile().catch(error => {
-              // Not fetching the profile is sad but the FxA logs will already
-              // have noise.
-              return null;
-            });
-          }
-        ).then(profile => {
-          if (profile) {
-            this._activeSession.profile = profile;
-          }
-
-          return Promise.resolve({
-            accountCreated: aMethod === "signUp",
-            user: this._user
-          });
-        });
-      },
-      reason => { return this._serverError(reason); }
-    );
-  },
-
-  /**
-   * Determine whether the incoming error means that the current account
-   * has new server-side state via deletion or password change, and if so,
-   * spawn the appropriate UI (sign in or refresh); otherwise re-reject.
-   *
-   * As of May 2014, the only HTTP call triggered by this._getAssertion()
-   * is to /certificate/sign via:
-   *   FxAccounts.getAssertion()
-   *     FxAccountsInternal.getCertificateSigned()
-   *       FxAccountsClient.signCertificate()
-   * See the latter method for possible (error code, errno) pairs.
-   */
-  _handleGetAssertionError(reason, aAudience, aPrincipal) {
-    log.debug("FxAccountsManager._handleGetAssertionError()");
-    let errno = (reason ? reason.errno : NaN) || NaN;
-    // If the previously valid email/password pair is no longer valid ...
-    if (errno == ERRNO_INVALID_AUTH_TOKEN) {
-      return this._fxAccounts.accountStatus().then(
-        (exists) => {
-          // ... if the email still maps to an account, the password
-          // must have changed, so ask the user to enter the new one ...
-          if (exists) {
-            return this.getAccount().then(
-              (user) => {
-                return this._refreshAuthentication(aAudience, user.email,
-                                                   aPrincipal,
-                                                   true /* logoutOnFailure */);
-              }
-            );
-          }
-          // ... otherwise, the account was deleted, so ask for Sign In/Up
-          return this._localSignOut().then(
-            () => {
-              return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience,
-                                     aPrincipal);
-            },
-            (reason) => {
-              // reject primary problem, not signout failure
-              log.error("Signing out in response to server error threw: " +
-                        reason);
-              return this._error(reason);
-            }
-          );
-        }
-      );
-    }
-    return Promise.reject(reason.message ? { error: reason.message } : reason);
-  },
-
-  _getAssertion(aAudience, aPrincipal) {
-    return this._fxAccounts.getAssertion(aAudience).then(
-      (result) => {
-        if (aPrincipal) {
-          this._addPermission(aPrincipal);
-        }
-        return result;
-      },
-      (reason) => {
-        return this._handleGetAssertionError(reason, aAudience, aPrincipal);
-      }
-    );
-  },
-
-  /**
-   * "Refresh authentication" means:
-   *   Interactively demonstrate knowledge of the FxA password
-   *   for the currently logged-in account.
-   * There are two very different scenarios:
-   *   1) The password has changed on the server. Failure should log
-   *      the current account OUT.
-   *   2) The person typing can't prove knowledge of the password used
-   *      to log in. Failure should do nothing.
-   */
-  _refreshAuthentication(aAudience, aEmail, aPrincipal,
-                         logoutOnFailure = false) {
-    this._refreshing = true;
-    return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
-                           aAudience, aPrincipal, aEmail).then(
-      (assertion) => {
-        this._refreshing = false;
-        return assertion;
-      },
-      (reason) => {
-        this._refreshing = false;
-        if (logoutOnFailure) {
-          return this._signOut().then(
-            () => {
-              return this._error(reason);
-            }
-          );
-        }
-        return this._error(reason);
-      }
-    );
-  },
-
-  _localSignOut() {
-    return this._fxAccounts.signOut(true);
-  },
-
-  _signOut() {
-    if (!this._activeSession) {
-      return Promise.resolve();
-    }
-
-    // We clear the local session cache as soon as we get the onlogout
-    // notification triggered within FxAccounts.signOut, so we save the
-    // session token value to be able to remove the remote server session
-    // in case that we have network connection.
-    let sessionToken = this._activeSession.sessionToken;
-
-    return this._localSignOut().then(
-      () => {
-        // At this point the local session should already be removed.
-
-        // The client can create new sessions up to the limit (100?).
-        // Orphaned tokens on the server will eventually be garbage collected.
-        if (Services.io.offline) {
-          return Promise.resolve();
-        }
-        // Otherwise, we try to remove the remote session.
-        let client = this._getFxAccountsClient();
-        return client.signOut(sessionToken).then(
-          result => {
-            let error = this._getError(result);
-            if (error) {
-              return this._error(error, result);
-            }
-            log.debug("Signed out");
-            return Promise.resolve();
-          },
-          reason => {
-            return this._serverError(reason);
-          }
-        );
-      }
-    );
-  },
-
-  _uiRequest(aRequest, aAudience, aPrincipal, aParams) {
-    if (Services.io.offline) {
-      return this._error(ERROR_OFFLINE);
-    }
-    let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
-               .createInstance(Ci.nsIFxAccountsUIGlue);
-    if (!ui[aRequest]) {
-      return this._error(ERROR_UI_REQUEST);
-    }
-
-    if (!aParams || !Array.isArray(aParams)) {
-      aParams = [aParams];
-    }
-
-    return ui[aRequest].apply(this, aParams).then(
-      result => {
-        // Even if we get a successful result from the UI, the account will
-        // most likely be unverified, so we cannot get an assertion.
-        if (result && result.verified) {
-          return this._getAssertion(aAudience, aPrincipal);
-        }
-
-        return this._error(ERROR_UNVERIFIED_ACCOUNT, {
-          user: result
-        });
-      },
-      error => {
-        return this._error(ERROR_UI_ERROR, error);
-      }
-    );
-  },
-
-  _addPermission(aPrincipal) {
-    // This will fail from tests cause we are running them in the child
-    // process until we have chrome tests in b2g. Bug 797164.
-    try {
-      permissionManager.addFromPrincipal(aPrincipal, FXACCOUNTS_PERMISSION,
-                                         Ci.nsIPermissionManager.ALLOW_ACTION);
-    } catch (e) {
-      log.warn("Could not add permission " + e);
-    }
-  },
-
-  // -- API --
-
-  signIn(aEmail, aPassword, aFetchKeys) {
-    return this._signInSignUp("signIn", aEmail, aPassword, aFetchKeys);
-  },
-
-  signUp(aEmail, aPassword, aFetchKeys) {
-    return this._signInSignUp("signUp", aEmail, aPassword, aFetchKeys);
-  },
-
-  signOut() {
-    if (!this._activeSession) {
-      // If there is no cached active session, we try to get it from the
-      // account storage.
-      return this.getAccount().then(
-        result => {
-          if (!result) {
-            return Promise.resolve();
-          }
-          return this._signOut();
-        }
-      );
-    }
-    return this._signOut();
-  },
-
-  resendVerificationEmail() {
-    return this._fxAccounts.resendVerificationEmail().then(
-      (result) => {
-        return result;
-      },
-      (error) => {
-        return this._error(ERROR_SERVER_ERROR, error);
-      }
-    );
-  },
-
-  getAccount() {
-    // We check first if we have session details cached.
-    if (this._activeSession) {
-      // If our cache says that the account is not yet verified,
-      // we kick off verification before returning what we have.
-      if (!this._activeSession.verified) {
-        this.verificationStatus(this._activeSession);
-      }
-      log.debug("Account " + JSON.stringify(this._user));
-      return Promise.resolve(this._user);
-    }
-
-    // If no cached information, we try to get it from the persistent storage.
-    return this._fxAccounts.getSignedInUser().then(
-      user => {
-        if (!user || !user.email) {
-          log.debug("No signed in account");
-          return Promise.resolve(null);
-        }
-
-        this._activeSession = user;
-        // If we get a stored information of a not yet verified account,
-        // we kick off verification before returning what we have.
-        if (!user.verified) {
-          this.verificationStatus(user);
-          // Trying to get the profile for unverified users will fail, so we
-          // don't even try in that case.
-          log.debug("Account ", this._user);
-          return Promise.resolve(this._user);
-        }
-
-        return this._fxAccounts.getSignedInUserProfile().then(profile => {
-          if (profile) {
-            this._activeSession.profile = profile;
-          }
-          log.debug("Account ", this._user);
-          return Promise.resolve(this._user);
-        }).catch(error => {
-          // FxAccounts logs already inform about the error.
-          log.debug("Account ", this._user);
-          return Promise.resolve(this._user);
-        });
-      }
-    );
-  },
-
-  queryAccount(aEmail) {
-    log.debug("queryAccount " + aEmail);
-    if (Services.io.offline) {
-      return this._error(ERROR_OFFLINE);
-    }
-
-    if (!aEmail) {
-      return this._error(ERROR_INVALID_EMAIL);
-    }
-
-    let client = this._getFxAccountsClient();
-    return client.accountExists(aEmail).then(
-      result => {
-        log.debug("Account " + (result ? "" : "does not ") + "exists");
-        let error = this._getError(result);
-        if (error) {
-          return this._error(error, result);
-        }
-
-        return Promise.resolve({
-          registered: result
-        });
-      },
-      reason => { this._serverError(reason); }
-    );
-  },
-
-  verificationStatus() {
-    log.debug("verificationStatus");
-    if (!this._activeSession || !this._activeSession.sessionToken) {
-      this._error(ERROR_NO_TOKEN_SESSION);
-    }
-
-    // There is no way to unverify an already verified account, so we just
-    // return the account details of a verified account
-    if (this._activeSession.verified) {
-      log.debug("Account already verified");
-      return;
-    }
-
-    if (Services.io.offline) {
-      log.warn("Offline; skipping verification.");
-      return;
-    }
-
-    let client = this._getFxAccountsClient();
-    client.recoveryEmailStatus(this._activeSession.sessionToken).then(
-      data => {
-        let error = this._getError(data);
-        if (error) {
-          this._error(error, data);
-        }
-        // If the verification status has changed, update state.
-        if (this._activeSession.verified != data.verified) {
-          this._activeSession.verified = data.verified;
-          this._fxAccounts.setSignedInUser(this._activeSession);
-          this._fxAccounts.getSignedInUserProfile().then(profile => {
-            if (profile) {
-              this._activeSession.profile = profile;
-            }
-          }).catch(error => {
-            // FxAccounts logs already inform about the error.
-          });
-        }
-        log.debug(JSON.stringify(this._user));
-      },
-      reason => { this._serverError(reason); }
-    );
-  },
-
-  /*
-   * Try to get an assertion for the given audience. Here we implement
-   * the heart of the response to navigator.mozId.request() on device.
-   * (We can also be called via the IAC API, but it's request() that
-   * makes this method complex.) The state machine looks like this,
-   * ignoring simple errors:
-   *   If no one is signed in, and we aren't suppressing the UI:
-   *     trigger the sign in flow.
-   *   else if we were asked to refresh and the grace period is up:
-   *     trigger the refresh flow.
-   *   else:
-   *      request user permission to share an assertion if we don't have it
-   *      already and ask the core code for an assertion, which might itself
-   *      trigger either the sign in or refresh flows (if our account
-   *      changed on the server).
-   *
-   * aOptions can include:
-   *   refreshAuthentication  - (bool) Force re-auth.
-   *   silent                 - (bool) Prevent any UI interaction.
-   *                            I.e., try to get an automatic assertion.
-   */
-  getAssertion(aAudience, aPrincipal, aOptions) {
-    if (!aAudience) {
-      return this._error(ERROR_INVALID_AUDIENCE);
-    }
-
-    let principal = aPrincipal;
-    log.debug("FxAccountsManager.getAssertion() aPrincipal: ",
-              principal.origin, principal.appId,
-              principal.isInIsolatedMozBrowserElement);
-
-    return this.getAccount().then(
-      user => {
-        if (user) {
-          // Three have-user cases to consider. First: are we unverified?
-          if (!user.verified) {
-            return this._error(ERROR_UNVERIFIED_ACCOUNT, {
-              user
-            });
-          }
-          // Second case: do we need to refresh?
-          if (aOptions &&
-              (typeof(aOptions.refreshAuthentication) != "undefined")) {
-            let gracePeriod = aOptions.refreshAuthentication;
-            if (typeof(gracePeriod) !== "number" || isNaN(gracePeriod)) {
-              return this._error(ERROR_INVALID_REFRESH_AUTH_VALUE);
-            }
-            // Forcing refreshAuth to silent is a contradiction in terms,
-            // though it might succeed silently if we didn't reject here.
-            if (aOptions.silent) {
-              return this._error(ERROR_NO_SILENT_REFRESH_AUTH);
-            }
-            let secondsSinceAuth = (Date.now() / 1000) -
-                                   this._activeSession.authAt;
-            if (secondsSinceAuth > gracePeriod) {
-              return this._refreshAuthentication(aAudience, user.email,
-                                                 principal,
-                                                 false /* logoutOnFailure */);
-            }
-          }
-          // Third case: we are all set *locally*. Probably we just return
-          // the assertion, but the attempt might lead to the server saying
-          // we are deleted or have a new password, which will trigger a flow.
-          // Also we need to check if we have permission to get the assertion,
-          // otherwise we need to show the forceAuth UI to let the user know
-          // that the RP with no fxa permissions is trying to obtain an
-          // assertion. Once the user authenticates herself in the forceAuth UI
-          // the permission will be remembered by default.
-          let permission = permissionManager.testPermissionFromPrincipal(
-            principal,
-            FXACCOUNTS_PERMISSION
-          );
-          if (permission == Ci.nsIPermissionManager.PROMPT_ACTION &&
-              !this._refreshing) {
-            return this._refreshAuthentication(aAudience, user.email,
-                                               principal,
-                                               false /* logoutOnFailure */);
-          } else if (permission == Ci.nsIPermissionManager.DENY_ACTION &&
-                     !this._refreshing) {
-            return this._error(ERROR_PERMISSION_DENIED);
-          } else if (this._refreshing) {
-            // If we are blocked asking for a password we should not continue
-            // the getAssertion process.
-            return Promise.resolve(null);
-          }
-          return this._getAssertion(aAudience, principal);
-        }
-        log.debug("No signed in user");
-        if (aOptions && aOptions.silent) {
-          return Promise.resolve(null);
-        }
-        return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience, principal);
-      }
-    );
-  },
-
-  getKeys() {
-    if (!Services.prefs.getBoolPref("services.sync.enabled", false)) {
-      return Promise.reject(ERROR_SYNC_DISABLED);
-    }
-
-    return this.getAccount().then(
-      user => {
-        if (!user) {
-          log.debug("No signed in user");
-          return Promise.resolve(null);
-        }
-
-        if (!user.verified) {
-          return this._error(ERROR_UNVERIFIED_ACCOUNT, {
-            user
-          });
-        }
-
-        return this._fxAccounts.getKeys();
-      }
-    );
-  }
-};
-
-FxAccountsManager.init();
--- a/services/fxaccounts/FxAccountsStorage.jsm
+++ b/services/fxaccounts/FxAccountsStorage.jsm
@@ -26,19 +26,18 @@ function FxAccountsStorageManagerCanStor
 
 // The storage manager object.
 this.FxAccountsStorageManager = function(options = {}) {
   this.options = {
     filename: options.filename || DEFAULT_STORAGE_FILENAME,
     baseDir: options.baseDir || OS.Constants.Path.profileDir,
   }
   this.plainStorage = new JSONStorage(this.options);
-  // On b2g we have no loginManager for secure storage, and tests may want
-  // to pretend secure storage isn't available.
-  let useSecure = "useSecure" in options ? options.useSecure : haveLoginManager;
+  // Tests may want to pretend secure storage isn't available.
+  let useSecure = "useSecure" in options ? options.useSecure : true;
   if (useSecure) {
     this.secureStorage = new LoginManagerStorage();
   } else {
     this.secureStorage = null;
   }
   this._clearCachedData();
   // See .initialize() below - this protects against it not being called.
   this._promiseInitialized = Promise.reject("initialize not called");
@@ -596,14 +595,8 @@ LoginManagerStorage.prototype = {
       }
       // just log and consume the error here - it may be a 3rd party login
       // manager replacement that's simply broken.
       log.error("Failed to get data from the login manager", ex);
     }
     return null;
   },
 }
-
-// A global variable to indicate if the login manager is available - it doesn't
-// exist on b2g. Defined here as the use of preprocessor directives skews line
-// numbers in the runtime, meaning stack-traces etc end up off by a few lines.
-// Doing it at the end of the file makes that less of a pita.
-var haveLoginManager = !AppConstants.MOZ_B2G;
--- a/services/fxaccounts/moz.build
+++ b/services/fxaccounts/moz.build
@@ -27,12 +27,8 @@ EXTRA_JS_MODULES += [
   'FxAccountsCommon.js',
   'FxAccountsConfig.jsm',
   'FxAccountsOAuthGrantClient.jsm',
   'FxAccountsProfile.jsm',
   'FxAccountsProfileClient.jsm',
   'FxAccountsStorage.jsm',
   'FxAccountsWebChannel.jsm',
 ]
-
-# For now, we will only be using the FxA manager in B2G.
-if CONFIG['MOZ_B2G']:
-  EXTRA_JS_MODULES += ['FxAccountsManager.jsm']
--- a/startupcache/moz.build
+++ b/startupcache/moz.build
@@ -2,18 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "XPCOM")
 
-if not CONFIG['MOZ_B2G']:
-    TEST_DIRS += ['test']
+TEST_DIRS += ['test']
 
 XPIDL_SOURCES += [
     'nsIStartupCache.idl',
 ]
 
 XPIDL_MODULE = 'startupcache'
 
 EXPORTS.mozilla.scache += [
--- a/taskcluster/ci/source-test/doc.yml
+++ b/taskcluster/ci/source-test/doc.yml
@@ -35,20 +35,19 @@ doc-upload:
         symbol: tc(DocUp)
         kind: test
         tier: 3
     run-on-projects: [mozilla-central]
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
     worker:
         docker-image: {in-tree: "lint"}
         max-run-time: 1800
+        taskcluster-proxy: true
     run:
         using: run-task
-        command: >
-            cd /home/worker/checkouts/gecko &&
-            ./mach doc-upload --bucket gecko-docs.mozilla.org --region us-west-2
+        command: cd /home/worker/checkouts/gecko && ./mach doc-upload
     scopes:
-        - secrets:get:project/releng/gecko/build/level-3/gecko-docs-upload
+        - secrets:get:project/releng/gecko/build/level-{level}/gecko-docs-upload
     when:
         files-changed:
             - '**/*.py'
             - '**/*.rst'
             - 'tools/docs/**'
--- a/taskcluster/taskgraph/transforms/job/run_task.py
+++ b/taskcluster/taskgraph/transforms/job/run_task.py
@@ -29,16 +29,18 @@ run_task_schema = Schema({
 })
 
 
 def common_setup(config, job, taskdesc):
     run = job['run']
     if run['checkout']:
         support_vcs_checkout(config, job, taskdesc)
 
+    taskdesc['worker'].setdefault('env', {})['MOZ_SCM_LEVEL'] = config.params['level']
+
 
 @run_job_using("docker-worker", "run-task", schema=run_task_schema)
 def docker_worker_run_task(config, job, taskdesc):
     run = job['run']
     worker = taskdesc['worker'] = job['worker']
     common_setup(config, job, taskdesc)
 
     if run.get('cache-dotcache') and int(config.params['level']) > 1:
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -52,17 +52,19 @@ task_description_schema = Schema({
     Optional('expires-after'): basestring,
     Optional('deadline-after'): basestring,
 
     # custom routes for this task; the default treeherder routes will be added
     # automatically
     Optional('routes'): [basestring],
 
     # custom scopes for this task; any scopes required for the worker will be
-    # added automatically
+    # added automatically. The following parameters will be substituted in each
+    # scope:
+    #  {level} -- the scm level of this push
     Optional('scopes'): [basestring],
 
     # Tags
     Optional('tags'): {basestring: object},
 
     # custom "task.extra" content
     Optional('extra'): {basestring: object},
 
@@ -945,21 +947,22 @@ def add_index_routes(config, tasks):
 
         del task['index']
         yield task
 
 
 @transforms.add
 def build_task(config, tasks):
     for task in tasks:
-        worker_type = task['worker-type'].format(level=str(config.params['level']))
+        level = str(config.params['level'])
+        worker_type = task['worker-type'].format(level=level)
         provisioner_id, worker_type = worker_type.split('/', 1)
 
         routes = task.get('routes', [])
-        scopes = task.get('scopes', [])
+        scopes = [s.format(level=level) for s in task.get('scopes', [])]
 
         # set up extra
         extra = task.get('extra', {})
         task_th = task.get('treeherder')
         if task_th:
             extra['treeherderEnv'] = task_th['environments']
 
             treeherder = extra.setdefault('treeherder', {})
--- a/taskcluster/taskgraph/try_option_syntax.py
+++ b/taskcluster/taskgraph/try_option_syntax.py
@@ -590,18 +590,18 @@ class TryOptionSyntax(object):
             else:
                 return False
 
         job_try_name = attr('job_try_name')
         if job_try_name:
             # Beware the subtle distinction between [] and None for self.jobs and self.platforms.
             # They will be [] if there was no try syntax, and None if try syntax was detected but
             # they remained unspecified.
-            if self.jobs and job_try_name not in self.jobs:
-                return False
+            if self.jobs:
+                return job_try_name in self.jobs
             elif not self.jobs and 'build' in task.dependencies:
                 # We exclude tasks with build dependencies from the default set of jobs because
                 # they will schedule their builds even if they end up optimized away. This means
                 # to run these tasks on try, they'll need to be explicitly specified by -j until
                 # we find a better solution (see bug 1372510).
                 return False
             elif not self.jobs and attr('build_platform'):
                 if self.platforms is None or attr('build_platform') in self.platforms:
--- a/toolkit/components/commandlines/nsCommandLine.cpp
+++ b/toolkit/components/commandlines/nsCommandLine.cpp
@@ -483,24 +483,23 @@ nsCommandLine::Init(int32_t argc, const 
   return NS_OK;
 }
 
 static void
 LogConsoleMessage(const char16_t* fmt, ...)
 {
   va_list args;
   va_start(args, fmt);
-  char16_t* msg = nsTextFormatter::vsmprintf(fmt, args);
+  nsString msg;
+  nsTextFormatter::vssprintf(msg, fmt, args);
   va_end(args);
 
   nsCOMPtr<nsIConsoleService> cs = do_GetService("@mozilla.org/consoleservice;1");
   if (cs)
-    cs->LogStringMessage(msg);
-
-  free(msg);
+    cs->LogStringMessage(msg.get());
 }
 
 nsresult
 nsCommandLine::EnumerateHandlers(EnumerateHandlersCallback aCallback, void *aClosure)
 {
   nsresult rv;
 
   nsCOMPtr<nsICategoryManager> catman
--- a/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
@@ -86,22 +86,17 @@ XPCOMUtils.defineLazyGetter(DownloadUIHe
 /**
  * Allows displaying prompts related to downloads.
  *
  * @param aParent
  *        The nsIDOMWindow to which prompts should be attached, or null to
  *        attach prompts to the most recently active window.
  */
 this.DownloadPrompter = function(aParent) {
-  if (AppConstants.MOZ_B2G) {
-    // On B2G there is no prompter implementation.
-    this._prompter = null;
-  } else {
-    this._prompter = Services.ww.getNewPrompter(aParent);
-  }
+  this._prompter = Services.ww.getNewPrompter(aParent);
 }
 
 this.DownloadPrompter.prototype = {
   /**
    * Constants with the different type of prompts.
    */
   ON_QUIT: "prompt-on-quit",
   ON_OFFLINE: "prompt-on-offline",
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -125,24 +125,16 @@ this.AppConstants = Object.freeze({
 
   MOZ_WIDGET_GTK:
 #ifdef MOZ_WIDGET_GTK
   true,
 #else
   false,
 #endif
 
-# MOZ_B2G covers both device and desktop b2g
-  MOZ_B2G:
-#ifdef MOZ_B2G
-  true,
-#else
-  false,
-#endif
-
   XP_UNIX:
 #ifdef XP_UNIX
   true,
 #else
   false,
 #endif
 
 # NOTE! XP_LINUX has to go after MOZ_WIDGET_ANDROID otherwise Android
@@ -204,23 +196,16 @@ this.AppConstants = Object.freeze({
 
   ASAN:
 #ifdef MOZ_ASAN
   true,
 #else
   false,
 #endif
 
-  MOZ_B2G_RIL:
-#ifdef MOZ_B2G_RIL
-  true,
-#else
-  false,
-#endif
-
   MOZ_GRAPHENE:
 #ifdef MOZ_GRAPHENE
   true,
 #else
   false,
 #endif
 
   MOZ_SYSTEM_NSS:
@@ -321,18 +306,16 @@ this.AppConstants = Object.freeze({
   MOZ_APP_VERSION: "@MOZ_APP_VERSION@",
   MOZ_APP_VERSION_DISPLAY: "@MOZ_APP_VERSION_DISPLAY@",
   MOZ_BUILD_APP: "@MOZ_BUILD_APP@",
   MOZ_MACBUNDLE_NAME: "@MOZ_MACBUNDLE_NAME@",
   MOZ_UPDATE_CHANNEL: "@MOZ_UPDATE_CHANNEL@",
   INSTALL_LOCALE: "@AB_CD@",
   MOZ_WIDGET_TOOLKIT: "@MOZ_WIDGET_TOOLKIT@",
   ANDROID_PACKAGE_NAME: "@ANDROID_PACKAGE_NAME@",
-  MOZ_B2G_VERSION: @MOZ_B2G_VERSION@,
-  MOZ_B2G_OS_NAME: @MOZ_B2G_OS_NAME@,
 
   DEBUG_JS_MODULES: "@DEBUG_JS_MODULES@",
 
   MOZ_BING_API_CLIENTID: "@MOZ_BING_API_CLIENTID@",
   MOZ_BING_API_KEY: "@MOZ_BING_API_KEY@",
   MOZ_GOOGLE_API_KEY: "@MOZ_GOOGLE_API_KEY@",
   MOZ_MOZILLA_API_KEY: "@MOZ_MOZILLA_API_KEY@",
 
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -918,20 +918,16 @@ Blocklist.prototype = {
         if (!(element instanceof Ci.nsIDOMElement))
           continue;
         switch (element.localName) {
         case "emItems":
           this._addonEntries = this._processItemNodes(element.childNodes, "emItem",
                                                       this._handleEmItemNode);
           break;
         case "pluginItems":
-          // We don't support plugins on b2g.
-          if (AppConstants.MOZ_B2G) {
-            return;
-          }
           this._pluginEntries = this._processItemNodes(element.childNodes, "pluginItem",
                                                        this._handlePluginItemNode);
           break;
         case "certItems":
           if (populateCertBlocklist) {
             this._processItemNodes(element.childNodes, "certItem",
                                    this._handleCertItemNode.bind(this));
           }
@@ -1157,18 +1153,17 @@ Blocklist.prototype = {
         blockEntry[matchElement.localName] = value;
       }
     }
     result.push(blockEntry);
   },
 
   /* See nsIBlocklistService */
   getPluginBlocklistState(plugin, appVersion, toolkitVersion) {
-    if (AppConstants.platform == "android" ||
-        AppConstants.MOZ_B2G) {
+    if (AppConstants.platform == "android") {
       return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
     }
     if (!this._isBlocklistLoaded())
       this._loadBlocklist();
     return this._getPluginBlocklistState(plugin, this._pluginEntries,
                                          appVersion, toolkitVersion);
   },
 
@@ -1320,20 +1315,16 @@ Blocklist.prototype = {
   },
 
   _notifyObserversBlocklistUpdated() {
     Services.obs.notifyObservers(this, "blocklist-updated");
     Services.ppmm.broadcastAsyncMessage("Blocklist:blocklistInvalidated", {});
   },
 
   _blocklistUpdated(oldAddonEntries, oldPluginEntries) {
-    if (AppConstants.MOZ_B2G) {
-      return;
-    }
-
     var addonList = [];
 
     // A helper function that reverts the prefs passed to default values.
     function resetPrefs(prefs) {
       for (let pref of prefs)
         gPref.clearUserPref(pref);
     }
     const types = ["extension", "theme", "locale", "dictionary", "service"];
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -146,20 +146,16 @@ nsXREDirProvider::Initialize(nsIFile *aX
     if (app) {
       bool per = false;
       app->GetFile(NS_APP_USER_PROFILE_50_DIR, &per, getter_AddRefs(mProfileDir));
       NS_ASSERTION(per, "NS_APP_USER_PROFILE_50_DIR must be persistent!");
       NS_ASSERTION(mProfileDir, "NS_APP_USER_PROFILE_50_DIR not defined! This shouldn't happen!");
     }
   }
 
-#ifdef MOZ_B2G
-  LoadAppBundleDirs();
-#endif
-
   return NS_OK;
 }
 
 nsresult
 nsXREDirProvider::SetProfile(nsIFile* aDir, nsIFile* aLocalDir)
 {
   NS_ASSERTION(aDir && aLocalDir, "We don't support no-profile apps yet!");
 
@@ -856,48 +852,16 @@ DeleteDirIfExists(nsIFile* dir)
     }
   }
   return NS_OK;
 }
 
 #endif // (defined(XP_WIN) || defined(XP_MACOSX)) &&
   // defined(MOZ_CONTENT_SANDBOX)
 
-#ifdef MOZ_B2G
-void
-nsXREDirProvider::LoadAppBundleDirs()
-{
-  nsCOMPtr<nsIFile> dir;
-  bool persistent = false;
-  nsresult rv = GetFile(XRE_APP_DISTRIBUTION_DIR, &persistent, getter_AddRefs(dir));
-  if (NS_FAILED(rv))
-    return;
-
-  dir->AppendNative(NS_LITERAL_CSTRING("bundles"));
-
-  nsCOMPtr<nsISimpleEnumerator> e;
-  rv = dir->GetDirectoryEntries(getter_AddRefs(e));
-  if (NS_FAILED(rv))
-    return;
-
-  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
-  if (!files)
-    return;
-
-  nsCOMPtr<nsIFile> subdir;
-  while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(subdir))) && subdir) {
-    mAppBundleDirectories.AppendObject(subdir);
-
-    nsCOMPtr<nsIFile> manifest =
-      CloneAndAppend(subdir, "chrome.manifest");
-    XRE_AddManifestLocation(NS_APP_LOCATION, manifest);
-  }
-}
-#endif
-
 static const char *const kAppendPrefDir[] = { "defaults", "preferences", nullptr };
 
 #ifdef DEBUG_bsmedberg
 static void
 DumpFileArray(const char *key,
               nsCOMArray<nsIFile> dirs)
 {
   fprintf(stderr, "nsXREDirProvider::GetFilesInternal(%s)\n", key);
--- a/toolkit/xre/nsXREDirProvider.h
+++ b/toolkit/xre/nsXREDirProvider.h
@@ -121,21 +121,16 @@ protected:
   // delimiters.
   static inline nsresult AppendProfileString(nsIFile* aFile, const char* aPath);
 
 #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
   // Load the temp directory for sandboxed content processes
   nsresult LoadContentProcessTempDir();
 #endif
 
-#ifdef MOZ_B2G
-  // Calculate and register app-bundled extension directories.
-  void LoadAppBundleDirs();
-#endif
-
   void Append(nsIFile* aDirectory);
 
   nsCOMPtr<nsIDirectoryServiceProvider> mAppProvider;
   // On OSX, mGREDir points to .app/Contents/Resources
   nsCOMPtr<nsIFile>      mGREDir;
   // On OSX, mGREBinDir points to .app/Contents/MacOS
   nsCOMPtr<nsIFile>      mGREBinDir;
   // On OSX, mXULAppDir points to .app/Contents/Resources/browser
--- a/tools/docs/mach_commands.py
+++ b/tools/docs/mach_commands.py
@@ -107,46 +107,51 @@ class Documentation(MachCommandBase):
         search_dirs = ('doc', 'docs')
         for d in search_dirs:
             p = os.path.join(path, d)
             if os.path.isfile(os.path.join(p, 'conf.py')):
                 return p
 
     @Command('doc-upload', category='devenv',
         description='Generate and upload documentation from the tree.')
-    @CommandArgument('--bucket', required=True,
-        help='Target S3 bucket.')
-    @CommandArgument('--region', required=True,
-        help='Region containing target S3 bucket.')
     @CommandArgument('what', nargs='*', metavar='DIRECTORY [, DIRECTORY]',
         help='Path(s) to documentation to build and upload.')
-    def upload_docs(self, bucket, region, what=None):
+    def upload_docs(self, what=None):
         self._activate_virtualenv()
         self.virtualenv_manager.install_pip_package('boto3==1.4.4')
 
         outdir = os.path.join(self.topobjdir, 'docs')
         self.build_docs(what=what, outdir=outdir, format='html')
 
-        self.s3_upload(os.path.join(outdir, 'html', 'Mozilla_Source_Tree_Docs'), bucket, region)
+        self.s3_upload(os.path.join(outdir, 'html', 'Mozilla_Source_Tree_Docs'))
 
-    def s3_upload(self, root, bucket, region):
+    def s3_upload(self, root):
         """Upload the contents of outdir recursively to S3"""
         import boto3
         import mimetypes
         import requests
 
-        # Get the credentials from the TC secrets service.  Note that these are
-        # only available to level-3 pushes.
+        region = 'us-west-2'
+        level = os.environ.get('MOZ_SCM_LEVEL', '1')
+        bucket = {
+            '1': 'gecko-docs.mozilla.org-l1',
+            '2': 'gecko-docs.mozilla.org-l2',
+            '3': 'gecko-docs.mozilla.org',
+        }[level]
+        secrets_url = 'http://taskcluster/secrets/v1/secret/'
+        secrets_url += 'project/releng/gecko/build/level-{}/gecko-docs-upload'.format(level)
+
+        # Get the credentials from the TC secrets service.  Note that these
+        # differ per SCM level
         if 'TASK_ID' in os.environ:
             print("Using AWS credentials from the secrets service")
             session = requests.Session()
-            secrets_url = 'http://taskcluster/secrets/repo:hg.mozilla.org/mozilla-central/gecko-docs-upload'
             res = session.get(secrets_url)
             res.raise_for_status()
-            secret = res.json()
+            secret = res.json()['secret']
             session = boto3.session.Session(
                 aws_access_key_id=secret['AWS_ACCESS_KEY_ID'],
                 aws_secret_access_key=secret['AWS_SECRET_ACCESS_KEY'],
                 region_name=region)
         else:
             print("Trying to use your AWS credentials..")
             session = boto3.session.Session(region_name=region)
         s3 = session.client('s3')
--- a/uriloader/base/nsURILoader.cpp
+++ b/uriloader/base/nsURILoader.cpp
@@ -400,26 +400,16 @@ nsresult nsDocumentOpenInfo::DispatchCon
   // future, because the user has specified external handling for the MIME
   // type.
   bool forceExternalHandling = false;
   uint32_t disposition;
   rv = aChannel->GetContentDisposition(&disposition);
 
   bool allowContentDispositionToForceExternalHandling = true;
 
-#ifdef MOZ_B2G
-
-  // On B2G, OMA content files should never be handled by an external handler
-  // (even if the server specifies Content-Disposition: attachment) because the
-  // data should never be stored on an unencrypted form.
-  allowContentDispositionToForceExternalHandling =
-    !mContentType.LowerCaseEqualsASCII("application/vnd.oma.drm.message");
-
-#endif
-
   if (NS_SUCCEEDED(rv) && (disposition == nsIChannel::DISPOSITION_ATTACHMENT) &&
       allowContentDispositionToForceExternalHandling) {
     forceExternalHandling = true;
   }
 
   LOG(("  forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
 
   // The type or data the contentListener wants.
--- a/widget/NativeKeyToDOMKeyName.h
+++ b/widget/NativeKeyToDOMKeyName.h
@@ -75,25 +75,19 @@
 #elif defined(MOZ_WIDGET_GTK)
 #undef KEY_MAP_GTK
 #define KEY_MAP_GTK(aCPPKeyName, aNativeKey) \
   NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
 #elif defined(ANDROID)
 #undef KEY_MAP_ANDROID
 #define KEY_MAP_ANDROID(aCPPKeyName, aNativeKey) \
   NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
-#ifndef MOZ_B2G
 #undef KEY_MAP_ANDROID_EXCEPT_B2G
 #define KEY_MAP_ANDROID_EXCEPT_B2G(aCPPKeyName, aNativeKey) \
   NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
-#else // #ifndef MOZ_B2G
-#undef KEY_MAP_B2G
-#define KEY_MAP_B2G(aCPPKeyName, aNativeKey) \
-  NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
-#endif  // #ifndef MOZ_B2G #else
 #endif
 
 /******************************************************************************
  * Modifier Keys
  ******************************************************************************/
 // Alt
 KEY_MAP_WIN     (Alt, VK_MENU)
 KEY_MAP_WIN     (Alt, VK_LMENU)
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -267,18 +267,17 @@ if toolkit in ('gtk2', 'gtk3', 'windows'
     UNIFIED_SOURCES += [
         'nsNativeTheme.cpp',
     ]
 if toolkit == 'gtk3':
     XPIDL_SOURCES += [
         'nsIApplicationChooser.idl',
     ]
 
-if not CONFIG['MOZ_B2G']:
-    DEFINES['MOZ_CROSS_PROCESS_IME'] = True
+DEFINES['MOZ_CROSS_PROCESS_IME'] = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/ipc',
     '/layout/base',
     '/layout/forms',
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/Dafsa.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "Dafsa.h"
+
+#include "mozilla/Assertions.h"
+#include "nsAString.h"
+
+const int mozilla::Dafsa::kKeyNotFound = -1;
+
+// Note the DAFSA implementation was lifted from eTLD code in Chromium that was
+// originally lifted from Firefox.
+
+// Read next offset from pos.
+// Returns true if an offset could be read, false otherwise.
+bool GetNextOffset(const unsigned char** pos, const unsigned char* end,
+                   const unsigned char** offset) {
+  if (*pos == end)
+    return false;
+
+  // When reading an offset the byte array must always contain at least
+  // three more bytes to consume. First the offset to read, then a node
+  // to skip over and finally a destination node. No object can be smaller
+  // than one byte.
+  MOZ_ASSERT(*pos + 2 < end);
+  size_t bytes_consumed;
+  switch (**pos & 0x60) {
+    case 0x60:  // Read three byte offset
+      *offset += (((*pos)[0] & 0x1F) << 16) | ((*pos)[1] << 8) | (*pos)[2];
+      bytes_consumed = 3;
+      break;
+    case 0x40:  // Read two byte offset
+      *offset += (((*pos)[0] & 0x1F) << 8) | (*pos)[1];
+      bytes_consumed = 2;
+      break;
+    default:
+      *offset += (*pos)[0] & 0x3F;
+      bytes_consumed = 1;
+  }
+  if ((**pos & 0x80) != 0) {
+    *pos = end;
+  } else {
+    *pos += bytes_consumed;
+  }
+  return true;
+}
+
+// Check if byte at offset is last in label.
+bool IsEOL(const unsigned char* offset, const unsigned char* end) {
+  MOZ_ASSERT(offset < end);
+  return (*offset & 0x80) != 0;
+}
+
+// Check if byte at offset matches first character in key.
+// This version matches characters not last in label.
+bool IsMatch(const unsigned char* offset, const unsigned char* end,
+             const char* key) {
+  MOZ_ASSERT(offset < end);
+  return *offset == *key;
+}
+
+// Check if byte at offset matches first character in key.
+// This version matches characters last in label.
+bool IsEndCharMatch(const unsigned char* offset, const unsigned char* end,
+                    const char* key) {
+  MOZ_ASSERT(offset < end);
+  return *offset == (*key | 0x80);
+}
+
+// Read return value at offset.
+// Returns true if a return value could be read, false otherwise.
+bool GetReturnValue(const unsigned char* offset, const unsigned char* end,
+                    int* return_value) {
+  MOZ_ASSERT(offset < end);
+  if ((*offset & 0xE0) == 0x80) {
+    *return_value = *offset & 0x0F;
+    return true;
+  }
+  return false;
+}
+
+// Lookup a domain key in a byte array generated by make_dafsa.py.
+// The rule type is returned if key is found, otherwise kKeyNotFound is returned.
+int LookupString(const unsigned char* graph, size_t length, const char* key,
+                 size_t key_length) {
+  const unsigned char* pos = graph;
+  const unsigned char* end = graph + length;
+  const unsigned char* offset = pos;
+  const char* key_end = key + key_length;
+  while (GetNextOffset(&pos, end, &offset)) {
+    //   char <char>+ end_char offsets
+    //   char <char>+ return value
+    //   char end_char offsets
+    //   char return value
+    //   end_char offsets
+    //   return_value
+    bool did_consume = false;
+    if (key != key_end && !IsEOL(offset, end)) {
+      // Leading <char> is not a match. Don't dive into this child
+      if (!IsMatch(offset, end, key))
+        continue;
+      did_consume = true;
+      ++offset;
+      ++key;
+      // Possible matches at this point:
+      // <char>+ end_char offsets
+      // <char>+ return value
+      // end_char offsets
+      // return value
+      // Remove all remaining <char> nodes possible
+      while (!IsEOL(offset, end) && key != key_end) {
+        if (!IsMatch(offset, end, key))
+          return mozilla::Dafsa::kKeyNotFound;
+        ++key;
+        ++offset;
+      }
+    }
+    // Possible matches at this point:
+    // end_char offsets
+    // return_value
+    // If one or more <char> elements were consumed, a failure
+    // to match is terminal. Otherwise, try the next node.
+    if (key == key_end) {
+      int return_value;
+      if (GetReturnValue(offset, end, &return_value))
+        return return_value;
+      // The DAFSA guarantees that if the first char is a match, all
+      // remaining char elements MUST match if the key is truly present.
+      if (did_consume)
+        return mozilla::Dafsa::kKeyNotFound;
+      continue;
+    }
+    if (!IsEndCharMatch(offset, end, key)) {
+      if (did_consume)
+        return mozilla::Dafsa::kKeyNotFound;  // Unexpected
+      continue;
+    }
+    ++key;
+    pos = ++offset;  // Dive into child
+  }
+  return mozilla::Dafsa::kKeyNotFound;  // No match
+}
+
+namespace mozilla {
+
+int Dafsa::Lookup(const nsACString& aKey) const
+{
+  return LookupString(mData.Elements(), mData.Length(),
+                      aKey.BeginReading(), aKey.Length());
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/Dafsa.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Dafsa_h
+#define mozilla_Dafsa_h
+
+#include "stdint.h"
+
+#include "mozilla/Span.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+/**
+ * A deterministic acyclic finite state automaton suitable for storing static
+ * dictionaries of tagged ascii strings. Consider using this if you have a very
+ * large set of strings that need an associated enum value.
+ *
+ * Currently the string tag is limited by `make_dafsa.py` to a value of [0-4].
+ * In theory [0-15] can easily be supported.
+ *
+ * See `make_dafsa.py` for more details.
+ */
+class Dafsa
+{
+public:
+  using Graph = Span<const uint8_t>;
+
+  /**
+   * Initializes the DAFSA with a binary encoding generated by `make_dafsa.py`.
+   */
+  explicit Dafsa(const Graph& aData) : mData(aData) {}
+
+  ~Dafsa() = default;
+
+  /**
+   * Searches for the given string in the DAFSA.
+   *
+   * @param aKey The string to search for.
+   * @returns kKeyNotFound if not found, otherwise the associated tag.
+   */
+  int Lookup(const nsACString& aKey) const;
+
+  static const int kKeyNotFound;
+
+private:
+  const Graph mData;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Dafsa_h
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/make_dafsa.py
@@ -0,0 +1,471 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+A Deterministic acyclic finite state automaton (DAFSA) is a compact
+representation of an unordered word list (dictionary).
+
+http://en.wikipedia.org/wiki/Deterministic_acyclic_finite_state_automaton
+
+This python program converts a list of strings to a byte array in C++.
+This python program fetches strings and return values from a gperf file
+and generates a C++ file with a byte array representing graph that can be
+used as a memory efficient replacement for the perfect hash table.
+
+The input strings are assumed to consist of printable 7-bit ASCII characters
+and the return values are assumed to be one digit integers.
+
+In this program a DAFSA is a diamond shaped graph starting at a common
+source node and ending at a common sink node. All internal nodes contain
+a label and each word is represented by the labels in one path from
+the source node to the sink node.
+
+The following python represention is used for nodes:
+
+  Source node: [ children ]
+  Internal node: (label, [ children ])
+  Sink node: None
+
+The graph is first compressed by prefixes like a trie. In the next step
+suffixes are compressed so that the graph gets diamond shaped. Finally
+one to one linked nodes are replaced by nodes with the labels joined.
+
+The order of the operations is crucial since lookups will be performed
+starting from the source with no backtracking. Thus a node must have at
+most one child with a label starting by the same character. The output
+is also arranged so that all jumps are to increasing addresses, thus forward
+in memory.
+
+The generated output has suffix free decoding so that the sign of leading
+bits in a link (a reference to a child node) indicate if it has a size of one,
+two or three bytes and if it is the last outgoing link from the actual node.
+A node label is terminated by a byte with the leading bit set.
+
+The generated byte array can described by the following BNF:
+
+<byte> ::= < 8-bit value in range [0x00-0xFF] >
+
+<char> ::= < printable 7-bit ASCII character, byte in range [0x20-0x7F] >
+<end_char> ::= < char + 0x80, byte in range [0xA0-0xFF] >
+<return value> ::= < value + 0x80, byte in range [0x80-0x8F] >
+
+<offset1> ::= < byte in range [0x00-0x3F] >
+<offset2> ::= < byte in range [0x40-0x5F] >
+<offset3> ::= < byte in range [0x60-0x7F] >
+
+<end_offset1> ::= < byte in range [0x80-0xBF] >
+<end_offset2> ::= < byte in range [0xC0-0xDF] >
+<end_offset3> ::= < byte in range [0xE0-0xFF] >
+
+<prefix> ::= <char>
+
+<label> ::= <end_char>
+          | <char> <label>
+
+<end_label> ::= <return_value>
+          | <char> <end_label>
+
+<offset> ::= <offset1>
+           | <offset2> <byte>
+           | <offset3> <byte> <byte>
+
+<end_offset> ::= <end_offset1>
+               | <end_offset2> <byte>
+               | <end_offset3> <byte> <byte>
+
+<offsets> ::= <end_offset>
+            | <offset> <offsets>
+
+<source> ::= <offsets>
+
+<node> ::= <label> <offsets>
+         | <prefix> <node>
+         | <end_label>
+
+<dafsa> ::= <source>
+          | <dafsa> <node>
+
+Decoding:
+
+<char> -> printable 7-bit ASCII character
+<end_char> & 0x7F -> printable 7-bit ASCII character
+<return value> & 0x0F -> integer
+<offset1 & 0x3F> -> integer
+((<offset2> & 0x1F>) << 8) + <byte> -> integer
+((<offset3> & 0x1F>) << 16) + (<byte> << 8) + <byte> -> integer
+
+end_offset1, end_offset2 and and_offset3 are decoded same as offset1,
+offset2 and offset3 respectively.
+
+The first offset in a list of offsets is the distance in bytes between the
+offset itself and the first child node. Subsequent offsets are the distance
+between previous child node and next child node. Thus each offset links a node
+to a child node. The distance is always counted between start addresses, i.e.
+first byte in decoded offset or first byte in child node.
+
+Example 1:
+
+%%
+aa, 1
+a, 2
+%%
+
+The input is first parsed to a list of words:
+["aa1", "a2"]
+
+A fully expanded graph is created from the words:
+source = [node1, node4]
+node1 = ("a", [node2])
+node2 = ("a", [node3])
+node3 = ("\x01", [sink])
+node4 = ("a", [node5])
+node5 = ("\x02", [sink])
+sink = None
+
+Compression results in the following graph:
+source = [node1]
+node1 = ("a", [node2, node3])
+node2 = ("\x02", [sink])
+node3 = ("a\x01", [sink])
+sink = None
+
+A C++ representation of the compressed graph is generated:
+
+const unsigned char dafsa[7] = {
+  0x81, 0xE1, 0x02, 0x81, 0x82, 0x61, 0x81,
+};
+
+The bytes in the generated array has the following meaning:
+
+ 0: 0x81 <end_offset1>  child at position 0 + (0x81 & 0x3F) -> jump to 1
+
+ 1: 0xE1 <end_char>     label character (0xE1 & 0x7F) -> match "a"
+ 2: 0x02 <offset1>      child at position 2 + (0x02 & 0x3F) -> jump to 4
+
+ 3: 0x81 <end_offset1>  child at position 4 + (0x81 & 0x3F) -> jump to 5
+ 4: 0x82 <return_value> 0x82 & 0x0F -> return 2
+
+ 5: 0x61 <char>         label character 0x61 -> match "a"
+ 6: 0x81 <return_value> 0x81 & 0x0F -> return 1
+
+Example 2:
+
+%%
+aa, 1
+bbb, 2
+baa, 1
+%%
+
+The input is first parsed to a list of words:
+["aa1", "bbb2", "baa1"]
+
+Compression results in the following graph:
+source = [node1, node2]
+node1 = ("b", [node2, node3])
+node2 = ("aa\x01", [sink])
+node3 = ("bb\x02", [sink])
+sink = None
+
+A C++ representation of the compressed graph is generated:
+
+const unsigned char dafsa[11] = {
+  0x02, 0x83, 0xE2, 0x02, 0x83, 0x61, 0x61, 0x81, 0x62, 0x62, 0x82,
+};
+
+The bytes in the generated array has the following meaning:
+
+ 0: 0x02 <offset1>      child at position 0 + (0x02 & 0x3F) -> jump to 2
+ 1: 0x83 <end_offset1>  child at position 2 + (0x83 & 0x3F) -> jump to 5
+
+ 2: 0xE2 <end_char>     label character (0xE2 & 0x7F) -> match "b"
+ 3: 0x02 <offset1>      child at position 3 + (0x02 & 0x3F) -> jump to 5
+ 4: 0x83 <end_offset1>  child at position 5 + (0x83 & 0x3F) -> jump to 8
+
+ 5: 0x61 <char>         label character 0x61 -> match "a"
+ 6: 0x61 <char>         label character 0x61 -> match "a"
+ 7: 0x81 <return_value> 0x81 & 0x0F -> return 1
+
+ 8: 0x62 <char>         label character 0x62 -> match "b"
+ 9: 0x62 <char>         label character 0x62 -> match "b"
+10: 0x82 <return_value> 0x82 & 0x0F -> return 2
+"""
+
+import sys
+
+class InputError(Exception):
+  """Exception raised for errors in the input file."""
+
+
+def to_dafsa(words):
+  """Generates a DAFSA from a word list and returns the source node.
+
+  Each word is split into characters so that each character is represented by
+  a unique node. It is assumed the word list is not empty.
+  """
+  if not words:
+    raise InputError('The domain list must not be empty')
+  def ToNodes(word):
+    """Split words into characters"""
+    if not 0x1F < ord(word[0]) < 0x80:
+      raise InputError('Domain names must be printable 7-bit ASCII')
+    if len(word) == 1:
+      return chr(ord(word[0]) & 0x0F), [None]
+    return word[0], [ToNodes(word[1:])]
+  return [ToNodes(word) for word in words]
+
+
+def to_words(node):
+  """Generates a word list from all paths starting from an internal node."""
+  if not node:
+    return ['']
+  return [(node[0] + word) for child in node[1] for word in to_words(child)]
+
+
+def reverse(dafsa):
+  """Generates a new DAFSA that is reversed, so that the old sink node becomes
+  the new source node.
+  """
+  sink = []
+  nodemap = {}
+
+  def dfs(node, parent):
+    """Creates reverse nodes.
+
+    A new reverse node will be created for each old node. The new node will
+    get a reversed label and the parents of the old node as children.
+    """
+    if not node:
+      sink.append(parent)
+    elif id(node) not in nodemap:
+      nodemap[id(node)] = (node[0][::-1], [parent])
+      for child in node[1]:
+        dfs(child, nodemap[id(node)])
+    else:
+      nodemap[id(node)][1].append(parent)
+
+  for node in dafsa:
+    dfs(node, None)
+  return sink
+
+
+def join_labels(dafsa):
+  """Generates a new DAFSA where internal nodes are merged if there is a one to
+  one connection.
+  """
+  parentcount = { id(None): 2 }
+  nodemap = { id(None): None }
+
+  def count_parents(node):
+    """Count incoming references"""
+    if id(node) in parentcount:
+      parentcount[id(node)] += 1
+    else:
+      parentcount[id(node)] = 1
+      for child in node[1]:
+        count_parents(child)
+
+  def join(node):
+    """Create new nodes"""
+    if id(node) not in nodemap:
+      children = [join(child) for child in node[1]]
+      if len(children) == 1 and parentcount[id(node[1][0])] == 1:
+        child = children[0]
+        nodemap[id(node)] = (node[0] + child[0], child[1])
+      else:
+        nodemap[id(node)] = (node[0], children)
+    return nodemap[id(node)]
+
+  for node in dafsa:
+    count_parents(node)
+  return [join(node) for node in dafsa]
+
+
+def join_suffixes(dafsa):
+  """Generates a new DAFSA where nodes that represent the same word lists
+  towards the sink are merged.
+  """
+  nodemap = { frozenset(('',)): None }
+
+  def join(node):
+    """Returns a macthing node. A new node is created if no matching node
+    exists. The graph is accessed in dfs order.
+    """
+    suffixes = frozenset(to_words(node))
+    if suffixes not in nodemap:
+      nodemap[suffixes] = (node[0], [join(child) for child in node[1]])
+    return nodemap[suffixes]
+
+  return [join(node) for node in dafsa]
+
+
+def top_sort(dafsa):
+  """Generates list of nodes in topological sort order."""
+  incoming = {}
+
+  def count_incoming(node):
+    """Counts incoming references."""
+    if node:
+      if id(node) not in incoming:
+        incoming[id(node)] = 1
+        for child in node[1]:
+          count_incoming(child)
+      else:
+        incoming[id(node)] += 1
+
+  for node in dafsa:
+    count_incoming(node)
+
+  for node in dafsa:
+    incoming[id(node)] -= 1
+
+  waiting = [node for node in dafsa if incoming[id(node)] == 0]
+  nodes = []
+
+  while waiting:
+    node = waiting.pop()
+    assert incoming[id(node)] == 0
+    nodes.append(node)
+    for child in node[1]:
+      if child:
+        incoming[id(child)] -= 1
+        if incoming[id(child)] == 0:
+          waiting.append(child)
+  return nodes
+
+
+def encode_links(children, offsets, current):
+  """Encodes a list of children as one, two or three byte offsets."""
+  if not children[0]:
+    # This is an <end_label> node and no links follow such nodes
+    assert len(children) == 1
+    return []
+  guess = 3 * len(children)
+  assert children
+  children = sorted(children, key = lambda x: -offsets[id(x)])
+  while True:
+    offset = current + guess
+    buf = []
+    for child in children:
+      last = len(buf)
+      distance = offset - offsets[id(child)]
+      assert distance > 0 and distance < (1 << 21)
+
+      if distance < (1 << 6):
+        # A 6-bit offset: "s0xxxxxx"
+        buf.append(distance)
+      elif distance < (1 << 13):
+        # A 13-bit offset: "s10xxxxxxxxxxxxx"
+        buf.append(0x40 | (distance >> 8))
+        buf.append(distance & 0xFF)
+      else:
+        # A 21-bit offset: "s11xxxxxxxxxxxxxxxxxxxxx"
+        buf.append(0x60 | (distance >> 16))
+        buf.append((distance >> 8) & 0xFF)
+        buf.append(distance & 0xFF)
+      # Distance in first link is relative to following record.
+      # Distance in other links are relative to previous link.
+      offset -= distance
+    if len(buf) == guess:
+      break
+    guess = len(buf)
+  # Set most significant bit to mark end of links in this node.
+  buf[last] |= (1 << 7)
+  buf.reverse()
+  return buf
+
+
+def encode_prefix(label):
+  """Encodes a node label as a list of bytes without a trailing high byte.
+
+  This method encodes a node if there is exactly one child  and the
+  child follows immidiately after so that no jump is needed. This label
+  will then be a prefix to the label in the child node.
+  """
+  assert label
+  return [ord(c) for c in reversed(label)]
+
+
+def encode_label(label):
+  """Encodes a node label as a list of bytes with a trailing high byte >0x80.
+  """
+  buf = encode_prefix(label)
+  # Set most significant bit to mark end of label in this node.
+  buf[0] |= (1 << 7)
+  return buf
+
+
+def encode(dafsa):
+  """Encodes a DAFSA to a list of bytes"""
+  output = []
+  offsets = {}
+
+  for node in reversed(top_sort(dafsa)):
+    if (len(node[1]) == 1 and node[1][0] and
+        (offsets[id(node[1][0])] == len(output))):
+      output.extend(encode_prefix(node[0]))
+    else:
+      output.extend(encode_links(node[1], offsets, len(output)))
+      output.extend(encode_label(node[0]))
+    offsets[id(node)] = len(output)
+
+  output.extend(encode_links(dafsa, offsets, len(output)))
+  output.reverse()
+  return output
+
+
+def to_cxx(data):
+  """Generates C++ code from a list of encoded bytes."""
+  text = '/* This file is generated. DO NOT EDIT!\n\n'
+  text += 'The byte array encodes effective tld names. See make_dafsa.py for'
+  text += ' documentation.'
+  text += '*/\n\n'
+  text += 'const unsigned char kDafsa[%s] = {\n' % len(data)
+  for i in range(0, len(data), 12):
+    text += '  '
+    text += ', '.join('0x%02x' % byte for byte in data[i:i + 12])
+    text += ',\n'
+  text += '};\n'
+  return text
+
+
+def words_to_cxx(words):
+  """Generates C++ code from a word list"""
+  dafsa = to_dafsa(words)
+  for fun in (reverse, join_suffixes, reverse, join_suffixes, join_labels):
+    dafsa = fun(dafsa)
+  return to_cxx(encode(dafsa))
+
+
+def parse_gperf(infile):
+  """Parses gperf file and extract strings and return code"""
+  lines = [line.strip() for line in infile]
+  # Extract strings after the first '%%' and before the second '%%'.
+  begin = lines.index('%%') + 1
+  end = lines.index('%%', begin)
+  lines = lines[begin:end]
+  for line in lines:
+    if line[-3:-1] != ', ':
+      raise InputError('Expected "domainname, <digit>", found "%s"' % line)
+    # Technically the DAFSA format could support return values in range [0-31],
+    # but the values below are the only with a defined meaning.
+    if line[-1] not in '0124':
+      raise InputError('Expected value to be one of {0,1,2,4}, found "%s"' %
+                       line[-1])
+  return [line[:-3] + line[-1] for line in lines]
+
+
+def main(outfile, infile):
+  with open(infile, 'r') as infile:
+    outfile.write(words_to_cxx(parse_gperf(infile)))
+  return 0
+
+
+if __name__ == '__main__':
+  if len(sys.argv) != 3:
+    print('usage: %s infile outfile' % sys.argv[0])
+    sys.exit(1)
+
+  with open(sys.argv[2], 'w') as outfile:
+    sys.exit(main(outfile, sys.argv[1]))
--- a/xpcom/ds/moz.build
+++ b/xpcom/ds/moz.build
@@ -77,23 +77,25 @@ EXPORTS += [
     'nsWhitespaceTokenizer.h',
     'PLDHashTable.h',
 ]
 
 EXPORTS.mozilla += [
     'ArenaAllocator.h',
     'ArenaAllocatorExtensions.h',
     'ArrayIterator.h',
+    'Dafsa.h',
     'IncrementalTokenizer.h',
     'Observer.h',
     'StickyTimeDuration.h',
     'Tokenizer.h',
 ]
 
 UNIFIED_SOURCES += [
+    'Dafsa.cpp',
     'IncrementalTokenizer.cpp',
     'nsArray.cpp',
     'nsArrayEnumerator.cpp',
     'nsArrayUtils.cpp',
     'nsAtomService.cpp',
     'nsAtomTable.cpp',
     'nsCOMArray.cpp',
     'nsCRT.cpp',
--- a/xpcom/string/nsTextFormatter.cpp
+++ b/xpcom/string/nsTextFormatter.cpp
@@ -1207,70 +1207,16 @@ StringStuff(SprintfStateStr* aState, con
   str->Append(aStr, aLen);
 
   aState->base = str->BeginWriting();
   aState->cur = aState->base + off;
 
   return 0;
 }
 
-/*
-** Stuff routine that automatically grows the malloc'd output buffer
-** before it overflows.
-*/
-static int
-GrowStuff(SprintfStateStr* aState, const char16_t* aStr, uint32_t aLen)
-{
-  ptrdiff_t off;
-  char16_t* newbase;
-  uint32_t newlen;
-
-  off = aState->cur - aState->base;
-  if (off + aLen >= aState->maxlen) {
-    /* Grow the buffer */
-    newlen = aState->maxlen + ((aLen > 32) ? aLen : 32);
-    if (aState->base) {
-      newbase = (char16_t*)moz_xrealloc(aState->base,
-                                        newlen * sizeof(char16_t));
-    } else {
-      newbase = (char16_t*)moz_xmalloc(newlen * sizeof(char16_t));
-    }
-    if (!newbase) {
-      /* Ran out of memory */
-      return -1;
-    }
-    aState->base = newbase;
-    aState->maxlen = newlen;
-    aState->cur = aState->base + off;
-  }
-
-  /* Copy data */
-  while (aLen) {
-    --aLen;
-    *aState->cur++ = *aStr++;
-  }
-  MOZ_ASSERT((uint32_t)(aState->cur - aState->base) <= aState->maxlen);
-  return 0;
-}
-
-/*
-** sprintf into a malloc'd buffer
-*/
-char16_t*
-nsTextFormatter::smprintf(const char16_t* aFmt, ...)
-{
-  va_list ap;
-  char16_t* rv;
-
-  va_start(ap, aFmt);
-  rv = nsTextFormatter::vsmprintf(aFmt, ap);
-  va_end(ap);
-  return rv;
-}
-
 uint32_t
 nsTextFormatter::ssprintf(nsAString& aOut, const char16_t* aFmt, ...)
 {
   va_list ap;
   uint32_t rv;
 
   va_start(ap, aFmt);
   rv = nsTextFormatter::vssprintf(aOut, aFmt, ap);
@@ -1288,36 +1234,16 @@ nsTextFormatter::vssprintf(nsAString& aO
   ss.maxlen = 0;
   ss.stuffclosure = &aOut;
 
   aOut.Truncate();
   int n = dosprintf(&ss, aFmt, aAp);
   return n ? n - 1 : n;
 }
 
-char16_t*
-nsTextFormatter::vsmprintf(const char16_t* aFmt, va_list aAp)
-{
-  SprintfStateStr ss;
-  int rv;
-
-  ss.stuff = GrowStuff;
-  ss.base = 0;
-  ss.cur = 0;
-  ss.maxlen = 0;
-  rv = dosprintf(&ss, aFmt, aAp);
-  if (rv < 0) {
-    if (ss.base) {
-      free(ss.base);
-    }
-    return 0;
-  }
-  return ss.base;
-}
-
 /*
 ** Stuff routine that discards overflow data
 */
 static int
 LimitStuff(SprintfStateStr* aState, const char16_t* aStr, uint32_t aLen)
 {
   uint32_t limit = aState->maxlen - (aState->cur - aState->base);
 
--- a/xpcom/string/nsTextFormatter.h
+++ b/xpcom/string/nsTextFormatter.h
@@ -48,25 +48,22 @@ public:
    * sprintf into a fixed size buffer. Guarantees that the buffer is null
    * terminated. Returns the length of the written output, NOT including the
    * null terminator, or (uint32_t)-1 if an error occurs.
    */
   static uint32_t snprintf(char16_t* aOut, uint32_t aOutLen,
                            const char16_t* aFmt, ...);
 
   /*
-   * sprintf into a moz_xmalloc'd buffer. Return a pointer to
-   * buffer on success, nullptr on failure. Use free() to free the buffer.
+   * sprintf into an existing nsAString, overwriting any contents it already
+   * has. Infallible.
    */
-  static char16_t* smprintf(const char16_t* aFmt, ...);
-
   static uint32_t ssprintf(nsAString& aOut, const char16_t* aFmt, ...);
 
   /*
    * va_list forms of the above.
    */
   static uint32_t vsnprintf(char16_t* aOut, uint32_t aOutLen, const char16_t* aFmt,
                             va_list aAp);
-  static char16_t* vsmprintf(const char16_t* aFmt, va_list aAp);
   static uint32_t vssprintf(nsAString& aOut, const char16_t* aFmt, va_list aAp);
 };
 
 #endif /* nsTextFormatter_h___ */
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/gtest/TestDafsa.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Dafsa.h"
+#include "gtest/gtest.h"
+
+#include "nsString.h"
+
+using mozilla::Dafsa;
+
+namespace dafsa_test_1 {
+#include "dafsa_test_1.inc" // kDafsa
+}
+
+TEST(Dafsa, Constructor)
+{
+  Dafsa d(dafsa_test_1::kDafsa);
+}
+
+TEST(Dafsa, StringsFound)
+{
+  Dafsa d(dafsa_test_1::kDafsa);
+
+  int tag = d.Lookup(NS_LITERAL_CSTRING("foo.bar.baz"));
+  EXPECT_EQ(tag, 1);
+
+  tag = d.Lookup(NS_LITERAL_CSTRING("a.test.string"));
+  EXPECT_EQ(tag, 0);
+
+  tag = d.Lookup(NS_LITERAL_CSTRING("a.test.string2"));
+  EXPECT_EQ(tag, 2);
+
+  tag = d.Lookup(NS_LITERAL_CSTRING("aaaa"));
+  EXPECT_EQ(tag, 4);
+}
+
+TEST(Dafsa, StringsNotFound)
+{
+  Dafsa d(dafsa_test_1::kDafsa);
+
+  // Matches all but last letter.
+  int tag = d.Lookup(NS_LITERAL_CSTRING("foo.bar.ba"));
+  EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+  // Matches prefix with extra letter.
+  tag = d.Lookup(NS_LITERAL_CSTRING("a.test.strings"));
+  EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+  // Matches small portion.
+  tag = d.Lookup(NS_LITERAL_CSTRING("a.test"));
+  EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+  // Matches repeating pattern with extra letters.
+  tag = d.Lookup(NS_LITERAL_CSTRING("aaaaa"));
+  EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+  // Empty string.
+  tag = d.Lookup(NS_LITERAL_CSTRING(""));
+  EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+}
+
+TEST(Dafsa, HugeString)
+{
+  Dafsa d(dafsa_test_1::kDafsa);
+
+  int tag = d.Lookup(NS_LITERAL_CSTRING(
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        "This is a very long string that is larger than the dafsa itself. "
+        ));
+  EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+}
--- a/xpcom/tests/gtest/TestExpirationTracker.cpp
+++ b/xpcom/tests/gtest/TestExpirationTracker.cpp
@@ -55,16 +55,18 @@ public:
   void LogAction(Object* aObj, const char* aAction) {
     if (logging) {
       printf("%d %p(%d): %s\n", PR_IntervalNow(),
              static_cast<void*>(aObj), aObj->mLastUsed, aAction);
     }
   }
 
   void DoRandomOperation() {
+    using mozilla::UniquePtr;
+
     Object* obj;
     switch (rand() & 0x7) {
     case 0: {
       if (mUniverse.Length() < 50) {
         obj = new Object();
         mUniverse.AppendElement(obj);
         nsExpirationTracker<Object,K>::AddObject(obj);
         LogAction(obj, "Created and added");
--- a/xpcom/tests/gtest/TestSTLWrappers.cpp
+++ b/xpcom/tests/gtest/TestSTLWrappers.cpp
@@ -24,16 +24,17 @@
 #  pragma warning( disable : 4530 )
 #  define TRY       try
 #  define CATCH(e)  catch (e)
 #else
 #  define TRY
 #  define CATCH(e)  if (0)
 #endif
 
+#include "gtest/gtest.h"
 
 #if defined(XP_UNIX)
 extern unsigned int _gdb_sleep_duration;
 #endif
 
 void ShouldAbort()
 {
 #if defined(XP_UNIX)
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/gtest/dafsa_test_1.dat
@@ -0,0 +1,6 @@
+%%
+foo.bar.baz, 1
+a.test.string, 0
+a.test.string2, 2
+aaaa, 4
+%%
--- a/xpcom/tests/gtest/moz.build
+++ b/xpcom/tests/gtest/moz.build
@@ -10,16 +10,17 @@ UNIFIED_SOURCES += [
     'TestAtoms.cpp',
     'TestAutoPtr.cpp',
     'TestAutoRef.cpp',
     'TestBase64.cpp',
     'TestCallTemplates.cpp',
     'TestCloneInputStream.cpp',
     'TestCOMPtrEq.cpp',
     'TestCRT.cpp',
+    'TestDafsa.cpp',
     'TestEncoding.cpp',
     'TestEscapeURL.cpp',
     'TestEventTargetQI.cpp',
     'TestExpirationTracker.cpp',
     'TestFile.cpp',
     'TestGCPostBarriers.cpp',
     'TestID.cpp',
     'TestMozPromise.cpp',
@@ -79,11 +80,18 @@ SOURCES += [
     'TestHashtables.cpp', # Redefines IFoo
     'TestNsRefPtr.cpp', # Redefines Foo
 ]
 
 LOCAL_INCLUDES += [
     '../../base',
 ]
 
+GENERATED_FILES = [
+    'dafsa_test_1.inc',
+]
+dafsa_data = GENERATED_FILES['dafsa_test_1.inc']
+dafsa_data.script = '../../ds/make_dafsa.py'
+dafsa_data.inputs = ['dafsa_test_1.dat']
+
 FINAL_LIBRARY = 'xul-gtest'
 
 include('/ipc/chromium/chromium-config.mozbuild')