Bug 1149294 - Part 3: Split thread-safe methods on ChromeUtils out into a new
authorNick Fitzgerald <fitzgen@gmail.com>
Wed, 17 Jun 2015 11:12:23 -0700
changeset 249459 6e4dca36cb90609ce5636e669c94a035ef94a5a6
parent 249458 b905fe812fab5779f90c75a104927c124cf0ebcd
child 249460 de4a6653d4f206c4bf047ea319751551477ec24b
push id28927
push usercbook@mozilla.com
push dateThu, 18 Jun 2015 13:13:33 +0000
treeherdermozilla-central@efe86609e776 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1149294
milestone41.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 1149294 - Part 3: Split thread-safe methods on ChromeUtils out into a new ThreadSafeChromeUtils interface and move the utils interfaces into dom/base; r=bholley
dom/base/ChromeUtils.cpp
dom/base/ChromeUtils.h
dom/base/MultipartBlobImpl.h
dom/base/moz.build
dom/base/nsContentPolicy.cpp
dom/bindings/Bindings.conf
dom/webidl/ChromeUtils.webidl
dom/webidl/ThreadSafeChromeUtils.webidl
dom/webidl/moz.build
js/public/Debug.h
js/src/vm/UbiNode.cpp
toolkit/devtools/server/ChromeUtils.cpp
toolkit/devtools/server/ChromeUtils.h
toolkit/devtools/server/HeapSnapshot.cpp
toolkit/devtools/server/HeapSnapshot.h
toolkit/devtools/server/moz.build
toolkit/devtools/server/tests/gtest/DevTools.h
toolkit/devtools/server/tests/unit/heap-snapshot-worker.js
new file mode 100644
--- /dev/null
+++ b/dom/base/ChromeUtils.cpp
@@ -0,0 +1,23 @@
+/* -*-  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 "mozilla/BasePrincipal.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ void
+ChromeUtils::OriginAttributesToCookieJar(GlobalObject& aGlobal,
+                                         const OriginAttributesDictionary& aAttrs,
+                                         nsCString& aCookieJar)
+{
+  OriginAttributes attrs(aAttrs);
+  attrs.CookieJar(aCookieJar);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/ChromeUtils.h
@@ -0,0 +1,52 @@
+/* -*-  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_dom_ChromeUtils__
+#define mozilla_dom_ChromeUtils__
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/dom/ThreadSafeChromeUtilsBinding.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+
+namespace devtools {
+class HeapSnapshot;
+}
+
+namespace dom {
+
+class ThreadSafeChromeUtils
+{
+public:
+  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  static void SaveHeapSnapshot(GlobalObject& global,
+                               JSContext* cx,
+                               const nsAString& filePath,
+                               const HeapSnapshotBoundaries& boundaries,
+                               ErrorResult& rv);
+
+  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  static already_AddRefed<devtools::HeapSnapshot> ReadHeapSnapshot(GlobalObject& global,
+                                                                   JSContext* cx,
+                                                                   const nsAString& filePath,
+                                                                   ErrorResult& rv);
+};
+
+class ChromeUtils : public ThreadSafeChromeUtils
+{
+public:
+  static void
+  OriginAttributesToCookieJar(dom::GlobalObject& aGlobal,
+                              const dom::OriginAttributesDictionary& aAttrs,
+                              nsCString& aCookieJar);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ChromeUtils__
--- a/dom/base/MultipartBlobImpl.h
+++ b/dom/base/MultipartBlobImpl.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/FileBinding.h"
 #include <algorithm>
+#include "nsPIDOMWindow.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 class MultipartBlobImpl final : public BlobImplBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -149,16 +149,17 @@ EXPORTS.mozilla += [
 ]
 
 EXPORTS.mozilla.dom += [
     'AnonymousContent.h',
     'Attr.h',
     'BarProps.h',
     'BlobSet.h',
     'ChildIterator.h',
+    'ChromeUtils.h',
     'Comment.h',
     'Console.h',
     'DirectionalityUtils.h',
     'DocumentFragment.h',
     'DocumentType.h',
     'DOMCursor.h',
     'DOMError.h',
     'DOMException.h',
@@ -204,16 +205,17 @@ EXPORTS.mozilla.dom += [
     'WebSocket.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnonymousContent.cpp',
     'Attr.cpp',
     'BarProps.cpp',
     'ChildIterator.cpp',
+    'ChromeUtils.cpp',
     'Comment.cpp',
     'Console.cpp',
     'Crypto.cpp',
     'DirectionalityUtils.cpp',
     'DocumentFragment.cpp',
     'DocumentType.cpp',
     'DOMCursor.cpp',
     'DOMError.cpp',
--- a/dom/base/nsContentPolicy.cpp
+++ b/dom/base/nsContentPolicy.cpp
@@ -1,30 +1,34 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 // vim: ft=cpp tw=78 sw=4 et ts=8
 /* 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/. */
 
