2012-11-30 00:07:59 -08:00
|
|
|
/* 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";
|
|
|
|
|
2013-04-11 13:59:08 -07:00
|
|
|
const {Cc, Ci, Cu} = require("chrome");
|
2014-03-17 11:11:00 -07:00
|
|
|
const {Promise: promise} = require("resource://gre/modules/Promise.jsm");
|
|
|
|
const EventEmitter = require("devtools/toolkit/event-emitter");
|
2012-12-13 05:03:55 -08:00
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2013-03-06 23:30:03 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
|
|
|
"resource://gre/modules/devtools/dbg-server.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
|
|
|
|
"resource://gre/modules/devtools/dbg-client.jsm");
|
2012-11-30 00:07:59 -08:00
|
|
|
|
|
|
|
const targets = new WeakMap();
|
2013-04-03 13:05:14 -07:00
|
|
|
const promiseTargets = new WeakMap();
|
2012-11-30 00:07:59 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Functions for creating Targets
|
|
|
|
*/
|
2013-04-11 13:59:08 -07:00
|
|
|
exports.TargetFactory = {
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Construct a Target
|
2013-04-03 13:05:14 -07:00
|
|
|
* @param {XULTab} tab
|
|
|
|
* The tab to use in creating a new target.
|
2013-03-06 23:30:03 -08:00
|
|
|
*
|
2012-11-30 00:07:59 -08:00
|
|
|
* @return A target object
|
|
|
|
*/
|
|
|
|
forTab: function TF_forTab(tab) {
|
|
|
|
let target = targets.get(tab);
|
|
|
|
if (target == null) {
|
|
|
|
target = new TabTarget(tab);
|
|
|
|
targets.set(tab, target);
|
|
|
|
}
|
|
|
|
return target;
|
|
|
|
},
|
|
|
|
|
2013-04-03 13:05:14 -07:00
|
|
|
/**
|
|
|
|
* Return a promise of a Target for a remote tab.
|
|
|
|
* @param {Object} options
|
|
|
|
* The options object has the following properties:
|
|
|
|
* {
|
|
|
|
* form: the remote protocol form of a tab,
|
2013-09-06 15:37:18 -07:00
|
|
|
* client: a DebuggerClient instance
|
|
|
|
* (caller owns this and is responsible for closing),
|
2013-04-03 13:05:14 -07:00
|
|
|
* chrome: true if the remote target is the whole process
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* @return A promise of a target object
|
|
|
|
*/
|
|
|
|
forRemoteTab: function TF_forRemoteTab(options) {
|
2013-07-11 00:12:20 -07:00
|
|
|
let targetPromise = promiseTargets.get(options);
|
|
|
|
if (targetPromise == null) {
|
2013-04-03 13:05:14 -07:00
|
|
|
let target = new TabTarget(options);
|
2013-07-11 00:12:20 -07:00
|
|
|
targetPromise = target.makeRemote().then(() => target);
|
|
|
|
promiseTargets.set(options, targetPromise);
|
2013-04-03 13:05:14 -07:00
|
|
|
}
|
2013-07-11 00:12:20 -07:00
|
|
|
return targetPromise;
|
2013-04-03 13:05:14 -07:00
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Creating a target for a tab that is being closed is a problem because it
|
|
|
|
* allows a leak as a result of coming after the close event which normally
|
|
|
|
* clears things up. This function allows us to ask if there is a known
|
|
|
|
* target for a tab without creating a target
|
|
|
|
* @return true/false
|
|
|
|
*/
|
|
|
|
isKnownTab: function TF_isKnownTab(tab) {
|
|
|
|
return targets.has(tab);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a Target
|
|
|
|
* @param {nsIDOMWindow} window
|
|
|
|
* The chromeWindow to use in creating a new target
|
|
|
|
* @return A target object
|
|
|
|
*/
|
|
|
|
forWindow: function TF_forWindow(window) {
|
|
|
|
let target = targets.get(window);
|
|
|
|
if (target == null) {
|
|
|
|
target = new WindowTarget(window);
|
|
|
|
targets.set(window, target);
|
|
|
|
}
|
|
|
|
return target;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2013-03-06 23:30:03 -08:00
|
|
|
* Get all of the targets known to the local browser instance
|
2012-11-30 00:07:59 -08:00
|
|
|
* @return An array of target objects
|
|
|
|
*/
|
|
|
|
allTargets: function TF_allTargets() {
|
|
|
|
let windows = [];
|
2013-04-11 13:59:08 -07:00
|
|
|
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
|
|
|
|
.getService(Ci.nsIWindowMediator);
|
2012-11-30 00:07:59 -08:00
|
|
|
let en = wm.getXULWindowEnumerator(null);
|
|
|
|
while (en.hasMoreElements()) {
|
|
|
|
windows.push(en.getNext());
|
|
|
|
}
|
|
|
|
|
|
|
|
return windows.map(function(window) {
|
|
|
|
return TargetFactory.forWindow(window);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The 'version' property allows the developer tools equivalent of browser
|
|
|
|
* detection. Browser detection is evil, however while we don't know what we
|
|
|
|
* will need to detect in the future, it is an easy way to postpone work.
|
2014-10-22 04:33:00 -07:00
|
|
|
* We should be looking to use the support features added in bug 1069673
|
|
|
|
* in place of version where possible.
|
2012-11-30 00:07:59 -08:00
|
|
|
*/
|
|
|
|
function getVersion() {
|
|
|
|
// FIXME: return something better
|
|
|
|
return 20;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Target represents something that we can debug. Targets are generally
|
|
|
|
* read-only. Any changes that you wish to make to a target should be done via
|
|
|
|
* a Tool that attaches to the target. i.e. a Target is just a pointer saying
|
|
|
|
* "the thing to debug is over there".
|
|
|
|
*
|
|
|
|
* Providing a generalized abstraction of a web-page or web-browser (available
|
|
|
|
* either locally or remotely) is beyond the scope of this class (and maybe
|
|
|
|
* also beyond the scope of this universe) However Target does attempt to
|
|
|
|
* abstract some common events and read-only properties common to many Tools.
|
|
|
|
*
|
|
|
|
* Supported read-only properties:
|
|
|
|
* - name, isRemote, url
|
|
|
|
*
|
|
|
|
* Target extends EventEmitter and provides support for the following events:
|
|
|
|
* - close: The target window has been closed. All tools attached to this
|
|
|
|
* target should close. This event is not currently cancelable.
|
|
|
|
* - navigate: The target window has navigated to a different URL
|
|
|
|
*
|
|
|
|
* Optional events:
|
|
|
|
* - will-navigate: The target window will navigate to a different URL
|
|
|
|
* - hidden: The target is not visible anymore (for TargetTab, another tab is selected)
|
|
|
|
* - visible: The target is visible (for TargetTab, tab is selected)
|
|
|
|
*
|
|
|
|
* Comparing Targets: 2 instances of a Target object can point at the same
|
|
|
|
* thing, so t1 !== t2 and t1 != t2 even when they represent the same object.
|
|
|
|
* To compare to targets use 't1.equals(t2)'.
|
|
|
|
*/
|
|
|
|
function Target() {
|
|
|
|
throw new Error("Use TargetFactory.newXXX or Target.getXXX to create a Target in place of 'new Target()'");
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.defineProperty(Target.prototype, "version", {
|
|
|
|
get: getVersion,
|
|
|
|
enumerable: true
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A TabTarget represents a page living in a browser tab. Generally these will
|
|
|
|
* be web pages served over http(s), but they don't have to be.
|
|
|
|
*/
|
|
|
|
function TabTarget(tab) {
|
2012-12-13 23:05:00 -08:00
|
|
|
EventEmitter.decorate(this);
|
2013-03-06 23:30:03 -08:00
|
|
|
this.destroy = this.destroy.bind(this);
|
|
|
|
this._handleThreadState = this._handleThreadState.bind(this);
|
|
|
|
this.on("thread-resumed", this._handleThreadState);
|
|
|
|
this.on("thread-paused", this._handleThreadState);
|
|
|
|
// Only real tabs need initialization here. Placeholder objects for remote
|
|
|
|
// targets will be initialized after a makeRemote method call.
|
|
|
|
if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
|
|
|
|
this._tab = tab;
|
|
|
|
this._setupListeners();
|
2013-04-03 13:05:14 -07:00
|
|
|
} else {
|
|
|
|
this._form = tab.form;
|
|
|
|
this._client = tab.client;
|
|
|
|
this._chrome = tab.chrome;
|
2013-03-06 23:30:03 -08:00
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
TabTarget.prototype = {
|
|
|
|
_webProgressListener: null,
|
|
|
|
|
2014-10-22 04:33:00 -07:00
|
|
|
/**
|
|
|
|
* Returns a promise for the protocol description from the root actor.
|
|
|
|
* Used internally with `target.actorHasMethod`. Takes advantage of
|
|
|
|
* caching if definition was fetched previously with the corresponding
|
|
|
|
* actor information. Must be a remote target.
|
|
|
|
*
|
|
|
|
* @return {Promise}
|
|
|
|
* {
|
|
|
|
* "category": "actor",
|
|
|
|
* "typeName": "longstractor",
|
|
|
|
* "methods": [{
|
|
|
|
* "name": "substring",
|
|
|
|
* "request": {
|
|
|
|
* "type": "substring",
|
|
|
|
* "start": {
|
|
|
|
* "_arg": 0,
|
|
|
|
* "type": "primitive"
|
|
|
|
* },
|
|
|
|
* "end": {
|
|
|
|
* "_arg": 1,
|
|
|
|
* "type": "primitive"
|
|
|
|
* }
|
|
|
|
* },
|
|
|
|
* "response": {
|
|
|
|
* "substring": {
|
|
|
|
* "_retval": "primitive"
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* }],
|
|
|
|
* "events": {}
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
getActorDescription: function (actorName) {
|
|
|
|
if (!this.client) {
|
|
|
|
throw new Error("TabTarget#getActorDescription() can only be called on remote tabs.");
|
|
|
|
}
|
|
|
|
|
|
|
|
let deferred = promise.defer();
|
|
|
|
|
|
|
|
if (this._protocolDescription && this._protocolDescription.types[actorName]) {
|
|
|
|
deferred.resolve(this._protocolDescription.types[actorName]);
|
|
|
|
} else {
|
|
|
|
this.client.mainRoot.protocolDescription(description => {
|
|
|
|
this._protocolDescription = description;
|
|
|
|
deferred.resolve(description.types[actorName]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a boolean indicating whether or not the specific actor
|
|
|
|
* type exists. Must be a remote target.
|
|
|
|
*
|
|
|
|
* @param {String} actorName
|
|
|
|
* @return {Boolean}
|
|
|
|
*/
|
|
|
|
hasActor: function (actorName) {
|
|
|
|
if (!this.client) {
|
|
|
|
throw new Error("TabTarget#hasActor() can only be called on remote tabs.");
|
|
|
|
}
|
|
|
|
if (this.form) {
|
|
|
|
return !!this.form[actorName + "Actor"];
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Queries the protocol description to see if an actor has
|
|
|
|
* an available method. The actor must already be lazily-loaded,
|
|
|
|
* so this is for use inside of tool. Returns a promise that
|
|
|
|
* resolves to a boolean. Must be a remote target.
|
|
|
|
*
|
|
|
|
* @param {String} actorName
|
|
|
|
* @param {String} methodName
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
actorHasMethod: function (actorName, methodName) {
|
|
|
|
if (!this.client) {
|
|
|
|
throw new Error("TabTarget#actorHasMethod() can only be called on remote tabs.");
|
|
|
|
}
|
|
|
|
return this.getActorDescription(actorName).then(desc => {
|
|
|
|
if (desc && desc.methods) {
|
|
|
|
return !!desc.methods.find(method => method.name === methodName);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a trait from the root actor.
|
|
|
|
*
|
|
|
|
* @param {String} traitName
|
|
|
|
* @return {Mixed}
|
|
|
|
*/
|
|
|
|
getTrait: function (traitName) {
|
|
|
|
if (!this.client) {
|
|
|
|
throw new Error("TabTarget#getTrait() can only be called on remote tabs.");
|
|
|
|
}
|
2014-11-11 19:57:00 -08:00
|
|
|
|
|
|
|
// If the targeted actor exposes traits and has a defined value for this traits,
|
|
|
|
// override the root actor traits
|
|
|
|
if (this.form.traits && traitName in this.form.traits) {
|
|
|
|
return this.form.traits[traitName];
|
|
|
|
}
|
|
|
|
|
2014-10-22 04:33:00 -07:00
|
|
|
return this.client.traits[traitName];
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
get version() { return getVersion(); },
|
|
|
|
|
|
|
|
get tab() {
|
|
|
|
return this._tab;
|
|
|
|
},
|
|
|
|
|
2013-03-06 23:30:03 -08:00
|
|
|
get form() {
|
|
|
|
return this._form;
|
|
|
|
},
|
|
|
|
|
2013-05-14 15:25:28 -07:00
|
|
|
get root() {
|
|
|
|
return this._root;
|
|
|
|
},
|
|
|
|
|
2013-03-06 23:30:03 -08:00
|
|
|
get client() {
|
|
|
|
return this._client;
|
|
|
|
},
|
|
|
|
|
|
|
|
get chrome() {
|
|
|
|
return this._chrome;
|
|
|
|
},
|
|
|
|
|
2012-12-13 02:26:42 -08:00
|
|
|
get window() {
|
2014-09-22 16:18:00 -07:00
|
|
|
// XXX - this is a footgun for e10s - there .contentWindow will be null,
|
|
|
|
// and even though .contentWindowAsCPOW *might* work, it will not work
|
|
|
|
// in all contexts. Consumers of .window need to be refactored to not
|
|
|
|
// rely on this.
|
|
|
|
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
|
|
|
|
Cu.reportError("The .window getter on devtools' |target| object isn't e10s friendly!\n"
|
|
|
|
+ Error().stack);
|
|
|
|
}
|
2013-03-06 23:30:03 -08:00
|
|
|
// Be extra careful here, since this may be called by HS_getHudByWindow
|
|
|
|
// during shutdown.
|
|
|
|
if (this._tab && this._tab.linkedBrowser) {
|
|
|
|
return this._tab.linkedBrowser.contentWindow;
|
|
|
|
}
|
2013-05-20 01:50:27 -07:00
|
|
|
return null;
|
2012-12-13 02:26:42 -08:00
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
get name() {
|
2014-11-11 19:57:00 -08:00
|
|
|
if (this._tab && this._tab.linkedBrowser.contentDocument) {
|
|
|
|
return this._tab.linkedBrowser.contentDocument.title
|
|
|
|
} else if (this.isAddon) {
|
|
|
|
return this._form.name;
|
|
|
|
} else {
|
|
|
|
return this._form.title;
|
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
get url() {
|
2014-08-20 12:49:11 -07:00
|
|
|
return this._tab ? this._tab.linkedBrowser.currentURI.spec :
|
2013-03-06 23:30:03 -08:00
|
|
|
this._form.url;
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
get isRemote() {
|
2013-03-06 23:30:03 -08:00
|
|
|
return !this.isLocalTab;
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
2014-03-27 04:32:00 -07:00
|
|
|
get isAddon() {
|
2014-11-11 19:57:00 -08:00
|
|
|
return !!(this._form && this._form.actor &&
|
|
|
|
this._form.actor.match(/conn\d+\.addon\d+/));
|
2014-03-27 04:32:00 -07:00
|
|
|
},
|
|
|
|
|
2012-12-14 17:10:43 -08:00
|
|
|
get isLocalTab() {
|
2013-03-06 23:30:03 -08:00
|
|
|
return !!this._tab;
|
2012-12-14 17:10:43 -08:00
|
|
|
},
|
|
|
|
|
2014-10-30 09:23:01 -07:00
|
|
|
get isMultiProcess() {
|
|
|
|
return !this.window;
|
|
|
|
},
|
|
|
|
|
2013-01-09 01:32:35 -08:00
|
|
|
get isThreadPaused() {
|
|
|
|
return !!this._isThreadPaused;
|
|
|
|
},
|
|
|
|
|
2013-03-06 23:30:03 -08:00
|
|
|
/**
|
|
|
|
* Adds remote protocol capabilities to the target, so that it can be used
|
|
|
|
* for tools that support the Remote Debugging Protocol even for local
|
|
|
|
* connections.
|
|
|
|
*/
|
2013-04-03 13:05:14 -07:00
|
|
|
makeRemote: function TabTarget_makeRemote() {
|
2013-03-06 23:30:03 -08:00
|
|
|
if (this._remote) {
|
|
|
|
return this._remote.promise;
|
|
|
|
}
|
|
|
|
|
2013-07-11 00:12:20 -07:00
|
|
|
this._remote = promise.defer();
|
2013-03-06 23:30:03 -08:00
|
|
|
|
2013-04-03 13:05:14 -07:00
|
|
|
if (this.isLocalTab) {
|
2013-03-06 23:30:03 -08:00
|
|
|
// Since a remote protocol connection will be made, let's start the
|
|
|
|
// DebuggerServer here, once and for all tools.
|
|
|
|
if (!DebuggerServer.initialized) {
|
|
|
|
DebuggerServer.init();
|
|
|
|
DebuggerServer.addBrowserActors();
|
|
|
|
}
|
|
|
|
|
|
|
|
this._client = new DebuggerClient(DebuggerServer.connectPipe());
|
|
|
|
// A local TabTarget will never perform chrome debugging.
|
|
|
|
this._chrome = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._setupRemoteListeners();
|
|
|
|
|
2013-04-22 23:09:24 -07:00
|
|
|
let attachTab = () => {
|
|
|
|
this._client.attachTab(this._form.actor, (aResponse, aTabClient) => {
|
|
|
|
if (!aTabClient) {
|
|
|
|
this._remote.reject("Unable to attach to the tab");
|
|
|
|
return;
|
|
|
|
}
|
Make the debugger frontend cope with an already connected target (bug 933212); r=jryans,fitzgen
* Made the DebuggerClient, which is actually the RootActor front, not consider one of the attached child fronts as "active". Since a single DebuggerClient (or RootFront) is kept around for the App Manager's lifetime, it makes sense to move the notion of "active" tab to the toolbox's target. As each toolbox gets destroyed, the fronts should be detaching from their actors (if they are stateful) so that the app is no longer in a debugging state. Debugging a new app (or reconnecting to a previous one) will create new fronts anyway.
* Slightly refactored the TabClient, ThreadClient, SourceClient and TracerClient towards a protocol.js-based architecture, by adding parent-child references and lifecycle management. Now a tab-scoped thread actor for instance has the tab as its parent, while a global-scoped thread actor (chrome debugger) has the DebuggerCLient (RootFront) as its parent. This lets parents reference their children, so that caching in the target object can work. It also allowed me to move some methods from the DebuggerClient to the actual front that should be responsible, like reconfigureTab, reconfigureThread and attachThread. These methods now use DebuggerClient.requester, too.
* Added some error handling in the debugger client requester around "before" and "after" callbacks, which exposed some errors in tests that are now fixed.
* Fixed the state handling in the thread actor so that merely detaching from a thread doesn't put it in the exited state. This is the part that what was necessary for Firebug's use case.
* Properly loading tracer and webgl actors now on b2g.
2014-01-14 07:39:40 -08:00
|
|
|
this.activeTab = aTabClient;
|
2013-04-22 23:09:24 -07:00
|
|
|
this.threadActor = aResponse.threadActor;
|
|
|
|
this._remote.resolve(null);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
if (this.isLocalTab) {
|
2013-04-10 21:55:14 -07:00
|
|
|
this._client.connect((aType, aTraits) => {
|
|
|
|
this._client.listTabs(aResponse => {
|
2013-05-14 15:25:28 -07:00
|
|
|
this._root = aResponse;
|
2013-10-29 10:47:16 -07:00
|
|
|
|
2014-08-20 12:49:11 -07:00
|
|
|
if (this.window) {
|
|
|
|
let windowUtils = this.window
|
|
|
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
let outerWindow = windowUtils.outerWindowID;
|
|
|
|
aResponse.tabs.some((tab) => {
|
|
|
|
if (tab.outerWindowID === outerWindow) {
|
|
|
|
this._form = tab;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-10-29 10:47:16 -07:00
|
|
|
if (!this._form) {
|
|
|
|
this._form = aResponse.tabs[aResponse.selected];
|
|
|
|
}
|
2013-04-22 23:09:24 -07:00
|
|
|
attachTab();
|
2013-04-10 21:55:14 -07:00
|
|
|
});
|
|
|
|
});
|
2013-04-22 23:09:24 -07:00
|
|
|
} else if (!this.chrome) {
|
|
|
|
// In the remote debugging case, the protocol connection will have been
|
|
|
|
// already initialized in the connection screen code.
|
|
|
|
attachTab();
|
|
|
|
} else {
|
|
|
|
// Remote chrome debugging doesn't need anything at this point.
|
|
|
|
this._remote.resolve(null);
|
2013-03-06 23:30:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return this._remote.promise;
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
2013-01-09 01:32:35 -08:00
|
|
|
* Listen to the different events.
|
2012-11-30 00:07:59 -08:00
|
|
|
*/
|
|
|
|
_setupListeners: function TabTarget__setupListeners() {
|
|
|
|
this._webProgressListener = new TabWebProgressListener(this);
|
|
|
|
this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
|
|
|
|
this.tab.addEventListener("TabClose", this);
|
|
|
|
this.tab.parentNode.addEventListener("TabSelect", this);
|
2013-02-17 03:13:00 -08:00
|
|
|
this.tab.ownerDocument.defaultView.addEventListener("unload", this);
|
2013-03-06 23:30:03 -08:00
|
|
|
},
|
|
|
|
|
2013-09-06 15:34:56 -07:00
|
|
|
/**
|
|
|
|
* Teardown event listeners.
|
|
|
|
*/
|
|
|
|
_teardownListeners: function TabTarget__teardownListeners() {
|
|
|
|
if (this._webProgressListener) {
|
|
|
|
this._webProgressListener.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
|
|
|
|
this._tab.removeEventListener("TabClose", this);
|
|
|
|
this._tab.parentNode.removeEventListener("TabSelect", this);
|
|
|
|
},
|
|
|
|
|
2013-03-06 23:30:03 -08:00
|
|
|
/**
|
|
|
|
* Setup listeners for remote debugging, updating existing ones as necessary.
|
|
|
|
*/
|
|
|
|
_setupRemoteListeners: function TabTarget__setupRemoteListeners() {
|
2014-02-25 21:29:09 -08:00
|
|
|
this.client.addListener("closed", this.destroy);
|
|
|
|
|
2013-11-07 06:14:40 -08:00
|
|
|
this._onTabDetached = (aType, aPacket) => {
|
|
|
|
// We have to filter message to ensure that this detach is for this tab
|
|
|
|
if (aPacket.from == this._form.actor) {
|
|
|
|
this.destroy();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.client.addListener("tabDetached", this._onTabDetached);
|
2013-03-06 23:30:03 -08:00
|
|
|
|
2014-06-14 03:49:00 -07:00
|
|
|
this._onTabNavigated = (aType, aPacket) => {
|
2013-04-04 01:23:42 -07:00
|
|
|
let event = Object.create(null);
|
|
|
|
event.url = aPacket.url;
|
|
|
|
event.title = aPacket.title;
|
2013-04-17 03:19:29 -07:00
|
|
|
event.nativeConsoleAPI = aPacket.nativeConsoleAPI;
|
2014-08-27 03:19:30 -07:00
|
|
|
event.isFrameSwitching = aPacket.isFrameSwitching;
|
2013-04-04 01:23:42 -07:00
|
|
|
// Send any stored event payload (DOMWindow or nsIRequest) for backwards
|
|
|
|
// compatibility with non-remotable tools.
|
2013-03-06 23:30:03 -08:00
|
|
|
if (aPacket.state == "start") {
|
2013-04-11 00:22:50 -07:00
|
|
|
event._navPayload = this._navRequest;
|
2013-04-04 01:23:42 -07:00
|
|
|
this.emit("will-navigate", event);
|
2013-04-11 00:22:50 -07:00
|
|
|
this._navRequest = null;
|
2013-03-06 23:30:03 -08:00
|
|
|
} else {
|
2013-04-11 00:22:50 -07:00
|
|
|
event._navPayload = this._navWindow;
|
2013-04-04 01:23:42 -07:00
|
|
|
this.emit("navigate", event);
|
2013-04-11 00:22:50 -07:00
|
|
|
this._navWindow = null;
|
2013-03-06 23:30:03 -08:00
|
|
|
}
|
2014-06-14 03:49:00 -07:00
|
|
|
};
|
2013-03-06 23:30:03 -08:00
|
|
|
this.client.addListener("tabNavigated", this._onTabNavigated);
|
2014-08-27 03:19:30 -07:00
|
|
|
|
|
|
|
this._onFrameUpdate = (aType, aPacket) => {
|
|
|
|
this.emit("frame-update", aPacket);
|
|
|
|
};
|
|
|
|
this.client.addListener("frameUpdate", this._onFrameUpdate);
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
2013-09-06 15:34:56 -07:00
|
|
|
/**
|
|
|
|
* Teardown listeners for remote debugging.
|
|
|
|
*/
|
|
|
|
_teardownRemoteListeners: function TabTarget__teardownRemoteListeners() {
|
2014-02-25 21:29:09 -08:00
|
|
|
this.client.removeListener("closed", this.destroy);
|
2013-09-06 15:34:56 -07:00
|
|
|
this.client.removeListener("tabNavigated", this._onTabNavigated);
|
2013-11-07 06:14:40 -08:00
|
|
|
this.client.removeListener("tabDetached", this._onTabDetached);
|
2014-08-27 03:19:30 -07:00
|
|
|
this.client.removeListener("frameUpdate", this._onFrameUpdate);
|
2013-09-06 15:34:56 -07:00
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Handle tabs events.
|
|
|
|
*/
|
|
|
|
handleEvent: function (event) {
|
|
|
|
switch (event.type) {
|
|
|
|
case "TabClose":
|
2013-02-17 03:13:00 -08:00
|
|
|
case "unload":
|
2012-11-30 00:07:59 -08:00
|
|
|
this.destroy();
|
|
|
|
break;
|
|
|
|
case "TabSelect":
|
|
|
|
if (this.tab.selected) {
|
|
|
|
this.emit("visible", event);
|
|
|
|
} else {
|
|
|
|
this.emit("hidden", event);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-01-09 01:32:35 -08:00
|
|
|
/**
|
|
|
|
* Handle script status.
|
|
|
|
*/
|
|
|
|
_handleThreadState: function(event) {
|
|
|
|
switch (event) {
|
|
|
|
case "thread-resumed":
|
|
|
|
this._isThreadPaused = false;
|
|
|
|
break;
|
|
|
|
case "thread-paused":
|
|
|
|
this._isThreadPaused = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Target is not alive anymore.
|
|
|
|
*/
|
|
|
|
destroy: function() {
|
2013-03-06 23:30:03 -08:00
|
|
|
// If several things call destroy then we give them all the same
|
|
|
|
// destruction promise so we're sure to destroy only once
|
|
|
|
if (this._destroyer) {
|
|
|
|
return this._destroyer.promise;
|
|
|
|
}
|
2012-12-13 05:03:55 -08:00
|
|
|
|
2013-07-11 00:12:20 -07:00
|
|
|
this._destroyer = promise.defer();
|
2013-03-06 23:30:03 -08:00
|
|
|
|
|
|
|
// Before taking any action, notify listeners that destruction is imminent.
|
|
|
|
this.emit("close");
|
|
|
|
|
|
|
|
// First of all, do cleanup tasks that pertain to both remoted and
|
|
|
|
// non-remoted targets.
|
|
|
|
this.off("thread-resumed", this._handleThreadState);
|
|
|
|
this.off("thread-paused", this._handleThreadState);
|
|
|
|
|
|
|
|
if (this._tab) {
|
2013-09-06 15:34:56 -07:00
|
|
|
this._teardownListeners();
|
2013-03-06 23:30:03 -08:00
|
|
|
}
|
|
|
|
|
Make the debugger frontend cope with an already connected target (bug 933212); r=jryans,fitzgen
* Made the DebuggerClient, which is actually the RootActor front, not consider one of the attached child fronts as "active". Since a single DebuggerClient (or RootFront) is kept around for the App Manager's lifetime, it makes sense to move the notion of "active" tab to the toolbox's target. As each toolbox gets destroyed, the fronts should be detaching from their actors (if they are stateful) so that the app is no longer in a debugging state. Debugging a new app (or reconnecting to a previous one) will create new fronts anyway.
* Slightly refactored the TabClient, ThreadClient, SourceClient and TracerClient towards a protocol.js-based architecture, by adding parent-child references and lifecycle management. Now a tab-scoped thread actor for instance has the tab as its parent, while a global-scoped thread actor (chrome debugger) has the DebuggerCLient (RootFront) as its parent. This lets parents reference their children, so that caching in the target object can work. It also allowed me to move some methods from the DebuggerClient to the actual front that should be responsible, like reconfigureTab, reconfigureThread and attachThread. These methods now use DebuggerClient.requester, too.
* Added some error handling in the debugger client requester around "before" and "after" callbacks, which exposed some errors in tests that are now fixed.
* Fixed the state handling in the thread actor so that merely detaching from a thread doesn't put it in the exited state. This is the part that what was necessary for Firebug's use case.
* Properly loading tracer and webgl actors now on b2g.
2014-01-14 07:39:40 -08:00
|
|
|
let cleanupAndResolve = () => {
|
|
|
|
this._cleanup();
|
|
|
|
this._destroyer.resolve(null);
|
|
|
|
};
|
2013-03-06 23:30:03 -08:00
|
|
|
// If this target was not remoted, the promise will be resolved before the
|
|
|
|
// function returns.
|
|
|
|
if (this._tab && !this._client) {
|
Make the debugger frontend cope with an already connected target (bug 933212); r=jryans,fitzgen
* Made the DebuggerClient, which is actually the RootActor front, not consider one of the attached child fronts as "active". Since a single DebuggerClient (or RootFront) is kept around for the App Manager's lifetime, it makes sense to move the notion of "active" tab to the toolbox's target. As each toolbox gets destroyed, the fronts should be detaching from their actors (if they are stateful) so that the app is no longer in a debugging state. Debugging a new app (or reconnecting to a previous one) will create new fronts anyway.
* Slightly refactored the TabClient, ThreadClient, SourceClient and TracerClient towards a protocol.js-based architecture, by adding parent-child references and lifecycle management. Now a tab-scoped thread actor for instance has the tab as its parent, while a global-scoped thread actor (chrome debugger) has the DebuggerCLient (RootFront) as its parent. This lets parents reference their children, so that caching in the target object can work. It also allowed me to move some methods from the DebuggerClient to the actual front that should be responsible, like reconfigureTab, reconfigureThread and attachThread. These methods now use DebuggerClient.requester, too.
* Added some error handling in the debugger client requester around "before" and "after" callbacks, which exposed some errors in tests that are now fixed.
* Fixed the state handling in the thread actor so that merely detaching from a thread doesn't put it in the exited state. This is the part that what was necessary for Firebug's use case.
* Properly loading tracer and webgl actors now on b2g.
2014-01-14 07:39:40 -08:00
|
|
|
cleanupAndResolve();
|
2013-03-06 23:30:03 -08:00
|
|
|
} else if (this._client) {
|
|
|
|
// If, on the other hand, this target was remoted, the promise will be
|
|
|
|
// resolved after the remote connection is closed.
|
2013-09-06 15:34:56 -07:00
|
|
|
this._teardownRemoteListeners();
|
2013-03-06 23:30:03 -08:00
|
|
|
|
2013-09-06 15:37:18 -07:00
|
|
|
if (this.isLocalTab) {
|
|
|
|
// We started with a local tab and created the client ourselves, so we
|
|
|
|
// should close it.
|
Make the debugger frontend cope with an already connected target (bug 933212); r=jryans,fitzgen
* Made the DebuggerClient, which is actually the RootActor front, not consider one of the attached child fronts as "active". Since a single DebuggerClient (or RootFront) is kept around for the App Manager's lifetime, it makes sense to move the notion of "active" tab to the toolbox's target. As each toolbox gets destroyed, the fronts should be detaching from their actors (if they are stateful) so that the app is no longer in a debugging state. Debugging a new app (or reconnecting to a previous one) will create new fronts anyway.
* Slightly refactored the TabClient, ThreadClient, SourceClient and TracerClient towards a protocol.js-based architecture, by adding parent-child references and lifecycle management. Now a tab-scoped thread actor for instance has the tab as its parent, while a global-scoped thread actor (chrome debugger) has the DebuggerCLient (RootFront) as its parent. This lets parents reference their children, so that caching in the target object can work. It also allowed me to move some methods from the DebuggerClient to the actual front that should be responsible, like reconfigureTab, reconfigureThread and attachThread. These methods now use DebuggerClient.requester, too.
* Added some error handling in the debugger client requester around "before" and "after" callbacks, which exposed some errors in tests that are now fixed.
* Fixed the state handling in the thread actor so that merely detaching from a thread doesn't put it in the exited state. This is the part that what was necessary for Firebug's use case.
* Properly loading tracer and webgl actors now on b2g.
2014-01-14 07:39:40 -08:00
|
|
|
this._client.close(cleanupAndResolve);
|
2013-09-06 15:37:18 -07:00
|
|
|
} else {
|
|
|
|
// The client was handed to us, so we are not responsible for closing
|
Make the debugger frontend cope with an already connected target (bug 933212); r=jryans,fitzgen
* Made the DebuggerClient, which is actually the RootActor front, not consider one of the attached child fronts as "active". Since a single DebuggerClient (or RootFront) is kept around for the App Manager's lifetime, it makes sense to move the notion of "active" tab to the toolbox's target. As each toolbox gets destroyed, the fronts should be detaching from their actors (if they are stateful) so that the app is no longer in a debugging state. Debugging a new app (or reconnecting to a previous one) will create new fronts anyway.
* Slightly refactored the TabClient, ThreadClient, SourceClient and TracerClient towards a protocol.js-based architecture, by adding parent-child references and lifecycle management. Now a tab-scoped thread actor for instance has the tab as its parent, while a global-scoped thread actor (chrome debugger) has the DebuggerCLient (RootFront) as its parent. This lets parents reference their children, so that caching in the target object can work. It also allowed me to move some methods from the DebuggerClient to the actual front that should be responsible, like reconfigureTab, reconfigureThread and attachThread. These methods now use DebuggerClient.requester, too.
* Added some error handling in the debugger client requester around "before" and "after" callbacks, which exposed some errors in tests that are now fixed.
* Fixed the state handling in the thread actor so that merely detaching from a thread doesn't put it in the exited state. This is the part that what was necessary for Firebug's use case.
* Properly loading tracer and webgl actors now on b2g.
2014-01-14 07:39:40 -08:00
|
|
|
// it. We just need to detach from the tab, if already attached.
|
|
|
|
if (this.activeTab) {
|
|
|
|
this.activeTab.detach(cleanupAndResolve);
|
|
|
|
} else {
|
|
|
|
cleanupAndResolve();
|
|
|
|
}
|
2013-09-06 15:37:18 -07:00
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
2012-12-13 05:03:55 -08:00
|
|
|
|
2013-03-06 23:30:03 -08:00
|
|
|
return this._destroyer.promise;
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
2013-09-06 15:34:56 -07:00
|
|
|
/**
|
|
|
|
* Clean up references to what this target points to.
|
|
|
|
*/
|
|
|
|
_cleanup: function TabTarget__cleanup() {
|
|
|
|
if (this._tab) {
|
|
|
|
targets.delete(this._tab);
|
|
|
|
} else {
|
|
|
|
promiseTargets.delete(this._form);
|
|
|
|
}
|
Make the debugger frontend cope with an already connected target (bug 933212); r=jryans,fitzgen
* Made the DebuggerClient, which is actually the RootActor front, not consider one of the attached child fronts as "active". Since a single DebuggerClient (or RootFront) is kept around for the App Manager's lifetime, it makes sense to move the notion of "active" tab to the toolbox's target. As each toolbox gets destroyed, the fronts should be detaching from their actors (if they are stateful) so that the app is no longer in a debugging state. Debugging a new app (or reconnecting to a previous one) will create new fronts anyway.
* Slightly refactored the TabClient, ThreadClient, SourceClient and TracerClient towards a protocol.js-based architecture, by adding parent-child references and lifecycle management. Now a tab-scoped thread actor for instance has the tab as its parent, while a global-scoped thread actor (chrome debugger) has the DebuggerCLient (RootFront) as its parent. This lets parents reference their children, so that caching in the target object can work. It also allowed me to move some methods from the DebuggerClient to the actual front that should be responsible, like reconfigureTab, reconfigureThread and attachThread. These methods now use DebuggerClient.requester, too.
* Added some error handling in the debugger client requester around "before" and "after" callbacks, which exposed some errors in tests that are now fixed.
* Fixed the state handling in the thread actor so that merely detaching from a thread doesn't put it in the exited state. This is the part that what was necessary for Firebug's use case.
* Properly loading tracer and webgl actors now on b2g.
2014-01-14 07:39:40 -08:00
|
|
|
this.activeTab = null;
|
2013-09-06 15:34:56 -07:00
|
|
|
this._client = null;
|
|
|
|
this._tab = null;
|
|
|
|
this._form = null;
|
|
|
|
this._remote = null;
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
toString: function() {
|
2013-03-06 23:30:03 -08:00
|
|
|
return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* WebProgressListener for TabTarget.
|
|
|
|
*
|
|
|
|
* @param object aTarget
|
|
|
|
* The TabTarget instance to work with.
|
|
|
|
*/
|
|
|
|
function TabWebProgressListener(aTarget) {
|
|
|
|
this.target = aTarget;
|
|
|
|
}
|
|
|
|
|
|
|
|
TabWebProgressListener.prototype = {
|
|
|
|
target: null,
|
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
|
|
|
|
|
|
|
|
onStateChange: function TWPL_onStateChange(progress, request, flag, status) {
|
|
|
|
let isStart = flag & Ci.nsIWebProgressListener.STATE_START;
|
|
|
|
let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
|
|
|
|
let isNetwork = flag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
|
|
|
|
let isRequest = flag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
|
|
|
|
|
|
|
|
// Skip non-interesting states.
|
|
|
|
if (!isStart || !isDocument || !isRequest || !isNetwork) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-02 16:34:00 -08:00
|
|
|
// emit event if the top frame is navigating
|
2014-09-22 16:18:00 -07:00
|
|
|
if (progress.isTopLevel) {
|
2013-04-04 01:23:42 -07:00
|
|
|
// Emit the event if the target is not remoted or store the payload for
|
|
|
|
// later emission otherwise.
|
|
|
|
if (this.target._client) {
|
2013-04-11 00:22:50 -07:00
|
|
|
this.target._navRequest = request;
|
2013-04-04 01:23:42 -07:00
|
|
|
} else {
|
|
|
|
this.target.emit("will-navigate", request);
|
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onProgressChange: function() {},
|
|
|
|
onSecurityChange: function() {},
|
|
|
|
onStatusChange: function() {},
|
|
|
|
|
2013-03-06 23:30:03 -08:00
|
|
|
onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
|
2012-12-19 03:18:44 -08:00
|
|
|
if (this.target &&
|
|
|
|
!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
|
|
|
|
let window = webProgress.DOMWindow;
|
2013-04-04 01:23:42 -07:00
|
|
|
// Emit the event if the target is not remoted or store the payload for
|
|
|
|
// later emission otherwise.
|
|
|
|
if (this.target._client) {
|
2013-04-11 00:22:50 -07:00
|
|
|
this.target._navWindow = window;
|
2013-04-04 01:23:42 -07:00
|
|
|
} else {
|
|
|
|
this.target.emit("navigate", window);
|
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
},
|
2013-03-06 23:30:03 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy the progress listener instance.
|
|
|
|
*/
|
|
|
|
destroy: function TWPL_destroy() {
|
|
|
|
if (this.target.tab) {
|
2014-03-10 09:34:41 -07:00
|
|
|
try {
|
|
|
|
this.target.tab.linkedBrowser.removeProgressListener(this);
|
|
|
|
} catch (ex) {
|
|
|
|
// This can throw when a tab crashes in e10s.
|
|
|
|
}
|
2013-03-06 23:30:03 -08:00
|
|
|
}
|
|
|
|
this.target._webProgressListener = null;
|
2013-04-11 00:22:50 -07:00
|
|
|
this.target._navRequest = null;
|
|
|
|
this.target._navWindow = null;
|
2013-03-06 23:30:03 -08:00
|
|
|
this.target = null;
|
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A WindowTarget represents a page living in a xul window or panel. Generally
|
|
|
|
* these will have a chrome: URL
|
|
|
|
*/
|
|
|
|
function WindowTarget(window) {
|
2012-12-13 23:05:00 -08:00
|
|
|
EventEmitter.decorate(this);
|
2012-11-30 00:07:59 -08:00
|
|
|
this._window = window;
|
2013-01-09 01:32:35 -08:00
|
|
|
this._setupListeners();
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
WindowTarget.prototype = {
|
|
|
|
get version() { return getVersion(); },
|
|
|
|
|
|
|
|
get window() {
|
|
|
|
return this._window;
|
|
|
|
},
|
|
|
|
|
|
|
|
get name() {
|
|
|
|
return this._window.document.title;
|
|
|
|
},
|
|
|
|
|
|
|
|
get url() {
|
|
|
|
return this._window.document.location.href;
|
|
|
|
},
|
|
|
|
|
|
|
|
get isRemote() {
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2012-12-14 17:10:43 -08:00
|
|
|
get isLocalTab() {
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2013-01-09 01:32:35 -08:00
|
|
|
get isThreadPaused() {
|
|
|
|
return !!this._isThreadPaused;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listen to the different events.
|
|
|
|
*/
|
|
|
|
_setupListeners: function() {
|
|
|
|
this._handleThreadState = this._handleThreadState.bind(this);
|
|
|
|
this.on("thread-paused", this._handleThreadState);
|
|
|
|
this.on("thread-resumed", this._handleThreadState);
|
|
|
|
},
|
|
|
|
|
|
|
|
_handleThreadState: function(event) {
|
|
|
|
switch (event) {
|
|
|
|
case "thread-resumed":
|
|
|
|
this._isThreadPaused = false;
|
|
|
|
break;
|
|
|
|
case "thread-paused":
|
|
|
|
this._isThreadPaused = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
/**
|
|
|
|
* Target is not alive anymore.
|
|
|
|
*/
|
|
|
|
destroy: function() {
|
|
|
|
if (!this._destroyed) {
|
|
|
|
this._destroyed = true;
|
|
|
|
|
2013-01-09 01:32:35 -08:00
|
|
|
this.off("thread-paused", this._handleThreadState);
|
|
|
|
this.off("thread-resumed", this._handleThreadState);
|
2012-12-13 05:03:55 -08:00
|
|
|
this.emit("close");
|
|
|
|
|
|
|
|
targets.delete(this._window);
|
|
|
|
this._window = null;
|
|
|
|
}
|
|
|
|
|
2013-07-11 00:12:20 -07:00
|
|
|
return promise.resolve(null);
|
2012-12-13 05:03:55 -08:00
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
toString: function() {
|
|
|
|
return 'WindowTarget:' + this.window;
|
|
|
|
},
|
|
|
|
};
|