Bug 1390489 - Port StatementRow to WebIDL bindings. r=asuth,qdot
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 22 Aug 2017 09:25:37 +0200
changeset 428111 89df4227af85ff946e1611e73136c3525fdde353
parent 428110 b0142c177ac0d9577cd0c5da75069e57272a0f7d
child 428112 7e6cd9dc49d54e193186c28fbe000da3630ca7e9
push id1567
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 12:36:05 +0000
treeherdermozilla-release@e512c14a0406 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, qdot
bugs1390489
milestone57.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
Bug 1390489 - Port StatementRow to WebIDL bindings. r=asuth,qdot
dom/bindings/Bindings.conf
dom/webidl/MozStorageStatementRow.webidl
dom/webidl/moz.build
storage/moz.build
storage/mozIStorageStatementRow.idl
storage/mozStorageStatement.h
storage/mozStorageStatementJSHelper.cpp
storage/mozStorageStatementJSHelper.h
storage/mozStorageStatementRow.cpp
storage/mozStorageStatementRow.h
storage/test/unit/test_js_helpers.js
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -617,16 +617,21 @@ DOMInterfaces = {
 'MozWakeLock': {
     'nativeType': 'mozilla::dom::WakeLock',
 },
 
 'MozTimeManager': {
     'nativeType': 'mozilla::dom::time::TimeManager',
 },
 
+'MozStorageStatementRow': {
+    'headerFile': 'mozilla/storage/mozStorageStatementRow.h',
+    'nativeType': 'mozilla::storage::StatementRow',
+},
+
 'MutationObserver': {
     'nativeType': 'nsDOMMutationObserver',
 },
 
 'MutationRecord': {
     'nativeType': 'nsDOMMutationRecord',
     'headerFile': 'nsDOMMutationObserver.h',
 },
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozStorageStatementRow.webidl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+[ChromeOnly]
+interface MozStorageStatementRow
+{
+  [Throws]
+  getter any(DOMString name);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -693,16 +693,17 @@ WEBIDL_FILES = [
     'MessageChannel.webidl',
     'MessageEvent.webidl',
     'MessagePort.webidl',
     'MimeType.webidl',
     'MimeTypeArray.webidl',
     'MouseEvent.webidl',
     'MouseScrollEvent.webidl',
     'MozSelfSupport.webidl',
+    'MozStorageStatementRow.webidl',
     'MozTimeManager.webidl',
     'MozWakeLock.webidl',
     'MutationEvent.webidl',
     'MutationObserver.webidl',
     'NamedNodeMap.webidl',
     'NativeOSFileInternals.webidl',
     'NetDashboard.webidl',
     'NetworkInformation.webidl',
--- a/storage/moz.build
+++ b/storage/moz.build
@@ -25,17 +25,16 @@ XPIDL_SOURCES += [
     'mozIStoragePendingStatement.idl',
     'mozIStorageProgressHandler.idl',
     'mozIStorageResultSet.idl',
     'mozIStorageRow.idl',
     'mozIStorageService.idl',
     'mozIStorageStatement.idl',
     'mozIStorageStatementCallback.idl',
     'mozIStorageStatementParams.idl',
-    'mozIStorageStatementRow.idl',
     'mozIStorageVacuumParticipant.idl',
     'mozIStorageValueArray.idl',
 ]
 
 XPIDL_MODULE = 'storage'
 
 EXPORTS += [
     'mozStorageHelper.h',
@@ -43,16 +42,17 @@ EXPORTS += [
 
 EXPORTS.mozilla += [
     'storage.h',
 ]
 
 # NOTE When adding something to this list, you probably need to add it to the
 #      storage.h file too.
 EXPORTS.mozilla.storage += [
+    'mozStorageStatementRow.h',
     'StatementCache.h',
     'Variant.h',
     'Variant_inl.h',
 ]
 # SEE ABOVE NOTE!
 
 UNIFIED_SOURCES += [
     'FileSystemModule.cpp',
deleted file mode 100644
--- a/storage/mozIStorageStatementRow.idl
+++ /dev/null
@@ -1,12 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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(02eeaf95-c3db-4182-9340-222c29f68f02)]
-interface mozIStorageStatementRow : nsISupports {
-  // Magic interface we return that implements nsIXPCScriptable, to allow
-  // for by-name access to rows.
-};
--- a/storage/mozStorageStatement.h
+++ b/storage/mozStorageStatement.h
@@ -21,16 +21,17 @@
 
 class nsIXPConnectJSObjectHolder;
 struct sqlite3_stmt;
 
 namespace mozilla {
 namespace storage {
 class StatementJSHelper;
 class Connection;
+class StatementRowHolder;
 
 class Statement final : public mozIStorageStatement
                       , public mozIStorageValueArray
                       , public StorageBaseStatementInternal
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_MOZISTORAGESTATEMENT
@@ -92,27 +93,33 @@ private:
      */
     RefPtr<BindingParamsArray> mParamsArray;
 
     /**
      * The following two members are only used with the JS helper.  They cache
      * the row and params objects.
      */
     nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
-    nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementRowHolder;
+    nsMainThreadPtrHandle<StatementRowHolder> mStatementRowHolder;
 
   /**
    * Internal version of finalize that allows us to tell it if it is being
    * called from the destructor so it can know not to dispatch events that
    * require a reference to us.
    *
    * @param aDestructing
    *        Is the destructor calling?
    */
   nsresult internalFinalize(bool aDestructing);
 
   friend class StatementJSHelper;
 };
 
+inline nsISupports*
+ToSupports(Statement* p)
+{
+  return NS_ISUPPORTS_CAST(mozIStorageStatement*, p);
+}
+
 } // namespace storage
 } // namespace mozilla
 
 #endif // mozStorageStatement_h
--- a/storage/mozStorageStatementJSHelper.cpp
+++ b/storage/mozStorageStatementJSHelper.cpp
@@ -82,49 +82,50 @@ stepFunc(JSContext *aCtx,
 
 nsresult
 StatementJSHelper::getRow(Statement *aStatement,
                           JSContext *aCtx,
                           JSObject *aScopeObj,
                           JS::Value *_row)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsresult rv;
 
 #ifdef DEBUG
   int32_t state;
   (void)aStatement->GetState(&state);
   NS_ASSERTION(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING,
                "Invalid state to get the row object - all calls will fail!");
 #endif
 
+  JS::RootedObject scope(aCtx, aScopeObj);
+
   if (!aStatement->mStatementRowHolder) {
-    JS::RootedObject scope(aCtx, aScopeObj);
-    nsCOMPtr<mozIStorageStatementRow> row(new StatementRow(aStatement));
+    dom::GlobalObject global(aCtx, scope);
+    if (global.Failed()) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global.GetAsSupports());
+
+    RefPtr<StatementRow> row(new StatementRow(window, aStatement));
     NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
 
-    nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
-    nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
-    rv = xpc->WrapNativeHolder(
-      aCtx,
-      ::JS_GetGlobalForObject(aCtx, scope),
-      row,
-      NS_GET_IID(mozIStorageStatementRow),
-      getter_AddRefs(holder)
-    );
-    NS_ENSURE_SUCCESS(rv, rv);
-    RefPtr<StatementRowHolder> rowHolder = new StatementRowHolder(holder);
+    RefPtr<StatementRowHolder> rowHolder = new StatementRowHolder(row);
+    NS_ENSURE_TRUE(rowHolder, NS_ERROR_OUT_OF_MEMORY);
+
     aStatement->mStatementRowHolder =
-      new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(
+      new nsMainThreadPtrHolder<StatementRowHolder>(
         "Statement::mStatementRowHolder", rowHolder);
   }
 
-  JS::Rooted<JSObject*> obj(aCtx);
-  obj = aStatement->mStatementRowHolder->GetJSObject();
-  NS_ENSURE_STATE(obj);
+  RefPtr<StatementRow> row(aStatement->mStatementRowHolder->Get());
+  JSObject* obj = row->WrapObject(aCtx, nullptr);
+  if (!obj) {
+    return NS_ERROR_UNEXPECTED;
+  }
 
   _row->setObject(*obj);
   return NS_OK;
 }
 
 nsresult
 StatementJSHelper::getParams(Statement *aStatement,
                              JSContext *aCtx,
@@ -265,21 +266,20 @@ StatementParamsHolder::~StatementParamsH
   // We are considered dead at this point, so any wrappers for row or params
   // need to lose their reference to the statement.
   nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
   nsCOMPtr<mozIStorageStatementParams> iObj = do_QueryWrappedNative(wrapper);
   StatementParams *obj = static_cast<StatementParams *>(iObj.get());
   obj->mStatement = nullptr;
 }
 
+NS_IMPL_ISUPPORTS0(StatementRowHolder);
+
 StatementRowHolder::~StatementRowHolder()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // We are considered dead at this point, so any wrappers for row or params
   // need to lose their reference to the statement.
-  nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
-  nsCOMPtr<mozIStorageStatementRow> iObj = do_QueryWrappedNative(wrapper);
-  StatementRow *obj = static_cast<StatementRow *>(iObj.get());
-  obj->mStatement = nullptr;
+  mRow->mStatement = nullptr;
 }
 
 } // namespace storage
 } // namespace mozilla
--- a/storage/mozStorageStatementJSHelper.h
+++ b/storage/mozStorageStatementJSHelper.h
@@ -10,16 +10,18 @@
 #include "nsIXPCScriptable.h"
 #include "nsIXPConnect.h"
 
 class Statement;
 
 namespace mozilla {
 namespace storage {
 
+class StatementRow;
+
 class StatementJSHelper : public nsIXPCScriptable
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIXPCSCRIPTABLE
 
 private:
   nsresult getRow(Statement *, JSContext *, JSObject *, JS::Value *);
@@ -49,22 +51,32 @@ public:
   explicit StatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder)
     : StatementJSObjectHolder(aHolder) {
   }
 
 private:
   virtual ~StatementParamsHolder();
 };
 
-class StatementRowHolder final: public StatementJSObjectHolder {
+class StatementRowHolder final: public nsISupports {
 public:
-  explicit StatementRowHolder(nsIXPConnectJSObjectHolder* aHolder)
-    : StatementJSObjectHolder(aHolder) {
+  NS_DECL_ISUPPORTS
+
+  explicit StatementRowHolder(StatementRow* aRow)
+    : mRow(aRow)
+  {
+  }
+
+  StatementRow* Get() const {
+    MOZ_ASSERT(mRow);
+    return mRow;
   }
 
 private:
   virtual ~StatementRowHolder();
+
+  RefPtr<StatementRow> mRow;
 };
 
 } // namespace storage
 } // namespace mozilla
 
 #endif // MOZSTORAGESTATEMENTJSHELPER_H
--- a/storage/mozStorageStatementRow.cpp
+++ b/storage/mozStorageStatementRow.cpp
@@ -2,156 +2,157 @@
  * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
  * 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 "nsMemory.h"
 #include "nsString.h"
 
+#include "mozilla/dom/MozStorageStatementRowBinding.h"
 #include "mozStorageStatementRow.h"
 #include "mozStorageStatement.h"
 
 #include "jsapi.h"
 
 #include "xpc_make_class.h"
 
 namespace mozilla {
 namespace storage {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// StatementRow
 
-StatementRow::StatementRow(Statement *aStatement)
-: mStatement(aStatement)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(StatementRow, mWindow)
+
+NS_INTERFACE_TABLE_HEAD(StatementRow)
+  NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+  NS_INTERFACE_TABLE(StatementRow, nsISupports)
+  NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(StatementRow)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StatementRow)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StatementRow)
+
+StatementRow::StatementRow(nsPIDOMWindowInner* aWindow, Statement *aStatement)
+: mWindow(aWindow),
+  mStatement(aStatement)
 {
 }
 
-NS_IMPL_ISUPPORTS(
-  StatementRow,
-  mozIStorageStatementRow,
-  nsIXPCScriptable
-)
-
-////////////////////////////////////////////////////////////////////////////////
-//// nsIXPCScriptable
-
-#define XPC_MAP_CLASSNAME         StatementRow
-#define XPC_MAP_QUOTED_CLASSNAME "StatementRow"
-#define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_GETPROPERTY | \
-                       XPC_SCRIPTABLE_WANT_RESOLVE | \
-                       XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
-#include "xpc_map_end.h"
-
-NS_IMETHODIMP
-StatementRow::GetProperty(nsIXPConnectWrappedNative *aWrapper,
-                          JSContext *aCtx,
-                          JSObject *aScopeObj,
-                          jsid aId,
-                          JS::Value *_vp,
-                          bool *_retval)
+JSObject*
+StatementRow::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
-  NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
-
-  JS::RootedObject scope(aCtx, aScopeObj);
-  if (JSID_IS_STRING(aId)) {
-    ::JSAutoByteString idBytes(aCtx, JSID_TO_STRING(aId));
-    NS_ENSURE_TRUE(!!idBytes, NS_ERROR_OUT_OF_MEMORY);
-    nsDependentCString jsid(idBytes.ptr());
-
-    uint32_t idx;
-    nsresult rv = mStatement->GetColumnIndex(jsid, &idx);
-    NS_ENSURE_SUCCESS(rv, rv);
-    int32_t type;
-    rv = mStatement->GetTypeOfIndex(idx, &type);
-    NS_ENSURE_SUCCESS(rv, rv);
+  return dom::MozStorageStatementRowBinding::Wrap(aCx, this, aGivenProto);
+}
 
-    if (type == mozIStorageValueArray::VALUE_TYPE_INTEGER ||
-        type == mozIStorageValueArray::VALUE_TYPE_FLOAT) {
-      double dval;
-      rv = mStatement->GetDouble(idx, &dval);
-      NS_ENSURE_SUCCESS(rv, rv);
-      *_vp = ::JS_NumberValue(dval);
-    }
-    else if (type == mozIStorageValueArray::VALUE_TYPE_TEXT) {
-      uint32_t bytes;
-      const char16_t *sval = reinterpret_cast<const char16_t *>(
-        static_cast<mozIStorageStatement *>(mStatement)->
-          AsSharedWString(idx, &bytes)
-      );
-      JSString *str = ::JS_NewUCStringCopyN(aCtx, sval, bytes / 2);
-      if (!str) {
-        *_retval = false;
-        return NS_OK;
-      }
-      _vp->setString(str);
-    }
-    else if (type == mozIStorageValueArray::VALUE_TYPE_BLOB) {
-      uint32_t length;
-      const uint8_t *blob = static_cast<mozIStorageStatement *>(mStatement)->
-        AsSharedBlob(idx, &length);
-      JSObject *obj = ::JS_NewArrayObject(aCtx, length);
-      if (!obj) {
-        *_retval = false;
-        return NS_OK;
-      }
-      _vp->setObject(*obj);
-
-      // Copy the blob over to the JS array.
-      for (uint32_t i = 0; i < length; i++) {
-        if (!::JS_DefineElement(aCtx, scope, i, blob[i], JSPROP_ENUMERATE)) {
-          *_retval = false;
-          return NS_OK;
-        }
-      }
-    }
-    else if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
-      _vp->setNull();
-    }
-    else {
-      NS_ERROR("unknown column type returned, what's going on?");
-    }
+void
+StatementRow::NamedGetter(JSContext* aCx,
+                          const nsAString& aName,
+                          bool& aFound,
+                          JS::MutableHandle<JS::Value> aResult,
+                          mozilla::ErrorResult& aRv)
+{
+  if (!mStatement) {
+    aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+    return;
   }
 
-  return NS_OK;
-}
+  nsCString name = NS_ConvertUTF16toUTF8(aName);
 