-/* 
+/*
  * Implementation of the "@mozilla.org/layout/content-policy;1" contract.
  */
 
 #include "mozilla/Logging.h"
 
 #include "nsISupports.h"
 #include "nsXPCOM.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentPolicy.h"
 #include "nsIURI.h"
+#include "nsIDocShell.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMWindow.h"
 #include "nsIContent.h"
+#include "nsILoadContext.h"
 #include "nsCOMArray.h"
 
+using mozilla::LogLevel;
+
 NS_IMPL_ISUPPORTS(nsContentPolicy, nsIContentPolicy)
 
 static PRLogModuleInfo* gConPolLog;
 
 nsresult
 NS_NewContentPolicy(nsIContentPolicy **aResult)
 {
   *aResult = new nsContentPolicy;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -265,18 +265,16 @@ DOMInterfaces = {
     'nativeType': 'nsGenericDOMDataNode',
     'concrete': False
 },
 
 'ChromeUtils': {
     # The codegen is dumb, and doesn't understand that this interface is only a
     # collection of static methods, so we have this `concrete: False` hack.
     'concrete': False,
-    'nativeType': 'mozilla::devtools::ChromeUtils',
-    'implicitJSContext': ['readHeapSnapshot', 'saveHeapSnapshot']
 },
 
 'ChromeWindow': {
     'concrete': False,
 },
 
 'ChromeWorker': {
     'headerFile': 'mozilla/dom/WorkerPrivate.h',
@@ -1268,16 +1266,24 @@ DOMInterfaces = {
 'TextEncoder': {
     'wrapperCache': False
 },
 
 'TextMetrics': {
     'wrapperCache': False
 },
 
+'ThreadSafeChromeUtils': {
+    # The codegen is dumb, and doesn't understand that this interface is only a
+    # collection of static methods, so we have this `concrete: False` hack.
+    'concrete': False,
+    'headerFile': 'mozilla/dom/ChromeUtils.h',
+    'implicitJSContext': ['readHeapSnapshot', 'saveHeapSnapshot']
+},
+
 'TimeRanges': {
     'wrapperCache': False
 },
 
 'TouchList': {
     'headerFile': 'mozilla/dom/TouchEvent.h',
 },
 
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -1,82 +1,31 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 /**
- * A collection of static utility methods that are only exposed to Chrome.
+ * A collection of static utility methods that are only exposed to Chrome. This
+ * interface is not exposed in workers, while ThreadSafeChromeUtils is.
  */
-[ChromeOnly, Exposed=(Window,System,Worker)]
-interface ChromeUtils {
-  /**
-   * Serialize a snapshot of the heap graph, as seen by |JS::ubi::Node| and
-   * restricted by |boundaries|, and write it to the provided file path.
-   *
-   * @param filePath          The file path to write the heap snapshot to.
-   *
-   * @param boundaries        The portion of the heap graph to write.
-   */
-  [Throws]
-  static void saveHeapSnapshot(DOMString filePath,
-                               optional HeapSnapshotBoundaries boundaries);
-
-  /**
-   * Deserialize a core dump into a HeapSnapshot.
-   *
-   * @param filePath          The file path to read the core dump from.
-   */
-  [Throws, NewObject]
-  static HeapSnapshot readHeapSnapshot(DOMString filePath);
-
+[ChromeOnly, Exposed=(Window,System)]
+interface ChromeUtils : ThreadSafeChromeUtils {
   /**
    * A helper that converts OriginAttributesDictionary to cookie jar opaque
    * identfier.
    *
    * @param originAttrs       The originAttributes from the caller.
    */
   static ByteString
   originAttributesToCookieJar(optional OriginAttributesDictionary originAttrs);
 };
 
 /**
- * A JS object whose properties specify what portion of the heap graph to
- * write. The recognized properties are:
- *
- * * globals: [ global, ... ]
- *   Dump only nodes that either:
- *   - belong in the compartment of one of the given globals;
- *   - belong to no compartment, but do belong to a Zone that contains one of
- *     the given globals;
- *   - are referred to directly by one of the last two kinds of nodes; or
- *   - is the fictional root node, described below.
- *
- * * debugger: Debugger object
- *   Like "globals", but use the Debugger's debuggees as the globals.
- *
- * * runtime: true
- *   Dump the entire heap graph, starting with the JSRuntime's roots.
- *
- * One, and only one, of these properties must exist on the boundaries object.
- *
- * The root of the dumped graph is a fictional node whose ubi::Node type name is
- * "CoreDumpRoot". If we are dumping the entire ubi::Node graph, this root node
- * has an edge for each of the JSRuntime's roots. If we are dumping a selected
- * set of globals, the root has an edge to each global, and an edge for each
- * incoming JS reference to the selected Zones.
- */
-dictionary HeapSnapshotBoundaries {
-  sequence<object> globals;
-  object           debugger;
-  boolean          runtime;
-};
-
-/**
  * Used by principals and the script security manager to represent origin
  * attributes.
  *
  * IMPORTANT: If you add any members here, you need to update the
  * methods on mozilla::OriginAttributes, and bump the CIDs of all
  * the principal implementations that use OriginAttributes in their
  * nsISerializable implementations.
  */
copy from dom/webidl/ChromeUtils.webidl
copy to dom/webidl/ThreadSafeChromeUtils.webidl
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ThreadSafeChromeUtils.webidl
@@ -1,47 +1,39 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 /**
- * A collection of static utility methods that are only exposed to Chrome.
+ * A collection of **thread-safe** static utility methods that are only exposed
+ * to Chrome. This interface is exposed in workers, while ChromeUtils is not.
  */
 [ChromeOnly, Exposed=(Window,System,Worker)]
-interface ChromeUtils {
+interface ThreadSafeChromeUtils {
   /**
    * Serialize a snapshot of the heap graph, as seen by |JS::ubi::Node| and
    * restricted by |boundaries|, and write it to the provided file path.
    *
    * @param filePath          The file path to write the heap snapshot to.
    *
    * @param boundaries        The portion of the heap graph to write.
    */
   [Throws]
   static void saveHeapSnapshot(DOMString filePath,
                                optional HeapSnapshotBoundaries boundaries);
 
   /**
    * Deserialize a core dump into a HeapSnapshot.
    *
-   * @param filePath          The file path to read the core dump from.
+   * @param filePath          The file path to read the heap snapshot from.
    */
   [Throws, NewObject]
   static HeapSnapshot readHeapSnapshot(DOMString filePath);
-
-  /**
-   * A helper that converts OriginAttributesDictionary to cookie jar opaque
-   * identfier.
-   *
-   * @param originAttrs       The originAttributes from the caller.
-   */
-  static ByteString
-  originAttributesToCookieJar(optional OriginAttributesDictionary originAttrs);
 };
 
 /**
  * A JS object whose properties specify what portion of the heap graph to
  * write. The recognized properties are:
  *
  * * globals: [ global, ... ]
  *   Dump only nodes that either:
@@ -65,22 +57,8 @@ interface ChromeUtils {
  * set of globals, the root has an edge to each global, and an edge for each
  * incoming JS reference to the selected Zones.
  */
 dictionary HeapSnapshotBoundaries {
   sequence<object> globals;
   object           debugger;
   boolean          runtime;
 };
-
-/**
- * Used by principals and the script security manager to represent origin
- * attributes.
- *
- * IMPORTANT: If you add any members here, you need to update the
- * methods on mozilla::OriginAttributes, and bump the CIDs of all
- * the principal implementations that use OriginAttributes in their
- * nsISerializable implementations.
- */
-dictionary OriginAttributesDictionary {
-  unsigned long appId = 0;
-  boolean inBrowser = false;
-};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -517,16 +517,17 @@ WEBIDL_FILES = [
     'TelephonyCallGroup.webidl',
     'TelephonyCallId.webidl',
     'Text.webidl',
     'TextDecoder.webidl',
     'TextEncoder.webidl',
     'TextTrack.webidl',
     'TextTrackCueList.webidl',
     'TextTrackList.webidl',
+    'ThreadSafeChromeUtils.webidl',
     'TimeEvent.webidl',
     'TimeRanges.webidl',
     'Touch.webidl',
     'TouchEvent.webidl',
     'TouchList.webidl',
     'TransitionEvent.webidl',
     'TreeBoxObject.webidl',
     'TreeColumn.webidl',
--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -9,16 +9,17 @@
 #ifndef js_Debug_h
 #define js_Debug_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/UniquePtr.h"
 
+#include "jsapi.h"
 #include "jspubtd.h"
 
 #include "js/GCAPI.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 
 namespace js {
 class Debugger;
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -112,19 +112,19 @@ class SimpleEdgeVectorTracer : public JS
     bool wantNames;
 
     void onChild(const JS::GCCellPtr& thing) override {
         if (!okay)
             return;
 
         // Don't trace permanent atoms and well-known symbols that are owned by
         // a parent JSRuntime.
-        if (kind == JS::TraceKind::String && static_cast<JSString*>(*thingp)->isPermanentAtom())
+        if (thing.isString() && thing.toString()->isPermanentAtom())
             return;
-        if (kind == JS::TraceKind::Symbol && static_cast<JS::Symbol*>(*thingp)->isWellKnownSymbol())
+        if (thing.isSymbol() && thing.toSymbol()->isWellKnownSymbol())
             return;
 
         char16_t* name16 = nullptr;
         if (wantNames) {
             // Ask the tracer to compute an edge name for us.
             char buffer[1024];
             getTracingEdgeName(buffer, sizeof(buffer));
             const char* name = buffer;
deleted file mode 100644
--- a/toolkit/devtools/server/ChromeUtils.cpp
+++ /dev/null
@@ -1,420 +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 "ChromeUtils.h"
-
-#include <google/protobuf/io/coded_stream.h>
-#include <google/protobuf/io/gzip_stream.h>
-
-#include "mozilla/devtools/AutoMemMap.h"
-#include "mozilla/devtools/HeapSnapshot.h"
-#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/BasePrincipal.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/TypeDecls.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(GetObjectZone(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 `ZeroCopyOutputStream`.
-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;
-    }
-}
-
-/* static */ already_AddRefed<HeapSnapshot>
-ChromeUtils::ReadHeapSnapshot(GlobalObject& global,
-                              JSContext* cx,
-                              const nsAString& filePath,
-                              ErrorResult& rv)
-{
-  UniquePtr<char[]> path(ToNewCString(filePath));
-  if (!path) {
-    rv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return nullptr;
-  }
-
-  AutoMemMap mm;
-  rv = mm.init(path.get());
-  if (rv.Failed())
-    return nullptr;
-
-  return HeapSnapshot::Create(cx, global,
-                              reinterpret_cast<const uint8_t*>(mm.address()),
-                              mm.size(), rv);
-}
-
-/* static */ void
-ChromeUtils::OriginAttributesToCookieJar(GlobalObject& aGlobal,
-                                         const OriginAttributesDictionary& aAttrs,
-                                         nsCString& aCookieJar)
-{
-  OriginAttributes attrs(aAttrs);
-  attrs.CookieJar(aCookieJar);
-}
-
-} // namespace devtools
-} // namespace mozilla
deleted file mode 100644
--- a/toolkit/devtools/server/ChromeUtils.h
+++ /dev/null
@@ -1,86 +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 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/AlreadyAddRefed.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 HeapSnapshot;
-
-
-class ChromeUtils
-{
-public:
-  static void SaveHeapSnapshot(dom::GlobalObject& global,
-                               JSContext* cx,
-                               const nsAString& filePath,
-                               const dom::HeapSnapshotBoundaries& boundaries,
-                               ErrorResult& rv);
-
-  static already_AddRefed<HeapSnapshot> ReadHeapSnapshot(dom::GlobalObject& global,
-                                                         JSContext* cx,
-                                                         const nsAString& filePath,
-                                                         ErrorResult& rv);
-
-  static void
-  OriginAttributesToCookieJar(dom::GlobalObject& aGlobal,
-                              const dom::OriginAttributesDictionary& aAttrs,
-                              nsCString& aCookieJar);
-};
-
-} // namespace devtools
-} // namespace mozilla
-
-#endif // mozilla_devtools_ChromeUtils__
--- a/toolkit/devtools/server/HeapSnapshot.cpp
+++ b/toolkit/devtools/server/HeapSnapshot.cpp
@@ -4,27 +4,44 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "HeapSnapshot.h"
 
 #include <google/protobuf/io/coded_stream.h>
 #include <google/protobuf/io/gzip_stream.h>
 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 
+#include "js/Debug.h"
+#include "js/TypeDecls.h"
+#include "js/UbiNodeTraverse.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/devtools/AutoMemMap.h"
+#include "mozilla/devtools/CoreDump.pb.h"
 #include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
+#include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/HeapSnapshotBinding.h"
+#include "mozilla/UniquePtr.h"
 
-#include "CoreDump.pb.h"
+#include "jsapi.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCRTGlue.h"
+#include "nsIOutputStream.h"
 #include "nsISupportsImpl.h"
+#include "nsNetUtil.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prtypes.h"
 
 namespace mozilla {
 namespace devtools {
 
+using namespace JS;
+using namespace dom;
+
 using ::google::protobuf::io::ArrayInputStream;
 using ::google::protobuf::io::CodedInputStream;
 using ::google::protobuf::io::GzipInputStream;
 using ::google::protobuf::io::ZeroCopyInputStream;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HeapSnapshot)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HeapSnapshot)
@@ -42,24 +59,24 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnap
 NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 /* virtual */ JSObject*
-HeapSnapshot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+HeapSnapshot::WrapObject(JSContext* aCx, HandleObject aGivenProto)
 {
-  return dom::HeapSnapshotBinding::Wrap(aCx, this, aGivenProto);
+  return HeapSnapshotBinding::Wrap(aCx, this, aGivenProto);
 }
 
 /* static */ already_AddRefed<HeapSnapshot>
 HeapSnapshot::Create(JSContext* cx,
-                     dom::GlobalObject& global,
+                     GlobalObject& global,
                      const uint8_t* buffer,
                      uint32_t size,
                      ErrorResult& rv)
 {
   nsRefPtr<HeapSnapshot> snapshot = new HeapSnapshot(cx, global.GetAsSupports());
   if (!snapshot->init(buffer, size)) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
@@ -212,11 +229,392 @@ HeapSnapshot::borrowUniqueString(const c
     if (!owned || !strings.add(ptr, Move(owned)))
       return nullptr;
   }
 
   MOZ_ASSERT(ptr->get() != duplicateString);
   return ptr->get();
 }
 
+// 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(GetObjectZone(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 `ZeroCopyOutputStream`.
+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();
+}
 
 } // namespace devtools
+
+namespace dom {
+
+using namespace JS;
+using namespace devtools;
+
+/* static */ void
+ThreadSafeChromeUtils::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;
+    }
+}
+
+/* static */ already_AddRefed<HeapSnapshot>
+ThreadSafeChromeUtils::ReadHeapSnapshot(GlobalObject& global,
+                                        JSContext* cx,
+                                        const nsAString& filePath,
+                                        ErrorResult& rv)
+{
+  UniquePtr<char[]> path(ToNewCString(filePath));
+  if (!path) {
+    rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  AutoMemMap mm;
+  rv = mm.init(path.get());
+  if (rv.Failed())
+    return nullptr;
+
+  return HeapSnapshot::Create(cx, global,
+                              reinterpret_cast<const uint8_t*>(mm.address()),
+                              mm.size(), rv);
+}
+
+} // namespace dom
 } // namespace mozilla
