Bug 867728 - Stream profiler JSON directly to a file. r=bgirard,terrence
authorViktor Stanchev <vstanchev@mozilla.com>
Mon, 21 Apr 2014 16:48:47 -0400
changeset 179872 19c2a56d49c2f227d18e89dd80b80a36b86b5758
parent 179871 9ac8dc04297e4d194d34f197d4261b203e4236ce
child 179873 7aeb61045689869a477e7f1c822f45c7861cdb42
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersbgirard, terrence
bugs867728
milestone31.0a1
Bug 867728 - Stream profiler JSON directly to a file. r=bgirard,terrence
toolkit/devtools/server/tests/unit/test_profiler_actor.js
tools/profiler/BreakpadSampler.cpp
tools/profiler/GeckoProfiler.h
tools/profiler/GeckoProfilerFunc.h
tools/profiler/GeckoProfilerImpl.h
tools/profiler/JSCustomObjectBuilder.cpp
tools/profiler/JSCustomObjectBuilder.h
tools/profiler/JSObjectBuilder.cpp
tools/profiler/JSObjectBuilder.h
tools/profiler/JSStreamWriter.cpp
tools/profiler/JSStreamWriter.h
tools/profiler/ProfileEntry.cpp
tools/profiler/ProfileEntry.h
tools/profiler/ProfilerBacktrace.cpp
tools/profiler/ProfilerBacktrace.h
tools/profiler/ProfilerMarkers.cpp
tools/profiler/ProfilerMarkers.h
tools/profiler/PseudoStack.h
tools/profiler/SaveProfileTask.cpp
tools/profiler/SaveProfileTask.h
tools/profiler/TableTicker.cpp
tools/profiler/TableTicker.h
tools/profiler/moz.build
tools/profiler/nsProfiler.cpp
tools/profiler/platform.cpp
--- a/toolkit/devtools/server/tests/unit/test_profiler_actor.js
+++ b/toolkit/devtools/server/tests/unit/test_profiler_actor.js
@@ -112,18 +112,17 @@ function test_profile(aClient, aProfiler
     do_check_eq(typeof aResponse.profile.threads, "object");
     do_check_eq(typeof aResponse.profile.threads[0], "object");
     do_check_eq(typeof aResponse.profile.threads[0].samples, "object");
     do_check_neq(aResponse.profile.threads[0].samples.length, 0);
 
     let location = stack.name + " (" + stack.filename + ":" + funcLine + ")";
     // At least one sample is expected to have been in the busy wait above.
     do_check_true(aResponse.profile.threads[0].samples.some(function(sample) {
-      return sample.name == "(root)" &&
-             typeof sample.frames == "object" &&
+      return typeof sample.frames == "object" &&
              sample.frames.length != 0 &&
              sample.frames.some(function(f) {
                return (f.line == stack.lineNumber) &&
                       (f.location == location);
              });
     }));
 
     aClient.request({ to: aProfiler, type: "stopProfiler" }, function (aResponse) {
--- a/tools/profiler/BreakpadSampler.cpp
+++ b/tools/profiler/BreakpadSampler.cpp
@@ -21,20 +21,16 @@
 #include "shared-libraries.h"
 #include "mozilla/StackWalk.h"
 #include "ProfileEntry.h"
 #include "SyncProfile.h"
 #include "SaveProfileTask.h"
 #include "UnwinderThread2.h"
 #include "TableTicker.h"
 
-// JSON
-#include "JSObjectBuilder.h"
-#include "nsIJSRuntimeService.h"
-
 // Meta
 #include "nsXPCOM.h"
 #include "nsXPCOMCID.h"
 #include "nsIHttpProtocolHandler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIXULRuntime.h"
 #include "nsIXULAppInfo.h"
 #include "nsDirectoryServiceUtils.h"
--- a/tools/profiler/GeckoProfiler.h
+++ b/tools/profiler/GeckoProfiler.h
@@ -146,16 +146,19 @@ static inline double* profiler_get_respo
 static inline void profiler_set_frame_number(int frameNumber) {}
 
 // Get the profile encoded as a JSON string.
 static inline char* profiler_get_profile() { return nullptr; }
 
 // Get the profile encoded as a JSON object.
 static inline JSObject* profiler_get_profile_jsobject(JSContext* aCx) { return nullptr; }
 
+// Get the profile and write it into a file
+static inline void profiler_save_profile_to_file(char* aFilename) { }
+
 // Get the features supported by the profiler that are accepted by profiler_init.
 // Returns a null terminated char* array.
 static inline char** profiler_get_features() { return nullptr; }
 
 // Print the current location to the console. This functill will do it best effort
 // to show the profiler's combined js/c++ if the profiler is running. Note that
 // printing the location require symbolicating which is very slow.
 static inline void profiler_print_location() {}
--- a/tools/profiler/GeckoProfilerFunc.h
+++ b/tools/profiler/GeckoProfilerFunc.h
@@ -51,16 +51,18 @@ void mozilla_sampler_frame_number(int fr
 const double* mozilla_sampler_get_responsiveness();
 
 void mozilla_sampler_save();
 
 char* mozilla_sampler_get_profile();
 
 JSObject *mozilla_sampler_get_profile_data(JSContext *aCx);
 
+void mozilla_sampler_save_profile_to_file(const char* aFilename);
+
 const char** mozilla_sampler_get_features();
 
 void mozilla_sampler_init(void* stackTop);
 
 void mozilla_sampler_shutdown();
 
 void mozilla_sampler_print_location1();
 void mozilla_sampler_print_location2();
--- a/tools/profiler/GeckoProfilerImpl.h
+++ b/tools/profiler/GeckoProfilerImpl.h
@@ -139,16 +139,22 @@ char* profiler_get_profile()
 
 static inline
 JSObject* profiler_get_profile_jsobject(JSContext* aCx)
 {
   return mozilla_sampler_get_profile_data(aCx);
 }
 
 static inline
+void profiler_save_profile_to_file(const char* aFilename)
+{
+  return mozilla_sampler_save_profile_to_file(aFilename);
+}
+
+static inline
 const char** profiler_get_features()
 {
   return mozilla_sampler_get_features();
 }
 
 static inline
 void profiler_print_location()
 {
deleted file mode 100644
--- a/tools/profiler/JSCustomObjectBuilder.cpp
+++ /dev/null
@@ -1,318 +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 "JSCustomObjectBuilder.h"
-
-#include "mozilla/ArrayUtils.h" // for ArrayLength
-#include "nsDataHashtable.h"
-#include "nsString.h"
-#include "nsTArray.h"
-#include "nsUTF8Utils.h"
-
-#if _MSC_VER
- #define snprintf _snprintf
-#endif
-
-// These are owned and deleted by JSCustomObject
-struct PropertyValue {
-  virtual ~PropertyValue() {}
-  virtual void SendToStream(std::ostream& stream) = 0;
-};
-
-template <typename T>
-struct finalizer_impl
-{
-  static void run(T) {}
-};
-
-template <typename T>
-struct finalizer_impl<T*>
-{
-  static void run(T* p) {
-    delete p;
-  }
-};
-
-template <>
-struct finalizer_impl<char *>
-{
-  static void run(char* p) {
-    free(p);
-  }
-};
-
-template <class T>
-class TemplatePropertyValue : public PropertyValue {
-public:
-  TemplatePropertyValue(T aValue)
-    : mValue(aValue)
-  {}
-
-  ~TemplatePropertyValue() {
-    finalizer_impl<T>::run(mValue);
-  }
-
-  virtual void SendToStream(std::ostream& stream);
-private:
-  T mValue;
-};
-
-// Escape a UTF8 string to a stream. When an illegal encoding
-// is found it will insert "INVALID" and the function will return.
-void EscapeToStream(std::ostream& stream, const char* str) {
-  stream << "\"";
-
-  size_t len = strlen(str);
-  const char* end = &str[len];
-  while (str < end) {
-    bool err;
-    const char* utf8CharStart = str;
-    uint32_t ucs4Char = UTF8CharEnumerator::NextChar(&str, end, &err);
-
-    if (err) {
-      // Encoding error
-      stream << "INVALID\"";
-      return;
-    }
-
-    // See http://www.ietf.org/rfc/rfc4627.txt?number=4627
-    // characters that must be escaped: quotation mark,
-    // reverse solidus, and the control characters
-    // (U+0000 through U+001F).
-    if (ucs4Char == '\"') {
-      stream << "\\\"";
-    } else if (ucs4Char == '\\') {
-      stream << "\\\\";
-    } else if (ucs4Char > 0xFF) {
-      char16_t chr[2];
-      ConvertUTF8toUTF16 encoder(chr);
-      encoder.write(utf8CharStart, uint32_t(str-utf8CharStart));
-      char escChar[13];
-      snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X\\u%04X", chr[0], chr[1]);
-      stream << escChar;
-    } else if (ucs4Char < 0x1F || ucs4Char > 0xFF) {
-      char escChar[7];
-      snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X", ucs4Char);
-      stream << escChar;
-    } else {
-      stream << char(ucs4Char);
-    }
-  }
-  stream << "\"";
-}
-
-class JSCustomObject {
-public:
-  JSCustomObject() {}
-  ~JSCustomObject();
-
-  friend std::ostream& operator<<(std::ostream& stream, JSCustomObject* entry);
-
-  template<class T>
-  void AddProperty(const char* aName, T aValue) {
-    mProperties.Put(nsDependentCString(aName), new TemplatePropertyValue<T>(aValue));
-  }
-
-  nsDataHashtable<nsCStringHashKey, PropertyValue*> mProperties;
-};
-
-class JSCustomArray {
-public:
-  nsTArray<PropertyValue*> mValues;
-
-  friend std::ostream& operator<<(std::ostream& stream, JSCustomArray* entry);
-
-  template<class T>
-  void AppendElement(T aValue) {
-    mValues.AppendElement(new TemplatePropertyValue<T>(aValue));
-  }
-};
-
-template <typename T>
-struct SendToStreamImpl
-{
-  static void run(std::ostream& stream, const T& t) {
-    stream << t;
-  }
-};
-
-template<typename T>
-struct SendToStreamImpl<T*>
-{
-  static void run(std::ostream& stream, T* t) {
-    stream << *t;
-  }
-};
-
-template <>
-struct SendToStreamImpl<char *>
-{
-  static void run(std::ostream& stream, char* p) {
-    EscapeToStream(stream, p);
-  }
-};
-
-template <>
-struct SendToStreamImpl<double>
-{
-  static void run(std::ostream& stream, double p) {
-    // 13 for ms, 16 of microseconds, plus an extra 2
-    stream.precision(18);
-    stream << p;
-  }
-};
-
-template <>
-struct SendToStreamImpl<JSCustomObject*>
-{
-  static void run(std::ostream& stream, JSCustomObject* p) {
-    stream << p;
-  }
-};
-
-template <>
-struct SendToStreamImpl<JSCustomArray*>
-{
-  static void run(std::ostream& stream, JSCustomArray* p) {
-    stream << p;
-  }
-};
-
-template <class T> void
-TemplatePropertyValue<T>::SendToStream(std::ostream& stream)
-{
-  SendToStreamImpl<T>::run(stream, mValue);
-}
-
-struct JSONStreamClosure {
-  std::ostream& mStream;
-  bool mNeedsComma;
-};
-
-PLDHashOperator HashTableOutput(const nsACString& aKey, PropertyValue* aValue, void* stream)
-{
-  JSONStreamClosure& streamClosure = *(JSONStreamClosure*)stream;
-  if (streamClosure.mNeedsComma) {
-    streamClosure.mStream << ",";
-  }
-  streamClosure.mNeedsComma = true;
-  EscapeToStream(streamClosure.mStream, (const char*)aKey.BeginReading());
-  streamClosure.mStream << ":";
-  aValue->SendToStream(streamClosure.mStream);
-  return PL_DHASH_NEXT;
-}
-
-std::ostream&
-operator<<(std::ostream& stream, JSCustomObject* entry)
-{
-  JSONStreamClosure streamClosure = {stream, false};
-  stream << "{";
-  entry->mProperties.EnumerateRead(HashTableOutput, &streamClosure);
-  stream << "}";
-  return stream;
-}
-
-std::ostream&
-operator<<(std::ostream& stream, JSCustomArray* entry)
-{
-  bool needsComma = false;
-  stream << "[";
-  for (uint32_t i = 0; i < entry->mValues.Length(); i++) {
-    if (needsComma) {
-      stream << ",";
-    }
-    entry->mValues[i]->SendToStream(stream);
-    needsComma = true;
-  }
-  stream << "]";
-  return stream;
-}
-
-PLDHashOperator HashTableFree(const nsACString& aKey, PropertyValue* aValue, void* stream)
-{
-  delete aValue;
-  return PL_DHASH_NEXT;
-}
-
-JSCustomObject::~JSCustomObject()
-{
-  mProperties.EnumerateRead(HashTableFree, nullptr);
-}
-
-JSCustomObjectBuilder::JSCustomObjectBuilder()
-{}
-
-void
-JSCustomObjectBuilder::DeleteObject(JSCustomObject* aObject)
-{
-  delete aObject;
-}
-
-void
-JSCustomObjectBuilder::Serialize(JSCustomObject* aObject, std::ostream& stream)
-{
-  stream << aObject;
-}
-
-void
-JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue)
-{
-  aObject->AddProperty(name, aValue);
-}
-
-void
-JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue)
-{
-  aObject->AddProperty(name, aValue);
-}
-
-void
-JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, int aValue)
-{
-  aObject->AddProperty(name, aValue);
-}
-
-void
-JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, double aValue)
-{
-  aObject->AddProperty(name, aValue);
-}
-
-void
-JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, const char *aValue)
-{
-  // aValue copy will be freed by the property desctructor (template specialization)
-  aObject->AddProperty(name, strdup(aValue));
-}
-
-void
-JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, int aValue)
-{
-  aArray->AppendElement(aValue);
-}
-
-void
-JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, const char *aValue)
-{
-  // aValue copy will be freed by the property desctructor (template specialization)
-  aArray->AppendElement(strdup(aValue));
-}
-
-void
-JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject)
-{
-  aArray->AppendElement(aObject);
-}
-
-JSCustomArray*
-JSCustomObjectBuilder::CreateArray() {
-  return new JSCustomArray();
-}
-
-JSCustomObject*
-JSCustomObjectBuilder::CreateObject() {
-  return new JSCustomObject();
-}
-
deleted file mode 100644
--- a/tools/profiler/JSCustomObjectBuilder.h
+++ /dev/null
@@ -1,65 +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/. */
-
-#ifndef JSCUSTOMOBJECTBUILDER_H
-#define JSCUSTOMOBJECTBUILDER_H
-
-#include <ostream>
-#include <stdlib.h>
-#include "js/RootingAPI.h"
-
-class JSCustomObject;
-class JSCustomArray;
-
-class JSCustomObjectBuilder
-{
-public:
-  typedef JSCustomObject* Object;
-  typedef JSCustomArray* Array;
-  typedef JSCustomObject* ObjectHandle;
-  typedef JSCustomArray* ArrayHandle;
-  typedef js::FakeRooted<JSCustomObject*> RootedObject;
-  typedef js::FakeRooted<JSCustomArray*> RootedArray;
-
-  // We need to ensure that this object lives on the stack so that GC sees it properly
-  JSCustomObjectBuilder();
-
-  void Serialize(JSCustomObject* aObject, std::ostream& stream);
-
-  void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue);
-  void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue);
-  void DefineProperty(JSCustomObject *aObject, const char *name, int value);
-  void DefineProperty(JSCustomObject *aObject, const char *name, double value);
-  void DefineProperty(JSCustomObject *aObject, const char *name, const char *value, size_t valueLength);
-  void DefineProperty(JSCustomObject *aObject, const char *name, const char *value);
-  void ArrayPush(JSCustomArray *aArray, int value);
-  void ArrayPush(JSCustomArray *aArray, const char *value);
-  void ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject);
-  JSCustomArray  *CreateArray();
-  JSCustomObject *CreateObject();
-
-  // Delete this object and all of its descendant
-  void DeleteObject(JSCustomObject* aObject);
-
-  JSContext *context() const { return nullptr; }
-
-private:
-  // This class can't be copied
-  JSCustomObjectBuilder(const JSCustomObjectBuilder&);
-  JSCustomObjectBuilder& operator=(const JSCustomObjectBuilder&);
-
-  void* operator new(size_t);
-  void* operator new[](size_t);
-  void operator delete(void*) {
-    // Since JSCustomObjectBuilder has a virtual destructor the compiler
-    // has to provide a destructor in the object file that will call
-    // operate delete in case there is a derived class since its
-    // destructor wont know how to free this instance.
-    abort();
-  }
-  void operator delete[](void*);
-};
-
-#endif
deleted file mode 100644
--- a/tools/profiler/JSObjectBuilder.cpp
+++ /dev/null
@@ -1,145 +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 "jsapi.h"
-#include "nsString.h"
-#include "JSObjectBuilder.h"
-
-JSObjectBuilder::JSObjectBuilder(JSContext *aCx) : mCx(aCx), mOk(true)
-{}
-
-void
-JSObjectBuilder::DefineProperty(JS::HandleObject aObject, const char *name, JS::HandleObject aValue)
-{
-  if (!mOk)
-    return;
-
-  mOk = JS_DefineProperty(mCx, aObject, name, aValue, JSPROP_ENUMERATE);
-}
-
-void
-JSObjectBuilder::DefineProperty(JS::HandleObject aObject, const char *name, int value)
-{
-  if (!mOk)
-    return;
-
-  mOk = JS_DefineProperty(mCx, aObject, name, value, JSPROP_ENUMERATE);
-}
-
-void
-JSObjectBuilder::DefineProperty(JS::HandleObject aObject, const char *name, double value)
-{
-  if (!mOk)
-    return;
-
-  mOk = JS_DefineProperty(mCx, aObject, name, value, JSPROP_ENUMERATE);
-}
-
-void
-JSObjectBuilder::DefineProperty(JS::HandleObject aObject, const char *name, nsAString &value)
-{
-  if (!mOk)
-    return;
-
-  const nsString &flat = PromiseFlatString(value);
-  JS::RootedString string(mCx, JS_NewUCStringCopyN(mCx, static_cast<const jschar*>(flat.get()), flat.Length()));
-  if (!string)
-    mOk = false;
-
-  if (!mOk)
-    return;
-
-  mOk = JS_DefineProperty(mCx, aObject, name, string, JSPROP_ENUMERATE);
-}
-
-void
-JSObjectBuilder::DefineProperty(JS::HandleObject aObject, const char *name, const char *value, size_t valueLength)
-{
-  if (!mOk)
-    return;
-
-  JS::RootedString string(mCx, JS_InternStringN(mCx, value, valueLength));
-  if (!string) {
-    mOk = false;
-    return;
-  }
-
-  mOk = JS_DefineProperty(mCx, aObject, name, string, JSPROP_ENUMERATE); }
-
-void
-JSObjectBuilder::DefineProperty(JS::HandleObject aObject, const char *name, const char *value)
-{
-  DefineProperty(aObject, name, value, strlen(value));
-}
-
-void
-JSObjectBuilder::ArrayPush(JS::HandleObject aArray, int value)
-{
-  if (!mOk)
-    return;
-
-  uint32_t length;
-  mOk = JS_GetArrayLength(mCx, aArray, &length);
-
-  if (!mOk)
-    return;
-
-  mOk = JS_SetElement(mCx, aArray, length, value);
-}
-
-void
-JSObjectBuilder::ArrayPush(JS::HandleObject aArray, const char *value)
-{
-  if (!mOk)
-    return;
-
-  JS::RootedString string(mCx, JS_NewStringCopyN(mCx, value, strlen(value)));
-  if (!string) {
-    mOk = false;
-    return;
-  }
-
-  uint32_t length;
-  mOk = JS_GetArrayLength(mCx, aArray, &length);
-
-  if (!mOk)
-    return;
-
-  mOk = JS_SetElement(mCx, aArray, length, string);
-}
-
-void
-JSObjectBuilder::ArrayPush(JS::HandleObject aArray, JS::HandleObject aObject)
-{
-  if (!mOk)
-    return;
-
-  uint32_t length;
-  mOk = JS_GetArrayLength(mCx, aArray, &length);
-
-  if (!mOk)
-    return;
-
-  mOk = JS_SetElement(mCx, aArray, length, aObject);
-}
-
-JSObject*
-JSObjectBuilder::CreateArray() {
-  JSObject *array = JS_NewArrayObject(mCx, 0);
-  if (!array)
-    mOk = false;
-
-  return array;
-}
-
-JSObject*
-JSObjectBuilder::CreateObject() {
-  JSObject *obj = JS_NewObject(mCx, nullptr, JS::NullPtr(), JS::NullPtr());
-  if (!obj)
-    mOk = false;
-
-  return obj;
-}
-
deleted file mode 100644
--- a/tools/profiler/JSObjectBuilder.h
+++ /dev/null
@@ -1,67 +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/. */
-
-#ifndef JSOBJECTBUILDER_H
-#define JSOBJECTBUILDER_H
-
-#include "js/TypeDecls.h"
-#include "js/RootingAPI.h"
-
-class JSCustomArray;
-class JSCustomObject;
-class JSCustomObjectBuilder;
-class nsAString;
-
-/* this is handy wrapper around JSAPI to make it more pleasant to use.
- * We collect the JSAPI errors and so that callers don't need to */
-class JSObjectBuilder
-{
-public:
-  typedef JS::Handle<JSObject*> ObjectHandle;
-  typedef JS::Handle<JSObject*> ArrayHandle;
-  typedef JS::Rooted<JSObject*> RootedObject;
-  typedef JS::Rooted<JSObject*> RootedArray;
-  typedef JSObject* Object;
-  typedef JSObject* Array;
-
-  // We need to ensure that this object lives on the stack so that GC sees it properly
-  explicit JSObjectBuilder(JSContext *aCx);
-  ~JSObjectBuilder() {}
-
-  void DefineProperty(JS::HandleObject aObject, const char *name, JS::HandleObject aValue);
-  void DefineProperty(JS::HandleObject aObject, const char *name, int value);
-  void DefineProperty(JS::HandleObject aObject, const char *name, double value);
-  void DefineProperty(JS::HandleObject aObject, const char *name, nsAString &value);
-  void DefineProperty(JS::HandleObject aObject, const char *name, const char *value, size_t valueLength);
-  void DefineProperty(JS::HandleObject aObject, const char *name, const char *value);
-  void ArrayPush(JS::HandleObject aArray, int value);
-  void ArrayPush(JS::HandleObject aArray, const char *value);
-  void ArrayPush(JS::HandleObject aArray, JS::HandleObject aObject);
-  JSObject *CreateArray();
-  JSObject *CreateObject();
-
-  JSContext *context() const { return mCx; }
-
-private:
-  JSObjectBuilder(const JSObjectBuilder&);
-  JSObjectBuilder& operator=(const JSObjectBuilder&);
-
-  void* operator new(size_t);
-  void* operator new[](size_t);
-  void operator delete(void*) {
-    // Since JSObjectBuilder has a virtual destructor the compiler
-    // has to provide a destructor in the object file that will call
-    // operate delete in case there is a derived class since its
-    // destructor wont know how to free this instance.
-    abort();
-  }
-  void operator delete[](void*);
-
-  JSContext *mCx;
-  int mOk;
-};
-
-#endif
-
new file mode 100644
--- /dev/null
+++ b/tools/profiler/JSStreamWriter.cpp
@@ -0,0 +1,180 @@
+/* -*- 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 "JSStreamWriter.h"
+
+#include "mozilla/ArrayUtils.h" // for ArrayLength
+#include "nsDataHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsUTF8Utils.h"
+
+#if _MSC_VER
+ #define snprintf _snprintf
+#endif
+
+#define ARRAY (void*)1
+#define OBJECT (void*)2
+
+// Escape a UTF8 string to a stream. When an illegal encoding
+// is found it will insert "INVALID" and the function will return.
+static void EscapeToStream(std::ostream& stream, const char* str) {
+  stream << "\"";
+
+  size_t len = strlen(str);
+  const char* end = &str[len];
+  while (str < end) {
+    bool err;
+    const char* utf8CharStart = str;
+    uint32_t ucs4Char = UTF8CharEnumerator::NextChar(&str, end, &err);
+
+    if (err) {
+      // Encoding error
+      stream << "INVALID\"";
+      return;
+    }
+
+    // See http://www.ietf.org/rfc/rfc4627.txt?number=4627
+    // characters that must be escaped: quotation mark,
+    // reverse solidus, and the control characters
+    // (U+0000 through U+001F).
+    if (ucs4Char == '\"') {
+      stream << "\\\"";
+    } else if (ucs4Char == '\\') {
+      stream << "\\\\";
+    } else if (ucs4Char > 0xFF) {
+      char16_t chr[2];
+      ConvertUTF8toUTF16 encoder(chr);
+      encoder.write(utf8CharStart, uint32_t(str-utf8CharStart));
+      char escChar[13];
+      snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X\\u%04X", chr[0], chr[1]);
+      stream << escChar;
+    } else if (ucs4Char < 0x1F || ucs4Char > 0xFF) {
+      char escChar[7];
+      snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X", ucs4Char);
+      stream << escChar;
+    } else {
+      stream << char(ucs4Char);
+    }
+  }
+  stream << "\"";
+}
+
+JSStreamWriter::JSStreamWriter(std::ostream& aStream)
+  : mStream(aStream)
+  , mNeedsComma(false)
+  , mNeedsName(false)
+{ }
+
+JSStreamWriter::~JSStreamWriter()
+{
+  MOZ_ASSERT(mStack.GetSize() == 0);
+}
+
+void
+JSStreamWriter::BeginObject()
+{
+  MOZ_ASSERT(!mNeedsName);
+  if (mNeedsComma && mStack.Peek() == ARRAY) {
+    mStream << ",";
+  }
+  mStream << "{";
+  mNeedsComma = false;
+  mNeedsName = true;
+  mStack.Push(OBJECT);
+}
+
+void
+JSStreamWriter::EndObject()
+{
+  MOZ_ASSERT(mStack.Peek() == OBJECT);
+  mStream << "}";
+  mNeedsComma = true;
+  mNeedsName = false;
+  mStack.Pop();
+  if (mStack.GetSize() > 0 && mStack.Peek() == OBJECT) {
+    mNeedsName = true;
+  }
+}
+
+void
+JSStreamWriter::BeginArray()
+{
+  MOZ_ASSERT(!mNeedsName);
+  if (mNeedsComma && mStack.Peek() == ARRAY) {
+    mStream << ",";
+  }
+  mStream << "[";
+  mNeedsComma = false;
+  mStack.Push(ARRAY);
+}
+
+void
+JSStreamWriter::EndArray()
+{
+  MOZ_ASSERT(!mNeedsName);
+  MOZ_ASSERT(mStack.Peek() == ARRAY);
+  mStream << "]";
+  mNeedsComma = true;
+  mStack.Pop();
+  if (mStack.GetSize() > 0 && mStack.Peek() == OBJECT) {
+    mNeedsName = true;
+  }
+}
+
+void
+JSStreamWriter::Name(const char *aName)
+{
+  MOZ_ASSERT(mNeedsName);
+  if (mNeedsComma && mStack.Peek() == OBJECT) {
+    mStream << ",";
+  }
+  EscapeToStream(mStream, aName);
+  mStream << ":";
+  mNeedsName = false;
+}
+
+void
+JSStreamWriter::Value(int aValue)
+{
+  MOZ_ASSERT(!mNeedsName);
+  if (mNeedsComma && mStack.Peek() == ARRAY) {
+    mStream << ",";
+  }
+  mStream << aValue;
+  mNeedsComma = true;
+  if (mStack.Peek() == OBJECT) {
+    mNeedsName = true;
+  }
+}
+
+void
+JSStreamWriter::Value(double aValue)
+{
+  MOZ_ASSERT(!mNeedsName);
+  if (mNeedsComma && mStack.Peek() == ARRAY) {
+    mStream << ",";
+  }
+  mStream.precision(18);
+  mStream << aValue;
+  mNeedsComma = true;
+  if (mStack.Peek() == OBJECT) {
+    mNeedsName = true;
+  }
+}
+
+void
+JSStreamWriter::Value(const char *aValue)
+{
+  MOZ_ASSERT(!mNeedsName);
+  if (mNeedsComma && mStack.Peek() == ARRAY) {
+    mStream << ",";
+  }
+  EscapeToStream(mStream, aValue);
+  mNeedsComma = true;
+  if (mStack.Peek() == OBJECT) {
+    mNeedsName = true;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/tools/profiler/JSStreamWriter.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#ifndef JSSTREAMWRITER_H
+#define JSSTREAMWRITER_H
+
+#include <ostream>
+#include <stdlib.h>
+#include "nsDeque.h"
+
+class JSStreamWriter
+{
+public:
+  JSStreamWriter(std::ostream& aStream);
+  ~JSStreamWriter();
+
+  void BeginObject();
+  void EndObject();
+  void BeginArray();
+  void EndArray();
+  void Name(const char *name);
+  void Value(int value);
+  void Value(double value);
+  void Value(const char *value, size_t valueLength);
+  void Value(const char *value);
+  template <typename T>
+  void NameValue(const char *aName, T aValue)
+  {
+    Name(aName);
+    Value(aValue);
+  }
+
+private:
+  std::ostream& mStream;
+  bool mNeedsComma;
+  bool mNeedsName;
+
+  nsDeque mStack;
+
+  // This class can't be copied
+  JSStreamWriter(const JSStreamWriter&);
+  JSStreamWriter& operator=(const JSStreamWriter&);
+
+  void* operator new(size_t);
+  void* operator new[](size_t);
+  void operator delete(void*) {
+    // Since JSStreamWriter has a virtual destructor the compiler
+    // has to provide a destructor in the object file that will call
+    // operate delete in case there is a derived class since its
+    // destructor won't know how to free this instance.
+    abort();
+  }
+  void operator delete[](void*);
+};
+
+#endif
--- a/tools/profiler/ProfileEntry.cpp
+++ b/tools/profiler/ProfileEntry.cpp
@@ -1,21 +1,22 @@
 /* -*- 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 <ostream>
+#include <sstream>
 #include "platform.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
+#include "jsapi.h"
 
 // JSON
-#include "JSObjectBuilder.h"
-#include "JSCustomObjectBuilder.h"
+#include "JSStreamWriter.h"
 
 // Self
 #include "ProfileEntry.h"
 
 #if _MSC_VER
  #define snprintf _snprintf
 #endif
 
@@ -303,148 +304,169 @@ void ThreadProfile::IterateTags(IterateT
     aCallback(entry, tagStringData);
 
     readPos = (readPos + incBy) % mEntrySize;
   }
 }
 
 void ThreadProfile::ToStreamAsJSON(std::ostream& stream)
 {
-  JSCustomObjectBuilder b;
-  JSCustomObject *profile = b.CreateObject();
-  BuildJSObject(b, profile);
-  b.Serialize(profile, stream);
-  b.DeleteObject(profile);
+  JSStreamWriter b(stream);
+  StreamJSObject(b);
+}
+
+void ThreadProfile::StreamJSObject(JSStreamWriter& b)
+{
+  b.BeginObject();
+    // Thread meta data
+    if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
+      // TODO Add the proper plugin name
+      b.NameValue("name", "Plugin");
+    } else {
+      b.NameValue("name", mName);
+    }
+    b.NameValue("tid", static_cast<int>(mThreadId));
+
+    b.Name("samples");
+    b.BeginArray();
+
+      bool sample = false;
+      int readPos = mReadPos;
+      while (readPos != mLastFlushPos) {
+        // Number of tag consumed
+        ProfileEntry entry = mEntries[readPos];
+
+        switch (entry.mTagName) {
+          case 'r':
+            {
+              if (sample) {
+                b.NameValue("responsiveness", entry.mTagFloat);
+              }
+            }
+            break;
+          case 'p':
+            {
+              if (sample) {
+                b.NameValue("power", entry.mTagFloat);
+              }
+            }
+            break;
+          case 'f':
+            {
+              if (sample) {
+                b.NameValue("frameNumber", entry.mTagLine);
+              }
+            }
+            break;
+          case 't':
+            {
+              if (sample) {
+                b.NameValue("time", entry.mTagFloat);
+              }
+            }
+            break;
+          case 's':
+            {
+              // end the previous sample if there was one
+              if (sample) {
+                b.EndObject();
+              }
+              // begin the next sample
+              b.BeginObject();
+
+              sample = true;
+
+              // Seek forward through the entire sample, looking for frames
+              // this is an easier approach to reason about than adding more
+              // control variables and cases to the loop that goes through the buffer once
+              b.Name("frames");
+              b.BeginArray();
+
+                b.BeginObject();
+                  b.NameValue("location", "(root)");
+                b.EndObject();
+
+                int framePos = (readPos + 1) % mEntrySize;
+                ProfileEntry frame = mEntries[framePos];
+                while (framePos != mLastFlushPos && frame.mTagName != 's') {
+                  int incBy = 1;
+                  frame = mEntries[framePos];
+                  // Read ahead to the next tag, if it's a 'd' tag process it now
+                  const char* tagStringData = frame.mTagData;
+                  int readAheadPos = (framePos + 1) % mEntrySize;
+                  char tagBuff[DYNAMIC_MAX_STRING];
+                  // Make sure the string is always null terminated if it fills up
+                  // DYNAMIC_MAX_STRING-2
+                  tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
+
+                  if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
+                    tagStringData = processDynamicTag(framePos, &incBy, tagBuff);
+                  }
+
+                  // Write one frame. It can have either
+                  // 1. only location - 'l' containing a memory address
+                  // 2. location and line number - 'c' followed by 'd's and an optional 'n'
+                  if (frame.mTagName == 'l') {
+                    b.BeginObject();
+                      // Bug 753041
+                      // We need a double cast here to tell GCC that we don't want to sign
+                      // extend 32-bit addresses starting with 0xFXXXXXX.
+                      unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
+                      snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
+                      b.NameValue("location", tagBuff);
+                    b.EndObject();
+                  } else if (frame.mTagName == 'c') {
+                    b.BeginObject();
+                      b.NameValue("location", tagStringData);
+                      readAheadPos = (framePos + incBy) % mEntrySize;
+                      if (readAheadPos != mLastFlushPos &&
+                          mEntries[readAheadPos].mTagName == 'n') {
+                        b.NameValue("line", mEntries[readAheadPos].mTagLine);
+                        incBy++;
+                      }
+                    b.EndObject();
+                  }
+                  framePos = (framePos + incBy) % mEntrySize;
+                }
+              b.EndArray();
+            }
+            break;
+        }
+        readPos = (readPos + 1) % mEntrySize;
+      }
+      if (sample) {
+        b.EndObject();
+      }
+    b.EndArray();
+
+    b.Name("markers");
+    b.BeginArray();
+      readPos = mReadPos;
+      while (readPos != mLastFlushPos) {
+        ProfileEntry entry = mEntries[readPos];
+        if (entry.mTagName == 'm') {
+           entry.getMarker()->StreamJSObject(b);
+        }
+        readPos = (readPos + 1) % mEntrySize;
+      }
+    b.EndArray();
+  b.EndObject();
 }
 
 JSObject* ThreadProfile::ToJSObject(JSContext *aCx)
 {
-  JSObjectBuilder b(aCx);
-  JS::RootedObject profile(aCx, b.CreateObject());
-  BuildJSObject(b, profile);
-  return profile;
+  JS::RootedValue val(aCx);
+  std::stringstream ss;
+  JSStreamWriter b(ss);
+  StreamJSObject(b);
+  NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str()));
+  JS_ParseJSON(aCx, static_cast<const jschar*>(js_string.get()), js_string.Length(), &val);
+  return &val.toObject();
 }
 
-template <typename Builder>
-void ThreadProfile::BuildJSObject(Builder& b,
-                                  typename Builder::ObjectHandle profile)
-{
-  // Thread meta data
-  if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
-    // TODO Add the proper plugin name
-    b.DefineProperty(profile, "name", "Plugin");
-  } else {
-    b.DefineProperty(profile, "name", mName);
-  }
-
-  b.DefineProperty(profile, "tid", static_cast<int>(mThreadId));
-
-  typename Builder::RootedArray samples(b.context(), b.CreateArray());
-  b.DefineProperty(profile, "samples", samples);
-
-  typename Builder::RootedArray markers(b.context(), b.CreateArray());
-  b.DefineProperty(profile, "markers", markers);
-
-  typename Builder::RootedObject sample(b.context());
-  typename Builder::RootedArray frames(b.context());
-
-  int readPos = mReadPos;
-  while (readPos != mLastFlushPos) {
-    // Number of tag consumed
-    int incBy = 1;
-    ProfileEntry entry = mEntries[readPos];
-
-    // Read ahead to the next tag, if it's a 'd' tag process it now
-    const char* tagStringData = entry.mTagData;
-    int readAheadPos = (readPos + 1) % mEntrySize;
-    char tagBuff[DYNAMIC_MAX_STRING];
-    // Make sure the string is always null terminated if it fills up
-    // DYNAMIC_MAX_STRING-2
-    tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
-
-    if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
-      tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
-    }
-
-    switch (entry.mTagName) {
-      case 'm':
-        {
-          entry.getMarker()->BuildJSObject(b, markers);
-        }
-        break;
-      case 'r':
-        {
-          if (sample) {
-            b.DefineProperty(sample, "responsiveness", entry.mTagFloat);
-          }
-        }
-        break;
-      case 'p':
-        {
-          if (sample) {
-            b.DefineProperty(sample, "power", entry.mTagFloat);
-          }
-        }
-        break;
-      case 'f':
-        {
-          if (sample) {
-            b.DefineProperty(sample, "frameNumber", entry.mTagLine);
-          }
-        }
-        break;
-      case 't':
-        {
-          if (sample) {
-            b.DefineProperty(sample, "time", entry.mTagFloat);
-          }
-        }
-        break;
-      case 's':
-        sample = b.CreateObject();
-        b.DefineProperty(sample, "name", tagStringData);
-        frames = b.CreateArray();
-        b.DefineProperty(sample, "frames", frames);
-        b.ArrayPush(samples, sample);
-        // Fall though to create a label for the 's' tag
-      case 'c':
-      case 'l':
-        {
-          if (sample) {
-            typename Builder::RootedObject frame(b.context(), b.CreateObject());
-            if (entry.mTagName == 'l') {
-              // Bug 753041
-              // We need a double cast here to tell GCC that we don't want to sign
-              // extend 32-bit addresses starting with 0xFXXXXXX.
-              unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
-              snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
-              b.DefineProperty(frame, "location", tagBuff);
-            } else {
-              b.DefineProperty(frame, "location", tagStringData);
-              readAheadPos = (readPos + incBy) % mEntrySize;
-              if (readAheadPos != mLastFlushPos &&
-                  mEntries[readAheadPos].mTagName == 'n') {
-                b.DefineProperty(frame, "line",
-                                 mEntries[readAheadPos].mTagLine);
-                incBy++;
-              }
-            }
-            b.ArrayPush(frames, frame);
-          }
-        }
-    }
-    readPos = (readPos + incBy) % mEntrySize;
-  }
-}
-
-template void ThreadProfile::BuildJSObject<JSObjectBuilder>(JSObjectBuilder& b,
-                                                            JS::HandleObject profile);
-template void ThreadProfile::BuildJSObject<JSCustomObjectBuilder>(JSCustomObjectBuilder& b,
-                                                                  JSCustomObject *profile);
-
 PseudoStack* ThreadProfile::GetPseudoStack()
 {
   return mPseudoStack;
 }
 
 void ThreadProfile::BeginUnwind()
 {
   mMutex.Lock();
--- a/tools/profiler/ProfileEntry.h
+++ b/tools/profiler/ProfileEntry.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZ_PROFILE_ENTRY_H
 #define MOZ_PROFILE_ENTRY_H
 
 #include <ostream>
 #include "GeckoProfiler.h"
 #include "platform.h"
+#include "JSStreamWriter.h"
 #include "ProfilerBacktrace.h"
 #include "mozilla/Mutex.h"
 
 class ThreadProfile;
 
 #pragma pack(push, 1)
 
 class ProfileEntry
@@ -77,17 +78,17 @@ public:
   char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff);
   void IterateTags(IterateTagsCallback aCallback);
   friend std::ostream& operator<<(std::ostream& stream,
                                   const ThreadProfile& profile);
   void ToStreamAsJSON(std::ostream& stream);
   JSObject *ToJSObject(JSContext *aCx);
   PseudoStack* GetPseudoStack();
   mozilla::Mutex* GetMutex();
-  template <typename Builder> void BuildJSObject(Builder& b, typename Builder::ObjectHandle profile);
+  void StreamJSObject(JSStreamWriter& b);
   void BeginUnwind();
   virtual void EndUnwind();
   virtual SyncProfile* AsSyncProfile() { return nullptr; }
 
   bool IsMainThread() const { return mIsMainThread; }
   const char* Name() const { return mName; }
   Thread::tid_t ThreadId() const { return mThreadId; }
 
--- a/tools/profiler/ProfilerBacktrace.cpp
+++ b/tools/profiler/ProfilerBacktrace.cpp
@@ -1,39 +1,30 @@
 /* -*- 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 "JSCustomObjectBuilder.h"
-#include "JSObjectBuilder.h"
+#include "JSStreamWriter.h"
 #include "ProfilerBacktrace.h"
 #include "SyncProfile.h"
 
+
 ProfilerBacktrace::ProfilerBacktrace(SyncProfile* aProfile)
   : mProfile(aProfile)
 {
   MOZ_ASSERT(aProfile);
 }
 
 ProfilerBacktrace::~ProfilerBacktrace()
 {
   if (mProfile->ShouldDestroy()) {
     delete mProfile;
   }
 }
 
-template<typename Builder> void
-ProfilerBacktrace::BuildJSObject(Builder& aObjBuilder,
-                                 typename Builder::ObjectHandle aScope)
+void
+ProfilerBacktrace::StreamJSObject(JSStreamWriter& b)
 {
   mozilla::MutexAutoLock lock(*mProfile->GetMutex());
-  mProfile->BuildJSObject(aObjBuilder, aScope);
+  mProfile->StreamJSObject(b);
 }
-
-template void
-ProfilerBacktrace::BuildJSObject<JSCustomObjectBuilder>(
-                                    JSCustomObjectBuilder& aObjBuilder,
-                                    JSCustomObjectBuilder::ObjectHandle aScope);
-template void
-ProfilerBacktrace::BuildJSObject<JSObjectBuilder>(JSObjectBuilder& aObjBuilder,
-                                          JSObjectBuilder::ObjectHandle aScope);
--- a/tools/profiler/ProfilerBacktrace.h
+++ b/tools/profiler/ProfilerBacktrace.h
@@ -10,18 +10,17 @@
 class SyncProfile;
 
 class ProfilerBacktrace
 {
 public:
   ProfilerBacktrace(SyncProfile* aProfile);
   ~ProfilerBacktrace();
 
-  template<typename Builder> void
-  BuildJSObject(Builder& aObjBuilder, typename Builder::ObjectHandle aScope);
+  void StreamJSObject(JSStreamWriter& b);
 
 private:
   ProfilerBacktrace(const ProfilerBacktrace&);
   ProfilerBacktrace& operator=(const ProfilerBacktrace&);
 
   SyncProfile*  mProfile;
 };
 
--- a/tools/profiler/ProfilerMarkers.cpp
+++ b/tools/profiler/ProfilerMarkers.cpp
@@ -21,130 +21,98 @@ ProfilerMarkerPayload::ProfilerMarkerPay
   , mStack(aStack)
 {}
 
 ProfilerMarkerPayload::~ProfilerMarkerPayload()
 {
   profiler_free_backtrace(mStack);
 }
 
-template<typename Builder> void
-ProfilerMarkerPayload::prepareCommonProps(const char* aMarkerType,
-                                          Builder& aBuilder,
-                                          typename Builder::ObjectHandle aObject)
+void
+ProfilerMarkerPayload::streamCommonProps(const char* aMarkerType,
+                                          JSStreamWriter& b)
 {
   MOZ_ASSERT(aMarkerType);
-  aBuilder.DefineProperty(aObject, "type", aMarkerType);
+  b.NameValue("type", aMarkerType);
   if (!mStartTime.IsNull()) {
-    aBuilder.DefineProperty(aObject, "startTime", profiler_time(mStartTime));
+    b.NameValue("startTime", profiler_time(mStartTime));
   }
   if (!mEndTime.IsNull()) {
-    aBuilder.DefineProperty(aObject, "endTime", profiler_time(mEndTime));
+    b.NameValue("endTime", profiler_time(mEndTime));
   }
   if (mStack) {
-    typename Builder::RootedObject stack(aBuilder.context(),
-                                         aBuilder.CreateObject());
-    aBuilder.DefineProperty(aObject, "stack", stack);
-    mStack->BuildJSObject(aBuilder, stack);
+    b.Name("stack");
+    mStack->StreamJSObject(b);
   }
 }
 
-template void
-ProfilerMarkerPayload::prepareCommonProps<JSCustomObjectBuilder>(
-                                   const char* aMarkerType,
-                                   JSCustomObjectBuilder& b,
-                                   JSCustomObjectBuilder::ObjectHandle aObject);
-template void
-ProfilerMarkerPayload::prepareCommonProps<JSObjectBuilder>(
-                                         const char* aMarkerType,
-                                         JSObjectBuilder& b,
-                                         JSObjectBuilder::ObjectHandle aObject);
-
 ProfilerMarkerTracing::ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData)
   : mCategory(aCategory)
   , mMetaData(aMetaData)
 {}
 
-template<typename Builder>
-typename Builder::Object
-ProfilerMarkerTracing::preparePayloadImp(Builder& b)
+void
+ProfilerMarkerTracing::streamPayloadImp(JSStreamWriter& b)
 {
-  typename Builder::RootedObject data(b.context(), b.CreateObject());
-  prepareCommonProps("tracing", b, data);
+  b.BeginObject();
+    streamCommonProps("tracing", b);
 
-  if (GetCategory()) {
-    b.DefineProperty(data, "category", GetCategory());
-  }
-  if (GetMetaData() != TRACING_DEFAULT) {
-    if (GetMetaData() == TRACING_INTERVAL_START) {
-      b.DefineProperty(data, "interval", "start");
-    } else if (GetMetaData() == TRACING_INTERVAL_END) {
-      b.DefineProperty(data, "interval", "end");
+    if (GetCategory()) {
+      b.NameValue("category", GetCategory());
     }
-  }
-
-  return data;
+    if (GetMetaData() != TRACING_DEFAULT) {
+      if (GetMetaData() == TRACING_INTERVAL_START) {
+        b.NameValue("interval", "start");
+      } else if (GetMetaData() == TRACING_INTERVAL_END) {
+        b.NameValue("interval", "end");
+      }
+    }
+  b.EndObject();
 }
 
-template JSCustomObjectBuilder::Object
-ProfilerMarkerTracing::preparePayloadImp<JSCustomObjectBuilder>(JSCustomObjectBuilder& b);
-template JSObjectBuilder::Object
-ProfilerMarkerTracing::preparePayloadImp<JSObjectBuilder>(JSObjectBuilder& b);
-
 ProfilerMarkerImagePayload::ProfilerMarkerImagePayload(gfxASurface *aImg)
   : mImg(aImg)
 {}
 
-template<typename Builder>
-typename Builder::Object
-ProfilerMarkerImagePayload::preparePayloadImp(Builder& b)
+void
+ProfilerMarkerImagePayload::streamPayloadImp(JSStreamWriter& b)
 {
-  typename Builder::RootedObject data(b.context(), b.CreateObject());
-  prepareCommonProps("innerHTML", b, data);
-  // TODO: Finish me
-  //b.DefineProperty(data, "innerHTML", "<img src=''/>");
-  return data;
+  b.BeginObject();
+    streamCommonProps("innerHTML", b);
+    // TODO: Finish me
+    //b.NameValue("innerHTML", "<img src=''/>");
+  b.EndObject();
 }
 
-template JSCustomObjectBuilder::Object
-ProfilerMarkerImagePayload::preparePayloadImp<JSCustomObjectBuilder>(JSCustomObjectBuilder& b);
-template JSObjectBuilder::Object
-ProfilerMarkerImagePayload::preparePayloadImp<JSObjectBuilder>(JSObjectBuilder& b);
-
 IOMarkerPayload::IOMarkerPayload(const char* aSource,
                                  const char* aFilename,
                                  const mozilla::TimeStamp& aStartTime,
                                  const mozilla::TimeStamp& aEndTime,
                                  ProfilerBacktrace* aStack)
   : ProfilerMarkerPayload(aStartTime, aEndTime, aStack),
     mSource(aSource)
 {
   mFilename = aFilename ? strdup(aFilename) : nullptr;
   MOZ_ASSERT(aSource);
 }
 
 IOMarkerPayload::~IOMarkerPayload(){
   free(mFilename);
 }
 
-template<typename Builder> typename Builder::Object
-IOMarkerPayload::preparePayloadImp(Builder& b)
+void
+IOMarkerPayload::streamPayloadImp(JSStreamWriter& b)
 {
-  typename Builder::RootedObject data(b.context(), b.CreateObject());
-  prepareCommonProps("io", b, data);
-  b.DefineProperty(data, "source", mSource);
-  if (mFilename != nullptr) {
-    b.DefineProperty(data, "filename", mFilename);
-  }
-
-  return data;
+  b.BeginObject();
+    streamCommonProps("io", b);
+    b.NameValue("source", mSource);
+    if (mFilename != nullptr) {
+      b.NameValue("filename", mFilename);
+    }
+  b.EndObject();
 }
 
-template JSCustomObjectBuilder::Object
-IOMarkerPayload::preparePayloadImp<JSCustomObjectBuilder>(JSCustomObjectBuilder& b);
-template JSObjectBuilder::Object
-IOMarkerPayload::preparePayloadImp<JSObjectBuilder>(JSObjectBuilder& b);
 
 void
 ProfilerJSEventMarker(const char *event)
 {
     PROFILER_MARKER(event);
 }
--- a/tools/profiler/ProfilerMarkers.h
+++ b/tools/profiler/ProfilerMarkers.h
@@ -1,18 +1,17 @@
 /* -*- 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/. */
 
 #ifndef PROFILER_MARKERS_H
 #define PROFILER_MARKERS_H
 