-NS_IMETHODIMP
-StatementRow::Resolve(nsIXPConnectWrappedNative *aWrapper,
-                      JSContext *aCtx,
-                      JSObject *aScopeObj,
-                      jsid aId,
-                      bool *aResolvedp,
-                      bool *_retval)
-{
-  JS::Rooted<JSObject*> scopeObj(aCtx, aScopeObj);
-
-  NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
-  // We do not throw at any point after this because we want to allow the
-  // prototype chain to be checked for the property.
-
-  if (JSID_IS_STRING(aId)) {
-    ::JSAutoByteString idBytes(aCtx, JSID_TO_STRING(aId));
-    NS_ENSURE_TRUE(!!idBytes, NS_ERROR_OUT_OF_MEMORY);
-    nsDependentCString name(idBytes.ptr());
-
-    uint32_t idx;
+  uint32_t idx;
+  {
     nsresult rv = mStatement->GetColumnIndex(name, &idx);
     if (NS_FAILED(rv)) {
       // It's highly likely that the name doesn't exist, so let the JS engine
       // check the prototype chain and throw if that doesn't have the property
       // either.
-      *aResolvedp = false;
-      return NS_OK;
+      aFound = false;
+      return;
     }
+  }
 
-    JS::Rooted<jsid> id(aCtx, aId);
-    *_retval = ::JS_DefinePropertyById(aCtx, scopeObj, id, JS::UndefinedHandleValue,
-                                       JSPROP_RESOLVING);
-    *aResolvedp = true;
-    return NS_OK;
+  int32_t type;
+  aRv = mStatement->GetTypeOfIndex(idx, &type);
+  if (aRv.Failed()) {
+    return;
   }
 
-  return NS_OK;
+  switch (type) {
+  case mozIStorageValueArray::VALUE_TYPE_INTEGER:
+  case mozIStorageValueArray::VALUE_TYPE_FLOAT: {
+    double dval;
+    aRv = mStatement->GetDouble(idx, &dval);
+    if (aRv.Failed()) {
+      return;
+    }
+    aResult.set(::JS_NumberValue(dval));
+    break;
+  }
+  case mozIStorageValueArray::VALUE_TYPE_TEXT: {
+    uint32_t bytes;
+    const char16_t *sval = reinterpret_cast<const char16_t *>(
+        static_cast<mozIStorageStatement *>(mStatement)->
+          AsSharedWString(idx, &bytes)
+      );
+    JSString *str = ::JS_NewUCStringCopyN(aCx, sval, bytes / 2);
+    if (!str) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return;
+    }
+    aResult.setString(str);
+    break;
+  }
+  case mozIStorageValueArray::VALUE_TYPE_BLOB: {
+    uint32_t length;
+    const uint8_t *blob = static_cast<mozIStorageStatement *>(mStatement)->
+      AsSharedBlob(idx, &length);
+    JS::Rooted<JSObject*> obj(aCx, ::JS_NewArrayObject(aCx, length));
+    if (!obj) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return;
+    }
+    aResult.setObject(*obj);
+
+    // Copy the blob over to the JS array.
+    for (uint32_t i = 0; i < length; i++) {
+      if (!::JS_DefineElement(aCx, obj, i, blob[i], JSPROP_ENUMERATE)) {
+        aRv.Throw(NS_ERROR_UNEXPECTED);
+        return;
+      }
+    }
+    break;
+  }
+  case mozIStorageValueArray::VALUE_TYPE_NULL:
+    aResult.setNull();
+    break;
+  default:
+    NS_ERROR("unknown column type returned, what's going on?");
+    break;
+  }
+  aFound = true;
+}
+
+void
+StatementRow::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+  if (!mStatement) {
+    return;
+  }
+
+  uint32_t columnCount;
+  nsresult rv = mStatement->GetColumnCount(&columnCount);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < columnCount; i++) {
+    nsAutoCString name;
+    nsresult rv = mStatement->GetColumnName(i, name);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+    aNames.AppendElement(NS_ConvertUTF8toUTF16(name));
+  }
 }
 
 } // namespace storage
 } // namespace mozilla
