Bug 820524 - Debugger, Web Console and Profiler should share the debugger client even for local sessions; r=jwalker,vporof,msucan,anton

This commit is contained in:
Panos Astithas 2013-03-07 09:30:03 +02:00
parent f48ddb1f9d
commit 947e375875
23 changed files with 402 additions and 416 deletions

View File

@ -47,13 +47,21 @@ function testCommands(dbg, cmd) {
is(output.value, "dbg continue", "debugger continued");
DeveloperToolbarTest.exec({
typed: "dbg close",
completed: false,
blankOutput: true
});
let target = TargetFactory.forTab(gBrowser.selectedTab);
ok(!gDevTools.getToolbox(target),
"Debugger was closed.");
finish();
let toolbox = gDevTools.getToolbox(target);
if (!toolbox) {
ok(true, "Debugger was closed.");
finish();
} else {
toolbox.on("destroyed", function () {
ok(true, "Debugger was closed.");
finish();
});
}
});
});
});

View File

@ -10,9 +10,11 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
this.EXPORTED_SYMBOLS = ["DebuggerPanel"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
@ -37,13 +39,6 @@ DebuggerPanel.prototype = {
this._ensureOnlyOneRunningDebugger();
if (!this.target.isRemote) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
}
let onDebuggerLoaded = function () {
this.panelWin.removeEventListener("Debugger:Loaded",
onDebuggerLoaded, true);
@ -62,7 +57,16 @@ DebuggerPanel.prototype = {
this.panelWin.addEventListener("Debugger:Connected",
onDebuggerConnected, true);
return deferred.promise;
// Remote debugging gets the debuggee from a RemoteTarget object.
if (this.target.isRemote) {
this.panelWin._remoteFlag = true;
return deferred.promise;
}
// Local debugging needs to convert the TabTarget to a RemoteTarget.
return this.target.makeRemote().then(function success() {
return deferred.promise;
});
},
// DevToolPanel API
@ -71,6 +75,7 @@ DebuggerPanel.prototype = {
get isReady() this._isReady,
destroy: function() {
this.emit("destroyed");
return Promise.resolve(null);
},

View File

@ -99,7 +99,7 @@ let DebuggerController = {
/**
* Prepares the hostname and port number for a remote debugger connection
* and handles connection retries and timeouts.
*
* XXX: remove all this (bug 823577)
* @return boolean
* True if connection should proceed normally, false otherwise.
*/
@ -165,15 +165,11 @@ let DebuggerController = {
window.dispatchEvent("Debugger:Connected");
}
let client;
// Remote debugging gets the debuggee from a RemoteTarget object.
if (this._target && this._target.isRemote) {
window._isRemoteDebugger = true;
client = this.client = this._target.client;
if (!window._isChromeDebugger) {
let client = this.client = this._target.client;
this._target.on("close", this._onTabDetached);
this._target.on("navigate", this._onTabNavigated);
this._target.on("will-navigate", this._onTabNavigated);
if (this._target.chrome) {
let dbg = this._target.form.chromeDebugger;
@ -184,25 +180,16 @@ let DebuggerController = {
return;
}
// Content or chrome debugging can connect directly to the debuggee.
// TODO: convert this to use a TabTarget.
let transport = window._isChromeDebugger
? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort)
: DebuggerServer.connectPipe();
// Chrome debugging needs to make the connection to the debuggee.
let transport = debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort);
client = this.client = new DebuggerClient(transport);
let client = this.client = new DebuggerClient(transport);
client.addListener("tabNavigated", this._onTabNavigated);
client.addListener("tabDetached", this._onTabDetached);
client.connect(function(aType, aTraits) {
client.listTabs(function(aResponse) {
if (window._isChromeDebugger) {
let dbg = aResponse.chromeDebugger;
this._startChromeDebugging(client, dbg, callback);
} else {
let tab = aResponse.tabs[aResponse.selected];
this._startDebuggingTab(client, tab, callback);
}
this._startChromeDebugging(client, aResponse.chromeDebugger, callback);
}.bind(this));
}.bind(this));
},
@ -218,8 +205,9 @@ let DebuggerController = {
this.client.removeListener("tabNavigated", this._onTabNavigated);
this.client.removeListener("tabDetached", this._onTabDetached);
// When remote debugging, the connection is closed by the RemoteTarget.
if (!window._isRemoteDebugger) {
// When debugging content or a remote instance, the connection is closed by
// the RemoteTarget.
if (window._isChromeDebugger) {
this.client.close();
}

View File

@ -12,6 +12,11 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
const targets = new WeakMap();
@ -21,8 +26,10 @@ const targets = new WeakMap();
this.TargetFactory = {
/**
* Construct a Target
* @param {XULTab} tab
* The tab to use in creating a new target
* @param {XULTab} | {Object} tab
* The tab to use in creating a new target, or an options object in
* case of remote targets.
*
* @return A target object
*/
forTab: function TF_forTab(tab) {
@ -61,27 +68,7 @@ this.TargetFactory = {
},
/**
* Construct a Target for a remote global
* @param {Object} form
* The serialized form of a debugging protocol actor.
* @param {DebuggerClient} client
* The debuger client instance to communicate with the server.
* @param {boolean} chrome
* A flag denoting that the debugging target is the remote process as a
* whole and not a single tab.
* @return A target object
*/
forRemote: function TF_forRemote(form, client, chrome) {
let target = targets.get(form);
if (target == null) {
target = new RemoteTarget(form, client, chrome);
targets.set(form, target);
}
return target;
},
/**
* Get all of the targets known to some browser instance (local if null)
* Get all of the targets known to the local browser instance
* @return An array of target objects
*/
allTargets: function TF_allTargets() {
@ -171,8 +158,16 @@ Object.defineProperty(Target.prototype, "version", {
*/
function TabTarget(tab) {
EventEmitter.decorate(this);
this._tab = tab;
this._setupListeners();
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();
}
}
TabTarget.prototype = {
@ -185,30 +180,99 @@ TabTarget.prototype = {
return this._tab;
},
get form() {
return this._form;
},
get client() {
return this._client;
},
get chrome() {
return this._chrome;
},
get window() {
return this._tab.linkedBrowser.contentWindow;
// 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;
}
},
get name() {
return this._tab.linkedBrowser.contentDocument.title;
return this._tab ? this._tab.linkedBrowser.contentDocument.title :
this._form.title;
},
get url() {
return this._tab.linkedBrowser.contentDocument.location.href;
return this._tab ? this._tab.linkedBrowser.contentDocument.location.href :
this._form.url;
},
get isRemote() {
return false;
return !this.isLocalTab;
},
get isLocalTab() {
return true;
return !!this._tab;
},
get isThreadPaused() {
return !!this._isThreadPaused;
},
/**
* 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.
*
* @param object aOptions
* An optional object containing remote connection options that is
* supplied when connecting to another instance.
*/
makeRemote: function TabTarget_makeRemote(aOptions) {
if (this._remote) {
return this._remote.promise;
}
this._remote = Promise.defer();
if (aOptions) {
this._form = aOptions.form;
this._client = aOptions.client;
this._chrome = aOptions.chrome;
} else {
// 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();
if (aOptions) {
// In the remote debugging case, the protocol connection will have been
// already initialized in the connection screen code.
this._remote.resolve(null);
} else {
this._client.connect(function(aType, aTraits) {
this._client.listTabs(function(aResponse) {
this._form = aResponse.tabs[aResponse.selected];
this._remote.resolve(null);
}.bind(this));
}.bind(this));
}
return this._remote.promise;
},
/**
* Listen to the different events.
*/
@ -218,9 +282,27 @@ TabTarget.prototype = {
this.tab.addEventListener("TabClose", this);
this.tab.parentNode.addEventListener("TabSelect", this);
this.tab.ownerDocument.defaultView.addEventListener("unload", this);
this._handleThreadState = this._handleThreadState.bind(this);
this.on("thread-resumed", this._handleThreadState);
this.on("thread-paused", this._handleThreadState);
},
/**
* Setup listeners for remote debugging, updating existing ones as necessary.
*/
_setupRemoteListeners: function TabTarget__setupRemoteListeners() {
// Reset any conflicting event handlers that were set before makeRemote().
if (this._webProgressListener) {
this._webProgressListener.destroy();
}
this.client.addListener("tabDetached", this.destroy);
this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
if (aPacket.state == "start") {
this.emit("will-navigate", aPacket);
} else {
this.emit("navigate", aPacket);
}
}.bind(this);
this.client.addListener("tabNavigated", this._onTabNavigated);
},
/**
@ -260,28 +342,66 @@ TabTarget.prototype = {
* Target is not alive anymore.
*/
destroy: function() {
if (!this._destroyed) {
this._destroyed = true;
// 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;
}
this.tab.linkedBrowser.removeProgressListener(this._webProgressListener)
this._webProgressListener.target = null;
this._webProgressListener = null;
this.tab.ownerDocument.defaultView.removeEventListener("unload", this);
this.tab.removeEventListener("TabClose", this);
this.tab.parentNode.removeEventListener("TabSelect", this);
this.off("thread-resumed", this._handleThreadState);
this.off("thread-paused", this._handleThreadState);
this.emit("close");
this._destroyer = Promise.defer();
// 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) {
this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
this._tab.removeEventListener("TabClose", this);
this._tab.parentNode.removeEventListener("TabSelect", this);
}
// If this target was not remoted, the promise will be resolved before the
// function returns.
// if (!this._remote) {
if (this._tab && !this._client) {
if (this._webProgressListener) {
this._webProgressListener.destroy();
}
targets.delete(this._tab);
this._tab = null;
this._client = null;
this._form = null;
this._remote = null;
this._destroyer.resolve(null);
} else if (this._client) {
// If, on the other hand, this target was remoted, the promise will be
// resolved after the remote connection is closed.
this.client.removeListener("tabNavigated", this._onTabNavigated);
this.client.removeListener("tabDetached", this.destroy);
this._client.close(function onClosed() {
let key = this._tab ? this._tab : this._form;
targets.delete(key);
this._client = null;
this._tab = null;
this._form = null;
this._remote = null;
this._destroyer.resolve(null);
}.bind(this));
}
return Promise.resolve(null);
return this._destroyer.promise;
},
toString: function() {
return 'TabTarget:' + this.tab;
return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
},
};
@ -322,13 +442,24 @@ TabWebProgressListener.prototype = {
onSecurityChange: function() {},
onStatusChange: function() {},
onLocationChange: function TwPL_onLocationChange(webProgress, request, URI, flags) {
onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
if (this.target &&
!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
let window = webProgress.DOMWindow;
this.target.emit("navigate", window);
}
},
/**
* Destroy the progress listener instance.
*/
destroy: function TWPL_destroy() {
if (this.target.tab) {
this.target.tab.linkedBrowser.removeProgressListener(this);
}
this.target._webProgressListener = null;
this.target = null;
}
};
@ -412,101 +543,3 @@ WindowTarget.prototype = {
return 'WindowTarget:' + this.window;
},
};
/**
* A RemoteTarget represents a page living in a remote Firefox instance.
*/
function RemoteTarget(form, client, chrome) {
EventEmitter.decorate(this);
this._client = client;
this._form = form;
this._chrome = chrome;
this._setupListeners();
}
RemoteTarget.prototype = {
supports: supports,
get version() getVersion(),
get isRemote() true,
get chrome() this._chrome,
get name() this._form.title,
get url() this._form.url,
get client() this._client,
get form() this._form,
get isLocalTab() false,
get isThreadPaused() !!this._isThreadPaused,
/**
* Listen to the different events.
*/
_setupListeners: function() {
this.destroy = this.destroy.bind(this);
this.client.addListener("tabDetached", this.destroy);
this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
if (aPacket.state == "start") {
this.emit("will-navigate", aPacket);
} else {
this.emit("navigate", aPacket);
}
}.bind(this);
this.client.addListener("tabNavigated", this._onTabNavigated);
this._handleThreadState = this._handleThreadState.bind(this);
this.on("thread-resumed", this._handleThreadState);
this.on("thread-paused", this._handleThreadState);
},
/**
* Handle script status.
*/
_handleThreadState: function(event) {
switch (event) {
case "thread-resumed":
this._isThreadPaused = false;
break;
case "thread-paused":
this._isThreadPaused = true;
break;
}
},
/**
* Target is not alive anymore.
*/
destroy: function RT_destroy() {
// 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;
}
this._destroyer = Promise.defer();
this.client.removeListener("tabNavigated", this._onTabNavigated);
this.client.removeListener("tabDetached", this.destroy);
this._client.close(function onClosed() {
this._client = null;
this.off("thread-resumed", this._handleThreadState);
this.off("thread-paused", this._handleThreadState);
this.emit("close");
this._destroyer.resolve(null);
}.bind(this));
return this._destroyer.promise;
},
toString: function() {
return 'RemoteTarget:' + this.form.actor;
},
};

View File

@ -126,7 +126,7 @@ this.Toolbox = function Toolbox(target, selectedTool, hostType) {
this._toolUnregistered = this._toolUnregistered.bind(this);
this.destroy = this.destroy.bind(this);
this._target.once("close", this.destroy);
this._target.on("close", this.destroy);
if (!hostType) {
hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
@ -432,12 +432,13 @@ Toolbox.prototype = {
* The id of the tool to switch to
*/
selectTool: function TBOX_selectTool(id) {
if (this._currentToolId == id) {
return;
}
let deferred = Promise.defer();
if (this._currentToolId == id) {
// Return the existing panel in order to have a consistent return value.
return Promise.resolve(this._toolPanels.get(id));
}
if (!this.isReady) {
throw new Error("Can't select tool, wait for toolbox 'ready' event");
}
@ -686,13 +687,10 @@ Toolbox.prototype = {
this.off("select", this._refreshHostTitle);
this.off("host-changed", this._refreshHostTitle);
let outstanding = [];
gDevTools.off("tool-registered", this._toolRegistered);
gDevTools.off("tool-unregistered", this._toolUnregistered);
// Remote targets need to be notified that the toolbox is being torn down.
if (this._target && this._target.isRemote) {
outstanding.push(this._target.destroy());
}
this._target = null;
let outstanding = [];
for (let [id, panel] of this._toolPanels) {
outstanding.push(panel.destroy());
@ -700,8 +698,13 @@ Toolbox.prototype = {
outstanding.push(this._host.destroy());
gDevTools.off("tool-registered", this._toolRegistered);
gDevTools.off("tool-unregistered", this._toolUnregistered);
// Targets need to be notified that the toolbox is being torn down, so that
// remote protocol connections can be gracefully terminated.
if (this._target) {
this._target.off("close", this.destroy);
outstanding.push(this._target.destroy());
}
this._target = null;
Promise.all(outstanding).then(function() {
this.emit("destroyed");

View File

@ -164,7 +164,14 @@ function handleConnectionTimeout() {
* Opens the toolbox.
*/
function openToolbox(form, chrome=false) {
let target = TargetFactory.forRemote(form, gClient, chrome);
gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
window.close();
let options = {
form: form,
client: gClient,
chrome: chrome
};
let target = TargetFactory.forTab(options);
target.makeRemote(options).then(function() {
gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
window.close();
});
}

View File

@ -51,7 +51,10 @@ function selectAndCheckById(id) {
function testToggle() {
toolbox.once("destroyed", function() {
gDevTools.showToolbox(target, "styleeditor").then(function() {
// Cannot reuse a target after it's destroyed.
target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "styleeditor").then(function(aToolbox) {
toolbox = aToolbox;
is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
finishUp();
});
@ -61,9 +64,10 @@ function testToggle() {
}
function finishUp() {
toolbox.destroy();
toolbox = null;
target = null;
gBrowser.removeCurrentTab();
finish();
toolbox.destroy().then(function() {
toolbox = null;
target = null;
gBrowser.removeCurrentTab();
finish();
});
}

View File

@ -41,7 +41,9 @@ function onVisible() {
function onWillNavigate(event, request) {
ok(true, "will-navigate event received");
target.once("navigate", onNavigate);
// Wait for navigation handling to complete before removing the tab, in order
// to avoid triggering assertions.
target.once("navigate", executeSoon.bind(null, onNavigate));
}
function onNavigate() {

View File

@ -90,12 +90,14 @@ function testToolSelect()
function testDestroy()
{
toolbox.destroy().then(function() {
target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target).then(testRememberHost);
});
}
function testRememberHost()
function testRememberHost(aToolbox)
{
toolbox = aToolbox;
// last host was the window - make sure it's the same when re-opening
is(toolbox.hostType, Toolbox.HostType.WINDOW, "host remembered");
@ -123,8 +125,9 @@ function cleanup()
{
Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
toolbox.destroy();
DevTools = Toolbox = toolbox = target = null;
gBrowser.removeCurrentTab();
finish();
}
toolbox.destroy().then(function() {
DevTools = Toolbox = toolbox = target = null;
gBrowser.removeCurrentTab();
finish();
});
}

View File

@ -66,9 +66,10 @@ function selectCB(event, id) {
}
function tidyUp() {
toolbox.destroy();
gBrowser.removeCurrentTab();
toolbox.destroy().then(function() {
gBrowser.removeCurrentTab();
toolbox = toolIDs = idIndex = Toolbox = null;
finish();
toolbox = toolIDs = idIndex = Toolbox = null;
finish();
});
}

View File

@ -54,23 +54,32 @@ function test() {
// destroy toolbox, create new one hosted in a window (with a
// different tool id), and check title
.then(function () toolbox.destroy())
.then(function () gDevTools.showToolbox(target, null,
Toolbox.HostType.WINDOW))
.then(function (aToolbox) { toolbox = aToolbox; })
.then(function () toolbox.selectTool(TOOL_ID_1))
.then(checkTitle.bind(null, LABEL_1, URL_2,
"toolbox destroyed and recreated"))
// clean up
.then(function () toolbox.destroy())
.then(function () {
toolbox = null;
gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("devtools.toolbox.host");
Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
finish();
// Give the tools a chance to handle the navigation event before
// destroying the toolbox.
executeSoon(function() {
toolbox.destroy()
.then(function () {
// After destroying the toolbox, a fresh target is required.
target = TargetFactory.forTab(gBrowser.selectedTab);
return gDevTools.showToolbox(target, null, Toolbox.HostType.WINDOW);
})
.then(function (aToolbox) { toolbox = aToolbox; })
.then(function () toolbox.selectTool(TOOL_ID_1))
.then(checkTitle.bind(null, LABEL_1, URL_2,
"toolbox destroyed and recreated"))
// clean up
.then(function () toolbox.destroy())
.then(function () {
toolbox = null;
gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("devtools.toolbox.host");
Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
finish();
});
});
});
});
}

View File

@ -412,6 +412,7 @@ InspectorPanel.prototype = {
this.nodemenu = null;
this.searchBox = null;
this.highlighter = null;
this._searchResults = null;
return Promise.resolve(null);
},

View File

@ -644,7 +644,7 @@ MarkupView.prototype = {
delete this._boundFocus;
this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
this._frame.contentWindow.removeEventListener("resize", this._boundUpdatePreview, true);
this._frame.contentWindow.removeEventListener("resize", this._boundResizePreview, true);
this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true);
this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true);
delete this._boundUpdatePreview;

View File

@ -23,19 +23,6 @@ XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
* DebuggerServer.
*/
function ProfilerConnection(client) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
this.isRemote = true;
if (!client) {
let transport = DebuggerServer.connectPipe();
client = new DebuggerClient(transport);
this.isRemote = false;
}
this.client = client;
}
@ -49,22 +36,10 @@ ProfilerConnection.prototype = {
* Function to be called once we're connected to the client.
*/
connect: function PCn_connect(aCallback) {
let client = this.client;
let listTabs = function () {
client.listTabs(function (aResponse) {
this.actor = aResponse.profilerActor;
aCallback();
}.bind(this));
}.bind(this);
if (this.isRemote) {
return void listTabs();
}
client.connect(function (aType, aTraits) {
listTabs();
});
this.client.listTabs(function (aResponse) {
this.actor = aResponse.profilerActor;
aCallback();
}.bind(this));
},
/**
@ -129,9 +104,7 @@ ProfilerConnection.prototype = {
* Cleanup.
*/
destroy: function PCn_destroy() {
this.client.close(function () {
this.client = null;
}.bind(this));
this.client = null;
}
};
@ -139,14 +112,13 @@ ProfilerConnection.prototype = {
* Object defining the profiler controller components.
*/
function ProfilerController(target) {
let client;
if (target.isRemote) {
client = target.client;
this.profiler = new ProfilerConnection(target.client);
// Chrome debugging targets have already obtained a reference to the profiler
// actor.
this._connected = !!target.chrome;
if (target.chrome) {
this.profiler.actor = target.form.profilerActor;
}
this.profiler = new ProfilerConnection(client);
this._connected = false;
}
ProfilerController.prototype = {
@ -160,7 +132,7 @@ ProfilerController.prototype = {
*/
connect: function (aCallback) {
if (this._connected) {
aCallback();
return void aCallback();
}
this.profiler.connect(function onConnect() {

View File

@ -9,12 +9,14 @@ const Cu = Components.utils;
Cu.import("resource:///modules/devtools/gDevTools.jsm");
Cu.import("resource:///modules/devtools/ProfilerController.jsm");
Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
@ -205,7 +207,6 @@ function ProfilerPanel(frame, toolbox) {
this.window = frame.window;
this.document = frame.document;
this.target = toolbox.target;
this.controller = new ProfilerController(this.target);
this.profiles = new Map();
this._uid = 0;
@ -256,23 +257,39 @@ ProfilerPanel.prototype = {
* @return Promise
*/
open: function PP_open() {
let deferred = Promise.defer();
let promise;
// Local profiling needs to make the target remote.
if (!this.target.isRemote) {
promise = this.target.makeRemote();
} else {
promise = Promise.resolve(this.target);
}
this.controller.connect(function onConnect() {
let create = this.document.getElementById("profiler-create");
create.addEventListener("click", this.createProfile.bind(this), false);
create.removeAttribute("disabled");
return promise
.then(function(target) {
let deferred = Promise.defer();
this.controller = new ProfilerController(this.target);
let profile = this.createProfile();
this.switchToProfile(profile, function () {
this.isReady = true;
this.emit("ready");
this.controller.connect(function onConnect() {
let create = this.document.getElementById("profiler-create");
create.addEventListener("click", this.createProfile.bind(this), false);
create.removeAttribute("disabled");
deferred.resolve(this);
let profile = this.createProfile();
this.switchToProfile(profile, function () {
this.isReady = true;
this.emit("ready");
deferred.resolve(this);
}.bind(this))
}.bind(this));
return deferred.promise;
}.bind(this))
}.bind(this));
return deferred.promise;
.then(null, function onError(reason) {
Cu.reportError("ProfilerPanel open failed. " +
reason.error + ": " + reason.message);
});
},
/**

View File

@ -53,10 +53,12 @@ gcli.addCommand({
mgr.on("off", aChangeHandler);
},
offChange: function(aTarget, aChangeHandler) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
mgr.off("on", aChangeHandler);
mgr.off("off", aChangeHandler);
if (aTarget.tab) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
mgr.off("on", aChangeHandler);
mgr.off("off", aChangeHandler);
}
},
},
exec: gcli_cmd_resize

View File

@ -57,9 +57,11 @@ gcli.addCommand({
tilt.on("change", aChangeHandler);
},
offChange: function(aTarget, aChangeHandler) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let tilt = TiltManager.getTiltForBrowser(browserWindow);
tilt.off("change", aChangeHandler);
if (aTarget.tab) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let tilt = TiltManager.getTiltForBrowser(browserWindow);
tilt.off("change", aChangeHandler);
}
},
},
exec: function(args, context) {

View File

@ -225,7 +225,9 @@ AutocompletePopup.prototype = {
*/
set selectedIndex(aIndex) {
this._list.selectedIndex = aIndex;
this._list.ensureIndexIsVisible(this._list.selectedIndex);
if (this._list.ensureIndexIsVisible) {
this._list.ensureIndexIsVisible(this._list.selectedIndex);
}
},
/**

View File

@ -237,12 +237,11 @@ WebConsole.prototype = {
if ((win = this.iframe.contentWindow) &&
(doc = win.document) &&
doc.readyState == "complete") {
this.iframe.addEventListener("load", onIframeLoad, true);
}
else {
initUI();
}
else {
this.iframe.addEventListener("load", onIframeLoad, true);
}
return deferred.promise;
},
@ -359,8 +358,6 @@ WebConsole.prototype = {
delete HUDService.hudReferences[this.hudId];
let tabWindow = this.target.isLocalTab ? this.target.window : null;
this._destroyer = Promise.defer();
let popupset = this.mainPopupSet;
@ -371,15 +368,15 @@ WebConsole.prototype = {
let onDestroy = function WC_onDestroyUI() {
try {
let tabWindow = this.target.isLocalTab ? this.target.window : null;
tabWindow && tabWindow.focus();
}
catch (ex) {
// Tab focus can fail if the tab is closed.
// Tab focus can fail if the tab or target is closed.
}
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
this._destroyer.resolve(null);
}.bind(this);

View File

@ -10,6 +10,9 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
"resource:///modules/HUDService.jsm");
@ -38,17 +41,31 @@ WebConsolePanel.prototype = {
{
let parentDoc = this._toolbox.doc;
let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
let promise = HUDService.openWebConsole(this.target, iframe);
let promise;
return promise.then(function onSuccess(aWebConsole) {
this.hud = aWebConsole;
this._isReady = true;
this.emit("ready");
return this;
}.bind(this), function onError(aReason) {
Cu.reportError("WebConsolePanel open failed. " +
aReason.error + ": " + aReason.message);
});
// Local debugging needs to make the target remote.
if (!this.target.isRemote) {
promise = this.target.makeRemote();
} else {
promise = Promise.resolve(this.target);
}
return promise
.then(function(aTarget) {
this._frameWindow._remoteTarget = aTarget;
return HUDService.openWebConsole(this.target, iframe);
}.bind(this))
.then(function onSuccess(aWebConsole) {
this.hud = aWebConsole;
this._isReady = true;
this.emit("ready");
return this;
}.bind(this), function onError(aReason) {
let msg = "WebConsolePanel open failed. " +
aReason.error + ": " + aReason.message;
dump(msg + "\n");
Cu.reportError(msg);
});
},
get target() this._toolbox.target,

View File

@ -77,21 +77,21 @@ function tab2Loaded(aEvent) {
try {
let target1 = TargetFactory.forTab(tab1);
gDevTools.closeToolbox(target1);
gDevTools.closeToolbox(target1).then(function() {
try {
let target2 = TargetFactory.forTab(tab2);
gDevTools.closeToolbox(target2);
}
catch (ex) {
ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
noErrors = false;
}
});
}
catch (ex) {
ok(false, "gDevTools.closeToolbox(target1) exception: " + ex);
noErrors = false;
}
try {
let target2 = TargetFactory.forTab(tab2);
gDevTools.closeToolbox(target2);
}
catch (ex) {
ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
noErrors = false;
}
}
function testEnd() {

View File

@ -15,15 +15,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "debuggerSocketConnect",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
@ -3988,7 +3979,6 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget)
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
this._onFileActivity = this._onFileActivity.bind(this);
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onListTabs = this._onListTabs.bind(this);
this._onAttachTab = this._onAttachTab.bind(this);
this._onAttachConsole = this._onAttachConsole.bind(this);
this._onCachedMessages = this._onCachedMessages.bind(this);
@ -4072,17 +4062,6 @@ WebConsoleConnectionProxy.prototype = {
*/
_hasNativeConsoleAPI: false,
/**
* Initialize the debugger server.
*/
initServer: function WCCP_initServer()
{
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
},
/**
* Initialize a debugger client and connect it to the debugger server.
*
@ -4111,16 +4090,7 @@ WebConsoleConnectionProxy.prototype = {
this._connectTimer = null;
}.bind(this));
// TODO: convert the non-remote path to use the target API as well.
let transport, client;
if (this.target.isRemote) {
client = this.client = this.target.client;
}
else {
this.initServer();
transport = DebuggerServer.connectPipe();
client = this.client = new DebuggerClient(transport);
}
let client = this.client = this.target.client;
client.addListener("pageError", this._onPageError);
client.addListener("consoleAPICall", this._onConsoleAPICall);
@ -4129,21 +4099,14 @@ WebConsoleConnectionProxy.prototype = {
client.addListener("fileActivity", this._onFileActivity);
client.addListener("tabNavigated", this._onTabNavigated);
if (this.target.isRemote) {
if (!this.target.chrome) {
// target.form is a TabActor grip
this._attachTab(this.target.form);
}
else {
// target.form is a RootActor grip
this._consoleActor = this.target.form.consoleActor;
this._attachConsole();
}
if (!this.target.chrome) {
// target.form is a TabActor grip
this._attachTab(this.target.form);
}
else {
client.connect(function(aType, aTraits) {
client.listTabs(this._onListTabs);
}.bind(this));
// target.form is a RootActor grip
this._consoleActor = this.target.form.consoleActor;
this._attachConsole();
}
return promise;
@ -4163,25 +4126,6 @@ WebConsoleConnectionProxy.prototype = {
this._connectDefer.reject(error);
},
/**
* The "listTabs" response handler.
*
* @private
* @param object aResponse
* The JSON response object received from the server.
*/
_onListTabs: function WCCP__onListTabs(aResponse)
{
if (aResponse.error) {
Cu.reportError("listTabs failed: " + aResponse.error + " " +
aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
this._attachTab(aResponse.tabs[aResponse.selected]);
},
/**
* Attach to the tab actor.
*
@ -4433,21 +4377,6 @@ WebConsoleConnectionProxy.prototype = {
return this._disconnecter.promise;
}
let onDisconnect = function() {
if (timer) {
timer.cancel();
timer = null;
this._disconnecter.resolve(null);
}
}.bind(this);
let timer = null;
let remoteTarget = this.target.isRemote;
if (!remoteTarget) {
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(onDisconnect, 1500, Ci.nsITimer.TYPE_ONE_SHOT);
}
this.client.removeListener("pageError", this._onPageError);
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
this.client.removeListener("networkEvent", this._onNetworkEvent);
@ -4455,28 +4384,13 @@ WebConsoleConnectionProxy.prototype = {
this.client.removeListener("fileActivity", this._onFileActivity);
this.client.removeListener("tabNavigated", this._onTabNavigated);
let client = this.client;
this.client = null;
this.webConsoleClient = null;
this.tabClient = null;
this.target = null;
this.connected = false;
this.owner = null;
if (!remoteTarget) {
try {
client.close(onDisconnect);
}
catch (ex) {
Cu.reportError("Web Console disconnect exception: " + ex);
Cu.reportError(ex.stack);
onDisconnect();
}
}
else {
onDisconnect();
}
this._disconnecter.resolve(null);
return this._disconnecter.promise;
},

View File

@ -679,7 +679,6 @@ DebuggerProgressListener.prototype = {
from: this._tabActor.actorID,
type: "tabNavigated",
url: aRequest.URI.spec,
title: "",
nativeConsoleAPI: true,
state: "start",
});