--- a/toolkit/devtools/server/HeapSnapshot.h
+++ b/toolkit/devtools/server/HeapSnapshot.h
@@ -124,12 +124,47 @@ public:
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   const char16_t* borrowUniqueString(const char16_t* duplicateString,
                                      size_t length);
 };
 
+// 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);
+
 } // namespace devtools
 } // namespace mozilla
 
 #endif // mozilla_devtools_HeapSnapshot__
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -14,26 +14,24 @@ if CONFIG['ENABLE_TESTS']:
 XPIDL_SOURCES += [
     'nsIJSInspector.idl',
 ]
 
 XPIDL_MODULE = 'jsinspector'
 
 EXPORTS.mozilla.devtools += [
     'AutoMemMap.h',
-    'ChromeUtils.h',
     'CoreDump.pb.h',
     'DeserializedNode.h',
     'HeapSnapshot.h',
     'ZeroCopyNSIOutputStream.h',
 ]
 
 SOURCES += [
     'AutoMemMap.cpp',
-    'ChromeUtils.cpp',
     'CoreDump.pb.cc',
     'DeserializedNode.cpp',
     'HeapSnapshot.cpp',
     'nsJSInspector.cpp',
     'ZeroCopyNSIOutputStream.cpp',
 ]
 
 # Disable RTTI in google protocol buffer
