Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Mon, 20 Aug 2018 19:31:32 +0300
changeset 432426 1ba33950cff38b7e7baaf5d96431a11814de83df
parent 432425 3d64c00c695a519b83b3644ab46f38b8a8add753 (current diff)
parent 432334 0d8fa976cabda7db3d65efdcb69aa785f5bec9c4 (diff)
child 432427 14ef4dec612654a0c3ba398bfcefd9436ea4abf6
child 432468 bbe6243b9227ae83d4ed466db473d21875c667ae
push id106731
push usershindli@mozilla.com
push dateMon, 20 Aug 2018 16:36:25 +0000
treeherdermozilla-inbound@14ef4dec6126 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -128,34 +128,34 @@ oddly_ordered_inclnames = set([
 # ways.  Here is the output we expect.  If the actual output differs from
 # this, one of the following must have happened.
 # - New SpiderMonkey code violates one of the checked rules.
 # - The tests/style/ files have changed without expected_output being changed
 #   accordingly.
 # - This script has been broken somehow.
 #
 expected_output = '''\
-js/src/tests/style/BadIncludes2.h:1: error:
-    vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h"
-
 js/src/tests/style/BadIncludes.h:3: error:
     the file includes itself
 
 js/src/tests/style/BadIncludes.h:6: error:
     "BadIncludes2.h" is included using the wrong path;
     did you forget a prefix, or is the file not yet committed?
 
 js/src/tests/style/BadIncludes.h:8: error:
     <tests/style/BadIncludes2.h> should be included using
     the #include "..." form
 
 js/src/tests/style/BadIncludes.h:10: error:
     "stdio.h" is included using the wrong path;
     did you forget a prefix, or is the file not yet committed?
 
+js/src/tests/style/BadIncludes2.h:1: error:
+    vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h"
+
 js/src/tests/style/BadIncludesOrder-inl.h:5:6: error:
     "vm/JSScript-inl.h" should be included after "vm/Interpreter-inl.h"
 
 js/src/tests/style/BadIncludesOrder-inl.h:6:7: error:
     "vm/Interpreter-inl.h" should be included after "js/Value.h"
 
 js/src/tests/style/BadIncludesOrder-inl.h:7:8: error:
     "js/Value.h" should be included after "ds/LifoAlloc.h"
@@ -301,17 +301,17 @@ def check_style(enable_fixup):
     edges = dict()      # type: dict(inclname, set(inclname))
 
     # We don't care what's inside the MFBT and MOZALLOC files, but because they
     # are #included from JS files we have to add them to the inclusion graph.
     for inclname in non_js_inclnames:
         edges[inclname] = set()
 
     # Process all the JS files.
-    for filename in js_names.keys():
+    for filename in sorted(js_names.keys()):
         inclname = js_names[filename]
         file_kind = FileKind.get(filename)
         if file_kind == FileKind.C or file_kind == FileKind.CPP or \
            file_kind == FileKind.H or file_kind == FileKind.INL_H:
             included_h_inclnames = set()    # type: set(inclname)
 
             with open(filename) as f:
                 code = read_file(f)
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3491,39 +3491,40 @@ class CGConstructorEnabled(CGAbstractMet
 
 def CreateBindingJSObject(descriptor, properties):
     objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType
 
     # We don't always need to root obj, but there are a variety
     # of cases where we do, so for simplicity, just always root it.
     if descriptor.proxy:
         if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
-            expandoValue = "JS::PrivateValue(&aObject->mExpandoAndGeneration)"
-        else:
-            expandoValue = "JS::UndefinedValue()"
-        create = fill(
-            """
-            JS::Rooted<JS::Value> expandoValue(aCx, ${expandoValue});
-            creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
-                                      proto, aObject, expandoValue, aReflector);
-            if (!aReflector) {
-              return false;
-            }
-
-            """,
-            expandoValue=expandoValue)
+            create = dedent(
+                """
+                MOZ_ASSERT(aObject->mExpandoAndGeneration.expando.isUndefined());
+                JS::Rooted<JS::Value> expandoValue(aCx, JS::PrivateValue(&aObject->mExpandoAndGeneration));
+                creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
+                                          proto, aObject, expandoValue, aReflector);
+                """)
+        else:
+            create = dedent(
+                """
+                creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
+                                          proto, aObject, JS::UndefinedHandleValue, aReflector);
+                """)
     else:
         create = dedent(
             """
             creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector);
-            if (!aReflector) {
-              return false;
-            }
             """)
-    return objDecl + create
+    return objDecl + create + dedent(
+        """
+        if (!aReflector) {
+          return false;
+        }
+        """)
 
 
 def InitUnforgeablePropertiesOnHolder(descriptor, properties, failureCode,
                                       holderName="unforgeableHolder"):
     """
     Define the unforgeable properties on the unforgeable holder for
     the interface represented by descriptor.
 
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -64,21 +64,63 @@ struct SetDOMProxyInformation
   SetDOMProxyInformation() {
     js::SetDOMProxyInformation((const void*) &DOMProxyHandler::family,
                                DOMProxyShadows);
   }
 };
 
 SetDOMProxyInformation gSetDOMProxyInformation;
 
+static inline void
+CheckExpandoObject(JSObject* proxy, const JS::Value& expando)
+{
+#ifdef DEBUG
+  JSObject* obj = &expando.toObject();
+  MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj));
+  MOZ_ASSERT(js::GetObjectCompartment(proxy) == js::GetObjectCompartment(obj));
+
+  // When we create an expando object in EnsureExpandoObject below, we preserve
+  // the wrapper. The wrapper is released when the object is unlinked, but we
+  // should never call these functions after that point.
+  nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
+  nsWrapperCache* cache;
+  CallQueryInterface(native, &cache);
+  MOZ_ASSERT(cache->PreservingWrapper());
+#endif
+}
+
+static inline void
+CheckExpandoAndGeneration(JSObject* proxy, js::ExpandoAndGeneration* expandoAndGeneration)
+{
+#ifdef DEBUG
+  JS::Value value = expandoAndGeneration->expando;
+  if (!value.isUndefined())
+    CheckExpandoObject(proxy, value);
+#endif
+}
+
+static inline void
+CheckDOMProxy(JSObject* proxy)
+{
+#ifdef DEBUG
+  MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
+  MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&proxy));
+  nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
+  nsWrapperCache* cache;
+  CallQueryInterface(native, &cache);
+  MOZ_ASSERT(cache->GetWrapperPreserveColor() == proxy);
+#endif
+}
+
 // static
 JSObject*
 DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj)
 {
-  MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
+  CheckDOMProxy(obj);
+
   JS::Value v = js::GetProxyPrivate(obj);
   if (v.isUndefined()) {
     return nullptr;
   }
 
   if (v.isObject()) {
     js::SetProxyPrivate(obj, UndefinedValue());
   } else {
@@ -99,33 +141,37 @@ DOMProxyHandler::GetAndClearExpandoObjec
     //
     // We don't need to do this in the non-expandoAndGeneration case, because
     // in that case our value is stored in a slot and slots will already mark
     // the old thing live when the value in the slot changes.
     JS::ExposeValueToActiveJS(v);
     expandoAndGeneration->expando = UndefinedValue();
   }
 
+  CheckExpandoObject(obj, v);
 
   return &v.toObject();
 }
 
 // static
 JSObject*
 DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle<JSObject*> obj)
 {
-  NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object");
+  CheckDOMProxy(obj);
+
   JS::Value v = js::GetProxyPrivate(obj);
   if (v.isObject()) {
+    CheckExpandoObject(obj, v);
     return &v.toObject();
   }
 
   js::ExpandoAndGeneration* expandoAndGeneration;
   if (!v.isUndefined()) {
     expandoAndGeneration = static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+    CheckExpandoAndGeneration(obj, expandoAndGeneration);
     if (expandoAndGeneration->expando.isObject()) {
       return &expandoAndGeneration->expando.toObject();
     }
   } else {
     expandoAndGeneration = nullptr;
   }
 
   JS::Rooted<JSObject*> expando(cx,
@@ -265,28 +311,32 @@ DOMProxyHandler::setCustom(JSContext* cx
   *done = false;
   return true;
 }
 
 //static
 JSObject *
 DOMProxyHandler::GetExpandoObject(JSObject *obj)
 {
-  MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
+  CheckDOMProxy(obj);
+
   JS::Value v = js::GetProxyPrivate(obj);
   if (v.isObject()) {
+    CheckExpandoObject(obj, v);
     return &v.toObject();
   }
 
   if (v.isUndefined()) {
     return nullptr;
   }
 
   js::ExpandoAndGeneration* expandoAndGeneration =
     static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+  CheckExpandoAndGeneration(obj, expandoAndGeneration);
+
   v = expandoAndGeneration->expando;
   return v.isUndefined() ? nullptr : &v.toObject();
 }
 
 void
 ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
 {
   DOMProxyHandler::trace(trc, proxy);
--- a/dom/cache/FileUtils.cpp
+++ b/dom/cache/FileUtils.cpp
@@ -179,18 +179,18 @@ BodyStartWriteStream(const QuotaInfo& aQ
   rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_TMP, getter_AddRefs(tmpFile));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = tmpFile->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   if (NS_WARN_IF(exists)) { return NS_ERROR_FILE_ALREADY_EXISTS; }
 
   nsCOMPtr<nsIOutputStream> fileStream =
-    FileOutputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
-                             aQuotaInfo.mOrigin, tmpFile);
+    CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
+                           aQuotaInfo.mOrigin, tmpFile);
   if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; }
 
   RefPtr<SnappyCompressOutputStream> compressed =
     new SnappyCompressOutputStream(fileStream);
 
   nsCOMPtr<nsIEventTarget> target =
     do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
 
@@ -258,18 +258,18 @@ BodyOpen(const QuotaInfo& aQuotaInfo, ns
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   bool exists;
   rv = finalFile->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   if (NS_WARN_IF(!exists)) { return NS_ERROR_FILE_NOT_FOUND; }
 
   nsCOMPtr<nsIInputStream> fileStream =
-    FileInputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
-                            aQuotaInfo.mOrigin, finalFile);
+    CreateFileInputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
+                          aQuotaInfo.mOrigin, finalFile);
   if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; }
 
   fileStream.forget(aStreamOut);
 
   return rv;
 }
 
 // static
--- a/dom/filehandle/ActorsParent.cpp
+++ b/dom/filehandle/ActorsParent.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/Unused.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/PBackgroundFileHandleParent.h"
 #include "mozilla/dom/PBackgroundFileRequestParent.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/ipc/PendingIPCBlobParent.h"
+#include "mozilla/dom/quota/MemoryOutputStream.h"
 #include "nsAutoPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsIEventTarget.h"
 #include "nsIFileStreams.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
@@ -43,16 +44,17 @@
 #define ASSERT_UNLESS_FUZZING(...) do { } while (0)
 #else
 #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
 #endif
 
 namespace mozilla {
 namespace dom {
 
+using namespace mozilla::dom::quota;
 using namespace mozilla::ipc;
 
 namespace {
 
 /******************************************************************************
  * Constants
  ******************************************************************************/
 
@@ -627,18 +629,16 @@ protected:
   GetResponse(FileRequestResponse& aResponse) override;
 };
 
 class ReadOp final
   : public CopyFileHandleOp
 {
   friend class FileHandle;
 
-  class MemoryOutputStream;
-
   const FileRequestReadParams mParams;
 
 private:
   // Only created by FileHandle.
   ReadOp(FileHandle* aFileHandle,
          const FileRequestParams& aParams);
 
   ~ReadOp()
@@ -646,44 +646,16 @@ private:
 
   virtual bool
   Init(FileHandle* aFileHandle) override;
 
   virtual void
   GetResponse(FileRequestResponse& aResponse) override;
 };
 
-class ReadOp::MemoryOutputStream final
-  : public nsIOutputStream
-{
-  nsCString mData;
-  uint64_t mOffset;
-
-public:
-  static already_AddRefed<MemoryOutputStream>
-  Create(uint64_t aSize);
-
-  const nsCString&
-  Data() const
-  {
-    return mData;
-  }
-
-private:
-  MemoryOutputStream()
-  : mOffset(0)
-  { }
-
-  virtual ~MemoryOutputStream()
-  { }
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIOUTPUTSTREAM
-};
-
 class WriteOp final
   : public CopyFileHandleOp
 {
   friend class FileHandle;
 
   const FileRequestWriteParams mParams;
 
 private:
@@ -2399,106 +2371,16 @@ ReadOp::GetResponse(FileRequestResponse&
 {
   AssertIsOnOwningThread();
 
   auto* stream = static_cast<MemoryOutputStream*>(mBufferStream.get());
 
   aResponse = FileRequestReadResponse(stream->Data());
 }
 
-// static
-already_AddRefed<ReadOp::MemoryOutputStream>
-ReadOp::
-MemoryOutputStream::Create(uint64_t aSize)
-{
-  MOZ_ASSERT(aSize, "Passed zero size!");
-
-  if (NS_WARN_IF(aSize > UINT32_MAX)) {
-    return nullptr;
-  }
-
-  RefPtr<MemoryOutputStream> stream = new MemoryOutputStream();
-
-  char* dummy;
-  uint32_t length = stream->mData.GetMutableData(&dummy, aSize, fallible);
-  if (NS_WARN_IF(length != aSize)) {
-    return nullptr;
-  }
-
-  return stream.forget();
-}
-
-NS_IMPL_ISUPPORTS(ReadOp::MemoryOutputStream, nsIOutputStream)
-
-NS_IMETHODIMP
-ReadOp::
-MemoryOutputStream::Close()
-{
-  mData.Truncate(mOffset);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ReadOp::
-MemoryOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval)
-{
-  return WriteSegments(NS_CopySegmentToBuffer, (char*)aBuf, aCount, _retval);
-}
-
-NS_IMETHODIMP
-ReadOp::
-MemoryOutputStream::Flush()
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ReadOp::
-MemoryOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
-                              uint32_t* _retval)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-ReadOp::
-MemoryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
-                                  uint32_t aCount, uint32_t* _retval)
-{
-  NS_ASSERTION(mData.Length() >= mOffset, "Bad stream state!");
-
-  uint32_t maxCount = mData.Length() - mOffset;
-  if (maxCount == 0) {
-    *_retval = 0;
-    return NS_OK;
-  }
-
-  if (aCount > maxCount) {
-    aCount = maxCount;
-  }
-
-  nsresult rv = aReader(this, aClosure, mData.BeginWriting() + mOffset, 0,
-                        aCount, _retval);
-  if (NS_SUCCEEDED(rv)) {
-    NS_ASSERTION(*_retval <= aCount,
-                 "Reader should not read more than we asked it to read!");
-    mOffset += *_retval;
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ReadOp::
-MemoryOutputStream::IsNonBlocking(bool* _retval)
-{
-  *_retval = false;
-  return NS_OK;
-}
-
 WriteOp::WriteOp(FileHandle* aFileHandle,
                  const FileRequestParams& aParams)
   : CopyFileHandleOp(aFileHandle)
   , mParams(aParams.get_FileRequestWriteParams())
 {
   MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestWriteParams);
 }
 
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -9084,17 +9084,18 @@ public:
 
     mCurrentMaintenance = nullptr;
     ProcessMaintenanceQueue();
   }
 
   nsThreadPool*
   GetOrCreateThreadPool();
 
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
+    mozilla::dom::indexedDB::QuotaClient, override)
 
   mozilla::dom::quota::Client::Type
   GetType() override;
 
   nsresult
   UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) override;
 
   nsresult
@@ -20415,24 +20416,24 @@ MutableFile::CreateStream(bool aReadOnly
   PersistenceType persistenceType = mDatabase->Type();
   const nsACString& group = mDatabase->Group();
   const nsACString& origin = mDatabase->Origin();
 
   nsCOMPtr<nsISupports> result;
 
   if (aReadOnly) {
     RefPtr<FileInputStream> stream =
-      FileInputStream::Create(persistenceType, group, origin, mFile, -1, -1,
-                              nsIFileInputStream::DEFER_OPEN);
+      CreateFileInputStream(persistenceType, group, origin, mFile, -1, -1,
+                            nsIFileInputStream::DEFER_OPEN);
     result = NS_ISUPPORTS_CAST(nsIFileInputStream*, stream);
   }
   else {
     RefPtr<FileStream> stream =
-      FileStream::Create(persistenceType, group, origin, mFile, -1, -1,
-                         nsIFileStream::DEFER_OPEN);
+      CreateFileStream(persistenceType, group, origin, mFile, -1, -1,
+                       nsIFileStream::DEFER_OPEN);
     result = NS_ISUPPORTS_CAST(nsIFileStream*, stream);
   }
   if (NS_WARN_IF(!result)) {
     return nullptr;
   }
 
   return result.forget();
 }
@@ -29082,20 +29083,20 @@ FileHelper::CreateFileFromStream(nsIFile
   // Create a journal file first.
   rv = aJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Now try to copy the stream.
   RefPtr<FileOutputStream> fileOutputStream =
-    FileOutputStream::Create(mFileManager->Type(),
-                             mFileManager->Group(),
-                             mFileManager->Origin(),
-                             aFile);
+    CreateFileOutputStream(mFileManager->Type(),
+                           mFileManager->Group(),
+                           mFileManager->Origin(),
+                           aFile);
   if (NS_WARN_IF(!fileOutputStream)) {
     return NS_ERROR_FAILURE;
   }
 
   if (aCompress) {
     RefPtr<SnappyCompressOutputStream> snappyOutputStream =
       new SnappyCompressOutputStream(fileOutputStream);
 
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -96,16 +96,17 @@ DIRS += [
     'performance',
     'webbrowserpersist',
     'xhr',
     'worklet',
     'script',
     'payments',
     'websocket',
     'serviceworkers',
+    'simpledb',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['plugins/ipc/hangui']
 
 DIRS += ['presentation']
 
 TEST_DIRS += [
--- a/dom/payments/PaymentRequestData.cpp
+++ b/dom/payments/PaymentRequestData.cpp
@@ -360,68 +360,62 @@ PaymentDetails::Create(const IPCPaymentD
 
   nsCOMPtr<nsIPaymentItem> total;
   nsresult rv = PaymentItem::Create(aIPCDetails.total(), getter_AddRefs(total));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<nsIArray> displayItems;
-  if (aIPCDetails.displayItemsPassed()) {
-    nsCOMPtr<nsIMutableArray> items = do_CreateInstance(NS_ARRAY_CONTRACTID);
-    MOZ_ASSERT(items);
-    for (const IPCPaymentItem& displayItem : aIPCDetails.displayItems()) {
-      nsCOMPtr<nsIPaymentItem> item;
-      rv = PaymentItem::Create(displayItem, getter_AddRefs(item));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      rv = items->AppendElement(item);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+  nsCOMPtr<nsIMutableArray> items = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  MOZ_ASSERT(items);
+  for (const IPCPaymentItem& displayItem : aIPCDetails.displayItems()) {
+    nsCOMPtr<nsIPaymentItem> item;
+    rv = PaymentItem::Create(displayItem, getter_AddRefs(item));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
-    displayItems = items.forget();
+    rv = items->AppendElement(item);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
+  displayItems = items.forget();
 
   nsCOMPtr<nsIArray> shippingOptions;
-  if (aIPCDetails.shippingOptionsPassed()) {
-    nsCOMPtr<nsIMutableArray> options = do_CreateInstance(NS_ARRAY_CONTRACTID);
-    MOZ_ASSERT(options);
-    for (const IPCPaymentShippingOption& shippingOption : aIPCDetails.shippingOptions()) {
-      nsCOMPtr<nsIPaymentShippingOption> option;
-      rv = PaymentShippingOption::Create(shippingOption, getter_AddRefs(option));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      rv = options->AppendElement(option);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+  nsCOMPtr<nsIMutableArray> options = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  MOZ_ASSERT(options);
+  for (const IPCPaymentShippingOption& shippingOption : aIPCDetails.shippingOptions()) {
+    nsCOMPtr<nsIPaymentShippingOption> option;
+    rv = PaymentShippingOption::Create(shippingOption, getter_AddRefs(option));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
-    shippingOptions = options.forget();
+    rv = options->AppendElement(option);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
+  shippingOptions = options.forget();
 
   nsCOMPtr<nsIArray> modifiers;
-  if (aIPCDetails.modifiersPassed()) {
-    nsCOMPtr<nsIMutableArray> detailsModifiers = do_CreateInstance(NS_ARRAY_CONTRACTID);
-    MOZ_ASSERT(detailsModifiers);
-    for (const IPCPaymentDetailsModifier& modifier : aIPCDetails.modifiers()) {
-      nsCOMPtr<nsIPaymentDetailsModifier> detailsModifier;
-      rv = PaymentDetailsModifier::Create(modifier, getter_AddRefs(detailsModifier));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      rv = detailsModifiers->AppendElement(detailsModifier);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+  nsCOMPtr<nsIMutableArray> detailsModifiers = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  MOZ_ASSERT(detailsModifiers);
+  for (const IPCPaymentDetailsModifier& modifier : aIPCDetails.modifiers()) {
+    nsCOMPtr<nsIPaymentDetailsModifier> detailsModifier;
+    rv = PaymentDetailsModifier::Create(modifier, getter_AddRefs(detailsModifier));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
-    modifiers = detailsModifiers.forget();
+    rv = detailsModifiers->AppendElement(detailsModifier);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
+  modifiers = detailsModifiers.forget();
 
   nsCOMPtr<nsIPaymentDetails> details =
     new PaymentDetails(aIPCDetails.id(), total, displayItems, shippingOptions,
                        modifiers, aIPCDetails.error(),
                        aIPCDetails.shippingAddressErrors());
 
   details.forget(aDetails);
   return NS_OK;
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -173,20 +173,17 @@ ConvertDetailsInit(JSContext* aCx,
   ConvertItem(aDetails.mTotal, total);
 
   aIPCDetails = IPCPaymentDetails(id,
                                   total,
                                   displayItems,
                                   shippingOptions,
                                   modifiers,
                                   EmptyString(), // error message
-                                  EmptyString(), // shippingAddressErrors
-                                  aDetails.mDisplayItems.WasPassed(),
-                                  aDetails.mShippingOptions.WasPassed(),
-                                  aDetails.mModifiers.WasPassed());
+                                  EmptyString()); // shippingAddressErrors
   return NS_OK;
 }
 
 nsresult
 ConvertDetailsUpdate(JSContext* aCx,
                      const PaymentDetailsUpdate& aDetails,
                      IPCPaymentDetails& aIPCDetails,
                      bool aRequestShipping)
@@ -218,20 +215,17 @@ ConvertDetailsUpdate(JSContext* aCx,
   }
 
   aIPCDetails = IPCPaymentDetails(EmptyString(), // id
                                   total,
                                   displayItems,
                                   shippingOptions,
                                   modifiers,
                                   error,
-                                  shippingAddressErrors,
-                                  aDetails.mDisplayItems.WasPassed(),
-                                  aDetails.mShippingOptions.WasPassed(),
-                                  aDetails.mModifiers.WasPassed());
+                                  shippingAddressErrors);
   return NS_OK;
 }
 
 void
 ConvertOptions(const PaymentOptions& aOptions,
                IPCPaymentOptions& aIPCOption)
 {
   uint8_t shippingTypeIndex = static_cast<uint8_t>(aOptions.mShippingType);
--- a/dom/payments/ipc/PPaymentRequest.ipdl
+++ b/dom/payments/ipc/PPaymentRequest.ipdl
@@ -51,19 +51,16 @@ struct IPCPaymentDetails
 {
   nsString id;
   IPCPaymentItem total;
   IPCPaymentItem[] displayItems;
   IPCPaymentShippingOption[] shippingOptions;
   IPCPaymentDetailsModifier[] modifiers;
   nsString error;
   nsString shippingAddressErrors;
-  bool displayItemsPassed;
-  bool shippingOptionsPassed;
-  bool modifiersPassed;
 };
 
 struct IPCPaymentOptions
 {
   bool requestPayerName;
   bool requestPayerEmail;
   bool requestPayerPhone;
   bool requestShipping;
--- a/dom/payments/test/ConstructorChromeScript.js
+++ b/dom/payments/test/ConstructorChromeScript.js
@@ -40,24 +40,24 @@ function checkSimplestRequest(payRequest
   }
   if (details.totalItem.amount.currency != "USD") {
     emitTestFail("total item's currency should be 'USD'.");
   }
   if (details.totalItem.amount.value != "1.00") {
     emitTestFail("total item's value should be '1.00'.");
   }
 
-  if (details.displayItems) {
-    emitTestFail("details.displayItems should be undefined.");
+  if (details.displayItems.length !== 0) {
+    emitTestFail("details.displayItems should be an empty array.");
   }
-  if (details.modifiers) {
-    emitTestFail("details.displayItems should be undefined.");
+  if (details.modifiers.length !== 0) {
+    emitTestFail("details.modifiers should be an empty array.");
   }
-  if (details.shippingOptions) {
-    emitTestFail("details.shippingOptions should be undefined.");
+  if (details.shippingOptions.length !== 0) {
+    emitTestFail("details.shippingOptions should be an empty array.");
   }
 
   // checking the default generated PaymentOptions parameter
   const paymentOptions = payRequest.paymentOptions;
   if (paymentOptions.requestPayerName) {
     emitTestFail("requestPayerName option should be false.");
   }
   if (paymentOptions.requestPayerEmail) {
@@ -314,24 +314,24 @@ function checkNonBasicCardRequest(payReq
   }
   if (details.totalItem.amount.currency != "USD") {
     emitTestFail("total item's currency should be 'USD'.");
   }
   if (details.totalItem.amount.value != "1.00") {
     emitTestFail("total item's value should be '1.00'.");
   }
 
-  if (details.displayItems) {
-    emitTestFail("details.displayItems should be undefined.");
+  if (details.displayItems.length !== 0) {
+    emitTestFail("details.displayItems should be an zero length array.");
   }
-  if (details.modifiers) {
-    emitTestFail("details.displayItems should be undefined.");
+  if (details.displayItems.length !== 0) {
+    emitTestFail("details.modifiers should be an zero length array.");
   }
-  if (details.shippingOptions) {
-    emitTestFail("details.shippingOptions should be undefined.");
+  if (details.displayItems.length !== 0) {
+    emitTestFail("details.shippingOptions should be an zero length array.");
   }
 
   // checking the default generated PaymentOptions parameter
   const paymentOptions = payRequest.paymentOptions;
   if (paymentOptions.requestPayerName) {
     emitTestFail("requestPayerName option should be false.");
   }
   if (paymentOptions.requestPayerEmail) {
--- a/dom/payments/test/browser_payment_in_different_tabs.js
+++ b/dom/payments/test/browser_payment_in_different_tabs.js
@@ -1,31 +1,39 @@
 "use strict";
 
 // kTestRoot is from head.js
 const kTestPage = kTestRoot + "simple_payment_request.html";
-
-add_task(async function() {
+const TABS_TO_OPEN = 5;
+add_task(async () => {
   Services.prefs.setBoolPref("dom.payments.request.enabled", true);
-  await BrowserTestUtils.withNewTab(kTestPage,
-    async function(browser) {
-      await BrowserTestUtils.withNewTab(kTestPage,
-        function(browser) {
-          const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"].getService(Ci.nsIPaymentRequestService);
-          ok(paymentSrv, "Fail to get PaymentRequestService.");
-
-          const paymentEnum = paymentSrv.enumerate();
-          ok(paymentEnum.hasMoreElements(), "PaymentRequestService should have at least one payment request.");
-          let tabIds = [];
-          while (paymentEnum.hasMoreElements()) {
-            let payment = paymentEnum.getNext().QueryInterface(Ci.nsIPaymentRequest);
-            ok(payment, "Fail to get existing payment request.");
-            checkSimplePayment(payment);
-            tabIds.push(payment.tabId);
-          }
-          is(tabIds.length, 2, "TabId array length should be 2.");
-          ok(tabIds[0] != tabIds[1], "TabIds should be different.");
-          Services.prefs.setBoolPref("dom.payments.request.enabled", false);
-        }
-      );
-    }
+  const tabs = [];
+  const options = {
+    gBrowser: Services.wm.getMostRecentWindow("navigator:browser").gBrowser,
+    url: kTestPage,
+  };
+  for (let i = 0; i < TABS_TO_OPEN; i++) {
+    const tab = await BrowserTestUtils.openNewForegroundTab(options);
+    tabs.push(tab);
+  }
+  const paymentSrv = Cc[
+    "@mozilla.org/dom/payments/payment-request-service;1"
+  ].getService(Ci.nsIPaymentRequestService);
+  ok(paymentSrv, "Fail to get PaymentRequestService.");
+  const paymentEnum = paymentSrv.enumerate();
+  ok(
+    paymentEnum.hasMoreElements(),
+    "PaymentRequestService should have at least one payment request."
   );
+  const payments = new Set();
+  while (paymentEnum.hasMoreElements()) {
+    const payment = paymentEnum.getNext().QueryInterface(Ci.nsIPaymentRequest);
+    ok(payment, "Fail to get existing payment request.");
+    checkSimplePayment(payment);
+    payments.add(payment);
+  }
+  is(payments.size, TABS_TO_OPEN, `Should be ${TABS_TO_OPEN} unique objects.`);
+  tabs.forEach(async tab => {
+    await TestUtils.waitForTick();
+    BrowserTestUtils.removeTab(tab);
+  });
+  Services.prefs.setBoolPref("dom.payments.request.enabled", false);
 });
--- a/dom/payments/test/head.js
+++ b/dom/payments/test/head.js
@@ -12,19 +12,19 @@ function checkSimplePayment(aSimplePayme
 
   // checking the passed PaymentDetails parameter
   const details = aSimplePayment.paymentDetails;
   is(details.id, "simple details", "details.id should be 'simple details'.");
   is(details.totalItem.label, "Donation", "total item's label should be 'Donation'.");
   is(details.totalItem.amount.currency, "USD", "total item's currency should be 'USD'.");
   is(details.totalItem.amount.value, "55.00", "total item's value should be '55.00'.");
 
-  ok(!details.displayItems, "details.displayItems should be undefined.");
-  ok(!details.modifiers, "details.modifiers should be undefined.");
-  ok(!details.shippingOptions, "details.shippingOptions should be undefined.");
+  is(details.displayItems.length, 0, "details.displayItems should be a zero length array.");
+  is(details.modifiers.length, 0, "details.modifiers should be a zero length array.");
+  is(details.shippingOptions.length, 0, "details.shippingOptions should be a zero length array.");
 
   // checking the default generated PaymentOptions parameter
   const paymentOptions = aSimplePayment.paymentOptions;
   ok(!paymentOptions.requestPayerName, "payerName option should be false");
   ok(!paymentOptions.requestPayerEmail, "payerEmail option should be false");
   ok(!paymentOptions.requestPayerPhone, "payerPhone option should be false");
   ok(!paymentOptions.requestShipping, "requestShipping option should be false");
   is(paymentOptions.shippingType, "shipping", "shippingType option should be 'shipping'");
--- a/dom/payments/test/simple_payment_request.html
+++ b/dom/payments/test/simple_payment_request.html
@@ -1,47 +1,42 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Payment Request Testing</title>
-    <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
-    <meta content="utf-8" http-equiv="encoding">
-  </head>
-  <body>
-    <h1>simple payment request.html</h1>
-    <script type="text/javascript">
-
-    const supportedInstruments = [{
-      supportedMethods: "basic-card",
-    }];
-    const details = {
-      id: "simple details",
-      total: {
-        label: "Donation",
-        amount: { currency: "USD", value: "55.00" }
-      },
-    };
+<!doctype html>
+<meta charset="utf-8">
+<title>Payment Request Testing</title>
+<script>
+const methods = [
+  {
+    supportedMethods: "basic-card",
+  },
+];
+const details = {
+  id: "simple details",
+  total: {
+    label: "Donation",
+    amount: { currency: "USD", value: "55.00" },
+  },
+};
 
-    try {
-      const payRequest = new PaymentRequest(supportedInstruments, details);
-      window.onmessage = async (e) => {
-        if (e.data === "show PaymentRequest") {
-          if (payRequest) {
-            payRequest.show();
-            window.parent.postMessage("successful", '*');
-          } else {
-            window.parent.postMessage("PaymentRequest does not exist", "*");
-          }
-        }
-      }
+let request;
+let msg = "successful";
+try {
+  request = new PaymentRequest(methods, details);
+} catch (err) {
+  msg = err.name;
+}
+window.parent.postMessage(msg, "*");
 
-      if(window.parent) {
-        window.parent.postMessage("successful", '*');
-      }
-    } catch(err) {
-      if(window.parent) {
-        window.parent.postMessage(err.name, '*');
-      }
+if (request) {
+  window.onmessage = async ({ data: action }) => {
+    switch (action) {
+      case "show PaymentRequest":
+        const responsePromise = request.show();
+        window.parent.postMessage("successful", "*");
+        try {
+          await responsePromise;
+        } catch (err) { /* graceful abort */ }
+        break;
+      default:
+        window.parent.postMessage(`fail - unknown postmessage action: ${action}`, "*");
     }
-
-    </script>
-  </body>
-</html>
+  };
+}
+</script>
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -31,16 +31,17 @@
 #include "mozilla/CondVar.h"
 #include "mozilla/dom/PContent.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/quota/PQuotaParent.h"
 #include "mozilla/dom/quota/PQuotaRequestParent.h"
 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
+#include "mozilla/dom/simpledb/ActorsParent.h"
 #include "mozilla/dom/StorageActivityService.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
@@ -3595,26 +3596,31 @@ QuotaManager::Init(const nsAString& aBas
 
   // Make a timer here to avoid potential failures later. We don't actually
   // initialize the timer until shutdown.
   mShutdownTimer = NS_NewTimer();
   if (NS_WARN_IF(!mShutdownTimer)) {
     return NS_ERROR_FAILURE;
   }
 
-  static_assert(Client::IDB == 0 && Client::ASMJS == 1 && Client::DOMCACHE == 2 &&
-                Client::TYPE_MAX == 3, "Fix the registration!");
+  static_assert(Client::IDB == 0 &&
+                Client::ASMJS == 1 &&
+                Client::DOMCACHE == 2 &&
+                Client::SDB == 3 &&
+                Client::TYPE_MAX == 4,
+                "Fix the registration!");
 
   MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX,
              "Should be using an auto array with correct capacity!");
 
   // Register clients.
   mClients.AppendElement(indexedDB::CreateQuotaClient());
   mClients.AppendElement(asmjscache::CreateClient());
   mClients.AppendElement(cache::CreateQuotaClient());
+  mClients.AppendElement(simpledb::CreateQuotaClient());
 
   return NS_OK;
 }
 
 void
 QuotaManager::Shutdown()
 {
   AssertIsOnOwningThread();
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -14,16 +14,17 @@
 #include "PersistenceType.h"
 
 class nsIFile;
 class nsIRunnable;
 
 #define IDB_DIRECTORY_NAME "idb"
 #define ASMJSCACHE_DIRECTORY_NAME "asmjs"
 #define DOMCACHE_DIRECTORY_NAME "cache"
+#define SDB_DIRECTORY_NAME "sdb"
 #define LS_DIRECTORY_NAME "ls"
 
 BEGIN_QUOTA_NAMESPACE
 
 class QuotaManager;
 class UsageInfo;
 
 // An abstract interface for quota manager clients.
@@ -37,16 +38,17 @@ public:
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   enum Type {
     IDB = 0,
     //LS,
     //APPCACHE,
     ASMJS,
     DOMCACHE,
+    SDB,
     TYPE_MAX
   };
 
   virtual Type
   GetType() = 0;
 
   static nsresult
   TypeToText(Type aType, nsAString& aText)
@@ -59,16 +61,20 @@ public:
       case ASMJS:
         aText.AssignLiteral(ASMJSCACHE_DIRECTORY_NAME);
         break;
 
       case DOMCACHE:
         aText.AssignLiteral(DOMCACHE_DIRECTORY_NAME);
         break;
 
+      case SDB:
+        aText.AssignLiteral(SDB_DIRECTORY_NAME);
+        break;
+
       case TYPE_MAX:
       default:
         MOZ_ASSERT_UNREACHABLE("Bad id value!");
         return NS_ERROR_UNEXPECTED;
     }
 
     return NS_OK;
   }
@@ -80,16 +86,19 @@ public:
       aType = IDB;
     }
     else if (aText.EqualsLiteral(ASMJSCACHE_DIRECTORY_NAME)) {
       aType = ASMJS;
     }
     else if (aText.EqualsLiteral(DOMCACHE_DIRECTORY_NAME)) {
       aType = DOMCACHE;
     }
+    else if (aText.EqualsLiteral(SDB_DIRECTORY_NAME)) {
+      aType = SDB;
+    }
     else {
       return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
   }
 
   // Methods which are called on the IO thread.
--- a/dom/quota/FileStreams.cpp
+++ b/dom/quota/FileStreams.cpp
@@ -4,17 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FileStreams.h"
 
 #include "QuotaManager.h"
 #include "prio.h"
 
-USING_QUOTA_NAMESPACE
+BEGIN_QUOTA_NAMESPACE
 
 template <class FileStreamBase>
 NS_IMETHODIMP
 FileQuotaStream<FileStreamBase>::SetEOF()
 {
   nsresult rv = FileStreamBase::SetEOF();
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -86,44 +86,56 @@ FileQuotaStreamWithWrite<FileStreamBase>
 
   rv = FileStreamBase::Write(aBuf, aCount, _retval);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 already_AddRefed<FileInputStream>
-FileInputStream::Create(PersistenceType aPersistenceType,
-                        const nsACString& aGroup, const nsACString& aOrigin,
-                        nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
-                        int32_t aBehaviorFlags)
+CreateFileInputStream(PersistenceType aPersistenceType,
+                      const nsACString& aGroup,
+                      const nsACString& aOrigin,
+                      nsIFile* aFile,
+                      int32_t aIOFlags,
+                      int32_t aPerm,
+                      int32_t aBehaviorFlags)
 {
   RefPtr<FileInputStream> stream =
     new FileInputStream(aPersistenceType, aGroup, aOrigin);
   nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags);
   NS_ENSURE_SUCCESS(rv, nullptr);
   return stream.forget();
 }
 
 already_AddRefed<FileOutputStream>
-FileOutputStream::Create(PersistenceType aPersistenceType,
-                         const nsACString& aGroup, const nsACString& aOrigin,
-                         nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
-                         int32_t aBehaviorFlags)
+CreateFileOutputStream(PersistenceType aPersistenceType,
+                       const nsACString& aGroup,
+                       const nsACString& aOrigin,
+                       nsIFile* aFile,
+                       int32_t aIOFlags,
+                       int32_t aPerm,
+                       int32_t aBehaviorFlags)
 {
   RefPtr<FileOutputStream> stream =
     new FileOutputStream(aPersistenceType, aGroup, aOrigin);
   nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags);
   NS_ENSURE_SUCCESS(rv, nullptr);
   return stream.forget();
 }
 
 already_AddRefed<FileStream>
-FileStream::Create(PersistenceType aPersistenceType, const nsACString& aGroup,
-                   const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags,
-                   int32_t aPerm, int32_t aBehaviorFlags)
+CreateFileStream(PersistenceType aPersistenceType,
+                 const nsACString& aGroup,
+                 const nsACString& aOrigin,
+                 nsIFile* aFile,
+                 int32_t aIOFlags,
+                 int32_t aPerm,
+                 int32_t aBehaviorFlags)
 {
   RefPtr<FileStream> stream =
     new FileStream(aPersistenceType, aGroup, aOrigin);
   nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags);
   NS_ENSURE_SUCCESS(rv, nullptr);
   return stream.forget();
 }
+
+END_QUOTA_NAMESPACE
--- a/dom/quota/FileStreams.h
+++ b/dom/quota/FileStreams.h
@@ -59,72 +59,84 @@ protected:
 };
 
 class FileInputStream : public FileQuotaStream<nsFileInputStream>
 {
 public:
   NS_INLINE_DECL_REFCOUNTING_INHERITED(FileInputStream,
                                        FileQuotaStream<nsFileInputStream>)
 
-  static already_AddRefed<FileInputStream>
-  Create(PersistenceType aPersistenceType, const nsACString& aGroup,
-         const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1,
-         int32_t aPerm = -1, int32_t aBehaviorFlags = 0);
-
-private:
   FileInputStream(PersistenceType aPersistenceType, const nsACString& aGroup,
                   const nsACString& aOrigin)
   : FileQuotaStream<nsFileInputStream>(aPersistenceType, aGroup, aOrigin)
   { }
 
+private:
   virtual ~FileInputStream() {
     Close();
   }
 };
 
 class FileOutputStream : public FileQuotaStreamWithWrite<nsFileOutputStream>
 {
 public:
   NS_INLINE_DECL_REFCOUNTING_INHERITED(FileOutputStream,
                                        FileQuotaStreamWithWrite<nsFileOutputStream>);
 
-  static already_AddRefed<FileOutputStream>
-  Create(PersistenceType aPersistenceType, const nsACString& aGroup,
-         const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1,
-         int32_t aPerm = -1, int32_t aBehaviorFlags = 0);
-
-private:
   FileOutputStream(PersistenceType aPersistenceType, const nsACString& aGroup,
                    const nsACString& aOrigin)
   : FileQuotaStreamWithWrite<nsFileOutputStream>(aPersistenceType, aGroup,
                                                  aOrigin)
   { }
 
+private:
   virtual ~FileOutputStream() {
     Close();
   }
 };
 
 class FileStream : public FileQuotaStreamWithWrite<nsFileStream>
 {
 public:
   NS_INLINE_DECL_REFCOUNTING_INHERITED(FileStream,
                                        FileQuotaStreamWithWrite<nsFileStream>)
 
-  static already_AddRefed<FileStream>
-  Create(PersistenceType aPersistenceType, const nsACString& aGroup,
-         const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1,
-         int32_t aPerm = -1, int32_t aBehaviorFlags = 0);
-
-private:
   FileStream(PersistenceType aPersistenceType, const nsACString& aGroup,
              const nsACString& aOrigin)
   : FileQuotaStreamWithWrite<nsFileStream>(aPersistenceType, aGroup, aOrigin)
   { }
 
+private:
   virtual ~FileStream() {
     Close();
   }
 };
 
+already_AddRefed<FileInputStream>
+CreateFileInputStream(PersistenceType aPersistenceType,
+                      const nsACString& aGroup,
+                      const nsACString& aOrigin,
+                      nsIFile* aFile,
+                      int32_t aIOFlags = -1,
+                      int32_t aPerm = -1,
+                      int32_t aBehaviorFlags = 0);
+
+already_AddRefed<FileOutputStream>
+CreateFileOutputStream(PersistenceType aPersistenceType,
+                       const nsACString& aGroup,
+                       const nsACString& aOrigin,
+                       nsIFile* aFile,
+                       int32_t aIOFlags = -1,
+                       int32_t aPerm = -1,
+                       int32_t aBehaviorFlags = 0);
+
+already_AddRefed<FileStream>
+CreateFileStream(PersistenceType aPersistenceType,
+                 const nsACString& aGroup,
+                 const nsACString& aOrigin,
+                 nsIFile* aFile,
+                 int32_t aIOFlags = -1,
+                 int32_t aPerm = -1,
+                 int32_t aBehaviorFlags = 0);
+
 END_QUOTA_NAMESPACE
 
 #endif /* mozilla_dom_quota_filestreams_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/quota/MemoryOutputStream.cpp
@@ -0,0 +1,100 @@
+/* -*- 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 "MemoryOutputStream.h"
+
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+// static
+already_AddRefed<MemoryOutputStream>
+MemoryOutputStream::Create(uint64_t aSize)
+{
+  MOZ_ASSERT(aSize, "Passed zero size!");
+
+  if (NS_WARN_IF(aSize > UINT32_MAX)) {
+    return nullptr;
+  }
+
+  RefPtr<MemoryOutputStream> stream = new MemoryOutputStream();
+
+  char* dummy;
+  uint32_t length = stream->mData.GetMutableData(&dummy, aSize, fallible);
+  if (NS_WARN_IF(length != aSize)) {
+    return nullptr;
+  }
+
+  return stream.forget();
+}
+
+NS_IMPL_ISUPPORTS(MemoryOutputStream, nsIOutputStream)
+
+NS_IMETHODIMP
+MemoryOutputStream::Close()
+{
+  mData.Truncate(mOffset);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+  return WriteSegments(NS_CopySegmentToBuffer, (char*)aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+MemoryOutputStream::Flush()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+                              uint32_t* _retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MemoryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+                                  uint32_t aCount, uint32_t* _retval)
+{
+  MOZ_ASSERT(mData.Length() >= mOffset, "Bad stream state!");
+
+  uint32_t maxCount = mData.Length() - mOffset;
+  if (maxCount == 0) {
+    *_retval = 0;
+    return NS_OK;
+  }
+
+  if (aCount > maxCount) {
+    aCount = maxCount;
+  }
+
+  nsresult rv = aReader(this, aClosure, mData.BeginWriting() + mOffset, 0,
+                        aCount, _retval);
+  if (NS_SUCCEEDED(rv)) {
+    MOZ_ASSERT(*_retval <= aCount,
+               "Reader should not read more than we asked it to read!");
+    mOffset += *_retval;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryOutputStream::IsNonBlocking(bool* _retval)
+{
+  *_retval = false;
+  return NS_OK;
+}
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/quota/MemoryOutputStream.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_MemoryOutputStream_h
+#define mozilla_dom_quota_MemoryOutputStream_h
+
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+// An output stream so you can read your potentially-async input stream into
+// a contiguous buffer in the form of an nsCString using NS_AsyncCopy.
+// Back when streams were more synchronous and people didn't know blocking I/O
+// was bad, if you wanted to read a stream into a flat buffer, you could use
+// NS_ReadInputStreamToString/NS_ReadInputStreamToBuffer.  But those don't work
+// with async streams. This can be used to replace hand-rolled Read/AsyncWait()
+// loops.  Because you specify the expected size up front, the nsCString buffer
+// is pre-allocated so wasteful reallocations can be avoided.  However,
+// nsCString currently may over-allocate and this can be problematic on 32-bit
+// windows until we're rid of that build configuration.
+class MemoryOutputStream final
+  : public nsIOutputStream
+{
+  nsCString mData;
+  uint64_t mOffset;
+
+public:
+  static already_AddRefed<MemoryOutputStream>
+  Create(uint64_t aSize);
+
+  const nsCString&
+  Data() const
+  {
+    return mData;
+  }
+
+private:
+  MemoryOutputStream()
+  : mOffset(0)
+  { }
+
+  virtual ~MemoryOutputStream()
+  { }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOUTPUTSTREAM
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_quota_MemoryOutputStream_h */
--- a/dom/quota/StorageManager.cpp
+++ b/dom/quota/StorageManager.cpp
@@ -14,16 +14,18 @@
 #include "mozilla/ErrorResult.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/Telemetry.h"
 #include "nsContentPermissionHelper.h"
 #include "nsIQuotaCallbacks.h"
 #include "nsIQuotaRequests.h"
 #include "nsPIDOMWindow.h"
 
+using namespace mozilla::dom::quota;
+
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 // This class is used to get quota usage, request persist and check persisted
 // status callbacks.
 class RequestResolver final
--- a/dom/quota/moz.build
+++ b/dom/quota/moz.build
@@ -10,16 +10,20 @@ with Files("**"):
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/xpcshell.ini'
 ]
 
+TEST_HARNESS_FILES.xpcshell.dom.quota.test += [
+    'test/head-shared.js',
+]
+
 XPIDL_SOURCES += [
     'nsIQuotaCallbacks.idl',
     'nsIQuotaManagerService.idl',
     'nsIQuotaRequests.idl',
     'nsIQuotaResults.idl',
 ]
 
 XPIDL_MODULE = 'dom_quota'
@@ -27,30 +31,32 @@ XPIDL_MODULE = 'dom_quota'
 EXPORTS.mozilla.dom += [
   'StorageManager.h',
 ]
 
 EXPORTS.mozilla.dom.quota += [
     'ActorsParent.h',
     'Client.h',
     'FileStreams.h',
+    'MemoryOutputStream.h',
     'OriginScope.h',
     'PersistenceType.h',
     'QuotaCommon.h',
     'QuotaManager.h',
     'QuotaManagerService.h',
     'QuotaObject.h',
     'SerializationHelpers.h',
     'UsageInfo.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActorsChild.cpp',
     'ActorsParent.cpp',
     'FileStreams.cpp',
+    'MemoryOutputStream.cpp',
     'QuotaManagerService.cpp',
     'QuotaRequests.cpp',
     'QuotaResults.cpp',
     'StorageManager.cpp',
 ]
 
 IPDL_SOURCES += [
     'PQuota.ipdl',
--- a/dom/quota/test/browser.ini
+++ b/dom/quota/test/browser.ini
@@ -1,10 +1,12 @@
 [DEFAULT]
 skip-if = (buildapp != "browser")
 support-files =
-  head.js
   browserHelpers.js
   browser_permissionsPrompt.html
+  head-shared.js
+  head.js
 
 [browser_permissionsPromptAllow.js]
 [browser_permissionsPromptDeny.js]
 [browser_permissionsPromptUnknown.js]
+[browser_simpledb.js]
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/browser_simpledb.js
@@ -0,0 +1,50 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(async function testSimpleDB() {
+  const name = "data";
+  const bufferSize = 100;
+
+  await SpecialPowers.pushPrefEnv({ set: [[ "dom.simpleDB.enabled", true ]] });
+
+  let database = getSimpleDatabase();
+
+  let request = database.open("data");
+  await requestFinished(request);
+
+  let buffer1 = getRandomBuffer(bufferSize);
+
+  request = database.write(buffer1);
+  await requestFinished(request);
+
+  request = database.seek(0);
+  await requestFinished(request);
+
+  request = database.read(bufferSize);
+  let result = await requestFinished(request);
+
+  let buffer2 = result.getAsArrayBuffer();
+
+  ok(compareBuffers(buffer1, buffer2), "Buffers equal.");
+
+  let database2 = getSimpleDatabase();
+
+  try {
+    request = database2.open(name);
+    await requestFinished(request);
+    ok(false, "Should have thrown!");
+  } catch(ex) {
+    ok(request.resultCode == NS_ERROR_STORAGE_BUSY, "Good result code.");
+  }
+
+  request = database.close();
+  await requestFinished(request);
+
+  request = database2.open(name);
+  await requestFinished(request);
+
+  request = database2.close();
+  await requestFinished(request);
+});
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/head-shared.js
@@ -0,0 +1,37 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function getBuffer(size)
+{
+  let buffer = new ArrayBuffer(size);
+  is(buffer.byteLength, size, "Correct byte length");
+  return buffer;
+}
+
+function getRandomBuffer(size)
+{
+  let buffer = getBuffer(size);
+  let view = new Uint8Array(buffer);
+  for (let i = 0; i < size; i++) {
+    view[i] = parseInt(Math.random() * 255)
+  }
+  return buffer;
+}
+
+function compareBuffers(buffer1, buffer2)
+{
+  if (buffer1.byteLength != buffer2.byteLength) {
+    return false;
+  }
+
+  let view1 = buffer1 instanceof Uint8Array ? buffer1 : new Uint8Array(buffer1);
+  let view2 = buffer2 instanceof Uint8Array ? buffer2 : new Uint8Array(buffer2);
+  for (let i = 0; i < buffer1.byteLength; i++) {
+    if (view1[i] != view2[i]) {
+      return false;
+    }
+  }
+  return true;
+}
--- a/dom/quota/test/head.js
+++ b/dom/quota/test/head.js
@@ -1,13 +1,15 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+const NS_ERROR_STORAGE_BUSY = Cr.NS_ERROR_STORAGE_BUSY;
+
 var gActiveListeners = {};
 
 // These event (un)registration handlers only work for one window, DONOT use
 // them with multiple windows.
 
 function registerPopupEventHandler(eventName, callback, win)
 {
   if (!win) {
@@ -131,8 +133,42 @@ function getPermission(url, permission)
   let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(Ci.nsIScriptSecurityManager);
   let principal = ssm.createCodebasePrincipal(uri, {});
 
   return Cc["@mozilla.org/permissionmanager;1"]
            .getService(Ci.nsIPermissionManager)
            .testPermissionFromPrincipal(principal, permission);
 }
+
+function getCurrentPrincipal()
+{
+  return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+}
+
+function getSimpleDatabase(principal)
+{
+  let connection = Cc["@mozilla.org/dom/sdb-connection;1"]
+    .createInstance(Ci.nsISDBConnection);
+
+  if (!principal) {
+    principal = getCurrentPrincipal();
+  }
+
+  connection.init(principal);
+
+  return connection;
+}
+
+function requestFinished(request) {
+  return new Promise(function(resolve, reject) {
+    request.callback = function(request) {
+      if (request.resultCode == Cr.NS_OK) {
+        resolve(request.result);
+      } else {
+        reject(request.resultCode);
+      }
+    }
+  });
+}
+
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/dom/quota/test/head-shared.js", this);
--- a/dom/quota/test/helpers.js
+++ b/dom/quota/test/helpers.js
@@ -1,13 +1,15 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+const NS_ERROR_STORAGE_BUSY = SpecialPowers.Cr.NS_ERROR_STORAGE_BUSY;
+
 var testGenerator = testSteps();
 
 function clearAllDatabases(callback)
 {
   let qms = SpecialPowers.Services.qms;
   let principal = SpecialPowers.wrap(document).nodePrincipal;
   let request = qms.clearStoragesForPrincipal(principal);
   let cb = SpecialPowers.wrapCallback(callback);
@@ -42,16 +44,17 @@ function* testHarnessSteps()
 
   info("Pushing preferences");
 
   SpecialPowers.pushPrefEnv(
     {
       "set": [
         ["dom.storageManager.enabled", true],
         ["dom.storageManager.prompt.testing", true],
+        ["dom.simpleDB.enabled", true],
       ]
     },
     nextTestHarnessStep
   );
   yield undefined;
 
   info("Clearing old databases");
 
@@ -133,16 +136,22 @@ function* testHarnessSteps()
          disableWorkerTest);
   } else {
     todo(false,
          "Skipping test in a worker because it's not structured properly");
   }
 
   info("Running test in main thread");
 
+  let script = document.createElement("script");
+  script.src = "head-shared.js";
+  script.onload = nextTestHarnessStep;
+  document.head.appendChild(script);
+  yield undefined;
+
   // Now run the test script in the main thread.
   testGenerator.next();
 
   yield undefined;
 }
 
 if (!window.runTest) {
   window.runTest = function()
@@ -299,8 +308,59 @@ function workerScript()
       default:
         throw new Error("Received a bad message from parent: " +
                         JSON.stringify(message));
     }
   };
 
   self.postMessage({ op: "ready" });
 }
+
+// SimpleDB connections and SpecialPowers wrapping:
+//
+// SpecialPowers provides a SpecialPowersHandler Proxy mechanism that lets our
+// content-privileged code borrow its chrome-privileged principal to access
+// things we shouldn't be able to access.  The proxies wrap their returned
+// values, so once we have something wrapped we can rely on returned objects
+// being wrapped as well.  The proxy will also automatically unwrap wrapped
+// arguments we pass in.  However, we need to invoke wrapCallback on callback
+// functions so that the arguments they receive will be wrapped because the
+// proxy does not automatically wrap content-privileged functions.
+//
+// Our use of (wrapped) SpecialPowers.Cc results in getSimpleDatabase()
+// producing a wrapped nsISDBConnection instance.  The nsISDBResult instances
+// exposed on the (wrapped) nsISDBRequest are also wrapped, so our
+// requestFinished helper wraps the results in helper objects that behave the
+// same as the result, automatically unwrapping the wrapped array/arraybuffer
+// results.
+
+function getSimpleDatabase()
+{
+  let connection = SpecialPowers.Cc["@mozilla.org/dom/sdb-connection;1"]
+    .createInstance(SpecialPowers.Ci.nsISDBConnection);
+
+  let principal = SpecialPowers.wrap(document).nodePrincipal;
+
+  connection.init(principal);
+
+  return connection;
+}
+
+function* requestFinished(request) {
+  request.callback = SpecialPowers.wrapCallback(continueToNextStepSync);
+  yield undefined;
+  if (request.resultCode == SpecialPowers.Cr.NS_OK) {
+    let result = request.result;
+    if (SpecialPowers.call_Instanceof(result, SpecialPowers.Ci.nsISDBResult)) {
+      let wrapper = {};
+      for (let i in result) {
+        if (typeof result[i] == "function") {
+          wrapper[i] = SpecialPowers.unwrap(result[i]);
+        } else {
+          wrapper[i] = result[i];
+        }
+      }
+      return wrapper;
+    }
+    return result;
+  }
+  throw request.resultCode;
+}
--- a/dom/quota/test/mochitest.ini
+++ b/dom/quota/test/mochitest.ini
@@ -1,17 +1,20 @@
 # 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/.
 
 [DEFAULT]
 support-files =
+  head-shared.js
   helpers.js
+  unit/test_simpledb.js
   unit/test_storage_manager_persist_allow.js
   unit/test_storage_manager_persist_deny.js
   unit/test_storage_manager_persisted.js
 
+[test_simpledb.html]
 [test_storage_manager_persist_allow.html]
 scheme=https
 [test_storage_manager_persist_deny.html]
 scheme=https
 [test_storage_manager_persisted.html]
 scheme=https
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/test_simpledb.html
@@ -0,0 +1,19 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>SimpleDB Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript" src="unit/test_simpledb.js"></script>
+  <script type="text/javascript" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/quota/test/unit/head.js
+++ b/dom/quota/test/unit/head.js
@@ -1,25 +1,26 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const NS_OK = Cr.NS_OK;
 const NS_ERROR_FAILURE = Cr.NS_ERROR_FAILURE;
 const NS_ERROR_UNEXPECTED = Cr.NS_ERROR_UNEXPECTED;
+const NS_ERROR_STORAGE_BUSY = Cr.NS_ERROR_STORAGE_BUSY;
 
 function is(a, b, msg)
 {
-  Assert.equal(a, b, Components.stack.caller);
+  Assert.equal(a, b, msg);
 }
 
 function ok(cond, msg)
 {
-  Assert.ok(!!cond, Components.stack.caller);
+  Assert.ok(!!cond, msg);
 }
 
 function run_test()
 {
   runTest();
 };
 
 if (!this.runTest) {
@@ -60,21 +61,23 @@ function continueToNextStep()
 function continueToNextStepSync()
 {
   testGenerator.next();
 }
 
 function enableTesting()
 {
   SpecialPowers.setBoolPref("dom.quotaManager.testing", true);
+  SpecialPowers.setBoolPref("dom.simpleDB.enabled", true);
 }
 
 function resetTesting()
 {
   SpecialPowers.clearUserPref("dom.quotaManager.testing");
+  SpecialPowers.clearUserPref("dom.simpleDB.enabled");
 }
 
 function init(callback)
 {
   let request = SpecialPowers._getQuotaManager().init();
   request.callback = callback;
 
   return request;
@@ -224,32 +227,16 @@ function getRelativeFile(relativePath)
   let file = profileDir.clone();
   relativePath.split('/').forEach(function(component) {
     file.append(component);
   });
 
   return file;
 }
 
-function compareBuffers(buffer1, buffer2)
-{
-  if (buffer1.byteLength != buffer2.byteLength) {
-    return false;
-  }
-
-  let view1 = buffer1 instanceof Uint8Array ? buffer1 : new Uint8Array(buffer1);
-  let view2 = buffer2 instanceof Uint8Array ? buffer2 : new Uint8Array(buffer2);
-  for (let i = 0; i < buffer1.byteLength; i++) {
-    if (view1[i] != view2[i]) {
-      return false;
-    }
-  }
-  return true;
-}
-
 function getPersistedFromMetadata(readBuffer)
 {
   const persistedPosition = 8; // Persisted state is stored in the 9th byte
   let view =
     readBuffer instanceof Uint8Array ? readBuffer : new Uint8Array(readBuffer);
 
   return !!view[persistedPosition];
 }
@@ -287,16 +274,44 @@ function getPrincipal(url)
   let uri = Cc["@mozilla.org/network/io-service;1"]
               .getService(Ci.nsIIOService)
               .newURI(url);
   let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(Ci.nsIScriptSecurityManager);
   return ssm.createCodebasePrincipal(uri, {});
 }
 
+function getCurrentPrincipal()
+{
+  return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+}
+
+function getSimpleDatabase(principal)
+{
+  let connection = Cc["@mozilla.org/dom/sdb-connection;1"]
+    .createInstance(Ci.nsISDBConnection);
+
+  if (!principal) {
+    principal = getCurrentPrincipal();
+  }
+
+  connection.init(principal);
+
+  return connection;
+}
+
+function* requestFinished(request) {
+  request.callback = continueToNextStepSync;
+  yield undefined;
+  if (request.resultCode == NS_OK) {
+    return request.result;
+  }
+  throw request.resultCode;
+}
+
 var SpecialPowers = {
   getBoolPref: function(prefName) {
     return this._getPrefs().getBoolPref(prefName);
   },
 
   setBoolPref: function(prefName, value) {
     this._getPrefs().setBoolPref(prefName, value);
   },
@@ -311,8 +326,20 @@ var SpecialPowers = {
     return prefService.getBranch(null);
   },
 
   _getQuotaManager: function() {
     return Cc["@mozilla.org/dom/quota-manager-service;1"]
              .getService(Ci.nsIQuotaManagerService);
   },
 };
+
+function loadSubscript(path)
+{
+  let file = do_get_file(path, false);
+  let uri = Cc["@mozilla.org/network/io-service;1"]
+    .getService(Ci.nsIIOService).newFileURI(file);
+  let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+    .getService(Ci.mozIJSSubScriptLoader);
+  scriptLoader.loadSubScript(uri.spec);
+}
+
+loadSubscript("../head-shared.js");
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_simpledb.js
@@ -0,0 +1,55 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var disableWorkerTest = "SimpleDB doesn't work in workers yet";
+
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  const name = "data";
+  const bufferSize = 100;
+
+  let database = getSimpleDatabase();
+
+  let request = database.open(name);
+  yield* requestFinished(request);
+
+  let buffer1 = getRandomBuffer(bufferSize);
+
+  request = database.write(buffer1);
+  yield* requestFinished(request);
+
+  request = database.seek(0);
+  yield* requestFinished(request);
+
+  request = database.read(bufferSize);
+  let result = yield* requestFinished(request);
+
+  let buffer2 = result.getAsArrayBuffer();
+
+  ok(compareBuffers(buffer1, buffer2), "Buffers equal.");
+
+  let database2 = getSimpleDatabase();
+
+  try {
+    request = database2.open(name);
+    yield* requestFinished(request);
+    ok(false, "Should have thrown!");
+  } catch(ex) {
+    ok(request.resultCode == NS_ERROR_STORAGE_BUSY, "Good result code.");
+  }
+
+  request = database.close();
+  yield* requestFinished(request);
+
+  request = database2.open(name);
+  yield* requestFinished(request);
+
+  request = database2.close();
+  yield* requestFinished(request);
+
+  finishTest();
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -27,12 +27,13 @@ skip-if = release_or_beta
 [test_getUsage.js]
 [test_idbSubdirUpgrade.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_originAttributesUpgrade.js]
 [test_persist.js]
 [test_removeAppsUpgrade.js]
 [test_removeLocalStorage.js]
+[test_simpledb.js]
 [test_storagePersistentUpgrade.js]
 [test_tempMetadataCleanup.js]
 [test_unknownFiles.js]
 [test_version2_1upgrade.js]
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/ActorsChild.cpp
@@ -0,0 +1,243 @@
+/* -*- 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 "ActorsChild.h"
+
+#include "nsVariant.h"
+#include "SDBConnection.h"
+#include "SDBRequest.h"
+#include "SDBResults.h"
+
+namespace mozilla {
+namespace dom {
+
+/*******************************************************************************
+ * SDBConnectionChild
+ ******************************************************************************/
+
+SDBConnectionChild::SDBConnectionChild(SDBConnection* aConnection)
+  : mConnection(aConnection)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aConnection);
+
+  MOZ_COUNT_CTOR(SDBConnectionChild);
+}
+
+SDBConnectionChild::~SDBConnectionChild()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(SDBConnectionChild);
+}
+
+void
+SDBConnectionChild::SendDeleteMeInternal()
+{
+  AssertIsOnOwningThread();
+
+  if (mConnection) {
+    mConnection->ClearBackgroundActor();
+    mConnection = nullptr;
+
+    MOZ_ALWAYS_TRUE(PBackgroundSDBConnectionChild::SendDeleteMe());
+  }
+}
+
+void
+SDBConnectionChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  if (mConnection) {
+    mConnection->ClearBackgroundActor();
+#ifdef DEBUG
+    mConnection = nullptr;
+#endif
+  }
+}
+
+PBackgroundSDBRequestChild*
+SDBConnectionChild::AllocPBackgroundSDBRequestChild(
+                                                const SDBRequestParams& aParams)
+{
+  AssertIsOnOwningThread();
+
+  MOZ_CRASH("PBackgroundSDBRequestChild actors should be manually "
+            "constructed!");
+}
+
+bool
+SDBConnectionChild::DeallocPBackgroundSDBRequestChild(
+                                             PBackgroundSDBRequestChild* aActor)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aActor);
+
+  delete static_cast<SDBRequestChild*>(aActor);
+  return true;
+}
+
+mozilla::ipc::IPCResult
+SDBConnectionChild::RecvAllowToClose()
+{
+  AssertIsOnOwningThread();
+
+  if (mConnection) {
+    mConnection->AllowToClose();
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SDBConnectionChild::RecvClosed()
+{
+  AssertIsOnOwningThread();
+
+  if (mConnection) {
+    mConnection->OnClose(/* aAbnormal */ true);
+  }
+
+  return IPC_OK();
+}
+
+/*******************************************************************************
+ * SDBRequestChild
+ ******************************************************************************/
+
+SDBRequestChild::SDBRequestChild(SDBRequest* aRequest)
+  : mConnection(aRequest->GetConnection())
+  , mRequest(aRequest)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aRequest);
+
+  MOZ_COUNT_CTOR(SDBRequestChild);
+}
+
+SDBRequestChild::~SDBRequestChild()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(SDBRequestChild);
+}
+
+#ifdef DEBUG
+
+void
+SDBRequestChild::AssertIsOnOwningThread() const
+{
+  MOZ_ASSERT(mRequest);
+  mRequest->AssertIsOnOwningThread();
+}
+
+#endif // DEBUG
+
+void
+SDBRequestChild::HandleResponse(nsresult aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NS_FAILED(aResponse));
+  MOZ_ASSERT(mRequest);
+
+  mRequest->SetError(aResponse);
+}
+
+void
+SDBRequestChild::HandleResponse()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mRequest);
+
+  RefPtr<nsVariant> variant = new nsVariant();
+  variant->SetAsVoid();
+
+  mRequest->SetResult(variant);
+}
+
+void
+SDBRequestChild::HandleResponse(const nsCString& aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mRequest);
+
+  RefPtr<SDBResult> result = new SDBResult(aResponse);
+
+  RefPtr<nsVariant> variant = new nsVariant();
+  variant->SetAsInterface(NS_GET_IID(nsISDBResult), result);
+
+  mRequest->SetResult(variant);
+}
+
+void
+SDBRequestChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  if (mConnection) {
+    mConnection->AssertIsOnOwningThread();
+
+    mConnection->OnRequestFinished();
+#ifdef DEBUG
+    mConnection = nullptr;
+#endif
+  }
+}
+
+mozilla::ipc::IPCResult
+SDBRequestChild::Recv__delete__(const SDBRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mRequest);
+  MOZ_ASSERT(mConnection);
+
+  switch (aResponse.type()) {
+    case SDBRequestResponse::Tnsresult:
+      HandleResponse(aResponse.get_nsresult());
+      break;
+
+    case SDBRequestResponse::TSDBRequestOpenResponse:
+      HandleResponse();
+
+      mConnection->OnOpen();
+
+      break;
+
+    case SDBRequestResponse::TSDBRequestSeekResponse:
+      HandleResponse();
+      break;
+
+    case SDBRequestResponse::TSDBRequestReadResponse:
+      HandleResponse(aResponse.get_SDBRequestReadResponse().data());
+      break;
+
+    case SDBRequestResponse::TSDBRequestWriteResponse:
+      HandleResponse();
+      break;
+
+    case SDBRequestResponse::TSDBRequestCloseResponse:
+      HandleResponse();
+
+      mConnection->OnClose(/* aAbnormal */ false);
+
+      break;
+
+    default:
+      MOZ_CRASH("Unknown response type!");
+  }
+
+  mConnection->OnRequestFinished();
+
+  // Null this out so that we don't try to call OnRequestFinished() again in
+  // ActorDestroy.
+  mConnection = nullptr;
+
+  return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/ActorsChild.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_simpledb_ActorsChild_h
+#define mozilla_dom_simpledb_ActorsChild_h
+
+#include "mozilla/dom/PBackgroundSDBConnectionChild.h"
+#include "mozilla/dom/PBackgroundSDBRequestChild.h"
+
+namespace mozilla {
+namespace ipc {
+
+class BackgroundChildImpl;
+
+} // namespace ipc
+
+namespace dom {
+
+class SDBConnection;
+class SDBRequest;
+
+class SDBConnectionChild final
+  : public PBackgroundSDBConnectionChild
+{
+  friend class mozilla::ipc::BackgroundChildImpl;
+  friend class SDBConnection;
+
+  SDBConnection* mConnection;
+
+  NS_DECL_OWNINGTHREAD
+
+public:
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(SDBConnectionChild);
+  }
+
+private:
+  // Only created by SDBConnection.
+  explicit SDBConnectionChild(SDBConnection* aConnection);
+
+  // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+  ~SDBConnectionChild();
+
+  void
+  SendDeleteMeInternal();
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual PBackgroundSDBRequestChild*
+  AllocPBackgroundSDBRequestChild(const SDBRequestParams& aParams) override;
+
+  virtual bool
+  DeallocPBackgroundSDBRequestChild(PBackgroundSDBRequestChild* aActor)
+                                    override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvAllowToClose() override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvClosed() override;
+};
+
+class SDBRequestChild final
+  : public PBackgroundSDBRequestChild
+{
+  friend class SDBConnectionChild;
+  friend class SDBConnection;
+
+  RefPtr<SDBConnection> mConnection;
+  RefPtr<SDBRequest> mRequest;
+
+public:
+  void
+  AssertIsOnOwningThread() const
+#ifdef DEBUG
+  ;
+#else
+  { }
+#endif
+
+private:
+  // Only created by SDBConnection.
+  explicit SDBRequestChild(SDBRequest* aRequest);
+
+  // Only destroyed by SDBConnectionChild.
+  ~SDBRequestChild();
+
+  void
+  HandleResponse(nsresult aResponse);
+
+  void
+  HandleResponse();
+
+  void
+  HandleResponse(const nsCString& aResponse);
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual mozilla::ipc::IPCResult
+  Recv__delete__(const SDBRequestResponse& aResponse) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_simpledb_ActorsChild_h
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/ActorsParent.cpp
@@ -0,0 +1,2010 @@
+/* -*- 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 "ActorsParent.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/PBackgroundSDBConnectionParent.h"
+#include "mozilla/dom/PBackgroundSDBRequestParent.h"
+#include "mozilla/dom/quota/Client.h"
+#include "mozilla/dom/quota/FileStreams.h"
+#include "mozilla/dom/quota/MemoryOutputStream.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/UsageInfo.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsIFileStreams.h"
+#include "nsISimpleEnumerator.h"
+#include "nsStringStream.h"
+#include "prio.h"
+#include "SimpleDBCommon.h"
+
+#define DISABLE_ASSERTS_FOR_FUZZING 0
+
+#if DISABLE_ASSERTS_FOR_FUZZING
+#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
+#else
+#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::dom::quota;
+using namespace mozilla::ipc;
+
+namespace {
+
+/*******************************************************************************
+ * Constants
+ ******************************************************************************/
+
+const uint32_t kCopyBufferSize = 32768;
+
+/*******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+class StreamHelper final
+  : public Runnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+  nsCOMPtr<nsIFileStream> mFileStream;
+  nsCOMPtr<nsIRunnable> mCallback;
+
+public:
+  StreamHelper(nsIFileStream* aFileStream,
+               nsIRunnable* aCallback);
+
+  void
+  AsyncClose();
+
+private:
+  ~StreamHelper() override;
+
+  void
+  RunOnBackgroundThread();
+
+  void
+  RunOnIOThread();
+
+  NS_DECL_NSIRUNNABLE
+};
+
+class Connection final
+  : public PBackgroundSDBConnectionParent
+{
+  RefPtr<DirectoryLock> mDirectoryLock;
+  nsCOMPtr<nsIFileStream> mFileStream;
+  const PrincipalInfo mPrincipalInfo;
+  nsCString mOrigin;
+  nsString mName;
+
+  bool mRunningRequest;
+  bool mOpen;
+  bool mAllowedToClose;
+  bool mActorDestroyed;
+
+public:
+  explicit Connection(const PrincipalInfo& aPrincipalInfo);
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::Connection)
+
+  nsIFileStream*
+  GetFileStream() const
+  {
+    AssertIsOnIOThread();
+
+    return mFileStream;
+  }
+
+  const PrincipalInfo&
+  GetPrincipalInfo() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    return mPrincipalInfo;
+  }
+
+  const nsCString&
+  Origin() const
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(!mOrigin.IsEmpty());
+
+    return mOrigin;
+  }
+
+  const nsString&
+  Name() const
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(!mName.IsEmpty());
+
+    return mName;
+  }
+
+  void
+  OnNewRequest();
+
+  void
+  OnRequestFinished();
+
+  void
+  OnOpen(const nsACString& aOrigin,
+         const nsAString& aName,
+         already_AddRefed<DirectoryLock> aDirectoryLock,
+         already_AddRefed<nsIFileStream> aFileStream);
+
+  void
+  OnClose();
+
+  void
+  AllowToClose();
+
+private:
+  ~Connection();
+
+  void
+  MaybeCloseStream();
+
+  bool
+  VerifyRequestParams(const SDBRequestParams& aParams) const;
+
+  // IPDL methods.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult
+  RecvDeleteMe() override;
+
+  virtual PBackgroundSDBRequestParent*
+  AllocPBackgroundSDBRequestParent(const SDBRequestParams& aParams) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvPBackgroundSDBRequestConstructor(PBackgroundSDBRequestParent* aActor,
+                                       const SDBRequestParams& aParams)
+                                       override;
+
+  virtual bool
+  DeallocPBackgroundSDBRequestParent(PBackgroundSDBRequestParent* aActor)
+                                     override;
+};
+
+class ConnectionOperationBase
+  : public Runnable
+  , public PBackgroundSDBRequestParent
+{
+  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+  RefPtr<Connection> mConnection;
+  nsresult mResultCode;
+  Atomic<bool> mOperationMayProceed;
+  bool mActorDestroyed;
+
+public:
+  nsIEventTarget*
+  OwningEventTarget() const
+  {
+    MOZ_ASSERT(mOwningEventTarget);
+
+    return mOwningEventTarget;
+  }
+
+  bool
+  IsOnOwningThread() const
+  {
+    MOZ_ASSERT(mOwningEventTarget);
+
+    bool current;
+    return
+      NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) && current;
+  }
+
+  void
+  AssertIsOnOwningThread() const
+  {
+    MOZ_ASSERT(IsOnBackgroundThread());
+    MOZ_ASSERT(IsOnOwningThread());
+  }
+
+  Connection*
+  GetConnection() const
+  {
+    MOZ_ASSERT(mConnection);
+
+    return mConnection;
+  }
+
+  nsresult
+  ResultCode() const
+  {
+    return mResultCode;
+  }
+
+  void
+  MaybeSetFailureCode(nsresult aErrorCode)
+  {
+    MOZ_ASSERT(NS_FAILED(aErrorCode));
+
+    if (NS_SUCCEEDED(mResultCode)) {
+      mResultCode = aErrorCode;
+    }
+  }
+
+  // May be called on any thread, but you should call IsActorDestroyed() if
+  // you know you're on the background thread because it is slightly faster.
+  bool
+  OperationMayProceed() const
+  {
+    return mOperationMayProceed;
+  }
+
+  bool
+  IsActorDestroyed() const
+  {
+    AssertIsOnOwningThread();
+
+    return mActorDestroyed;
+  }
+
+  // May be overridden by subclasses if they need to perform work on the
+  // background thread before being dispatched but must always call the base
+  // class implementation. Returning false will kill the child actors and
+  // prevent dispatch.
+  virtual bool
+  Init();
+
+  virtual nsresult
+  Dispatch();
+
+  // This callback will be called on the background thread before releasing the
+  // final reference to this request object. Subclasses may perform any
+  // additional cleanup here but must always call the base class implementation.
+  virtual void
+  Cleanup();
+
+protected:
+  ConnectionOperationBase(Connection* aConnection)
+    : Runnable("dom::ConnectionOperationBase")
+    , mOwningEventTarget(GetCurrentThreadEventTarget())
+    , mConnection(aConnection)
+    , mResultCode(NS_OK)
+    , mOperationMayProceed(true)
+    , mActorDestroyed(false)
+  {
+    AssertIsOnOwningThread();
+  }
+
+  ~ConnectionOperationBase() override;
+
+  void
+  SendResults();
+
+  void
+  DatabaseWork();
+
+  // Methods that subclasses must implement.
+  virtual nsresult
+  DoDatabaseWork(nsIFileStream* aFileStream) = 0;
+
+  // Subclasses use this override to set the IPDL response value.
+  virtual void
+  GetResponse(SDBRequestResponse& aResponse) = 0;
+
+  // A method that subclasses may implement.
+  virtual void
+  OnSuccess();
+
+private:
+  NS_IMETHOD
+  Run() override;
+
+  // IPDL methods.
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class OpenOp final
+  : public ConnectionOperationBase
+  , public OpenDirectoryListener
+{
+  enum class State
+  {
+    // Just created on the PBackground thread, dispatched to the main thread.
+    // Next step is FinishOpen.
+    Initial,
+
+    // Opening directory or initializing quota manager on the PBackground
+    // thread. Next step is either DirectoryOpenPending if quota manager is
+    // already initialized or QuotaManagerPending if quota manager needs to be
+    // initialized.
+    FinishOpen,
+
+    // Waiting for quota manager initialization to complete on the PBackground
+    // thread. Next step is either SendingResults if initialization failed or
+    // DirectoryOpenPending if initialization succeeded.
+    QuotaManagerPending,
+
+    // Waiting for directory open allowed on the PBackground thread. The next
+    // step is either SendingResults if directory lock failed to acquire, or
+    // DatabaseWorkOpen if directory lock is acquired.
+    DirectoryOpenPending,
+
+    // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
+    // SendingResults.
+    DatabaseWorkOpen,
+
+    // Waiting to send/sending results on the PBackground thread. Next step is
+    // Completed.
+    SendingResults,
+
+    // All done.
+    Completed
+  };
+
+  const SDBRequestOpenParams mParams;
+  RefPtr<DirectoryLock> mDirectoryLock;
+  nsCOMPtr<nsIFileStream> mFileStream;
+  nsCString mSuffix;
+  nsCString mGroup;
+  nsCString mOrigin;
+  State mState;
+  bool mFileStreamOpen;
+
+public:
+  OpenOp(Connection* aConnection, const SDBRequestParams& aParams);
+
+  nsresult
+  Dispatch() override;
+
+private:
+  ~OpenOp() override;
+
+  nsresult
+  Open();
+
+  nsresult
+  FinishOpen();
+
+  nsresult
+  QuotaManagerOpen();
+
+  nsresult
+  OpenDirectory();
+
+  nsresult
+  SendToIOThread();
+
+  nsresult
+  DatabaseWork();
+
+  void
+  StreamClosedCallback();
+
+  // ConnectionOperationBase overrides
+  nsresult
+  DoDatabaseWork(nsIFileStream* aFileStream) override;
+
+  void
+  GetResponse(SDBRequestResponse& aResponse) override;
+
+  void
+  OnSuccess() override;
+
+  void
+  Cleanup() override;
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_IMETHOD
+  Run() override;
+
+  // OpenDirectoryListener overrides.
+  void
+  DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  void
+  DirectoryLockFailed() override;
+};
+
+class SeekOp final
+  : public ConnectionOperationBase
+{
+  const SDBRequestSeekParams mParams;
+
+public:
+  SeekOp(Connection* aConnection,
+         const SDBRequestParams& aParams);
+
+private:
+  ~SeekOp() override = default;
+
+  nsresult
+  DoDatabaseWork(nsIFileStream* aFileStream) override;
+
+  void
+  GetResponse(SDBRequestResponse& aResponse) override;
+};
+
+class ReadOp final
+  : public ConnectionOperationBase
+{
+  const SDBRequestReadParams mParams;
+
+  RefPtr<MemoryOutputStream> mOutputStream;
+
+public:
+  ReadOp(Connection* aConnection,
+         const SDBRequestParams& aParams);
+
+  bool
+  Init() override;
+
+private:
+  ~ReadOp() override = default;
+
+  nsresult
+  DoDatabaseWork(nsIFileStream* aFileStream) override;
+
+  void
+  GetResponse(SDBRequestResponse& aResponse) override;
+};
+
+class WriteOp final
+  : public ConnectionOperationBase
+{
+  const SDBRequestWriteParams mParams;
+
+  nsCOMPtr<nsIInputStream> mInputStream;
+
+  uint64_t mSize;
+
+public:
+  WriteOp(Connection* aConnection,
+          const SDBRequestParams& aParams);
+
+  bool
+  Init() override;
+
+private:
+  ~WriteOp() override = default;
+
+  nsresult
+  DoDatabaseWork(nsIFileStream* aFileStream) override;
+
+  void
+  GetResponse(SDBRequestResponse& aResponse) override;
+};
+
+class CloseOp final
+  : public ConnectionOperationBase
+{
+public:
+  explicit CloseOp(Connection* aConnection);
+
+private:
+  ~CloseOp() override = default;
+
+  nsresult
+  DoDatabaseWork(nsIFileStream* aFileStream) override;
+
+  void
+  GetResponse(SDBRequestResponse& aResponse) override;
+
+  void
+  OnSuccess() override;
+};
+
+/*******************************************************************************
+ * Other class declarations
+ ******************************************************************************/
+
+class QuotaClient final
+  : public mozilla::dom::quota::Client
+{
+  static QuotaClient* sInstance;
+
+  bool mShutdownRequested;
+
+public:
+  QuotaClient();
+
+  static bool
+  IsShuttingDownOnBackgroundThread()
+  {
+    AssertIsOnBackgroundThread();
+
+    if (sInstance) {
+      return sInstance->IsShuttingDown();
+    }
+
+    return QuotaManager::IsShuttingDown();
+  }
+
+  static bool
+  IsShuttingDownOnNonBackgroundThread()
+  {
+    MOZ_ASSERT(!IsOnBackgroundThread());
+
+    return QuotaManager::IsShuttingDown();
+  }
+
+  bool
+  IsShuttingDown() const
+  {
+    AssertIsOnBackgroundThread();
+
+    return mShutdownRequested;
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override)
+
+  Type
+  GetType() override;
+
+  nsresult
+  InitOrigin(PersistenceType aPersistenceType,
+             const nsACString& aGroup,
+             const nsACString& aOrigin,
+             const AtomicBool& aCanceled,
+             UsageInfo* aUsageInfo) override;
+
+  nsresult
+  GetUsageForOrigin(PersistenceType aPersistenceType,
+                    const nsACString& aGroup,
+                    const nsACString& aOrigin,
+                    const AtomicBool& aCanceled,
+                    UsageInfo* aUsageInfo) override;
+
+  void
+  OnOriginClearCompleted(PersistenceType aPersistenceType,
+                         const nsACString& aOrigin)
+                         override;
+
+  void
+  ReleaseIOThreadObjects() override;
+
+  void
+  AbortOperations(const nsACString& aOrigin) override;
+
+  void
+  AbortOperationsForProcess(ContentParentId aContentParentId) override;
+
+  void
+  StartIdleMaintenance() override;
+
+  void
+  StopIdleMaintenance() override;
+
+  void
+  ShutdownWorkThreads() override;
+
+private:
+  ~QuotaClient() override;
+};
+
+/*******************************************************************************
+ * Globals
+ ******************************************************************************/
+
+typedef nsTArray<RefPtr<Connection>> ConnectionArray;
+
+StaticAutoPtr<ConnectionArray> gOpenConnections;
+
+} // namespace
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+
+PBackgroundSDBConnectionParent*
+AllocPBackgroundSDBConnectionParent(const PrincipalInfo& aPrincipalInfo)
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+    return nullptr;
+  }
+
+  if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  RefPtr<Connection> actor = new Connection(aPrincipalInfo);
+
+  return actor.forget().take();
+}
+
+bool
+RecvPBackgroundSDBConnectionConstructor(PBackgroundSDBConnectionParent* aActor,
+                                        const PrincipalInfo& aPrincipalInfo)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+
+  return true;
+}
+
+bool
+DeallocPBackgroundSDBConnectionParent(PBackgroundSDBConnectionParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  RefPtr<Connection> actor = dont_AddRef(static_cast<Connection*>(aActor));
+  return true;
+}
+
+namespace simpledb {
+
+already_AddRefed<mozilla::dom::quota::Client>
+CreateQuotaClient()
+{
+  AssertIsOnBackgroundThread();
+
+  RefPtr<QuotaClient> client = new QuotaClient();
+  return client.forget();
+}
+
+} // namespace simpledb
+
+/*******************************************************************************
+ * StreamHelper
+ ******************************************************************************/
+
+StreamHelper::StreamHelper(nsIFileStream* aFileStream,
+                           nsIRunnable* aCallback)
+  : Runnable("dom::StreamHelper")
+  , mOwningEventTarget(GetCurrentThreadEventTarget())
+  , mFileStream(aFileStream)
+  , mCallback(aCallback)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aFileStream);
+  MOZ_ASSERT(aCallback);
+}
+
+StreamHelper::~StreamHelper()
+{
+  MOZ_ASSERT(!mFileStream);
+  MOZ_ASSERT(!mCallback);
+}
+
+void
+StreamHelper::AsyncClose()
+{
+  AssertIsOnBackgroundThread();
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  MOZ_ALWAYS_SUCCEEDS(
+    quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void
+StreamHelper::RunOnBackgroundThread()
+{
+  AssertIsOnBackgroundThread();
+
+  nsCOMPtr<nsIFileStream> fileStream;
+  mFileStream.swap(fileStream);
+
+  nsCOMPtr<nsIRunnable> callback;
+  mCallback.swap(callback);
+
+  callback->Run();
+}
+
+void
+StreamHelper::RunOnIOThread()
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(mFileStream);
+
+  nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(mFileStream);
+  MOZ_ASSERT(inputStream);
+
+  nsresult rv = inputStream->Close();
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+NS_IMETHODIMP
+StreamHelper::Run()
+{
+  MOZ_ASSERT(mCallback);
+
+  if (IsOnBackgroundThread()) {
+    RunOnBackgroundThread();
+  } else {
+    RunOnIOThread();
+  }
+
+  return NS_OK;
+}
+
+/*******************************************************************************
+ * Connection
+ ******************************************************************************/
+
+Connection::Connection(const PrincipalInfo& aPrincipalInfo)
+  : mPrincipalInfo(aPrincipalInfo)
+  , mRunningRequest(false)
+  , mOpen(false)
+  , mAllowedToClose(false)
+  , mActorDestroyed(false)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+}
+
+Connection::~Connection()
+{
+  MOZ_ASSERT(!mRunningRequest);
+  MOZ_ASSERT(!mOpen);
+  MOZ_ASSERT(mActorDestroyed);
+}
+
+void
+Connection::OnNewRequest()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mRunningRequest);
+
+  mRunningRequest = true;
+}
+
+void
+Connection::OnRequestFinished()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mRunningRequest);
+
+  mRunningRequest = false;
+
+  MaybeCloseStream();
+}
+
+void
+Connection::OnOpen(const nsACString& aOrigin,
+                   const nsAString& aName,
+                   already_AddRefed<DirectoryLock> aDirectoryLock,
+                   already_AddRefed<nsIFileStream> aFileStream)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!aOrigin.IsEmpty());
+  MOZ_ASSERT(!aName.IsEmpty());
+  MOZ_ASSERT(mOrigin.IsEmpty());
+  MOZ_ASSERT(mName.IsEmpty());
+  MOZ_ASSERT(!mDirectoryLock);
+  MOZ_ASSERT(!mFileStream);
+  MOZ_ASSERT(!mOpen);
+
+  mOrigin = aOrigin;
+  mName = aName;
+  mDirectoryLock = aDirectoryLock;
+  mFileStream = aFileStream;
+  mOpen = true;
+
+  if (!gOpenConnections) {
+    gOpenConnections = new ConnectionArray();
+  }
+
+  gOpenConnections->AppendElement(this);
+}
+
+void
+Connection::OnClose()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mOrigin.IsEmpty());
+  MOZ_ASSERT(mDirectoryLock);
+  MOZ_ASSERT(mFileStream);
+  MOZ_ASSERT(mOpen);
+
+  mOrigin.Truncate();
+  mName.Truncate();
+  mDirectoryLock = nullptr;
+  mFileStream = nullptr;
+  mOpen = false;
+
+  MOZ_ASSERT(gOpenConnections);
+  gOpenConnections->RemoveElement(this);
+
+  if (gOpenConnections->IsEmpty()) {
+    gOpenConnections = nullptr;
+  }
+
+  if (mAllowedToClose && !mActorDestroyed) {
+    Unused << SendClosed();
+  }
+}
+
+void
+Connection::AllowToClose()
+{
+  AssertIsOnBackgroundThread();
+
+  if (mAllowedToClose) {
+    return;
+  }
+
+  mAllowedToClose = true;
+
+  if (!mActorDestroyed) {
+    Unused << SendAllowToClose();
+  }
+
+  MaybeCloseStream();
+}
+
+void
+Connection::MaybeCloseStream()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mRunningRequest &&
+      mOpen &&
+      mAllowedToClose) {
+    nsCOMPtr<nsIRunnable> callback =
+      NewRunnableMethod("dom::Connection::OnClose",
+                        this,
+                        &Connection::OnClose);
+
+    RefPtr<StreamHelper> helper = new StreamHelper(mFileStream, callback);
+    helper->AsyncClose();
+  }
+}
+
+bool
+Connection::VerifyRequestParams(const SDBRequestParams& aParams) const
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != SDBRequestParams::T__None);
+
+  switch (aParams.type()) {
+    case SDBRequestParams::TSDBRequestOpenParams: {
+      if (NS_WARN_IF(mOpen)) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case SDBRequestParams::TSDBRequestSeekParams:
+    case SDBRequestParams::TSDBRequestReadParams:
+    case SDBRequestParams::TSDBRequestWriteParams:
+    case SDBRequestParams::TSDBRequestCloseParams: {
+      if (NS_WARN_IF(!mOpen)) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return true;
+}
+
+void
+Connection::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+
+  AllowToClose();
+}
+
+mozilla::ipc::IPCResult
+Connection::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  IProtocol* mgr = Manager();
+  if (!PBackgroundSDBConnectionParent::Send__delete__(this)) {
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+
+  return IPC_OK();
+}
+
+PBackgroundSDBRequestParent*
+Connection::AllocPBackgroundSDBRequestParent(const SDBRequestParams& aParams)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != SDBRequestParams::T__None);
+
+  if (aParams.type() == SDBRequestParams::TSDBRequestOpenParams &&
+      NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+    return nullptr;
+  }
+
+  if (mAllowedToClose) {
+    return nullptr;
+  }
+
+#ifdef DEBUG
+  // Always verify parameters in DEBUG builds!
+  bool trustParams = false;
+#else
+  PBackgroundParent* backgroundActor = Manager();
+  MOZ_ASSERT(backgroundActor);
+
+  bool trustParams = !BackgroundParent::IsOtherProcessActor(backgroundActor);
+#endif
+
+  if (NS_WARN_IF(!trustParams && !VerifyRequestParams(aParams))) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  if (NS_WARN_IF(mRunningRequest)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  RefPtr<ConnectionOperationBase> actor;
+
+  switch (aParams.type()) {
+    case SDBRequestParams::TSDBRequestOpenParams:
+      actor = new OpenOp(this, aParams);
+      break;
+
+    case SDBRequestParams::TSDBRequestSeekParams:
+      actor = new SeekOp(this, aParams);
+      break;
+
+    case SDBRequestParams::TSDBRequestReadParams:
+      actor = new ReadOp(this, aParams);
+      break;
+
+    case SDBRequestParams::TSDBRequestWriteParams:
+      actor = new WriteOp(this, aParams);
+      break;
+
+    case SDBRequestParams::TSDBRequestCloseParams:
+      actor = new CloseOp(this);
+      break;
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  // Transfer ownership to IPDL.
+  return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult
+Connection::RecvPBackgroundSDBRequestConstructor(
+                                            PBackgroundSDBRequestParent* aActor,
+                                            const SDBRequestParams& aParams)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(aParams.type() != SDBRequestParams::T__None);
+  MOZ_ASSERT_IF(aParams.type() == SDBRequestParams::TSDBRequestOpenParams,
+                !QuotaClient::IsShuttingDownOnBackgroundThread());
+  MOZ_ASSERT(!mAllowedToClose);
+  MOZ_ASSERT(!mRunningRequest);
+
+  auto* op = static_cast<ConnectionOperationBase*>(aActor);
+
+  if (NS_WARN_IF(!op->Init())) {
+    op->Cleanup();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  if (NS_WARN_IF(NS_FAILED(op->Dispatch()))) {
+    op->Cleanup();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  return IPC_OK();
+}
+
+bool
+Connection::DeallocPBackgroundSDBRequestParent(
+                                            PBackgroundSDBRequestParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  // Transfer ownership back from IPDL.
+  RefPtr<ConnectionOperationBase> actor =
+    dont_AddRef(static_cast<ConnectionOperationBase*>(aActor));
+  return true;
+}
+
+/*******************************************************************************
+ * ConnectionOperationBase
+ ******************************************************************************/
+
+ConnectionOperationBase::~ConnectionOperationBase()
+{
+  MOZ_ASSERT(!mConnection,
+             "ConnectionOperationBase::Cleanup() was not called by a subclass!");
+  MOZ_ASSERT(mActorDestroyed);
+}
+
+bool
+ConnectionOperationBase::Init()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mConnection);
+
+  mConnection->OnNewRequest();
+
+  return true;
+}
+
+nsresult
+ConnectionOperationBase::Dispatch()
+{
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+      IsActorDestroyed()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+ConnectionOperationBase::Cleanup()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mConnection);
+
+  mConnection->OnRequestFinished();
+
+  mConnection = nullptr;
+}
+
+void
+ConnectionOperationBase::SendResults()
+{
+  AssertIsOnOwningThread();
+
+  if (IsActorDestroyed()) {
+    MaybeSetFailureCode(NS_ERROR_FAILURE);
+  } else {
+    SDBRequestResponse response;
+
+    if (NS_SUCCEEDED(mResultCode)) {
+      GetResponse(response);
+
+      MOZ_ASSERT(response.type() != SDBRequestResponse::T__None);
+      MOZ_ASSERT(response.type() != SDBRequestResponse::Tnsresult);
+    } else {
+      response = mResultCode;
+    }
+
+    Unused <<
+      PBackgroundSDBRequestParent::Send__delete__(this, response);
+
+    if (NS_SUCCEEDED(mResultCode)) {
+      OnSuccess();
+    }
+  }
+
+  Cleanup();
+}
+
+void
+ConnectionOperationBase::DatabaseWork()
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+
+  if (!OperationMayProceed()) {
+    // The operation was canceled in some way, likely because the child process
+    // has crashed.
+    mResultCode = NS_ERROR_FAILURE;
+  } else {
+    nsIFileStream* fileStream = mConnection->GetFileStream();
+    MOZ_ASSERT(fileStream);
+
+    nsresult rv = DoDatabaseWork(fileStream);
+    if (NS_FAILED(rv)) {
+      mResultCode = rv;
+    }
+  }
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void
+ConnectionOperationBase::OnSuccess()
+{
+  AssertIsOnOwningThread();
+}
+
+NS_IMETHODIMP
+ConnectionOperationBase::Run()
+{
+  if (IsOnBackgroundThread()) {
+    SendResults();
+  } else {
+    DatabaseWork();
+  }
+
+  return NS_OK;
+}
+
+void
+ConnectionOperationBase::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+
+  mOperationMayProceed = false;
+  mActorDestroyed = true;
+}
+
+OpenOp::OpenOp(Connection* aConnection, const SDBRequestParams& aParams)
+  : ConnectionOperationBase(aConnection)
+  , mParams(aParams.get_SDBRequestOpenParams())
+  , mState(State::Initial)
+  , mFileStreamOpen(false)
+{
+  MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestOpenParams);
+}
+
+OpenOp::~OpenOp()
+{
+  MOZ_ASSERT(!mDirectoryLock);
+  MOZ_ASSERT(!mFileStream);
+  MOZ_ASSERT(!mFileStreamOpen);
+  MOZ_ASSERT_IF(OperationMayProceed(),
+                mState == State::Initial || mState == State::Completed);
+}
+
+nsresult
+OpenOp::Dispatch()
+{
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+  return NS_OK;
+}
+
+nsresult
+OpenOp::Open()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State::Initial);
+
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+      !OperationMayProceed()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (NS_WARN_IF(!Preferences::GetBool(kPrefSimpleDBEnabled, false))) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  const PrincipalInfo& principalInfo = GetConnection()->GetPrincipalInfo();
+
+  if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
+    QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin);
+  } else {
+    MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
+
+    nsresult rv;
+    nsCOMPtr<nsIPrincipal> principal =
+      PrincipalInfoToPrincipal(principalInfo, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = QuotaManager::GetInfoFromPrincipal(principal,
+                                            &mSuffix,
+                                            &mGroup,
+                                            &mOrigin);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  mState = State::FinishOpen;
+  MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
+
+  return NS_OK;
+}
+
+nsresult
+OpenOp::FinishOpen()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::FinishOpen);
+
+  if (gOpenConnections) {
+    for (Connection* connection : *gOpenConnections) {
+      if (connection->Origin() == mOrigin &&
+          connection->Name() == mParams.name()) {
+        return NS_ERROR_STORAGE_BUSY;
+      }
+    }
+  }
+
+  if (QuotaManager::Get()) {
+    nsresult rv = OpenDirectory();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    return NS_OK;
+  }
+
+  mState = State::QuotaManagerPending;
+  QuotaManager::GetOrCreate(this);
+
+  return NS_OK;
+}
+
+nsresult
+OpenOp::QuotaManagerOpen()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::QuotaManagerPending);
+
+  if (NS_WARN_IF(!QuotaManager::Get())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = OpenDirectory();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+OpenOp::OpenDirectory()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::FinishOpen ||
+             mState == State::QuotaManagerPending);
+  MOZ_ASSERT(!mOrigin.IsEmpty());
+  MOZ_ASSERT(!mDirectoryLock);
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+  MOZ_ASSERT(QuotaManager::Get());
+
+  mState = State::DirectoryOpenPending;
+  QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
+                                     mGroup,
+                                     mOrigin,
+                                     mozilla::dom::quota::Client::SDB,
+                                     /* aExclusive */ false,
+                                     this);
+
+  return NS_OK;
+}
+
+nsresult
+OpenOp::SendToIOThread()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::DirectoryOpenPending);
+
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+      IsActorDestroyed()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mFileStream = new FileStream(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  // Must set this before dispatching otherwise we will race with the IO thread.
+  mState = State::DatabaseWorkOpen;
+
+  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+OpenOp::DatabaseWork()
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(mState == State::DatabaseWorkOpen);
+  MOZ_ASSERT(mFileStream);
+  MOZ_ASSERT(!mFileStreamOpen);
+
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+      !OperationMayProceed()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  nsCOMPtr<nsIFile> dbDirectory;
+  nsresult rv =
+    quotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
+                                            mSuffix,
+                                            mGroup,
+                                            mOrigin,
+                                            getter_AddRefs(dbDirectory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = dbDirectory->Append(NS_LITERAL_STRING(SDB_DIRECTORY_NAME));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool exists;
+  rv = dbDirectory->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!exists) {
+    rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+#ifdef DEBUG
+  else {
+    bool isDirectory;
+    MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory)));
+    MOZ_ASSERT(isDirectory);
+  }
+#endif
+
+  nsCOMPtr<nsIFile> dbFile;
+  rv = dbDirectory->Clone(getter_AddRefs(dbFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = dbFile->Append(mParams.name());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsString databaseFilePath;
+  rv = dbFile->GetPath(databaseFilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mFileStream->Init(dbFile, PR_RDWR | PR_CREATE_FILE, 0644, 0);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mFileStreamOpen = true;
+
+  rv = DoDatabaseWork(mFileStream);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Must set mState before dispatching otherwise we will race with the owning
+  // thread.
+  mState = State::SendingResults;
+
+  rv = OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+OpenOp::StreamClosedCallback()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NS_FAILED(ResultCode()));
+  MOZ_ASSERT(mDirectoryLock);
+  MOZ_ASSERT(mFileStream);
+  MOZ_ASSERT(mFileStreamOpen);
+
+  mDirectoryLock = nullptr;
+  mFileStream = nullptr;
+  mFileStreamOpen = false;
+}
+
+nsresult
+OpenOp::DoDatabaseWork(nsIFileStream* aFileStream)
+{
+  AssertIsOnIOThread();
+
+  return NS_OK;
+}
+
+void
+OpenOp::GetResponse(SDBRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  aResponse = SDBRequestOpenResponse();
+}
+
+void
+OpenOp::OnSuccess()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
+  MOZ_ASSERT(!mOrigin.IsEmpty());
+  MOZ_ASSERT(mDirectoryLock);
+  MOZ_ASSERT(mFileStream);
+  MOZ_ASSERT(mFileStreamOpen);
+
+  RefPtr<DirectoryLock> directoryLock;
+  nsCOMPtr<nsIFileStream> fileStream;
+
+  mDirectoryLock.swap(directoryLock);
+  mFileStream.swap(fileStream);
+  mFileStreamOpen = false;
+
+  GetConnection()->OnOpen(mOrigin,
+                          mParams.name(),
+                          directoryLock.forget(),
+                          fileStream.forget());
+}
+
+void
+OpenOp::Cleanup()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT_IF(mFileStreamOpen, mFileStream);
+
+  if (mFileStream && mFileStreamOpen) {
+    // If we have an initialized file stream then the operation must have failed
+    // and there must be a directory lock too.
+    MOZ_ASSERT(NS_FAILED(ResultCode()));
+    MOZ_ASSERT(mDirectoryLock);
+
+    // We must close the stream on the I/O thread before releasing it on this
+    // thread. The directory lock can't be released either.
+    nsCOMPtr<nsIRunnable> callback =
+      NewRunnableMethod("dom::OpenOp::StreamClosedCallback",
+                        this,
+                        &OpenOp::StreamClosedCallback);
+
+    RefPtr<StreamHelper> helper = new StreamHelper(mFileStream, callback);
+    helper->AsyncClose();
+  } else {
+    MOZ_ASSERT(!mFileStreamOpen);
+
+    mDirectoryLock = nullptr;
+    mFileStream = nullptr;
+  }
+
+  ConnectionOperationBase::Cleanup();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(OpenOp, ConnectionOperationBase)
+
+NS_IMETHODIMP
+OpenOp::Run()
+{
+  nsresult rv;
+
+  switch (mState) {
+    case State::Initial:
+      rv = Open();
+      break;
+
+    case State::FinishOpen:
+      rv = FinishOpen();
+      break;
+
+    case State::QuotaManagerPending:
+      rv = QuotaManagerOpen();
+      break;
+
+    case State::DatabaseWorkOpen:
+      rv = DatabaseWork();
+      break;
+
+    case State::SendingResults:
+      SendResults();
+      return NS_OK;
+
+    default:
+      MOZ_CRASH("Bad state!");
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) {
+    MaybeSetFailureCode(rv);
+
+    // Must set mState before dispatching otherwise we will race with the owning
+    // thread.
+    mState = State::SendingResults;
+
+    if (IsOnOwningThread()) {
+      SendResults();
+    } else {
+      MOZ_ALWAYS_SUCCEEDS(
+        OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+OpenOp::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  mDirectoryLock = aLock;
+
+  nsresult rv = SendToIOThread();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    MaybeSetFailureCode(rv);
+
+    // The caller holds a strong reference to us, no need for a self reference
+    // before calling Run().
+
+    mState = State::SendingResults;
+    MOZ_ALWAYS_SUCCEEDS(Run());
+
+    return;
+  }
+}
+
+void
+OpenOp::DirectoryLockFailed()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  MaybeSetFailureCode(NS_ERROR_FAILURE);
+
+  // The caller holds a strong reference to us, no need for a self reference
+  // before calling Run().
+
+  mState = State::SendingResults;
+  MOZ_ALWAYS_SUCCEEDS(Run());
+}
+
+SeekOp::SeekOp(Connection* aConnection,
+               const SDBRequestParams& aParams)
+  : ConnectionOperationBase(aConnection)
+  , mParams(aParams.get_SDBRequestSeekParams())
+{
+  MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestSeekParams);
+}
+
+nsresult
+SeekOp::DoDatabaseWork(nsIFileStream* aFileStream)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aFileStream);
+
+  nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(aFileStream);
+  MOZ_ASSERT(seekableStream);
+
+  nsresult rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET,
+                                     mParams.offset());
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+SeekOp::GetResponse(SDBRequestResponse& aResponse)
+{
+  aResponse = SDBRequestSeekResponse();
+}
+
+ReadOp::ReadOp(Connection* aConnection,
+               const SDBRequestParams& aParams)
+  : ConnectionOperationBase(aConnection)
+  , mParams(aParams.get_SDBRequestReadParams())
+{
+  MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestReadParams);
+}
+
+bool
+ReadOp::Init()
+{
+  AssertIsOnOwningThread();
+
+  if (NS_WARN_IF(!ConnectionOperationBase::Init())) {
+    return false;
+  }
+
+  mOutputStream = MemoryOutputStream::Create(mParams.size());
+  if (NS_WARN_IF(!mOutputStream)) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+ReadOp::DoDatabaseWork(nsIFileStream* aFileStream)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aFileStream);
+
+  nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(aFileStream);
+  MOZ_ASSERT(inputStream);
+
+  nsresult rv;
+
+  uint64_t offset = 0;
+
+  do {
+    char copyBuffer[kCopyBufferSize];
+
+    uint64_t max = mParams.size() - offset;
+    if (max == 0) {
+      break;
+    }
+
+    uint32_t count = sizeof(copyBuffer);
+    if (count > max) {
+      count = max;
+    }
+
+    uint32_t numRead;
+    rv = inputStream->Read(copyBuffer, count, &numRead);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!numRead) {
+      break;
+    }
+
+    uint32_t numWrite;
+    rv = mOutputStream->Write(copyBuffer, numRead, &numWrite);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(numWrite != numRead)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    offset += numWrite;
+  } while (true);
+
+  MOZ_ASSERT(offset == mParams.size());
+
+  MOZ_ALWAYS_SUCCEEDS(mOutputStream->Close());
+
+  return NS_OK;
+}
+
+void
+ReadOp::GetResponse(SDBRequestResponse& aResponse)
+{
+  aResponse = SDBRequestReadResponse(mOutputStream->Data());
+}
+
+WriteOp::WriteOp(Connection* aConnection,
+                 const SDBRequestParams& aParams)
+  : ConnectionOperationBase(aConnection)
+  , mParams(aParams.get_SDBRequestWriteParams())
+  , mSize(0)
+{
+  MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestWriteParams);
+}
+
+bool
+WriteOp::Init()
+{
+  AssertIsOnOwningThread();
+
+  if (NS_WARN_IF(!ConnectionOperationBase::Init())) {
+    return false;
+  }
+
+  const nsCString& string = mParams.data();
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  nsresult rv =
+    NS_NewCStringInputStream(getter_AddRefs(inputStream), string);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  mInputStream = std::move(inputStream);
+  mSize = string.Length();
+
+  return true;
+}
+
+nsresult
+WriteOp::DoDatabaseWork(nsIFileStream* aFileStream)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aFileStream);
+
+  nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(aFileStream);
+  MOZ_ASSERT(outputStream);
+
+  nsresult rv;
+
+  do {
+    char copyBuffer[kCopyBufferSize];
+
+    uint32_t numRead;
+    rv = mInputStream->Read(copyBuffer, sizeof(copyBuffer), &numRead);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      break;
+    }
+
+    if (!numRead) {
+      break;
+    }
+
+    uint32_t numWrite;
+    rv = outputStream->Write(copyBuffer, numRead, &numWrite);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(numWrite != numRead)) {
+      return NS_ERROR_FAILURE;
+    }
+  } while (true);
+
+  MOZ_ALWAYS_SUCCEEDS(mInputStream->Close());
+
+  return NS_OK;
+}
+
+void
+WriteOp::GetResponse(SDBRequestResponse& aResponse)
+{
+  aResponse = SDBRequestWriteResponse();
+}
+
+CloseOp::CloseOp(Connection* aConnection)
+  : ConnectionOperationBase(aConnection)
+{ }
+
+nsresult
+CloseOp::DoDatabaseWork(nsIFileStream* aFileStream)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aFileStream);
+
+  nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(aFileStream);
+  MOZ_ASSERT(inputStream);
+
+  nsresult rv = inputStream->Close();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+CloseOp::GetResponse(SDBRequestResponse& aResponse)
+{
+  aResponse = SDBRequestCloseResponse();
+}
+
+void
+CloseOp::OnSuccess()
+{
+  AssertIsOnOwningThread();
+
+  GetConnection()->OnClose();
+}
+
+/*******************************************************************************
+ * QuotaClient
+ ******************************************************************************/
+
+QuotaClient* QuotaClient::sInstance = nullptr;
+
+QuotaClient::QuotaClient()
+  : mShutdownRequested(false)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
+
+  sInstance = this;
+}
+
+QuotaClient::~QuotaClient()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
+
+  sInstance = nullptr;
+}
+
+mozilla::dom::quota::Client::Type
+QuotaClient::GetType()
+{
+  return QuotaClient::SDB;
+}
+
+nsresult
+QuotaClient::InitOrigin(PersistenceType aPersistenceType,
+                        const nsACString& aGroup,
+                        const nsACString& aOrigin,
+                        const AtomicBool& aCanceled,
+                        UsageInfo* aUsageInfo)
+{
+  AssertIsOnIOThread();
+
+  if (!aUsageInfo) {
+    return NS_OK;
+  }
+
+  return GetUsageForOrigin(aPersistenceType,
+                           aGroup,
+                           aOrigin,
+                           aCanceled,
+                           aUsageInfo);
+}
+
+nsresult
+QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType,
+                               const nsACString& aGroup,
+                               const nsACString& aOrigin,
+                               const AtomicBool& aCanceled,
+                               UsageInfo* aUsageInfo)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aUsageInfo);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin,
+                                                    getter_AddRefs(directory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(directory);
+
+  rv = directory->Append(NS_LITERAL_STRING(SDB_DIRECTORY_NAME));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  DebugOnly<bool> exists;
+  MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
+
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool hasMore;
+  while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+         hasMore && !aCanceled) {
+    nsCOMPtr<nsISupports> entry;
+    rv = entries->GetNext(getter_AddRefs(entry));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+    MOZ_ASSERT(file);
+
+    int64_t fileSize;
+    rv = file->GetFileSize(&fileSize);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    MOZ_ASSERT(fileSize >= 0);
+
+    aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
+                                    const nsACString& aOrigin)
+{
+  AssertIsOnIOThread();
+}
+
+void
+QuotaClient::ReleaseIOThreadObjects()
+{
+  AssertIsOnIOThread();
+}
+
+void
+QuotaClient::AbortOperations(const nsACString& aOrigin)
+{
+  AssertIsOnBackgroundThread();
+
+  if (gOpenConnections) {
+    for (Connection* connection : *gOpenConnections) {
+      if (aOrigin.IsVoid() || connection->Origin() == aOrigin) {
+        connection->AllowToClose();
+      }
+    }
+  }
+}
+
+void
+QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId)
+{
+  AssertIsOnBackgroundThread();
+}
+
+void
+QuotaClient::StartIdleMaintenance()
+{
+  AssertIsOnBackgroundThread();
+}
+
+void
+QuotaClient::StopIdleMaintenance()
+{
+  AssertIsOnBackgroundThread();
+}
+
+void
+QuotaClient::ShutdownWorkThreads()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mShutdownRequested);
+
+  mShutdownRequested = true;
+
+  if (gOpenConnections) {
+    for (Connection* connection : *gOpenConnections) {
+      connection->AllowToClose();
+    }
+
+    MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return !gOpenConnections; }));
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/ActorsParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_simpledb_ActorsParent_h
+#define mozilla_dom_simpledb_ActorsParent_h
+
+template <class> struct already_AddRefed;
+
+namespace mozilla {
+
+namespace ipc {
+
+class PrincipalInfo;
+
+} // namespace ipc
+
+namespace dom {
+
+class PBackgroundSDBConnectionParent;
+
+namespace quota {
+
+class Client;
+
+} // namespace quota
+
+PBackgroundSDBConnectionParent*
+AllocPBackgroundSDBConnectionParent(
+                             const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+bool
+RecvPBackgroundSDBConnectionConstructor(
+                             PBackgroundSDBConnectionParent* aActor,
+                             const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+bool
+DeallocPBackgroundSDBConnectionParent(PBackgroundSDBConnectionParent* aActor);
+
+namespace simpledb {
+
+already_AddRefed<mozilla::dom::quota::Client>
+CreateQuotaClient();
+
+} // namespace simpledb
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_simpledb_ActorsParent_h
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/PBackgroundSDBConnection.ipdl
@@ -0,0 +1,64 @@
+/* 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 PBackground;
+include protocol PBackgroundSDBRequest;
+
+namespace mozilla {
+namespace dom {
+
+struct SDBRequestOpenParams
+{
+  nsString name;
+};
+
+struct SDBRequestSeekParams
+{
+  uint64_t offset;
+};
+
+struct SDBRequestReadParams
+{
+  uint64_t size;
+};
+
+struct SDBRequestWriteParams
+{
+  nsCString data;
+};
+
+struct SDBRequestCloseParams
+{
+};
+
+union SDBRequestParams
+{
+  SDBRequestOpenParams;
+  SDBRequestSeekParams;
+  SDBRequestReadParams;
+  SDBRequestWriteParams;
+  SDBRequestCloseParams;
+};
+
+protocol PBackgroundSDBConnection
+{
+  manager PBackground;
+
+  manages PBackgroundSDBRequest;
+
+parent:
+  async DeleteMe();
+
+  async PBackgroundSDBRequest(SDBRequestParams params);
+
+child:
+  async __delete__();
+
+  async AllowToClose();
+
+  async Closed();
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/PBackgroundSDBRequest.ipdl
@@ -0,0 +1,50 @@
+/* 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 PBackgroundSDBConnection;
+
+namespace mozilla {
+namespace dom {
+
+struct SDBRequestOpenResponse
+{
+};
+
+struct SDBRequestSeekResponse
+{
+};
+
+struct SDBRequestReadResponse
+{
+  nsCString data;
+};
+
+struct SDBRequestWriteResponse
+{
+};
+
+struct SDBRequestCloseResponse
+{
+};
+
+union SDBRequestResponse
+{
+  nsresult;
+  SDBRequestOpenResponse;
+  SDBRequestSeekResponse;
+  SDBRequestReadResponse;
+  SDBRequestWriteResponse;
+  SDBRequestCloseResponse;
+};
+
+protocol PBackgroundSDBRequest
+{
+  manager PBackgroundSDBConnection;
+
+child:
+  async __delete__(SDBRequestResponse response);
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SDBConnection.cpp
@@ -0,0 +1,428 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "SDBConnection.h"
+
+#include "ActorsChild.h"
+#include "jsfriendapi.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsISDBCallbacks.h"
+#include "SDBRequest.h"
+#include "SimpleDBCommon.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+nsresult
+GetWriteData(JSContext* aCx,
+             JS::Handle<JS::Value> aValue,
+             nsCString& aData)
+{
+  if (aValue.isObject()) {
+    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+    bool isView = false;
+    if (JS_IsArrayBufferObject(obj) ||
+        (isView = JS_IsArrayBufferViewObject(obj))) {
+      uint8_t* data;
+      uint32_t length;
+      bool unused;
+      if (isView) {
+        JS_GetObjectAsArrayBufferView(obj, &length, &unused, &data);
+      } else {
+        JS_GetObjectAsArrayBuffer(obj, &length, &data);
+      }
+
+      if (NS_WARN_IF(!aData.Assign(reinterpret_cast<char*>(data),
+                                   length,
+                                   fallible_t()))) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+
+      return NS_OK;
+    }
+  }
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace
+
+SDBConnection::SDBConnection()
+  : mBackgroundActor(nullptr)
+  , mRunningRequest(false)
+  , mOpen(false)
+  , mAllowedToClose(false)
+{
+  AssertIsOnOwningThread();
+}
+
+SDBConnection::~SDBConnection()
+{
+  AssertIsOnOwningThread();
+
+  if (mBackgroundActor) {
+    mBackgroundActor->SendDeleteMeInternal();
+    MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
+  }
+}
+
+// static
+nsresult
+SDBConnection::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+  MOZ_ASSERT(aResult);
+
+  if (NS_WARN_IF(!Preferences::GetBool(kPrefSimpleDBEnabled, false))) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (aOuter) {
+    return NS_ERROR_NO_AGGREGATION;
+  }
+
+  RefPtr<SDBConnection> connection = new SDBConnection();
+
+  nsresult rv = connection->QueryInterface(aIID, aResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+SDBConnection::ClearBackgroundActor()
+{
+  AssertIsOnOwningThread();
+
+  mBackgroundActor = nullptr;
+}
+
+void
+SDBConnection::OnNewRequest()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mRunningRequest);
+
+  mRunningRequest = true;
+}
+
+void
+SDBConnection::OnRequestFinished()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mRunningRequest);
+
+  mRunningRequest = false;
+}
+
+void
+SDBConnection::OnOpen()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mOpen);
+
+  mOpen = true;
+}
+
+void
+SDBConnection::OnClose(bool aAbnormal)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mOpen);
+
+  mOpen = false;
+
+  if (aAbnormal) {
+    MOZ_ASSERT(mAllowedToClose);
+
+    if (mCloseCallback) {
+      mCloseCallback->OnClose(this);
+    }
+  }
+}
+
+void
+SDBConnection::AllowToClose()
+{
+  AssertIsOnOwningThread();
+
+  mAllowedToClose = true;
+}
+
+nsresult
+SDBConnection::CheckState()
+{
+  AssertIsOnOwningThread();
+
+  if (mAllowedToClose) {
+    return NS_ERROR_ABORT;
+  }
+
+  if (mRunningRequest) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+SDBConnection::EnsureBackgroundActor()
+{
+  AssertIsOnOwningThread();
+
+  if (mBackgroundActor) {
+    return NS_OK;
+  }
+
+  PBackgroundChild* backgroundActor =
+    BackgroundChild::GetOrCreateForCurrentThread();
+  if (NS_WARN_IF(!backgroundActor)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  SDBConnectionChild* actor = new SDBConnectionChild(this);
+
+  mBackgroundActor =
+    static_cast<SDBConnectionChild*>(
+      backgroundActor->SendPBackgroundSDBConnectionConstructor(
+                                                              actor,
+                                                              *mPrincipalInfo));
+  if (NS_WARN_IF(!mBackgroundActor)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+SDBConnection::InitiateRequest(SDBRequest* aRequest,
+                               const SDBRequestParams& aParams)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aRequest);
+  MOZ_ASSERT(mBackgroundActor);
+
+  auto actor = new SDBRequestChild(aRequest);
+
+  if (!mBackgroundActor->SendPBackgroundSDBRequestConstructor(actor, aParams)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Balanced in SDBRequestChild::Recv__delete__().
+  OnNewRequest();
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(SDBConnection, nsISDBConnection)
+
+NS_IMETHODIMP
+SDBConnection::Init(nsIPrincipal *aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+
+  nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
+  nsresult rv = PrincipalToPrincipalInfo(aPrincipal, principalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (principalInfo->type() != PrincipalInfo::TContentPrincipalInfo &&
+      principalInfo->type() != PrincipalInfo::TSystemPrincipalInfo) {
+    NS_WARNING("Simpledb not allowed for this principal!");
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mPrincipalInfo = std::move(principalInfo);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBConnection::Open(const nsAString& aName, nsISDBRequest** _retval)
+{
+  AssertIsOnOwningThread();
+
+  nsresult rv = CheckState();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mOpen) {
+    return NS_ERROR_ALREADY_INITIALIZED;
+  }
+
+  SDBRequestOpenParams params;
+  params.name() = aName;
+
+  RefPtr<SDBRequest> request = new SDBRequest(this);
+
+  rv = EnsureBackgroundActor();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = InitiateRequest(request, params);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBConnection::Seek(uint64_t aOffset, nsISDBRequest** _retval)
+{
+  AssertIsOnOwningThread();
+
+  nsresult rv = CheckState();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!mOpen) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  SDBRequestSeekParams params;
+  params.offset() = aOffset;
+
+  RefPtr<SDBRequest> request = new SDBRequest(this);
+
+  rv = InitiateRequest(request, params);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBConnection::Read(uint64_t aSize, nsISDBRequest** _retval)
+{
+  AssertIsOnOwningThread();
+
+  nsresult rv = CheckState();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!mOpen) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  SDBRequestReadParams params;
+  params.size() = aSize;
+
+  RefPtr<SDBRequest> request = new SDBRequest(this);
+
+  rv = InitiateRequest(request, params);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBConnection::Write(JS::HandleValue aValue,
+                     JSContext* aCx,
+                     nsISDBRequest** _retval)
+{
+  AssertIsOnOwningThread();
+
+  nsresult rv = CheckState();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!mOpen) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  JS::Rooted<JS::Value> value(aCx, aValue);
+
+  nsCString data;
+  rv = GetWriteData(aCx, value, data);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  SDBRequestWriteParams params;
+  params.data() = data;
+
+  RefPtr<SDBRequest> request = new SDBRequest(this);
+
+  rv = InitiateRequest(request, params);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBConnection::Close(nsISDBRequest** _retval)
+{
+  AssertIsOnOwningThread();
+
+  nsresult rv = CheckState();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!mOpen) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  SDBRequestCloseParams params;
+
+  RefPtr<SDBRequest> request = new SDBRequest(this);
+
+  rv = InitiateRequest(request, params);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBConnection::GetCloseCallback(nsISDBCloseCallback** aCloseCallback)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aCloseCallback);
+
+  NS_IF_ADDREF(*aCloseCallback = mCloseCallback);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBConnection::SetCloseCallback(nsISDBCloseCallback* aCloseCallback)
+{
+  AssertIsOnOwningThread();
+
+  mCloseCallback = aCloseCallback;
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SDBConnection.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_simpledb_SDBConnection_h
+#define mozilla_dom_simpledb_SDBConnection_h
+
+#include "nsAutoPtr.h"
+#include "nsISDBConnection.h"
+#include "nsTArray.h"
+
+#define NS_SDBCONNECTION_CONTRACTID \
+  "@mozilla.org/dom/sdb-connection;1"
+
+class nsISDBCloseCallback;
+
+namespace mozilla {
+
+namespace ipc {
+
+class PBackgroundChild;
+class PrincipalInfo;
+
+} // namespace ipc
+
+namespace dom {
+
+class SDBConnectionChild;
+class SDBRequest;
+class SDBRequestParams;
+
+class SDBConnection final
+  : public nsISDBConnection
+{
+  typedef mozilla::ipc::PBackgroundChild PBackgroundChild;
+  typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
+  nsCOMPtr<nsISDBCloseCallback> mCloseCallback;
+
+  nsAutoPtr<PrincipalInfo> mPrincipalInfo;
+
+  SDBConnectionChild* mBackgroundActor;
+
+  bool mRunningRequest;
+  bool mOpen;
+  bool mAllowedToClose;
+
+public:
+  static nsresult
+  Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(SDBConnection);
+  }
+
+  void
+  ClearBackgroundActor();
+
+  void
+  OnNewRequest();
+
+  void
+  OnRequestFinished();
+
+  void
+  OnOpen();
+
+  void
+  OnClose(bool aAbnormal);
+
+  void
+  AllowToClose();
+
+private:
+  SDBConnection();
+
+  ~SDBConnection();
+
+  nsresult
+  CheckState();
+
+  nsresult
+  EnsureBackgroundActor();
+
+  nsresult
+  InitiateRequest(SDBRequest* aRequest,
+                  const SDBRequestParams& aParams);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISDBCONNECTION
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_simpledb_SDBConnection_h */
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SDBRequest.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "SDBRequest.h"
+
+#include "nsISDBCallbacks.h"
+#include "nsThreadUtils.h"
+#include "SDBConnection.h"
+
+namespace mozilla {
+namespace dom {
+
+SDBRequest::SDBRequest(SDBConnection* aConnection)
+  : mConnection(aConnection)
+  , mResultCode(NS_OK)
+  , mHaveResultOrErrorCode(false)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aConnection);
+}
+
+SDBRequest::~SDBRequest()
+{
+  AssertIsOnOwningThread();
+}
+
+void
+SDBRequest::SetResult(nsIVariant* aResult)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aResult);
+  MOZ_ASSERT(mConnection);
+  MOZ_ASSERT(!mHaveResultOrErrorCode);
+
+  mResult = aResult;
+  mHaveResultOrErrorCode = true;
+
+  FireCallback();
+}
+
+void
+SDBRequest::SetError(nsresult aRv)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mConnection);
+  MOZ_ASSERT(mResultCode == NS_OK);
+  MOZ_ASSERT(!mHaveResultOrErrorCode);
+
+  mResultCode = aRv;
+  mHaveResultOrErrorCode = true;
+
+  FireCallback();
+}
+
+void
+SDBRequest::FireCallback()
+{
+  AssertIsOnOwningThread();
+
+  if (mCallback) {
+    nsCOMPtr<nsISDBCallback> callback;
+    callback.swap(mCallback);
+
+    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(
+      NewRunnableMethod<RefPtr<SDBRequest>>(
+        "nsISDBCallback::OnComplete",
+        callback,
+        &nsISDBCallback::OnComplete,
+        this)));
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SDBRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SDBRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SDBRequest)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsISDBRequest)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(SDBRequest, mCallback, mResult)
+
+NS_IMETHODIMP
+SDBRequest::GetResult(nsIVariant** aResult)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aResult);
+
+  if (!mHaveResultOrErrorCode) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_IF_ADDREF(*aResult = mResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBRequest::GetResultCode(nsresult* aResultCode)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aResultCode);
+
+  if (!mHaveResultOrErrorCode) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  *aResultCode = mResultCode;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBRequest::GetCallback(nsISDBCallback** aCallback)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aCallback);
+
+  NS_IF_ADDREF(*aCallback = mCallback);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBRequest::SetCallback(nsISDBCallback* aCallback)
+{
+  AssertIsOnOwningThread();
+
+  mCallback = aCallback;
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SDBRequest.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_simpledb_SDBRequest_h
+#define mozilla_dom_simpledb_SDBRequest_h
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISDBRequest.h"
+#include "nsIVariant.h"
+
+class nsISDBCallback;
+
+namespace mozilla {
+namespace dom {
+
+class SDBConnection;
+
+class SDBRequest final
+  : public nsISDBRequest
+{
+  RefPtr<SDBConnection> mConnection;
+
+  nsCOMPtr<nsIVariant> mResult;
+  nsCOMPtr<nsISDBCallback> mCallback;
+
+  nsresult mResultCode;
+  bool mHaveResultOrErrorCode;
+
+public:
+  explicit SDBRequest(SDBConnection* aConnection);
+
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(SDBRequest);
+  }
+
+  SDBConnection*
+  GetConnection() const
+  {
+    AssertIsOnOwningThread();
+
+    return mConnection;
+  }
+
+  void
+  SetResult(nsIVariant* aResult);
+
+  void
+  SetError(nsresult aRv);
+
+private:
+  ~SDBRequest();
+
+  void
+  FireCallback();
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_NSISDBREQUEST
+  NS_DECL_CYCLE_COLLECTION_CLASS(SDBRequest)
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_simpledb_SDBRequest_h
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SDBResults.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 "SDBResults.h"
+
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+SDBResult::SDBResult(const nsACString& aData)
+  : mData(aData)
+{
+}
+
+NS_IMPL_ISUPPORTS(SDBResult,
+                  nsISDBResult)
+
+NS_IMETHODIMP
+SDBResult::GetAsArray(uint32_t* aDataLen, uint8_t** aData)
+{
+  MOZ_ASSERT(aDataLen);
+  MOZ_ASSERT(aData);
+
+  if (mData.IsEmpty()) {
+    *aDataLen = 0;
+    *aData = nullptr;
+    return NS_OK;
+  }
+
+  uint32_t length = mData.Length();
+
+  uint8_t* data = static_cast<uint8_t*>(moz_xmalloc(length * sizeof(uint8_t)));
+  if (!data) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  memcpy(data, mData.BeginReading(), length * sizeof(uint8_t));
+
+  *aDataLen = length;
+  *aData = data;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SDBResult::GetAsArrayBuffer(JSContext* aCx, JS::MutableHandleValue _retval)
+{
+  JS::Rooted<JSObject*> arrayBuffer(aCx);
+  nsresult rv =
+    nsContentUtils::CreateArrayBuffer(aCx, mData, arrayBuffer.address());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  _retval.setObject(*arrayBuffer);
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SDBResults.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_simpledb_SDBResults_h
+#define mozilla_dom_simpledb_SDBResults_h
+
+#include "nsISDBResults.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class SDBResult
+  : public nsISDBResult
+{
+  nsCString mData;
+
+public:
+  explicit SDBResult(const nsACString& aData);
+
+private:
+  virtual ~SDBResult()
+  { }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISDBRESULT
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_simpledb_SDBResults_h
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SimpleDBCommon.cpp
@@ -0,0 +1,15 @@
+/* -*- 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 "SimpleDBCommon.h"
+
+namespace mozilla {
+namespace dom {
+
+const char* kPrefSimpleDBEnabled = "dom.simpleDB.enabled";
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/SimpleDBCommon.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_simpledb_SimpledbCommon_h
+#define mozilla_dom_simpledb_SimpledbCommon_h
+
+namespace mozilla {
+namespace dom {
+
+extern const char* kPrefSimpleDBEnabled;
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_simpledb_SimpledbCommon_h
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/moz.build
@@ -0,0 +1,43 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+XPIDL_SOURCES += [
+    'nsISDBCallbacks.idl',
+    'nsISDBConnection.idl',
+    'nsISDBRequest.idl',
+    'nsISDBResults.idl',
+]
+
+XPIDL_MODULE = 'dom_simpledb'
+
+EXPORTS.mozilla.dom.simpledb += [
+    'ActorsParent.h',
+]
+
+EXPORTS.mozilla.dom += [
+    'SDBConnection.h',
+]
+
+UNIFIED_SOURCES += [
+    'ActorsChild.cpp',
+    'ActorsParent.cpp',
+    'SDBConnection.cpp',
+    'SDBRequest.cpp',
+    'SDBResults.cpp',
+    'SimpleDBCommon.cpp',
+]
+
+IPDL_SOURCES += [
+    'PBackgroundSDBConnection.ipdl',
+    'PBackgroundSDBRequest.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += ['-Wno-error=shadow']
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/nsISDBCallbacks.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "nsISupports.idl"
+
+interface nsISDBConnection;
+interface nsISDBRequest;
+
+[scriptable, function, uuid(8cbd576c-c6bf-42fd-96ee-3b824dafe1d4)]
+interface nsISDBCallback : nsISupports
+{
+  void onComplete(in nsISDBRequest aRequest);
+};
+
+[scriptable, function, uuid(e0821d43-62b9-40fe-99f8-ff9ab3184cbf)]
+interface nsISDBCloseCallback : nsISupports
+{
+  void onClose(in nsISDBConnection aConnection);
+};
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/nsISDBConnection.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsISDBCloseCallback;
+interface nsISDBRequest;
+
+[scriptable, builtinclass, uuid(ea420fdd-548f-44f9-9286-59aad6a40f01)]
+interface nsISDBConnection : nsISupports
+{
+  [must_use] void
+  init(in nsIPrincipal aPrincipal);
+
+  [must_use] nsISDBRequest
+  open(in AString aName);
+
+  [must_use] nsISDBRequest
+  seek(in unsigned long long offset);
+
+  [must_use] nsISDBRequest
+  read(in unsigned long long size);
+
+  [must_use, implicit_jscontext] nsISDBRequest
+  write(in jsval value);
+
+  [must_use] nsISDBRequest
+  close();
+
+  attribute nsISDBCloseCallback closeCallback;
+};
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/nsISDBRequest.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "nsISupports.idl"
+
+interface nsISDBCallback;
+interface nsIVariant;
+
+[scriptable, uuid(13f05bcf-715c-427e-aac8-df9b2c1ec1e3)]
+interface nsISDBRequest : nsISupports
+{
+  [must_use] readonly attribute nsIVariant result;
+
+  [must_use] readonly attribute nsresult resultCode;
+
+  attribute nsISDBCallback callback;
+};
new file mode 100644
--- /dev/null
+++ b/dom/simpledb/nsISDBResults.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "nsISupports.idl"
+
+[scriptable, uuid(bca19e01-b34e-4a48-8875-2f4cb871febf)]
+interface nsISDBResult : nsISupports
+{
+  [must_use] void
+  getAsArray([optional] out uint32_t dataLen,
+             [array, retval, size_is(dataLen)] out uint8_t data);
+
+  [must_use, implicit_jscontext] jsval
+  getAsArrayBuffer();
+};
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -11,16 +11,17 @@
 #include "FileDescriptorSetChild.h"
 #ifdef MOZ_WEBRTC
 #include "CamerasChild.h"
 #endif
 #include "mozilla/media/MediaChild.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/SchedulerGroup.h"
 #include "mozilla/dom/ClientManagerActors.h"
+#include "mozilla/dom/PBackgroundSDBConnectionChild.h"
 #include "mozilla/dom/PFileSystemRequestChild.h"
 #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"
@@ -234,16 +235,34 @@ BackgroundChildImpl::DeallocPBackgroundL
                                       PBackgroundLocalStorageCacheChild* aActor)
 {
   MOZ_ASSERT(aActor);
 
   delete aActor;
   return true;
 }
 
+BackgroundChildImpl::PBackgroundSDBConnectionChild*
+BackgroundChildImpl::AllocPBackgroundSDBConnectionChild(
+                                            const PrincipalInfo& aPrincipalInfo)
+{
+  MOZ_CRASH("PBackgroundSDBConnectionChild actor should be manually "
+            "constructed!");
+}
+
+bool
+BackgroundChildImpl::DeallocPBackgroundSDBConnectionChild(
+                                          PBackgroundSDBConnectionChild* 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(
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -65,16 +65,24 @@ protected:
 
   virtual PBackgroundIndexedDBUtilsChild*
   AllocPBackgroundIndexedDBUtilsChild() override;
 
   virtual bool
   DeallocPBackgroundIndexedDBUtilsChild(PBackgroundIndexedDBUtilsChild* aActor)
                                         override;
 
+  virtual PBackgroundSDBConnectionChild*
+  AllocPBackgroundSDBConnectionChild(const PrincipalInfo& aPrincipalInfo)
+                                     override;
+
+  virtual bool
+  DeallocPBackgroundSDBConnectionChild(PBackgroundSDBConnectionChild* aActor)
+                                       override;
+
   virtual PBackgroundLocalStorageCacheChild*
   AllocPBackgroundLocalStorageCacheChild(const PrincipalInfo& aPrincipalInfo,
                                          const nsCString& aOriginKey,
                                          const uint32_t& aPrivateBrowsingId)
                                          override;
 
   virtual bool
   DeallocPBackgroundLocalStorageCacheChild(
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -29,16 +29,17 @@
 #include "mozilla/dom/StorageActivityService.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/ipc/TemporaryIPCBlobParent.h"
 #include "mozilla/dom/quota/ActorsParent.h"
+#include "mozilla/dom/simpledb/ActorsParent.h"
 #include "mozilla/dom/StorageIPC.h"
 #include "mozilla/dom/MIDIManagerParent.h"
 #include "mozilla/dom/MIDIPortParent.h"
 #include "mozilla/dom/MIDIPlatformService.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/IPCStreamAlloc.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
@@ -247,16 +248,53 @@ BackgroundParentImpl::RecvFlushPendingFi
   AssertIsOnBackgroundThread();
 
   if (!mozilla::dom::indexedDB::RecvFlushPendingFileDeletions()) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
+BackgroundParentImpl::PBackgroundSDBConnectionParent*
+BackgroundParentImpl::AllocPBackgroundSDBConnectionParent(
+                                            const PrincipalInfo& aPrincipalInfo)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  return mozilla::dom::AllocPBackgroundSDBConnectionParent(aPrincipalInfo);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundSDBConnectionConstructor(
+                                         PBackgroundSDBConnectionParent* aActor,
+                                         const PrincipalInfo& aPrincipalInfo)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  if (!mozilla::dom::RecvPBackgroundSDBConnectionConstructor(aActor,
+                                                             aPrincipalInfo)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+bool
+BackgroundParentImpl::DeallocPBackgroundSDBConnectionParent(
+                                         PBackgroundSDBConnectionParent* aActor)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  return mozilla::dom::DeallocPBackgroundSDBConnectionParent(aActor);
+}
+
 BackgroundParentImpl::PBackgroundLocalStorageCacheParent*
 BackgroundParentImpl::AllocPBackgroundLocalStorageCacheParent(
                                             const PrincipalInfo& aPrincipalInfo,
                                             const nsCString& aOriginKey,
                                             const uint32_t& aPrivateBrowsingId)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -58,16 +58,30 @@ protected:
   virtual bool
   DeallocPBackgroundIndexedDBUtilsParent(
                                         PBackgroundIndexedDBUtilsParent* aActor)
                                         override;
 
   virtual mozilla::ipc::IPCResult
   RecvFlushPendingFileDeletions() override;
 
+  virtual PBackgroundSDBConnectionParent*
+  AllocPBackgroundSDBConnectionParent(const PrincipalInfo& aPrincipalInfo)
+                                      override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvPBackgroundSDBConnectionConstructor(
+                                         PBackgroundSDBConnectionParent* aActor,
+                                         const PrincipalInfo& aPrincipalInfo)
+                                         override;
+
+  virtual bool
+  DeallocPBackgroundSDBConnectionParent(PBackgroundSDBConnectionParent* aActor)
+                                        override;
+
   virtual PBackgroundLocalStorageCacheParent*
   AllocPBackgroundLocalStorageCacheParent(const PrincipalInfo& aPrincipalInfo,
                                           const nsCString& aOriginKey,
                                           const uint32_t& aPrivateBrowsingId)
                                           override;
 
   virtual mozilla::ipc::IPCResult
   RecvPBackgroundLocalStorageCacheConstructor(
--- 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 PBackgroundSDBConnection;
 include protocol PBackgroundLocalStorageCache;
 include protocol PBackgroundStorage;
 include protocol PBackgroundTest;
 include protocol PBroadcastChannel;
 include protocol PCache;
 include protocol PCacheStorage;
 include protocol PCacheStreamControl;
 include protocol PClientManager;
@@ -61,16 +62,17 @@ using mozilla::dom::asmjscache::WritePar
 namespace mozilla {
 namespace ipc {
 
 sync protocol PBackground
 {
   manages PAsmJSCacheEntry;
   manages PBackgroundIDBFactory;
   manages PBackgroundIndexedDBUtils;
+  manages PBackgroundSDBConnection;
   manages PBackgroundLocalStorageCache;
   manages PBackgroundStorage;
   manages PBackgroundTest;
   manages PBroadcastChannel;
   manages PCache;
   manages PCacheStorage;
   manages PCacheStreamControl;
   manages PClientManager;
@@ -103,16 +105,18 @@ parent:
 
   async PBackgroundIDBFactory(LoggingInfo loggingInfo);
 
   async PBackgroundIndexedDBUtils();
 
   // Use only for testing!
   async FlushPendingFileDeletions();
 
+  async PBackgroundSDBConnection(PrincipalInfo principalInfo);
+
   async PBackgroundLocalStorageCache(PrincipalInfo principalInfo,
                                      nsCString originKey,
                                      uint32_t privateBrowsingId);
 
   async PBackgroundStorage(nsString profilePath);
 
   async PVsync();
 
--- a/js/src/vm/JSContext-inl.h
+++ b/js/src/vm/JSContext-inl.h
@@ -15,18 +15,16 @@
 #include "vm/HelperThreads.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
 #include "vm/Realm.h"
 #include "vm/SymbolType.h"
 
 namespace js {
 
-#ifdef JS_CRASH_DIAGNOSTICS
-
 class CompartmentChecker
 {
     JS::Compartment* compartment;
 
   public:
     explicit CompartmentChecker(JSContext* cx)
       : compartment(cx->compartment())
     {
@@ -170,18 +168,16 @@ class CompartmentChecker
         check(desc.value(), argIndex);
     }
 
     void check(TypeSet::Type type, int argIndex) {
         check(type.maybeCompartment(), argIndex);
     }
 };
 
-#endif // JS_CRASH_DIAGNOSTICS
-
 /*
  * Don't perform these checks when called from a finalizer. The checking
  * depends on other objects not having been swept yet.
  */
 #define START_ASSERT_SAME_COMPARTMENT()                                 \
     if (JS::RuntimeHeapIsCollecting())                                  \
         return;                                                         \
     CompartmentChecker c(cx)
--- a/js/xpconnect/src/XPCModule.h
+++ b/js/xpconnect/src/XPCModule.h
@@ -35,19 +35,17 @@ NS_DEFINE_NAMED_CID(MOZ_JSSUBSCRIPTLOADE
 
 #define XPCONNECT_CIDENTRIES                                                  \
   { &kNS_JS_ID_CID, false, nullptr,  nsJSIDConstructor },                     \
   { &kNS_XPCONNECT_CID, false, nullptr,  nsIXPConnectConstructor },           \
   { &kMOZJSCOMPONENTLOADER_CID, false, nullptr, mozJSComponentLoaderConstructor },\
   { &kMOZ_JSSUBSCRIPTLOADER_CID, false, nullptr, mozJSSubScriptLoaderConstructor },
 
 #define XPCONNECT_CONTRACTS                                                   \
-  { XPC_ID_CONTRACTID, &kNS_JS_ID_CID },                                      \
   { XPC_XPCONNECT_CONTRACTID, &kNS_XPCONNECT_CID },                           \
-  { XPC_CONTEXT_STACK_CONTRACTID, &kNS_XPCONNECT_CID },                       \
   { MOZJSCOMPONENTLOADER_CONTRACTID, &kMOZJSCOMPONENTLOADER_CID },            \
   { MOZJSSUBSCRIPTLOADER_CONTRACTID, &kMOZ_JSSUBSCRIPTLOADER_CID },
 
 #define XPCONNECT_CATEGORIES \
   { "module-loader", "js", MOZJSCOMPONENTLOADER_CONTRACTID },
 
 nsresult xpcModuleCtor();
 void xpcModuleDtor();
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -51,21 +51,19 @@ NS_IMPL_ISUPPORTS(nsXPConnect, nsIXPConn
 nsXPConnect* nsXPConnect::gSelf = nullptr;
 bool         nsXPConnect::gOnceAliveNowDead = false;
 
 // Global cache of the default script security manager (QI'd to
 // nsIScriptSecurityManager) and the system principal.
 nsIScriptSecurityManager* nsXPConnect::gScriptSecurityManager = nullptr;
 nsIPrincipal* nsXPConnect::gSystemPrincipal = nullptr;
 
-const char XPC_CONTEXT_STACK_CONTRACTID[] = "@mozilla.org/js/xpc/ContextStack;1";
 const char XPC_EXCEPTION_CONTRACTID[]     = "@mozilla.org/js/xpc/Exception;1";
 const char XPC_CONSOLE_CONTRACTID[]       = "@mozilla.org/consoleservice;1";
 const char XPC_SCRIPT_ERROR_CONTRACTID[]  = "@mozilla.org/scripterror;1";
-const char XPC_ID_CONTRACTID[]            = "@mozilla.org/js/xpc/ID;1";
 const char XPC_XPCONNECT_CONTRACTID[]     = "@mozilla.org/js/xpc/XPConnect;1";
 
 /***************************************************************************/
 
 // This global should be used very sparingly: only to create and destroy
 // nsXPConnect and when creating a new cooperative (non-primary) XPCJSContext.
 static XPCJSContext* gPrimaryContext;
 
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -177,21 +177,19 @@ class Exception;
 #define XPC_NATIVE_PROTO_MAP_LENGTH              8
 #define XPC_DYING_NATIVE_PROTO_MAP_LENGTH        8
 #define XPC_NATIVE_INTERFACE_MAP_LENGTH         32
 #define XPC_NATIVE_SET_MAP_LENGTH               32
 #define XPC_WRAPPER_MAP_LENGTH                   8
 
 /***************************************************************************/
 // data declarations...
-extern const char XPC_CONTEXT_STACK_CONTRACTID[];
 extern const char XPC_EXCEPTION_CONTRACTID[];
 extern const char XPC_CONSOLE_CONTRACTID[];
 extern const char XPC_SCRIPT_ERROR_CONTRACTID[];
-extern const char XPC_ID_CONTRACTID[];
 extern const char XPC_XPCONNECT_CONTRACTID[];
 
 /***************************************************************************/
 // Useful macros...
 
 #define XPC_STRING_GETTER_BODY(dest, src)                                     \
     NS_ENSURE_ARG_POINTER(dest);                                              \
     *dest = src ? moz_xstrdup(src) : nullptr;                                 \
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1484094-1-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+  <meta charset="utf-8">
+  <script type="text/javascript">
+function start()
+{
+  var input = document.querySelector("input");
+  input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+  input.addEventListener("focus", () => {
+    input.setSelectionRange(input.value.length, input.value.length);
+    document.documentElement.removeAttribute("class");
+  });
+  input.focus();
+}
+  </script>
+</head>
+<body onload="start()">
+  <input type="text"></input>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1484094-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+  <meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript">
+function start()
+{
+  var input = document.querySelector("input");
+  input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+  input.addEventListener("focus", () => {
+    input.setSelectionRange(0, 0);
+    synthesizeKey("KEY_ArrowRight");
+    document.documentElement.removeAttribute("class");
+  });
+  input.focus();
+}
+  </script>
+</head>
+<body onload="start()">
+  <input type="text"></input>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1484094-2-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+  <meta charset="utf-8">
+  <script type="text/javascript">
+function start()
+{
+  var input = document.querySelector("input");
+  input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+  input.addEventListener("focus", () => {
+    input.setSelectionRange(0, 0);
+    document.documentElement.removeAttribute("class");
+  });
+  input.focus();
+}
+  </script>
+</head>
+<body onload="start()">
+  <input type="text"></input>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1484094-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+  <meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript">
+function start()
+{
+  var input = document.querySelector("input");
+  input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+  input.addEventListener("focus", () => {
+    input.setSelectionRange(input.value.length, input.value.length);
+    synthesizeKey("KEY_ArrowLeft");
+    document.documentElement.removeAttribute("class");
+  });
+  input.focus();
+}
+  </script>
+</head>
+<body onload="start()">
+  <input type="text"></input>
+</body>
+</html>
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -312,16 +312,20 @@ support-files =
   bug1415416.html
   bug1415416-ref.html
   bug1423331-1.html
   bug1423331-1-ref.html
   bug1423331-2.html
   bug1423331-2-ref.html
   bug1423331-3.html
   bug1423331-4.html
+  bug1484094-1.html
+  bug1484094-1-ref.html
+  bug1484094-2.html
+  bug1484094-2-ref.html
   image_rgrg-256x256.png
   input-invalid-ref.html
   input-maxlength-invalid-change.html
   input-maxlength-ui-invalid-change.html
   input-maxlength-ui-valid-change.html
   input-maxlength-valid-before-change.html
   input-maxlength-valid-change.html
   input-minlength-invalid-change.html
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -194,16 +194,18 @@ var tests = [
     [ 'bug1354478-6.html' , 'bug1354478-6-ref.html'] ,
     [ 'bug1359411.html'   , 'bug1359411-ref.html' ] ,
     [ 'bug1415416.html'   , 'bug1415416-ref.html' ] ,
     [ 'bug1423331-1.html' , 'bug1423331-1-ref.html' ] ,
     [ 'bug1423331-2.html' , 'bug1423331-2-ref.html' ] ,
     // FIXME(bug 1434949): These two fail in some platforms.
     // [ 'bug1423331-3.html' , 'bug1423331-1-ref.html' ] ,
     // [ 'bug1423331-4.html' , 'bug1423331-2-ref.html' ] ,
+    [ 'bug1484094-1.html' , 'bug1484094-1-ref.html' ] ,
+    [ 'bug1484094-2.html' , 'bug1484094-2-ref.html' ] ,
     function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled']]}, nextTest);} ,
 ];
 
 if (!navigator.appVersion.includes("Android")) {
   tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]);
   tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]);
   tests.push([ 'bug923376.html'   , 'bug923376-ref.html'   ]);
   tests.push(function() {SpecialPowers.pushPrefEnv({'set': [['layout.css.overflow-clip-box.enabled', true]]}, nextTest);});
--- a/layout/build/nsLayoutCID.h
+++ b/layout/build/nsLayoutCID.h
@@ -26,16 +26,20 @@
 // {3B581FD4-3497-426c-8F61-3658B971CB80}
 #define NS_TREEBOXOBJECT_CID \
 { 0x3b581fd4, 0x3497, 0x426c, { 0x8f, 0x61, 0x36, 0x58, 0xb9, 0x71, 0xcb, 0x80 } }
 
 // {2fe88332-31c6-4829-b247-a07d8a73e80f}
 #define NS_CANVASRENDERINGCONTEXTWEBGL_CID \
 { 0x2fe88332, 0x31c6, 0x4829, { 0xb2, 0x47, 0xa0, 0x7d, 0x8a, 0x7e, 0xe8, 0x0fe } }
 
+// {ae2793c0-2ba3-4adb-9c5e-c23525812c64}
+#define NS_SDBCONNECTION_CID \
+{ 0xae2793c0, 0x2ba3, 0x4adb, { 0x9c, 0x5e, 0xc2, 0x35, 0x25, 0x81, 0x2c, 0x64 } }
+
 // {A746DECD-AE74-4d86-8E75-4FDA81A9BE90}
 #define NS_DOMSESSIONSTORAGEMANAGER_CID               \
 { 0xa746decd, 0xae74, 0x4d86, { 0x8e, 0x75, 0x4f, 0xda, 0x81, 0xa9, 0xbe, 0x90 } }
 
 // {656DB07C-AA80-49e4-BCE8-E431BAAE697D}
 #define NS_DOMLOCALSTORAGEMANAGER_CID               \
 { 0x656db07c, 0xaa80, 0x49e4, { 0xbc, 0xe8, 0xe4, 0x31, 0xba, 0xae, 0x69, 0x7d } }
 
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -61,16 +61,17 @@
 #include "nsGlobalWindowCommands.h"
 #include "nsIControllerCommandTable.h"
 #include "nsJSProtocolHandler.h"
 #include "nsIControllerContext.h"
 #include "nsZipArchive.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BlobURL.h"
 #include "mozilla/dom/DOMRequest.h"
+#include "mozilla/dom/SDBConnection.h"
 #include "mozilla/dom/LocalStorageManager.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/SessionStorageManager.h"
 #include "mozilla/dom/StorageActivityService.h"
 #include "mozilla/dom/WorkerDebuggerManager.h"
 #include "mozilla/dom/Notification.h"
@@ -529,16 +530,17 @@ NS_DEFINE_NAMED_CID(NS_JSURIMUTATOR_CID)
 NS_DEFINE_NAMED_CID(NS_WINDOWCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_WINDOWCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_PLUGINDOCLOADERFACTORY_CID);
 NS_DEFINE_NAMED_CID(NS_PLUGINDOCUMENT_CID);
 NS_DEFINE_NAMED_CID(NS_VIDEODOCUMENT_CID);
 NS_DEFINE_NAMED_CID(NS_STYLESHEETSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURI_CID);
 NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURIMUTATOR_CID);
+NS_DEFINE_NAMED_CID(NS_SDBCONNECTION_CID);
 NS_DEFINE_NAMED_CID(NS_DOMSESSIONSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMLOCALSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 NS_DEFINE_NAMED_CID(QUOTAMANAGER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID);
 NS_DEFINE_NAMED_CID(STORAGEACTIVITYSERVICE_CID);
 NS_DEFINE_NAMED_CID(NOTIFICATIONTELEMETRYSERVICE_CID);
@@ -766,16 +768,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_WINDOWCOMMANDTABLE_CID, false, nullptr, CreateWindowCommandTableConstructor },
   { &kNS_WINDOWCONTROLLER_CID, false, nullptr, CreateWindowControllerWithSingletonCommandTable },
   { &kNS_PLUGINDOCLOADERFACTORY_CID, false, nullptr, CreateContentDLF },
   { &kNS_PLUGINDOCUMENT_CID, false, nullptr, CreatePluginDocument },
   { &kNS_VIDEODOCUMENT_CID, false, nullptr, CreateVideoDocument },
   { &kNS_STYLESHEETSERVICE_CID, false, nullptr, nsStyleSheetServiceConstructor },
   { &kNS_HOSTOBJECTURI_CID, false, nullptr, BlobURLMutatorConstructor }, // do_CreateInstance returns mutator
   { &kNS_HOSTOBJECTURIMUTATOR_CID, false, nullptr, BlobURLMutatorConstructor },
+  { &kNS_SDBCONNECTION_CID, false, nullptr, SDBConnection::Create },
   { &kNS_DOMSESSIONSTORAGEMANAGER_CID, false, nullptr, SessionStorageManagerConstructor },
   { &kNS_DOMLOCALSTORAGEMANAGER_CID, false, nullptr, LocalStorageManagerConstructor },
   { &kNS_TEXTEDITOR_CID, false, nullptr, TextEditorConstructor },
   { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor },
   { &kQUOTAMANAGER_SERVICE_CID, false, nullptr, QuotaManagerServiceConstructor },
   { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor },
   { &kSTORAGEACTIVITYSERVICE_CID, false, nullptr, StorageActivityServiceConstructor },
   { &kNOTIFICATIONTELEMETRYSERVICE_CID, false, nullptr, NotificationTelemetryServiceConstructor },
@@ -866,16 +869,17 @@ static const mozilla::Module::ContractID
 #ifdef MOZ_XUL
   { "@mozilla.org/xul/xul-sort-service;1", &kNS_XULSORTSERVICE_CID },
 #endif
   { CONTENT_DLF_CONTRACTID, &kNS_CONTENT_DOCUMENT_LOADER_FACTORY_CID },
   { NS_JSPROTOCOLHANDLER_CONTRACTID, &kNS_JSPROTOCOLHANDLER_CID },
   { NS_WINDOWCONTROLLER_CONTRACTID, &kNS_WINDOWCONTROLLER_CID },
   { PLUGIN_DLF_CONTRACTID, &kNS_PLUGINDOCLOADERFACTORY_CID },
   { NS_STYLESHEETSERVICE_CONTRACTID, &kNS_STYLESHEETSERVICE_CID },
+  { NS_SDBCONNECTION_CONTRACTID, &kNS_SDBCONNECTION_CID },
   { "@mozilla.org/dom/localStorage-manager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   // Keeping the old ContractID for backward compatibility
   { "@mozilla.org/dom/storagemanager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/sessionStorage-manager;1", &kNS_DOMSESSIONSTORAGEMANAGER_CID },
   { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
   { QUOTAMANAGER_SERVICE_CONTRACTID, &kQUOTAMANAGER_SERVICE_CID },
   { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID },
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -7989,43 +7989,46 @@ IsAcceptableCaretPosition(const gfxSkipC
 {
   if (aIter.IsOriginalCharSkipped())
     return false;
   uint32_t index = aIter.GetSkippedOffset();
   if (aRespectClusters && !aTextRun->IsClusterStart(index))
     return false;
   if (index > 0) {
     // Check whether the proposed position is in between the two halves of a
-    // surrogate pair, or before a Variation Selector character;
-    // if so, this is not a valid character boundary.
+    // surrogate pair, before a Variation Selector character, or within a
+    // ligated emoji sequence; if so, this is not a valid character boundary.
     // (In the case where we are respecting clusters, we won't actually get
     // this far because the low surrogate is also marked as non-clusterStart
     // so we'll return FALSE above.)
     uint32_t offs = aIter.GetOriginalOffset();
     const nsTextFragment* frag = aFrame->GetContent()->GetText();
     uint32_t ch = frag->CharAt(offs);
 
     if (gfxFontUtils::IsVarSelector(ch) ||
         (NS_IS_LOW_SURROGATE(ch) && offs > 0 &&
-         NS_IS_HIGH_SURROGATE(frag->CharAt(offs - 1)))) {
+         NS_IS_HIGH_SURROGATE(frag->CharAt(offs - 1))) ||
+        (!aTextRun->IsLigatureGroupStart(index) &&
+         unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault)) {
       return false;
     }
 
     // If the proposed position is before a high surrogate, we need to decode
     // the surrogate pair (if valid) and check the resulting character.
     if (NS_IS_HIGH_SURROGATE(ch) && offs + 1 < frag->GetLength()) {
       uint32_t ch2 = frag->CharAt(offs + 1);
       if (NS_IS_LOW_SURROGATE(ch2)) {
         ch = SURROGATE_TO_UCS4(ch, ch2);
         // If the character is a (Plane-14) variation selector,
-        // or a Regional Indicator character that is ligated with the previous
-        // character, this is not a valid boundary.
+        // or an emoji character that is ligated with the previous
+        // character (i.e. part of a Regional-Indicator flag pair,
+        // or an emoji-ZWJ sequence), this is not a valid boundary.
         if (gfxFontUtils::IsVarSelector(ch) ||
-            (gfxFontUtils::IsRegionalIndicator(ch) &&
-             !aTextRun->IsLigatureGroupStart(index))) {
+            (!aTextRun->IsLigatureGroupStart(index) &&
+             unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault)) {
           return false;
         }
       }
     }
   }
   return true;
 }