mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1191 lines
33 KiB
JavaScript
1191 lines
33 KiB
JavaScript
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cu = Components.utils;
|
|
|
|
const CONSOLEAPI_CLASS_ID = "{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}";
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
|
"resource://gre/modules/Services.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
|
|
"resource:///modules/WebConsoleUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
|
|
return WebConsoleUtils.l10n;
|
|
});
|
|
|
|
|
|
var 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";
|
|
|
|
|
|
// Possible directions that can be passed to HUDService.animate().
|
|
const ANIMATE_OUT = 0;
|
|
const ANIMATE_IN = 1;
|
|
|
|
// Minimum console height, in pixels.
|
|
const MINIMUM_CONSOLE_HEIGHT = 150;
|
|
|
|
// Minimum page height, in pixels. This prevents the Web Console from
|
|
// remembering a height that covers the whole page.
|
|
const MINIMUM_PAGE_HEIGHT = 50;
|
|
|
|
// The default console height, as a ratio from the content window inner height.
|
|
const DEFAULT_CONSOLE_HEIGHT = 0.33;
|
|
|
|
// This script is inserted into the content process.
|
|
const CONTENT_SCRIPT_URL = "chrome://browser/content/devtools/HUDService-content.js";
|
|
|
|
// points to the file to load in the Web Console iframe.
|
|
const UI_IFRAME_URL = "chrome://browser/content/devtools/webconsole.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.onTabClose = this.onTabClose.bind(this);
|
|
this.onTabSelect = this.onTabSelect.bind(this);
|
|
this.onWindowUnload = this.onWindowUnload.bind(this);
|
|
|
|
// Remembers the last console height, in pixels.
|
|
this.lastConsoleHeight = Services.prefs.getIntPref("devtools.hud.height");
|
|
|
|
/**
|
|
* Keeps a reference for each HeadsUpDisplay that is created
|
|
*/
|
|
this.hudReferences = {};
|
|
};
|
|
|
|
HUD_SERVICE.prototype =
|
|
{
|
|
/**
|
|
* getter for UI commands to be used by the frontend
|
|
*
|
|
* @returns object
|
|
*/
|
|
get consoleUI() {
|
|
return HeadsUpDisplayUICommands;
|
|
},
|
|
|
|
/**
|
|
* The sequencer is a generator (after initialization) that returns unique
|
|
* integers
|
|
*/
|
|
sequencer: null,
|
|
|
|
/**
|
|
* Firefox-specific current tab getter
|
|
*
|
|
* @returns nsIDOMWindow
|
|
*/
|
|
currentContext: function HS_currentContext() {
|
|
return Services.wm.getMostRecentWindow("navigator:browser");
|
|
},
|
|
|
|
/**
|
|
* Activate a HeadsUpDisplay for the given tab context.
|
|
*
|
|
* @param nsIDOMElement aTab
|
|
* The xul:tab element.
|
|
* @param boolean aAnimated
|
|
* True if you want to animate the opening of the Web console.
|
|
* @return object
|
|
* The new HeadsUpDisplay instance.
|
|
*/
|
|
activateHUDForContext: function HS_activateHUDForContext(aTab, aAnimated)
|
|
{
|
|
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;
|
|
|
|
gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose, false);
|
|
gBrowser.tabContainer.addEventListener("TabSelect", this.onTabSelect, false);
|
|
window.addEventListener("unload", this.onWindowUnload, false);
|
|
|
|
let hud = new WebConsole(aTab);
|
|
this.hudReferences[hudId] = hud;
|
|
|
|
if (!aAnimated || hud.consolePanel) {
|
|
this.disableAnimation(hudId);
|
|
}
|
|
|
|
HeadsUpDisplayUICommands.refreshCommand();
|
|
|
|
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.
|
|
* @param boolean aAnimated
|
|
* True if you want to animate the closing of the Web console.
|
|
* @return void
|
|
*/
|
|
deactivateHUDForContext: function HS_deactivateHUDForContext(aTab, aAnimated)
|
|
{
|
|
let hudId = "hud_" + aTab.linkedPanel;
|
|
if (!(hudId in this.hudReferences)) {
|
|
return;
|
|
}
|
|
|
|
if (!aAnimated) {
|
|
this.storeHeight(hudId);
|
|
}
|
|
|
|
let hud = this.getHudReferenceById(hudId);
|
|
let document = hud.chromeDocument;
|
|
|
|
hud.destroy();
|
|
|
|
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;
|
|
tabContainer.removeEventListener("TabClose", this.onTabClose, false);
|
|
tabContainer.removeEventListener("TabSelect", this.onTabSelect, false);
|
|
|
|
this.suspend();
|
|
}
|
|
|
|
let contentWindow = aTab.linkedBrowser.contentWindow;
|
|
contentWindow.focus();
|
|
|
|
HeadsUpDisplayUICommands.refreshCommand();
|
|
|
|
let id = WebConsoleUtils.supportsString(hudId);
|
|
Services.obs.notifyObservers(id, "web-console-destroyed", null);
|
|
},
|
|
|
|
/**
|
|
* 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, false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the HeadsUpDisplay object associated to a content window.
|
|
*
|
|
* @param nsIDOMWindow aContentWindow
|
|
* @returns object
|
|
*/
|
|
getHudByWindow: function HS_getHudByWindow(aContentWindow)
|
|
{
|
|
let hudId = this.getHudIdByWindow(aContentWindow);
|
|
return hudId ? this.hudReferences[hudId] : null;
|
|
},
|
|
|
|
/**
|
|
* Returns the hudId that is corresponding to the hud activated for the
|
|
* passed aContentWindow. If there is no matching hudId null is returned.
|
|
*
|
|
* @param nsIDOMWindow aContentWindow
|
|
* @returns string or null
|
|
*/
|
|
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;
|
|
},
|
|
|
|
/**
|
|
* Returns the hudReference for a given id.
|
|
*
|
|
* @param string aId
|
|
* @returns Object
|
|
*/
|
|
getHudReferenceById: function HS_getHudReferenceById(aId)
|
|
{
|
|
return aId in this.hudReferences ? this.hudReferences[aId] : null;
|
|
},
|
|
|
|
/**
|
|
* Assign a function to this property to listen for every request that
|
|
* completes. Used by unit tests. The callback takes one argument: the HTTP
|
|
* activity object as received from the remote Web Console.
|
|
*
|
|
* @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);
|
|
},
|
|
|
|
/**
|
|
* onTabClose event handler function
|
|
*
|
|
* @param aEvent
|
|
* @returns void
|
|
*/
|
|
onTabClose: function HS_onTabClose(aEvent)
|
|
{
|
|
this.deactivateHUDForContext(aEvent.target, false);
|
|
},
|
|
|
|
/**
|
|
* onTabSelect event handler function
|
|
*
|
|
* @param aEvent
|
|
* @returns void
|
|
*/
|
|
onTabSelect: function HS_onTabSelect(aEvent)
|
|
{
|
|
HeadsUpDisplayUICommands.refreshCommand();
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
|
|
tabContainer.removeEventListener("TabClose", this.onTabClose, false);
|
|
tabContainer.removeEventListener("TabSelect", this.onTabSelect, false);
|
|
|
|
let tab = tabContainer.firstChild;
|
|
while (tab != null) {
|
|
this.deactivateHUDForContext(tab, false);
|
|
tab = tab.nextSibling;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Animates the Console appropriately.
|
|
*
|
|
* @param string aHUDId The ID of the console.
|
|
* @param string aDirection Whether to animate the console appearing
|
|
* (ANIMATE_IN) or disappearing (ANIMATE_OUT).
|
|
* @param function aCallback An optional callback, which will be called with
|
|
* the "transitionend" event passed as a parameter once the animation
|
|
* finishes.
|
|
*/
|
|
animate: function HS_animate(aHUDId, aDirection, aCallback)
|
|
{
|
|
let hudBox = this.getHudReferenceById(aHUDId).iframe;
|
|
if (!hudBox.hasAttribute("animated")) {
|
|
if (aCallback) {
|
|
aCallback();
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (aDirection) {
|
|
case ANIMATE_OUT:
|
|
hudBox.style.height = 0;
|
|
break;
|
|
case ANIMATE_IN:
|
|
this.resetHeight(aHUDId);
|
|
break;
|
|
}
|
|
|
|
if (aCallback) {
|
|
hudBox.addEventListener("transitionend", aCallback, false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Disables all animation for a console, for unit testing. After this call,
|
|
* the console will instantly take on a reasonable height, and the close
|
|
* animation will not occur.
|
|
*
|
|
* @param string aHUDId The ID of the console.
|
|
*/
|
|
disableAnimation: function HS_disableAnimation(aHUDId)
|
|
{
|
|
let hudBox = HUDService.hudReferences[aHUDId].iframe;
|
|
if (hudBox.hasAttribute("animated")) {
|
|
hudBox.removeAttribute("animated");
|
|
this.resetHeight(aHUDId);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Reset the height of the Web Console.
|
|
*
|
|
* @param string aHUDId The ID of the Web Console.
|
|
*/
|
|
resetHeight: function HS_resetHeight(aHUDId)
|
|
{
|
|
let HUD = this.hudReferences[aHUDId];
|
|
let innerHeight = HUD.tab.linkedBrowser.clientHeight;
|
|
let chromeWindow = HUD.chromeWindow;
|
|
if (!HUD.consolePanel) {
|
|
let splitterStyle = chromeWindow.getComputedStyle(HUD.splitter, null);
|
|
innerHeight += parseInt(splitterStyle.height) +
|
|
parseInt(splitterStyle.borderTopWidth) +
|
|
parseInt(splitterStyle.borderBottomWidth);
|
|
}
|
|
|
|
let boxStyle = chromeWindow.getComputedStyle(HUD.iframe, null);
|
|
innerHeight += parseInt(boxStyle.height) +
|
|
parseInt(boxStyle.borderTopWidth) +
|
|
parseInt(boxStyle.borderBottomWidth);
|
|
|
|
let height = this.lastConsoleHeight > 0 ? this.lastConsoleHeight :
|
|
Math.ceil(innerHeight * DEFAULT_CONSOLE_HEIGHT);
|
|
|
|
if ((innerHeight - height) < MINIMUM_PAGE_HEIGHT) {
|
|
height = innerHeight - MINIMUM_PAGE_HEIGHT;
|
|
}
|
|
|
|
if (isNaN(height) || height < MINIMUM_CONSOLE_HEIGHT) {
|
|
height = MINIMUM_CONSOLE_HEIGHT;
|
|
}
|
|
|
|
HUD.iframe.style.height = height + "px";
|
|
},
|
|
|
|
/**
|
|
* Remember the height of the given Web Console, such that it can later be
|
|
* reused when other Web Consoles are open.
|
|
*
|
|
* @param string aHUDId The ID of the Web Console.
|
|
*/
|
|
storeHeight: function HS_storeHeight(aHUDId)
|
|
{
|
|
let hudBox = this.hudReferences[aHUDId].iframe;
|
|
let window = hudBox.ownerDocument.defaultView;
|
|
let style = window.getComputedStyle(hudBox, null);
|
|
let height = parseInt(style.height);
|
|
height += parseInt(style.borderTopWidth);
|
|
height += parseInt(style.borderBottomWidth);
|
|
this.lastConsoleHeight = height;
|
|
|
|
let pref = Services.prefs.getIntPref("devtools.hud.height");
|
|
if (pref > -1) {
|
|
Services.prefs.setIntPref("devtools.hud.height", height);
|
|
}
|
|
},
|
|
};
|
|
|
|
|
|
/**
|
|
* A WebConsole instance is an interactive console initialized *per tab*
|
|
* that displays console log data as well as provides an interactive terminal to
|
|
* manipulate the current tab'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.
|
|
*/
|
|
function WebConsole(aTab)
|
|
{
|
|
this.tab = aTab;
|
|
this._onIframeLoad = this._onIframeLoad.bind(this);
|
|
this._asyncRequests = {};
|
|
this._init();
|
|
}
|
|
|
|
WebConsole.prototype = {
|
|
/**
|
|
* The xul:tab for which the current Web Console instance was created.
|
|
* @type nsIDOMElement
|
|
*/
|
|
tab: null,
|
|
|
|
/**
|
|
* Getter for HUDService.lastFinishedRequestCallback.
|
|
*
|
|
* @see HUDService.lastFinishedRequestCallback
|
|
* @type function
|
|
*/
|
|
get lastFinishedRequestCallback() HUDService.lastFinishedRequestCallback,
|
|
|
|
/**
|
|
* Track callback functions registered for specific async requests sent to
|
|
* the content process.
|
|
*
|
|
* @private
|
|
* @type object
|
|
*/
|
|
_asyncRequests: null,
|
|
|
|
/**
|
|
* Message names that the HUD listens for. These messages come from the remote
|
|
* Web Console content script.
|
|
*
|
|
* @private
|
|
* @type array
|
|
*/
|
|
_messageListeners: ["JSTerm:EvalObject", "WebConsole:ConsoleAPI",
|
|
"WebConsole:CachedMessages", "WebConsole:PageError", "JSTerm:EvalResult",
|
|
"JSTerm:AutocompleteProperties", "JSTerm:ClearOutput",
|
|
"JSTerm:InspectObject", "WebConsole:NetworkActivity",
|
|
"WebConsole:FileActivity", "WebConsole:LocationChange",
|
|
"JSTerm:NonNativeConsoleAPI"],
|
|
|
|
/**
|
|
* The xul:panel that holds the Web Console when it is positioned as a window.
|
|
* @type nsIDOMElement
|
|
*/
|
|
consolePanel: null,
|
|
|
|
/**
|
|
* The current tab location.
|
|
* @type string
|
|
*/
|
|
contentLocation: "",
|
|
|
|
/**
|
|
* Getter for the xul:popupset that holds any popups we open.
|
|
* @type nsIDOMElement
|
|
*/
|
|
get mainPopupSet()
|
|
{
|
|
return this.chromeDocument.getElementById("mainPopupSet");
|
|
},
|
|
|
|
/**
|
|
* Getter for the output element that holds messages we display.
|
|
* @type nsIDOMElement
|
|
*/
|
|
get outputNode()
|
|
{
|
|
return this.ui ? this.ui.outputNode : null;
|
|
},
|
|
|
|
get gViewSourceUtils() this.chromeWindow.gViewSourceUtils,
|
|
|
|
/**
|
|
* Initialize the Web Console instance.
|
|
* @private
|
|
*/
|
|
_init: function WC__init()
|
|
{
|
|
this.chromeDocument = this.tab.ownerDocument;
|
|
this.chromeWindow = this.chromeDocument.defaultView;
|
|
this.messageManager = this.tab.linkedBrowser.messageManager;
|
|
this.hudId = "hud_" + this.tab.linkedPanel;
|
|
this.notificationBox = this.chromeDocument
|
|
.getElementById(this.tab.linkedPanel);
|
|
|
|
this._initUI();
|
|
},
|
|
|
|
/**
|
|
* Initialize the Web Console UI. This method sets up the iframe.
|
|
* @private
|
|
*/
|
|
_initUI: function WC__initUI()
|
|
{
|
|
this.splitter = this.chromeDocument.createElement("splitter");
|
|
this.splitter.setAttribute("class", "web-console-splitter");
|
|
|
|
this.iframe = this.chromeDocument.createElement("iframe");
|
|
this.iframe.setAttribute("id", this.hudId);
|
|
this.iframe.setAttribute("class", "web-console-frame");
|
|
this.iframe.setAttribute("animated", "true");
|
|
this.iframe.setAttribute("tooltip", "aHTMLTooltip");
|
|
this.iframe.style.height = 0;
|
|
this.iframe.addEventListener("load", this._onIframeLoad, true);
|
|
this.iframe.setAttribute("src", UI_IFRAME_URL);
|
|
|
|
let position = Services.prefs.getCharPref("devtools.webconsole.position");
|
|
this.positionConsole(position);
|
|
},
|
|
|
|
/**
|
|
* The "load" event handler for the Web Console iframe.
|
|
* @private
|
|
*/
|
|
_onIframeLoad: function WC__onIframeLoad()
|
|
{
|
|
this.iframe.removeEventListener("load", this._onIframeLoad, true);
|
|
|
|
let position = Services.prefs.getCharPref("devtools.webconsole.position");
|
|
|
|
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
|
|
this.ui = new this.iframeWindow.WebConsoleFrame(this, position);
|
|
this._setupMessageManager();
|
|
},
|
|
|
|
/**
|
|
* Create a panel to open the web console if it should float above
|
|
* the content in its own window.
|
|
* @private
|
|
*/
|
|
_createOwnWindowPanel: function WC__createOwnWindowPanel()
|
|
{
|
|
if (this.consolePanel) {
|
|
return;
|
|
}
|
|
|
|
let width = 0;
|
|
try {
|
|
width = Services.prefs.getIntPref("devtools.webconsole.width");
|
|
}
|
|
catch (ex) {}
|
|
|
|
if (width < 1) {
|
|
width = this.iframe.clientWidth || this.chromeWindow.innerWidth;
|
|
}
|
|
|
|
let height = this.iframe.clientHeight;
|
|
|
|
let top = 0;
|
|
try {
|
|
top = Services.prefs.getIntPref("devtools.webconsole.top");
|
|
}
|
|
catch (ex) {}
|
|
|
|
let left = 0;
|
|
try {
|
|
left = Services.prefs.getIntPref("devtools.webconsole.left");
|
|
}
|
|
catch (ex) {}
|
|
|
|
let panel = this.chromeDocument.createElementNS(XUL_NS, "panel");
|
|
|
|
let config = { id: "console_window_" + this.hudId,
|
|
label: this.getPanelTitle(),
|
|
titlebar: "normal",
|
|
noautohide: "true",
|
|
norestorefocus: "true",
|
|
close: "true",
|
|
flex: "1",
|
|
hudId: this.hudId,
|
|
width: width,
|
|
position: "overlap",
|
|
top: top,
|
|
left: left,
|
|
};
|
|
|
|
for (let attr in config) {
|
|
panel.setAttribute(attr, config[attr]);
|
|
}
|
|
|
|
panel.classList.add("web-console-panel");
|
|
|
|
let onPopupShown = (function HUD_onPopupShown() {
|
|
panel.removeEventListener("popupshown", onPopupShown, false);
|
|
|
|
// Make sure that the HUDBox size updates when the panel is resized.
|
|
|
|
let height = panel.clientHeight;
|
|
|
|
this.iframe.style.height = "auto";
|
|
this.iframe.flex = 1;
|
|
|
|
panel.setAttribute("height", height);
|
|
}).bind(this);
|
|
|
|
panel.addEventListener("popupshown", onPopupShown,false);
|
|
|
|
let onPopupHidden = (function HUD_onPopupHidden(aEvent) {
|
|
if (aEvent.target != panel) {
|
|
return;
|
|
}
|
|
|
|
panel.removeEventListener("popuphidden", onPopupHidden, false);
|
|
|
|
let width = 0;
|
|
try {
|
|
width = Services.prefs.getIntPref("devtools.webconsole.width");
|
|
}
|
|
catch (ex) { }
|
|
|
|
if (width > 0) {
|
|
Services.prefs.setIntPref("devtools.webconsole.width", panel.clientWidth);
|
|
}
|
|
|
|
// Are we destroying the HUD or repositioning it?
|
|
if (this.consoleWindowUnregisterOnHide) {
|
|
HUDService.deactivateHUDForContext(this.tab, false);
|
|
}
|
|
}).bind(this);
|
|
|
|
panel.addEventListener("popuphidden", onPopupHidden, false);
|
|
|
|
let lastIndex = -1;
|
|
|
|
if (this.outputNode && this.outputNode.getIndexOfFirstVisibleRow) {
|
|
lastIndex = this.outputNode.getIndexOfFirstVisibleRow() +
|
|
this.outputNode.getNumberOfVisibleRows() - 1;
|
|
}
|
|
|
|
if (this.splitter.parentNode) {
|
|
this.splitter.parentNode.removeChild(this.splitter);
|
|
}
|
|
|
|
this._beforePositionConsole("window", lastIndex);
|
|
|
|
panel.appendChild(this.iframe);
|
|
|
|
let space = this.chromeDocument.createElement("spacer");
|
|
space.flex = 1;
|
|
|
|
let bottomBox = this.chromeDocument.createElement("hbox");
|
|
|
|
let resizer = this.chromeDocument.createElement("resizer");
|
|
resizer.setAttribute("dir", "bottomend");
|
|
resizer.setAttribute("element", config.id);
|
|
|
|
bottomBox.appendChild(space);
|
|
bottomBox.appendChild(resizer);
|
|
|
|
panel.appendChild(bottomBox);
|
|
|
|
this.mainPopupSet.appendChild(panel);
|
|
|
|
panel.openPopup(null, "overlay", left, top, false, false);
|
|
|
|
this.consolePanel = panel;
|
|
this.consoleWindowUnregisterOnHide = true;
|
|
},
|
|
|
|
/**
|
|
* Retrieve the Web Console panel title.
|
|
*
|
|
* @return string
|
|
* The Web Console panel title.
|
|
*/
|
|
getPanelTitle: function WC_getPanelTitle()
|
|
{
|
|
return l10n.getFormatStr("webConsoleWindowTitleAndURL",
|
|
[this.contentLocation]);
|
|
},
|
|
|
|
positions: {
|
|
above: 0, // the childNode index
|
|
below: 2,
|
|
window: null
|
|
},
|
|
|
|
consoleWindowUnregisterOnHide: true,
|
|
|
|
/**
|
|
* Position the Web Console UI.
|
|
*
|
|
* @param string aPosition
|
|
* The desired Web Console UI location: above, below or window.
|
|
*/
|
|
positionConsole: function WC_positionConsole(aPosition)
|
|
{
|
|
if (!(aPosition in this.positions)) {
|
|
throw new Error("Incorrect argument: " + aPosition +
|
|
". Cannot position Web Console");
|
|
}
|
|
|
|
if (aPosition == "window") {
|
|
this._createOwnWindowPanel();
|
|
return;
|
|
}
|
|
|
|
let height = this.iframe.clientHeight;
|
|
|
|
// get the node position index
|
|
let nodeIdx = this.positions[aPosition];
|
|
let nBox = this.notificationBox;
|
|
let node = nBox.childNodes[nodeIdx];
|
|
|
|
// check to see if console is already positioned in aPosition
|
|
if (node == this.iframe) {
|
|
return;
|
|
}
|
|
|
|
let lastIndex = -1;
|
|
|
|
if (this.outputNode && this.outputNode.getIndexOfFirstVisibleRow) {
|
|
lastIndex = this.outputNode.getIndexOfFirstVisibleRow() +
|
|
this.outputNode.getNumberOfVisibleRows() - 1;
|
|
}
|
|
|
|
// remove the console and splitter and reposition
|
|
if (this.splitter.parentNode) {
|
|
this.splitter.parentNode.removeChild(this.splitter);
|
|
}
|
|
|
|
this._beforePositionConsole(aPosition, lastIndex);
|
|
|
|
if (aPosition == "below") {
|
|
nBox.appendChild(this.splitter);
|
|
nBox.appendChild(this.iframe);
|
|
}
|
|
else {
|
|
nBox.insertBefore(this.splitter, node);
|
|
nBox.insertBefore(this.iframe, this.splitter);
|
|
}
|
|
|
|
if (this.consolePanel) {
|
|
// must destroy the consolePanel
|
|
this.consoleWindowUnregisterOnHide = false;
|
|
this.consolePanel.hidePopup();
|
|
this.consolePanel.parentNode.removeChild(this.consolePanel);
|
|
this.consolePanel = null; // remove this as we're not in panel anymore
|
|
this.iframe.removeAttribute("flex");
|
|
this.iframe.removeAttribute("height");
|
|
this.iframe.style.height = height + "px";
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Common code that needs to execute before the Web Console is repositioned.
|
|
* @private
|
|
* @param string aPosition
|
|
* The new position: "above", "below" or "window".
|
|
* @param number aLastIndex
|
|
* The last visible message in the console output before repositioning
|
|
* occurred.
|
|
*/
|
|
_beforePositionConsole:
|
|
function WC__beforePositionConsole(aPosition, aLastIndex)
|
|
{
|
|
if (!this.ui) {
|
|
return;
|
|
}
|
|
|
|
let onLoad = function() {
|
|
this.iframe.removeEventListener("load", onLoad, true);
|
|
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
|
|
this.ui.positionConsole(aPosition, this.iframeWindow);
|
|
|
|
if (aLastIndex > -1 && aLastIndex < this.outputNode.getRowCount()) {
|
|
this.outputNode.ensureIndexIsVisible(aLastIndex);
|
|
}
|
|
|
|
this._currentUIPosition = aPosition;
|
|
Services.prefs.setCharPref("devtools.webconsole.position", aPosition);
|
|
}.bind(this);
|
|
|
|
this.iframe.addEventListener("load", onLoad, true);
|
|
},
|
|
|
|
/**
|
|
* The JSTerm object that manages the console's input.
|
|
* @see webconsole.js::JSTerm
|
|
* @type object
|
|
*/
|
|
get jsterm()
|
|
{
|
|
return this.ui ? this.ui.jsterm : null;
|
|
},
|
|
|
|
/**
|
|
* The close button handler.
|
|
*/
|
|
onCloseButton: function WC_onCloseButton()
|
|
{
|
|
HUDService.animate(this.hudId, ANIMATE_OUT, function() {
|
|
HUDService.deactivateHUDForContext(this.tab, true);
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* The clear output button handler.
|
|
*/
|
|
onClearButton: function WC_onClearButton()
|
|
{
|
|
this.ui.jsterm.clearOutput(true);
|
|
this.chromeWindow.DeveloperToolbar.resetErrorsCount(this.tab);
|
|
},
|
|
|
|
/**
|
|
* Setup the message manager used to communicate with the Web Console content
|
|
* script. This method loads the content script, adds the message listeners
|
|
* and initializes the connection to the content script.
|
|
*
|
|
* @private
|
|
*/
|
|
_setupMessageManager: function WC__setupMessageManager()
|
|
{
|
|
this.messageManager.loadFrameScript(CONTENT_SCRIPT_URL, true);
|
|
|
|
this._messageListeners.forEach(function(aName) {
|
|
this.messageManager.addMessageListener(aName, this.ui);
|
|
}, this);
|
|
|
|
let message = {
|
|
features: ["ConsoleAPI", "JSTerm", "PageError", "NetworkMonitor",
|
|
"LocationChange"],
|
|
cachedMessages: ["ConsoleAPI", "PageError"],
|
|
NetworkMonitor: { monitorFileActivity: true },
|
|
JSTerm: { notifyNonNativeConsoleAPI: true },
|
|
preferences: {
|
|
"NetworkMonitor.saveRequestAndResponseBodies":
|
|
this.ui.saveRequestAndResponseBodies,
|
|
},
|
|
};
|
|
|
|
this.sendMessageToContent("WebConsole:Init", message);
|
|
},
|
|
|
|
/**
|
|
* 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);
|
|
},
|
|
|
|
/**
|
|
* Handler for messages that have an associated callback function. The
|
|
* this.sendMessageToContent() allows one to provide a function to be invoked
|
|
* when the content script replies to the message previously sent. This is the
|
|
* method that invokes the callback.
|
|
*
|
|
* @see this.sendMessageToContent
|
|
* @private
|
|
* @param object aResponse
|
|
* Message object received from the content script.
|
|
*/
|
|
_receiveMessageWithCallback:
|
|
function WC__receiveMessageWithCallback(aResponse)
|
|
{
|
|
if (aResponse.id in this._asyncRequests) {
|
|
let request = this._asyncRequests[aResponse.id];
|
|
request.callback(aResponse, request.message);
|
|
delete this._asyncRequests[aResponse.id];
|
|
}
|
|
else {
|
|
Cu.reportError("receiveMessageWithCallback response for stale request " +
|
|
"ID " + aResponse.id);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Send a message to the content script.
|
|
*
|
|
* @param string aName
|
|
* The name of the message you want to send.
|
|
*
|
|
* @param object aMessage
|
|
* The message object you want to send. This object needs to have no
|
|
* cyclic references and it needs to be JSON-stringifiable.
|
|
*
|
|
* @param function [aCallback]
|
|
* Optional function you want to have called when the content script
|
|
* replies to your message. Your callback receives two arguments:
|
|
* (1) the response object from the content script and (2) the message
|
|
* you sent to the content script (which is aMessage here).
|
|
*/
|
|
sendMessageToContent:
|
|
function WC_sendMessageToContent(aName, aMessage, aCallback)
|
|
{
|
|
aMessage.hudId = this.hudId;
|
|
if (!("id" in aMessage)) {
|
|
aMessage.id = "HUDChrome-" + HUDService.sequenceId();
|
|
}
|
|
|
|
if (aCallback) {
|
|
this._asyncRequests[aMessage.id] = {
|
|
name: aName,
|
|
message: aMessage,
|
|
callback: aCallback,
|
|
};
|
|
}
|
|
this.messageManager.sendAsyncMessage(aName, aMessage);
|
|
},
|
|
|
|
/**
|
|
* Handler for the "WebConsole:LocationChange" message. If the Web Console is
|
|
* opened in a panel the panel title is updated.
|
|
*
|
|
* @param object aMessage
|
|
* The message received from the content script. It needs to hold two
|
|
* properties: location and title.
|
|
*/
|
|
onLocationChange: function WC_onLocationChange(aMessage)
|
|
{
|
|
this.contentLocation = aMessage.location;
|
|
if (this.consolePanel) {
|
|
this.consolePanel.label = this.getPanelTitle();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Alias for the WebConsoleFrame.setFilterState() method.
|
|
* @see webconsole.js::WebConsoleFrame.setFilterState()
|
|
*/
|
|
setFilterState: function WC_setFilterState()
|
|
{
|
|
this.ui && this.ui.setFilterState.apply(this.ui, arguments);
|
|
},
|
|
|
|
/**
|
|
* Open a link in a new tab.
|
|
*
|
|
* @param string aLink
|
|
* The URL you want to open in a new tab.
|
|
*/
|
|
openLink: function WC_openLink(aLink)
|
|
{
|
|
this.chromeWindow.openUILinkIn(aLink, "tab");
|
|
},
|
|
|
|
/**
|
|
* Destroy the object. Call this method to avoid memory leaks when the Web
|
|
* Console is closed.
|
|
*/
|
|
destroy: function WC_destroy()
|
|
{
|
|
this.sendMessageToContent("WebConsole:Destroy", {});
|
|
|
|
this._messageListeners.forEach(function(aName) {
|
|
this.messageManager.removeMessageListener(aName, this.ui);
|
|
}, this);
|
|
|
|
// Make sure that the console panel does not try to call
|
|
// deactivateHUDForContext() again.
|
|
this.consoleWindowUnregisterOnHide = false;
|
|
|
|
let popupset = this.mainPopupSet;
|
|
let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
|
|
for (let panel of panels) {
|
|
if (panel != this.consolePanel) {
|
|
panel.hidePopup();
|
|
}
|
|
}
|
|
|
|
if (this.ui) {
|
|
this.ui.destroy();
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (this.iframe.parentNode) {
|
|
this.iframe.parentNode.removeChild(this.iframe);
|
|
}
|
|
|
|
if (this.splitter.parentNode) {
|
|
this.splitter.parentNode.removeChild(this.splitter);
|
|
}
|
|
},
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// HeadsUpDisplayUICommands
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
var HeadsUpDisplayUICommands = {
|
|
refreshCommand: function UIC_refreshCommand() {
|
|
var window = HUDService.currentContext();
|
|
if (!window) {
|
|
return;
|
|
}
|
|
|
|
let command = window.document.getElementById("Tools:WebConsole");
|
|
if (this.getOpenHUD() != null) {
|
|
command.setAttribute("checked", true);
|
|
} else {
|
|
command.setAttribute("checked", false);
|
|
}
|
|
},
|
|
|
|
toggleHUD: function UIC_toggleHUD() {
|
|
var window = HUDService.currentContext();
|
|
var gBrowser = window.gBrowser;
|
|
var linkedBrowser = gBrowser.selectedTab.linkedBrowser;
|
|
var tabId = gBrowser.getNotificationBox(linkedBrowser).getAttribute("id");
|
|
var hudId = "hud_" + tabId;
|
|
var ownerDocument = gBrowser.selectedTab.ownerDocument;
|
|
var hud = ownerDocument.getElementById(hudId);
|
|
var hudRef = HUDService.hudReferences[hudId];
|
|
|
|
if (hudRef && hud) {
|
|
if (hudRef.consolePanel) {
|
|
hudRef.consolePanel.hidePopup();
|
|
}
|
|
else {
|
|
HUDService.storeHeight(hudId);
|
|
|
|
HUDService.animate(hudId, ANIMATE_OUT, function() {
|
|
// If the user closes the console while the console is animating away,
|
|
// then these callbacks will queue up, but all the callbacks after the
|
|
// first will have no console to operate on. This test handles this
|
|
// case gracefully.
|
|
if (ownerDocument.getElementById(hudId)) {
|
|
HUDService.deactivateHUDForContext(gBrowser.selectedTab, true);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
else {
|
|
HUDService.activateHUDForContext(gBrowser.selectedTab, true);
|
|
HUDService.animate(hudId, ANIMATE_IN);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Find the hudId for the active chrome window.
|
|
* @return string|null
|
|
* The hudId or null if the active chrome window 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()
|
|
{
|
|
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");
|
|
},
|
|
};
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "HUDService", function () {
|
|
return new HUD_SERVICE();
|
|
});
|
|
|