--- a/toolkit/devtools/server/tests/gtest/DevTools.h
+++ b/toolkit/devtools/server/tests/gtest/DevTools.h
@@ -8,24 +8,26 @@
 
 #include "CoreDump.pb.h"
 #include "jsapi.h"
 #include "jspubtd.h"
 #include "nsCRTGlue.h"
 
 #include "gtest/gtest.h"
 #include "gmock/gmock.h"
-#include "mozilla/devtools/ChromeUtils.h"
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/Move.h"
 #include "mozilla/UniquePtr.h"
 #include "js/UbiNode.h"
 
 using namespace mozilla;
 using namespace mozilla::devtools;
+using namespace mozilla::dom;
 using namespace testing;
 
 // GTest fixture class that all of our tests derive from.
 struct DevTools : public ::testing::Test {
   bool                       _initialized;
   JSRuntime*                 rt;
   JSContext*                 cx;
   JSCompartment*             compartment;
--- a/toolkit/devtools/server/tests/unit/heap-snapshot-worker.js
+++ b/toolkit/devtools/server/tests/unit/heap-snapshot-worker.js
@@ -3,23 +3,27 @@
 
 console.log("Initializing worker.");
 
 self.onmessage = e => {
   console.log("Starting test.");
   try {
     const { filePath } = e.data;
 
-    ok(ChromeUtils, "Should have access to ChromeUtils");
-    ok(HeapSnapshot, "Should have access to HeapSnapshot");
+    ok(typeof ChromeUtils === "undefined",
+       "Should not have access to ChromeUtils in a worker.");
+    ok(ThreadSafeChromeUtils,
+       "Should have access to ThreadSafeChromeUtils in a worker.");
+    ok(HeapSnapshot,
+       "Should have access to HeapSnapshot in a worker.");
 
-    ChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
+    ThreadSafeChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
     ok(true, "Should be able to save a snapshot.");
 
-    const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+    const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(filePath);
     ok(snapshot, "Should be able to read a heap snapshot");
     ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
   } catch (e) {
     ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
   } finally {
     done();
   }
 };