--- a/storage/mozStorageStatementRow.h
+++ b/storage/mozStorageStatementRow.h
@@ -2,38 +2,53 @@
  * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
  * 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 MOZSTORAGESTATEMENTROW_H
 #define MOZSTORAGESTATEMENTROW_H
 
-#include "mozIStorageStatementRow.h"
-#include "nsIXPCScriptable.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsPIDOMWindow.h"
+#include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace storage {
 
 class Statement;
 
-class StatementRow final : public mozIStorageStatementRow
-                         , public nsIXPCScriptable
+class StatementRow final : public nsISupports
+                         , public nsWrapperCache
 {
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_MOZISTORAGESTATEMENTROW
-  NS_DECL_NSIXPCSCRIPTABLE
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StatementRow)
+
+  explicit StatementRow(nsPIDOMWindowInner* aWindow, Statement *aStatement);
 
-  explicit StatementRow(Statement *aStatement);
-protected:
+  void NamedGetter(JSContext* aCx,
+                   const nsAString& aName,
+                   bool& aFound,
+                   JS::MutableHandle<JS::Value> aResult,
+                   mozilla::ErrorResult& aRv);
+  void GetSupportedNames(nsTArray<nsString>& aNames);
 
+  JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsPIDOMWindowInner* GetParentObject() const
+  {
+    return mWindow;
+  }
+
+private:
   ~StatementRow() {}
 
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
   Statement *mStatement;
 
   friend class StatementRowHolder;
 };
 
 } // namespace storage
 } // namespace mozilla
 