-#include "JSCustomObjectBuilder.h"
-#include "JSObjectBuilder.h"
+#include "JSStreamWriter.h"
 #include "mozilla/TimeStamp.h"
 #include "nsAutoPtr.h"
 
 /**
  * This is an abstract object that can be implied to supply
  * data to be attached with a profiler marker. Most data inserted
  * into a profile is stored in a circular buffer. This buffer
  * typically wraps around and overwrites most entries. Because
@@ -37,106 +36,87 @@ public:
   /**
    * Called from the main thread
    */
   virtual ~ProfilerMarkerPayload();
 
   /**
    * Called from the main thread
    */
-  template<typename Builder>
-  typename Builder::Object PreparePayload(Builder& b)
-  {
-    return preparePayload(b);
+  void StreamPayload(JSStreamWriter& b) {
+    return streamPayload(b);
   }
 
 protected:
   /**
    * Called from the main thread
    */
-  template<typename Builder>
-  void prepareCommonProps(const char* aMarkerType, Builder& aBuilder,
-                          typename Builder::ObjectHandle aObject);
+  void streamCommonProps(const char* aMarkerType, JSStreamWriter& b);
 
   /**
    * Called from the main thread
    */
-  virtual JSCustomObjectBuilder::Object
-  preparePayload(JSCustomObjectBuilder& b) = 0;
-
-  /**
-   * Called from the main thread
-   */
-  virtual JSObjectBuilder::Object
-  preparePayload(JSObjectBuilder& b) = 0;
+  virtual void
+  streamPayload(JSStreamWriter& b) = 0;
 
 private:
   mozilla::TimeStamp  mStartTime;
   mozilla::TimeStamp  mEndTime;
   ProfilerBacktrace*  mStack;
 };
 
 class ProfilerMarkerTracing : public ProfilerMarkerPayload
 {
 public:
   ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData);
 
   const char *GetCategory() const { return mCategory; }
   TracingMetadata GetMetaData() const { return mMetaData; }
 
 protected:
