Bug 1588997 - Convert ObjectClient to protocol.js front. r=nchevobbe.
authorjaril <jarilvalenciano@gmail.com>
Thu, 17 Oct 2019 16:06:25 +0000
changeset 498071 a15ba287ac6fd84643d248ace00bf18c18382ada
parent 498070 70426e17c6445e928a200123d6a65859870859ff
child 498072 4946569ae95bbf189dea8bc8dfff8d839b496f4b
push id36705
push useraciure@mozilla.com
push dateFri, 18 Oct 2019 09:53:40 +0000
treeherdermozilla-central@a15ba287ac6f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1588997
milestone71.0a1
first release with
nightly linux32
a15ba287ac6f / 71.0a1 / 20191018095340 / files
nightly linux64
a15ba287ac6f / 71.0a1 / 20191018095340 / files
nightly mac
a15ba287ac6f / 71.0a1 / 20191018095340 / files
nightly win32
a15ba287ac6f / 71.0a1 / 20191018095340 / files
nightly win64
a15ba287ac6f / 71.0a1 / 20191018095340 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1588997 - Convert ObjectClient to protocol.js front. r=nchevobbe. - Converted the ObjectClient into an protocoljs Front - Converted the SymbolIteratorClient into a protocoljs Front and moved it to devtools/shared/fronts - Converted the PropertyIteratorClient into a protocoljs Front and moved it to devtools/shared/fronts - Converted the EnvironmentClient into a protocoljs Front and moved it to devtools/shared/fronts - Modified calls to `DebuggerClient.release()` so that it tries to call the ObjectFront's release method first, and falls back on `DebuggerClient.release()` if there's no object front - Changed reps so that it instantiates only one ObjectClient per grip - Changed tests so that they expect what the Front's request method resolves to where applicable (i.e. ObjectFront.allocationStack resolves to allocationStack, not a packet object with an allocationStack property) - Changed callbacks provided to ObjectClient methods to be chained to the ObjectFront methods (e.g. ObjectClient.getScope(callback) changed to ObjectFront.getScope().callback()) - Changed tests to use async/await (test_framebindings-x.js, test_functiongrips-x.js, test_objectgrips-x.js) - Changed tests to expect protocoljs to throw an error string instead of an error object (test_objectgrips-fn-apply-03.js, test_threadlifetime-02.js, test_pauselifetime-03.js) Differential Revision: https://phabricator.services.mozilla.com/D48182
browser/components/extensions/parent/ext-devtools-panels.js
devtools/client/debugger/packages/devtools-reps/src/launchpad/index.js
devtools/client/debugger/src/client/firefox/commands.js
devtools/client/debugger/src/client/firefox/types.js
devtools/client/debugger/test/mochitest/browser_dbg-scopes-mutations.js
devtools/client/debugger/test/mochitest/browser_dbg-sourcemaps-reloading.js
devtools/client/inspector/inspector.js
devtools/client/scratchpad/scratchpad.js
devtools/client/shared/components/reps/reps.js
devtools/client/shared/widgets/VariablesViewController.jsm
devtools/client/webconsole/commands.js
devtools/client/webconsole/test/browser/browser_console_cpow.js
devtools/client/webconsole/test/browser/browser_console_webconsole_console_api_calls.js
devtools/client/webconsole/webconsole-connection-proxy.js
devtools/server/tests/browser/browser_dbg_promises-allocation-stack.js
devtools/server/tests/browser/browser_dbg_promises-chrome-allocation-stack.js
devtools/server/tests/browser/browser_dbg_promises-fulfillment-stack.js
devtools/server/tests/browser/browser_dbg_promises-rejection-stack.js
devtools/server/tests/unit/head_dbg.js
devtools/server/tests/unit/test_framebindings-01.js
devtools/server/tests/unit/test_framebindings-02.js
devtools/server/tests/unit/test_framebindings-03.js
devtools/server/tests/unit/test_framebindings-04.js
devtools/server/tests/unit/test_framebindings-05.js
devtools/server/tests/unit/test_framebindings-06.js
devtools/server/tests/unit/test_framebindings-07.js
devtools/server/tests/unit/test_functiongrips-01.js
devtools/server/tests/unit/test_objectgrips-01.js
devtools/server/tests/unit/test_objectgrips-02.js
devtools/server/tests/unit/test_objectgrips-03.js
devtools/server/tests/unit/test_objectgrips-04.js
devtools/server/tests/unit/test_objectgrips-05.js
devtools/server/tests/unit/test_objectgrips-06.js
devtools/server/tests/unit/test_objectgrips-07.js
devtools/server/tests/unit/test_objectgrips-08.js
devtools/server/tests/unit/test_objectgrips-10.js
devtools/server/tests/unit/test_objectgrips-11.js
devtools/server/tests/unit/test_objectgrips-12.js
devtools/server/tests/unit/test_objectgrips-13.js
devtools/server/tests/unit/test_objectgrips-17.js
devtools/server/tests/unit/test_objectgrips-18.js
devtools/server/tests/unit/test_objectgrips-20.js
devtools/server/tests/unit/test_objectgrips-21.js
devtools/server/tests/unit/test_objectgrips-22.js
devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
devtools/server/tests/unit/test_pauselifetime-03.js
devtools/server/tests/unit/test_promises_client_getdependentpromises.js
devtools/server/tests/unit/test_threadlifetime-02.js
devtools/shared/client/environment-client.js
devtools/shared/client/moz.build
devtools/shared/client/object-client.js
devtools/shared/client/property-iterator-client.js
devtools/shared/client/symbol-iterator-client.js
devtools/shared/fronts/environment.js
devtools/shared/fronts/moz.build
devtools/shared/fronts/property-iterator.js
devtools/shared/fronts/symbol-iterator.js
devtools/shared/specs/index.js
devtools/shared/webconsole/test/test_bug819670_getter_throws.html
devtools/shared/webconsole/test/test_object_actor.html
devtools/shared/webconsole/test/test_object_actor_native_getters.html
devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
--- a/browser/components/extensions/parent/ext-devtools-panels.js
+++ b/browser/components/extensions/parent/ext-devtools-panels.js
@@ -556,20 +556,28 @@ class ParentDevToolsInspectorSidebar ext
 
   _updateLastObjectValueGrip(newObjectValueGrip = null) {
     const { _lastObjectValueGrip } = this;
 
     this._lastObjectValueGrip = newObjectValueGrip;
 
     const oldActor = _lastObjectValueGrip && _lastObjectValueGrip.actor;
     const newActor = newObjectValueGrip && newObjectValueGrip.actor;
+    const client = this.toolbox.target.client;
 
     // Release the previously active actor on the remote debugging server.
     if (oldActor && oldActor !== newActor) {
-      this.toolbox.target.client.release(oldActor);
+      const objFront = client.getFrontByID(oldActor);
+      if (objFront) {
+        objFront.release();
+        return;
+      }
+
+      // In case there's no object front, use the client's release method.
+      client.release(oldActor).catch(() => {});
     }
   }
 }
 
 const sidebarsById = new Map();
 
 this.devtools_panels = class extends ExtensionAPI {
   getAPI(context) {
--- a/devtools/client/debugger/packages/devtools-reps/src/launchpad/index.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/launchpad/index.js
@@ -29,17 +29,25 @@ function onConnect(connection) {
 
     createObjectClient: function(grip) {
       return connection.tabConnection.threadFront.pauseGrip(grip);
     },
     createLongStringClient: function(grip) {
       return connection.tabConnection.tabTarget.activeConsole.longString(grip);
     },
     releaseActor: function(actor) {
-      return connection.tabConnection.debuggerClient.release(actor);
+      const debuggerClient = connection.tabConnection.debuggerClient;
+      const objFront = debuggerClient.getFrontByID(actor);
+
+      if (objFront) {
+        return objFront.release();
+      }
+
+      // In case there's no object front, use the client's release method.
+      return debuggerClient.release(actor).catch(() => {});
     },
   };
 
   const store = configureStore({
     makeThunkArgs: (args, state) => ({ ...args, client }),
     client,
   });
   renderRoot(React, ReactDOM, RepsConsole, store);
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -65,31 +65,36 @@ function setupCommands(dependencies: Dep
 }
 
 function createObjectClient(grip: Grip) {
   return debuggerClient.createObjectClient(grip);
 }
 
 async function loadObjectProperties(root: Node) {
   const utils = Reps.objectInspector.utils;
-  const properties = await utils.loadProperties.loadItemProperties(root, {
-    createObjectClient,
-  });
+  const properties = await utils.loadProperties.loadItemProperties(
+    root,
+    debuggerClient
+  );
   return utils.node.getChildren({
     item: root,
     loadedProperties: new Map([[root.path, properties]]),
   });
 }
 
 function releaseActor(actor: String) {
   if (!actor) {
     return;
   }
 
-  return debuggerClient.release(actor);
+  const objFront = debuggerClient.getFrontByID(actor);
+
+  if (objFront) {
+    return objFront.release().catch(() => {});
+  }
 }
 
 function sendPacket(packet: Object) {
   return debuggerClient.request(packet);
 }
 
 // Transforms targets from {[ThreadType]: TargetMap} to TargetMap
 function getTargetsMap(): { string: Target } {
@@ -531,16 +536,20 @@ async function getSourceActorBreakableLi
     } else if (!e.message || !e.message.match(/Connection closed/)) {
       throw e;
     }
   }
 
   return actorLines;
 }
 
