Bug 1549785 - Implement Runtime.releaseObject. r=remote-protocol-reviewers,ato
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 14 May 2019 21:59:50 +0000
changeset 473894 b1406a7e07ff3e12aa20a3b644ff4a86b8045dd5
parent 473893 0e0c4b2166dc3753033cd9121bdd598e355f8ef9
child 473895 b143758e66ec86fac847d65cb4243d8f92f6b3e2
push id36018
push userrgurzau@mozilla.com
push dateWed, 15 May 2019 15:58:16 +0000
treeherdermozilla-central@d865d7a290f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersremote-protocol-reviewers, ato
bugs1549785
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1549785 - Implement Runtime.releaseObject. r=remote-protocol-reviewers,ato Differential Revision: https://phabricator.services.mozilla.com/D30231
remote/domains/content/Runtime.jsm
remote/domains/content/runtime/ExecutionContext.jsm
remote/test/browser/browser.ini
remote/test/browser/browser_runtime_remote_objects.js
--- a/remote/domains/content/Runtime.jsm
+++ b/remote/domains/content/Runtime.jsm
@@ -65,16 +65,30 @@ class Runtime extends ContentProcessDoma
     }
     if (typeof(request.expression) != "string") {
       throw new Error(`Expecting 'expression' attribute to be a string. ` +
         `But was: ${typeof(request.expression)}`);
     }
     return context.evaluate(request.expression);
   }
 
+  releaseObject({ objectId }) {
+    let context = null;
+    for (const ctx of this.contexts.values()) {
+      if (ctx.hasRemoteObject(objectId)) {
+        context = ctx;
+        break;
+      }
+    }
+    if (!context) {
+      throw new Error(`Unable to get execution context by ID: ${objectId}`);
+    }
+    context.releaseObject(objectId);
+  }
+
   callFunctionOn(request) {
     let context = null;
     // When an `objectId` is passed, we want to execute the function of a given object
     // So we first have to find its ExecutionContext
     if (request.objectId) {
       for (const ctx of this.contexts.values()) {
         if (ctx.hasRemoteObject(request.objectId)) {
           context = ctx;
--- a/remote/domains/content/runtime/ExecutionContext.jsm
+++ b/remote/domains/content/runtime/ExecutionContext.jsm
@@ -43,16 +43,20 @@ class ExecutionContext {
   destructor() {
     this._debugger.removeDebuggee(this._debuggee);
   }
 
   hasRemoteObject(id) {
     return this._remoteObjects.has(id);
   }
 
+  releaseObject(id) {
+    return this._remoteObjects.delete(id);
+  }
+
   /**
    * Evaluate a Javascript expression.
    *
    * @param {String} expression
    *   The JS expression to evaluate against the JS context.
    * @return {Object} A multi-form object depending if the execution succeed or failed.
    *   If the expression failed to evaluate, it will return an object with an
    *   `exceptionDetails` attribute matching the `ExceptionDetails` CDP type.
--- a/remote/test/browser/browser.ini
+++ b/remote/test/browser/browser.ini
@@ -6,13 +6,14 @@ support-files =
   head.js
 skip-if = debug || asan # bug 1546945
 
 [browser_cdp.js]
 [browser_main_target.js]
 [browser_page_frameNavigated.js]
 [browser_page_runtime_events.js]
 [browser_runtime_evaluate.js]
+[browser_runtime_remote_objects.js]
 [browser_runtime_callFunctionOn.js]
 [browser_runtime_executionContext.js]
 skip-if = os == "mac" || (verify && os == 'win') # bug 1547961
 [browser_tabs.js]
 [browser_target.js]
new file mode 100644
--- /dev/null
+++ b/remote/test/browser/browser_runtime_remote_objects.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the Runtime remote object
+
+const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
+
+add_task(async function() {
+  // Open a test page, to prevent debugging the random default page
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
+
+  // Start the CDP server
+  await RemoteAgent.listen(Services.io.newURI("http://localhost:9222"));
+
+  // Retrieve the chrome-remote-interface library object
+  const CDP = await getCDP();
+
+  // Connect to the server
+  const client = await CDP({
+    target(list) {
+      // Ensure debugging the right target, i.e. the one for our test tab.
+      return list.find(target => target.url == TEST_URI);
+    },
+  });
+  ok(true, "CDP client has been instantiated");
+
+  const firstContext = await testRuntimeEnable(client);
+  const contextId = firstContext.id;
+
+  await testObjectRelease(client, contextId);
+
+  await client.close();
+  ok(true, "The client is closed");
+
+  BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  await RemoteAgent.close();
+});
+
+async function testRuntimeEnable({ Runtime }) {
+  // Enable watching for new execution context
+  await Runtime.enable();
+  ok(true, "Runtime domain has been enabled");
+
+  // Calling Runtime.enable will emit executionContextCreated for the existing contexts
+  const { context } = await Runtime.executionContextCreated();
+  ok(!!context.id, "The execution context has an id");
+  ok(context.auxData.isDefault, "The execution context is the default one");
+  ok(!!context.auxData.frameId, "The execution context has a frame id set");
+
+  return context;
+}
+
+async function testObjectRelease({ Runtime }, contextId) {
+  const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 42 })" });
+  is(result.subtype, null, "JS Object have no subtype");
+  is(result.type, "object", "The type is correct");
+  ok(!!result.objectId, "Got an object id");
+
+  const { result: result2 } = await Runtime.callFunctionOn({
+    executionContextId: contextId,
+    functionDeclaration: "obj => JSON.stringify(obj)",
+    arguments: [{ objectId: result.objectId }],
+  });
+  is(result2.type, "string", "The type is correct");
+  is(result2.value, JSON.stringify({ foo: 42 }), "Got the object's JSON");
+
+  const { result: result3 } = await Runtime.callFunctionOn({
+    objectId: result.objectId,
+    functionDeclaration: "function () { return this.foo; }",
+  });
+  is(result3.type, "number", "The type is correct");
+  is(result3.value, 42, "Got the object's foo attribute");
+
+  await Runtime.releaseObject({
+    objectId: result.objectId,
+  });
+  ok(true, "Object is released");
+
+  try {
+    await Runtime.callFunctionOn({
+      executionContextId: contextId,
+      functionDeclaration: "() => {}",
+      arguments: [{ objectId: result.objectId }],
+    });
+    ok(false, "callFunctionOn with a released object as argument should throw");
+  } catch (e) {
+    ok(e.message.includes("Cannot find object with ID:"), "callFunctionOn throws on released argument");
+  }
+  try {
+    await Runtime.callFunctionOn({
+      objectId: result.objectId,
+      functionDeclaration: "() => {}",
+    });
+    ok(false, "callFunctionOn with a released object as target should throw");
+  } catch (e) {
+    ok(e.message.includes("Unable to get the context for object with id"), "callFunctionOn throws on released target");
+  }
+}