Bug 827083 - Cannot attach remote web console to Firefox Android; r=past

This commit is contained in:
Mihai Sucan 2013-01-11 19:31:09 +02:00
Родитель 0f057d1516
Коммит aa99330a69
9 изменённых файлов: 394 добавлений и 635 удалений

Просмотреть файл

@ -468,6 +468,10 @@ Toolbox.prototype = {
/**
* Create a host object based on the given host type.
*
* Warning: some hosts require that the toolbox target provides a reference to
* the attached tab. Not all Targets have a tab property - make sure you correctly
* mix and match hosts and targets.
*
* @param {string} hostType
* The host type of the new host object
*

Просмотреть файл

@ -13,7 +13,9 @@ const Cu = Components.utils;
const CONSOLEAPI_CLASS_ID = "{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource:///modules/devtools/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
"resource:///modules/devtools/Target.jsm");
@ -24,45 +26,30 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/promise/core.js");
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
this.EXPORTED_SYMBOLS = ["HUDService"];
function LogFactory(aMessagePrefix)
{
function log(aMessage) {
var _msg = aMessagePrefix + " " + aMessage + "\n";
dump(_msg);
}
return log;
}
let log = LogFactory("*** HUDService:");
// The HTML namespace.
const HTML_NS = "http://www.w3.org/1999/xhtml";
// The XUL namespace.
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
///////////////////////////////////////////////////////////////////////////
//// The HUD service
function HUD_SERVICE()
{
// These methods access the "this" object, but they're registered as
// event listeners. So we hammer in the "this" binding.
this.onWindowUnload = this.onWindowUnload.bind(this);
/**
* Keeps a reference for each HeadsUpDisplay that is created
*/
this.hudReferences = {};
};
}
HUD_SERVICE.prototype =
{
/**
* Keeps a reference for each HeadsUpDisplay that is created
* @type object
*/
hudReferences: null,
/**
* getter for UI commands to be used by the frontend
*
@ -72,12 +59,6 @@ HUD_SERVICE.prototype =
return HeadsUpDisplayUICommands;
},
/**
* The sequencer is a generator (after initialization) that returns unique
* integers
*/
sequencer: null,
/**
* Firefox-specific current tab getter
*
@ -88,134 +69,22 @@ HUD_SERVICE.prototype =
},
/**
* Activate a HeadsUpDisplay for the given tab context.
* Open a Web Console for the given target.
*
* @param nsIDOMElement aTab
* The xul:tab element.
* @see devtools/framework/Target.jsm for details about targets.
*
* @param object aTarget
* The target that the web console will connect to.
* @param nsIDOMElement aIframe
* The iframe element into which to place the web console.
* @param RemoteTarget aTarget
* The target that the web console will connect to.
* @return object
* The new HeadsUpDisplay instance.
* A Promise object for the opening of the new WebConsole instance.
*/
activateHUDForContext: function HS_activateHUDForContext(aTab, aIframe,
aTarget)
openWebConsole: function HS_openWebConsole(aTarget, aIframe)
{
let hudId = "hud_" + aTab.linkedPanel;
if (hudId in this.hudReferences) {
return this.hudReferences[hudId];
}
this.wakeup();
let window = aTab.ownerDocument.defaultView;
let gBrowser = window.gBrowser;
window.addEventListener("unload", this.onWindowUnload, false);
let hud = new WebConsole(aTab, aIframe, aTarget);
this.hudReferences[hudId] = hud;
return hud;
},
/**
* Deactivate a HeadsUpDisplay for the given tab context.
*
* @param nsIDOMElement aTab
* The xul:tab element you want to enable the Web Console for.
* @return void
*/
deactivateHUDForContext: function HS_deactivateHUDForContext(aTab)
{
let hudId = "hud_" + aTab.linkedPanel;
if (!(hudId in this.hudReferences)) {
return;
}
let hud = this.getHudReferenceById(hudId);
let document = hud.chromeDocument;
hud.destroy(function() {
let id = WebConsoleUtils.supportsString(hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
});
delete this.hudReferences[hudId];
if (Object.keys(this.hudReferences).length == 0) {
let autocompletePopup = document.
getElementById("webConsole_autocompletePopup");
if (autocompletePopup) {
autocompletePopup.parentNode.removeChild(autocompletePopup);
}
let window = document.defaultView;
window.removeEventListener("unload", this.onWindowUnload, false);
let gBrowser = window.gBrowser;
let tabContainer = gBrowser.tabContainer;
this.suspend();
}
let contentWindow = aTab.linkedBrowser.contentWindow;
contentWindow.focus();
},
/**
* get a unique ID from the sequence generator
*
* @returns integer
*/
sequenceId: function HS_sequencerId()
{
if (!this.sequencer) {
this.sequencer = this.createSequencer(-1);
}
return this.sequencer.next();
},
/**
* "Wake up" the Web Console activity. This is called when the first Web
* Console is open. This method initializes the various observers we have.
*
* @returns void
*/
wakeup: function HS_wakeup()
{
if (Object.keys(this.hudReferences).length > 0) {
return;
}
WebConsoleObserver.init();
},
/**
* Suspend Web Console activity. This is called when all Web Consoles are
* closed.
*
* @returns void
*/
suspend: function HS_suspend()
{
delete this.lastFinishedRequestCallback;
WebConsoleObserver.uninit();
},
/**
* Shutdown all HeadsUpDisplays on quit-application-granted.
*
* @returns void
*/
shutdown: function HS_shutdown()
{
for (let hud of this.hudReferences) {
this.deactivateHUDForContext(hud.tab);
}
let hud = new WebConsole(aTarget, aIframe);
this.hudReferences[hud.hudId] = hud;
return hud.init();
},
/**
@ -226,8 +95,13 @@ HUD_SERVICE.prototype =
*/
getHudByWindow: function HS_getHudByWindow(aContentWindow)
{
let hudId = this.getHudIdByWindow(aContentWindow);
return hudId ? this.hudReferences[hudId] : null;
for each (let hud in this.hudReferences) {
let target = hud.target;
if (target && target.tab && target.window === aContentWindow) {
return hud;
}
}
return null;
},
/**
@ -239,16 +113,8 @@ HUD_SERVICE.prototype =
*/
getHudIdByWindow: function HS_getHudIdByWindow(aContentWindow)
{
let window = this.currentContext();
let index =
window.gBrowser.getBrowserIndexForDocument(aContentWindow.document);
if (index == -1) {
return null;
}
let tab = window.gBrowser.tabs[index];
let hudId = "hud_" + tab.linkedPanel;
return hudId in this.hudReferences ? hudId : null;
let hud = this.getHudByWindow(aContentWindow);
return hud ? hud.hudId : null;
},
/**
@ -270,101 +136,45 @@ HUD_SERVICE.prototype =
* @type function
*/
lastFinishedRequestCallback: null,
/**
* Creates a generator that always returns a unique number for use in the
* indexes
*
* @returns Generator
*/
createSequencer: function HS_createSequencer(aInt)
{
function sequencer(aInt)
{
while(1) {
aInt++;
yield aInt;
}
}
return sequencer(aInt);
},
/**
* Called whenever a browser window closes. Cleans up any consoles still
* around.
*
* @param nsIDOMEvent aEvent
* The dispatched event.
* @returns void
*/
onWindowUnload: function HS_onWindowUnload(aEvent)
{
let window = aEvent.target.defaultView;
window.removeEventListener("unload", this.onWindowUnload, false);
let gBrowser = window.gBrowser;
let tabContainer = gBrowser.tabContainer;
let tab = tabContainer.firstChild;
while (tab != null) {
this.deactivateHUDForContext(tab);
tab = tab.nextSibling;
}
},
};
/**
* A WebConsole instance is an interactive console initialized *per tab*
* A WebConsole instance is an interactive console initialized *per target*
* that displays console log data as well as provides an interactive terminal to
* manipulate the current tab's document content.
* manipulate the target's document content.
*
* This object only wraps the iframe that holds the Web Console UI.
*
* @param nsIDOMElement aTab
* The xul:tab for which you want the WebConsole object.
* @constructor
* @param object aTarget
* The target that the web console will connect to.
* @param nsIDOMElement aIframe
* iframe into which we should create the WebConsole UI.
* @param RemoteTarget aTarget
* The target that the web console will connect to.
*/
function WebConsole(aTab, aIframe, aTarget)
function WebConsole(aTarget, aIframe)
{
this.tab = aTab;
if (this.tab == null) {
throw new Error('Missing tab');
}
this.iframe = aIframe;
if (this.iframe == null) {
console.trace();
throw new Error('Missing iframe');
}
this.chromeDocument = this.tab.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.hudId = "hud_" + this.tab.linkedPanel;
this.target = aTarget;
this._onIframeLoad = this._onIframeLoad.bind(this);
this.iframe.className = "web-console-frame";
this.iframe.addEventListener("load", this._onIframeLoad, true);
this.positionConsole();
this.chromeDocument = this.iframe.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.hudId = "hud_" + Date.now();
this.target = aTarget;
}
WebConsole.prototype = {
/**
* The xul:tab for which the current Web Console instance was created.
* @type nsIDOMElement
*/
tab: null,
chromeWindow: null,
chromeDocument: null,
hudId: null,
target: null,
iframe: null,
_destroyer: null,
get browserWindow()
{
return this.target.isLocalTab ?
this.chromeWindow.top : HUDService.currentContext();
},
/**
* Getter for HUDService.lastFinishedRequestCallback.
@ -380,7 +190,7 @@ WebConsole.prototype = {
*/
get mainPopupSet()
{
return this.chromeDocument.getElementById("mainPopupSet");
return this.browserWindow.document.getElementById("mainPopupSet");
},
/**
@ -392,18 +202,48 @@ WebConsole.prototype = {
return this.ui ? this.ui.outputNode : null;
},
get gViewSourceUtils() this.chromeWindow.gViewSourceUtils,
get gViewSourceUtils() this.browserWindow.gViewSourceUtils,
/**
* The "load" event handler for the Web Console iframe.
* @private
* Initialize the Web Console instance.
*
* @return object
* A Promise for the initialization.
*/
_onIframeLoad: function WC__onIframeLoad()
init: function WC_init()
{
this.iframe.removeEventListener("load", this._onIframeLoad, true);
let deferred = Promise.defer();
let onIframeLoad = function() {
this.iframe.removeEventListener("load", onIframeLoad, true);
initUI();
}.bind(this);
let initUI = function() {
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
this.ui = new this.iframeWindow.WebConsoleFrame(this);
this.ui.init().then(onSuccess, onFailure);
}.bind(this);
let onSuccess = function() {
deferred.resolve(this);
}.bind(this);
let onFailure = function(aReason) {
deferred.reject(aReason);
};
let win, doc;
if ((win = this.iframe.contentWindow) &&
(doc = win.document) &&
doc.readyState == "complete") {
this.iframe.addEventListener("load", onIframeLoad, true);
}
else {
initUI();
}
return deferred.promise;
},
/**
@ -418,50 +258,6 @@ WebConsole.prototype = {
return l10n.getFormatStr("webConsoleWindowTitleAndURL", [url]);
},
consoleWindowUnregisterOnHide: true,
/**
* Position the Web Console UI.
*/
positionConsole: function WC_positionConsole()
{
let lastIndex = -1;
if (this.outputNode && this.outputNode.getIndexOfFirstVisibleRow) {
lastIndex = this.outputNode.getIndexOfFirstVisibleRow() +
this.outputNode.getNumberOfVisibleRows() - 1;
}
this._beforePositionConsole(lastIndex);
},
/**
* Common code that needs to execute before the Web Console is repositioned.
* @private
* @param number aLastIndex
* The last visible message in the console output before repositioning
* occurred.
*/
_beforePositionConsole:
function WC__beforePositionConsole(aLastIndex)
{
if (!this.ui) {
return;
}
let onLoad = function() {
this.iframe.removeEventListener("load", onLoad, true);
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
this.ui.positionConsole(this.iframeWindow);
if (aLastIndex > -1 && aLastIndex < this.outputNode.getRowCount()) {
this.outputNode.ensureIndexIsVisible(aLastIndex);
}
}.bind(this);
this.iframe.addEventListener("load", onLoad, true);
},
/**
* The JSTerm object that manages the console's input.
* @see webconsole.js::JSTerm
@ -478,7 +274,9 @@ WebConsole.prototype = {
*/
_onClearButton: function WC__onClearButton()
{
this.chromeWindow.DeveloperToolbar.resetErrorsCount(this.tab);
if (this.target.isLocalTab) {
this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab);
}
},
/**
@ -498,7 +296,7 @@ WebConsole.prototype = {
*/
openLink: function WC_openLink(aLink)
{
this.chromeWindow.openUILinkIn(aLink, "tab");
this.browserWindow.openUILinkIn(aLink, "tab");
},
/**
@ -530,12 +328,13 @@ WebConsole.prototype = {
viewSourceInStyleEditor:
function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
{
let styleSheets = this.tab.linkedBrowser.contentWindow.document.styleSheets;
let styleSheets = {};
if (this.target.isLocalTab) {
styleSheets = this.target.window.document.styleSheets;
}
for each (let style in styleSheets) {
if (style.href == aSourceURL) {
let target = TargetFactory.forTab(this.tab);
let gDevTools = this.chromeWindow.gDevTools;
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
toolbox.getCurrentPanel().selectStyleSheet(style, aSourceLine);
});
return;
@ -549,15 +348,20 @@ WebConsole.prototype = {
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.
*
* @param function [aOnDestroy]
* Optional function to invoke when the Web Console instance is
* destroyed.
* @return object
* A Promise object that is resolved once the Web Console is closed.
*/
destroy: function WC_destroy(aOnDestroy)
destroy: function WC_destroy()
{
// Make sure that the console panel does not try to call
// deactivateHUDForContext() again.
this.consoleWindowUnregisterOnHide = false;
if (this._destroyer) {
return this._destroyer.promise;
}
delete HUDService.hudReferences[this.hudId];
let tabWindow = this.target.isLocalTab ? this.target.window : null;
this._destroyer = Promise.defer();
let popupset = this.mainPopupSet;
let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
@ -566,27 +370,27 @@ WebConsole.prototype = {
}
let onDestroy = function WC_onDestroyUI() {
// Remove the iframe and the consolePanel if the Web Console is inside a
// floating panel.
if (this.consolePanel && this.consolePanel.parentNode) {
this.consolePanel.hidePopup();
this.consolePanel.parentNode.removeChild(this.consolePanel);
this.consolePanel = null;
try {
tabWindow && tabWindow.focus();
}
catch (ex) {
// Tab focus can fail if the tab is closed.
}
if (this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
aOnDestroy && aOnDestroy();
this._destroyer.resolve(null);
}.bind(this);
if (this.ui) {
this.ui.destroy(onDestroy);
this.ui.destroy().then(onDestroy);
}
else {
onDestroy();
}
return this._destroyer.promise;
},
};
@ -595,9 +399,16 @@ WebConsole.prototype = {
//////////////////////////////////////////////////////////////////////////
var HeadsUpDisplayUICommands = {
toggleHUD: function UIC_toggleHUD(aOptions)
/**
* Toggle the Web Console for the current tab.
*
* @return object
* A Promise for either the opening of the toolbox that holds the Web
* Console, or a Promise for the closing of the toolbox.
*/
toggleHUD: function UIC_toggleHUD()
{
var window = HUDService.currentContext();
let window = HUDService.currentContext();
let target = TargetFactory.forTab(window.gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
@ -606,84 +417,21 @@ var HeadsUpDisplayUICommands = {
gDevTools.showToolbox(target, "webconsole");
},
toggleRemoteHUD: function UIC_toggleRemoteHUD()
{
if (this.getOpenHUD()) {
this.toggleHUD();
return;
}
let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
let check = { value: false };
let input = { value: host + ":" + port };
let result = Services.prompt.prompt(null,
l10n.getStr("remoteWebConsolePromptTitle"),
l10n.getStr("remoteWebConsolePromptMessage"),
input, null, check);
if (!result) {
return;
}
let parts = input.value.split(":");
if (parts.length != 2) {
return;
}
[host, port] = parts;
if (!host.length || !port.length) {
return;
}
Services.prefs.setCharPref("devtools.debugger.remote-host", host);
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
this.toggleHUD({
host: host,
port: port,
});
},
/**
* Find the hudId for the active chrome window.
* @return string|null
* The hudId or null if the active chrome window has no open Web
* Find if there is a Web Console open for the current tab and return the
* instance.
* @return object|null
* The WebConsole object or null if the active tab has no open Web
* Console.
*/
getOpenHUD: function UIC_getOpenHUD() {
let chromeWindow = HUDService.currentContext();
let hudId = "hud_" + chromeWindow.gBrowser.selectedTab.linkedPanel;
return hudId in HUDService.hudReferences ? hudId : null;
},
};
//////////////////////////////////////////////////////////////////////////
// WebConsoleObserver
//////////////////////////////////////////////////////////////////////////
var WebConsoleObserver = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
init: function WCO_init()
getOpenHUD: function UIC_getOpenHUD()
{
Services.obs.addObserver(this, "quit-application-granted", false);
},
observe: function WCO_observe(aSubject, aTopic)
{
if (aTopic == "quit-application-granted") {
HUDService.shutdown();
}
},
uninit: function WCO_uninit()
{
Services.obs.removeObserver(this, "quit-application-granted");
let window = HUDService.currentContext();
let target = TargetFactory.forTab(window.gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
let panel = toolbox ? toolbox.getPanel("webconsole") : null;
return panel ? panel.hud : null;
},
};
const HUDService = new HUD_SERVICE();

Просмотреть файл

@ -9,13 +9,13 @@ this.EXPORTED_SYMBOLS = [ "WebConsolePanel" ];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/commonjs/promise/core.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
"resource:///modules/HUDService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
"resource:///modules/devtools/EventEmitter.jsm");
/**
* A DevToolPanel that controls the Web Console.
*/
@ -26,32 +26,29 @@ function WebConsolePanel(iframeWindow, toolbox) {
}
WebConsolePanel.prototype = {
hud: null,
/**
* open is effectively an asynchronous constructor
* Open is effectively an asynchronous constructor.
*
* @return object
* A Promise that is resolved when the Web Console completes opening.
*/
open: function StyleEditor_open() {
let parentDoc = this._frameWindow.document.defaultView.parent.document;
open: function WCP_open()
{
let parentDoc = this._toolbox.doc;
let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
this.hud = HUDService.activateHUDForContext(this.target.tab, iframe,
this._toolbox.target);
let deferred = Promise.defer();
let hudId = this.hud.hudId;
let onOpen = function _onWebConsoleOpen(aSubject) {
aSubject.QueryInterface(Ci.nsISupportsString);
if (hudId == aSubject.data) {
Services.obs.removeObserver(onOpen, "web-console-created");
let promise = HUDService.openWebConsole(this.target, iframe);
return promise.then(function onSuccess(aWebConsole) {
this.hud = aWebConsole;
this._isReady = true;
this.emit("ready");
deferred.resolve(this);
}
}.bind(this);
Services.obs.addObserver(onOpen, "web-console-created", false);
return deferred.promise;
return this;
}.bind(this), function onError(aReason) {
Cu.reportError("WebConsolePanel open failed. " +
aReason.error + ": " + aReason.message);
});
},
get target() this._toolbox.target,
@ -61,28 +58,15 @@ WebConsolePanel.prototype = {
destroy: function WCP_destroy()
{
if (this.destroyer) {
return this.destroyer.promise;
if (this._destroyer) {
return this._destroyer;
}
this.destroyer = Promise.defer();
let hudId = this.hud.hudId;
let onClose = function _onWebConsoleClose(aSubject)
{
aSubject.QueryInterface(Ci.nsISupportsString);
if (hudId == aSubject.data) {
Services.obs.removeObserver(onClose, "web-console-destroyed");
this._destroyer = this.hud.destroy();
this._destroyer.then(function() {
this.emit("destroyed");
this.destroyer.resolve(null);
}
}.bind(this);
}.bind(this));
Services.obs.addObserver(onClose, "web-console-destroyed", false);
HUDService.deactivateHUDForContext(this.hud.tab, false);
return this.destroyer.promise;
return this._destroyer;
},
};

Просмотреть файл

@ -28,14 +28,17 @@ function testClosingAfterCompletion(hud) {
// Focus the inputNode and perform the keycombo to close the WebConsole.
inputNode.focus();
EventUtils.synthesizeKey("k", { accelKey: true, shiftKey: true });
// We can't test for errors right away, because the error occurs after a
// setTimeout(..., 0) in the WebConsole code.
executeSoon(function() {
gDevTools.once("toolbox-destroyed", function() {
browser.removeEventListener("error", errorListener, false);
is(errorWhileClosing, false, "no error while closing the WebConsole");
finishTest();
});
if (Services.appinfo.OS == "Darwin") {
EventUtils.synthesizeKey("k", { accelKey: true, altKey: true });
} else {
EventUtils.synthesizeKey("k", { accelKey: true, shiftKey: true });
}
}

Просмотреть файл

@ -57,13 +57,14 @@ function openConsoles() {
let tab = openTabs[i];
openConsole(tab, function(index, hud) {
ok(hud, "HUD is open for tab " + index);
let window = hud.tab.linkedBrowser.contentWindow;
let window = hud.target.tab.linkedBrowser.contentWindow;
window.console.log("message for tab " + index);
consolesOpen++;
}.bind(null, i));
}
waitForSuccess({
timeout: 10000,
name: "4 web consoles opened",
validatorFn: function()
{

Просмотреть файл

@ -67,6 +67,7 @@ function onStyleEditorReady(aEvent, aPanel)
return sheet;
}
}
return null;
}
waitForFocus(function() {

Просмотреть файл

@ -238,9 +238,11 @@ function finishTest()
finish();
return;
}
if (hud.jsterm) {
hud.jsterm.clearOutput(true);
}
closeConsole(hud.tab, finish);
closeConsole(hud.target.tab, finish);
hud = null;
}

Просмотреть файл

@ -43,6 +43,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/promise/core.js");
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
@ -168,9 +171,9 @@ const MIN_FONT_SIZE = 10;
const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
/**
* A WebConsoleFrame instance is an interactive console initialized *per tab*
* A WebConsoleFrame instance is an interactive console initialized *per target*
* that displays console log data as well as provides an interactive terminal to
* manipulate the current tab's document content.
* manipulate the target's document content.
*
* The WebConsoleFrame is responsible for the actual Web Console UI
* implementation.
@ -190,18 +193,9 @@ function WebConsoleFrame(aWebConsoleOwner)
this._toggleFilter = this._toggleFilter.bind(this);
this._flushMessageQueue = this._flushMessageQueue.bind(this);
this._connectionTimeout = this._connectionTimeout.bind(this);
this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._outputTimerInitialized = false;
this._initDefaultFilterPrefs();
this._commandController = new CommandController(this);
this.positionConsole(window);
this.jsterm = new JSTerm(this);
this.jsterm.inputNode.focus();
this._initConnection();
}
WebConsoleFrame.prototype = {
@ -222,13 +216,6 @@ WebConsoleFrame.prototype = {
*/
proxy: null,
/**
* Timer used for the connection.
* @private
* @type object
*/
_connectTimer: null,
/**
* Getter for the xul:popupset that holds any popups we open.
* @type nsIDOMElement
@ -306,7 +293,7 @@ WebConsoleFrame.prototype = {
groupDepth: 0,
/**
* The current tab location.
* The current target location.
* @type string
*/
contentLocation: "",
@ -336,6 +323,8 @@ WebConsoleFrame.prototype = {
*/
get webConsoleClient() this.proxy ? this.proxy.webConsoleClient : null,
_destroyer: null,
_saveRequestAndResponseBodies: false,
/**
@ -364,60 +353,51 @@ WebConsoleFrame.prototype = {
}.bind(this));
},
/**
* Initialize the WebConsoleFrame instance.
* @return object
* A Promise object for the initialization.
*/
init: function WCF_init()
{
this._initUI();
return this._initConnection();
},
/**
* Connect to the server using the remote debugging protocol.
*
* @private
* @return object
* A Promise object that is resolved/reject based on the connection
* result.
*/
_initConnection: function WCF__initConnection()
{
let deferred = Promise.defer();
this.proxy = new WebConsoleConnectionProxy(this, this.owner.target);
let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT);
this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._connectTimer.initWithCallback(this._connectionTimeout,
timeout, Ci.nsITimer.TYPE_ONE_SHOT);
this.proxy.connect(function() {
// Don't complete connection if the connection timed-out.
if (this._connectTimer) {
this._connectTimer.cancel();
this._connectTimer = null;
let onSuccess = function() {
this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
this._onInitComplete();
}
}.bind(this));
},
/**
* Connection timeout handler. This method simply prints a message informing
* the user that the connection timed-out.
* @private
*/
_connectionTimeout: function WCF__connectionTimeout()
{
this._connectTimer = null;
deferred.resolve(this);
}.bind(this);
let onFailure = function(aReason) {
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_ERROR,
l10n.getStr("connectionTimeout"));
aReason.error + ": " + aReason.message);
this.outputMessage(CATEGORY_JS, node);
deferred.reject(aReason);
}.bind(this);
// Allow initialization to complete.
this._onInitComplete();
},
let sendNotification = function() {
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-created", null);
}.bind(this);
/**
* Reset the connection timeout timer.
* @private
*/
_resetConnectionTimeout: function WCF__resetConnectionTimeout()
{
let timer = this._connectTimer;
if (timer) {
let timeout = timer.delay;
timer.cancel();
timer.initWithCallback(this._connectionTimeout, timeout,
Ci.nsITimer.TYPE_ONE_SHOT);
}
this.proxy.connect().then(onSuccess, onFailure).then(sendNotification);
return deferred.promise;
},
/**
@ -426,6 +406,18 @@ WebConsoleFrame.prototype = {
*/
_initUI: function WCF__initUI()
{
// Remember that this script is loaded in the webconsole.xul context:
// |window| is the iframe global.
this.window = window;
this.document = this.window.document;
this.rootElement = this.document.documentElement;
this._initDefaultFilterPrefs();
// Register the controller to handle "select all" properly.
this._commandController = new CommandController(this);
this.window.controllers.insertControllerAt(0, this._commandController);
let doc = this.document;
this.filterBox = doc.querySelector(".hud-filter-box");
@ -482,6 +474,10 @@ WebConsoleFrame.prototype = {
this.owner._onClearButton();
this.jsterm.clearOutput(true);
}.bind(this));
this.jsterm = new JSTerm(this);
this.jsterm.init();
this.jsterm.inputNode.focus();
},
/**
@ -567,54 +563,6 @@ WebConsoleFrame.prototype = {
}, this);
},
/**
* Callback method for when the Web Console initialization is complete. For
* now this method sends the web-console-created notification using the
* nsIObserverService.
*
* @private
*/
_onInitComplete: function WC__onInitComplete()
{
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-created", null);
},
/**
* Position the console in a different location.
*
* Note: you do not usually call this method. This is called by the WebConsole
* instance that owns this iframe. You need to call this if you write
* a different owner or you manually reposition the iframe.
*
* @param object aNewWindow
* Repositioning causes the iframe to reload - bug 254144. You need to
* provide the new window object so we can reinitialize the UI as
* needed.
*/
positionConsole: function WCF_positionConsole(aNewWindow)
{
this.window = aNewWindow;
this.document = this.window.document;
this.rootElement = this.document.documentElement;
// register the controller to handle "select all" properly
this.window.controllers.insertControllerAt(0, this._commandController);
let oldOutputNode = this.outputNode;
this._initUI();
this.jsterm && this.jsterm._initUI();
if (oldOutputNode && oldOutputNode.childNodes.length) {
let parentNode = this.outputNode.parentNode;
parentNode.replaceChild(oldOutputNode, this.outputNode);
this.outputNode = oldOutputNode;
}
this.jsterm && this.jsterm.inputNode.focus();
},
/**
* Increase, decrease or reset the font size.
*
@ -2706,15 +2654,21 @@ WebConsoleFrame.prototype = {
},
/**
* Destroy the HUD object. Call this method to avoid memory leaks when the Web
* Console is closed.
* Destroy the WebConsoleFrame object. Call this method to avoid memory leaks
* when the Web Console is closed.
*
* @param function [aOnDestroy]
* Optional function to invoke when the Web Console instance is
* @return object
* A Promise that is resolved when the WebConsoleFrame instance is
* destroyed.
*/
destroy: function WCF_destroy(aOnDestroy)
destroy: function WCF_destroy()
{
if (this._destroyer) {
return this._destroyer.promise;
}
this._destroyer = Promise.defer();
this._cssNodes = {};
this._outputQueue = [];
this._pruneCategoriesQueue = {};
@ -2726,22 +2680,26 @@ WebConsoleFrame.prototype = {
}
this._outputTimer = null;
if (this._connectTimer) {
this._connectTimer.cancel();
}
this._connectTimer = null;
if (this.proxy) {
this.proxy.disconnect(aOnDestroy);
this.proxy = null;
}
if (this.jsterm) {
this.jsterm.destroy();
this.jsterm = null;
}
this._commandController = null;
let onDestroy = function() {
this._destroyer.resolve(null);
}.bind(this);
if (this.proxy) {
this.proxy.disconnect().then(onDestroy);
this.proxy = null;
}
else {
onDestroy();
}
return this._destroyer.promise;
},
};
@ -2763,12 +2721,8 @@ function JSTerm(aWebConsoleFrame)
this.history = [];
this.historyIndex = 0;
this.historyPlaceHolder = 0; // this.history.length;
this.autocompletePopup = new AutocompletePopup(this.hud.owner.chromeDocument);
this.autocompletePopup.onSelect = this.onAutocompleteSelect.bind(this);
this.autocompletePopup.onClick = this.acceptProposedCompletion.bind(this);
this._keyPress = this.keyPress.bind(this);
this._inputEventHandler = this.inputEventHandler.bind(this);
this._initUI();
}
JSTerm.prototype = {
@ -2790,6 +2744,10 @@ JSTerm.prototype = {
*/
history: null,
autocompletePopup: null,
inputNode: null,
completeNode: null,
/**
* Getter for the element that holds the messages we display.
* @type nsIDOMElement
@ -2808,10 +2766,14 @@ JSTerm.prototype = {
/**
* Initialize the JSTerminal UI.
* @private
*/
_initUI: function JST__initUI()
init: function JST_init()
{
let chromeDocument = this.hud.owner.chromeDocument;
this.autocompletePopup = new AutocompletePopup(chromeDocument);
this.autocompletePopup.onSelect = this.onAutocompleteSelect.bind(this);
this.autocompletePopup.onClick = this.acceptProposedCompletion.bind(this);
let doc = this.hud.document;
this.completeNode = doc.querySelector(".jsterm-complete-node");
this.inputNode = doc.querySelector(".jsterm-input-node");
@ -3819,6 +3781,12 @@ JSTerm.prototype = {
this.autocompletePopup.destroy();
this.autocompletePopup = null;
let popup = this.hud.owner.chromeDocument
.getElementById("webConsole_autocompletePopup");
if (popup) {
popup.parentNode.removeChild(popup);
}
this.inputNode.removeEventListener("keypress", this._keyPress, false);
this.inputNode.removeEventListener("input", this._inputEventHandler, false);
this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
@ -4047,6 +4015,11 @@ 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);
this._connectionTimeout = this._connectionTimeout.bind(this);
}
WebConsoleConnectionProxy.prototype = {
@ -4092,6 +4065,16 @@ WebConsoleConnectionProxy.prototype = {
*/
connected: false,
/**
* Timer used for the connection.
* @private
* @type object
*/
_connectTimer: null,
_connectDefer: null,
_disconnecter: null,
/**
* The WebConsoleActor ID.
*
@ -4130,11 +4113,31 @@ WebConsoleConnectionProxy.prototype = {
/**
* Initialize a debugger client and connect it to the debugger server.
*
* @param function [aCallback]
* Optional function to invoke when connection is established.
* @return object
* A Promise object that is resolved/rejected based on the success of
* the connection initialization.
*/
connect: function WCCP_connect(aCallback)
connect: function WCCP_connect()
{
if (this._connectDefer) {
return this._connectDefer.promise;
}
this._connectDefer = Promise.defer();
let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT);
this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._connectTimer.initWithCallback(this._connectionTimeout,
timeout, Ci.nsITimer.TYPE_ONE_SHOT);
let promise = this._connectDefer.promise;
promise.then(function _onSucess() {
this._connectTimer.cancel();
this._connectTimer = null;
}.bind(this), function _onFailure() {
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) {
@ -4143,7 +4146,6 @@ WebConsoleConnectionProxy.prototype = {
else {
this.initServer();
transport = DebuggerServer.connectPipe();
client = this.client = new DebuggerClient(transport);
}
@ -4157,39 +4159,54 @@ WebConsoleConnectionProxy.prototype = {
if (this.target.isRemote) {
if (!this.target.chrome) {
// target.form is a TabActor grip
this._attachTab(this.target.form, aCallback);
this._attachTab(this.target.form);
}
else {
// target.form is a RootActor grip
this._consoleActor = this.target.form.consoleActor;
this._attachConsole(aCallback);
this._attachConsole();
}
}
else {
client.connect(function(aType, aTraits) {
client.listTabs(this._onListTabs.bind(this, aCallback));
client.listTabs(this._onListTabs);
}.bind(this));
}
return promise;
},
/**
* Connection timeout handler.
* @private
*/
_connectionTimeout: function WCCP__connectionTimeout()
{
let error = {
error: "timeout",
message: l10n.getStr("connectionTimeout"),
};
this._connectDefer.reject(error);
},
/**
* The "listTabs" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
*/
_onListTabs: function WCCP__onListTabs(aCallback, aResponse)
_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], aCallback);
this._attachTab(aResponse.tabs[aResponse.selected]);
},
/**
@ -4198,74 +4215,65 @@ WebConsoleConnectionProxy.prototype = {
* @private
* @param object aTab
* Grip for the tab to attach to.
* @param function aCallback
* Function to invoke when the connection is established.
*/
_attachTab: function WCCP__attachTab(aTab, aCallback)
_attachTab: function WCCP__attachTab(aTab)
{
this._consoleActor = aTab.consoleActor;
this._tabActor = aTab.actor;
this.owner.onLocationChange(aTab.url, aTab.title);
this.client.attachTab(this._tabActor,
this._onAttachTab.bind(this, aCallback));
this.client.attachTab(this._tabActor, this._onAttachTab);
},
/**
* The "attachTab" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
* @param object aTabClient
* The TabClient instance for the attached tab.
*/
_onAttachTab: function WCCP__onAttachTab(aCallback, aResponse, aTabClient)
_onAttachTab: function WCCP__onAttachTab(aResponse, aTabClient)
{
if (aResponse.error) {
Cu.reportError("attachTab failed: " + aResponse.error + " " +
aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
this.tabClient = aTabClient;
this._attachConsole(aCallback);
this._attachConsole();
},
/**
* Attach to the Web Console actor.
*
* @private
* @param function aCallback
* Function to invoke when the connection is established.
*/
_attachConsole: function WCCP__attachConsole(aCallback)
_attachConsole: function WCCP__attachConsole()
{
let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
"FileActivity"];
this.client.attachConsole(this._consoleActor, listeners,
this._onAttachConsole.bind(this, aCallback));
this._onAttachConsole);
},
/**
* The "attachConsole" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
* @param object aWebConsoleClient
* The WebConsoleClient instance for the attached console, for the
* specific tab we work with.
*/
_onAttachConsole:
function WCCP__onAttachConsole(aCallback, aResponse, aWebConsoleClient)
_onAttachConsole: function WCCP__onAttachConsole(aResponse, aWebConsoleClient)
{
if (aResponse.error) {
Cu.reportError("attachConsole failed: " + aResponse.error + " " +
aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
@ -4274,27 +4282,31 @@ WebConsoleConnectionProxy.prototype = {
this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;
let msgs = ["PageError", "ConsoleAPI"];
this.webConsoleClient.getCachedMessages(msgs,
this._onCachedMessages.bind(this, aCallback));
this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
},
/**
* The "cachedMessages" response handler.
*
* @private
* @param function [aCallback]
* Optional function to invoke once the connection is established.
* @param object aResponse
* The JSON response object received from the server.
*/
_onCachedMessages: function WCCP__onCachedMessages(aCallback, aResponse)
_onCachedMessages: function WCCP__onCachedMessages(aResponse)
{
if (aResponse.error) {
Cu.reportError("Web Console getCachedMessages error: " + aResponse.error +
" " + aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
if (!this._connectTimer) {
// This happens if the Promise is rejected (eg. a timeout), but the
// connection attempt is successful, nonetheless.
Cu.reportError("Web Console getCachedMessages error: invalid state.");
}
this.owner.displayCachedMessages(aResponse.messages);
if (!this._hasNativeConsoleAPI) {
@ -4302,7 +4314,7 @@ WebConsoleConnectionProxy.prototype = {
}
this.connected = true;
aCallback && aCallback();
this._connectDefer.resolve(this);
},
/**
@ -4432,30 +4444,33 @@ WebConsoleConnectionProxy.prototype = {
/**
* Disconnect the Web Console from the remote server.
*
* @param function [aOnDisconnect]
* Optional function to invoke when the connection is dropped.
* @return object
* A Promise object that is resolved when disconnect completes.
*/
disconnect: function WCCP_disconnect(aOnDisconnect)
disconnect: function WCCP_disconnect()
{
if (this._disconnecter) {
return this._disconnecter.promise;
}
this._disconnecter = Promise.defer();
if (!this.client) {
aOnDisconnect && aOnDisconnect();
return;
this._disconnecter.resolve(null);
return this._disconnecter.promise;
}
let onDisconnect = function() {
if (timer) {
timer.cancel();
timer = null;
this._disconnecter.resolve(null);
}
if (aOnDisconnect) {
aOnDisconnect();
aOnDisconnect = null;
}
};
}.bind(this);
let timer = null;
let remoteTarget = this.target.isRemote;
if (aOnDisconnect && !remoteTarget) {
if (!remoteTarget) {
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(onDisconnect, 1500, Ci.nsITimer.TYPE_ONE_SHOT);
}
@ -4476,20 +4491,21 @@ WebConsoleConnectionProxy.prototype = {
this.connected = false;
this.owner = null;
try {
if (!remoteTarget) {
try {
client.close(onDisconnect);
}
}
catch (ex) {
Cu.reportError("Web Console disconnect exception: " + ex);
Cu.reportError(ex.stack);
onDisconnect();
}
if (remoteTarget) {
}
else {
onDisconnect();
}
return this._disconnecter.promise;
},
};

Просмотреть файл

@ -183,7 +183,7 @@ WebConsoleActor.prototype =
this.consoleProgressListener.destroy();
this.consoleProgressListener = null;
}
this.conn.removeActorPool(this.actorPool);
this.conn.removeActorPool(this._actorPool);
this._actorPool = null;
this.sandbox = null;
this._sandboxWindowId = 0;