Bug 1024774 - Part 3: Serialize heap snapshots. r=jimb
☠☠ backed out by 0b202671c9e2 ☠ ☠
authorNick Fitzgerald <fitzgen@gmail.com>
Wed, 22 Apr 2015 11:09:54 -0700
changeset 240581 2522eb4d3cf249a9add3dff6c860299d35430145
parent 240580 89a502d88f9c39d4702a3fc342035d41b5d5e830
child 240582 cf7bbb3774c044d9ca526f96e94606719c44b4f3
push id28636
push userkwierso@gmail.com
push dateThu, 23 Apr 2015 00:16:12 +0000
treeherdermozilla-central@a5af73b32ac8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs1024774
milestone40.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 1024774 - Part 3: Serialize heap snapshots. r=jimb
js/public/Debug.h
js/public/UbiNode.h
js/public/UbiNodeTraverse.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/vm/Debugger-inl.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/DebuggerMemory.cpp
js/src/vm/UbiNode.cpp
toolkit/devtools/server/ChromeUtils.cpp
toolkit/devtools/server/ChromeUtils.h
toolkit/devtools/server/CoreDump.pb.cc
toolkit/devtools/server/CoreDump.pb.h
toolkit/devtools/server/CoreDump.proto
toolkit/devtools/server/generate-core-dump-sources.sh
toolkit/devtools/server/moz.build
--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -257,17 +257,23 @@ class BuilderOrigin : public Builder {
 //
 // Debugger.Memory wants to be able to report how many bytes items in memory are
 // consuming. To do this, it needs a function that accepts a pointer to a block,
 // and returns the number of bytes allocated to that block. SpiderMonkey itself
 // doesn't know which function is appropriate to use, but the embedding does.
 
 // Tell Debuggers in |runtime| to use |mallocSizeOf| to find the size of
 // malloc'd blocks.
-void SetDebuggerMallocSizeOf(JSRuntime* runtime, mozilla::MallocSizeOf mallocSizeOf);
+JS_PUBLIC_API(void)
+SetDebuggerMallocSizeOf(JSRuntime* runtime, mozilla::MallocSizeOf mallocSizeOf);
+
+// Get the MallocSizeOf function that the given runtime is using to find the
+// size of malloc'd blocks.
+JS_PUBLIC_API(mozilla::MallocSizeOf)
+GetDebuggerMallocSizeOf(JSRuntime* runtime);
 
 
 
 // Debugger and Garbage Collection Events
 // --------------------------------------
 //
 // The Debugger wants to report about its debuggees' GC cycles, however entering
 // JS after a GC is troublesome since SpiderMonkey will often do something like
@@ -311,15 +317,20 @@ onNewPromise(JSContext* cx, HandleObject
 // unsettled to settled once).
 JS_PUBLIC_API(void)
 onPromiseSettled(JSContext* cx, HandleObject promise);
 
 
 
 // Return true if the given value is a Debugger object, false otherwise.
 JS_PUBLIC_API(bool)
-IsDebugger(JS::Value val);
+IsDebugger(const JSObject &obj);
+
+// Append each of the debuggee global objects observed by the Debugger object
+// |dbgObj| to |vector|. Returns true on success, false on failure.
+JS_PUBLIC_API(bool)
+GetDebuggeeGlobals(JSContext *cx, const JSObject &dbgObj, AutoObjectVector &vector);
 
 } // namespace dbg
 } // namespace JS
 
 
 #endif /* js_Debug_h */
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -246,18 +246,18 @@ struct Concrete {
     // |nsISupports|, we would like a ubi::Node that knows its final
     // implementation type.
     //
     // So, we delegate the actual construction to this specialization, which
     // knows Referent's details.
     static void construct(void* storage, Referent* referent);
 };
 