--- a/storage/test/unit/test_js_helpers.js
+++ b/storage/test/unit/test_js_helpers.js
@@ -19,27 +19,30 @@ function test_params_enumerate() {
   // Make sure they are right.
   let expected = ["a", "b", "c"];
   let index = 0;
   for (let name in stmt.params) {
     if (name == "QueryInterface")
       continue;
     do_check_eq(name, expected[index++]);
   }
+  do_check_eq(index, 3);
 }
 
 function test_params_prototype() {
   let stmt = createStatement(
     "SELECT * FROM sqlite_master"
   );
 
   // Set a property on the prototype and make sure it exist (will not be a
   // bindable parameter, however).
   Object.getPrototypeOf(stmt.params).test = 2;
   do_check_eq(stmt.params.test, 2);
+
+  delete Object.getPrototypeOf(stmt.params).test;
   stmt.finalize();
 }
 
 function test_row_prototype() {
   let stmt = createStatement(
     "SELECT * FROM sqlite_master"
   );
 
@@ -50,16 +53,44 @@ function test_row_prototype() {
   Object.getPrototypeOf(stmt.row).test = 2;
   do_check_eq(stmt.row.test, 2);
 
   // Clean up after ourselves.
   delete Object.getPrototypeOf(stmt.row).test;
   stmt.finalize();
 }
 
+function test_row_enumerate() {
+  let stmt = createStatement(
+    "SELECT * FROM test"
+  );
+
+  do_check_true(stmt.executeStep());
+
+  let expected = ["id", "string"];
+  let expected_values = [123, "foo"];
+  let index = 0;
+  for (let name in stmt.row) {
+    do_check_eq(name, expected[index]);
+    do_check_eq(stmt.row[name], expected_values[index]);
+    index++;
+  }
+  do_check_eq(index, 2);
+
+  // Save off the row helper, then forget the statement and trigger a GC.  We
+  // want to ensure that if the row helper is retained but the statement is
+  // destroyed, that no crash occurs and that the late access attempt simply
+  // throws an error.
+  let savedOffRow = stmt.row;
+  stmt = null;
+  Components.utils.forceGC();
+  Assert.throws(() => { return savedOffRow.string; },
+                "GC'ed statement should throw");
+}
+
 function test_params_gets_sync() {
   // Added for bug 562866.
   /*
   let stmt = createStatement(
     "SELECT * FROM test WHERE id IN (:a, :b, :c)"
   );
 
   // Make sure we do not assert in getting the value.
@@ -95,25 +126,29 @@ function test_params_gets_async() {
   */
 }
 
 // Test Runner
 
 var tests = [
   test_params_enumerate,
   test_params_prototype,
+  test_row_enumerate,
   test_row_prototype,
   test_params_gets_sync,
   test_params_gets_async,
 ];
 function run_test() {
   cleanup();
 
   // Create our database.
   getOpenedDatabase().executeSimpleSQL(
     "CREATE TABLE test (" +
-      "id INTEGER PRIMARY KEY " +
+      "id INTEGER PRIMARY KEY, string TEXT" +
     ")"
   );
+  getOpenedDatabase().executeSimpleSQL(
+    "INSERT INTO test (id, string) VALUES (123, 'foo')"
+  );
 
   // Run the tests.
   tests.forEach(test => test());
 }