-  virtual JSCustomObjectBuilder::Object
-  preparePayload(JSCustomObjectBuilder& b) { return preparePayloadImp(b); }
-  virtual JSObjectBuilder::Object
-  preparePayload(JSObjectBuilder& b) { return preparePayloadImp(b); }
+  virtual void
+  streamPayload(JSStreamWriter& b) { return streamPayloadImp(b); }
 
 private:
-  template<typename Builder>
-  typename Builder::Object preparePayloadImp(Builder& b);
+  void streamPayloadImp(JSStreamWriter& b);
 
 private:
   const char *mCategory;
   TracingMetadata mMetaData;
 };
 
 
 class gfxASurface;
 class ProfilerMarkerImagePayload : public ProfilerMarkerPayload
 {
 public:
   ProfilerMarkerImagePayload(gfxASurface *aImg);
 
 protected:
-  virtual JSCustomObjectBuilder::Object
-  preparePayload(JSCustomObjectBuilder& b) { return preparePayloadImp(b); }
-  virtual JSObjectBuilder::Object
-  preparePayload(JSObjectBuilder& b) { return preparePayloadImp(b); }
+  virtual void
+  streamPayload(JSStreamWriter& b) { return streamPayloadImp(b); }
 
 private:
-  template<typename Builder>
-  typename Builder::Object preparePayloadImp(Builder& b);
+  void streamPayloadImp(JSStreamWriter& b);
 
   nsRefPtr<gfxASurface> mImg;
 };
 
 class IOMarkerPayload : public ProfilerMarkerPayload
 {
 public:
   IOMarkerPayload(const char* aSource, const char* aFilename, const mozilla::TimeStamp& aStartTime,
                   const mozilla::TimeStamp& aEndTime,
                   ProfilerBacktrace* aStack);
   ~IOMarkerPayload();
 
 protected:
-  virtual JSCustomObjectBuilder::Object
-  preparePayload(JSCustomObjectBuilder& b) { return preparePayloadImp(b); }
-  virtual JSObjectBuilder::Object
-  preparePayload(JSObjectBuilder& b) { return preparePayloadImp(b); }
+  virtual void
+  streamPayload(JSStreamWriter& b) { return streamPayloadImp(b); }
 
 private:
-  template<typename Builder>
-  typename Builder::Object preparePayloadImp(Builder& b);
+  void streamPayloadImp(JSStreamWriter& b);
 
   const char* mSource;
   char* mFilename;
 };
 
 #endif // PROFILER_MARKERS_H