-// A container for a Base instance; all members simply forward to the contained instance.
-// This container allows us to pass ubi::Node instances by value.
+// A container for a Base instance; all members simply forward to the contained
+// instance.  This container allows us to pass ubi::Node instances by value.
 class Node {
     // Storage in which we allocate Base subclasses.
     mozilla::AlignedStorage2<Base> storage;
     Base* base() { return storage.addr(); }
     const Base* base() const { return storage.addr(); }
 
     template<typename T>
     void construct(T* ptr) {
@@ -351,16 +351,31 @@ class Node {
     size_t size(mozilla::MallocSizeOf mallocSizeof) const {
         return base()->size(mallocSizeof);
     }
 
     UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames = true) const {
         return base()->edges(cx, wantNames);
     }
 
+    // An identifier for this node, guaranteed to be stable and unique for as
+    // long as this ubi::Node's referent is alive and at the same address.
+    //
+    // This is probably suitable for use in serializations, as it is an integral
+    // type. It may also help save memory when constructing HashSets of
+    // ubi::Nodes: since a uintptr_t will always be smaller than a ubi::Node, a
+    // HashSet<ubi::Node::Id> will use less space per element than a
+    // HashSet<ubi::Node>.
+    //
+    // (Note that 'unique' only means 'up to equality on ubi::Node'; see the
+    // caveats about multiple objects allocated at the same address for
+    // 'ubi::Node::operator=='.)
+    typedef uintptr_t Id;
+    Id identifier() const { return reinterpret_cast<Id>(base()->ptr); }
+
     // A hash policy for ubi::Nodes.
     // This simply uses the stock PointerHasher on the ubi::Node's pointer.
     // We specialize DefaultHasher below to make this the default.
     class HashPolicy {
         typedef js::PointerHasher<void*, mozilla::tl::FloorLog2<sizeof(void*)>::value> PtrHash;
 
       public:
         typedef Node Lookup;
@@ -470,16 +485,43 @@ class SimpleEdge : public Edge {
         this->~SimpleEdge();
         new(this) SimpleEdge(mozilla::Move(rhs));
         return *this;
     }
 };
 
 typedef mozilla::Vector<SimpleEdge, 8, js::TempAllocPolicy> SimpleEdgeVector;
 
+// An EdgeRange concrete class that holds a pre-existing vector of
+// SimpleEdges. A PreComputedEdgeRange does not take ownership of its
+// SimpleEdgeVector; it is up to the PreComputedEdgeRange's consumer to manage
+// that lifetime.
+class PreComputedEdgeRange : public EdgeRange {
+    SimpleEdgeVector& edges;
+    size_t            i;
+
+    void settle() {
+        front_ = i < edges.length() ? &edges[i] : nullptr;
+    }
+
+  public:
+    explicit PreComputedEdgeRange(JSContext* cx, SimpleEdgeVector& edges)
+      : edges(edges),
+        i(0)
+    {
+        settle();
+    }
+
+    void popFront() override {
+        MOZ_ASSERT(!empty());
+        i++;
+        settle();
+    }
+};
+
 
 // RootList is a class that can be pointed to by a |ubi::Node|, creating a
 // fictional root-of-roots which has edges to every GC root in the JS
 // runtime. Having a single root |ubi::Node| is useful for algorithms written
 // with the assumption that there aren't multiple roots (such as computing
 // dominator trees) and you want a single point of entry. It also ensures that
 // the roots themselves get visited by |ubi::BreadthFirst| (they would otherwise
 // only be used as starting points).
@@ -516,16 +558,20 @@ class MOZ_STACK_CLASS RootList {
 
     // Find all GC roots.
     bool init();
     // Find only GC roots in the provided set of |Zone|s.
     bool init(ZoneSet& debuggees);
     // Find only GC roots in the given Debugger object's set of debuggee zones.
     bool init(HandleObject debuggees);
 
+    // Returns true if the RootList has been initialized successfully, false
+    // otherwise.
+    bool initialized() { return noGC.isSome(); }
+
     // Explicitly add the given Node as a root in this RootList. If wantNames is
     // true, you must pass an edgeName. The RootList does not take ownership of
     // edgeName.
     bool addRoot(Node node, const char16_t* edgeName = nullptr);
 };
 
 
 // Concrete classes for ubi::Node referent types.
--- a/js/public/UbiNodeTraverse.h
+++ b/js/public/UbiNodeTraverse.h
@@ -115,17 +115,17 @@ struct BreadthFirst {
     // This should be called only once per instance of this class.
     //
     // Return false on OOM or error return from |handler::operator()|.
     bool traverse()
     {
         MOZ_ASSERT(!traversalBegun);
         traversalBegun = true;
 
-        // While there are pending nodes, visit them, until we've found a path to the target.
+        // While there are pending nodes, visit them.
         while (!pending.empty()) {
             Node origin = pending.front();
             pending.popFront();
 
             // Get a range containing all origin's outgoing edges.
             auto range = origin.edges(cx, wantNames);
             if (!range)
                 return false;
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -291,16 +291,22 @@ js::IsSystemZone(Zone* zone)
 
 JS_FRIEND_API(bool)
 js::IsAtomsCompartment(JSCompartment* comp)
 {
     return comp->runtimeFromAnyThread()->isAtomsCompartment(comp);
 }
 
 JS_FRIEND_API(bool)
+js::IsAtomsZone(JS::Zone* zone)
+{
+    return zone->runtimeFromAnyThread()->isAtomsZone(zone);
+}
+
+JS_FRIEND_API(bool)
 js::IsInNonStrictPropertySet(JSContext* cx)
 {
     jsbytecode* pc;
     JSScript* script = cx->currentScript(&pc, JSContext::ALLOW_CROSS_COMPARTMENT);
     return script && !IsStrictSetPC(pc) && (js_CodeSpec[*pc].format & JOF_SET);
 }
 
 JS_FRIEND_API(bool)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -468,16 +468,19 @@ extern JS_FRIEND_API(bool)
 IsSystemCompartment(JSCompartment* comp);
 
 extern JS_FRIEND_API(bool)
 IsSystemZone(JS::Zone* zone);
 
 extern JS_FRIEND_API(bool)
 IsAtomsCompartment(JSCompartment* comp);
 
+extern JS_FRIEND_API(bool)
+IsAtomsZone(JS::Zone *zone);
+
 /*
  * Returns whether we're in a non-strict property set (in that we're in a
  * non-strict script and the bytecode we're on is a property set).  The return
  * value does NOT indicate any sort of exception was thrown: it's just a
  * boolean.
  */
 extern JS_FRIEND_API(bool)
 IsInNonStrictPropertySet(JSContext* cx);
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -22,17 +22,17 @@ js::Debugger::onLeaveFrame(JSContext* cx
     MOZ_ASSERT_IF(evalTraps, frame.isDebuggee());
     if (frame.isDebuggee())
         ok = slowPathOnLeaveFrame(cx, frame, ok);
     MOZ_ASSERT(!inFrameMaps(frame));
     return ok;
 }
 
 /* static */ inline js::Debugger*
-js::Debugger::fromJSObject(JSObject* obj)
+js::Debugger::fromJSObject(const JSObject* obj)
 {
     MOZ_ASSERT(js::GetObjectClass(obj) == &jsclass);
     return (Debugger*) obj->as<NativeObject>().getPrivate();
 }
 
 /* static */ JSTrapStatus
 js::Debugger::onEnterFrame(JSContext* cx, AbstractFramePtr frame)
 {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -7877,26 +7877,37 @@ JS::dbg::onNewPromise(JSContext* cx, Han
 JS_PUBLIC_API(void)
 JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise)
 {
     AssertIsPromise(cx, promise);
     Debugger::slowPathPromiseHook(cx, Debugger::OnPromiseSettled, promise);
 }
 
 JS_PUBLIC_API(bool)
-JS::dbg::IsDebugger(JS::Value val)
-{
-    if (!val.isObject())
-        return false;
-
-    JSObject& obj = val.toObject();
-    if (obj.getClass() != &Debugger::jsclass)
-        return false;
-
-    return js::Debugger::fromJSObject(&obj) != nullptr;
+JS::dbg::IsDebugger(const JSObject &obj)
+{
+    return js::GetObjectClass(&obj) == &Debugger::jsclass &&
+           js::Debugger::fromJSObject(&obj) != nullptr;
+}
+
+JS_PUBLIC_API(bool)
+JS::dbg::GetDebuggeeGlobals(JSContext *cx, const JSObject &dbgObj, AutoObjectVector &vector)
+{
+    MOZ_ASSERT(IsDebugger(dbgObj));
+    js::Debugger *dbg = js::Debugger::fromJSObject(&dbgObj);
+
+    if (!vector.reserve(vector.length() + dbg->debuggees.count())) {
+        JS_ReportOutOfMemory(cx);
+        return false;
+    }
+
+    for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront())
+        vector.infallibleAppend(static_cast<JSObject*>(r.front()));
+
+    return true;
 }
 
 
 /*** JS::dbg::GarbageCollectionEvent **************************************************************/
 
 namespace JS {
 namespace dbg {
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -183,17 +183,18 @@ typedef JSObject Env;
 
 class Debugger : private mozilla::LinkedListElement<Debugger>
 {
     friend class Breakpoint;
     friend class DebuggerMemory;
     friend class SavedStacks;
     friend class mozilla::LinkedListElement<Debugger>;
     friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
-    friend bool (::JS::dbg::IsDebugger)(JS::Value val);
+    friend bool (::JS::dbg::IsDebugger)(const JSObject&);
+    friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, const JSObject&, AutoObjectVector&);
     friend JSObject* SavedStacksMetadataCallback(JSContext* cx);
     friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise);
     friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise);
     friend bool JS::dbg::FireOnGarbageCollectionHook(JSContext* cx,
                                                      JS::dbg::GarbageCollectionEvent::Ptr&& data);
 
   public:
     enum Hook {
@@ -592,17 +593,17 @@ class Debugger : private mozilla::Linked
 
   public:
     Debugger(JSContext* cx, NativeObject* dbg);
     ~Debugger();
 
     bool init(JSContext* cx);
     inline const js::HeapPtrNativeObject& toJSObject() const;
     inline js::HeapPtrNativeObject& toJSObjectRef();
-    static inline Debugger* fromJSObject(JSObject* obj);
+    static inline Debugger* fromJSObject(const JSObject* obj);
     static Debugger* fromChildJSObject(JSObject* obj);
 
     bool hasMemory() const;
     DebuggerMemory& memory() const;
 
     WeakGlobalObjectSet::Range allDebuggees() const { return debuggees.all(); }
 
     /*********************************** Methods for interaction with the GC. */
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -314,21 +314,28 @@ DebuggerMemory::setOnGarbageCollection(J
 {
     THIS_DEBUGGER_MEMORY(cx, argc, vp, "(set onGarbageCollection)", args, memory);
     return Debugger::setHookImpl(cx, args, *memory->getDebugger(), Debugger::OnGarbageCollection);
 }
 
 
 /* Debugger.Memory.prototype.takeCensus */
 
-void
-JS::dbg::SetDebuggerMallocSizeOf(JSRuntime* rt, mozilla::MallocSizeOf mallocSizeOf) {
+JS_PUBLIC_API(void)
+JS::dbg::SetDebuggerMallocSizeOf(JSRuntime* rt, mozilla::MallocSizeOf mallocSizeOf)
+{
     rt->debuggerMallocSizeOf = mallocSizeOf;
 }
 
+JS_PUBLIC_API(mozilla::MallocSizeOf)
+JS::dbg::GetDebuggerMallocSizeOf(JSRuntime *rt)
+{
+    return rt->debuggerMallocSizeOf;
+}
+
 namespace js {
 namespace dbg {
 
 // Common data for census traversals.
 struct Census {
     JSContext * const cx;
     JS::ZoneSet debuggeeZones;
     Zone* atomsZone;
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -314,18 +314,18 @@ RootList::init(ZoneSet& debuggees)
 
     noGC.emplace(cx->runtime());
     return true;
 }
 
 bool
 RootList::init(HandleObject debuggees)
 {
-    MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(ObjectValue(*debuggees)));
-    js::Debugger* dbg = js::Debugger::fromJSObject(debuggees);
+    MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees));
+    js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get());
 
     ZoneSet debuggeeZones;
     if (!debuggeeZones.init())
         return false;
 
     for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
         if (!debuggeeZones.put(r.front()->zone()))
             return false;
@@ -357,36 +357,16 @@ RootList::addRoot(Node node, const char1
         name = DuplicateString(cx, edgeName);
         if (!name)
             return false;
     }
 
     return edges.append(mozilla::Move(SimpleEdge(name.release(), node)));
 }
 
-// An EdgeRange concrete class that holds a pre-existing vector of SimpleEdges.
-class PreComputedEdgeRange : public EdgeRange {
-    SimpleEdgeVector& edges;
-    size_t           i;
-
-    void settle() {
-        front_ = i < edges.length() ? &edges[i] : nullptr;
-    }
-
-  public:
-    explicit PreComputedEdgeRange(JSContext* cx, SimpleEdgeVector& edges)
-      : edges(edges),
-        i(0)
-    {
-        settle();
-    }
-
-    void popFront() override { i++; settle(); }
-};
-
 const char16_t Concrete<RootList>::concreteTypeName[] = MOZ_UTF16("RootList");
 
 UniquePtr<EdgeRange>
 Concrete<RootList>::edges(JSContext* cx, bool wantNames) const {
     MOZ_ASSERT_IF(wantNames, get().wantNames);
     return UniquePtr<EdgeRange>(cx->new_<PreComputedEdgeRange>(cx, get().edges));
 }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/ChromeUtils.cpp
@@ -0,0 +1,386 @@
+/* -*-  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 "ChromeUtils.h"
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/gzip_stream.h>
+
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCRTGlue.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prtypes.h"
+
+#include "js/Debug.h"
+#include "js/UbiNodeTraverse.h"
+
+namespace mozilla {
+namespace devtools {
+
+using namespace JS;
+using namespace dom;
+
+
+// If we are only taking a snapshot of the heap affected by the given set of
+// globals, find the set of zones the globals are allocated within. Returns
+// false on OOM failure.
+static bool
+PopulateZonesWithGlobals(ZoneSet &zones, AutoObjectVector &globals)
+{
+  if (!zones.init())
+    return false;
+
+  unsigned length = globals.length();
+  for (unsigned i = 0; i < length; i++) {
+    if (!zones.put(GetTenuredGCThingZone(globals[i])))
+      return false;
+  }
+
+  return true;
+}
+
+// Add the given set of globals as explicit roots in the given roots
+// list. Returns false on OOM failure.
+static bool
+AddGlobalsAsRoots(AutoObjectVector &globals, ubi::RootList &roots)
+{
+  unsigned length = globals.length();
+  for (unsigned i = 0; i < length; i++) {
+    if (!roots.addRoot(ubi::Node(globals[i].get()),
+                       MOZ_UTF16("heap snapshot global")))
+    {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Choose roots and limits for a traversal, given `boundaries`. Set `roots` to
+// the set of nodes within the boundaries that are referred to by nodes
+// outside. If `boundaries` does not include all JS zones, initialize `zones` to
+// the set of included zones; otherwise, leave `zones` uninitialized. (You can
+// use zones.initialized() to check.)
+//
+// If `boundaries` is incoherent, or we encounter an error while trying to
+// handle it, or we run out of memory, set `rv` appropriately and return
+// `false`.
+static bool
+EstablishBoundaries(JSContext *cx,
+                    ErrorResult &rv,
+                    const HeapSnapshotBoundaries &boundaries,
+                    ubi::RootList &roots,
+                    ZoneSet &zones)
+{
+  MOZ_ASSERT(!roots.initialized());
+  MOZ_ASSERT(!zones.initialized());
+
+  bool foundBoundaryProperty = false;
+
+  if (boundaries.mRuntime.WasPassed()) {
+    foundBoundaryProperty = true;
+
+    if (!boundaries.mRuntime.Value()) {
+      rv.Throw(NS_ERROR_INVALID_ARG);
+      return false;
+    }
+
+    if (!roots.init()) {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return false;
+    }
+  }
+
+  if (boundaries.mDebugger.WasPassed()) {
+    if (foundBoundaryProperty) {
+      rv.Throw(NS_ERROR_INVALID_ARG);
+      return false;
+    }
+    foundBoundaryProperty = true;
+
+    JSObject *dbgObj = boundaries.mDebugger.Value();
+    if (!dbgObj || !dbg::IsDebugger(*dbgObj)) {
+      rv.Throw(NS_ERROR_INVALID_ARG);
+      return false;
+    }
+
+    AutoObjectVector globals(cx);
+    if (!dbg::GetDebuggeeGlobals(cx, *dbgObj, globals) ||
+        !PopulateZonesWithGlobals(zones, globals) ||
+        !roots.init(zones) ||
+        !AddGlobalsAsRoots(globals, roots))
+    {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return false;
+    }
+  }
+
+  if (boundaries.mGlobals.WasPassed()) {
+    if (foundBoundaryProperty) {
+      rv.Throw(NS_ERROR_INVALID_ARG);
+      return false;
+    }
+    foundBoundaryProperty = true;
+
+    uint32_t length = boundaries.mGlobals.Value().Length();
+    if (length == 0) {
+      rv.Throw(NS_ERROR_INVALID_ARG);
+      return false;
+    }
+
+    AutoObjectVector globals(cx);
+    for (uint32_t i = 0; i < length; i++) {
+      JSObject *global = boundaries.mGlobals.Value().ElementAt(i);
+      if (!JS_IsGlobalObject(global)) {
+        rv.Throw(NS_ERROR_INVALID_ARG);
+        return false;
+      }
+      if (!globals.append(global)) {
+        rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+        return false;
+      }
+    }
+
+    if (!PopulateZonesWithGlobals(zones, globals) ||
+        !roots.init(zones) ||
+        !AddGlobalsAsRoots(globals, roots))
+    {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return false;
+    }
+  }
+
+  if (!foundBoundaryProperty) {
+    rv.Throw(NS_ERROR_INVALID_ARG);
+    return false;
+  }
+
+  MOZ_ASSERT(roots.initialized());
+  MOZ_ASSERT_IF(boundaries.mDebugger.WasPassed(), zones.initialized());
+  MOZ_ASSERT_IF(boundaries.mGlobals.WasPassed(), zones.initialized());
+  return true;
+}
+
+
+// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to
+// the given `CodedOutputStream`.
+class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter
+{
+  JSContext *cx;
+  bool      wantNames;
+
+  ::google::protobuf::io::ZeroCopyOutputStream &stream;
+
+  bool writeMessage(const ::google::protobuf::MessageLite &message) {
+    // We have to create a new CodedOutputStream when writing each message so
+    // that the 64MB size limit used by Coded{Output,Input}Stream to prevent
+    // integer overflow is enforced per message rather than on the whole stream.
+    ::google::protobuf::io::CodedOutputStream codedStream(&stream);
+    codedStream.WriteVarint32(message.ByteSize());
+    message.SerializeWithCachedSizes(&codedStream);
+    return !codedStream.HadError();
+  }
+
+public:
+  StreamWriter(JSContext *cx,
+               ::google::protobuf::io::ZeroCopyOutputStream &stream,
+               bool wantNames)
+    : cx(cx)
+    , wantNames(wantNames)
+    , stream(stream)
+  { }
+
+  ~StreamWriter() override { }
+
+  virtual bool writeMetadata(uint64_t timestamp) override {
+    protobuf::Metadata metadata;
+    metadata.set_timestamp(timestamp);
+    return writeMessage(metadata);
+  }
+
+  virtual bool writeNode(const JS::ubi::Node &ubiNode,
+                         EdgePolicy includeEdges) override {
+    protobuf::Node protobufNode;
+    protobufNode.set_id(ubiNode.identifier());
+
+    const char16_t *typeName = ubiNode.typeName();
+    size_t length = NS_strlen(typeName) * sizeof(char16_t);
+    protobufNode.set_typename_(typeName, length);
+
+    JSRuntime *rt = JS_GetRuntime(cx);
+    mozilla::MallocSizeOf mallocSizeOf = dbg::GetDebuggerMallocSizeOf(rt);
+    MOZ_ASSERT(mallocSizeOf);
+    protobufNode.set_size(ubiNode.size(mallocSizeOf));
+
+    if (includeEdges) {
+      auto edges = ubiNode.edges(cx, wantNames);
+      if (NS_WARN_IF(!edges))
+        return false;
+
+      for ( ; !edges->empty(); edges->popFront()) {
+        const ubi::Edge &ubiEdge = edges->front();
+
+        protobuf::Edge *protobufEdge = protobufNode.add_edges();
+        if (NS_WARN_IF(!protobufEdge)) {
+          return false;
+        }
+
+        protobufEdge->set_referent(ubiEdge.referent.identifier());
+
+        if (wantNames && ubiEdge.name) {
+          size_t length = NS_strlen(ubiEdge.name) * sizeof(char16_t);
+          protobufEdge->set_name(ubiEdge.name, length);
+        }
+      }
+    }
+
+    return writeMessage(protobufNode);
+  }
+};
+
+// A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
+// core dump.
+class MOZ_STACK_CLASS HeapSnapshotHandler {
+  CoreDumpWriter& writer;
+  JS::ZoneSet*   zones;
+
+public:
+  HeapSnapshotHandler(CoreDumpWriter& writer,
+                      JS::ZoneSet* zones)
+    : writer(writer),
+      zones(zones)
+  { }
+
+  // JS::ubi::BreadthFirst handler interface.
+
+  class NodeData { };
+  typedef JS::ubi::BreadthFirst<HeapSnapshotHandler> Traversal;
+  bool operator() (Traversal &traversal,
+                   JS::ubi::Node origin,
+                   const JS::ubi::Edge &edge,
+                   NodeData *,
+                   bool first)
+  {
+    // We're only interested in the first time we reach edge.referent, not in
+    // every edge arriving at that node. "But, don't we want to serialize every
+    // edge in the heap graph?" you ask. Don't worry! This edge is still
+    // serialized into the core dump. Serializing a node also serializes each of
+    // its edges, and if we are traversing a given edge, we must have already
+    // visited and serialized the origin node and its edges.
+    if (!first)
+      return true;
+
+    const JS::ubi::Node &referent = edge.referent;
+
+    if (!zones)
+      // We aren't targeting a particular set of zones, so serialize all the
+      // things!
+      return writer.writeNode(referent, CoreDumpWriter::INCLUDE_EDGES);
+
+    // We are targeting a particular set of zones. If this node is in our target
+    // set, serialize it and all of its edges. If this node is _not_ in our
+    // target set, we also serialize under the assumption that it is a shared
+    // resource being used by something in our target zones since we reached it
+    // by traversing the heap graph. However, we do not serialize its outgoing
+    // edges and we abandon further traversal from this node.
+
+    JS::Zone *zone = referent.zone();
+
+    if (zones->has(zone))
+      return writer.writeNode(referent, CoreDumpWriter::INCLUDE_EDGES);
+
+    traversal.abandonReferent();
+    return writer.writeNode(referent, CoreDumpWriter::EXCLUDE_EDGES);
+  }
+};
+
+
+bool
+WriteHeapGraph(JSContext *cx,
+               const JS::ubi::Node &node,
+               CoreDumpWriter &writer,
+               bool wantNames,
+               JS::ZoneSet *zones,
+               JS::AutoCheckCannotGC &noGC)
+{
+  // Serialize the starting node to the core dump.
+
+  if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
+    return false;
+  }
+
+  // Walk the heap graph starting from the given node and serialize it into the
+  // core dump.
+
+  HeapSnapshotHandler handler(writer, zones);
+  HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
+  if (!traversal.init())
+    return false;
+  traversal.wantNames = wantNames;
+
+  return traversal.addStartVisited(node) &&
+    traversal.traverse();
+}
+
+/* static */ void
+ChromeUtils::SaveHeapSnapshot(GlobalObject &global,
+                              JSContext *cx,
+                              const nsAString &filePath,
+                              const HeapSnapshotBoundaries &boundaries,
+                              ErrorResult& rv)
+{
+  bool wantNames = true;
+  ZoneSet zones;
+  Maybe<AutoCheckCannotGC> maybeNoGC;
+  ubi::RootList rootList(cx, maybeNoGC, wantNames);
+  if (!EstablishBoundaries(cx, rv, boundaries, rootList, zones))
+    return;
+
+  MOZ_ASSERT(maybeNoGC.isSome());
+  ubi::Node roots(&rootList);
+
+  nsCOMPtr<nsIFile> file;
+  rv = NS_NewLocalFile(filePath, false, getter_AddRefs(file));
+  if (NS_WARN_IF(rv.Failed()))
+    return;
+
+  nsCOMPtr<nsIOutputStream> outputStream;
+  rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
+                                   PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+                                   -1, 0);
+  if (NS_WARN_IF(rv.Failed()))
+    return;
+
+  ZeroCopyNSIOutputStream zeroCopyStream(outputStream);
+  ::google::protobuf::io::GzipOutputStream gzipStream(&zeroCopyStream);
+
+  StreamWriter writer(cx, gzipStream, wantNames);
+
+  // Serialize the initial heap snapshot metadata to the core dump.
+  if (!writer.writeMetadata(PR_Now()) ||
+      // Serialize the heap graph to the core dump, starting from our list of
+      // roots.
+      !WriteHeapGraph(cx,
+                      roots,
+                      writer,
+                      wantNames,
+                      zones.initialized() ? &zones : nullptr,
+                      maybeNoGC.ref()))
+    {
+      rv.Throw(zeroCopyStream.failed()
+               ? zeroCopyStream.result()
+               : NS_ERROR_UNEXPECTED);
+      return;
+    }
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/ChromeUtils.h
@@ -0,0 +1,72 @@
+/* -*-  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 mozilla_devtools_ChromeUtils__
+#define mozilla_devtools_ChromeUtils__
+
+#include "CoreDump.pb.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+#include "js/UbiNode.h"
+#include "js/UbiNodeTraverse.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+
+namespace mozilla {
+namespace devtools {
+
+// A `CoreDumpWriter` is given the data we wish to save in a core dump and
+// serializes it to disk, or memory, or a socket, etc.
+class CoreDumpWriter
+{
+public:
+  virtual ~CoreDumpWriter() { };
+
+  // Write the given bits of metadata we would like to associate with this core
+  // dump.
+  virtual bool writeMetadata(uint64_t timestamp) = 0;
+
+  enum EdgePolicy : bool {
+    INCLUDE_EDGES = true,
+    EXCLUDE_EDGES = false
+  };
+
+  // Write the given `JS::ubi::Node` to the core dump. The given `EdgePolicy`
+  // dictates whether its outgoing edges should also be written to the core
+  // dump, or excluded.
+  virtual bool writeNode(const JS::ubi::Node &node,
+                         EdgePolicy includeEdges) = 0;
+};
+
+
+// Serialize the heap graph as seen from `node` with the given
+// `CoreDumpWriter`. If `wantNames` is true, capture edge names. If `zones` is
+// non-null, only capture the sub-graph within the zone set, otherwise capture
+// the whole heap graph. Returns false on failure.
+bool
+WriteHeapGraph(JSContext *cx,
+               const JS::ubi::Node &node,
+               CoreDumpWriter &writer,
+               bool wantNames,
+               JS::ZoneSet *zones,
+               JS::AutoCheckCannotGC &noGC);
+
+
+class ChromeUtils
+{
+public:
+  static void SaveHeapSnapshot(dom::GlobalObject &global,
+                               JSContext *cx,
+                               const nsAString &filePath,
+                               const dom::HeapSnapshotBoundaries &boundaries,
+                               ErrorResult &rv);
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_ChromeUtils__
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/CoreDump.pb.cc
@@ -0,0 +1,1005 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: CoreDump.proto
+
+#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION
+#include "CoreDump.pb.h"
+
+#include <algorithm>
+
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/once.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/wire_format_lite_inl.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/generated_message_reflection.h>
+#include <google/protobuf/reflection_ops.h>
+#include <google/protobuf/wire_format.h>
+// @@protoc_insertion_point(includes)
+
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+
+namespace {
+
+const ::google::protobuf::Descriptor* Metadata_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+  Metadata_reflection_ = NULL;
+const ::google::protobuf::Descriptor* Node_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+  Node_reflection_ = NULL;
+const ::google::protobuf::Descriptor* Edge_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+  Edge_reflection_ = NULL;
+
+}  // namespace
+
+
+void protobuf_AssignDesc_CoreDump_2eproto() {
+  protobuf_AddDesc_CoreDump_2eproto();
+  const ::google::protobuf::FileDescriptor* file =
+    ::google::protobuf::DescriptorPool::generated_pool()->FindFileByName(
+      "CoreDump.proto");
+  GOOGLE_CHECK(file != NULL);
+  Metadata_descriptor_ = file->message_type(0);
+  static const int Metadata_offsets_[1] = {
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Metadata, timestamp_),
+  };
+  Metadata_reflection_ =
+    new ::google::protobuf::internal::GeneratedMessageReflection(
+      Metadata_descriptor_,
+      Metadata::default_instance_,
+      Metadata_offsets_,
+      GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Metadata, _has_bits_[0]),
+      GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Metadata, _unknown_fields_),
+      -1,
+      ::google::protobuf::DescriptorPool::generated_pool(),
+      ::google::protobuf::MessageFactory::generated_factory(),
+      sizeof(Metadata));
+  Node_descriptor_ = file->message_type(1);
+  static const int Node_offsets_[4] = {
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, id_),
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, typename__),
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, size_),
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, edges_),
+  };
+  Node_reflection_ =
+    new ::google::protobuf::internal::GeneratedMessageReflection(
+      Node_descriptor_,
+      Node::default_instance_,
+      Node_offsets_,
+      GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, _has_bits_[0]),
+      GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, _unknown_fields_),
+      -1,
+      ::google::protobuf::DescriptorPool::generated_pool(),
+      ::google::protobuf::MessageFactory::generated_factory(),
+      sizeof(Node));
+  Edge_descriptor_ = file->message_type(2);
+  static const int Edge_offsets_[2] = {
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, referent_),
+    GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, name_),
+  };
+  Edge_reflection_ =
+    new ::google::protobuf::internal::GeneratedMessageReflection(
+      Edge_descriptor_,
+      Edge::default_instance_,
+      Edge_offsets_,
+      GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, _has_bits_[0]),
+      GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, _unknown_fields_),
+      -1,
+      ::google::protobuf::DescriptorPool::generated_pool(),
+      ::google::protobuf::MessageFactory::generated_factory(),
+      sizeof(Edge));
+}
+
+namespace {
+
+GOOGLE_PROTOBUF_DECLARE_ONCE(protobuf_AssignDescriptors_once_);
+inline void protobuf_AssignDescriptorsOnce() {
+  ::google::protobuf::GoogleOnceInit(&protobuf_AssignDescriptors_once_,
+                 &protobuf_AssignDesc_CoreDump_2eproto);
+}
+
+void protobuf_RegisterTypes(const ::std::string&) {
+  protobuf_AssignDescriptorsOnce();
+  ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+    Metadata_descriptor_, &Metadata::default_instance());
+  ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+    Node_descriptor_, &Node::default_instance());
+  ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+    Edge_descriptor_, &Edge::default_instance());
+}
+
+}  // namespace
+
+void protobuf_ShutdownFile_CoreDump_2eproto() {
+  delete Metadata::default_instance_;
+  delete Metadata_reflection_;
+  delete Node::default_instance_;
+  delete Node_reflection_;
+  delete Edge::default_instance_;
+  delete Edge_reflection_;
+}
+
+void protobuf_AddDesc_CoreDump_2eproto() {
+  static bool already_here = false;
+  if (already_here) return;
+  already_here = true;
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  ::google::protobuf::DescriptorPool::InternalAddGeneratedFile(
+    "\n\016CoreDump.proto\022\031mozilla.devtools.proto"
+    "buf\"\035\n\010Metadata\022\021\n\ttimeStamp\030\001 \001(\004\"b\n\004No"
+    "de\022\n\n\002id\030\001 \001(\004\022\020\n\010typeName\030\002 \001(\014\022\014\n\004size"
+    "\030\003 \001(\004\022.\n\005edges\030\004 \003(\0132\037.mozilla.devtools"
+    ".protobuf.Edge\"&\n\004Edge\022\020\n\010referent\030\001 \001(\004"
+    "\022\014\n\004name\030\002 \001(\014", 214);
+  ::google::protobuf::MessageFactory::InternalRegisterGeneratedFile(
+    "CoreDump.proto", &protobuf_RegisterTypes);
+  Metadata::default_instance_ = new Metadata();
+  Node::default_instance_ = new Node();
+  Edge::default_instance_ = new Edge();
+  Metadata::default_instance_->InitAsDefaultInstance();
+  Node::default_instance_->InitAsDefaultInstance();
+  Edge::default_instance_->InitAsDefaultInstance();
+  ::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_CoreDump_2eproto);
+}
+
+// Force AddDescriptors() to be called at static initialization time.
+struct StaticDescriptorInitializer_CoreDump_2eproto {
+  StaticDescriptorInitializer_CoreDump_2eproto() {
+    protobuf_AddDesc_CoreDump_2eproto();
+  }
+} static_descriptor_initializer_CoreDump_2eproto_;
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int Metadata::kTimeStampFieldNumber;
+#endif  // !_MSC_VER
+
+Metadata::Metadata()
+  : ::google::protobuf::Message() {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.Metadata)
+}
+
+void Metadata::InitAsDefaultInstance() {
+}
+
+Metadata::Metadata(const Metadata& from)
+  : ::google::protobuf::Message() {
+  SharedCtor();
+  MergeFrom(from);
+  // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Metadata)
+}
+
+void Metadata::SharedCtor() {
+  _cached_size_ = 0;
+  timestamp_ = GOOGLE_ULONGLONG(0);
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+Metadata::~Metadata() {
+  // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Metadata)
+  SharedDtor();
+}
+
+void Metadata::SharedDtor() {
+  if (this != default_instance_) {
+  }
+}
+
+void Metadata::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* Metadata::descriptor() {
+  protobuf_AssignDescriptorsOnce();
+  return Metadata_descriptor_;
+}
+
+const Metadata& Metadata::default_instance() {
+  if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+  return *default_instance_;
+}
+
+Metadata* Metadata::default_instance_ = NULL;
+
+Metadata* Metadata::New() const {
+  return new Metadata;
+}
+
+void Metadata::Clear() {
+  timestamp_ = GOOGLE_ULONGLONG(0);
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+  mutable_unknown_fields()->Clear();
+}
+
+bool Metadata::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+  ::google::protobuf::uint32 tag;
+  // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.Metadata)
+  for (;;) {
+    ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+    tag = p.first;
+    if (!p.second) goto handle_unusual;
+    switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // optional uint64 timeStamp = 1;
+      case 1: {
+        if (tag == 8) {
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+                 input, &timestamp_)));
+          set_has_timestamp();
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectAtEnd()) goto success;
+        break;
+      }
+
+      default: {
+      handle_unusual:
+        if (tag == 0 ||
+            ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+          goto success;
+        }
+        DO_(::google::protobuf::internal::WireFormat::SkipField(
+              input, tag, mutable_unknown_fields()));
+        break;
+      }
+    }
+  }
+success:
+  // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.Metadata)
+  return true;
+failure:
+  // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.Metadata)
+  return false;
+#undef DO_
+}
+
+void Metadata::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.Metadata)
+  // optional uint64 timeStamp = 1;
+  if (has_timestamp()) {
+    ::google::protobuf::internal::WireFormatLite::WriteUInt64(1, this->timestamp(), output);
+  }
+
+  if (!unknown_fields().empty()) {
+    ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+        unknown_fields(), output);
+  }
+  // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Metadata)
+}
+
+::google::protobuf::uint8* Metadata::SerializeWithCachedSizesToArray(
+    ::google::protobuf::uint8* target) const {
+  // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Metadata)
+  // optional uint64 timeStamp = 1;
+  if (has_timestamp()) {
+    target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(1, this->timestamp(), target);
+  }
+
+  if (!unknown_fields().empty()) {
+    target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+        unknown_fields(), target);
+  }
+  // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Metadata)
+  return target;
+}
+
+int Metadata::ByteSize() const {
+  int total_size = 0;
+
+  if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    // optional uint64 timeStamp = 1;
+    if (has_timestamp()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::UInt64Size(
+          this->timestamp());
+    }
+
+  }
+  if (!unknown_fields().empty()) {
+    total_size +=
+      ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+        unknown_fields());
+  }
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void Metadata::MergeFrom(const ::google::protobuf::Message& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  const Metadata* source =
+    ::google::protobuf::internal::dynamic_cast_if_available<const Metadata*>(
+      &from);
+  if (source == NULL) {
+    ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+  } else {
+    MergeFrom(*source);
+  }
+}
+
+void Metadata::MergeFrom(const Metadata& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (from.has_timestamp()) {
+      set_timestamp(from.timestamp());
+    }
+  }
+  mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void Metadata::CopyFrom(const ::google::protobuf::Message& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+void Metadata::CopyFrom(const Metadata& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool Metadata::IsInitialized() const {
+
+  return true;
+}
+
+void Metadata::Swap(Metadata* other) {
+  if (other != this) {
+    std::swap(timestamp_, other->timestamp_);
+    std::swap(_has_bits_[0], other->_has_bits_[0]);
+    _unknown_fields_.Swap(&other->_unknown_fields_);
+    std::swap(_cached_size_, other->_cached_size_);
+  }
+}
+
+::google::protobuf::Metadata Metadata::GetMetadata() const {
+  protobuf_AssignDescriptorsOnce();
+  ::google::protobuf::Metadata metadata;
+  metadata.descriptor = Metadata_descriptor_;
+  metadata.reflection = Metadata_reflection_;
+  return metadata;
+}
+
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int Node::kIdFieldNumber;
+const int Node::kTypeNameFieldNumber;
+const int Node::kSizeFieldNumber;
+const int Node::kEdgesFieldNumber;
+#endif  // !_MSC_VER
+
+Node::Node()
+  : ::google::protobuf::Message() {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.Node)
+}
+
+void Node::InitAsDefaultInstance() {
+}
+
+Node::Node(const Node& from)
+  : ::google::protobuf::Message() {
+  SharedCtor();
+  MergeFrom(from);
+  // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Node)
+}
+
+void Node::SharedCtor() {
+  ::google::protobuf::internal::GetEmptyString();
+  _cached_size_ = 0;
+  id_ = GOOGLE_ULONGLONG(0);
+  typename__ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  size_ = GOOGLE_ULONGLONG(0);
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+Node::~Node() {
+  // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Node)
+  SharedDtor();
+}
+
+void Node::SharedDtor() {
+  if (typename__ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    delete typename__;
+  }
+  if (this != default_instance_) {
+  }
+}
+
+void Node::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* Node::descriptor() {
+  protobuf_AssignDescriptorsOnce();
+  return Node_descriptor_;
+}
+
+const Node& Node::default_instance() {
+  if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+  return *default_instance_;
+}
+
+Node* Node::default_instance_ = NULL;
+
+Node* Node::New() const {
+  return new Node;
+}
+
+void Node::Clear() {
+  if (_has_bits_[0 / 32] & 7) {
+    id_ = GOOGLE_ULONGLONG(0);
+    if (has_typename_()) {
+      if (typename__ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+        typename__->clear();
+      }
+    }
+    size_ = GOOGLE_ULONGLONG(0);
+  }
+  edges_.Clear();
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+  mutable_unknown_fields()->Clear();
+}
+
+bool Node::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+  ::google::protobuf::uint32 tag;
+  // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.Node)
+  for (;;) {
+    ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+    tag = p.first;
+    if (!p.second) goto handle_unusual;
+    switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // optional uint64 id = 1;
+      case 1: {
+        if (tag == 8) {
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+                 input, &id_)));
+          set_has_id();
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(18)) goto parse_typeName;
+        break;
+      }
+
+      // optional bytes typeName = 2;
+      case 2: {
+        if (tag == 18) {
+         parse_typeName:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+                input, this->mutable_typename_()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(24)) goto parse_size;
+        break;
+      }
+
+      // optional uint64 size = 3;
+      case 3: {
+        if (tag == 24) {
+         parse_size:
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+                 input, &size_)));
+          set_has_size();
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(34)) goto parse_edges;
+        break;
+      }
+
+      // repeated .mozilla.devtools.protobuf.Edge edges = 4;
+      case 4: {
+        if (tag == 34) {
+         parse_edges:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(
+                input, add_edges()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(34)) goto parse_edges;
+        if (input->ExpectAtEnd()) goto success;
+        break;
+      }
+
+      default: {
+      handle_unusual:
+        if (tag == 0 ||
+            ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+          goto success;
+        }
+        DO_(::google::protobuf::internal::WireFormat::SkipField(
+              input, tag, mutable_unknown_fields()));
+        break;
+      }
+    }
+  }
+success:
+  // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.Node)
+  return true;
+failure:
+  // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.Node)
+  return false;
+#undef DO_
+}
+
+void Node::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.Node)
+  // optional uint64 id = 1;
+  if (has_id()) {
+    ::google::protobuf::internal::WireFormatLite::WriteUInt64(1, this->id(), output);
+  }
+
+  // optional bytes typeName = 2;
+  if (has_typename_()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+      2, this->typename_(), output);
+  }
+
+  // optional uint64 size = 3;
+  if (has_size()) {
+    ::google::protobuf::internal::WireFormatLite::WriteUInt64(3, this->size(), output);
+  }
+
+  // repeated .mozilla.devtools.protobuf.Edge edges = 4;
+  for (int i = 0; i < this->edges_size(); i++) {
+    ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
+      4, this->edges(i), output);
+  }
+
+  if (!unknown_fields().empty()) {
+    ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+        unknown_fields(), output);
+  }
+  // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Node)
+}
+
+::google::protobuf::uint8* Node::SerializeWithCachedSizesToArray(
+    ::google::protobuf::uint8* target) const {
+  // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Node)
+  // optional uint64 id = 1;
+  if (has_id()) {
+    target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(1, this->id(), target);
+  }
+
+  // optional bytes typeName = 2;
+  if (has_typename_()) {
+    target =
+      ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+        2, this->typename_(), target);
+  }
+
+  // optional uint64 size = 3;
+  if (has_size()) {
+    target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(3, this->size(), target);
+  }
+
+  // repeated .mozilla.devtools.protobuf.Edge edges = 4;
+  for (int i = 0; i < this->edges_size(); i++) {
+    target = ::google::protobuf::internal::WireFormatLite::
+      WriteMessageNoVirtualToArray(
+        4, this->edges(i), target);
+  }
+
+  if (!unknown_fields().empty()) {
+    target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+        unknown_fields(), target);
+  }
+  // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Node)
+  return target;
+}
+
+int Node::ByteSize() const {
+  int total_size = 0;
+
+  if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    // optional uint64 id = 1;
+    if (has_id()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::UInt64Size(
+          this->id());
+    }
+
+    // optional bytes typeName = 2;
+    if (has_typename_()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::BytesSize(
+          this->typename_());
+    }
+
+    // optional uint64 size = 3;
+    if (has_size()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::UInt64Size(
+          this->size());
+    }
+
+  }
+  // repeated .mozilla.devtools.protobuf.Edge edges = 4;
+  total_size += 1 * this->edges_size();
+  for (int i = 0; i < this->edges_size(); i++) {
+    total_size +=
+      ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+        this->edges(i));
+  }
+
+  if (!unknown_fields().empty()) {
+    total_size +=
+      ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+        unknown_fields());
+  }
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void Node::MergeFrom(const ::google::protobuf::Message& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  const Node* source =
+    ::google::protobuf::internal::dynamic_cast_if_available<const Node*>(
+      &from);
+  if (source == NULL) {
+    ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+  } else {
+    MergeFrom(*source);
+  }
+}
+
+void Node::MergeFrom(const Node& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  edges_.MergeFrom(from.edges_);
+  if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (from.has_id()) {
+      set_id(from.id());
+    }
+    if (from.has_typename_()) {
+      set_typename_(from.typename_());
+    }
+    if (from.has_size()) {
+      set_size(from.size());
+    }
+  }
+  mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void Node::CopyFrom(const ::google::protobuf::Message& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+void Node::CopyFrom(const Node& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool Node::IsInitialized() const {
+
+  return true;
+}
+
+void Node::Swap(Node* other) {
+  if (other != this) {
+    std::swap(id_, other->id_);
+    std::swap(typename__, other->typename__);
+    std::swap(size_, other->size_);
+    edges_.Swap(&other->edges_);
+    std::swap(_has_bits_[0], other->_has_bits_[0]);
+    _unknown_fields_.Swap(&other->_unknown_fields_);
+    std::swap(_cached_size_, other->_cached_size_);
+  }
+}
+
+::google::protobuf::Metadata Node::GetMetadata() const {
+  protobuf_AssignDescriptorsOnce();
+  ::google::protobuf::Metadata metadata;
+  metadata.descriptor = Node_descriptor_;
+  metadata.reflection = Node_reflection_;
+  return metadata;
+}
+
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int Edge::kReferentFieldNumber;
+const int Edge::kNameFieldNumber;
+#endif  // !_MSC_VER
+
+Edge::Edge()
+  : ::google::protobuf::Message() {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.Edge)
+}
+
+void Edge::InitAsDefaultInstance() {
+}
+
+Edge::Edge(const Edge& from)
+  : ::google::protobuf::Message() {
+  SharedCtor();
+  MergeFrom(from);
+  // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Edge)
+}
+
+void Edge::SharedCtor() {
+  ::google::protobuf::internal::GetEmptyString();
+  _cached_size_ = 0;
+  referent_ = GOOGLE_ULONGLONG(0);
+  name_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+Edge::~Edge() {
+  // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Edge)
+  SharedDtor();
+}
+
+void Edge::SharedDtor() {
+  if (name_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    delete name_;
+  }
+  if (this != default_instance_) {
+  }
+}
+
+void Edge::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* Edge::descriptor() {
+  protobuf_AssignDescriptorsOnce();
+  return Edge_descriptor_;
+}
+
+const Edge& Edge::default_instance() {
+  if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+  return *default_instance_;
+}
+
+Edge* Edge::default_instance_ = NULL;
+
+Edge* Edge::New() const {
+  return new Edge;
+}
+
+void Edge::Clear() {
+  if (_has_bits_[0 / 32] & 3) {
+    referent_ = GOOGLE_ULONGLONG(0);
+    if (has_name()) {
+      if (name_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+        name_->clear();
+      }
+    }
+  }
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+  mutable_unknown_fields()->Clear();
+}
+
+bool Edge::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+  ::google::protobuf::uint32 tag;
+  // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.Edge)
+  for (;;) {
+    ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+    tag = p.first;
+    if (!p.second) goto handle_unusual;
+    switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // optional uint64 referent = 1;
+      case 1: {
+        if (tag == 8) {
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+                 input, &referent_)));
+          set_has_referent();
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(18)) goto parse_name;
+        break;
+      }
+
+      // optional bytes name = 2;
+      case 2: {
+        if (tag == 18) {
+         parse_name:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+                input, this->mutable_name()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectAtEnd()) goto success;
+        break;
+      }
+
+      default: {
+      handle_unusual:
+        if (tag == 0 ||
+            ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+          goto success;
+        }
+        DO_(::google::protobuf::internal::WireFormat::SkipField(
+              input, tag, mutable_unknown_fields()));
+        break;
+      }
+    }
+  }
+success:
+  // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.Edge)
+  return true;
+failure:
+  // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.Edge)
+  return false;
+#undef DO_
+}
+
+void Edge::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.Edge)
+  // optional uint64 referent = 1;
+  if (has_referent()) {
+    ::google::protobuf::internal::WireFormatLite::WriteUInt64(1, this->referent(), output);
+  }
+
+  // optional bytes name = 2;
+  if (has_name()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+      2, this->name(), output);
+  }
+
+  if (!unknown_fields().empty()) {
+    ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+        unknown_fields(), output);
+  }
+  // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Edge)
+}
+
+::google::protobuf::uint8* Edge::SerializeWithCachedSizesToArray(
+    ::google::protobuf::uint8* target) const {
+  // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Edge)
+  // optional uint64 referent = 1;
+  if (has_referent()) {
+    target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(1, this->referent(), target);
+  }
+
+  // optional bytes name = 2;
+  if (has_name()) {
+    target =
+      ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+        2, this->name(), target);
+  }
+
+  if (!unknown_fields().empty()) {
+    target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+        unknown_fields(), target);
+  }
+  // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Edge)
+  return target;
+}
+
+int Edge::ByteSize() const {
+  int total_size = 0;
+
+  if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    // optional uint64 referent = 1;
+    if (has_referent()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::UInt64Size(
+          this->referent());
+    }
+
+    // optional bytes name = 2;
+    if (has_name()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::BytesSize(
+          this->name());
+    }
+
+  }
+  if (!unknown_fields().empty()) {
+    total_size +=
+      ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+        unknown_fields());
+  }
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void Edge::MergeFrom(const ::google::protobuf::Message& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  const Edge* source =
+    ::google::protobuf::internal::dynamic_cast_if_available<const Edge*>(
+      &from);
+  if (source == NULL) {
+    ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+  } else {
+    MergeFrom(*source);
+  }
+}
+
+void Edge::MergeFrom(const Edge& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (from.has_referent()) {
+      set_referent(from.referent());
+    }
+    if (from.has_name()) {
+      set_name(from.name());
+    }
+  }
+  mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void Edge::CopyFrom(const ::google::protobuf::Message& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+void Edge::CopyFrom(const Edge& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool Edge::IsInitialized() const {
+
+  return true;
+}
+
+void Edge::Swap(Edge* other) {
+  if (other != this) {
+    std::swap(referent_, other->referent_);
+    std::swap(name_, other->name_);
+    std::swap(_has_bits_[0], other->_has_bits_[0]);
+    _unknown_fields_.Swap(&other->_unknown_fields_);
+    std::swap(_cached_size_, other->_cached_size_);
+  }
+}
+
+::google::protobuf::Metadata Edge::GetMetadata() const {
+  protobuf_AssignDescriptorsOnce();
+  ::google::protobuf::Metadata metadata;
+  metadata.descriptor = Edge_descriptor_;
+  metadata.reflection = Edge_reflection_;
+  return metadata;
+}
+
+
+// @@protoc_insertion_point(namespace_scope)
+
+}  // namespace protobuf
+}  // namespace devtools
+}  // namespace mozilla
+
+// @@protoc_insertion_point(global_scope)
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/CoreDump.pb.h
@@ -0,0 +1,643 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: CoreDump.proto
+
+#ifndef PROTOBUF_CoreDump_2eproto__INCLUDED
+#define PROTOBUF_CoreDump_2eproto__INCLUDED
+
+#include <string>
+
+#include <google/protobuf/stubs/common.h>
+
+#if GOOGLE_PROTOBUF_VERSION < 2006000
+#error This file was generated by a newer version of protoc which is
+#error incompatible with your Protocol Buffer headers.  Please update
+#error your headers.
+#endif
+#if 2006001 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
+#error This file was generated by an older version of protoc which is
+#error incompatible with your Protocol Buffer headers.  Please
+#error regenerate this file with a newer version of protoc.
+#endif
+
+#include <google/protobuf/generated_message_util.h>
+#include <google/protobuf/message.h>
+#include <google/protobuf/repeated_field.h>
+#include <google/protobuf/extension_set.h>
+#include <google/protobuf/unknown_field_set.h>
+// @@protoc_insertion_point(includes)
+
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+
+// Internal implementation detail -- do not call these.
+void  protobuf_AddDesc_CoreDump_2eproto();
+void protobuf_AssignDesc_CoreDump_2eproto();
+void protobuf_ShutdownFile_CoreDump_2eproto();
+
+class Metadata;
+class Node;
+class Edge;
+
+// ===================================================================
+
+class Metadata : public ::google::protobuf::Message {
+ public:
+  Metadata();
+  virtual ~Metadata();
+
+  Metadata(const Metadata& from);
+
+  inline Metadata& operator=(const Metadata& from) {
+    CopyFrom(from);
+    return *this;
+  }
+
+  inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+    return _unknown_fields_;
+  }
+
+  inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+    return &_unknown_fields_;
+  }
+
+  static const ::google::protobuf::Descriptor* descriptor();
+  static const Metadata& default_instance();
+
+  void Swap(Metadata* other);
+
+  // implements Message ----------------------------------------------
+
+  Metadata* New() const;
+  void CopyFrom(const ::google::protobuf::Message& from);
+  void MergeFrom(const ::google::protobuf::Message& from);
+  void CopyFrom(const Metadata& from);
+  void MergeFrom(const Metadata& from);
+  void Clear();
+  bool IsInitialized() const;
+
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+  int GetCachedSize() const { return _cached_size_; }
+  private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  public:
+  ::google::protobuf::Metadata GetMetadata() const;
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  // optional uint64 timeStamp = 1;
+  inline bool has_timestamp() const;
+  inline void clear_timestamp();
+  static const int kTimeStampFieldNumber = 1;
+  inline ::google::protobuf::uint64 timestamp() const;
+  inline void set_timestamp(::google::protobuf::uint64 value);
+
+  // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Metadata)
+ private:
+  inline void set_has_timestamp();
+  inline void clear_has_timestamp();
+
+  ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+  ::google::protobuf::uint32 _has_bits_[1];
+  mutable int _cached_size_;
+  ::google::protobuf::uint64 timestamp_;
+  friend void  protobuf_AddDesc_CoreDump_2eproto();
+  friend void protobuf_AssignDesc_CoreDump_2eproto();
+  friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+  void InitAsDefaultInstance();
+  static Metadata* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class Node : public ::google::protobuf::Message {
+ public:
+  Node();
+  virtual ~Node();
+
+  Node(const Node& from);
+
+  inline Node& operator=(const Node& from) {
+    CopyFrom(from);
+    return *this;
+  }
+
+  inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+    return _unknown_fields_;
+  }
+
+  inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+    return &_unknown_fields_;
+  }
+
+  static const ::google::protobuf::Descriptor* descriptor();
+  static const Node& default_instance();
+
+  void Swap(Node* other);
+
+  // implements Message ----------------------------------------------
+
+  Node* New() const;
+  void CopyFrom(const ::google::protobuf::Message& from);
+  void MergeFrom(const ::google::protobuf::Message& from);
+  void CopyFrom(const Node& from);
+  void MergeFrom(const Node& from);
+  void Clear();
+  bool IsInitialized() const;
+
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+  int GetCachedSize() const { return _cached_size_; }
+  private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  public:
+  ::google::protobuf::Metadata GetMetadata() const;
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  // optional uint64 id = 1;
+  inline bool has_id() const;
+  inline void clear_id();
+  static const int kIdFieldNumber = 1;
+  inline ::google::protobuf::uint64 id() const;
+  inline void set_id(::google::protobuf::uint64 value);
+
+  // optional bytes typeName = 2;
+  inline bool has_typename_() const;
+  inline void clear_typename_();
+  static const int kTypeNameFieldNumber = 2;
+  inline const ::std::string& typename_() const;
+  inline void set_typename_(const ::std::string& value);
+  inline void set_typename_(const char* value);
+  inline void set_typename_(const void* value, size_t size);
+  inline ::std::string* mutable_typename_();
+  inline ::std::string* release_typename_();
+  inline void set_allocated_typename_(::std::string* typename_);
+
+  // optional uint64 size = 3;
+  inline bool has_size() const;
+  inline void clear_size();
+  static const int kSizeFieldNumber = 3;
+  inline ::google::protobuf::uint64 size() const;
+  inline void set_size(::google::protobuf::uint64 value);
+
+  // repeated .mozilla.devtools.protobuf.Edge edges = 4;
+  inline int edges_size() const;
+  inline void clear_edges();
+  static const int kEdgesFieldNumber = 4;
+  inline const ::mozilla::devtools::protobuf::Edge& edges(int index) const;
+  inline ::mozilla::devtools::protobuf::Edge* mutable_edges(int index);
+  inline ::mozilla::devtools::protobuf::Edge* add_edges();
+  inline const ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >&
+      edges() const;
+  inline ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >*
+      mutable_edges();
+
+  // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Node)
+ private:
+  inline void set_has_id();
+  inline void clear_has_id();
+  inline void set_has_typename_();
+  inline void clear_has_typename_();
+  inline void set_has_size();
+  inline void clear_has_size();
+
+  ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+  ::google::protobuf::uint32 _has_bits_[1];
+  mutable int _cached_size_;
+  ::google::protobuf::uint64 id_;
+  ::std::string* typename__;
+  ::google::protobuf::uint64 size_;
+  ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge > edges_;
+  friend void  protobuf_AddDesc_CoreDump_2eproto();
+  friend void protobuf_AssignDesc_CoreDump_2eproto();
+  friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+  void InitAsDefaultInstance();
+  static Node* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class Edge : public ::google::protobuf::Message {
+ public:
+  Edge();
+  virtual ~Edge();
+
+  Edge(const Edge& from);
+
+  inline Edge& operator=(const Edge& from) {
+    CopyFrom(from);
+    return *this;
+  }
+
+  inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+    return _unknown_fields_;
+  }
+
+  inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+    return &_unknown_fields_;
+  }
+
+  static const ::google::protobuf::Descriptor* descriptor();
+  static const Edge& default_instance();
+
+  void Swap(Edge* other);
+
+  // implements Message ----------------------------------------------
+
+  Edge* New() const;
+  void CopyFrom(const ::google::protobuf::Message& from);
+  void MergeFrom(const ::google::protobuf::Message& from);
+  void CopyFrom(const Edge& from);
+  void MergeFrom(const Edge& from);
+  void Clear();
+  bool IsInitialized() const;
+
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+  int GetCachedSize() const { return _cached_size_; }
+  private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  public:
+  ::google::protobuf::Metadata GetMetadata() const;
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  // optional uint64 referent = 1;
+  inline bool has_referent() const;
+  inline void clear_referent();
+  static const int kReferentFieldNumber = 1;
+  inline ::google::protobuf::uint64 referent() const;
+  inline void set_referent(::google::protobuf::uint64 value);
+
+  // optional bytes name = 2;
+  inline bool has_name() const;
+  inline void clear_name();
+  static const int kNameFieldNumber = 2;
+  inline const ::std::string& name() const;
+  inline void set_name(const ::std::string& value);
+  inline void set_name(const char* value);
+  inline void set_name(const void* value, size_t size);
+  inline ::std::string* mutable_name();
+  inline ::std::string* release_name();
+  inline void set_allocated_name(::std::string* name);
+
+  // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Edge)
+ private:
+  inline void set_has_referent();
+  inline void clear_has_referent();
+  inline void set_has_name();
+  inline void clear_has_name();
+
+  ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+  ::google::protobuf::uint32 _has_bits_[1];
+  mutable int _cached_size_;
+  ::google::protobuf::uint64 referent_;
+  ::std::string* name_;
+  friend void  protobuf_AddDesc_CoreDump_2eproto();
+  friend void protobuf_AssignDesc_CoreDump_2eproto();
+  friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+  void InitAsDefaultInstance();
+  static Edge* default_instance_;
+};
+// ===================================================================
+
+
+// ===================================================================
+
+// Metadata
+
+// optional uint64 timeStamp = 1;
+inline bool Metadata::has_timestamp() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void Metadata::set_has_timestamp() {
+  _has_bits_[0] |= 0x00000001u;
+}
+inline void Metadata::clear_has_timestamp() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+inline void Metadata::clear_timestamp() {
+  timestamp_ = GOOGLE_ULONGLONG(0);
+  clear_has_timestamp();
+}
+inline ::google::protobuf::uint64 Metadata::timestamp() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Metadata.timeStamp)
+  return timestamp_;
+}
+inline void Metadata::set_timestamp(::google::protobuf::uint64 value) {
+  set_has_timestamp();
+  timestamp_ = value;
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Metadata.timeStamp)
+}
+
+// -------------------------------------------------------------------
+
+// Node
+
+// optional uint64 id = 1;
+inline bool Node::has_id() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void Node::set_has_id() {
+  _has_bits_[0] |= 0x00000001u;
+}
+inline void Node::clear_has_id() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+inline void Node::clear_id() {
+  id_ = GOOGLE_ULONGLONG(0);
+  clear_has_id();
+}
+inline ::google::protobuf::uint64 Node::id() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.id)
+  return id_;
+}
+inline void Node::set_id(::google::protobuf::uint64 value) {
+  set_has_id();
+  id_ = value;
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.id)
+}
+
+// optional bytes typeName = 2;
+inline bool Node::has_typename_() const {
+  return (_has_bits_[0] & 0x00000002u) != 0;
+}
+inline void Node::set_has_typename_() {
+  _has_bits_[0] |= 0x00000002u;
+}
+inline void Node::clear_has_typename_() {
+  _has_bits_[0] &= ~0x00000002u;
+}
+inline void Node::clear_typename_() {
+  if (typename__ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    typename__->clear();
+  }
+  clear_has_typename_();
+}
+inline const ::std::string& Node::typename_() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.typeName)
+  return *typename__;
+}
+inline void Node::set_typename_(const ::std::string& value) {
+  set_has_typename_();
+  if (typename__ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    typename__ = new ::std::string;
+  }
+  typename__->assign(value);
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.typeName)
+}
+inline void Node::set_typename_(const char* value) {
+  set_has_typename_();
+  if (typename__ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    typename__ = new ::std::string;
+  }
+  typename__->assign(value);
+  // @@protoc_insertion_point(field_set_char:mozilla.devtools.protobuf.Node.typeName)
+}
+inline void Node::set_typename_(const void* value, size_t size) {
+  set_has_typename_();
+  if (typename__ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    typename__ = new ::std::string;
+  }
+  typename__->assign(reinterpret_cast<const char*>(value), size);
+  // @@protoc_insertion_point(field_set_pointer:mozilla.devtools.protobuf.Node.typeName)
+}
+inline ::std::string* Node::mutable_typename_() {
+  set_has_typename_();
+  if (typename__ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    typename__ = new ::std::string;
+  }
+  // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.typeName)
+  return typename__;
+}
+inline ::std::string* Node::release_typename_() {
+  clear_has_typename_();
+  if (typename__ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    return NULL;
+  } else {
+    ::std::string* temp = typename__;
+    typename__ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+    return temp;
+  }
+}
+inline void Node::set_allocated_typename_(::std::string* typename_) {
+  if (typename__ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    delete typename__;
+  }
+  if (typename_) {
+    set_has_typename_();
+    typename__ = typename_;
+  } else {
+    clear_has_typename_();
+    typename__ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.typeName)
+}
+
+// optional uint64 size = 3;
+inline bool Node::has_size() const {
+  return (_has_bits_[0] & 0x00000004u) != 0;
+}
+inline void Node::set_has_size() {
+  _has_bits_[0] |= 0x00000004u;
+}
+inline void Node::clear_has_size() {
+  _has_bits_[0] &= ~0x00000004u;
+}
+inline void Node::clear_size() {
+  size_ = GOOGLE_ULONGLONG(0);
+  clear_has_size();
+}
+inline ::google::protobuf::uint64 Node::size() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.size)
+  return size_;
+}
+inline void Node::set_size(::google::protobuf::uint64 value) {
+  set_has_size();
+  size_ = value;
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.size)
+}
+
+// repeated .mozilla.devtools.protobuf.Edge edges = 4;
+inline int Node::edges_size() const {
+  return edges_.size();
+}
+inline void Node::clear_edges() {
+  edges_.Clear();
+}
+inline const ::mozilla::devtools::protobuf::Edge& Node::edges(int index) const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.edges)
+  return edges_.Get(index);
+}
+inline ::mozilla::devtools::protobuf::Edge* Node::mutable_edges(int index) {
+  // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.edges)
+  return edges_.Mutable(index);
+}
+inline ::mozilla::devtools::protobuf::Edge* Node::add_edges() {
+  // @@protoc_insertion_point(field_add:mozilla.devtools.protobuf.Node.edges)
+  return edges_.Add();
+}
+inline const ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >&
+Node::edges() const {
+  // @@protoc_insertion_point(field_list:mozilla.devtools.protobuf.Node.edges)
+  return edges_;
+}
+inline ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >*
+Node::mutable_edges() {
+  // @@protoc_insertion_point(field_mutable_list:mozilla.devtools.protobuf.Node.edges)
+  return &edges_;
+}
+
+// -------------------------------------------------------------------
+
+// Edge
+
+// optional uint64 referent = 1;
+inline bool Edge::has_referent() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void Edge::set_has_referent() {
+  _has_bits_[0] |= 0x00000001u;
+}
+inline void Edge::clear_has_referent() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+inline void Edge::clear_referent() {
+  referent_ = GOOGLE_ULONGLONG(0);
+  clear_has_referent();
+}
+inline ::google::protobuf::uint64 Edge::referent() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Edge.referent)
+  return referent_;
+}
+inline void Edge::set_referent(::google::protobuf::uint64 value) {
+  set_has_referent();
+  referent_ = value;
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Edge.referent)
+}
+
+// optional bytes name = 2;
+inline bool Edge::has_name() const {
+  return (_has_bits_[0] & 0x00000002u) != 0;
+}
+inline void Edge::set_has_name() {
+  _has_bits_[0] |= 0x00000002u;
+}
+inline void Edge::clear_has_name() {
+  _has_bits_[0] &= ~0x00000002u;
+}
+inline void Edge::clear_name() {
+  if (name_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    name_->clear();
+  }
+  clear_has_name();
+}
+inline const ::std::string& Edge::name() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Edge.name)
+  return *name_;
+}
+inline void Edge::set_name(const ::std::string& value) {
+  set_has_name();
+  if (name_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    name_ = new ::std::string;
+  }
+  name_->assign(value);
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Edge.name)
+}
+inline void Edge::set_name(const char* value) {
+  set_has_name();
+  if (name_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    name_ = new ::std::string;
+  }
+  name_->assign(value);
+  // @@protoc_insertion_point(field_set_char:mozilla.devtools.protobuf.Edge.name)
+}
+inline void Edge::set_name(const void* value, size_t size) {
+  set_has_name();
+  if (name_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    name_ = new ::std::string;
+  }
+  name_->assign(reinterpret_cast<const char*>(value), size);
+  // @@protoc_insertion_point(field_set_pointer:mozilla.devtools.protobuf.Edge.name)
+}
+inline ::std::string* Edge::mutable_name() {
+  set_has_name();
+  if (name_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    name_ = new ::std::string;
+  }
+  // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Edge.name)
+  return name_;
+}
+inline ::std::string* Edge::release_name() {
+  clear_has_name();
+  if (name_ == &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    return NULL;
+  } else {
+    ::std::string* temp = name_;
+    name_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+    return temp;
+  }
+}
+inline void Edge::set_allocated_name(::std::string* name) {
+  if (name_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+    delete name_;
+  }
+  if (name) {
+    set_has_name();
+    name_ = name;
+  } else {
+    clear_has_name();
+    name_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Edge.name)
+}
+
+
+// @@protoc_insertion_point(namespace_scope)
+
+}  // namespace protobuf
+}  // namespace devtools
+}  // namespace mozilla
+
+#ifndef SWIG
+namespace google {
+namespace protobuf {
+
+
+}  // namespace google
+}  // namespace protobuf
+#endif  // SWIG
+
+// @@protoc_insertion_point(global_scope)
+
+#endif  // PROTOBUF_CoreDump_2eproto__INCLUDED
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/CoreDump.proto
@@ -0,0 +1,69 @@
+/* -*- Mode: protobuf; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+// # Core Dumps
+//
+// A core dump is a serialized snapshot of the heap graph. We serialize the heap
+// as a series of protobuf messages with each message prefixed by its Varint32
+// byte size so we can delimit individual protobuf messages (protobuf parsers
+// cannot determine where a message ends on their own).
+//
+// The first protobuf message is an instance of the `Metadata` message. All
+// subsequent messages will be instances of the `Node` message. The first of
+// these `Node` messages is the root node of the serialized heap graph. Here is
+// a diagram of our core dump format:
+//
+//     +-----------------------------------------------------------------------+
+//     | Varint32: The size of following `Metadata` message.                   |
+//     +-----------------------------------------------------------------------+
+//     | message: The core dump `Metadata` message.                            |
+//     +-----------------------------------------------------------------------+
+//     | Varint32: The size of the following `Node` message.                   |
+//     +-----------------------------------------------------------------------+
+//     | message: The first `Node` message. This is the root node.             |
+//     +-----------------------------------------------------------------------+
+//     | Varint32: The size of the following `Node` message.                   |
+//     +-----------------------------------------------------------------------+
+//     | message: A `Node` message.                                            |
+//     +-----------------------------------------------------------------------+
+//     | Varint32: The size of the following `Node` message.                   |
+//     +-----------------------------------------------------------------------+
+//     | message: A `Node` message.                                            |
+//     +-----------------------------------------------------------------------+
+//     | .                                                                     |
+//     | .                                                                     |
+//     | .                                                                     |
+//     +-----------------------------------------------------------------------+
+//
+// In practice, certain message fields have a lot of duplication (such as type
+// or edge name strings). Rather than try and de-duplicate this information at
+// the protobuf message and field level, core dumps should be written with
+// `google::protobuf::io::GzipOutputStream` and read from
+// `google::protobuf::io::GzipInputStream`.
+
+package mozilla.devtools.protobuf;
+
+// A collection of metadata about this core dump.
+message Metadata {
+    // Number of microseconds since midnight (00:00:00) 1 January 1970 UTC.
+    optional uint64 timeStamp = 1;
+}
+
+// A serialized version of `JS::ubi::Node` and its outgoing edges.
+message Node {
+    optional uint64 id       = 1;
+    // char16_t[]
+    optional bytes  typeName = 2;
+    optional uint64 size     = 3;
+    repeated Edge   edges    = 4;
+}
+
+// A serialized edge from the heap graph.
+message Edge {
+    optional uint64 referent = 1;
+    // char16_t[]
+    optional bytes  name     = 2;
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/toolkit/devtools/server/generate-core-dump-sources.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+# A script to generate toolkit/devtools/server/CoreDump.pb.{h,cc} from
+# toolkit/devtools/server/CoreDump.proto. This script assumes you have
+# downloaded and installed the protocol buffer compiler, and that it is either
+# on your $PATH or located at $PROTOC_PATH.
+#
+# These files were last compiled with libprotoc 2.4.1.
+
+set -e
+
+cd $(dirname $0)
+
+if [ -n $PROTOC_PATH ]; then
+    PROTOC_PATH=`which protoc`
+fi
+
+if [ ! -e $PROTOC_PATH ]; then
+    echo You must install the protocol compiler from
+    echo https://code.google.com/p/protobuf/downloads/list
+    exit 1
+fi
+
+echo Using $PROTOC_PATH as the protocol compiler
+
+$PROTOC_PATH --cpp_out="." CoreDump.proto
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -10,24 +10,31 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit
 
 XPIDL_SOURCES += [
     'nsIJSInspector.idl',
 ]
 
 XPIDL_MODULE = 'jsinspector'
 
 EXPORTS.mozilla.devtools += [
+    'ChromeUtils.h',
+    'CoreDump.pb.h',
     'ZeroCopyNSIOutputStream.h',
 ]
 
 SOURCES += [
+    'ChromeUtils.cpp',
+    'CoreDump.pb.cc',
     'nsJSInspector.cpp',
     'ZeroCopyNSIOutputStream.cpp',
 ]
 
+# Disable RTTI in google protocol buffer
+DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
+
 FINAL_LIBRARY = 'xul'
 
 EXTRA_JS_MODULES.devtools += [
     'content-server.jsm',
     'dbg-server.jsm',
 ]
 
 EXTRA_JS_MODULES.devtools.server += [