gecko-dev/devtools/server/actors/promises.js
J. Ryan Stinnett b2575a1b27 Bug 1315391 - Clean up actor destruction after changing to destroy. r=ochameau
The `destroy` method in some actors would throw errors or was incomplete,
leading to various issues closing the toolbox after the previous patch.

This cleans up all such cases noticed through manual testing of the toolbox.

MozReview-Commit-ID: 6EZYFwjSri

--HG--
extra : rebase_source : b9db68be857285de4269f7354f6ecbf703c82e29
2016-11-11 18:24:41 -06:00

201 lines
5.9 KiB
JavaScript

/* 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 protocol = require("devtools/shared/protocol");
const { promisesSpec } = require("devtools/shared/specs/promises");
const { expectState, ActorPool } = require("devtools/server/actors/common");
const { ObjectActor, createValueGrip } = require("devtools/server/actors/object");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "events", "sdk/event/core");
/**
* The Promises Actor provides support for getting the list of live promises and
* observing changes to their settlement state.
*/
var PromisesActor = protocol.ActorClassWithSpec(promisesSpec, {
/**
* @param conn DebuggerServerConnection.
* @param parentActor TabActor|RootActor
*/
initialize: function (conn, parentActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.conn = conn;
this.parentActor = parentActor;
this.state = "detached";
this._dbg = null;
this._gripDepth = 0;
this._navigationLifetimePool = null;
this._newPromises = null;
this._promisesSettled = null;
this.objectGrip = this.objectGrip.bind(this);
this._makePromiseEventHandler = this._makePromiseEventHandler.bind(this);
this._onWindowReady = this._onWindowReady.bind(this);
},
destroy: function () {
if (this.state === "attached") {
this.detach();
}
protocol.Actor.prototype.destroy.call(this, this.conn);
},
get dbg() {
if (!this._dbg) {
this._dbg = this.parentActor.makeDebugger();
}
return this._dbg;
},
/**
* Attach to the PromisesActor.
*/
attach: expectState("detached", function () {
this.dbg.addDebuggees();
this._navigationLifetimePool = this._createActorPool();
this.conn.addActorPool(this._navigationLifetimePool);
this._newPromises = [];
this._promisesSettled = [];
this.dbg.findScripts().forEach(s => {
this.parentActor.sources.createSourceActors(s.source);
});
this.dbg.onNewScript = s => {
this.parentActor.sources.createSourceActors(s.source);
};
events.on(this.parentActor, "window-ready", this._onWindowReady);
this.state = "attached";
}, "attaching to the PromisesActor"),
/**
* Detach from the PromisesActor upon Debugger closing.
*/
detach: expectState("attached", function () {
this.dbg.removeAllDebuggees();
this.dbg.enabled = false;
this._dbg = null;
this._newPromises = null;
this._promisesSettled = null;
if (this._navigationLifetimePool) {
this.conn.removeActorPool(this._navigationLifetimePool);
this._navigationLifetimePool = null;
}
events.off(this.parentActor, "window-ready", this._onWindowReady);
this.state = "detached";
}),
_createActorPool: function () {
let pool = new ActorPool(this.conn);
pool.objectActors = new WeakMap();
return pool;
},
/**
* Create an ObjectActor for the given Promise object.
*
* @param object promise
* The promise object
* @return object
* An ObjectActor object that wraps the given Promise object
*/
_createObjectActorForPromise: function (promise) {
if (this._navigationLifetimePool.objectActors.has(promise)) {
return this._navigationLifetimePool.objectActors.get(promise);
}
let actor = new ObjectActor(promise, {
getGripDepth: () => this._gripDepth,
incrementGripDepth: () => this._gripDepth++,
decrementGripDepth: () => this._gripDepth--,
createValueGrip: v =>
createValueGrip(v, this._navigationLifetimePool, this.objectGrip),
sources: () => this.parentActor.sources,
createEnvironmentActor: () => DevToolsUtils.reportException(
"PromisesActor", Error("createEnvironmentActor not yet implemented")),
getGlobalDebugObject: () => DevToolsUtils.reportException(
"PromisesActor", Error("getGlobalDebugObject not yet implemented")),
});
this._navigationLifetimePool.addActor(actor);
this._navigationLifetimePool.objectActors.set(promise, actor);
return actor;
},
/**
* Get a grip for the given Promise object.
*
* @param object value
* The Promise object
* @return object
* The grip for the given Promise object
*/
objectGrip: function (value) {
return this._createObjectActorForPromise(value).grip();
},
/**
* Get a list of ObjectActors for all live Promise Objects.
*/
listPromises: function () {
let promises = this.dbg.findObjects({ class: "Promise" });
this.dbg.onNewPromise = this._makePromiseEventHandler(this._newPromises,
"new-promises");
this.dbg.onPromiseSettled = this._makePromiseEventHandler(
this._promisesSettled, "promises-settled");
return promises.map(p => this._createObjectActorForPromise(p));
},
/**
* Creates an event handler for onNewPromise that will add the new
* Promise ObjectActor to the array and schedule it to be emitted as a
* batch for the provided event.
*
* @param array array
* The list of Promise ObjectActors to emit
* @param string eventName
* The event name
*/
_makePromiseEventHandler: function (array, eventName) {
return promise => {
let actor = this._createObjectActorForPromise(promise);
let needsScheduling = array.length == 0;
array.push(actor);
if (needsScheduling) {
DevToolsUtils.executeSoon(() => {
events.emit(this, eventName, array.splice(0, array.length));
});
}
};
},
_onWindowReady: expectState("attached", function ({ isTopLevel }) {
if (!isTopLevel) {
return;
}
this._navigationLifetimePool.cleanup();
this.dbg.removeAllDebuggees();
this.dbg.addDebuggees();
})
});
exports.PromisesActor = PromisesActor;