+function getFrontByID(actorID: String) {
+  return debuggerClient.getFrontByID(actorID);
+}
+
 const clientCommands = {
   autocomplete,
   blackBox,
   createObjectClient,
   loadObjectProperties,
   releaseActor,
   interrupt,
   pauseGrip,
@@ -577,11 +586,12 @@ const clientCommands = {
   fetchThreads,
   getMainThread,
   sendPacket,
   setSkipPausing,
   setEventListenerBreakpoints,
   getEventListenerBreakpointTypes,
   detachWorkers,
   lookupTarget,
+  getFrontByID,
 };
 
 export { setupCommands, clientCommands };
--- a/devtools/client/debugger/src/client/firefox/types.js
+++ b/devtools/client/debugger/src/client/firefox/types.js
@@ -258,16 +258,17 @@ export type DebuggerClient = {
     listProcesses: () => Promise<{ processes: ProcessDescriptor }>,
     on: (string, Function) => void,
   },
   connect: () => Promise<*>,
   request: (packet: Object) => Promise<*>,
   attachConsole: (actor: String, listeners: Array<*>) => Promise<*>,
   createObjectClient: (grip: Grip) => ObjectClient,
   release: (actor: String) => {},
+  getFrontByID: (actor: String) => { release: () => Promise<*> },
 };
 
 type ProcessDescriptor = Object;
 
 /**
  * A grip is a JSON value that refers to a specific JavaScript value in the
  * debuggee. Grips appear anywhere an arbitrary value from the debuggee needs
  * to be conveyed to the client: stack frames, object property lists, lexical
--- a/devtools/client/debugger/test/mochitest/browser_dbg-scopes-mutations.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-scopes-mutations.js
@@ -21,16 +21,17 @@ function expandNode(dbg, index) {
 
 add_task(async function() {
   const dbg = await initDebugger("doc-script-mutate.html");
 
   let onPaused = waitForPaused(dbg);
   invokeInTab("mutate");
   await onPaused;
   await waitForSelectedSource(dbg, "script-mutate");
+  await waitForDispatch(dbg, "ADD_INLINE_PREVIEW");
 
   is(
     getScopeNodeLabel(dbg, 2),
     "<this>",
     'The second element in the scope panel is "<this>"'
   );
   is(
     getScopeNodeLabel(dbg, 4),
@@ -69,16 +70,17 @@ add_task(async function() {
   is(
     getScopeNodeValue(dbg, 7),
     '"Doe"',
     'The "lastName" element has the expected "Doe" value'
   );
 
   await resume(dbg);
   await waitForPaused(dbg);
+  await waitForDispatch(dbg, "ADD_INLINE_PREVIEW");
 
   is(
     getScopeNodeLabel(dbg, 2),
     "<this>",
     'The second element in the scope panel is "<this>"'
   );
   is(
     getScopeNodeLabel(dbg, 4),
--- a/devtools/client/debugger/test/mochitest/browser_dbg-sourcemaps-reloading.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-sourcemaps-reloading.js
@@ -33,16 +33,17 @@ add_task(async function() {
   await addBreakpoint(dbg, entrySrc, 15, 0);
   await disableBreakpoint(dbg, entrySrc, 15, 0);
 
   // Test reloading the debugger
   await reload(dbg, "opts.js");
   await waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
 
   await waitForPaused(dbg);
+  await waitForDispatch(dbg, "ADD_INLINE_PREVIEW");
   assertPausedLocation(dbg);
 
   await waitForBreakpointCount(dbg, 2);
   is(getBreakpointCount(), 2, "Three breakpoints exist");
 
   ok(
     getBreakpoint({ sourceId: entrySrc.id, line: 15, column: 0 }),
     "Breakpoint has correct line"
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -153,17 +153,24 @@ function Inspector(toolbox) {
   this.store = Store({
     createObjectClient: object => {
       return new ObjectClient(toolbox.target.client, object);
     },
     releaseActor: actor => {
       if (!actor) {
         return;
       }
-      toolbox.target.client.release(actor);
+      const objFront = toolbox.target.client.getFrontByID(actor);
+      if (objFront) {
+        objFront.release();
+        return;
+      }
+
+      // In case there's no object front, use the client's release method.
+      toolbox.target.client.release(actor).catch(() => {});
     },
   });
 
   // Map [panel id => panel instance]
   // Stores all the instances of sidebar panels like rule view, computed view, ...
   this._panels = new Map();
 
   this._clearSearchResultsLabel = this._clearSearchResultsLabel.bind(this);
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -116,18 +116,18 @@ loader.lazyRequireGetter(
 loader.lazyRequireGetter(
   this,
   "DebuggerClient",
   "devtools/shared/client/debugger-client",
   true
 );
 loader.lazyRequireGetter(
   this,
-  "EnvironmentClient",
-  "devtools/shared/client/environment-client"
+  "EnvironmentFront",
+  "devtools/shared/fronts/environment"
 );
 loader.lazyRequireGetter(
   this,
   "ObjectClient",
   "devtools/shared/client/object-client"
 );
 loader.lazyRequireGetter(
   this,
@@ -2328,31 +2328,36 @@ ScratchpadSidebar.prototype = {
           this.variablesView = new VariablesView(container, {
             searchEnabled: true,
             searchPlaceholder: this._scratchpad.strings.GetStringFromName(
               "propertiesFilterPlaceholder"
             ),
           });
 
           VariablesViewController.attach(this.variablesView, {
-            getEnvironmentClient: grip => {
-              return new EnvironmentClient(
+            getEnvironmentFront: grip => {
+              return new EnvironmentFront(
                 this._scratchpad.debuggerClient,
                 grip
               );
             },
             getObjectClient: grip => {
               return new ObjectClient(this._scratchpad.debuggerClient, grip);
             },
             getLongStringClient: actor => {
               return this._scratchpad.webConsoleFront.longString(actor);
             },
             releaseActor: actor => {
+              const objFront = this._scratchpad.debuggerClient.getFrontByID(
+                actor
+              );
               // Ignore release failure, since the object actor may have been already GC.
-              this._scratchpad.debuggerClient.release(actor).catch(() => {});
+              if (objFront) {
+                objFront.release().catch(() => {});
+              }
             },
           });
         }
         this._update(obj).then(resolve);
       };
 
       if (this._sidebar.getCurrentTabID() == "variablesview") {
         onTabReady();
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -3952,50 +3952,61 @@ const {
   nodeIsProxy,
   nodeNeedsNumericalBuckets,
   nodeIsLongString
 } = __webpack_require__(114);
 
 function loadItemProperties(item, client, loadedProperties) {
   const gripItem = getClosestGripNode(item);
   const value = getValue(gripItem);
+
   const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : [];
   const promises = [];
   let objectClient;
-
-  const getObjectClient = () => objectClient || client.createObjectClient(value);
+  
+  if (value && client && client.getFrontByID) {
+    objectClient = client.getFrontByID(value.actor);
+  }
+
+  const getObjectClient = function() {
+    if (!objectClient) {
+      objectClient = client.createObjectClient(value);
+    }
+
+    return objectClient;
+  }
 
   if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
     promises.push(enumIndexedProperties(getObjectClient(), start, end));
   }
-
+  
   if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) {
     promises.push(enumNonIndexedProperties(getObjectClient(), start, end));
   }
-
+  
   if (shouldLoadItemEntries(item, loadedProperties)) {
     promises.push(enumEntries(getObjectClient(), start, end));
   }
-
+  
   if (shouldLoadItemPrototype(item, loadedProperties)) {
     promises.push(getPrototype(getObjectClient()));
   }
-
+  
   if (shouldLoadItemSymbols(item, loadedProperties)) {
     promises.push(enumSymbols(getObjectClient(), start, end));
   }
-
+  
   if (shouldLoadItemFullText(item, loadedProperties)) {
     promises.push(getFullText(client.createLongStringClient(value), item));
   }
-
+  
   if (shouldLoadItemProxySlots(item, loadedProperties)) {
     promises.push(getProxySlots(getObjectClient()));
   }
-
+  
   return Promise.all(promises).then(mergeResponses);
 }
 
 function mergeResponses(responses) {
   const data = {};
 
   for (const response of responses) {
     if (response.hasOwnProperty("ownProperties")) {
@@ -4085,62 +4096,54 @@ module.exports = {
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 const {
   getValue,
   nodeHasFullText
 } = __webpack_require__(114);
 
 async function enumIndexedProperties(objectClient, start, end) {
   try {
-    const {
-      iterator
-    } = await objectClient.enumProperties({
+    const iterator = await objectClient.enumProperties({
       ignoreNonIndexedProperties: true
     });
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumIndexedProperties", e);
     return {};
   }
 }
 
 async function enumNonIndexedProperties(objectClient, start, end) {
   try {
-    const {
-      iterator
-    } = await objectClient.enumProperties({
+    const iterator = await objectClient.enumProperties({
       ignoreIndexedProperties: true
     });
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumNonIndexedProperties", e);
     return {};
   }
 }
 
 async function enumEntries(objectClient, start, end) {
   try {
-    const {
-      iterator
-    } = await objectClient.enumEntries();
+    const iterator = await objectClient.enumEntries();
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumEntries", e);
     return {};
   }
 }
 
 async function enumSymbols(objectClient, start, end) {
   try {
-    const {
-      iterator
-    } = await objectClient.enumSymbols();
+    const iterator = await objectClient.enumSymbols();
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumSymbols", e);
     return {};
   }
 }
 
@@ -8544,9 +8547,9 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBP
 		window.classNames = classNames;
 	}
 }());
 
 
 /***/ })
 
 /******/ });