--- a/tools/profiler/PseudoStack.h
+++ b/tools/profiler/PseudoStack.h
@@ -105,34 +105,34 @@ public:
                         reinterpret_cast<uintptr_t>(sparg) | NoCopyBit));
     }
   }
 };
 
 class ProfilerMarkerPayload;
 template<typename T>
 class ProfilerLinkedList;
-class JSAObjectBuilder;
+class JSStreamWriter;
 class JSCustomArray;
 class ThreadProfile;
 class ProfilerMarker {
   friend class ProfilerLinkedList<ProfilerMarker>;
 public:
   ProfilerMarker(const char* aMarkerName,
          ProfilerMarkerPayload* aPayload = nullptr,
          float aTime = 0);
 
   ~ProfilerMarker();
 
   const char* GetMarkerName() const {
     return mMarkerName;
   }
 
-  template<typename Builder> void
-  BuildJSObject(Builder& b, typename Builder::ArrayHandle markers) const;
+  void
+  StreamJSObject(JSStreamWriter& b) const;
 
   void SetGeneration(int aGenID);
 
   bool HasExpired(int aGenID) const {
     return mGenID + 2 <= aGenID;
   }
 
   float GetTime();
--- a/tools/profiler/SaveProfileTask.cpp
+++ b/tools/profiler/SaveProfileTask.cpp
@@ -1,25 +1,16 @@
 /* -*- 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 "SaveProfileTask.h"
 #include "GeckoProfiler.h"
 
-static bool
-WriteCallback(const jschar *buf, uint32_t len, void *data)
-{
-  std::ofstream& stream = *static_cast<std::ofstream*>(data);
-  nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
-  stream << profile.Data();
-  return true;
-}
-
 nsresult
 SaveProfileTask::Run() {
   // Get file path
 #if defined(SPS_PLAT_arm_android) && !defined(MOZ_WIDGET_GONK)
   nsCString tmpPath;
   tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
 #else
   nsCOMPtr<nsIFile> tmpFile;
@@ -34,58 +25,17 @@ SaveProfileTask::Run() {
   if (NS_FAILED(rv))
     return rv;
 
   rv = tmpFile->GetNativePath(tmpPath);
   if (NS_FAILED(rv))
     return rv;
 #endif
 
-  // Create a JSContext to run a JSObjectBuilder :(
-  // Based on XPCShellEnvironment
-  JSRuntime *rt;
-  JSContext *cx;
-  nsCOMPtr<nsIJSRuntimeService> rtsvc
-    = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
-  if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
-    LOG("failed to get RuntimeService");
-    return NS_ERROR_FAILURE;;
-  }
-
-  cx = JS_NewContext(rt, 8192);
-  if (!cx) {
-    LOG("Failed to get context");
-    return NS_ERROR_FAILURE;
-  }
-
-  {
-    JSAutoRequest ar(cx);
-    static const JSClass c = {
-      "global", JSCLASS_GLOBAL_FLAGS,
-      JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
-      JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
-      nullptr, nullptr, nullptr, nullptr,
-      JS_GlobalObjectTraceHook
-    };
-    JSObject *obj = JS_NewGlobalObject(cx, &c, nullptr, JS::FireOnNewGlobalHook);
-
-    std::ofstream stream;
-    stream.open(tmpPath.get());
-    if (stream.is_open()) {
-      JSAutoCompartment autoComp(cx, obj);
-      JSObject* profileObj = profiler_get_profile_jsobject(cx);
-      JS::Rooted<JS::Value> val(cx, OBJECT_TO_JSVAL(profileObj));
-      JS_Stringify(cx, &val, JS::NullPtr(), JS::NullHandleValue, WriteCallback, &stream);
-      stream.close();
-      LOGF("Saved to %s", tmpPath.get());
-    } else {
-      LOG("Fail to open profile log file.");
-    }
-  }
-  JS_DestroyContext(cx);
+  profiler_save_profile_to_file(tmpPath.get());
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS1(ProfileSaveEvent, nsIProfileSaveEvent)
 
 nsresult
 ProfileSaveEvent::AddSubProfile(const char* aProfile) {
--- a/tools/profiler/SaveProfileTask.h
+++ b/tools/profiler/SaveProfileTask.h
@@ -10,19 +10,16 @@
 #include "nsThreadUtils.h"
 #include "nsIXULRuntime.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsXULAppAPI.h"
 #include "nsIJSRuntimeService.h"
 #include "nsIProfileSaveEvent.h"
 
-#include <ostream>
-#include <fstream>
-
 #ifdef XP_WIN
  #include <windows.h>
  #define getpid GetCurrentProcessId
 #else
  #include <unistd.h>
 #endif
 
 /**
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -16,18 +16,17 @@
 #include "prenv.h"
 #include "prtime.h"
 #include "shared-libraries.h"
 #include "mozilla/StackWalk.h"
 #include "TableTicker.h"
 #include "nsXULAppAPI.h"
 
 // JSON
-#include "JSObjectBuilder.h"
-#include "JSCustomObjectBuilder.h"
+#include "JSStreamWriter.h"
 
 // Meta
 #include "nsXPCOM.h"
 #include "nsXPCOMCID.h"
 #include "nsIHttpProtocolHandler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIXULRuntime.h"
 #include "nsIXULAppInfo.h"
@@ -100,213 +99,219 @@ void TableTicker::HandleSaveRequest()
   mSaveRequested = false;
 
   // TODO: Use use the ipc/chromium Tasks here to support processes
   // without XPCOM.
   nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask();
   NS_DispatchToMainThread(runnable);
 }
 
-template <typename Builder>
-typename Builder::Object TableTicker::GetMetaJSCustomObject(Builder& b)
+void TableTicker::StreamMetaJSCustomObject(JSStreamWriter& b)
 {
-  typename Builder::RootedObject meta(b.context(), b.CreateObject());
+  b.BeginObject();
 
-  b.DefineProperty(meta, "version", 2);
-  b.DefineProperty(meta, "interval", interval());
-  b.DefineProperty(meta, "stackwalk", mUseStackWalk);
-  b.DefineProperty(meta, "jank", mJankOnly);
-  b.DefineProperty(meta, "processType", XRE_GetProcessType());
+    b.NameValue("version", 2);
+    b.NameValue("interval", interval());
+    b.NameValue("stackwalk", mUseStackWalk);
+    b.NameValue("jank", mJankOnly);
+    b.NameValue("processType", XRE_GetProcessType());
+
+    TimeDuration delta = TimeStamp::Now() - sStartTime;
+    b.NameValue("startTime", static_cast<float>(PR_Now()/1000.0 - delta.ToMilliseconds()));
 
-  TimeDuration delta = TimeStamp::Now() - sStartTime;
-  b.DefineProperty(meta, "startTime", static_cast<float>(PR_Now()/1000.0 - delta.ToMilliseconds()));
-
-  nsresult res;
-  nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
-  if (!NS_FAILED(res)) {
-    nsAutoCString string;
+    nsresult res;
+    nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
+    if (!NS_FAILED(res)) {
+      nsAutoCString string;
 
-    res = http->GetPlatform(string);
-    if (!NS_FAILED(res))
-      b.DefineProperty(meta, "platform", string.Data());
+      res = http->GetPlatform(string);
+      if (!NS_FAILED(res))
+        b.NameValue("platform", string.Data());
 
-    res = http->GetOscpu(string);
-    if (!NS_FAILED(res))
-      b.DefineProperty(meta, "oscpu", string.Data());
+      res = http->GetOscpu(string);
+      if (!NS_FAILED(res))
+        b.NameValue("oscpu", string.Data());
 
-    res = http->GetMisc(string);
-    if (!NS_FAILED(res))
-      b.DefineProperty(meta, "misc", string.Data());
-  }
+      res = http->GetMisc(string);
+      if (!NS_FAILED(res))
+        b.NameValue("misc", string.Data());
+    }
 
-  nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
-  if (runtime) {
-    nsAutoCString string;
+    nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+    if (runtime) {
+      nsAutoCString string;
 
-    res = runtime->GetXPCOMABI(string);
-    if (!NS_FAILED(res))
-      b.DefineProperty(meta, "abi", string.Data());
+      res = runtime->GetXPCOMABI(string);
+      if (!NS_FAILED(res))
+        b.NameValue("abi", string.Data());
 
-    res = runtime->GetWidgetToolkit(string);
-    if (!NS_FAILED(res))
-      b.DefineProperty(meta, "toolkit", string.Data());
-  }
+      res = runtime->GetWidgetToolkit(string);
+      if (!NS_FAILED(res))
+        b.NameValue("toolkit", string.Data());
+    }
 
-  nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
-  if (appInfo) {
-    nsAutoCString string;
+    nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
+    if (appInfo) {
+      nsAutoCString string;
 
-    res = appInfo->GetName(string);
-    if (!NS_FAILED(res))
-      b.DefineProperty(meta, "product", string.Data());
-  }
+      res = appInfo->GetName(string);
+      if (!NS_FAILED(res))
+        b.NameValue("product", string.Data());
+    }
 
-  return meta;
+  b.EndObject();
 }
 
 void TableTicker::ToStreamAsJSON(std::ostream& stream)
 {
-  JSCustomObjectBuilder b;
-  JSCustomObject* profile = b.CreateObject();
-  BuildJSObject(b, profile);
-  b.Serialize(profile, stream);
-  b.DeleteObject(profile);
+  JSStreamWriter b(stream);
+  StreamJSObject(b);
 }
 
 JSObject* TableTicker::ToJSObject(JSContext *aCx)
 {
-  JSObjectBuilder b(aCx);
-  JS::RootedObject profile(aCx, b.CreateObject());
-  BuildJSObject(b, profile);
-  return profile;
+  JS::RootedValue val(aCx);
+  std::stringstream ss;
+  JSStreamWriter b(ss);
+  StreamJSObject(b);
+  NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str()));
+  JS_ParseJSON(aCx, static_cast<const jschar*>(js_string.get()), js_string.Length(), &val);
+  return &val.toObject();
 }
 
-template <typename Builder>
 struct SubprocessClosure {
-  SubprocessClosure(Builder *aBuilder, typename Builder::ArrayHandle aThreads)
-    : mBuilder(aBuilder), mThreads(aThreads)
+  SubprocessClosure(JSStreamWriter *aWriter)
+    : mWriter(aWriter)
   {}
 
-  Builder* mBuilder;
-  typename Builder::ArrayHandle mThreads;
+  JSStreamWriter* mWriter;
 };
 
-template <typename Builder>
 void SubProcessCallback(const char* aProfile, void* aClosure)
 {
   // Called by the observer to get their profile data included
   // as a sub profile
-  SubprocessClosure<Builder>* closure = (SubprocessClosure<Builder>*)aClosure;
+  SubprocessClosure* closure = (SubprocessClosure*)aClosure;
 
-  closure->mBuilder->ArrayPush(closure->mThreads, aProfile);
+  // Add the string profile into the profile
+  closure->mWriter->Value(aProfile);
 }
 
+
 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
-template <typename Builder>
 static
-typename Builder::Object BuildJavaThreadJSObject(Builder& b)
+void BuildJavaThreadJSObject(JSStreamWriter& b)
 {
-  typename Builder::RootedObject javaThread(b.context(), b.CreateObject());
-  b.DefineProperty(javaThread, "name", "Java Main Thread");
+  b.BeginObject();
 
-  typename Builder::RootedArray samples(b.context(), b.CreateArray());
-  b.DefineProperty(javaThread, "samples", samples);
+    b.NameValue("name", "Java Main Thread");
+
+    b.Name("samples");
+    b.BeginArray();
 
-  int sampleId = 0;
-  while (true) {
-    int frameId = 0;
-    typename Builder::RootedObject sample(b.context());
-    typename Builder::RootedArray frames(b.context());
-    while (true) {
-      nsCString result;
-      bool hasFrame = AndroidBridge::Bridge()->GetFrameNameJavaProfiling(0, sampleId, frameId, result);
-      if (!hasFrame) {
-        if (frames) {
-          b.DefineProperty(sample, "frames", frames);
+      // for each sample
+      for (int sampleId = 0; true; sampleId++) {
+        bool firstRun = true;
+        // for each frame
+        for (int frameId = 0; true; frameId++) {
+          nsCString result;
+          bool hasFrame = AndroidBridge::Bridge()->GetFrameNameJavaProfiling(0, sampleId, frameId, result);
+          // when we run out of frames, we stop looping
+          if (!hasFrame) {
+            // if we found at least one frame, we have objects to close
+            if (!firstRun) {
+                b.EndArray();
+              b.EndObject();
+            }
+            break;
+          }
+          // the first time around, open the sample object and frames array
+          if (firstRun) {
+            firstRun = false;
+
+            double sampleTime =
+              mozilla::widget::android::GeckoJavaSampler::GetSampleTimeJavaProfiling(0, sampleId);
+
+            b.BeginObject();
+              b.NameValue("time", sampleTime);
+
+              b.Name("frames");
+              b.BeginArray();
+          }
+          // add a frame to the sample
+          b.BeginObject();
+            b.NameValue("location", result.BeginReading());
+          b.EndObject();
         }
-        break;
-      }
-      if (!sample) {
-        sample = b.CreateObject();
-        frames = b.CreateArray();
-        b.DefineProperty(sample, "frames", frames);
-        b.ArrayPush(samples, sample);
-
-        double sampleTime =
-          mozilla::widget::android::GeckoJavaSampler::GetSampleTimeJavaProfiling(0, sampleId);
-        b.DefineProperty(sample, "time", sampleTime);
+        // if we found no frames for this sample, we are done
+        if (firstRun) {
+          break;
+        }
       }
-      typename Builder::RootedObject frame(b.context(), b.CreateObject());
-      b.DefineProperty(frame, "location", result.BeginReading());
-      b.ArrayPush(frames, frame);
-      frameId++;
-    }
-    if (frameId == 0) {
-      break;
-    }
-    sampleId++;
-  }
 
-  return javaThread;
+    b.EndArray();
+
+  b.EndObject();
 }
 #endif
 
-template <typename Builder>
-void TableTicker::BuildJSObject(Builder& b, typename Builder::ObjectHandle profile)
+void TableTicker::StreamJSObject(JSStreamWriter& b)
 {
-  // Put shared library info
-  b.DefineProperty(profile, "libs", GetSharedLibraryInfoString().c_str());
+  b.BeginObject();
+    // Put shared library info
+    b.NameValue("libs", GetSharedLibraryInfoString().c_str());
 
-  // Put meta data
-  typename Builder::RootedObject meta(b.context(), GetMetaJSCustomObject(b));
-  b.DefineProperty(profile, "meta", meta);
+    // Put meta data
+    b.Name("meta");
+    StreamMetaJSCustomObject(b);
 
-  // Lists the samples for each ThreadProfile
-  typename Builder::RootedArray threads(b.context(), b.CreateArray());
-  b.DefineProperty(profile, "threads", threads);
+    // Lists the samples for each ThreadProfile
+    b.Name("threads");
+    b.BeginArray();
 
-  SetPaused(true);
+      SetPaused(true);
 
-  {
-    mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex);
+      {
+        mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex);
 
-    for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
-      // Thread not being profiled, skip it
-      if (!sRegisteredThreads->at(i)->Profile())
-        continue;
+        for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
+          // Thread not being profiled, skip it
+          if (!sRegisteredThreads->at(i)->Profile())
+            continue;
 
-      MutexAutoLock lock(*sRegisteredThreads->at(i)->Profile()->GetMutex());
+          MutexAutoLock lock(*sRegisteredThreads->at(i)->Profile()->GetMutex());
 
-      typename Builder::RootedObject threadSamples(b.context(), b.CreateObject());
-      sRegisteredThreads->at(i)->Profile()->BuildJSObject(b, threadSamples);
-      b.ArrayPush(threads, threadSamples);
-    }
-  }
+          sRegisteredThreads->at(i)->Profile()->StreamJSObject(b);
+        }
+      }
 
-#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
-  if (ProfileJava()) {
-    mozilla::widget::android::GeckoJavaSampler::PauseJavaProfiling();
-
-    typename Builder::RootedObject javaThread(b.context(), BuildJavaThreadJSObject(b));
-    b.ArrayPush(threads, javaThread);
+      // Send a event asking any subprocesses (plugins) to
+      // give us their information
+      SubprocessClosure closure(&b);
+      nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+      if (os) {
+        nsRefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback, &closure);
+        os->NotifyObservers(pse, "profiler-subprocess", nullptr);
+      }
 
-    mozilla::widget::android::GeckoJavaSampler::UnpauseJavaProfiling();
-  }
-#endif
+  #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+      if (ProfileJava()) {
+        mozilla::widget::android::GeckoJavaSampler::PauseJavaProfiling();
 
-  SetPaused(false);
+        BuildJavaThreadJSObject(b);
 
-  // Send a event asking any subprocesses (plugins) to
-  // give us their information
-  SubprocessClosure<Builder> closure(&b, threads);
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (os) {
-    nsRefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback<Builder>, &closure);
-    os->NotifyObservers(pse, "profiler-subprocess", nullptr);
-  }
+        mozilla::widget::android::GeckoJavaSampler::UnpauseJavaProfiling();
+      }
+  #endif
+
+      SetPaused(false);
+    b.EndArray();
+
+  b.EndObject();
+
 }
 
 // END SaveProfileTask et al
 ////////////////////////////////////////////////////////////////////////
 
 static
 void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr)
 {
--- a/tools/profiler/TableTicker.h
+++ b/tools/profiler/TableTicker.h
@@ -171,18 +171,17 @@ class TableTicker: public Sampler {
       }
     }
 
     return mPrimaryThreadProfile;
   }
 
   void ToStreamAsJSON(std::ostream& stream);
   virtual JSObject *ToJSObject(JSContext *aCx);
-  template <typename Builder> typename Builder::Object GetMetaJSCustomObject(Builder& b);
-
+  void StreamMetaJSCustomObject(JSStreamWriter& b);
   bool HasUnwinderThread() const { return mUnwinderThread; }
   bool ProfileJS() const { return mProfileJS; }
   bool ProfileJava() const { return mProfileJava; }
   bool ProfilePower() const { return mProfilePower; }
   bool ProfileThreads() const { return mProfileThreads; }
   bool InPrivacyMode() const { return mPrivacyMode; }
   bool AddMainThreadIO() const { return mAddMainThreadIO; }
 
@@ -191,17 +190,17 @@ protected:
   virtual void UnwinderTick(TickSample* sample);
 
   // Called within a signal. This function must be reentrant
   virtual void InplaceTick(TickSample* sample);
 
   // Not implemented on platforms which do not support backtracing
   void doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample);
 
-  template <typename Builder> void BuildJSObject(Builder& b, typename Builder::ObjectHandle profile);
+  void StreamJSObject(JSStreamWriter& b);
 
   // This represent the application's main thread (SAMPLER_INIT)
   ThreadProfile* mPrimaryThreadProfile;
   bool mSaveRequested;
   bool mAddLeafAddresses;
   bool mUseStackWalk;
   bool mJankOnly;
   bool mProfileJS;
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -19,18 +19,17 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
         'PseudoStack.h',
         'shared-libraries.h',
     ]
     EXTRA_JS_MODULES = [
         'Profiler.jsm',
     ]
     UNIFIED_SOURCES += [
         'BreakpadSampler.cpp',
-        'JSCustomObjectBuilder.cpp',
-        'JSObjectBuilder.cpp',
+        'JSStreamWriter.cpp',
         'nsProfiler.cpp',
         'nsProfilerFactory.cpp',
         'platform.cpp',
         'ProfileEntry.cpp',
         'ProfilerBacktrace.cpp',
         'ProfilerIOInterposeObserver.cpp',
         'ProfilerMarkers.cpp',
         'SaveProfileTask.cpp',
--- a/tools/profiler/nsProfiler.cpp
+++ b/tools/profiler/nsProfiler.cpp
@@ -199,20 +199,20 @@ nsProfiler::GetSharedLibraryInformation(
 {
   aOutString.Assign(NS_ConvertUTF8toUTF16(GetSharedLibraryInfoString().c_str()));
   return NS_OK;
 }
 
 NS_IMETHODIMP nsProfiler::GetProfileData(JSContext* aCx,
                                          JS::MutableHandle<JS::Value> aResult)
 {
-  JSObject *obj = profiler_get_profile_jsobject(aCx);
-  if (!obj)
+  JS::RootedObject obj(aCx, profiler_get_profile_jsobject(aCx));
+  if (!obj) {
     return NS_ERROR_FAILURE;
-
+  }
   aResult.setObject(*obj);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsProfiler::IsActive(bool *aIsActive)
 {
   *aIsActive = profiler_is_active();
--- a/tools/profiler/platform.cpp
+++ b/tools/profiler/platform.cpp
@@ -127,39 +127,30 @@ ProfilerMarker::SetGeneration(int aGenID
   mGenID = aGenID;
 }
 
 float
 ProfilerMarker::GetTime() {
   return mTime;
 }
 
-template<typename Builder> void
-ProfilerMarker::BuildJSObject(Builder& b, typename Builder::ArrayHandle markers) const {
-  typename Builder::RootedObject marker(b.context(), b.CreateObject());
-  b.DefineProperty(marker, "name", GetMarkerName());
-  // TODO: Store the callsite for this marker if available:
-  // if have location data
-  //   b.DefineProperty(marker, "location", ...);
-  if (mPayload) {
-    typename Builder::RootedObject markerData(b.context(),
-                                              mPayload->PreparePayload(b));
-    b.DefineProperty(marker, "data", markerData);
-  }
-  b.DefineProperty(marker, "time", mTime);
-  b.ArrayPush(markers, marker);
+void ProfilerMarker::StreamJSObject(JSStreamWriter& b) const {
+  b.BeginObject();
+    b.NameValue("name", GetMarkerName());
+    // TODO: Store the callsite for this marker if available:
+    // if have location data
+    //   b.NameValue(marker, "location", ...);
+    if (mPayload) {
+      b.Name("data");
+      mPayload->StreamPayload(b);
+    }
+    b.NameValue("time", mTime);
+  b.EndObject();
 }
 
-template void
-ProfilerMarker::BuildJSObject<JSCustomObjectBuilder>(JSCustomObjectBuilder& b,
-                              JSCustomObjectBuilder::ArrayHandle markers) const;
-template void
-ProfilerMarker::BuildJSObject<JSObjectBuilder>(JSObjectBuilder& b,
-                                    JSObjectBuilder::ArrayHandle markers) const;
-
 PendingMarkers::~PendingMarkers() {
   clearMarkers();
   if (mSignalLock != false) {
     // We're releasing the pseudostack while it's still in use.
     // The label macros keep a non ref counted reference to the
     // stack to avoid a TLS. If these are not all cleared we will
     // get a use-after-free so better to crash now.
     abort();
@@ -568,16 +559,34 @@ JSObject *mozilla_sampler_get_profile_da
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return nullptr;
   }
 
   return t->ToJSObject(aCx);
 }
 
+void mozilla_sampler_save_profile_to_file(const char* aFilename)
+{
+  TableTicker *t = tlsTicker.get();
+  if (!t) {
+    return;
+  }
+
+  std::ofstream stream;
+  stream.open(aFilename);
+  if (stream.is_open()) {
+    t->ToStreamAsJSON(stream);
+    stream.close();
+    LOGF("Saved to %s", aFilename);
+  } else {
+    LOG("Fail to open profile log file.");
+  }
+}
+
 
 const char** mozilla_sampler_get_features()
 {
   static const char* features[] = {
 #if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND)
     // Walk the C++ stack.
     "stackwalk",
 #endif