-});
\ No newline at end of file
+});
--- a/devtools/client/shared/widgets/VariablesViewController.jsm
+++ b/devtools/client/shared/widgets/VariablesViewController.jsm
@@ -34,17 +34,17 @@ var L10N = new LocalizationHelper(DBG_ST
  * as manage actor lifespans.
  *
  * @param VariablesView aView
  *        The view to attach to.
  * @param object aOptions [optional]
  *        Options for configuring the controller. Supported options:
  *        - getObjectClient: @see this._setClientGetters
  *        - getLongStringClient: @see this._setClientGetters
- *        - getEnvironmentClient: @see this._setClientGetters
+ *        - getEnvironmentFront: @see this._setClientGetters
  *        - releaseActor: @see this._setClientGetters
  *        - overrideValueEvalMacro: @see _setEvaluationMacros
  *        - getterOrSetterEvalMacro: @see _setEvaluationMacros
  *        - simpleValueEvalMacro: @see _setEvaluationMacros
  */
 function VariablesViewController(aView, aOptions = {}) {
   this.addExpander = this.addExpander.bind(this);
 
@@ -75,28 +75,28 @@ VariablesViewController.prototype = {
 
   /**
    * Set the functions used to retrieve debugger client grips.
    *
    * @param object aOptions
    *        Options for getting the client grips. Supported options:
    *        - getObjectClient: callback for creating an object grip client
    *        - getLongStringClient: callback for creating a long string grip client
-   *        - getEnvironmentClient: callback for creating an environment client
+   *        - getEnvironmentFront: callback for creating an environment front
    *        - releaseActor: callback for releasing an actor when it's no longer needed
    */
   _setClientGetters: function(aOptions) {
     if (aOptions.getObjectClient) {
       this._getObjectClient = aOptions.getObjectClient;
     }
     if (aOptions.getLongStringClient) {
       this._getLongStringClient = aOptions.getLongStringClient;
     }
-    if (aOptions.getEnvironmentClient) {
-      this._getEnvironmentClient = aOptions.getEnvironmentClient;
+    if (aOptions.getEnvironmentFront) {
+      this._getEnvironmentFront = aOptions.getEnvironmentFront;
     }
     if (aOptions.releaseActor) {
       this._releaseActor = aOptions.releaseActor;
     }
   },
 
   /**
    * Sets the functions used when evaluating strings in the variables view.
@@ -248,55 +248,53 @@ VariablesViewController.prototype = {
     const objectClient = this._getObjectClient(aGrip);
     const isArray = aGrip.preview && aGrip.preview.kind === "ArrayLike";
     if (isArray) {
       // First enumerate array items, e.g. properties from `0` to `array.length`.
       const options = {
         ignoreNonIndexedProperties: true,
         query: aQuery,
       };
-      objectClient.enumProperties(options, ({ iterator }) => {
+      objectClient.enumProperties(options).then(iterator => {
         const sliceGrip = {
           type: "property-iterator",
           propertyIterator: iterator,
           start: 0,
           count: iterator.count,
         };
         this._populatePropertySlices(aTarget, sliceGrip).then(() => {
           // Then enumerate the rest of the properties, like length, buffer, etc.
           const options = {
             ignoreIndexedProperties: true,
             sort: true,
             query: aQuery,
           };
-          objectClient.enumProperties(options, ({ iterator }) => {
+          objectClient.enumProperties(options).then(iterator => {
             const sliceGrip = {
               type: "property-iterator",
               propertyIterator: iterator,
               start: 0,
               count: iterator.count,
             };
             deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
           });
         });
       });
     } else {
+      const options = { sort: true, query: aQuery };
       // For objects, we just enumerate all the properties sorted by name.
-      objectClient.enumProperties(
-        { sort: true, query: aQuery },
-        ({ iterator }) => {
-          const sliceGrip = {
-            type: "property-iterator",
-            propertyIterator: iterator,
-            start: 0,
-            count: iterator.count,
-          };
-          deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
-        }
-      );
+      objectClient.enumProperties(options).then(iterator => {
+        const sliceGrip = {
+          type: "property-iterator",
+          propertyIterator: iterator,
+          start: 0,
+          count: iterator.count,
+        };
+        deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
+      });
     }
     return deferred.promise;
   },
 
   /**
    * Adds the given prototype in the view.
    *
    * @param Scope aTarget
@@ -321,17 +319,17 @@ VariablesViewController.prototype = {
    * @param object aGrip
    *        The grip to use to populate the target.
    */
   _populateFromObject: function(aTarget, aGrip) {
     if (aGrip.class === "Proxy") {
       // Refuse to play the proxy's stupid game and just expose the target and handler.
       const deferred = defer();
       const objectClient = this._getObjectClient(aGrip);
-      objectClient.getProxySlots(aResponse => {
+      objectClient.getProxySlots().then(aResponse => {
         const target = aTarget.addItem(
           "<target>",
           { value: aResponse.proxyTarget },
           { internalItem: true }
         );
         this.addExpander(target, aResponse.proxyTarget);
         const handler = aTarget.addItem(
           "<handler>",
@@ -378,32 +376,32 @@ VariablesViewController.prototype = {
     // Fetch properties by slices if there is too many in order to prevent UI freeze.
     if (
       "ownPropertyLength" in aGrip &&
       aGrip.ownPropertyLength >= MAX_PROPERTY_ITEMS
     ) {
       return this._populateFromObjectWithIterator(aTarget, aGrip).then(() => {
         const deferred = defer();
         const objectClient = this._getObjectClient(aGrip);
-        objectClient.getPrototype(({ prototype }) => {
+        objectClient.getPrototype().then(prototype => {
           this._populateObjectPrototype(aTarget, prototype);
           deferred.resolve();
         });
         return deferred.promise;
       });
     }
 
     return this._populateProperties(aTarget, aGrip);
   },
 
   _populateProperties: function(aTarget, aGrip, aOptions) {
     const deferred = defer();
 
     const objectClient = this._getObjectClient(aGrip);
-    objectClient.getPrototypeAndProperties(aResponse => {
+    objectClient.getPrototypeAndProperties().then(aResponse => {
       const ownProperties = aResponse.ownProperties || {};
       const prototype = aResponse.prototype || null;
       // 'safeGetterValues' is new and isn't necessary defined on old actors.
       const safeGetterValues = aResponse.safeGetterValues || {};
       const sortable = VariablesView.isSortable(aGrip.class);
 
       // Merge the safe getter values into one object such that we can use it
       // in VariablesView.
@@ -426,17 +424,17 @@ VariablesViewController.prototype = {
       });
 
       // Add the variable's __proto__.
       this._populateObjectPrototype(aTarget, prototype);
 
       // If the object is a function we need to fetch its scope chain
       // to show them as closures for the respective function.
       if (aGrip.class == "Function") {
-        objectClient.getScope(aResponse => {
+        objectClient.getScope().then(aResponse => {
           if (aResponse.error) {
             // This function is bound to a built-in object or it's not present
             // in the current scope chain. Not necessarily an actual error,
             // it just means that there's no closure for the function.
             console.warn(aResponse.error + ": " + aResponse.message);
             return void deferred.resolve();
           }
           this._populateWithClosure(aTarget, aResponse.scope).then(
@@ -476,20 +474,22 @@ VariablesViewController.prototype = {
       closure.showArrow();
 
       // Add nodes for every argument and every other variable in scope.
       if (environment.bindings) {
         this._populateWithEnvironmentBindings(closure, environment.bindings);
       } else {
         const deferred = defer();
         objectScopes.push(deferred.promise);
-        this._getEnvironmentClient(environment).getBindings(response => {
-          this._populateWithEnvironmentBindings(closure, response.bindings);
-          deferred.resolve();
-        });
+        this._getEnvironmentFront(environment)
+          .getBindings()
+          .then(response => {
+            this._populateWithEnvironmentBindings(closure, response.bindings);
+            deferred.resolve();
+          });
       }
     } while ((environment = environment.parent));
 
     return promise.all(objectScopes).then(() => {
       // Signal that scopes have been fetched.
       this.view.emit("fetched", "scopes", funcScope);
     });
   },
@@ -529,17 +529,17 @@ VariablesViewController.prototype = {
   },
 
   _populateFromEntries: function(target, grip) {
     const objGrip = grip.obj;
     const objectClient = this._getObjectClient(objGrip);
 
     // eslint-disable-next-line new-cap
     return new promise((resolve, reject) => {
-      objectClient.enumEntries(response => {
+      objectClient.enumEntries().then(response => {
         if (response.error) {
           // Older server might not support the enumEntries method
           console.warn(response.error + ": " + response.message);
           resolve();
         } else {
           const sliceGrip = {
             type: "property-iterator",
             propertyIterator: response.iterator,
--- a/devtools/client/webconsole/commands.js
+++ b/devtools/client/webconsole/commands.js
@@ -27,31 +27,37 @@ class ConsoleCommands {
     return new LongStringClient(this.debuggerClient, object);
   }
 
   releaseActor(actor) {
     if (!actor) {
       return null;
     }
 
-    return this.debuggerClient.release(actor);
+    const objFront = this.debuggerClient.getFrontByID(actor);
+    if (objFront) {
+      return objFront.release();
+    }
+
+    // In case there's no object front, use the client's release method.
+    return this.debuggerClient.release(actor).catch(() => {});
   }
 
   async fetchObjectProperties(grip, ignoreNonIndexedProperties) {
     const client = new ObjectClient(this.currentTarget.client, grip);
-    const { iterator } = await client.enumProperties({
+    const iterator = await client.enumProperties({
       ignoreNonIndexedProperties,
     });
     const { ownProperties } = await iterator.slice(0, iterator.count);
     return ownProperties;
   }
 
   async fetchObjectEntries(grip) {
     const client = new ObjectClient(this.currentTarget.client, grip);
-    const { iterator } = await client.enumEntries();
+    const iterator = await client.enumEntries();
     const { ownProperties } = await iterator.slice(0, iterator.count);
     return ownProperties;
   }
 
   timeWarp(executionPoint) {
     return this.threadFront.timeWarp(executionPoint);
   }
 }
--- a/devtools/client/webconsole/test/browser/browser_console_cpow.js
+++ b/devtools/client/webconsole/test/browser/browser_console_cpow.js
@@ -92,39 +92,39 @@ async function testBackEnd(hud, actor) {
     Reflect.ownKeys(response.ownProperties).length,
     0,
     "No property was retrieved."
   );
   is(response.ownSymbols.length, 0, "No symbol property was retrieved.");
   is(response.prototype.type, "null", "The prototype is null.");
 
   response = await objClient.enumProperties({ ignoreIndexedProperties: true });
-  let slice = await response.iterator.slice(0, response.iterator.count);
+  let slice = await response.slice(0, response.count);
   is(
     Reflect.ownKeys(slice.ownProperties).length,
     0,
     "No property was retrieved."
   );
 
   response = await objClient.enumProperties({});
-  slice = await response.iterator.slice(0, response.iterator.count);
+  slice = await response.slice(0, response.count);
   is(
     Reflect.ownKeys(slice.ownProperties).length,
     0,
     "No property was retrieved."
   );
 
   response = await objClient.getOwnPropertyNames();
   is(response.ownPropertyNames.length, 0, "No property was retrieved.");
 
   response = await objClient.getProperty("x");
   is(response.descriptor, undefined, "The property does not exist.");
 
   response = await objClient.enumSymbols();
-  slice = await response.iterator.slice(0, response.iterator.count);
+  slice = await response.slice(0, response.count);
   is(slice.ownSymbols.length, 0, "No symbol property was retrieved.");
 
   response = await objClient.getPrototype();
   is(response.prototype.type, "null", "The prototype is null.");
 
   response = await objClient.getDisplayString();
   is(response.displayString, "<cpow>", "The CPOW stringifies to <cpow>");
 }
--- a/devtools/client/webconsole/test/browser/browser_console_webconsole_console_api_calls.js
+++ b/devtools/client/webconsole/test/browser/browser_console_webconsole_console_api_calls.js
@@ -143,10 +143,14 @@ async function checkContentConsoleApiMes
   await onContentMessagesDisplayed;
 
   for (const expectedMessage of expectedMessages) {
     ok(findMessage(hud, expectedMessage), `"${expectedMessage}" is visible`);
   }
 
   info("Clear and close the Browser Console");
   await clearOutput(hud);
+  // We use waitForTick here because of a race condition. Otherwise, the test
+  // would occassionally fail because the transport is closed before pending server
+  // responses have been sent.
+  await waitForTick();
   await BrowserConsoleManager.toggleBrowserConsole();
 }
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -357,16 +357,22 @@ class WebConsoleConnectionProxy {
   /**
    * Release an object actor.
    *
    * @param string actor
    *        The actor ID to send the request to.
    */
   releaseActor(actor) {
     if (this.client) {
+      const objFront = this.client.getFrontByID(actor);
+      if (objFront) {
+        objFront.release().catch(() => {});
+        return;
+      }
+      // In case there's no object front, use the client's release method.
       this.client.release(actor).catch(() => {});
     }
   }
 
   /**
    * Disconnect the Web Console from the remote server.
    *
    * @return object
--- a/devtools/server/tests/browser/browser_dbg_promises-allocation-stack.js
+++ b/devtools/server/tests/browser/browser_dbg_promises-allocation-stack.js
@@ -48,19 +48,19 @@ async function testGetAllocationStack(ta
 
   const form = await onNewPromise;
   ok(form, "Found our promise p");
 
   const objectClient = new ObjectClient(target.client, form);
   ok(objectClient, "Got Object Client");
 
   const response = await objectClient.getPromiseAllocationStack();
-  ok(response.allocationStack.length, "Got promise allocation stack.");
+  ok(response.length, "Got promise allocation stack.");
 
-  for (const stack of response.allocationStack) {
+  for (const stack of response) {
     is(stack.source.url, TAB_URL, "Got correct source URL.");
     is(
       stack.functionDisplayName,
       "makePromises",
       "Got correct function display name."
     );
     is(typeof stack.line, "number", "Expect stack line to be a number.");
     is(typeof stack.column, "number", "Expect stack column to be a number.");
--- a/devtools/server/tests/browser/browser_dbg_promises-chrome-allocation-stack.js
+++ b/devtools/server/tests/browser/browser_dbg_promises-chrome-allocation-stack.js
@@ -78,22 +78,22 @@ async function testGetAllocationStack(cl
   makePromises();
 
   const form = await onNewPromise;
   ok(form, "Found our promise p");
 
   const objectClient = new ObjectClient(client, form);
   ok(objectClient, "Got Object Client");
 
-  const response = await objectClient.getPromiseAllocationStack();
-  ok(response.allocationStack.length, "Got promise allocation stack.");
+  const allocationStack = await objectClient.getPromiseAllocationStack();
+  ok(allocationStack.length, "Got promise allocation stack.");
 
   for (let i = 0; i < STACK_DATA.length; i++) {
     const data = STACK_DATA[i];
-    const stack = response.allocationStack[i];
+    const stack = allocationStack[i];
 
     ok(stack.source.url.startsWith("chrome:"), "Got a chrome source URL");
     ok(stack.source.url.endsWith(SOURCE_URL), "Got correct source URL.");
     is(
       stack.functionDisplayName,
       data.functionDisplayName,
       "Got correct function display name."
     );
--- a/devtools/server/tests/browser/browser_dbg_promises-fulfillment-stack.js
+++ b/devtools/server/tests/browser/browser_dbg_promises-fulfillment-stack.js
@@ -64,21 +64,21 @@ async function testGetFulfillmentStack(t
   });
 
   const form = await onNewPromise;
   ok(form, "Found our promise p");
 
   const objectClient = new ObjectClient(target.client, form);
   ok(objectClient, "Got Object Client");
 
-  const response = await objectClient.getPromiseFulfillmentStack();
-  ok(response.fulfillmentStack.length, "Got promise allocation stack.");
+  const fulfillmentStack = await objectClient.getPromiseFulfillmentStack();
+  ok(fulfillmentStack.length, "Got promise allocation stack.");
 
   for (let i = 0; i < TEST_DATA.length; i++) {
-    const stack = response.fulfillmentStack[i];
+    const stack = fulfillmentStack[i];
     const data = TEST_DATA[i];
     is(stack.source.url, TAB_URL, "Got correct source URL.");
     is(
       stack.functionDisplayName,
       data.functionDisplayName,
       "Got correct function display name."
     );
     is(stack.line, data.line, "Got correct stack line number.");
--- a/devtools/server/tests/browser/browser_dbg_promises-rejection-stack.js
+++ b/devtools/server/tests/browser/browser_dbg_promises-rejection-stack.js
@@ -73,21 +73,21 @@ async function testGetRejectionStack(tab
   });
 
   const form = await onNewPromise;
   ok(form, "Found our promise p");
 
   const objectClient = new ObjectClient(target.client, form);
   ok(objectClient, "Got Object Client");
 
-  const response = await objectClient.getPromiseRejectionStack();
-  ok(response.rejectionStack.length, "Got promise allocation stack.");
+  const rejectionStack = await objectClient.getPromiseRejectionStack();
+  ok(rejectionStack.length, "Got promise allocation stack.");
 
   for (let i = 0; i < TEST_DATA.length; i++) {
-    const stack = response.rejectionStack[i];
+    const stack = rejectionStack[i];
     const data = TEST_DATA[i];
     is(stack.source.url, TAB_URL, "Got correct source URL.");
     is(
       stack.functionDisplayName,
       data.functionDisplayName,
       "Got correct function display name."
     );
     is(stack.line, data.line, "Got correct stack line number.");
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -271,19 +271,17 @@ function waitForProperty(dbg, property) 
 function setBreakpoint(threadFront, location) {
   dump("Setting breakpoint.\n");
   return threadFront.setBreakpoint(location, {});
 }
 
 function getPrototypeAndProperties(objClient) {
   dump("getting prototype and properties.\n");
 
-  return new Promise(resolve => {
-    objClient.getPrototypeAndProperties(response => resolve(response));
-  });
+  return objClient.getPrototypeAndProperties();
 }
 
 function dumpn(msg) {
   dump("DBG-TEST: " + msg + "\n");
 }
 
 function testExceptionHook(ex) {
   try {
--- a/devtools/server/tests/unit/test_framebindings-01.js
+++ b/devtools/server/tests/unit/test_framebindings-01.js
@@ -30,17 +30,17 @@ function run_test() {
       gThreadFront = threadFront;
       test_pause_frame();
     });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const bindings = packet.frame.environment.bindings;
     const args = bindings.arguments;
     const vars = bindings.variables;
 
     Assert.equal(args.length, 6);
     Assert.equal(args[0].number.value, 42);
     Assert.equal(args[1].bool.value, true);
     Assert.equal(args[2].string.value, "nasu");
@@ -52,32 +52,30 @@ function test_pause_frame() {
 
     Assert.equal(vars.a.value, 1);
     Assert.equal(vars.b.value, true);
     Assert.equal(vars.c.value.type, "object");
     Assert.equal(vars.c.value.class, "Object");
     Assert.ok(!!vars.c.value.actor);
 
     const objClient = gThreadFront.pauseGrip(vars.c.value);
-    objClient.getPrototypeAndProperties(function(response) {
-      Assert.equal(response.ownProperties.a.configurable, true);
-      Assert.equal(response.ownProperties.a.enumerable, true);
-      Assert.equal(response.ownProperties.a.writable, true);
-      Assert.equal(response.ownProperties.a.value, "a");
+    const response = await objClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.a.configurable, true);
+    Assert.equal(response.ownProperties.a.enumerable, true);
+    Assert.equal(response.ownProperties.a.writable, true);
+    Assert.equal(response.ownProperties.a.value, "a");
 
-      Assert.equal(response.ownProperties.b.configurable, true);
-      Assert.equal(response.ownProperties.b.enumerable, true);
-      Assert.equal(response.ownProperties.b.writable, true);
-      Assert.equal(response.ownProperties.b.value.type, "undefined");
-      Assert.equal(false, "class" in response.ownProperties.b.value);
+    Assert.equal(response.ownProperties.b.configurable, true);
+    Assert.equal(response.ownProperties.b.enumerable, true);
+    Assert.equal(response.ownProperties.b.writable, true);
+    Assert.equal(response.ownProperties.b.value.type, "undefined");
+    Assert.equal(false, "class" in response.ownProperties.b.value);
 
-      gThreadFront.resume().then(function() {
-        finishClient(gClient);
-      });
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     function stopMe(number, bool, string, null_, undef, object) {
       var a = 1;
       var b = true;
       var c = { a: "a", b: undefined };
--- a/devtools/server/tests/unit/test_framebindings-02.js
+++ b/devtools/server/tests/unit/test_framebindings-02.js
@@ -30,40 +30,38 @@ function run_test() {
       gThreadFront = threadFront;
       test_pause_frame();
     });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     let parentEnv = packet.frame.environment.parent;
     const bindings = parentEnv.bindings;
     const args = bindings.arguments;
     const vars = bindings.variables;
     Assert.notEqual(parentEnv, undefined);
     Assert.equal(args.length, 0);
     Assert.equal(vars.stopMe.value.type, "object");
     Assert.equal(vars.stopMe.value.class, "Function");
     Assert.ok(!!vars.stopMe.value.actor);
 
     // Skip the global lexical scope.
     parentEnv = parentEnv.parent.parent;
     Assert.notEqual(parentEnv, undefined);
     const objClient = gThreadFront.pauseGrip(parentEnv.object);
-    objClient.getPrototypeAndProperties(function(response) {
-      Assert.equal(response.ownProperties.Object.value.type, "object");
-      Assert.equal(response.ownProperties.Object.value.class, "Function");
-      Assert.ok(!!response.ownProperties.Object.value.actor);
+    const response = await objClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.Object.value.type, "object");
+    Assert.equal(response.ownProperties.Object.value.class, "Function");
+    Assert.ok(!!response.ownProperties.Object.value.actor);
 
-      gThreadFront.resume().then(function() {
-        finishClient(gClient);
-      });
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     function stopMe(number, bool, string, null_, undef, object) {
       var a = 1;
       var b = true;
       var c = { a: "a" };
--- a/devtools/server/tests/unit/test_framebindings-03.js
+++ b/devtools/server/tests/unit/test_framebindings-03.js
@@ -31,17 +31,17 @@ function run_test() {
       gThreadFront = threadFront;
       test_pause_frame();
     });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const env = packet.frame.environment;
     Assert.notEqual(env, undefined);
 
     const parentEnv = env.parent;
     Assert.notEqual(parentEnv, undefined);
 
     const bindings = parentEnv.bindings;
     const args = bindings.arguments;
@@ -49,26 +49,24 @@ function test_pause_frame() {
     Assert.equal(args.length, 1);
     Assert.equal(args[0].number.value, 10);
     Assert.equal(vars.r.value, 10);
     Assert.equal(vars.a.value, Math.PI * 100);
     Assert.equal(vars.arguments.value.class, "Arguments");
     Assert.ok(!!vars.arguments.value.actor);
 
     const objClient = gThreadFront.pauseGrip(env.object);
-    objClient.getPrototypeAndProperties(function(response) {
-      Assert.equal(response.ownProperties.PI.value, Math.PI);
-      Assert.equal(response.ownProperties.cos.value.type, "object");
-      Assert.equal(response.ownProperties.cos.value.class, "Function");
-      Assert.ok(!!response.ownProperties.cos.value.actor);
+    const response = await objClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.PI.value, Math.PI);
+    Assert.equal(response.ownProperties.cos.value.type, "object");
+    Assert.equal(response.ownProperties.cos.value.class, "Function");
+    Assert.ok(!!response.ownProperties.cos.value.actor);
 
-      gThreadFront.resume().then(function() {
-        finishClient(gClient);
-      });
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     function stopMe(number) {
       var a;
       var r = number;
       with (Math) {
--- a/devtools/server/tests/unit/test_framebindings-04.js
+++ b/devtools/server/tests/unit/test_framebindings-04.js
@@ -32,55 +32,52 @@ function run_test() {
       gThreadFront = threadFront;
       test_pause_frame();
     });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const env = packet.frame.environment;
     Assert.notEqual(env, undefined);
 
     const objClient = gThreadFront.pauseGrip(env.object);
-    objClient.getPrototypeAndProperties(function(response) {
-      Assert.equal(response.ownProperties.one.value, 1);
-      Assert.equal(response.ownProperties.two.value, 2);
-      Assert.equal(response.ownProperties.foo, undefined);
+    let response = await objClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.one.value, 1);
+    Assert.equal(response.ownProperties.two.value, 2);
+    Assert.equal(response.ownProperties.foo, undefined);
 
-      let parentEnv = env.parent;
-      Assert.notEqual(parentEnv, undefined);
+    let parentEnv = env.parent;
+    Assert.notEqual(parentEnv, undefined);
 
-      const parentClient = gThreadFront.pauseGrip(parentEnv.object);
-      parentClient.getPrototypeAndProperties(function(response) {
-        Assert.equal(response.ownProperties.PI.value, Math.PI);
-        Assert.equal(response.ownProperties.cos.value.type, "object");
-        Assert.equal(response.ownProperties.cos.value.class, "Function");
-        Assert.ok(!!response.ownProperties.cos.value.actor);
+    const parentClient = gThreadFront.pauseGrip(parentEnv.object);
+    response = await parentClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.PI.value, Math.PI);
+    Assert.equal(response.ownProperties.cos.value.type, "object");
+    Assert.equal(response.ownProperties.cos.value.class, "Function");
+    Assert.ok(!!response.ownProperties.cos.value.actor);
 
-        parentEnv = parentEnv.parent;
-        Assert.notEqual(parentEnv, undefined);
+    parentEnv = parentEnv.parent;
+    Assert.notEqual(parentEnv, undefined);
 
-        const bindings = parentEnv.bindings;
-        const args = bindings.arguments;
-        const vars = bindings.variables;
-        Assert.equal(args.length, 1);
-        Assert.equal(args[0].number.value, 10);
-        Assert.equal(vars.r.value, 10);
-        Assert.equal(vars.a.value, Math.PI * 100);
-        Assert.equal(vars.arguments.value.class, "Arguments");
-        Assert.ok(!!vars.arguments.value.actor);
-        Assert.equal(vars.foo.value, 2 * Math.PI);
+    const bindings = parentEnv.bindings;
+    const args = bindings.arguments;
+    const vars = bindings.variables;
+    Assert.equal(args.length, 1);
+    Assert.equal(args[0].number.value, 10);
+    Assert.equal(vars.r.value, 10);
+    Assert.equal(vars.a.value, Math.PI * 100);
+    Assert.equal(vars.arguments.value.class, "Arguments");
+    Assert.ok(!!vars.arguments.value.actor);
+    Assert.equal(vars.foo.value, 2 * Math.PI);
 
-        gThreadFront.resume().then(function() {
-          finishClient(gClient);
-        });
-      });
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     function stopMe(number) {
       var a, obj = { one: 1, two: 2 };
       var r = number;
       with (Math) {
--- a/devtools/server/tests/unit/test_framebindings-05.js
+++ b/devtools/server/tests/unit/test_framebindings-05.js
@@ -31,44 +31,41 @@ function run_test() {
       gThreadFront = threadFront;
       test_pause_frame();
     });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const env = packet.frame.environment;
     Assert.notEqual(env, undefined);
 
     const objClient = gThreadFront.pauseGrip(env.object);
-    objClient.getPrototypeAndProperties(function(response) {
-      Assert.equal(response.ownProperties.PI.value, Math.PI);
-      Assert.equal(response.ownProperties.cos.value.type, "object");
-      Assert.equal(response.ownProperties.cos.value.class, "Function");
-      Assert.ok(!!response.ownProperties.cos.value.actor);
+    let response = await objClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.PI.value, Math.PI);
+    Assert.equal(response.ownProperties.cos.value.type, "object");
+    Assert.equal(response.ownProperties.cos.value.class, "Function");
+    Assert.ok(!!response.ownProperties.cos.value.actor);
 
-      // Skip the global lexical scope.
-      const parentEnv = env.parent.parent;
-      Assert.notEqual(parentEnv, undefined);
+    // Skip the global lexical scope.
+    const parentEnv = env.parent.parent;
+    Assert.notEqual(parentEnv, undefined);
 
-      const parentClient = gThreadFront.pauseGrip(parentEnv.object);
-      parentClient.getPrototypeAndProperties(function(response) {
-        Assert.equal(response.ownProperties.a.value, Math.PI * 100);
-        Assert.equal(response.ownProperties.r.value, 10);
-        Assert.equal(response.ownProperties.Object.value.type, "object");
-        Assert.equal(response.ownProperties.Object.value.class, "Function");
-        Assert.ok(!!response.ownProperties.Object.value.actor);
+    const parentClient = gThreadFront.pauseGrip(parentEnv.object);
+    response = await parentClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.a.value, Math.PI * 100);
+    Assert.equal(response.ownProperties.r.value, 10);
+    Assert.equal(response.ownProperties.Object.value.type, "object");
+    Assert.equal(response.ownProperties.Object.value.class, "Function");
+    Assert.ok(!!response.ownProperties.Object.value.actor);
 
-        gThreadFront.resume().then(function() {
-          finishClient(gClient);
-        });
-      });
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   gDebuggee.eval(
     "var a, r = 10;\n" +
       "with (Math) {\n" +
       "  a = PI * r * r;\n" +
       "  debugger;\n" +
       "}"
--- a/devtools/server/tests/unit/test_framebindings-06.js
+++ b/devtools/server/tests/unit/test_framebindings-06.js
@@ -27,36 +27,35 @@ function run_test() {
       gThreadFront = threadFront;
       test_banana_environment();
     });
   });
   do_test_pending();
 }
 
 function test_banana_environment() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const env = packet.frame.environment;
     equal(env.type, "function");
     equal(env.function.name, "banana3");
     let parent = env.parent;
     equal(parent.type, "block");
     ok("banana3" in parent.bindings.variables);
     parent = parent.parent;
     equal(parent.type, "function");
     equal(parent.function.name, "banana2");
     parent = parent.parent;
     equal(parent.type, "block");
     ok("banana2" in parent.bindings.variables);
     parent = parent.parent;
     equal(parent.type, "function");
     equal(parent.function.name, "banana");
 
-    gThreadFront.resume().then(function() {
-      finishClient(gClient);
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   gDebuggee.eval(
     "function banana(x) {\n" +
       "  return function banana2(y) {\n" +
       "    return function banana3(z) {\n" +
       '      eval("");\n' +
       "      debugger;\n" +
--- a/devtools/server/tests/unit/test_framebindings-07.js
+++ b/devtools/server/tests/unit/test_framebindings-07.js
@@ -2,25 +2,25 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
 var gDebuggee;
 var gClient;
 var gThreadFront;
-const EnvironmentClient = require("devtools/shared/client/environment-client");
+const { EnvironmentFront } = require("devtools/shared/fronts/environment");
 
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
-// Test that the EnvironmentClient's getBindings() method works as expected.
+// Test that the EnvironmentFront's getBindings() method works as expected.
 function run_test() {
   initTestDebuggerServer();
   gDebuggee = addTestGlobal("test-bindings");
 
   gClient = new DebuggerClient(DebuggerServer.connectPipe());
   gClient.connect().then(function() {
     attachTestTabAndResume(gClient, "test-bindings", function(
       response,
@@ -30,44 +30,39 @@ function run_test() {
       gThreadFront = threadFront;
       test_banana_environment();
     });
   });
   do_test_pending();
 }
 
 function test_banana_environment() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const environment = packet.frame.environment;
     Assert.equal(environment.type, "function");
 
     const parent = environment.parent;
     Assert.equal(parent.type, "block");
 
     const grandpa = parent.parent;
     Assert.equal(grandpa.type, "function");
 
-    const envClient = new EnvironmentClient(gClient, environment);
-    envClient.getBindings(response => {
-      Assert.equal(response.bindings.arguments[0].z.value, "z");
+    const envClient = new EnvironmentFront(gClient, environment);
+    let response = await envClient.getBindings();
+    Assert.equal(response.arguments[0].z.value, "z");
 
-      const parentClient = new EnvironmentClient(gClient, parent);
-      parentClient.getBindings(response => {
-        Assert.equal(
-          response.bindings.variables.banana3.value.class,
-          "Function"
-        );
+    const parentClient = new EnvironmentFront(gClient, parent);
+    response = await parentClient.getBindings();
+    Assert.equal(response.variables.banana3.value.class, "Function");
 
-        const grandpaClient = new EnvironmentClient(gClient, grandpa);
-        grandpaClient.getBindings(response => {
-          Assert.equal(response.bindings.arguments[0].y.value, "y");
-          gThreadFront.resume().then(() => finishClient(gClient));
-        });
-      });
-    });
+    const grandpaClient = new EnvironmentFront(gClient, grandpa);
+    response = await grandpaClient.getBindings();
+    Assert.equal(response.arguments[0].y.value, "y");
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   gDebuggee.eval(
     "function banana(x) {\n" +
       "  return function banana2(y) {\n" +
       "    return function banana3(z) {\n" +
       '      eval("");\n' +
       "      debugger;\n" +
--- a/devtools/server/tests/unit/test_functiongrips-01.js
+++ b/devtools/server/tests/unit/test_functiongrips-01.js
@@ -30,74 +30,72 @@ function run_test() {
       gThreadFront = threadFront;
       test_named_function();
     });
   });
   do_test_pending();
 }
 
 function test_named_function() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const args = packet.frame.arguments;
 
     Assert.equal(args[0].class, "Function");
     Assert.equal(args[0].name, "stopMe");
     Assert.equal(args[0].displayName, "stopMe");
 
     const objClient = gThreadFront.pauseGrip(args[0]);
-    objClient.getParameterNames(function(response) {
-      Assert.equal(response.parameterNames.length, 1);
-      Assert.equal(response.parameterNames[0], "arg1");
+    const response = await objClient.getParameterNames();
+    Assert.equal(response.parameterNames.length, 1);
+    Assert.equal(response.parameterNames[0], "arg1");
 
-      gThreadFront.resume().then(test_inferred_name_function);
-    });
+    await gThreadFront.resume();
+    test_inferred_name_function();
   });
 
   gDebuggee.eval("stopMe(stopMe)");
 }
 
 function test_inferred_name_function() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const args = packet.frame.arguments;
 
     Assert.equal(args[0].class, "Function");
     // No name for an anonymous function, but it should have an inferred name.
     Assert.equal(args[0].name, undefined);
     Assert.equal(args[0].displayName, "m");
 
     const objClient = gThreadFront.pauseGrip(args[0]);
-    objClient.getParameterNames(function(response) {
-      Assert.equal(response.parameterNames.length, 3);
-      Assert.equal(response.parameterNames[0], "foo");
-      Assert.equal(response.parameterNames[1], "bar");
-      Assert.equal(response.parameterNames[2], "baz");
+    const response = await objClient.getParameterNames();
+    Assert.equal(response.parameterNames.length, 3);
+    Assert.equal(response.parameterNames[0], "foo");
+    Assert.equal(response.parameterNames[1], "bar");
+    Assert.equal(response.parameterNames[2], "baz");
 
-      gThreadFront.resume().then(test_anonymous_function);
-    });
+    await gThreadFront.resume();
+    test_anonymous_function();
   });
 
   gDebuggee.eval("var o = { m: function(foo, bar, baz) { } }; stopMe(o.m)");
 }
 
 function test_anonymous_function() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const args = packet.frame.arguments;
 
     Assert.equal(args[0].class, "Function");
     // No name for an anonymous function, and no inferred name, either.
     Assert.equal(args[0].name, undefined);
     Assert.equal(args[0].displayName, undefined);
 
     const objClient = gThreadFront.pauseGrip(args[0]);
-    objClient.getParameterNames(function(response) {
-      Assert.equal(response.parameterNames.length, 3);
-      Assert.equal(response.parameterNames[0], "foo");
-      Assert.equal(response.parameterNames[1], "bar");
-      Assert.equal(response.parameterNames[2], "baz");
+    const response = await objClient.getParameterNames();
+    Assert.equal(response.parameterNames.length, 3);
+    Assert.equal(response.parameterNames[0], "foo");
+    Assert.equal(response.parameterNames[1], "bar");
+    Assert.equal(response.parameterNames[2], "baz");
 
-      gThreadFront.resume().then(function() {
-        finishClient(gClient);
-      });
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   gDebuggee.eval("stopMe(function(foo, bar, baz) { })");
 }
--- a/devtools/server/tests/unit/test_objectgrips-01.js
+++ b/devtools/server/tests/unit/test_objectgrips-01.js
@@ -6,30 +6,30 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const args = packet.frame.arguments;
 
         Assert.equal(args[0].class, "Object");
 
         const objClient = threadFront.pauseGrip(args[0]);
-        objClient.getOwnPropertyNames(function(response) {
-          Assert.equal(response.ownPropertyNames.length, 3);
-          Assert.equal(response.ownPropertyNames[0], "a");
-          Assert.equal(response.ownPropertyNames[1], "b");
-          Assert.equal(response.ownPropertyNames[2], "c");
+        const response = await objClient.getOwnPropertyNames();
+        Assert.equal(response.ownPropertyNames.length, 3);
+        Assert.equal(response.ownPropertyNames[0], "a");
+        Assert.equal(response.ownPropertyNames[1], "b");
+        Assert.equal(response.ownPropertyNames[2], "c");
 
-          threadFront.resume().then(resolve);
-        });
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       debuggee.eval("stopMe({ a: 1, b: true, c: 'foo' })");
--- a/devtools/server/tests/unit/test_objectgrips-02.js
+++ b/devtools/server/tests/unit/test_objectgrips-02.js
@@ -7,34 +7,33 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const args = packet.frame.arguments;
 
         Assert.equal(args[0].class, "Object");
 
         const objClient = threadFront.pauseGrip(args[0]);
-        objClient.getPrototype(function(response) {
-          Assert.ok(response.prototype != undefined);
+        let response = await objClient.getPrototype();
+        Assert.ok(response.prototype != undefined);
 
-          const protoClient = threadFront.pauseGrip(response.prototype);
-          protoClient.getOwnPropertyNames(function(response) {
-            Assert.equal(response.ownPropertyNames.length, 2);
-            Assert.equal(response.ownPropertyNames[0], "b");
-            Assert.equal(response.ownPropertyNames[1], "c");
+        const protoClient = threadFront.pauseGrip(response.prototype);
+        response = await protoClient.getOwnPropertyNames();
+        Assert.equal(response.ownPropertyNames.length, 2);
+        Assert.equal(response.ownPropertyNames[0], "b");
+        Assert.equal(response.ownPropertyNames[1], "c");
 
-            threadFront.resume().then(resolve);
-          });
-        });
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       debuggee.eval(
--- a/devtools/server/tests/unit/test_objectgrips-03.js
+++ b/devtools/server/tests/unit/test_objectgrips-03.js
@@ -7,45 +7,43 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const args = packet.frame.arguments;
 
         Assert.equal(args[0].class, "Object");
 
         const objClient = threadFront.pauseGrip(args[0]);
-        objClient.getProperty("x", function(response) {
-          Assert.equal(response.descriptor.configurable, true);
-          Assert.equal(response.descriptor.enumerable, true);
-          Assert.equal(response.descriptor.writable, true);
-          Assert.equal(response.descriptor.value, 10);
-
-          objClient.getProperty("y", function(response) {
-            Assert.equal(response.descriptor.configurable, true);
-            Assert.equal(response.descriptor.enumerable, true);
-            Assert.equal(response.descriptor.writable, true);
-            Assert.equal(response.descriptor.value, "kaiju");
+        let response = await objClient.getProperty("x");
+        Assert.equal(response.descriptor.configurable, true);
+        Assert.equal(response.descriptor.enumerable, true);
+        Assert.equal(response.descriptor.writable, true);
+        Assert.equal(response.descriptor.value, 10);
 
-            objClient.getProperty("a", function(response) {
-              Assert.equal(response.descriptor.configurable, true);
-              Assert.equal(response.descriptor.enumerable, true);
-              Assert.equal(response.descriptor.get.type, "object");
-              Assert.equal(response.descriptor.get.class, "Function");
-              Assert.equal(response.descriptor.set.type, "undefined");
+        response = await objClient.getProperty("y");
+        Assert.equal(response.descriptor.configurable, true);
+        Assert.equal(response.descriptor.enumerable, true);
+        Assert.equal(response.descriptor.writable, true);
+        Assert.equal(response.descriptor.value, "kaiju");
 
-              threadFront.resume().then(resolve);
-            });
-          });
-        });
+        response = await objClient.getProperty("a");
+        Assert.equal(response.descriptor.configurable, true);
+        Assert.equal(response.descriptor.enumerable, true);
+        Assert.equal(response.descriptor.get.type, "object");
+        Assert.equal(response.descriptor.get.class, "Function");
+        Assert.equal(response.descriptor.set.type, "undefined");
+
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       debuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })");
--- a/devtools/server/tests/unit/test_objectgrips-04.js
+++ b/devtools/server/tests/unit/test_objectgrips-04.js
@@ -7,48 +7,47 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const args = packet.frame.arguments;
 
         Assert.equal(args[0].class, "Object");
 
         const objClient = threadFront.pauseGrip(args[0]);
-        objClient.getPrototypeAndProperties(function(response) {
-          Assert.equal(response.ownProperties.x.configurable, true);
-          Assert.equal(response.ownProperties.x.enumerable, true);
-          Assert.equal(response.ownProperties.x.writable, true);
-          Assert.equal(response.ownProperties.x.value, 10);
+        let response = await objClient.getPrototypeAndProperties();
+        Assert.equal(response.ownProperties.x.configurable, true);
+        Assert.equal(response.ownProperties.x.enumerable, true);
+        Assert.equal(response.ownProperties.x.writable, true);
+        Assert.equal(response.ownProperties.x.value, 10);
 
-          Assert.equal(response.ownProperties.y.configurable, true);
-          Assert.equal(response.ownProperties.y.enumerable, true);
-          Assert.equal(response.ownProperties.y.writable, true);
-          Assert.equal(response.ownProperties.y.value, "kaiju");
+        Assert.equal(response.ownProperties.y.configurable, true);
+        Assert.equal(response.ownProperties.y.enumerable, true);
+        Assert.equal(response.ownProperties.y.writable, true);
+        Assert.equal(response.ownProperties.y.value, "kaiju");
 
-          Assert.equal(response.ownProperties.a.configurable, true);
-          Assert.equal(response.ownProperties.a.enumerable, true);
-          Assert.equal(response.ownProperties.a.get.type, "object");
-          Assert.equal(response.ownProperties.a.get.class, "Function");
-          Assert.equal(response.ownProperties.a.set.type, "undefined");
-
-          Assert.ok(response.prototype != undefined);
+        Assert.equal(response.ownProperties.a.configurable, true);
+        Assert.equal(response.ownProperties.a.enumerable, true);
+        Assert.equal(response.ownProperties.a.get.type, "object");
+        Assert.equal(response.ownProperties.a.get.class, "Function");
+        Assert.equal(response.ownProperties.a.set.type, "undefined");
 
-          const protoClient = threadFront.pauseGrip(response.prototype);
-          protoClient.getOwnPropertyNames(function(response) {
-            Assert.ok(response.ownPropertyNames.toString != undefined);
+        Assert.ok(response.prototype != undefined);
 
-            threadFront.resume().then(resolve);
-          });
-        });
+        const protoClient = threadFront.pauseGrip(response.prototype);
+        response = await protoClient.getOwnPropertyNames();
+        Assert.ok(response.ownPropertyNames.toString != undefined);
+
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       debuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })");
--- a/devtools/server/tests/unit/test_objectgrips-05.js
+++ b/devtools/server/tests/unit/test_objectgrips-05.js
@@ -11,30 +11,31 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const obj1 = packet.frame.arguments[0];
         Assert.ok(obj1.frozen);
 
         const obj1Client = threadFront.pauseGrip(obj1);
         Assert.ok(obj1Client.isFrozen);
 
         const obj2 = packet.frame.arguments[1];
         Assert.ok(!obj2.frozen);
 
         const obj2Client = threadFront.pauseGrip(obj2);
         Assert.ok(!obj2Client.isFrozen);
 
-        threadFront.resume().then(resolve);
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       /* eslint-disable no-undef */
--- a/devtools/server/tests/unit/test_objectgrips-06.js
+++ b/devtools/server/tests/unit/test_objectgrips-06.js
@@ -11,30 +11,31 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const obj1 = packet.frame.arguments[0];
         Assert.ok(obj1.sealed);
 
         const obj1Client = threadFront.pauseGrip(obj1);
         Assert.ok(obj1Client.isSealed);
 
         const obj2 = packet.frame.arguments[1];
         Assert.ok(!obj2.sealed);
 
         const obj2Client = threadFront.pauseGrip(obj2);
         Assert.ok(!obj2Client.isSealed);
 
-        threadFront.resume().then(resolve);
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       /* eslint-disable no-undef */
--- a/devtools/server/tests/unit/test_objectgrips-07.js
+++ b/devtools/server/tests/unit/test_objectgrips-07.js
@@ -11,17 +11,17 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const [f, s, ne, e] = packet.frame.arguments;
         const [
           fClient,
           sClient,
           neClient,
           eClient,
         ] = packet.frame.arguments.map(a => threadFront.pauseGrip(a));
 
@@ -32,17 +32,18 @@ add_task(
         Assert.ok(!sClient.isExtensible);
 
         Assert.ok(!ne.extensible);
         Assert.ok(!neClient.isExtensible);
 
         Assert.ok(e.extensible);
         Assert.ok(eClient.isExtensible);
 
-        threadFront.resume().then(resolve);
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       /* eslint-disable no-undef */
--- a/devtools/server/tests/unit/test_objectgrips-08.js
+++ b/devtools/server/tests/unit/test_objectgrips-08.js
@@ -6,34 +6,34 @@
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee }) => {
     return new Promise(resolve => {
-      threadFront.once("paused", function(packet) {
+      threadFront.once("paused", async function(packet) {
         const args = packet.frame.arguments;
 
         Assert.equal(args[0].class, "Object");
 
         const objClient = threadFront.pauseGrip(args[0]);
-        objClient.getPrototypeAndProperties(function(response) {
-          const { a, b, c, d, e, f, g } = response.ownProperties;
-          testPropertyType(a, "Infinity");
-          testPropertyType(b, "-Infinity");
-          testPropertyType(c, "NaN");
-          testPropertyType(d, "-0");
-          testPropertyType(e, "BigInt");
-          testPropertyType(f, "BigInt");
-          testPropertyType(g, "BigInt");
+        const response = await objClient.getPrototypeAndProperties();
+        const { a, b, c, d, e, f, g } = response.ownProperties;
+        testPropertyType(a, "Infinity");
+        testPropertyType(b, "-Infinity");
+        testPropertyType(c, "NaN");
+        testPropertyType(d, "-0");
+        testPropertyType(e, "BigInt");
+        testPropertyType(f, "BigInt");
+        testPropertyType(g, "BigInt");
 
-          threadFront.resume().then(resolve);
-        });
+        await threadFront.resume();
+        resolve();
       });
 
       debuggee.eval(
         function stopMe(arg1) {
           debugger;
         }.toString()
       );
       debuggee.eval(`stopMe({
--- a/devtools/server/tests/unit/test_objectgrips-10.js
+++ b/devtools/server/tests/unit/test_objectgrips-10.js
@@ -30,52 +30,53 @@ function run_test() {
       gThreadFront = threadFront;
       test_object_grip();
     });
   });
   do_test_pending();
 }
 
 function test_object_grip() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const person = packet.frame.environment.bindings.variables.person;
 
     Assert.equal(person.value.class, "Object");
 
     const personClient = gThreadFront.pauseGrip(person.value);
-    personClient.getPrototypeAndProperties(response => {
-      Assert.equal(response.ownProperties.getName.value.class, "Function");
+    let response = await personClient.getPrototypeAndProperties();
+    Assert.equal(response.ownProperties.getName.value.class, "Function");
 
-      Assert.equal(response.ownProperties.getAge.value.class, "Function");
+    Assert.equal(response.ownProperties.getAge.value.class, "Function");
 
-      Assert.equal(response.ownProperties.getFoo.value.class, "Function");
+    Assert.equal(response.ownProperties.getFoo.value.class, "Function");
 
-      const getNameClient = gThreadFront.pauseGrip(
-        response.ownProperties.getName.value
-      );
-      const getAgeClient = gThreadFront.pauseGrip(
-        response.ownProperties.getAge.value
-      );
-      const getFooClient = gThreadFront.pauseGrip(
-        response.ownProperties.getFoo.value
-      );
-      getNameClient.getScope(response => {
-        Assert.equal(response.scope.bindings.arguments[0].name.value, "Bob");
+    const getNameClient = gThreadFront.pauseGrip(
+      response.ownProperties.getName.value
+    );
+    const getAgeClient = gThreadFront.pauseGrip(
+      response.ownProperties.getAge.value
+    );
+    const getFooClient = gThreadFront.pauseGrip(
+      response.ownProperties.getFoo.value
+    );
 
-        getAgeClient.getScope(response => {
-          Assert.equal(response.scope.bindings.arguments[1].age.value, 58);
-
-          getFooClient.getScope(response => {
-            Assert.equal(response.scope.bindings.variables.foo.value, 10);
+    response = await getNameClient.getScope();
+    let bindings = await response.scope.bindings();
+    Assert.equal(bindings.arguments[0].name.value, "Bob");
 
-            gThreadFront.resume().then(() => finishClient(gClient));
-          });
-        });
-      });
-    });
+    response = await getAgeClient.getScope();
+    bindings = await response.scope.bindings();
+    Assert.equal(bindings.arguments[1].age.value, 58);
+
+    response = await getFooClient.getScope();
+    bindings = await response.scope.bindings();
+    Assert.equal(bindings.variables.foo.value, 10);
+
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     var PersonFactory = function (name, age) {
       var foo = 10;
       return {
         getName: function () { return name; },
--- a/devtools/server/tests/unit/test_objectgrips-11.js
+++ b/devtools/server/tests/unit/test_objectgrips-11.js
@@ -34,29 +34,27 @@ function run_test() {
       gThreadFront = threadFront;
       test_object_grip();
     });
   });
   do_test_pending();
 }
 
 function test_object_grip() {
-  gThreadFront.once("paused", function(packet) {
+  gThreadFront.once("paused", async function(packet) {
     const args = packet.frame.arguments;
 
     const objClient = gThreadFront.pauseGrip(args[0]);
-    objClient.getOwnPropertyNames(function(response) {
-      const opn = response.ownPropertyNames;
-      Assert.equal(opn.length, 4);
-      opn.sort();
-      Assert.equal(opn[0], "columnNumber");
-      Assert.equal(opn[1], "fileName");
-      Assert.equal(opn[2], "lineNumber");
-      Assert.equal(opn[3], "message");
+    const response = await objClient.getOwnPropertyNames();
+    const opn = response.ownPropertyNames;
+    Assert.equal(opn.length, 4);
+    opn.sort();
+    Assert.equal(opn[0], "columnNumber");
+    Assert.equal(opn[1], "fileName");
+    Assert.equal(opn[2], "lineNumber");
+    Assert.equal(opn[3], "message");
 
-      gThreadFront.resume().then(function() {
-        finishClient(gClient);
-      });
-    });
+    await gThreadFront.resume();
+    finishClient(gClient);
   });
 
   gDebuggee.eval("stopMe(new TypeError('error message text'))");
 }
--- a/devtools/server/tests/unit/test_objectgrips-12.js
+++ b/devtools/server/tests/unit/test_objectgrips-12.js
@@ -158,26 +158,24 @@ function test_display_string() {
     },
   ];
 
   PromiseTestUtils.expectUncaughtRejection(/Error/);
 
   gThreadFront.once("paused", function(packet) {
     const args = packet.frame.arguments;
 
-    (function loop() {
+    (async function loop() {
       const objClient = gThreadFront.pauseGrip(args.pop());
-      objClient.getDisplayString(function({ displayString }) {
-        Assert.equal(displayString, testCases.pop().output);
-        if (args.length) {
-          loop();
-        } else {
-          gThreadFront.resume().then(function() {
-            finishClient(gClient);
-          });
-        }
-      });
+      const response = await objClient.getDisplayString();
+      Assert.equal(response.displayString, testCases.pop().output);
+      if (args.length) {
+        loop();
+      } else {
+        await gThreadFront.resume();
+        finishClient(gClient);
+      }
     })();
   });
 
   const inputs = testCases.map(({ input }) => input).join(",");
   gDebuggee.eval("stopMe(" + inputs + ")");
 }
--- a/devtools/server/tests/unit/test_objectgrips-13.js
+++ b/devtools/server/tests/unit/test_objectgrips-13.js
@@ -51,25 +51,24 @@ function eval_code() {
       "this.line0 = Error().lineNumber;",
       "function f() {}",
       "stopMe(f, {});",
     ].join("\n"),
     gDebuggee
   );
 }
 
-function test_definition_site(func, obj) {
-  func.getDefinitionSite(({ error, source, line, column }) => {
-    Assert.ok(!error);
-    Assert.equal(source.url, getFilePath("test_objectgrips-13.js"));
-    Assert.equal(line, gDebuggee.line0 + 1);
-    Assert.equal(column, 0);
+async function test_definition_site(func, obj) {
+  const response = await func.getDefinitionSite();
+  Assert.ok(!response.error);
+  Assert.equal(response.source.url, getFilePath("test_objectgrips-13.js"));
+  Assert.equal(response.line, gDebuggee.line0 + 1);
+  Assert.equal(response.column, 0);
 
-    test_bad_definition_site(obj);
-  });
+  test_bad_definition_site(obj);
 }
 
 function test_bad_definition_site(obj) {
   try {
     obj._client.request("definitionSite", () => Assert.ok(false));
   } catch (e) {
     gThreadFront.resume().then(() => finishClient(gClient));
   }
--- a/devtools/server/tests/unit/test_objectgrips-17.js
+++ b/devtools/server/tests/unit/test_objectgrips-17.js
@@ -196,17 +196,17 @@ function check_proxy_grip(debuggee, test
 }
 
 function check_proxy_slots(debuggee, testOptions, grip, proxySlots) {
   const { global } = testOptions;
 
   if (grip.class !== "Proxy") {
     strictEqual(
       proxySlots,
-      undefined,
+      null,
       "Slots can only be retrived for Proxy grips."
     );
   } else if (global === debuggee) {
     const { proxyTarget, proxyHandler } = proxySlots;
     strictEqual(proxyTarget.type, "object", "There is a [[ProxyTarget]] grip.");
     strictEqual(
       proxyHandler.type,
       "object",
--- a/devtools/server/tests/unit/test_objectgrips-18.js
+++ b/devtools/server/tests/unit/test_objectgrips-18.js
@@ -48,24 +48,17 @@ add_task(
         yield 1;
         yield 2;
       };
 
       stopMe(obj);
     `);
     });
 
-    async function check_enum_properties(response) {
-      info("Check enumProperties response");
-      ok(
-        response && Object.getOwnPropertyNames(response).includes("iterator"),
-        "The response object has an iterator property"
-      );
-
-      const { iterator } = response;
+    async function check_enum_properties(iterator) {
       equal(iterator.count, 10, "iterator.count has the expected value");
 
       info("Check iterator.slice response for all properties");
       let sliceResponse = await iterator.slice(0, iterator.count);
       ok(
         sliceResponse &&
           Object.getOwnPropertyNames(sliceResponse).includes("ownProperties"),
         "The response object has an ownProperties property"
@@ -108,24 +101,17 @@ add_task(
         "The response has the expected number of properties"
       );
       equal(names[0], `property_2_key`);
       equal(names[1], `property_3_key`);
       equal(ownProperties[names[0]].value, `property_2_value`);
       equal(ownProperties[names[1]].value, `property_3_value`);
     }
 
-    async function check_enum_symbols(response) {
-      info("Check enumProperties response");
-      ok(
-        response && Object.getOwnPropertyNames(response).includes("iterator"),
-        "The response object has an iterator property"
-      );
-
-      const { iterator } = response;
+    async function check_enum_symbols(iterator) {
       equal(iterator.count, 13, "iterator.count has the expected value");
 
       info("Check iterator.slice response for all symbols");
       let sliceResponse = await iterator.slice(0, iterator.count);
       ok(
         sliceResponse &&
           Object.getOwnPropertyNames(sliceResponse).includes("ownSymbols"),
         "The response object has an ownSymbols property"
--- a/devtools/server/tests/unit/test_objectgrips-20.js
+++ b/devtools/server/tests/unit/test_objectgrips-20.js
@@ -281,23 +281,17 @@ async function test_object_grip(
             ? evaledObject
             : JSON.stringify(evaledObject)
         });
       `);
     });
   });
 }
 
-async function check_enum_properties(response, expected = []) {
-  ok(
-    response && Object.getOwnPropertyNames(response).includes("iterator"),
-    "The response object has an iterator property"
-  );
-
-  const { iterator } = response;
+async function check_enum_properties(iterator, expected = []) {
   equal(
     iterator.count,
     expected.length,
     "iterator.count has the expected value"
   );
 
   info("Check iterator.slice response for all properties");
   const sliceResponse = await iterator.slice(0, iterator.count);
--- a/devtools/server/tests/unit/test_objectgrips-21.js
+++ b/devtools/server/tests/unit/test_objectgrips-21.js
@@ -241,31 +241,31 @@ async function test_unsafe_grips(
           response = await objClient.getPrototypeAndProperties();
           check_properties(response.ownProperties, data, isUnsafe);
           check_symbols(response.ownSymbols, data, isUnsafe);
           check_prototype(response.prototype, data, isUnsafe);
 
           response = await objClient.enumProperties({
             ignoreIndexedProperties: true,
           });
-          slice = await response.iterator.slice(0, response.iterator.count);
+          slice = await response.slice(0, response.count);
           check_properties(slice.ownProperties, data, isUnsafe);
 
           response = await objClient.enumProperties({});
-          slice = await response.iterator.slice(0, response.iterator.count);
+          slice = await response.slice(0, response.count);
           check_properties(slice.ownProperties, data, isUnsafe);
 
           response = await objClient.getOwnPropertyNames();
           check_property_names(response.ownPropertyNames, data, isUnsafe);
 
           response = await objClient.getProperty("x");
           check_property(response.descriptor, data, isUnsafe);
 
           response = await objClient.enumSymbols();
-          slice = await response.iterator.slice(0, response.iterator.count);
+          slice = await response.slice(0, response.count);
           check_symbol_names(slice.ownSymbols, data, isUnsafe);
 
           response = await objClient.getProperty(Symbol.for("x"));
           check_symbol(response.descriptor, data, isUnsafe);
 
           response = await objClient.getPrototype();
           check_prototype(response.prototype, data, isUnsafe);
 
--- a/devtools/server/tests/unit/test_objectgrips-22.js
+++ b/devtools/server/tests/unit/test_objectgrips-22.js
@@ -10,17 +10,17 @@ registerCleanupFunction(() => {
 });
 
 add_task(
   threadFrontTest(async ({ threadFront, debuggee, client }) => {
     await new Promise(function(resolve) {
       threadFront.once("paused", async function(packet) {
         const [grip] = packet.frame.arguments;
         const objClient = threadFront.pauseGrip(grip);
-        const { iterator } = await objClient.enumSymbols();
+        const iterator = await objClient.enumSymbols();
         const { ownSymbols } = await iterator.slice(0, iterator.count);
 
         strictEqual(ownSymbols.length, 1, "There is 1 symbol property.");
         const { name, descriptor } = ownSymbols[0];
         strictEqual(name, "Symbol(sym)", "Got right symbol name.");
         deepEqual(
           descriptor,
           {
--- a/devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
+++ b/devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
@@ -44,17 +44,17 @@ async function test_object_grip(debuggee
   const method = threadFront.pauseGrip(
     (await objClient.getPropertyValue("method", null)).value.return
   );
 
   try {
     await method.apply(obj, []);
     Assert.ok(false, "expected exception");
   } catch (err) {
-    Assert.equal(err.message, "debugee object is not callable");
+    Assert.ok(!!err.match(/debugee object is not callable/));
   }
 }
 
 function eval_and_resume(debuggee, threadFront, code, callback) {
   return new Promise((resolve, reject) => {
     wait_for_pause(threadFront, callback).then(resolve, reject);
 
     // This synchronously blocks until 'threadFront.resume()' above runs
--- a/devtools/server/tests/unit/test_pauselifetime-03.js
+++ b/devtools/server/tests/unit/test_pauselifetime-03.js
@@ -41,33 +41,35 @@ function test_pause_frame() {
     Assert.ok(!!objActor);
 
     const objClient = gThreadFront.pauseGrip(args[0]);
     Assert.ok(objClient.valid);
 
     // Make a bogus request to the grip actor.  Should get
     // unrecognized-packet-type (and not no-such-actor).
     try {
-      await gClient.request({ to: objActor, type: "bogusRequest" });
+      const objFront = gClient.getFrontByID(objActor);
+      await objFront.request({ to: objActor, type: "bogusRequest" });
       ok(false, "bogusRequest should throw");
     } catch (e) {
       ok(true, "bogusRequest thrown");
-      Assert.equal(e.error, "unrecognizedPacketType");
+      Assert.ok(!!e.match(/unrecognizedPacketType/));
     }
     Assert.ok(objClient.valid);
 
     gThreadFront.resume().then(async function() {
       // Now that we've resumed, should get no-such-actor for the
       // same request.
       try {
-        await gClient.request({ to: objActor, type: "bogusRequest" });
+        const objFront = gClient.getFrontByID(objActor);
+        await objFront.request({ to: objActor, type: "bogusRequest" });
         ok(false, "bogusRequest should throw");
       } catch (e) {
         ok(true, "bogusRequest thrown");
-        Assert.equal(e.error, "noSuchActor");
+        Assert.ok(!!e.match(/noSuchActor/));
       }
       Assert.ok(!objClient.valid);
       finishClient(gClient);
     });
   });
 
   gDebuggee.eval(
     "(" +
--- a/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
+++ b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
@@ -63,17 +63,17 @@ async function testGetDependentPromises(
   ok(grip, "Found our promise p.");
 
   const objectClient = new ObjectClient(client, grip);
   ok(objectClient, "Got Object Client.");
 
   // Get the dependent promises for promise p and assert that the list of
   // dependent promises is correct
   await new Promise(resolve => {
-    objectClient.getDependentPromises(response => {
+    objectClient.getDependentPromises().then(response => {
       const dependentNames = response.promises.map(
         p => p.preview.ownProperties.name.value
       );
       const expectedDependentNames = ["q", "r"];
 
       equal(
         dependentNames.length,
         expectedDependentNames.length,
--- a/devtools/server/tests/unit/test_threadlifetime-02.js
+++ b/devtools/server/tests/unit/test_threadlifetime-02.js
@@ -39,36 +39,38 @@ function test_thread_lifetime() {
 
     // Create a thread-lifetime actor for this object.
     const response = await gClient.request({
       to: pauseGrip.actor,
       type: "threadGrip",
     });
     // Successful promotion won't return an error.
     Assert.equal(response.error, undefined);
-    gThreadFront.once("paused", function(packet) {
+    gThreadFront.once("paused", async function(packet) {
       // Verify that the promoted actor is returned again.
       Assert.equal(pauseGrip.actor, packet.frame.arguments[0].actor);
       // Now that we've resumed, release the thread-lifetime grip.
-      gClient.release(pauseGrip.actor, async function(response) {
-        try {
-          await gClient.request(
-            { to: pauseGrip.actor, type: "bogusRequest" },
-            function(response) {
-              Assert.equal(response.error, "noSuchActor");
-              gThreadFront.resume().then(function() {
-                finishClient(gClient);
-              });
-            }
-          );
-          ok(false, "bogusRequest should throw");
-        } catch (e) {
-          ok(true, "bogusRequest thrown");
-        }
-      });
+      const objFront = new ObjectClient(gClient, pauseGrip);
+      await objFront.release();
+      const objFront2 = new ObjectClient(gClient, pauseGrip);
+
+      try {
+        await objFront2
+          .request({ to: pauseGrip.actor, type: "bogusRequest" })
+          .catch(function(response) {
+            Assert.ok(!!response.match(/noSuchActor/));
+            gThreadFront.resume().then(function() {
+              finishClient(gClient);
+            });
+            throw new Error();
+          });
+        ok(false, "bogusRequest should throw");
+      } catch (e) {
+        ok(true, "bogusRequest thrown");
+      }
     });
     gThreadFront.resume();
   });
 
   gDebuggee.eval(
     "(" +
       function() {
         function stopMe(arg1) {
--- a/devtools/shared/client/moz.build
+++ b/devtools/shared/client/moz.build
@@ -4,15 +4,12 @@
 # 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/.
 
 DevToolsModules(
     'connection-manager.js',
     'constants.js',
     'debugger-client.js',
     'deprecated-thread-client.js',
-    'environment-client.js',
     'event-source.js',
     'long-string-client.js',
     'object-client.js',
-    'property-iterator-client.js',
-    'symbol-iterator-client.js',
 )
--- a/devtools/shared/client/object-client.js
+++ b/devtools/shared/client/object-client.js
@@ -1,408 +1,252 @@
 /* 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/. */
 
 "use strict";
 
+const { objectSpec } = require("devtools/shared/specs/object");
 const {
-  arg,
-  DebuggerClient,
-} = require("devtools/shared/client/debugger-client");
-loader.lazyRequireGetter(
-  this,
-  "PropertyIteratorClient",
-  "devtools/shared/client/property-iterator-client"
-);
-loader.lazyRequireGetter(
-  this,
-  "SymbolIteratorClient",
-  "devtools/shared/client/symbol-iterator-client"
-);
+  FrontClassWithSpec,
+  registerFront,
+} = require("devtools/shared/protocol");
 
 /**
  * Grip clients are used to retrieve information about the relevant object.
  *
  * @param client DebuggerClient
  *        The debugger client parent.
  * @param grip object
  *        A pause-lifetime object grip returned by the protocol.
  */
-function ObjectClient(client, grip) {
-  this._grip = grip;
-  this._client = client;
-  this.request = this._client.request;
-}
+class ObjectClient extends FrontClassWithSpec(objectSpec) {
+  constructor(client, grip) {
+    super(client);
+    this._grip = grip;
+    this._client = client;
+    this.valid = true;
+    this.actorID = this._grip.actor;
 
-ObjectClient.prototype = {
+    this.manage(this);
+  }
+
   get actor() {
-    return this._grip.actor;
-  },
+    return this.actorID;
+  }
+
   get _transport() {
     return this._client._transport;
-  },
-
-  valid: true,
+  }
 
   get isFrozen() {
     return this._grip.frozen;
-  },
+  }
+
   get isSealed() {
     return this._grip.sealed;
-  },
+  }
+
   get isExtensible() {
     return this._grip.extensible;
-  },
-
-  threadGrip: DebuggerClient.requester({
-    type: "threadGrip",
-  }),
+  }
 
-  getDefinitionSite: DebuggerClient.requester(
-    {
-      type: "definitionSite",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class != "Function") {
-          throw new Error(
-            "getDefinitionSite is only valid for function grips."
-          );
-        }
-        return packet;
-      },
+  getDefinitionSite() {
+    if (this._grip.class != "Function") {
+      console.error("getDefinitionSite is only valid for function grips.");
+      return null;
     }
-  ),
+    return super.definitionSite();
+  }
 
   /**
    * Request the names of a function's formal parameters.
-   *
-   * @param onResponse function
-   *        Called with an object of the form:
-   *        { parameterNames:[<parameterName>, ...] }
-   *        where each <parameterName> is the name of a parameter.
    */
-  getParameterNames: DebuggerClient.requester(
-    {
-      type: "parameterNames",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class !== "Function") {
-          throw new Error(
-            "getParameterNames is only valid for function grips."
-          );
-        }
-        return packet;
-      },
+  getParameterNames() {
+    if (this._grip.class !== "Function") {
+      console.error("getParameterNames is only valid for function grips.");
+      return null;
     }
-  ),
+    return super.parameterNames();
+  }
 
   /**
    * Request the names of the properties defined on the object and not its
    * prototype.
-   *
-   * @param onResponse function Called with the request's response.
    */
-  getOwnPropertyNames: DebuggerClient.requester({
-    type: "ownPropertyNames",
-  }),
+  getOwnPropertyNames() {
+    return super.ownPropertyNames();
+  }
 
   /**
    * Request the prototype and own properties of the object.
-   *
-   * @param onResponse function Called with the request's response.
    */
-  getPrototypeAndProperties: DebuggerClient.requester({
-    type: "prototypeAndProperties",
-  }),
+  getPrototypeAndProperties() {
+    return super.prototypeAndProperties();
+  }
 
   /**
-   * Request a PropertyIteratorClient instance to ease listing
+   * Request a PropertyIteratorFront instance to ease listing
    * properties for this object.
    *
    * @param options Object
    *        A dictionary object with various boolean attributes:
    *        - ignoreIndexedProperties Boolean
    *          If true, filters out Array items.
    *          e.g. properties names between `0` and `object.length`.
    *        - ignoreNonIndexedProperties Boolean
    *          If true, filters out items that aren't array items
    *          e.g. properties names that are not a number between `0`
    *          and `object.length`.
    *        - sort Boolean
    *          If true, the iterator will sort the properties by name
    *          before dispatching them.
-   * @param onResponse function Called with the client instance.
    */
-  enumProperties: DebuggerClient.requester(
-    {
-      type: "enumProperties",
-      options: arg(0),
-    },
-    {
-      after: function(response) {
-        if (response.iterator) {
-          return {
-            iterator: new PropertyIteratorClient(
-              this._client,
-              response.iterator
-            ),
-          };
-        }
-        return response;
-      },
-    }
-  ),
+  enumProperties(options) {
+    return super.enumProperties(options);
+  }
 
   /**
-   * Request a PropertyIteratorClient instance to enumerate entries in a
+   * Request a PropertyIteratorFront instance to enumerate entries in a
    * Map/Set-like object.
-   *
-   * @param onResponse function Called with the request's response.
    */
-  enumEntries: DebuggerClient.requester(
-    {
-      type: "enumEntries",
-    },
-    {
-      before: function(packet) {
-        if (
-          !["Map", "WeakMap", "Set", "WeakSet", "Storage"].includes(
-            this._grip.class
-          )
-        ) {
-          throw new Error(
-            "enumEntries is only valid for Map/Set/Storage-like grips."
-          );
-        }
-        return packet;
-      },
-      after: function(response) {
-        if (response.iterator) {
-          return {
-            iterator: new PropertyIteratorClient(
-              this._client,
-              response.iterator
-            ),
-          };
-        }
-        return response;
-      },
+  enumEntries() {
+    if (
+      !["Map", "WeakMap", "Set", "WeakSet", "Storage"].includes(
+        this._grip.class
+      )
+    ) {
+      console.error(
+        "enumEntries is only valid for Map/Set/Storage-like grips."
+      );
+      return null;
     }
-  ),
+    return super.enumEntries();
+  }
 
   /**
-   * Request a SymbolIteratorClient instance to enumerate symbols in an object.
-   *
-   * @param onResponse function Called with the request's response.
+   * Request a SymbolIteratorFront instance to enumerate symbols in an object.
    */
-  enumSymbols: DebuggerClient.requester(
-    {
-      type: "enumSymbols",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.type !== "object") {
-          throw new Error("enumSymbols is only valid for objects grips.");
-        }
-        return packet;
-      },
-      after: function(response) {
-        if (response.iterator) {
-          return {
-            iterator: new SymbolIteratorClient(this._client, response.iterator),
-          };
-        }
-        return response;
-      },
+  enumSymbols() {
+    if (this._grip.type !== "object") {
+      console.error("enumSymbols is only valid for objects grips.");
+      return null;
     }
-  ),
+    return super.enumSymbols();
+  }
 
   /**
    * Request the property descriptor of the object's specified property.
    *
    * @param name string The name of the requested property.
-   * @param onResponse function Called with the request's response.
    */
-  getProperty: DebuggerClient.requester({
-    type: "property",
-    name: arg(0),
-  }),
+  getProperty(name) {
+    return super.property(name);
+  }
 
   /**
    * Request the value of the object's specified property.
    *
    * @param name string The name of the requested property.
    * @param receiverId string|null The actorId of the receiver to be used for getters.
-   * @param onResponse function Called with the request's response.
    */
-  getPropertyValue: DebuggerClient.requester({
-    type: "propertyValue",
-    name: arg(0),
-    receiverId: arg(1),
-  }),
+  getPropertyValue(name, receiverId) {
+    return super.propertyValue(name, receiverId);
+  }
 
   /**
    * Request the prototype of the object.
-   *
-   * @param onResponse function Called with the request's response.
    */
-  getPrototype: DebuggerClient.requester({
-    type: "prototype",
-  }),
-
-  /**
-   * Evaluate a callable object with context and arguments.
-   *
-   * @param context any The value to use as the function context.
-   * @param arguments Array<any> An array of values to use as the function's arguments.
-   * @param onResponse function Called with the request's response.
-   */
-  apply: DebuggerClient.requester({
-    type: "apply",
-    context: arg(0),
-    arguments: arg(1),
-  }),
+  getPrototype() {
+    return super.prototype();
+  }
 
   /**
    * Request the display string of the object.
-   *
-   * @param onResponse function Called with the request's response.
    */
-  getDisplayString: DebuggerClient.requester({
-    type: "displayString",
-  }),
+  getDisplayString() {
+    return super.displayString();
+  }
 
   /**
    * Request the scope of the object.
-   *
-   * @param onResponse function Called with the request's response.
    */
-  getScope: DebuggerClient.requester(
-    {
-      type: "scope",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class !== "Function") {
-          throw new Error("scope is only valid for function grips.");
-        }
-        return packet;
-      },
+  getScope() {
+    if (this._grip.class !== "Function") {
+      console.error("scope is only valid for function grips.");
+      return null;
     }
-  ),
+    return super.scope();
+  }
 
   /**
    * Request the promises directly depending on the current promise.
    */
-  getDependentPromises: DebuggerClient.requester(
-    {
-      type: "dependentPromises",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class !== "Promise") {
-          throw new Error(
-            "getDependentPromises is only valid for promise " + "grips."
-          );
-        }
-        return packet;
-      },
+  getDependentPromises() {
+    if (this._grip.class !== "Promise") {
+      console.error("getDependentPromises is only valid for promise grips.");
+      return null;
     }
-  ),
+    return super.dependentPromises();
+  }
 
   /**
    * Request the stack to the promise's allocation point.
    */
-  getPromiseAllocationStack: DebuggerClient.requester(
-    {
-      type: "allocationStack",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class !== "Promise") {
-          throw new Error(
-            "getAllocationStack is only valid for promise grips."
-          );
-        }
-        return packet;
-      },
+  getPromiseAllocationStack() {
+    if (this._grip.class !== "Promise") {
+      console.error(
+        "getPromiseAllocationStack is only valid for promise grips."
+      );
+      return null;
     }
-  ),
+    return super.allocationStack();
+  }
 
   /**
    * Request the stack to the promise's fulfillment point.
    */
-  getPromiseFulfillmentStack: DebuggerClient.requester(
-    {
-      type: "fulfillmentStack",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class !== "Promise") {
-          throw new Error(
-            "getPromiseFulfillmentStack is only valid for " + "promise grips."
-          );
-        }
-        return packet;
-      },
+  getPromiseFulfillmentStack() {
+    if (this._grip.class !== "Promise") {
+      console.error(
+        "getPromiseFulfillmentStack is only valid for promise grips."
+      );
+      return null;
     }
-  ),
+    return super.fulfillmentStack();
+  }
 
   /**
    * Request the stack to the promise's rejection point.
    */
-  getPromiseRejectionStack: DebuggerClient.requester(
-    {
-      type: "rejectionStack",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class !== "Promise") {
-          throw new Error(
-            "getPromiseRejectionStack is only valid for " + "promise grips."
-          );
-        }
-        return packet;
-      },
+  getPromiseRejectionStack() {
+    if (this._grip.class !== "Promise") {
+      console.error(
+        "getPromiseRejectionStack is only valid for promise grips."
+      );
+      return null;
     }
-  ),
+    return super.rejectionStack();
+  }
 
   /**
    * Request the target and handler internal slots of a proxy.
    */
-  getProxySlots: DebuggerClient.requester(
-    {
-      type: "proxySlots",
-    },
-    {
-      before: function(packet) {
-        if (this._grip.class !== "Proxy") {
-          throw new Error("getProxySlots is only valid for proxy grips.");
-        }
-        return packet;
-      },
-      after: function(response) {
-        // Before Firefox 68 (bug 1392760), the proxySlots request didn't exist.
-        // The proxy target and handler were directly included in the grip.
-        if (response.error === "unrecognizedPacketType") {
-          const { proxyTarget, proxyHandler } = this._grip;
-          return { proxyTarget, proxyHandler };
-        }
-        return response;
-      },
+  getProxySlots() {
+    if (this._grip.class !== "Proxy") {
+      console.error("getProxySlots is only valid for proxy grips.");
+      return null;
     }
-  ),
-  addWatchpoint: DebuggerClient.requester({
-    type: "addWatchpoint",
-    property: arg(0),
-    label: arg(1),
-    watchpointType: arg(2),
-  }),
-  removeWatchpoint: DebuggerClient.requester({
-    type: "removeWatchpoint",
-    property: arg(0),
-  }),
-};
+
+    const response = super.proxySlots();
+    // Before Firefox 68 (bug 1392760), the proxySlots request didn't exist.
+    // The proxy target and handler were directly included in the grip.
+    if (response.error === "unrecognizedPacketType") {
+      const { proxyTarget, proxyHandler } = this._grip;
+      return { proxyTarget, proxyHandler };
+    }
+
+    return response;
+  }
+}
 
 module.exports = ObjectClient;
+registerFront(ObjectClient);
rename from devtools/shared/client/environment-client.js
rename to devtools/shared/fronts/environment.js
--- a/devtools/shared/client/environment-client.js
+++ b/devtools/shared/fronts/environment.js
@@ -1,53 +1,48 @@
 /* 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/. */
 
 "use strict";
 
 const {
-  arg,
-  DebuggerClient,
-} = require("devtools/shared/client/debugger-client");
+  FrontClassWithSpec,
+  registerFront,
+} = require("devtools/shared/protocol");
+const { environmentSpec } = require("devtools/shared/specs/environment");
 
 /**
- * Environment clients are used to manipulate the lexical environment actors.
+ * Environment fronts are used to manipulate the lexical environment actors.
  *
  * @param client DebuggerClient
  *        The debugger client parent.
  * @param form Object
  *        The form sent across the remote debugging protocol.
  */
-function EnvironmentClient(client, form) {
-  this._client = client;
-  this._form = form;
-  this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
+class EnvironmentFront extends FrontClassWithSpec(environmentSpec) {
+  constructor(client, form) {
+    super(client, form);
+    this._client = client;
+    if (form) {
+      this._form = form;
+      this.actorID = form.actor;
+      this.manage(this);
+    }
+  }
 
-EnvironmentClient.prototype = {
   get actor() {
     return this._form.actor;
-  },
+  }
   get _transport() {
     return this._client._transport;
-  },
+  }
 
   /**
    * Fetches the bindings introduced by this lexical environment.
    */
-  getBindings: DebuggerClient.requester({
-    type: "bindings",
-  }),
+  getBindings() {
+    return super.bindings();
+  }
+}
 
-  /**
-   * Changes the value of the identifier whose name is name (a string) to that
-   * represented by value (a grip).
-   */
-  assign: DebuggerClient.requester({
-    type: "assign",
-    name: arg(0),
-    value: arg(1),
-  }),
-};
-
-module.exports = EnvironmentClient;
+exports.EnvironmentFront = EnvironmentFront;
+registerFront(EnvironmentFront);
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -16,32 +16,35 @@ DevToolsModules(
     'accessibility.js',
     'actor-registry.js',
     'animation.js',
     'array-buffer.js',
     'changes.js',
     'css-properties.js',
     'device.js',
     'emulation.js',
+    'environment.js',
     'framerate.js',
     'highlighters.js',
     'inspector.js',
     'layout.js',
     'manifest.js',
     'memory.js',
     'node.js',
     'perf.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
+    'property-iterator.js',
     'reflow.js',
     'root.js',
     'screenshot.js',
     'source.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
+    'symbol-iterator.js',
     'thread.js',
     'webconsole.js',
-    'websocket.js',
+    'websocket.js'
 )
rename from devtools/shared/client/property-iterator-client.js
rename to devtools/shared/fronts/property-iterator.js
--- a/devtools/shared/client/property-iterator-client.js
+++ b/devtools/shared/fronts/property-iterator.js
@@ -1,93 +1,68 @@
 /* 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/. */
 
 "use strict";
 
 const {
-  arg,
-  DebuggerClient,
-} = require("devtools/shared/client/debugger-client");
+  FrontClassWithSpec,
+  registerFront,
+} = require("devtools/shared/protocol");
+const {
+  propertyIteratorSpec,
+} = require("devtools/shared/specs/property-iterator");
 
 /**
- * A PropertyIteratorClient provides a way to access to property names and
+ * A PropertyIteratorFront provides a way to access to property names and
  * values of an object efficiently, slice by slice.
  * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
+ * this is controled while creating the PropertyIteratorFront
  * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- *        The debugger client parent.
- * @param grip Object
- *        A PropertyIteratorActor grip returned by the protocol via
- *        BrowsingContextTargetActor.enumProperties request.
  */
-function PropertyIteratorClient(client, grip) {
-  this._grip = grip;
-  this._client = client;
-  this.request = this._client.request;
-}
+class PropertyIteratorFront extends FrontClassWithSpec(propertyIteratorSpec) {
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
+    this._client = client;
+  }
 
-PropertyIteratorClient.prototype = {
   get actor() {
     return this._grip.actor;
-  },
+  }
 
   /**
    * Get the total number of properties available in the iterator.
    */
   get count() {
     return this._grip.count;
-  },
+  }
 
   /**
    * Get one or more property names that correspond to the positions in the
    * indexes parameter.
    *
    * @param indexes Array
    *        An array of property indexes.
-   * @param callback Function
-   *        The function called when we receive the property names.
    */
-  names: DebuggerClient.requester(
-    {
-      type: "names",
-      indexes: arg(0),
-    },
-    {}
-  ),
+  names(indexes) {
+    return super.names({ indexes });
+  }
 
   /**
    * Get a set of following property value(s).
    *
    * @param start Number
    *        The index of the first property to fetch.
    * @param count Number
    *        The number of properties to fetch.
-   * @param callback Function
-   *        The function called when we receive the property values.
    */
-  slice: DebuggerClient.requester(
-    {
-      type: "slice",
-      start: arg(0),
-      count: arg(1),
-    },
-    {}
-  ),
+  slice(start, count) {
+    return super.slice({ start, count });
+  }
 
-  /**
-   * Get all the property values.
-   *
-   * @param callback Function
-   *        The function called when we receive the property values.
-   */
-  all: DebuggerClient.requester(
-    {
-      type: "all",
-    },
-    {}
-  ),
-};
+  form(form) {
+    this._grip = form;
+  }
+}
 
-module.exports = PropertyIteratorClient;
+exports.PropertyIteratorFront = PropertyIteratorFront;
+registerFront(PropertyIteratorFront);
rename from devtools/shared/client/symbol-iterator-client.js
rename to devtools/shared/fronts/symbol-iterator.js
--- a/devtools/shared/client/symbol-iterator-client.js
+++ b/devtools/shared/fronts/symbol-iterator.js
@@ -1,73 +1,61 @@
 /* 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/. */
 
 "use strict";
 
 const {
-  arg,
-  DebuggerClient,
-} = require("devtools/shared/client/debugger-client");
+  FrontClassWithSpec,
+  registerFront,
+} = require("devtools/shared/protocol");
+const { symbolIteratorSpec } = require("devtools/shared/specs/symbol-iterator");
 
 /**
- * A SymbolIteratorClient provides a way to access to symbols
+ * A SymbolIteratorFront provides a way to access to symbols
  * of an object efficiently, slice by slice.
  *
  * @param client DebuggerClient
  *        The debugger client parent.
  * @param grip Object
  *        A SymbolIteratorActor grip returned by the protocol via
  *        BrowsingContextTargetActor.enumSymbols request.
  */
-function SymbolIteratorClient(client, grip) {
-  this._grip = grip;
-  this._client = client;
-  this.request = this._client.request;
-}
+class SymbolIteratorFront extends FrontClassWithSpec(symbolIteratorSpec) {
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
+    this._client = client;
+  }
 
-SymbolIteratorClient.prototype = {
   get actor() {
     return this._grip.actor;
-  },
+  }
 
   /**
    * Get the total number of symbols available in the iterator.
    */
   get count() {
     return this._grip.count;
-  },
+  }
 
   /**
    * Get a set of following symbols.
    *
    * @param start Number
    *        The index of the first symbol to fetch.
    * @param count Number
    *        The number of symbols to fetch.
    * @param callback Function
    *        The function called when we receive the symbols.
    */
-  slice: DebuggerClient.requester(
-    {
-      type: "slice",
-      start: arg(0),
-      count: arg(1),
-    },
-    {}
-  ),
+  slice(start, count) {
+    const argumentObject = { start, count };
+    return super.slice(argumentObject);
+  }
 
-  /**
-   * Get all the symbols.
-   *
-   * @param callback Function
-   *        The function called when we receive the symbols.
-   */
-  all: DebuggerClient.requester(
-    {
-      type: "all",
-    },
-    {}
-  ),
-};
+  form(form) {
+    this._grip = form;
+  }
+}
 
-module.exports = SymbolIteratorClient;
+exports.SymbolIteratorFront = SymbolIteratorFront;
+registerFront(SymbolIteratorFront);
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -72,21 +72,20 @@ const Types = (exports.__TypesForTests =
     spec: "devtools/shared/specs/device",
     front: "devtools/shared/fronts/device",
   },
   {
     types: ["emulation"],
     spec: "devtools/shared/specs/emulation",
     front: "devtools/shared/fronts/emulation",
   },
-  /* environment has old fashion client and no front */
   {
     types: ["environment"],
     spec: "devtools/shared/specs/environment",
-    front: null,
+    front: "devtools/shared/fronts/environment",
   },
   /* frame has old fashion client and no front */
   {
     types: ["frame"],
     spec: "devtools/shared/specs/frame",
     front: null,
   },
   {
@@ -169,17 +168,17 @@ const Types = (exports.__TypesForTests =
   {
     types: ["promises"],
     spec: "devtools/shared/specs/promises",
     front: "devtools/shared/fronts/promises",
   },
   {
     types: ["propertyIterator"],
     spec: "devtools/shared/specs/property-iterator",
-    front: null,
+    front: "devtools/shared/fronts/property-iterator",
   },
   {
     types: ["reflow"],
     spec: "devtools/shared/specs/reflow",
     front: "devtools/shared/fronts/reflow",
   },
   {
     types: ["screenshot"],
@@ -227,17 +226,17 @@ const Types = (exports.__TypesForTests =
   {
     types: ["symbol"],
     spec: "devtools/shared/specs/symbol",
     front: null,
   },
   {
     types: ["symbolIterator"],
     spec: "devtools/shared/specs/symbol-iterator",
-    front: null,
+    front: "devtools/shared/fronts/symbol-iterator",
   },
   {
     types: ["browsingContextTarget"],
     spec: "devtools/shared/specs/targets/browsing-context",
     front: "devtools/shared/fronts/targets/browsing-context",
   },
   {
     types: ["chromeWindowTarget"],
--- a/devtools/shared/webconsole/test/test_bug819670_getter_throws.html
+++ b/devtools/shared/webconsole/test/test_bug819670_getter_throws.html
@@ -37,17 +37,17 @@ function onEvaluate(aState, aResponse)
     },
   });
 
   ok(!aResponse.exception, "no eval exception");
   ok(!aResponse.helperResult, "no helper result");
 
   onInspect = onInspect.bind(null, aState);
   let client = new ObjectClient(aState.dbgClient, aResponse.result);
-  client.getPrototypeAndProperties(onInspect);
+  client.getPrototypeAndProperties().then(onInspect);
 }
 
 function onInspect(aState, aResponse)
 {
   ok(!aResponse.error, "no response error");
 
   let expectedProps =  Object.getOwnPropertyNames(document.__proto__);
 
--- a/devtools/shared/webconsole/test/test_object_actor.html
+++ b/devtools/shared/webconsole/test/test_object_actor.html
@@ -148,17 +148,17 @@ function onConsoleCall(state, aPacket) {
 
   state.webConsoleFront.off("consoleAPICall", onConsoleCall);
 
   info("inspecting object properties");
   let args = aPacket.message.arguments;
   onProperties = onProperties.bind(null, state);
 
   let client = new ObjectClient(state.dbgClient, args[1]);
-  client.getPrototypeAndProperties(onProperties);
+  client.getPrototypeAndProperties().then(onProperties);
 }
 
 function onProperties(state, response) {
   let props = response.ownProperties;
   is(Object.keys(props).length, Object.keys(expectedProps).length,
      "number of enumerable properties");
   checkObject(props, expectedProps);
 
--- a/devtools/shared/webconsole/test/test_object_actor_native_getters.html
+++ b/devtools/shared/webconsole/test/test_object_actor_native_getters.html
@@ -69,17 +69,17 @@ function onConsoleCall(aState, aPacket)
 
   aState.webConsoleFront.off("consoleAPICall", onConsoleCall);
 
   info("inspecting object properties");
   let args = aPacket.message.arguments;
   onProperties = onProperties.bind(null, aState);
 
   let client = new ObjectClient(aState.dbgClient, args[1]);
-  client.getPrototypeAndProperties(onProperties);
+  client.getPrototypeAndProperties().then(onProperties);
 }
 
 function onProperties(aState, aResponse)
 {
   let props = aResponse.ownProperties;
   let keys = Object.keys(props);
   info(keys.length + " ownProperties: " + keys);
 
--- a/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
+++ b/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
@@ -48,17 +48,17 @@ function onConsoleCall(aState, aPacket)
 
   aState.webConsoleFront.off("consoleAPICall", onConsoleCall);
 
   info("inspecting object properties");
   let args = aPacket.message.arguments;
   onProperties = onProperties.bind(null, aState);
 
   let client = new ObjectClient(aState.dbgClient, args[1]);
-  client.getPrototypeAndProperties(onProperties);
+  client.getPrototypeAndProperties().then(onProperties);
 }
 
 function onProperties(aState, aResponse)
 {
   let props = aResponse.ownProperties;
   let keys = Object.keys(props);
   info(keys.length + " ownProperties: " + keys);